Merge Chromium src@r53293
Change-Id: Ia79acf8670f385cee48c45b0a75371d8e950af34
diff --git a/net/base/address_family.h b/net/base/address_family.h
index 065927f..fee0584 100644
--- a/net/base/address_family.h
+++ b/net/base/address_family.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -15,6 +15,13 @@
ADDRESS_FAMILY_IPV6, // AF_INET6
};
-} // namesapce net
+// HostResolverFlags is a bitflag enum wrapper around the addrinfo.ai_flags
+// supported by host resolver procedures.
+enum {
+ HOST_RESOLVER_CANONNAME = 1 << 0, // 0x1, AI_CANONNAME
+};
+typedef int HostResolverFlags;
+
+} // namespace net
#endif // NET_BASE_ADDRESS_FAMILY_H_
diff --git a/net/base/address_list.cc b/net/base/address_list.cc
index 93ec009..1c97311 100644
--- a/net/base/address_list.cc
+++ b/net/base/address_list.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -7,15 +7,28 @@
#include <stdlib.h>
#include "base/logging.h"
+#include "net/base/net_util.h"
#include "net/base/sys_addrinfo.h"
namespace net {
namespace {
-// Make a deep copy of |info|. This copy should be deleted using
+char* do_strdup(const char* src) {
+#if defined(OS_WIN)
+ return _strdup(src);
+#else
+ return strdup(src);
+#endif
+}
+
+// Make a copy of |info| (the dynamically-allocated parts are copied as well).
+// If |recursive| is true, chained entries via ai_next are copied too.
+// Copy returned by this function should be deleted using
// DeleteCopyOfAddrinfo(), and NOT freeaddrinfo().
-struct addrinfo* CreateCopyOfAddrinfo(const struct addrinfo* info) {
+struct addrinfo* CreateCopyOfAddrinfo(const struct addrinfo* info,
+ bool recursive) {
+ DCHECK(info);
struct addrinfo* copy = new addrinfo;
// Copy all the fields (some of these are pointers, we will fix that next).
@@ -23,11 +36,7 @@
// ai_canonname is a NULL-terminated string.
if (info->ai_canonname) {
-#ifdef OS_WIN
- copy->ai_canonname = _strdup(info->ai_canonname);
-#else
- copy->ai_canonname = strdup(info->ai_canonname);
-#endif
+ copy->ai_canonname = do_strdup(info->ai_canonname);
}
// ai_addr is a buffer of length ai_addrlen.
@@ -37,14 +46,17 @@
}
// Recursive copy.
- if (info->ai_next)
- copy->ai_next = CreateCopyOfAddrinfo(info->ai_next);
+ if (recursive && info->ai_next)
+ copy->ai_next = CreateCopyOfAddrinfo(info->ai_next, recursive);
+ else
+ copy->ai_next = NULL;
return copy;
}
// Free an addrinfo that was created by CreateCopyOfAddrinfo().
void FreeMyAddrinfo(struct addrinfo* info) {
+ DCHECK(info);
if (info->ai_canonname)
free(info->ai_canonname); // Allocated by strdup.
@@ -60,28 +72,11 @@
FreeMyAddrinfo(next);
}
-// Returns the address to port field in |info|.
-uint16* GetPortField(const struct addrinfo* info) {
- if (info->ai_family == AF_INET) {
- DCHECK_EQ(sizeof(sockaddr_in), info->ai_addrlen);
- struct sockaddr_in* sockaddr =
- reinterpret_cast<struct sockaddr_in*>(info->ai_addr);
- return &sockaddr->sin_port;
- } else if (info->ai_family == AF_INET6) {
- DCHECK_EQ(sizeof(sockaddr_in6), info->ai_addrlen);
- struct sockaddr_in6* sockaddr =
- reinterpret_cast<struct sockaddr_in6*>(info->ai_addr);
- return &sockaddr->sin6_port;
- } else {
- NOTREACHED();
- return NULL;
- }
-}
-
// Assign the port for all addresses in the list.
void SetPortRecursive(struct addrinfo* info, int port) {
- uint16* port_field = GetPortField(info);
- *port_field = htons(port);
+ uint16* port_field = GetPortFieldFromAddrinfo(info);
+ if (port_field)
+ *port_field = htons(port);
// Assign recursively.
if (info->ai_next)
@@ -94,8 +89,27 @@
data_ = new Data(head, true /*is_system_created*/);
}
-void AddressList::Copy(const struct addrinfo* head) {
- data_ = new Data(CreateCopyOfAddrinfo(head), false /*is_system_created*/);
+void AddressList::Copy(const struct addrinfo* head, bool recursive) {
+ data_ = new Data(CreateCopyOfAddrinfo(head, recursive),
+ false /*is_system_created*/);
+}
+
+void AddressList::Append(const struct addrinfo* head) {
+ DCHECK(head);
+ struct addrinfo* new_head;
+ if (data_->is_system_created) {
+ new_head = CreateCopyOfAddrinfo(data_->head, true);
+ data_ = new Data(new_head, false /*is_system_created*/);
+ } else {
+ new_head = data_->head;
+ }
+
+ // Find the end of current linked list and append new data there.
+ struct addrinfo* copy_ptr = new_head;
+ while (copy_ptr->ai_next)
+ copy_ptr = copy_ptr->ai_next;
+ DCHECK(!head->ai_canonname);
+ copy_ptr->ai_next = CreateCopyOfAddrinfo(head, true);
}
void AddressList::SetPort(int port) {
@@ -103,8 +117,15 @@
}
int AddressList::GetPort() const {
- uint16* port_field = GetPortField(data_->head);
- return ntohs(*port_field);
+ return GetPortFromAddrinfo(data_->head);
+}
+
+bool AddressList::GetCanonicalName(std::string* canonical_name) const {
+ DCHECK(canonical_name);
+ if (!data_ || !data_->head->ai_canonname)
+ return false;
+ canonical_name->assign(data_->head->ai_canonname);
+ return true;
}
void AddressList::SetFrom(const AddressList& src, int port) {
@@ -113,7 +134,7 @@
*this = src;
} else {
// Otherwise we need to make a copy in order to change the port number.
- Copy(src.head());
+ Copy(src.head(), true);
SetPort(port);
}
}
@@ -123,25 +144,54 @@
}
// static
-AddressList AddressList::CreateIPv6Address(unsigned char data[16]) {
+AddressList AddressList::CreateIPv4Address(unsigned char data[4],
+ const std::string& canonical_name) {
struct addrinfo* ai = new addrinfo;
memset(ai, 0, sizeof(addrinfo));
-
- ai->ai_family = AF_INET6;
+ ai->ai_family = AF_INET;
ai->ai_socktype = SOCK_STREAM;
- ai->ai_addrlen = sizeof(struct sockaddr_in6);
+ const size_t sockaddr_in_size = sizeof(struct sockaddr_in);
+ ai->ai_addrlen = sockaddr_in_size;
+ if (!canonical_name.empty())
+ ai->ai_canonname = do_strdup(canonical_name.c_str());
- struct sockaddr_in6* addr6 = reinterpret_cast<struct sockaddr_in6*>(
- new char[ai->ai_addrlen]);
- memset(addr6, 0, sizeof(struct sockaddr_in6));
-
- ai->ai_addr = reinterpret_cast<struct sockaddr*>(addr6);
- addr6->sin6_family = AF_INET6;
- memcpy(&addr6->sin6_addr, data, 16);
+ struct sockaddr_in* addr = reinterpret_cast<struct sockaddr_in*>(
+ new char[sockaddr_in_size]);
+ memset(addr, 0, sockaddr_in_size);
+ addr->sin_family = AF_INET;
+ memcpy(&addr->sin_addr, data, 4);
+ ai->ai_addr = reinterpret_cast<struct sockaddr*>(addr);
return AddressList(new Data(ai, false /*is_system_created*/));
}
+// static
+AddressList AddressList::CreateIPv6Address(unsigned char data[16],
+ const std::string& canonical_name) {
+ struct addrinfo* ai = new addrinfo;
+ memset(ai, 0, sizeof(addrinfo));
+ ai->ai_family = AF_INET6;
+ ai->ai_socktype = SOCK_STREAM;
+ const size_t sockaddr_in6_size = sizeof(struct sockaddr_in6);
+ ai->ai_addrlen = sockaddr_in6_size;
+ if (!canonical_name.empty())
+ ai->ai_canonname = do_strdup(canonical_name.c_str());
+
+ struct sockaddr_in6* addr6 = reinterpret_cast<struct sockaddr_in6*>(
+ new char[sockaddr_in6_size]);
+ memset(addr6, 0, sockaddr_in6_size);
+ addr6->sin6_family = AF_INET6;
+ memcpy(&addr6->sin6_addr, data, 16);
+ ai->ai_addr = reinterpret_cast<struct sockaddr*>(addr6);
+
+ return AddressList(new Data(ai, false /*is_system_created*/));
+}
+
+AddressList::Data::Data(struct addrinfo* ai, bool is_system_created)
+ : head(ai), is_system_created(is_system_created) {
+ DCHECK(head);
+}
+
AddressList::Data::~Data() {
// Call either freeaddrinfo(head), or FreeMyAddrinfo(head), depending who
// created the data.
diff --git a/net/base/address_list.h b/net/base/address_list.h
index 3087472..ce17645 100644
--- a/net/base/address_list.h
+++ b/net/base/address_list.h
@@ -1,10 +1,12 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
#ifndef NET_BASE_ADDRESS_LIST_H_
#define NET_BASE_ADDRESS_LIST_H_
+#include <string>
+
#include "base/ref_counted.h"
struct addrinfo;
@@ -23,8 +25,14 @@
// object.
void Adopt(struct addrinfo* head);
- // Copies the given addrinfo rather than adopting it.
- void Copy(const struct addrinfo* head);
+ // Copies the given addrinfo rather than adopting it. If |recursive| is true,
+ // all linked struct addrinfos will be copied as well. Otherwise only the head
+ // will be copied, and the rest of linked entries will be ignored.
+ void Copy(const struct addrinfo* head, bool recursive);
+
+ // Appends a copy of |head| and all its linked addrinfos to the stored
+ // addrinfo.
+ void Append(const struct addrinfo* head);
// Sets the port of all addresses in the list to |port| (that is the
// sin[6]_port field for the sockaddrs).
@@ -40,20 +48,37 @@
// a reference to |src|'s data.) Otherwise we will make a copy.
void SetFrom(const AddressList& src, int port);
+ // Gets the canonical name for the address.
+ // If the canonical name exists, |*canonical_name| is filled in with the
+ // value and true is returned. If it does not exist, |*canonical_name| is
+ // not altered and false is returned.
+ // |canonical_name| must be a non-null value.
+ bool GetCanonicalName(std::string* canonical_name) const;
+
// Clears all data from this address list. This leaves the list in the same
// empty state as when first constructed.
void Reset();
- // Used by unit-tests to manually set the TCP socket address.
- static AddressList CreateIPv6Address(unsigned char data[16]);
+ // Used by unit-tests to manually create an IPv4 AddressList. |data| should
+ // be an IPv4 address in network order (big endian).
+ // If |canonical_name| is non-empty, it will be duplicated in the
+ // ai_canonname field of the addrinfo struct.
+ static AddressList CreateIPv4Address(unsigned char data[4],
+ const std::string& canonical_name);
+
+ // Used by unit-tests to manually create an IPv6 AddressList. |data| should
+ // be an IPv6 address in network order (big endian).
+ // If |canonical_name| is non-empty, it will be duplicated in the
+ // ai_canonname field of the addrinfo struct.
+ static AddressList CreateIPv6Address(unsigned char data[16],
+ const std::string& canonical_name);
// Get access to the head of the addrinfo list.
const struct addrinfo* head() const { return data_->head; }
private:
struct Data : public base::RefCountedThreadSafe<Data> {
- Data(struct addrinfo* ai, bool is_system_created)
- : head(ai), is_system_created(is_system_created) {}
+ Data(struct addrinfo* ai, bool is_system_created);
struct addrinfo* head;
// Indicates which free function to use for |head|.
diff --git a/net/base/address_list_net_log_param.cc b/net/base/address_list_net_log_param.cc
new file mode 100644
index 0000000..05b1a0d
--- /dev/null
+++ b/net/base/address_list_net_log_param.cc
@@ -0,0 +1,30 @@
+// 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 "net/base/address_list_net_log_param.h"
+
+#include "base/values.h"
+#include "net/base/net_util.h"
+#include "net/base/sys_addrinfo.h"
+
+namespace net {
+
+AddressListNetLogParam::AddressListNetLogParam(const AddressList& address_list)
+ : address_list_(address_list) {
+}
+
+Value* AddressListNetLogParam::ToValue() const {
+ DictionaryValue* dict = new DictionaryValue();
+ ListValue* list = new ListValue();
+
+ for (const addrinfo* head = address_list_.head();
+ head != NULL ; head = head->ai_next) {
+ list->Append(Value::CreateStringValue(NetAddressToStringWithPort(head)));
+ }
+
+ dict->Set(L"address_list", list);
+ return dict;
+}
+
+} // namespace
diff --git a/net/base/address_list_net_log_param.h b/net/base/address_list_net_log_param.h
new file mode 100644
index 0000000..2a56961
--- /dev/null
+++ b/net/base/address_list_net_log_param.h
@@ -0,0 +1,28 @@
+// 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.
+
+#ifndef NET_BASE_ADDRESS_LIST_NET_LOG_PARAM_H_
+#define NET_BASE_ADDRESS_LIST_NET_LOG_PARAM_H_
+
+#include "net/base/address_list.h"
+#include "net/base/net_log.h"
+
+namespace net {
+
+// NetLog parameter to describe an address list.
+// Note that AddressList uses ref-counted data, so this doesn't introduce
+// much of a memory overhead.
+class AddressListNetLogParam : public NetLog::EventParameters {
+ public:
+ explicit AddressListNetLogParam(const AddressList& address_list);
+
+ virtual Value* ToValue() const;
+
+ private:
+ AddressList address_list_;
+};
+
+} // namespace net
+
+#endif // NET_BASE_ADDRESS_LIST_NET_LOG_PARAM_H_
diff --git a/net/base/address_list_unittest.cc b/net/base/address_list_unittest.cc
index d440c15..6e38c7d 100644
--- a/net/base/address_list_unittest.cc
+++ b/net/base/address_list_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -7,6 +7,7 @@
#include "base/string_util.h"
#include "net/base/host_resolver_proc.h"
#include "net/base/net_util.h"
+#include "net/base/sys_addrinfo.h"
#if defined(OS_WIN)
#include "net/base/winsock_init.h"
#endif
@@ -15,20 +16,30 @@
namespace {
// Use getaddrinfo() to allocate an addrinfo structure.
-void CreateAddressList(net::AddressList* addrlist, int port) {
+void CreateAddressList(const std::string& hostname,
+ int port,
+ net::AddressList* addrlist) {
#if defined(OS_WIN)
net::EnsureWinsockInit();
#endif
- int rv = SystemHostResolverProc("192.168.1.1",
+ int rv = SystemHostResolverProc(hostname,
net::ADDRESS_FAMILY_UNSPECIFIED,
- addrlist);
+ 0,
+ addrlist, NULL);
EXPECT_EQ(0, rv);
addrlist->SetPort(port);
}
+void CreateLongAddressList(net::AddressList* addrlist, int port) {
+ CreateAddressList("192.168.1.1", port, addrlist);
+ net::AddressList second_list;
+ CreateAddressList("192.168.1.2", port, &second_list);
+ addrlist->Append(second_list.head());
+}
+
TEST(AddressListTest, GetPort) {
net::AddressList addrlist;
- CreateAddressList(&addrlist, 81);
+ CreateAddressList("192.168.1.1", 81, &addrlist);
EXPECT_EQ(81, addrlist.GetPort());
addrlist.SetPort(83);
@@ -37,7 +48,7 @@
TEST(AddressListTest, Assignment) {
net::AddressList addrlist1;
- CreateAddressList(&addrlist1, 85);
+ CreateAddressList("192.168.1.1", 85, &addrlist1);
EXPECT_EQ(85, addrlist1.GetPort());
// Should reference the same data as addrlist1 -- so when we change addrlist1
@@ -50,13 +61,15 @@
EXPECT_EQ(80, addrlist2.GetPort());
}
-TEST(AddressListTest, Copy) {
+TEST(AddressListTest, CopyRecursive) {
net::AddressList addrlist1;
- CreateAddressList(&addrlist1, 85);
+ CreateLongAddressList(&addrlist1, 85);
EXPECT_EQ(85, addrlist1.GetPort());
net::AddressList addrlist2;
- addrlist2.Copy(addrlist1.head());
+ addrlist2.Copy(addrlist1.head(), true);
+
+ ASSERT_TRUE(addrlist2.head()->ai_next != NULL);
// addrlist1 is the same as addrlist2 at this point.
EXPECT_EQ(85, addrlist1.GetPort());
@@ -70,4 +83,93 @@
EXPECT_EQ(90, addrlist2.GetPort());
}
+TEST(AddressListTest, CopyNonRecursive) {
+ net::AddressList addrlist1;
+ CreateLongAddressList(&addrlist1, 85);
+ EXPECT_EQ(85, addrlist1.GetPort());
+
+ net::AddressList addrlist2;
+ addrlist2.Copy(addrlist1.head(), false);
+
+ ASSERT_TRUE(addrlist2.head()->ai_next == NULL);
+
+ // addrlist1 is the same as addrlist2 at this point.
+ EXPECT_EQ(85, addrlist1.GetPort());
+ EXPECT_EQ(85, addrlist2.GetPort());
+
+ // Changes to addrlist1 are not reflected in addrlist2.
+ addrlist1.SetPort(70);
+ addrlist2.SetPort(90);
+
+ EXPECT_EQ(70, addrlist1.GetPort());
+ EXPECT_EQ(90, addrlist2.GetPort());
+}
+
+TEST(AddressListTest, Append) {
+ net::AddressList addrlist1;
+ CreateAddressList("192.168.1.1", 11, &addrlist1);
+ EXPECT_EQ(11, addrlist1.GetPort());
+ net::AddressList addrlist2;
+ CreateAddressList("192.168.1.2", 12, &addrlist2);
+ EXPECT_EQ(12, addrlist2.GetPort());
+
+ ASSERT_TRUE(addrlist1.head()->ai_next == NULL);
+ addrlist1.Append(addrlist2.head());
+ ASSERT_TRUE(addrlist1.head()->ai_next != NULL);
+
+ net::AddressList addrlist3;
+ addrlist3.Copy(addrlist1.head()->ai_next, false);
+ EXPECT_EQ(12, addrlist3.GetPort());
+}
+
+static const char* kCanonicalHostname = "canonical.bar.com";
+
+TEST(AddressListTest, Canonical) {
+ // Create an addrinfo with a canonical name.
+ sockaddr_in address;
+ // The contents of address do not matter for this test,
+ // so just zero-ing them out for consistency.
+ memset(&address, 0x0, sizeof(address));
+ struct addrinfo ai;
+ memset(&ai, 0x0, sizeof(ai));
+ ai.ai_family = AF_INET;
+ ai.ai_socktype = SOCK_STREAM;
+ ai.ai_addrlen = sizeof(address);
+ ai.ai_addr = reinterpret_cast<sockaddr*>(&address);
+ ai.ai_canonname = const_cast<char *>(kCanonicalHostname);
+
+ // Copy the addrinfo struct into an AddressList object and
+ // make sure it seems correct.
+ net::AddressList addrlist1;
+ addrlist1.Copy(&ai, true);
+ const struct addrinfo* addrinfo1 = addrlist1.head();
+ EXPECT_TRUE(addrinfo1 != NULL);
+ EXPECT_TRUE(addrinfo1->ai_next == NULL);
+ std::string canon_name1;
+ EXPECT_TRUE(addrlist1.GetCanonicalName(&canon_name1));
+ EXPECT_EQ("canonical.bar.com", canon_name1);
+
+ // Copy the AddressList to another one.
+ net::AddressList addrlist2;
+ addrlist2.Copy(addrinfo1, true);
+ const struct addrinfo* addrinfo2 = addrlist2.head();
+ EXPECT_TRUE(addrinfo2 != NULL);
+ EXPECT_TRUE(addrinfo2->ai_next == NULL);
+ EXPECT_TRUE(addrinfo2->ai_canonname != NULL);
+ EXPECT_NE(addrinfo1, addrinfo2);
+ EXPECT_NE(addrinfo1->ai_canonname, addrinfo2->ai_canonname);
+ std::string canon_name2;
+ EXPECT_TRUE(addrlist2.GetCanonicalName(&canon_name2));
+ EXPECT_EQ("canonical.bar.com", canon_name2);
+
+ // Make sure that GetCanonicalName correctly returns false
+ // when ai_canonname is NULL.
+ ai.ai_canonname = NULL;
+ net::AddressList addrlist_no_canon;
+ addrlist_no_canon.Copy(&ai, true);
+ std::string canon_name3 = "blah";
+ EXPECT_FALSE(addrlist_no_canon.GetCanonicalName(&canon_name3));
+ EXPECT_EQ("blah", canon_name3);
+}
+
} // namespace
diff --git a/net/base/auth.h b/net/base/auth.h
index ec31bfc..1704e5b 100644
--- a/net/base/auth.h
+++ b/net/base/auth.h
@@ -16,6 +16,17 @@
class AuthChallengeInfo :
public base::RefCountedThreadSafe<AuthChallengeInfo> {
public:
+ bool operator==(const AuthChallengeInfo& that) const {
+ return (this->is_proxy == that.is_proxy &&
+ this->host_and_port == that.host_and_port &&
+ this->scheme == that.scheme &&
+ this->realm == that.realm);
+ }
+
+ bool operator!=(const AuthChallengeInfo& that) const {
+ return !(*this == that);
+ }
+
bool is_proxy; // true for Proxy-Authenticate, false for WWW-Authenticate.
std::wstring host_and_port; // <host>:<port> of the server asking for auth
// (could be the proxy).
diff --git a/net/base/cache_type.h b/net/base/cache_type.h
index 341ce7a..0823389 100644
--- a/net/base/cache_type.h
+++ b/net/base/cache_type.h
@@ -12,6 +12,7 @@
DISK_CACHE, // Disk is used as the backing storage.
MEMORY_CACHE, // Data is stored only in memory.
MEDIA_CACHE, // Optimized to handle media files.
+ APP_CACHE // Backing store for an AppCache.
};
} // namespace disk_cache
diff --git a/net/base/capturing_net_log.cc b/net/base/capturing_net_log.cc
new file mode 100644
index 0000000..08f3b8d
--- /dev/null
+++ b/net/base/capturing_net_log.cc
@@ -0,0 +1,43 @@
+// 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 "net/base/capturing_net_log.h"
+
+namespace net {
+
+CapturingNetLog::CapturingNetLog(size_t max_num_entries)
+ : next_id_(0), max_num_entries_(max_num_entries) {
+}
+
+void CapturingNetLog::AddEntry(EventType type,
+ const base::TimeTicks& time,
+ const Source& source,
+ EventPhase phase,
+ EventParameters* extra_parameters) {
+ Entry entry(type, time, source, phase, extra_parameters);
+ if (entries_.size() + 1 < max_num_entries_)
+ entries_.push_back(entry);
+}
+
+uint32 CapturingNetLog::NextID() {
+ return next_id_++;
+}
+
+void CapturingNetLog::Clear() {
+ entries_.clear();
+}
+
+void CapturingBoundNetLog::Clear() {
+ capturing_net_log_->Clear();
+}
+
+void CapturingBoundNetLog::AppendTo(const BoundNetLog& net_log) const {
+ for (size_t i = 0; i < entries().size(); ++i) {
+ const CapturingNetLog::Entry& entry = entries()[i];
+ net_log.AddEntryWithTime(entry.type, entry.time, entry.phase,
+ entry.extra_parameters);
+ }
+}
+
+} // namespace net
diff --git a/net/base/capturing_net_log.h b/net/base/capturing_net_log.h
new file mode 100644
index 0000000..cad2d26
--- /dev/null
+++ b/net/base/capturing_net_log.h
@@ -0,0 +1,110 @@
+// 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.
+
+#ifndef NET_BASE_CAPTURING_NET_LOG_H_
+#define NET_BASE_CAPTURING_NET_LOG_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "base/time.h"
+#include "net/base/net_log.h"
+
+namespace net {
+
+// CapturingNetLog is an implementation of NetLog that saves messages to a
+// bounded buffer.
+class CapturingNetLog : public NetLog {
+ public:
+ struct Entry {
+ Entry(EventType type,
+ const base::TimeTicks& time,
+ Source source,
+ EventPhase phase,
+ EventParameters* extra_parameters)
+ : type(type), time(time), source(source), phase(phase),
+ extra_parameters(extra_parameters) {
+ }
+
+ EventType type;
+ base::TimeTicks time;
+ Source source;
+ EventPhase phase;
+ scoped_refptr<EventParameters> extra_parameters;
+ };
+
+ // Ordered set of entries that were logged.
+ typedef std::vector<Entry> EntryList;
+
+ enum { kUnbounded = -1 };
+
+ // Creates a CapturingNetLog that logs a maximum of |max_num_entries|
+ // messages.
+ explicit CapturingNetLog(size_t max_num_entries);
+
+ // NetLog implementation:
+ virtual void AddEntry(EventType type,
+ const base::TimeTicks& time,
+ const Source& source,
+ EventPhase phase,
+ EventParameters* extra_parameters);
+ virtual uint32 NextID();
+ virtual bool HasListener() const { return true; }
+
+ // Returns the list of all entries in the log.
+ const EntryList& entries() const { return entries_; }
+
+ void Clear();
+
+ private:
+ uint32 next_id_;
+ size_t max_num_entries_;
+ EntryList entries_;
+
+ DISALLOW_COPY_AND_ASSIGN(CapturingNetLog);
+};
+
+// Helper class that exposes a similar API as BoundNetLog, but uses a
+// CapturingNetLog rather than the more generic NetLog.
+//
+// CapturingBoundNetLog can easily be converted to a BoundNetLog using the
+// bound() method.
+class CapturingBoundNetLog {
+ public:
+ CapturingBoundNetLog(const NetLog::Source& source, CapturingNetLog* net_log)
+ : source_(source), capturing_net_log_(net_log) {
+ }
+
+ explicit CapturingBoundNetLog(size_t max_num_entries)
+ : capturing_net_log_(new CapturingNetLog(max_num_entries)) {}
+
+ // The returned BoundNetLog is only valid while |this| is alive.
+ BoundNetLog bound() const {
+ return BoundNetLog(source_, capturing_net_log_.get());
+ }
+
+ // Returns the list of all entries in the log.
+ const CapturingNetLog::EntryList& entries() const {
+ return capturing_net_log_->entries();
+ }
+
+ void Clear();
+
+ // Sends all of captured messages to |net_log|, using the same source ID
+ // as |net_log|.
+ void AppendTo(const BoundNetLog& net_log) const;
+
+ private:
+ NetLog::Source source_;
+ scoped_ptr<CapturingNetLog> capturing_net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(CapturingBoundNetLog);
+};
+
+} // namespace net
+
+#endif // NET_BASE_CAPTURING_NET_LOG_H_
+
diff --git a/net/base/cert_database.h b/net/base/cert_database.h
index e78c22e..31e3401 100644
--- a/net/base/cert_database.h
+++ b/net/base/cert_database.h
@@ -1,14 +1,16 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
#ifndef NET_BASE_CERT_DATABASE_H_
#define NET_BASE_CERT_DATABASE_H_
-#include "net/base/x509_certificate.h"
+#include "base/basictypes.h"
namespace net {
+class X509Certificate;
+
// This class provides functions to manipulate the local
// certificate store.
@@ -20,12 +22,16 @@
public:
CertDatabase();
- // Extract and Store User (Client) Certificate from a data blob.
- // Return true if successful.
- bool AddUserCert(const char* data, int len);
+ // Check whether this is a valid user cert that we have the private key for.
+ // Returns OK or a network error code such as ERR_CERT_CONTAINS_ERRORS.
+ int CheckUserCert(X509Certificate* cert);
+
+ // Store user (client) certificate. Assumes CheckUserCert has already passed.
+ // Returns OK, or ERR_ADD_USER_CERT_FAILED if there was a problem saving to
+ // the platform cert database, or possibly other network error codes.
+ int AddUserCert(X509Certificate* cert);
private:
- void Init();
DISALLOW_COPY_AND_ASSIGN(CertDatabase);
};
diff --git a/net/base/cert_database_mac.cc b/net/base/cert_database_mac.cc
index 5caf9db..b0afd50 100644
--- a/net/base/cert_database_mac.cc
+++ b/net/base/cert_database_mac.cc
@@ -1,24 +1,60 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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 "net/base/cert_database.h"
+#include <Security/Security.h>
+
+#include "base/crypto/cssm_init.h"
+#include "base/lock.h"
#include "base/logging.h"
+#include "net/base/net_errors.h"
+#include "net/base/x509_certificate.h"
namespace net {
CertDatabase::CertDatabase() {
- NOTIMPLEMENTED();
}
-bool CertDatabase::AddUserCert(const char* data, int len) {
- NOTIMPLEMENTED();
- return false;
+int CertDatabase::CheckUserCert(X509Certificate* cert) {
+ if (!cert)
+ return ERR_CERT_INVALID;
+ if (cert->HasExpired())
+ return ERR_CERT_DATE_INVALID;
+
+ // Verify the Keychain already has the corresponding private key:
+ SecIdentityRef identity = NULL;
+ OSStatus err = SecIdentityCreateWithCertificate(NULL, cert->os_cert_handle(),
+ &identity);
+ if (err == errSecItemNotFound) {
+ LOG(ERROR) << "CertDatabase couldn't find private key for user cert";
+ return ERR_NO_PRIVATE_KEY_FOR_CERT;
+ }
+ if (err != noErr || !identity) {
+ // TODO(snej): Map the error code more intelligently.
+ return ERR_CERT_INVALID;
+ }
+
+ CFRelease(identity);
+ return OK;
}
-void CertDatabase::Init() {
- NOTIMPLEMENTED();
+int CertDatabase::AddUserCert(X509Certificate* cert) {
+ OSStatus err;
+ {
+ AutoLock locked(base::GetMacSecurityServicesLock());
+ err = SecCertificateAddToKeychain(cert->os_cert_handle(), NULL);
+ }
+ switch (err) {
+ case noErr:
+ case errSecDuplicateItem:
+ return OK;
+ default:
+ LOG(ERROR) << "CertDatabase failed to add cert to keychain: " << err;
+ // TODO(snej): Map the error code more intelligently.
+ return ERR_ADD_USER_CERT_FAILED;
+ }
}
} // namespace net
diff --git a/net/base/cert_database_nss.cc b/net/base/cert_database_nss.cc
index e3c1a09..98930ff 100644
--- a/net/base/cert_database_nss.cc
+++ b/net/base/cert_database_nss.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -15,31 +15,20 @@
#include "base/logging.h"
#include "base/scoped_ptr.h"
#include "base/nss_util.h"
+#include "net/base/net_errors.h"
+#include "net/base/x509_certificate.h"
namespace net {
CertDatabase::CertDatabase() {
- Init();
+ base::EnsureNSSInit();
}
-bool CertDatabase::AddUserCert(const char* data, int len) {
- CERTCertificate* cert = NULL;
- PK11SlotInfo* slot = NULL;
- std::string nickname;
- bool is_success = true;
-
- // Make a copy of "data" since CERT_DecodeCertPackage
- // might modify it.
- char* data_copy = new char[len];
- memcpy(data_copy, data, len);
-
- // Parse into a certificate structure.
- cert = CERT_DecodeCertFromPackage(data_copy, len);
- delete [] data_copy;
- if (!cert) {
- LOG(ERROR) << "Couldn't create a temporary certificate";
- return false;
- }
+int CertDatabase::CheckUserCert(X509Certificate* cert_obj) {
+ if (!cert_obj)
+ return ERR_CERT_INVALID;
+ if (cert_obj->HasExpired())
+ return ERR_CERT_DATE_INVALID;
// Check if the private key corresponding to the certificate exist
// We shouldn't accept any random client certificate sent by a CA.
@@ -48,22 +37,25 @@
// also imports the certificate if the private key exists. This
// doesn't seem to be the case.
- slot = PK11_KeyForCertExists(cert, NULL, NULL);
+ CERTCertificate* cert = cert_obj->os_cert_handle();
+ PK11SlotInfo* slot = PK11_KeyForCertExists(cert, NULL, NULL);
if (!slot) {
LOG(ERROR) << "No corresponding private key in store";
- CERT_DestroyCertificate(cert);
- return false;
+ return ERR_NO_PRIVATE_KEY_FOR_CERT;
}
PK11_FreeSlot(slot);
- slot = NULL;
- // TODO(gauravsh): We also need to make sure another certificate
- // doesn't already exist for the same private key.
+ return OK;
+}
+
+int CertDatabase::AddUserCert(X509Certificate* cert_obj) {
+ CERTCertificate* cert = cert_obj->os_cert_handle();
+ PK11SlotInfo* slot = NULL;
+ std::string nickname;
// Create a nickname for this certificate.
// We use the scheme used by Firefox:
// --> <subject's common name>'s <issuer's common name> ID.
- //
std::string username, ca_name;
char* temp_username = CERT_GetCommonName(&cert->subject);
@@ -78,21 +70,19 @@
}
nickname = username + "'s " + ca_name + " ID";
- slot = PK11_ImportCertForKey(cert,
- const_cast<char*>(nickname.c_str()),
- NULL);
- if (slot) {
- PK11_FreeSlot(slot);
- } else {
- LOG(ERROR) << "Couldn't import user certificate.";
- is_success = false;
+ {
+ base::AutoNSSWriteLock lock;
+ slot = PK11_ImportCertForKey(cert,
+ const_cast<char*>(nickname.c_str()),
+ NULL);
}
- CERT_DestroyCertificate(cert);
- return is_success;
-}
-void CertDatabase::Init() {
- base::EnsureNSSInit();
+ if (!slot) {
+ LOG(ERROR) << "Couldn't import user certificate.";
+ return ERR_ADD_USER_CERT_FAILED;
+ }
+ PK11_FreeSlot(slot);
+ return OK;
}
} // namespace net
diff --git a/net/base/cert_database_win.cc b/net/base/cert_database_win.cc
index 5caf9db..4c5e8df 100644
--- a/net/base/cert_database_win.cc
+++ b/net/base/cert_database_win.cc
@@ -1,24 +1,56 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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 "net/base/cert_database.h"
-#include "base/logging.h"
+#include <windows.h>
+#include <wincrypt.h>
+#pragma comment(lib, "crypt32.lib")
+
+#include "net/base/net_errors.h"
+#include "net/base/x509_certificate.h"
namespace net {
CertDatabase::CertDatabase() {
- NOTIMPLEMENTED();
}
-bool CertDatabase::AddUserCert(const char* data, int len) {
- NOTIMPLEMENTED();
- return false;
+int CertDatabase::CheckUserCert(X509Certificate* cert) {
+ if (!cert)
+ return ERR_CERT_INVALID;
+ if (cert->HasExpired())
+ return ERR_CERT_DATE_INVALID;
+
+ // TODO(rsleevi): Should CRYPT_FIND_SILENT_KEYSET_FLAG be specified? A UI
+ // may be shown here / this call may block.
+ if (!CryptFindCertificateKeyProvInfo(cert->os_cert_handle(), 0, NULL))
+ return ERR_NO_PRIVATE_KEY_FOR_CERT;
+
+ return OK;
}
-void CertDatabase::Init() {
- NOTIMPLEMENTED();
+int CertDatabase::AddUserCert(X509Certificate* cert) {
+ // TODO(rsleevi): Would it be more appropriate to have the CertDatabase take
+ // construction parameters (Keychain filepath on Mac OS X, PKCS #11 slot on
+ // NSS, and Store Type / Path) here? For now, certs will be stashed into the
+ // user's personal store, which will not automatically mark them as trusted,
+ // but will allow them to be used for client auth.
+ HCERTSTORE cert_db = CertOpenSystemStore(NULL, L"MY");
+ if (!cert_db)
+ return ERR_ADD_USER_CERT_FAILED;
+
+ BOOL added = CertAddCertificateContextToStore(cert_db,
+ cert->os_cert_handle(),
+ CERT_STORE_ADD_USE_EXISTING,
+ NULL);
+
+ CertCloseStore(cert_db, 0);
+
+ if (!added)
+ return ERR_ADD_USER_CERT_FAILED;
+
+ return OK;
}
} // namespace net
diff --git a/net/base/cert_test_util.cc b/net/base/cert_test_util.cc
new file mode 100644
index 0000000..9fc6573
--- /dev/null
+++ b/net/base/cert_test_util.cc
@@ -0,0 +1,102 @@
+// 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 "net/base/cert_test_util.h"
+
+#include "build/build_config.h"
+
+#if defined(USE_NSS)
+#include <cert.h>
+#include "base/nss_util.h"
+#elif defined(OS_MACOSX)
+#include <Security/Security.h>
+#include "base/scoped_cftyperef.h"
+#endif
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "net/base/x509_certificate.h"
+
+namespace net {
+
+#if defined(USE_NSS)
+X509Certificate* LoadTemporaryRootCert(const FilePath& filename) {
+ base::EnsureNSSInit();
+
+ std::string rawcert;
+ if (!file_util::ReadFileToString(filename, &rawcert)) {
+ LOG(ERROR) << "Can't load certificate " << filename.value();
+ return NULL;
+ }
+
+ CERTCertificate *cert;
+ cert = CERT_DecodeCertFromPackage(const_cast<char *>(rawcert.c_str()),
+ rawcert.length());
+ if (!cert) {
+ LOG(ERROR) << "Can't convert certificate " << filename.value();
+ return NULL;
+ }
+
+ // TODO(port): remove this const_cast after NSS 3.12.3 is released
+ CERTCertTrust trust;
+ int rv = CERT_DecodeTrustString(&trust, const_cast<char *>("TCu,Cu,Tu"));
+ if (rv != SECSuccess) {
+ LOG(ERROR) << "Can't decode trust string";
+ CERT_DestroyCertificate(cert);
+ return NULL;
+ }
+
+ rv = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), cert, &trust);
+ if (rv != SECSuccess) {
+ LOG(ERROR) << "Can't change trust for certificate " << filename.value();
+ CERT_DestroyCertificate(cert);
+ return NULL;
+ }
+
+ X509Certificate* result = X509Certificate::CreateFromHandle(
+ cert,
+ X509Certificate::SOURCE_LONE_CERT_IMPORT,
+ X509Certificate::OSCertHandles());
+ CERT_DestroyCertificate(cert);
+ return result;
+}
+#endif
+
+#if defined(OS_MACOSX)
+X509Certificate* LoadTemporaryRootCert(const FilePath& filename) {
+ std::string rawcert;
+ if (!file_util::ReadFileToString(filename, &rawcert)) {
+ LOG(ERROR) << "Can't load certificate " << filename.value();
+ return NULL;
+ }
+
+ CFDataRef pem = CFDataCreate(kCFAllocatorDefault,
+ reinterpret_cast<const UInt8*>(rawcert.data()),
+ static_cast<CFIndex>(rawcert.size()));
+ if (!pem)
+ return NULL;
+ scoped_cftyperef<CFDataRef> scoped_pem(pem);
+
+ SecExternalFormat input_format = kSecFormatUnknown;
+ SecExternalItemType item_type = kSecItemTypeUnknown;
+ CFArrayRef cert_array = NULL;
+ if (SecKeychainItemImport(pem, NULL, &input_format, &item_type, 0, NULL, NULL,
+ &cert_array))
+ return NULL;
+ scoped_cftyperef<CFArrayRef> scoped_cert_array(cert_array);
+
+ if (!CFArrayGetCount(cert_array))
+ return NULL;
+
+ SecCertificateRef cert_ref = static_cast<SecCertificateRef>(
+ const_cast<void*>(CFArrayGetValueAtIndex(cert_array, 0)));
+
+ return X509Certificate::CreateFromHandle(cert_ref,
+ X509Certificate::SOURCE_LONE_CERT_IMPORT,
+ X509Certificate::OSCertHandles());
+}
+#endif
+
+} // namespace net
diff --git a/net/base/cert_test_util.h b/net/base/cert_test_util.h
new file mode 100644
index 0000000..a288774
--- /dev/null
+++ b/net/base/cert_test_util.h
@@ -0,0 +1,23 @@
+// 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.
+
+#ifndef NET_BASE_CERT_TEST_UTIL_H_
+#define NET_BASE_CERT_TEST_UTIL_H_
+
+#include "base/file_path.h"
+#include "build/build_config.h"
+
+namespace net {
+
+class X509Certificate;
+
+#if defined(USE_NSS) || defined(OS_MACOSX)
+// Loads and trusts a root CA certificate (stored in a file) temporarily.
+// TODO(wtc): Implement this function on Windows (http://crbug.com/8470).
+X509Certificate* LoadTemporaryRootCert(const FilePath& filename);
+#endif
+
+} // namespace net
+
+#endif // NET_BASE_CERT_TEST_UTIL_H_
diff --git a/net/base/completion_callback.h b/net/base/completion_callback.h
index 7a5655b..11a6b3a 100644
--- a/net/base/completion_callback.h
+++ b/net/base/completion_callback.h
@@ -5,7 +5,7 @@
#ifndef NET_BASE_COMPLETION_CALLBACK_H__
#define NET_BASE_COMPLETION_CALLBACK_H__
-#include "base/task.h"
+#include "base/callback.h"
namespace net {
diff --git a/net/base/connection_type_histograms.cc b/net/base/connection_type_histograms.cc
index 7326a92..de780f2 100644
--- a/net/base/connection_type_histograms.cc
+++ b/net/base/connection_type_histograms.cc
@@ -20,22 +20,18 @@
//
// Each histogram has an unused bucket at the end to allow seamless future
// expansion.
-void UpdateConnectionTypeHistograms(ConnectionType type, bool success) {
+void UpdateConnectionTypeHistograms(ConnectionType type) {
static bool had_connection_type[NUM_OF_CONNECTION_TYPES];
if (type >= 0 && type < NUM_OF_CONNECTION_TYPES) {
if (!had_connection_type[type]) {
had_connection_type[type] = true;
- UMA_HISTOGRAM_ENUMERATION("Net.HadConnectionType2",
+ UMA_HISTOGRAM_ENUMERATION("Net.HadConnectionType3",
type, NUM_OF_CONNECTION_TYPES);
}
- if (success)
- UMA_HISTOGRAM_ENUMERATION("Net.ConnectionTypeCount2",
- type, NUM_OF_CONNECTION_TYPES);
- else
- UMA_HISTOGRAM_ENUMERATION("Net.ConnectionTypeFailCount2",
- type, NUM_OF_CONNECTION_TYPES);
+ UMA_HISTOGRAM_ENUMERATION("Net.ConnectionTypeCount3",
+ type, NUM_OF_CONNECTION_TYPES);
} else {
NOTREACHED(); // Someone's logging an invalid type!
}
diff --git a/net/base/connection_type_histograms.h b/net/base/connection_type_histograms.h
index c8517ff..582f09d 100644
--- a/net/base/connection_type_histograms.h
+++ b/net/base/connection_type_histograms.h
@@ -33,8 +33,7 @@
};
// Update the connection type histograms. |type| is the connection type.
-// |success| is whether or not the connection was successful or not.
-void UpdateConnectionTypeHistograms(ConnectionType type, bool success);
+void UpdateConnectionTypeHistograms(ConnectionType type);
} // namespace net
diff --git a/net/base/cookie_monster.cc b/net/base/cookie_monster.cc
index 805bfc1..e79cf47 100644
--- a/net/base/cookie_monster.cc
+++ b/net/base/cookie_monster.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -53,6 +53,7 @@
#include "base/string_tokenizer.h"
#include "base/string_util.h"
#include "googleurl/src/gurl.h"
+#include "googleurl/src/url_canon.h"
#include "net/base/net_util.h"
#include "net/base/registry_controlled_domain.h"
@@ -66,21 +67,36 @@
using base::Time;
using base::TimeDelta;
+static const int kMinutesInTenYears = 10 * 365 * 24 * 60;
+
namespace net {
+namespace {
+
// Cookie garbage collection thresholds. Based off of the Mozilla defaults.
// It might seem scary to have a high purge value, but really it's not. You
// just make sure that you increase the max to cover the increase in purge,
// and we would have been purging the same amount of cookies. We're just
// going through the garbage collection process less often.
-static const size_t kNumCookiesPerHost = 70; // ~50 cookies
-static const size_t kNumCookiesPerHostPurge = 20;
-static const size_t kNumCookiesTotal = 3300; // ~3000 cookies
-static const size_t kNumCookiesTotalPurge = 300;
+const size_t kNumCookiesPerHost = 70; // ~50 cookies
+const size_t kNumCookiesPerHostPurge = 20;
+const size_t kNumCookiesTotal = 3300; // ~3000 cookies
+const size_t kNumCookiesTotalPurge = 300;
// Default minimum delay after updating a cookie's LastAccessDate before we
// will update it again.
-static const int kDefaultAccessUpdateThresholdSeconds = 60;
+const int kDefaultAccessUpdateThresholdSeconds = 60;
+
+// Comparator to sort cookies from highest creation date to lowest
+// creation date.
+struct OrderByCreationTimeDesc {
+ bool operator()(const CookieMonster::CookieMap::iterator& a,
+ const CookieMonster::CookieMap::iterator& b) const {
+ return a->second->CreationDate() > b->second->CreationDate();
+ }
+};
+
+} // namespace
// static
bool CookieMonster::enable_file_scheme_ = false;
@@ -90,19 +106,14 @@
enable_file_scheme_ = true;
}
-CookieMonster::CookieMonster()
- : initialized_(false),
- store_(NULL),
- last_access_threshold_(
- TimeDelta::FromSeconds(kDefaultAccessUpdateThresholdSeconds)) {
- SetDefaultCookieableSchemes();
-}
-
-CookieMonster::CookieMonster(PersistentCookieStore* store)
+CookieMonster::CookieMonster(PersistentCookieStore* store, Delegate* delegate)
: initialized_(false),
store_(store),
last_access_threshold_(
- TimeDelta::FromSeconds(kDefaultAccessUpdateThresholdSeconds)) {
+ TimeDelta::FromSeconds(kDefaultAccessUpdateThresholdSeconds)),
+ delegate_(delegate),
+ last_statistic_record_time_(Time::Now()) {
+ InitializeHistograms();
SetDefaultCookieableSchemes();
}
@@ -110,6 +121,59 @@
DeleteAll(false);
}
+// Initialize all histogram counter variables used in this class.
+//
+// Normal histogram usage involves using the macros defined in
+// histogram.h, which automatically takes care of declaring these
+// variables (as statics), initializing them, and accumulating into
+// them, all from a single entry point. Unfortunately, that solution
+// doesn't work for the CookieMonster, as it's vulnerable to races between
+// separate threads executing the same functions and hence initializing the
+// same static variables. There isn't a race danger in the histogram
+// accumulation calls; they are written to be resilient to simultaneous
+// calls from multiple threads.
+//
+// The solution taken here is to have per-CookieMonster instance
+// variables that are constructed during CookieMonster construction.
+// Note that these variables refer to the same underlying histogram,
+// so we still race (but safely) with other CookieMonster instances
+// for accumulation.
+//
+// To do this we've expanded out the individual histogram macros calls,
+// with declarations of the variables in the class decl, initialization here
+// (done from the class constructor) and direct calls to the accumulation
+// methods where needed. The specific histogram macro calls on which the
+// initialization is based are included in comments below.
+void CookieMonster::InitializeHistograms() {
+ // From UMA_HISTOGRAM_CUSTOM_COUNTS
+ histogram_expiration_duration_minutes_ = Histogram::FactoryGet(
+ "net.CookieExpirationDurationMinutes",
+ 1, kMinutesInTenYears, 50,
+ Histogram::kUmaTargetedHistogramFlag);
+ histogram_between_access_interval_minutes_ = Histogram::FactoryGet(
+ "net.CookieBetweenAccessIntervalMinutes",
+ 1, kMinutesInTenYears, 50,
+ Histogram::kUmaTargetedHistogramFlag);
+ histogram_evicted_last_access_minutes_ = Histogram::FactoryGet(
+ "net.CookieEvictedLastAccessMinutes",
+ 1, kMinutesInTenYears, 50,
+ Histogram::kUmaTargetedHistogramFlag);
+ histogram_count_ = Histogram::FactoryGet(
+ "net.CookieCount", 1, 4000, 50,
+ Histogram::kUmaTargetedHistogramFlag);
+
+ // From UMA_HISTOGRAM_COUNTS_10000 & UMA_HISTOGRAM_CUSTOM_COUNTS
+ histogram_number_duplicate_db_cookies_ = Histogram::FactoryGet(
+ "Net.NumDuplicateCookiesInDb", 1, 10000, 50,
+ Histogram::kUmaTargetedHistogramFlag);
+
+ // From UMA_HISTOGRAM_ENUMERATION
+ histogram_cookie_deletion_cause_ = LinearHistogram::FactoryGet(
+ "net.CookieDeletionCause", 1,
+ DELETE_COOKIE_LAST_ENTRY, DELETE_COOKIE_LAST_ENTRY + 1,
+ Histogram::kUmaTargetedHistogramFlag);
+}
+
void CookieMonster::InitStore() {
DCHECK(store_) << "Store must exist to initialize";
@@ -125,6 +189,153 @@
it != cookies.end(); ++it) {
InternalInsertCookie(it->first, it->second, false);
}
+
+ // After importing cookies from the PersistentCookieStore, verify that
+ // none of our constraints are violated.
+ //
+ // In particular, the backing store might have given us duplicate cookies.
+ EnsureCookiesMapIsValid();
+}
+
+void CookieMonster::EnsureCookiesMapIsValid() {
+ lock_.AssertAcquired();
+
+ int num_duplicates_trimmed = 0;
+
+ // Iterate through all the of the cookies, grouped by host.
+ CookieMap::iterator prev_range_end = cookies_.begin();
+ while (prev_range_end != cookies_.end()) {
+ CookieMap::iterator cur_range_begin = prev_range_end;
+ const std::string key = cur_range_begin->first; // Keep a copy.
+ CookieMap::iterator cur_range_end = cookies_.upper_bound(key);
+ prev_range_end = cur_range_end;
+
+ // Ensure no equivalent cookies for this host.
+ num_duplicates_trimmed +=
+ TrimDuplicateCookiesForHost(key, cur_range_begin, cur_range_end);
+ }
+
+ // Record how many duplicates were found in the database.
+ // See InitializeHistograms() for details.
+ histogram_cookie_deletion_cause_->Add(num_duplicates_trimmed);
+
+ // TODO(eroman): Should also verify that there are no cookies with the same
+ // creation time, since that is assumed to be unique by the rest of the code.
+}
+
+// Our strategy to find duplicates is:
+// (1) Build a map from (cookiename, cookiepath) to
+// {list of cookies with this signature, sorted by creation time}.
+// (2) For each list with more than 1 entry, keep the cookie having the
+// most recent creation time, and delete the others.
+namespace {
+// Two cookies are considered equivalent if they have the same domain,
+// name, and path.
+struct CookieSignature {
+ public:
+ CookieSignature(const std::string& name, const std::string& domain,
+ const std::string& path)
+ : name(name),
+ domain(domain),
+ path(path) {}
+
+ // To be a key for a map this class needs to be assignable, copyable,
+ // and have an operator<. The default assignment operator
+ // and copy constructor are exactly what we want.
+
+ bool operator<(const CookieSignature& cs) const {
+ // Name compare dominates, then domain, then path.
+ int diff = name.compare(cs.name);
+ if (diff != 0)
+ return diff < 0;
+
+ diff = domain.compare(cs.domain);
+ if (diff != 0)
+ return diff < 0;
+
+ return path.compare(cs.path) < 0;
+ }
+
+ std::string name;
+ std::string domain;
+ std::string path;
+};
+}
+
+int CookieMonster::TrimDuplicateCookiesForHost(
+ const std::string& key,
+ CookieMap::iterator begin,
+ CookieMap::iterator end) {
+ lock_.AssertAcquired();
+
+ // Set of cookies ordered by creation time.
+ typedef std::set<CookieMap::iterator, OrderByCreationTimeDesc> CookieSet;
+
+ // Helper map we populate to find the duplicates.
+ typedef std::map<CookieSignature, CookieSet> EquivalenceMap;
+ EquivalenceMap equivalent_cookies;
+
+ // The number of duplicate cookies that have been found.
+ int num_duplicates = 0;
+
+ // Iterate through all of the cookies in our range, and insert them into
+ // the equivalence map.
+ for (CookieMap::iterator it = begin; it != end; ++it) {
+ DCHECK_EQ(key, it->first);
+ CanonicalCookie* cookie = it->second;
+
+ CookieSignature signature(cookie->Name(), cookie->Domain(),
+ cookie->Path());
+ CookieSet& list = equivalent_cookies[signature];
+
+ // We found a duplicate!
+ if (!list.empty())
+ num_duplicates++;
+
+ // We save the iterator into |cookies_| rather than the actual cookie
+ // pointer, since we may need to delete it later.
+ list.insert(it);
+ }
+
+ // If there were no duplicates, we are done!
+ if (num_duplicates == 0)
+ return 0;
+
+ // Otherwise, delete all the duplicate cookies, both from our in-memory store
+ // and from the backing store.
+ for (EquivalenceMap::iterator it = equivalent_cookies.begin();
+ it != equivalent_cookies.end();
+ ++it) {
+ const CookieSignature& signature = it->first;
+ CookieSet& dupes = it->second;
+
+ if (dupes.size() <= 1)
+ continue; // This cookiename/path has no duplicates.
+
+ // Since |dups| is sorted by creation time (descending), the first cookie
+ // is the most recent one, so we will keep it. The rest are duplicates.
+ dupes.erase(dupes.begin());
+
+ LOG(ERROR) << StringPrintf("Found %d duplicate cookies for host='%s', "
+ "with {name='%s', domain='%s', path='%s'}",
+ static_cast<int>(dupes.size()),
+ key.c_str(),
+ signature.name.c_str(),
+ signature.domain.c_str(),
+ signature.path.c_str());
+
+ // Remove all the cookies identified by |dupes|. It is valid to delete our
+ // list of iterators one at a time, since |cookies_| is a multimap (they
+ // don't invalidate existing iterators following deletion).
+ for (CookieSet::iterator dupes_it = dupes.begin();
+ dupes_it != dupes.end();
+ ++dupes_it) {
+ InternalDeleteCookie(*dupes_it, true /*sync_to_store*/,
+ DELETE_COOKIE_DUPLICATE_IN_BACKING_STORE);
+ }
+ }
+
+ return num_duplicates;
}
void CookieMonster::SetDefaultCookieableSchemes() {
@@ -260,6 +471,10 @@
return Time();
}
+bool CookieMonster::DomainIsHostOnly(const std::string& domain_string) {
+ return (domain_string.empty() || domain_string[0] != '.');
+}
+
// Returns the effective TLD+1 for a given host. This only makes sense for http
// and https schemes. For other schemes, the host will be returned unchanged
// (minus any leading .).
@@ -268,27 +483,28 @@
if (scheme == "http" || scheme == "https")
return RegistryControlledDomainService::GetDomainAndRegistry(host);
- if (!host.empty() && host[0] == '.')
+ if (!CookieMonster::DomainIsHostOnly(host))
return host.substr(1);
return host;
}
-// Determine the cookie domain key to use for setting the specified cookie.
+// Determine the cookie domain key to use for setting a cookie with the
+// specified domain attribute string.
// On success returns true, and sets cookie_domain_key to either a
// -host cookie key (ex: "google.com")
// -domain cookie key (ex: ".google.com")
-static bool GetCookieDomainKey(const GURL& url,
- const CookieMonster::ParsedCookie& pc,
- std::string* cookie_domain_key) {
+static bool GetCookieDomainKeyWithString(const GURL& url,
+ const std::string& domain_string,
+ std::string* cookie_domain_key) {
const std::string url_host(url.host());
- // If no domain was specified in the cookie, default to a host cookie.
+ // If no domain was specified in the domain string, default to a host cookie.
// We match IE/Firefox in allowing a domain=IPADDR if it matches the url
// ip address hostname exactly. It should be treated as a host cookie.
- if (!pc.HasDomain() || pc.Domain().empty() ||
- (url.HostIsIPAddress() && url_host == pc.Domain())) {
+ if (domain_string.empty() ||
+ (url.HostIsIPAddress() && url_host == domain_string)) {
*cookie_domain_key = url_host;
- DCHECK((*cookie_domain_key)[0] != '.');
+ DCHECK(CookieMonster::DomainIsHostOnly(*cookie_domain_key));
return true;
}
@@ -300,7 +516,7 @@
// also treats domain=.....my.domain.com like domain=.my.domain.com, but
// neither IE nor Safari do this, and we don't either.
url_canon::CanonHostInfo ignored;
- std::string cookie_domain(net::CanonicalizeHost(pc.Domain(), &ignored));
+ std::string cookie_domain(net::CanonicalizeHost(domain_string, &ignored));
if (cookie_domain.empty())
return false;
if (cookie_domain[0] != '.')
@@ -330,8 +546,18 @@
return true;
}
-static std::string CanonPath(const GURL& url,
- const CookieMonster::ParsedCookie& pc) {
+// Determine the cookie domain key to use for setting the specified cookie.
+static bool GetCookieDomainKey(const GURL& url,
+ const CookieMonster::ParsedCookie& pc,
+ std::string* cookie_domain_key) {
+ std::string domain_string;
+ if (pc.HasDomain())
+ domain_string = pc.Domain();
+ return GetCookieDomainKeyWithString(url, domain_string, cookie_domain_key);
+}
+
+static std::string CanonPathWithString(const GURL& url,
+ const std::string& path_string) {
// The RFC says the path should be a prefix of the current URL path.
// However, Mozilla allows you to set any path for compatibility with
// broken websites. We unfortunately will mimic this behavior. We try
@@ -339,8 +565,8 @@
// default the path to something reasonable.
// The path was supplied in the cookie, we'll take it.
- if (pc.HasPath() && !pc.Path().empty() && pc.Path()[0] == '/')
- return pc.Path();
+ if (!path_string.empty() && path_string[0] == '/')
+ return path_string;
// The path was not supplied in the cookie or invalid, we will default
// to the current URL path.
@@ -360,8 +586,20 @@
return url_path.substr(0, idx);
}
+static std::string CanonPath(const GURL& url,
+ const CookieMonster::ParsedCookie& pc) {
+ std::string path_string;
+ if (pc.HasPath())
+ path_string = pc.Path();
+ return CanonPathWithString(url, path_string);
+}
+
static Time CanonExpiration(const CookieMonster::ParsedCookie& pc,
- const Time& current) {
+ const Time& current,
+ const CookieOptions& options) {
+ if (options.force_session())
+ return Time();
+
// First, try the Max-Age attribute.
uint64 max_age = 0;
if (pc.HasMaxAge() &&
@@ -383,6 +621,8 @@
}
bool CookieMonster::HasCookieableScheme(const GURL& url) {
+ lock_.AssertAcquired();
+
// Make sure the request is on a cookie-able url scheme.
for (size_t i = 0; i < cookieable_schemes_.size(); ++i) {
// We matched a scheme.
@@ -399,6 +639,11 @@
void CookieMonster::SetCookieableSchemes(
const char* schemes[], size_t num_schemes) {
+ AutoLock autolock(lock_);
+
+ // Cookieable Schemes must be set before first use of function.
+ DCHECK(!initialized_);
+
cookieable_schemes_.clear();
cookieable_schemes_.insert(cookieable_schemes_.end(),
schemes, schemes + num_schemes);
@@ -409,11 +654,12 @@
const std::string& cookie_line,
const Time& creation_time_or_null,
const CookieOptions& options) {
+ AutoLock autolock(lock_);
+
if (!HasCookieableScheme(url)) {
return false;
}
- AutoLock autolock(lock_);
InitIfNecessary();
COOKIE_DLOG(INFO) << "SetCookie() line: " << cookie_line;
@@ -445,9 +691,10 @@
std::string cookie_path = CanonPath(url, pc);
scoped_ptr<CanonicalCookie> cc;
- Time cookie_expires = CanonExpiration(pc, creation_time);
+ Time cookie_expires = CanonExpiration(pc, creation_time, options);
- cc.reset(new CanonicalCookie(pc.Name(), pc.Value(), cookie_path,
+ cc.reset(new CanonicalCookie(pc.Name(), pc.Value(), cookie_domain,
+ cookie_path,
pc.IsSecure(), pc.IsHttpOnly(),
creation_time, creation_time,
!cookie_expires.is_null(), cookie_expires));
@@ -456,20 +703,58 @@
COOKIE_DLOG(WARNING) << "Failed to allocate CanonicalCookie";
return false;
}
+ return SetCanonicalCookie(&cc, cookie_domain, creation_time, options);
+}
- if (DeleteAnyEquivalentCookie(cookie_domain,
- *cc,
+bool CookieMonster::SetCookieWithDetails(
+ const GURL& url, const std::string& name, const std::string& value,
+ const std::string& domain, const std::string& path,
+ const base::Time& expiration_time, bool secure, bool http_only) {
+
+ AutoLock autolock(lock_);
+
+ if (!HasCookieableScheme(url))
+ return false;
+
+ InitIfNecessary();
+
+ Time creation_time = CurrentTime();
+ last_time_seen_ = creation_time;
+
+ scoped_ptr<CanonicalCookie> cc;
+ cc.reset(CanonicalCookie::Create(
+ url, name, value, domain, path,
+ creation_time, expiration_time,
+ secure, http_only));
+
+ if (!cc.get())
+ return false;
+
+ CookieOptions options;
+ options.set_include_httponly();
+ return SetCanonicalCookie(&cc, cc->Domain(), creation_time, options);
+}
+
+bool CookieMonster::SetCanonicalCookie(scoped_ptr<CanonicalCookie>* cc,
+ const std::string& cookie_domain,
+ const Time& creation_time,
+ const CookieOptions& options) {
+ if (DeleteAnyEquivalentCookie(cookie_domain, **cc,
options.exclude_httponly())) {
COOKIE_DLOG(INFO) << "SetCookie() not clobbering httponly cookie";
return false;
}
- COOKIE_DLOG(INFO) << "SetCookie() cc: " << cc->DebugString();
+ COOKIE_DLOG(INFO) << "SetCookie() cc: " << (*cc)->DebugString();
// Realize that we might be setting an expired cookie, and the only point
// was to delete the cookie which we've already done.
- if (!cc->IsExpired(creation_time))
- InternalInsertCookie(cookie_domain, cc.release(), true);
+ if (!(*cc)->IsExpired(creation_time)) {
+ // See InitializeHistograms() for details.
+ histogram_expiration_duration_minutes_->Add(
+ ((*cc)->ExpiryDate() - creation_time).InMinutes());
+ InternalInsertCookie(cookie_domain, cc->release(), true);
+ }
// We assume that hopefully setting a cookie will be less common than
// querying a cookie. Since setting a cookie can put us over our limits,
@@ -484,12 +769,18 @@
void CookieMonster::InternalInsertCookie(const std::string& key,
CanonicalCookie* cc,
bool sync_to_store) {
+ lock_.AssertAcquired();
+
if (cc->IsPersistent() && store_ && sync_to_store)
store_->AddCookie(key, *cc);
cookies_.insert(CookieMap::value_type(key, cc));
+ if (delegate_.get())
+ delegate_->OnCookieChanged(*cc, false);
}
void CookieMonster::InternalUpdateCookieAccessTime(CanonicalCookie* cc) {
+ lock_.AssertAcquired();
+
// Based off the Mozilla code. When a cookie has been accessed recently,
// don't bother updating its access time again. This reduces the number of
// updates we do during pageload, which in turn reduces the chance our storage
@@ -498,17 +789,29 @@
if ((current - cc->LastAccessDate()) < last_access_threshold_)
return;
+ // See InitializeHistograms() for details.
+ histogram_between_access_interval_minutes_->Add(
+ (current - cc->LastAccessDate()).InMinutes());
+
cc->SetLastAccessDate(current);
if (cc->IsPersistent() && store_)
store_->UpdateCookieAccessTime(*cc);
}
void CookieMonster::InternalDeleteCookie(CookieMap::iterator it,
- bool sync_to_store) {
+ bool sync_to_store,
+ DeletionCause deletion_cause) {
+ lock_.AssertAcquired();
+
+ // See InitializeHistograms() for details.
+ histogram_cookie_deletion_cause_->Add(deletion_cause);
+
CanonicalCookie* cc = it->second;
COOKIE_DLOG(INFO) << "InternalDeleteCookie() cc: " << cc->DebugString();
if (cc->IsPersistent() && store_ && sync_to_store)
store_->DeleteCookie(*cc);
+ if (delegate_.get())
+ delegate_->OnCookieChanged(*cc, true);
cookies_.erase(it);
delete cc;
}
@@ -516,6 +819,8 @@
bool CookieMonster::DeleteAnyEquivalentCookie(const std::string& key,
const CanonicalCookie& ecc,
bool skip_httponly) {
+ lock_.AssertAcquired();
+
bool found_equivalent_cookie = false;
bool skipped_httponly = false;
for (CookieMapItPair its = cookies_.equal_range(key);
@@ -527,19 +832,14 @@
if (ecc.IsEquivalent(*cc)) {
// We should never have more than one equivalent cookie, since they should
// overwrite each other.
- DCHECK(!found_equivalent_cookie) <<
+ CHECK(!found_equivalent_cookie) <<
"Duplicate equivalent cookies found, cookie store is corrupted.";
if (skip_httponly && cc->IsHttpOnly()) {
skipped_httponly = true;
} else {
- InternalDeleteCookie(curit, true);
+ InternalDeleteCookie(curit, true, DELETE_COOKIE_OVERWRITE);
}
found_equivalent_cookie = true;
-#ifdef NDEBUG
- // Speed optimization: No point looping through the rest of the cookies
- // since we're only doing it as a consistency check.
- break;
-#endif
}
}
return skipped_httponly;
@@ -547,6 +847,8 @@
int CookieMonster::GarbageCollect(const Time& current,
const std::string& key) {
+ lock_.AssertAcquired();
+
int num_deleted = 0;
// Collect garbage for this key.
@@ -583,6 +885,8 @@
const CookieMapItPair& itpair,
size_t num_max,
size_t num_purge) {
+ lock_.AssertAcquired();
+
// First, delete anything that's expired.
std::vector<CookieMap::iterator> cookie_its;
int num_deleted = GarbageCollectExpired(current, itpair, &cookie_its);
@@ -596,8 +900,12 @@
std::partial_sort(cookie_its.begin(), cookie_its.begin() + num_purge,
cookie_its.end(), LRUCookieSorter);
- for (size_t i = 0; i < num_purge; ++i)
- InternalDeleteCookie(cookie_its[i], true);
+ for (size_t i = 0; i < num_purge; ++i) {
+ // See InitializeHistograms() for details.
+ histogram_evicted_last_access_minutes_->Add(
+ (current - cookie_its[i]->second->LastAccessDate()).InMinutes());
+ InternalDeleteCookie(cookie_its[i], true, DELETE_COOKIE_EVICTED);
+ }
num_deleted += num_purge;
}
@@ -609,13 +917,15 @@
const Time& current,
const CookieMapItPair& itpair,
std::vector<CookieMap::iterator>* cookie_its) {
+ lock_.AssertAcquired();
+
int num_deleted = 0;
for (CookieMap::iterator it = itpair.first, end = itpair.second; it != end;) {
CookieMap::iterator curit = it;
++it;
if (curit->second->IsExpired(current)) {
- InternalDeleteCookie(curit, true);
+ InternalDeleteCookie(curit, true, DELETE_COOKIE_EXPIRED);
++num_deleted;
} else if (cookie_its) {
cookie_its->push_back(curit);
@@ -633,7 +943,9 @@
for (CookieMap::iterator it = cookies_.begin(); it != cookies_.end();) {
CookieMap::iterator curit = it;
++it;
- InternalDeleteCookie(curit, sync_to_store);
+ InternalDeleteCookie(curit, sync_to_store,
+ sync_to_store ? DELETE_COOKIE_EXPLICIT :
+ DELETE_COOKIE_DONT_RECORD /* Destruction. */);
++num_deleted;
}
@@ -654,7 +966,7 @@
if (cc->CreationDate() >= delete_begin &&
(delete_end.is_null() || cc->CreationDate() < delete_end)) {
- InternalDeleteCookie(curit, sync_to_store);
+ InternalDeleteCookie(curit, sync_to_store, DELETE_COOKIE_EXPLICIT);
++num_deleted;
}
}
@@ -667,6 +979,28 @@
return DeleteAllCreatedBetween(delete_begin, Time(), sync_to_store);
}
+int CookieMonster::DeleteAllForHost(const GURL& url) {
+ AutoLock autolock(lock_);
+ InitIfNecessary();
+
+ if (!HasCookieableScheme(url))
+ return 0;
+
+ // We store host cookies in the store by their canonical host name;
+ // domain cookies are stored with a leading ".". So this is a pretty
+ // simple lookup and per-cookie delete.
+ int num_deleted = 0;
+ for (CookieMapItPair its = cookies_.equal_range(url.host());
+ its.first != its.second;) {
+ CookieMap::iterator curit = its.first;
+ ++its.first;
+ num_deleted++;
+
+ InternalDeleteCookie(curit, true, DELETE_COOKIE_EXPLICIT);
+ }
+ return num_deleted;
+}
+
bool CookieMonster::DeleteCookie(const std::string& domain,
const CanonicalCookie& cookie,
bool sync_to_store) {
@@ -677,7 +1011,7 @@
its.first != its.second; ++its.first) {
// The creation date acts as our unique index...
if (its.first->second->CreationDate() == cookie.CreationDate()) {
- InternalDeleteCookie(its.first, sync_to_store);
+ InternalDeleteCookie(its.first, sync_to_store, DELETE_COOKIE_EXPLICIT);
return true;
}
}
@@ -714,6 +1048,9 @@
// should be fast and simple enough for now.
std::string CookieMonster::GetCookiesWithOptions(const GURL& url,
const CookieOptions& options) {
+ AutoLock autolock(lock_);
+ InitIfNecessary();
+
if (!HasCookieableScheme(url)) {
return std::string();
}
@@ -743,6 +1080,9 @@
void CookieMonster::DeleteCookie(const GURL& url,
const std::string& cookie_name) {
+ AutoLock autolock(lock_);
+ InitIfNecessary();
+
if (!HasCookieableScheme(url))
return;
@@ -765,8 +1105,9 @@
for (CookieMap::iterator it = cookies_.begin(); it != cookies_.end();) {
CookieMap::iterator curit = it;
++it;
- if (matching_cookies.find(curit->second) != matching_cookies.end())
- InternalDeleteCookie(curit, true);
+ if (matching_cookies.find(curit->second) != matching_cookies.end()) {
+ InternalDeleteCookie(curit, true, DELETE_COOKIE_EXPLICIT);
+ }
}
}
@@ -788,7 +1129,7 @@
CookieList cookie_list;
for (CookieMap::iterator it = cookies_.begin(); it != cookies_.end(); ++it)
- cookie_list.push_back(CookieListPair(it->first, *it->second));
+ cookie_list.push_back(*it->second);
return cookie_list;
}
@@ -797,47 +1138,22 @@
AutoLock autolock(lock_);
InitIfNecessary();
- // Do not return removed cookies.
- GarbageCollectExpired(Time::Now(),
- CookieMapItPair(cookies_.begin(), cookies_.end()),
- NULL);
-
- CookieList cookie_list;
- if (!HasCookieableScheme(url))
- return cookie_list;
-
- bool secure = url.SchemeIsSecure();
-
- // Query for the full host, For example: 'a.c.blah.com'.
- std::string key(url.host());
- FindRawCookies(key, secure, &cookie_list);
-
- // See if we can search for domain cookies, i.e. if the host has a TLD + 1.
- const std::string domain(GetEffectiveDomain(url.scheme(), key));
- if (domain.empty())
- return cookie_list;
-
- // Use same logic as in FindCookiesForHostAndDomain.
- DCHECK_LE(domain.length(), key.length());
- DCHECK_EQ(0, key.compare(key.length() - domain.length(), domain.length(),
- domain));
- for (key = "." + key; key.length() > domain.length(); ) {
- FindRawCookies(key, secure, &cookie_list);
- const size_t next_dot = key.find('.', 1); // Skip over leading dot.
- key.erase(0, next_dot);
- }
- return cookie_list;
+ return InternalGetAllCookiesForURL(url);
}
void CookieMonster::FindCookiesForHostAndDomain(
const GURL& url,
const CookieOptions& options,
std::vector<CanonicalCookie*>* cookies) {
- AutoLock autolock(lock_);
- InitIfNecessary();
+ lock_.AssertAcquired();
const Time current_time(CurrentTime());
+ // Probe to save statistics relatively frequently. We do it here rather
+ // than in the set path as many websites won't set cookies, and we
+ // want to collect statistics whenever the browser's being used.
+ RecordPeriodicStats(current_time);
+
// Query for the full host, For example: 'a.c.blah.com'.
std::string key(url.host());
FindCookiesForKey(key, url, options, current_time, cookies);
@@ -868,6 +1184,8 @@
const CookieOptions& options,
const Time& current,
std::vector<CanonicalCookie*>* cookies) {
+ lock_.AssertAcquired();
+
bool secure = url.SchemeIsSecure();
for (CookieMapItPair its = cookies_.equal_range(key);
@@ -878,7 +1196,7 @@
// If the cookie is expired, delete it.
if (cc->IsExpired(current)) {
- InternalDeleteCookie(curit, true);
+ InternalDeleteCookie(curit, true, DELETE_COOKIE_EXPIRED);
continue;
}
@@ -902,15 +1220,76 @@
void CookieMonster::FindRawCookies(const std::string& key,
bool include_secure,
+ const std::string& path,
CookieList* list) {
+ lock_.AssertAcquired();
+
for (CookieMapItPair its = cookies_.equal_range(key);
its.first != its.second; ++its.first) {
CanonicalCookie* cc = its.first->second;
- if (include_secure || !cc->IsSecure())
- list->push_back(CookieListPair(key, *cc));
+ if (!include_secure && cc->IsSecure())
+ continue;
+ if (!cc->IsOnPath(path))
+ continue;
+ list->push_back(*cc);
}
}
+CookieMonster::CookieList CookieMonster::InternalGetAllCookiesForURL(
+ const GURL& url) {
+ lock_.AssertAcquired();
+
+ // Do not return removed cookies.
+ GarbageCollectExpired(Time::Now(),
+ CookieMapItPair(cookies_.begin(), cookies_.end()),
+ NULL);
+
+ CookieList cookie_list;
+ if (!HasCookieableScheme(url))
+ return cookie_list;
+
+ bool secure = url.SchemeIsSecure();
+
+ // Query for the full host, For example: 'a.c.blah.com'.
+ std::string key(url.host());
+ FindRawCookies(key, secure, url.path(), &cookie_list);
+
+ // See if we can search for domain cookies, i.e. if the host has a TLD + 1.
+ const std::string domain(GetEffectiveDomain(url.scheme(), key));
+ if (domain.empty())
+ return cookie_list;
+
+ // Use same logic as in FindCookiesForHostAndDomain.
+ DCHECK_LE(domain.length(), key.length());
+ DCHECK_EQ(0, key.compare(key.length() - domain.length(), domain.length(),
+ domain));
+ for (key = "." + key; key.length() > domain.length(); ) {
+ FindRawCookies(key, secure, url.path(), &cookie_list);
+ const size_t next_dot = key.find('.', 1); // Skip over leading dot.
+ key.erase(0, next_dot);
+ }
+ return cookie_list;
+}
+
+// Test to see if stats should be recorded, and record them if so.
+// The goal here is to get sampling for the average browser-hour of
+// activity. We won't take samples when the web isn't being surfed,
+// and when the web is being surfed, we'll take samples about every
+// kRecordStatisticsIntervalSeconds.
+// last_statistic_record_time_ is initialized to Now() rather than null
+// in the constructor so that we won't take statistics right after
+// startup, to avoid bias from browsers that are started but not used.
+void CookieMonster::RecordPeriodicStats(const base::Time& current_time) {
+ const base::TimeDelta kRecordStatisticsIntervalTime(
+ base::TimeDelta::FromSeconds(kRecordStatisticsIntervalSeconds));
+
+ if (current_time - last_statistic_record_time_ >
+ kRecordStatisticsIntervalTime) {
+ // See InitializeHistograms() for details.
+ histogram_count_->Add(cookies_.size());
+ last_statistic_record_time_ = current_time;
+ }
+}
CookieMonster::ParsedCookie::ParsedCookie(const std::string& cookie_line)
: is_valid_(false),
@@ -943,7 +1322,7 @@
static inline bool SeekTo(std::string::const_iterator* it,
const std::string::const_iterator& end,
const char* chars) {
- for (; *it != end && !CharIsA(**it, chars); ++(*it));
+ for (; *it != end && !CharIsA(**it, chars); ++(*it)) {}
return *it == end;
}
// Seek the iterator to the first occurrence of a character not in |chars|.
@@ -951,72 +1330,157 @@
static inline bool SeekPast(std::string::const_iterator* it,
const std::string::const_iterator& end,
const char* chars) {
- for (; *it != end && CharIsA(**it, chars); ++(*it));
+ for (; *it != end && CharIsA(**it, chars); ++(*it)) {}
return *it == end;
}
static inline bool SeekBackPast(std::string::const_iterator* it,
const std::string::const_iterator& end,
const char* chars) {
- for (; *it != end && CharIsA(**it, chars); --(*it));
+ for (; *it != end && CharIsA(**it, chars); --(*it)) {}
return *it == end;
}
+const char CookieMonster::ParsedCookie::kTerminator[] = "\n\r\0";
+const int CookieMonster::ParsedCookie::kTerminatorLen =
+ sizeof(kTerminator) - 1;
+const char CookieMonster::ParsedCookie::kWhitespace[] = " \t";
+const char CookieMonster::ParsedCookie::kValueSeparator[] = ";";
+const char CookieMonster::ParsedCookie::kTokenSeparator[] = ";=";
+
+std::string::const_iterator CookieMonster::ParsedCookie::FindFirstTerminator(
+ const std::string& s) {
+ std::string::const_iterator end = s.end();
+ size_t term_pos =
+ s.find_first_of(std::string(kTerminator, kTerminatorLen));
+ if (term_pos != std::string::npos) {
+ // We found a character we should treat as an end of string.
+ end = s.begin() + term_pos;
+ }
+ return end;
+}
+
+bool CookieMonster::ParsedCookie::ParseToken(
+ std::string::const_iterator* it,
+ const std::string::const_iterator& end,
+ std::string::const_iterator* token_start,
+ std::string::const_iterator* token_end) {
+ DCHECK(it && token_start && token_end);
+ std::string::const_iterator token_real_end;
+
+ // Seek past any whitespace before the "token" (the name).
+ // token_start should point at the first character in the token
+ if (SeekPast(it, end, kWhitespace))
+ return false; // No token, whitespace or empty.
+ *token_start = *it;
+
+ // Seek over the token, to the token separator.
+ // token_real_end should point at the token separator, i.e. '='.
+ // If it == end after the seek, we probably have a token-value.
+ SeekTo(it, end, kTokenSeparator);
+ token_real_end = *it;
+
+ // Ignore any whitespace between the token and the token separator.
+ // token_end should point after the last interesting token character,
+ // pointing at either whitespace, or at '=' (and equal to token_real_end).
+ if (*it != *token_start) { // We could have an empty token name.
+ --(*it); // Go back before the token separator.
+ // Skip over any whitespace to the first non-whitespace character.
+ SeekBackPast(it, *token_start, kWhitespace);
+ // Point after it.
+ ++(*it);
+ }
+ *token_end = *it;
+
+ // Seek us back to the end of the token.
+ *it = token_real_end;
+ return true;
+}
+
+void CookieMonster::ParsedCookie::ParseValue(
+ std::string::const_iterator* it,
+ const std::string::const_iterator& end,
+ std::string::const_iterator* value_start,
+ std::string::const_iterator* value_end) {
+ DCHECK(it && value_start && value_end);
+
+ // Seek past any whitespace that might in-between the token and value.
+ SeekPast(it, end, kWhitespace);
+ // value_start should point at the first character of the value.
+ *value_start = *it;
+
+ // It is unclear exactly how quoted string values should be handled.
+ // Major browsers do different things, for example, Firefox supports
+ // semicolons embedded in a quoted value, while IE does not. Looking at
+ // the specs, RFC 2109 and 2965 allow for a quoted-string as the value.
+ // However, these specs were apparently written after browsers had
+ // implemented cookies, and they seem very distant from the reality of
+ // what is actually implemented and used on the web. The original spec
+ // from Netscape is possibly what is closest to the cookies used today.
+ // This spec didn't have explicit support for double quoted strings, and
+ // states that ; is not allowed as part of a value. We had originally
+ // implement the Firefox behavior (A="B;C"; -> A="B;C";). However, since
+ // there is no standard that makes sense, we decided to follow the behavior
+ // of IE and Safari, which is closer to the original Netscape proposal.
+ // This means that A="B;C" -> A="B;. This also makes the code much simpler
+ // and reduces the possibility for invalid cookies, where other browsers
+ // like Opera currently reject those invalid cookies (ex A="B" "C";).
+
+ // Just look for ';' to terminate ('=' allowed).
+ // We can hit the end, maybe they didn't terminate.
+ SeekTo(it, end, kValueSeparator);
+
+ // Will be pointed at the ; seperator or the end.
+ *value_end = *it;
+
+ // Ignore any unwanted whitespace after the value.
+ if (*value_end != *value_start) { // Could have an empty value
+ --(*value_end);
+ SeekBackPast(value_end, *value_start, kWhitespace);
+ ++(*value_end);
+ }
+}
+
+std::string CookieMonster::ParsedCookie::ParseTokenString(
+ const std::string& token) {
+ std::string::const_iterator it = token.begin();
+ std::string::const_iterator end = FindFirstTerminator(token);
+
+ std::string::const_iterator token_start, token_end;
+ if (ParseToken(&it, end, &token_start, &token_end))
+ return std::string(token_start, token_end);
+ return std::string();
+}
+
+std::string CookieMonster::ParsedCookie::ParseValueString(
+ const std::string& value) {
+ std::string::const_iterator it = value.begin();
+ std::string::const_iterator end = FindFirstTerminator(value);
+
+ std::string::const_iterator value_start, value_end;
+ ParseValue(&it, end, &value_start, &value_end);
+ return std::string(value_start, value_end);
+}
+
// Parse all token/value pairs and populate pairs_.
void CookieMonster::ParsedCookie::ParseTokenValuePairs(
const std::string& cookie_line) {
- static const char kTerminator[] = "\n\r\0";
- static const int kTerminatorLen = sizeof(kTerminator) - 1;
- static const char kWhitespace[] = " \t";
- static const char kValueSeparator[] = ";";
- static const char kTokenSeparator[] = ";=";
-
pairs_.clear();
// Ok, here we go. We should be expecting to be starting somewhere
// before the cookie line, not including any header name...
std::string::const_iterator start = cookie_line.begin();
- std::string::const_iterator end = cookie_line.end();
std::string::const_iterator it = start;
// TODO Make sure we're stripping \r\n in the network code. Then we
// can log any unexpected terminators.
- size_t term_pos =
- cookie_line.find_first_of(std::string(kTerminator, kTerminatorLen));
- if (term_pos != std::string::npos) {
- // We found a character we should treat as an end of string.
- end = start + term_pos;
- }
+ std::string::const_iterator end = FindFirstTerminator(cookie_line);
for (int pair_num = 0; pair_num < kMaxPairs && it != end; ++pair_num) {
TokenValuePair pair;
- std::string::const_iterator token_start, token_real_end, token_end;
- // Seek past any whitespace before the "token" (the name).
- // token_start should point at the first character in the token
- if (SeekPast(&it, end, kWhitespace))
- break; // No token, whitespace or empty.
- token_start = it;
-
- // Seek over the token, to the token separator.
- // token_real_end should point at the token separator, i.e. '='.
- // If it == end after the seek, we probably have a token-value.
- SeekTo(&it, end, kTokenSeparator);
- token_real_end = it;
-
- // Ignore any whitespace between the token and the token separator.
- // token_end should point after the last interesting token character,
- // pointing at either whitespace, or at '=' (and equal to token_real_end).
- if (it != token_start) { // We could have an empty token name.
- --it; // Go back before the token separator.
- // Skip over any whitespace to the first non-whitespace character.
- SeekBackPast(&it, token_start, kWhitespace);
- // Point after it.
- ++it;
- }
- token_end = it;
-
- // Seek us back to the end of the token.
- it = token_real_end;
+ std::string::const_iterator token_start, token_end;
+ if (!ParseToken(&it, end, &token_start, &token_end))
+ break;
if (it == end || *it != '=') {
// We have a token-value, we didn't have any token name.
@@ -1043,45 +1507,10 @@
// OK, now try to parse a value.
std::string::const_iterator value_start, value_end;
-
- // Seek past any whitespace that might in-between the token and value.
- SeekPast(&it, end, kWhitespace);
- // value_start should point at the first character of the value.
- value_start = it;
-
- // It is unclear exactly how quoted string values should be handled.
- // Major browsers do different things, for example, Firefox supports
- // semicolons embedded in a quoted value, while IE does not. Looking at
- // the specs, RFC 2109 and 2965 allow for a quoted-string as the value.
- // However, these specs were apparently written after browsers had
- // implemented cookies, and they seem very distant from the reality of
- // what is actually implemented and used on the web. The original spec
- // from Netscape is possibly what is closest to the cookies used today.
- // This spec didn't have explicit support for double quoted strings, and
- // states that ; is not allowed as part of a value. We had originally
- // implement the Firefox behavior (A="B;C"; -> A="B;C";). However, since
- // there is no standard that makes sense, we decided to follow the behavior
- // of IE and Safari, which is closer to the original Netscape proposal.
- // This means that A="B;C" -> A="B;. This also makes the code much simpler
- // and reduces the possibility for invalid cookies, where other browsers
- // like Opera currently reject those invalid cookies (ex A="B" "C";).
-
- // Just look for ';' to terminate ('=' allowed).
- // We can hit the end, maybe they didn't terminate.
- SeekTo(&it, end, kValueSeparator);
-
- // Will be pointed at the ; seperator or the end.
- value_end = it;
-
- // Ignore any unwanted whitespace after the value.
- if (value_end != value_start) { // Could have an empty value
- --value_end;
- SeekBackPast(&value_end, value_start, kWhitespace);
- ++value_end;
- }
-
+ ParseValue(&it, end, &value_start, &value_end);
// OK, we're finished with a Token/Value.
pair.second = std::string(value_start, value_end);
+
// From RFC2109: "Attributes (names) (attr) are case-insensitive."
if (pair_num != 0)
StringToLowerASCII(&pair.first);
@@ -1104,19 +1533,21 @@
// We skip over the first token/value, the user supplied one.
for (size_t i = 1; i < pairs_.size(); ++i) {
- if (pairs_[i].first == kPathTokenName)
+ if (pairs_[i].first == kPathTokenName) {
path_index_ = i;
- else if (pairs_[i].first == kDomainTokenName)
+ } else if (pairs_[i].first == kDomainTokenName) {
domain_index_ = i;
- else if (pairs_[i].first == kExpiresTokenName)
+ } else if (pairs_[i].first == kExpiresTokenName) {
expires_index_ = i;
- else if (pairs_[i].first == kMaxAgeTokenName)
+ } else if (pairs_[i].first == kMaxAgeTokenName) {
maxage_index_ = i;
- else if (pairs_[i].first == kSecureTokenName)
+ } else if (pairs_[i].first == kSecureTokenName) {
secure_index_ = i;
- else if (pairs_[i].first == kHttpOnlyTokenName)
+ } else if (pairs_[i].first == kHttpOnlyTokenName) {
httponly_index_ = i;
- else { /* some attribute we don't know or don't care about. */ }
+ } else {
+ /* some attribute we don't know or don't care about. */
+ }
}
}
@@ -1135,6 +1566,77 @@
return out;
}
+CookieMonster::CanonicalCookie::CanonicalCookie(const GURL& url,
+ const ParsedCookie& pc)
+ : name_(pc.Name()),
+ value_(pc.Value()),
+ path_(CanonPath(url, pc)),
+ creation_date_(Time::Now()),
+ last_access_date_(Time()),
+ has_expires_(pc.HasExpires()),
+ secure_(pc.IsSecure()),
+ httponly_(pc.IsHttpOnly()) {
+ if (has_expires_)
+ expiry_date_ = CanonExpiration(pc, creation_date_, CookieOptions());
+
+ // Do the best we can with the domain.
+ std::string cookie_domain;
+ std::string domain_string;
+ if (pc.HasDomain()) {
+ domain_string = pc.Domain();
+ }
+ bool result
+ = GetCookieDomainKeyWithString(url, domain_string,
+ &cookie_domain);
+ // Caller is responsible for passing in good arguments.
+ DCHECK(result);
+ domain_ = cookie_domain;
+}
+
+CookieMonster::CanonicalCookie* CookieMonster::CanonicalCookie::Create(
+ const GURL& url, const std::string& name, const std::string& value,
+ const std::string& domain, const std::string& path,
+ const base::Time& creation_time, const base::Time& expiration_time,
+ bool secure, bool http_only) {
+ // Expect valid attribute tokens and values, as defined by the ParsedCookie
+ // logic, otherwise don't create the cookie.
+ std::string parsed_name = ParsedCookie::ParseTokenString(name);
+ if (parsed_name != name)
+ return NULL;
+ std::string parsed_value = ParsedCookie::ParseValueString(value);
+ if (parsed_value != value)
+ return NULL;
+
+ std::string parsed_domain = ParsedCookie::ParseValueString(domain);
+ if (parsed_domain != domain)
+ return NULL;
+ std::string cookie_domain;
+ if (!GetCookieDomainKeyWithString(url, parsed_domain, &cookie_domain))
+ return false;
+
+ std::string parsed_path = ParsedCookie::ParseValueString(path);
+ if (parsed_path != path)
+ return NULL;
+
+ std::string cookie_path = CanonPathWithString(url, parsed_path);
+ // Expect that the path was either not specified (empty), or is valid.
+ if (!parsed_path.empty() && cookie_path != parsed_path)
+ return NULL;
+ // Canonicalize path again to make sure it escapes characters as needed.
+ url_parse::Component path_component(0, cookie_path.length());
+ url_canon::RawCanonOutputT<char> canon_path;
+ url_parse::Component canon_path_component;
+ url_canon::CanonicalizePath(cookie_path.data(), path_component,
+ &canon_path, &canon_path_component);
+ cookie_path = std::string(canon_path.data() + canon_path_component.begin,
+ canon_path_component.len);
+
+ return new CanonicalCookie(parsed_name, parsed_value, cookie_domain,
+ cookie_path, secure, http_only,
+ creation_time, creation_time,
+ !expiration_time.is_null(), expiration_time);
+}
+
bool CookieMonster::CanonicalCookie::IsOnPath(
const std::string& url_path) const {
@@ -1172,9 +1674,12 @@
}
std::string CookieMonster::CanonicalCookie::DebugString() const {
- return StringPrintf("name: %s value: %s path: %s creation: %" PRId64,
- name_.c_str(), value_.c_str(), path_.c_str(),
+ return StringPrintf("name: %s value: %s domain: %s path: %s creation: %"
+ PRId64,
+ name_.c_str(), value_.c_str(),
+ domain_.c_str(), path_.c_str(),
static_cast<int64>(creation_date_.ToTimeT()));
}
} // namespace
+
diff --git a/net/base/cookie_monster.h b/net/base/cookie_monster.h
index 7578c46..623363f 100644
--- a/net/base/cookie_monster.h
+++ b/net/base/cookie_monster.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -13,7 +13,10 @@
#include <vector>
#include "base/basictypes.h"
+#include "base/histogram.h"
#include "base/lock.h"
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
#include "base/time.h"
#include "net/base/cookie_store.h"
@@ -33,8 +36,9 @@
// - Verify that our domain enforcement and non-dotted handling is correct
class CookieMonster : public CookieStore {
public:
- class ParsedCookie;
class CanonicalCookie;
+ class Delegate;
+ class ParsedCookie;
class PersistentCookieStore;
// NOTE(deanm):
@@ -47,24 +51,27 @@
typedef std::multimap<std::string, CanonicalCookie*> CookieMap;
typedef std::pair<CookieMap::iterator, CookieMap::iterator> CookieMapItPair;
typedef std::pair<std::string, CanonicalCookie*> KeyedCanonicalCookie;
- typedef std::pair<std::string, CanonicalCookie> CookieListPair;
- typedef std::vector<CookieListPair> CookieList;
+ typedef std::vector<CanonicalCookie> CookieList;
-
- CookieMonster();
-
- // The store passed in should not have had Init() called on it yet. This class
- // will take care of initializing it. The backing store is NOT owned by this
- // class, but it must remain valid for the duration of the cookie monster's
- // existence.
- CookieMonster(PersistentCookieStore* store);
+ // The store passed in should not have had Init() called on it yet. This
+ // class will take care of initializing it. The backing store is NOT owned by
+ // this class, but it must remain valid for the duration of the cookie
+ // monster's existence. If |store| is NULL, then no backing store will be
+ // updated. If |delegate| is non-NULL, it will be notified on
+ // creation/deletion of cookies.
+ CookieMonster(PersistentCookieStore* store, Delegate* delegate);
#ifdef UNIT_TEST
- CookieMonster(int last_access_threshold_milliseconds)
+ CookieMonster(PersistentCookieStore* store,
+ Delegate* delegate,
+ int last_access_threshold_milliseconds)
: initialized_(false),
- store_(NULL),
+ store_(store),
last_access_threshold_(base::TimeDelta::FromMilliseconds(
- last_access_threshold_milliseconds)) {
+ last_access_threshold_milliseconds)),
+ delegate_(delegate),
+ last_statistic_record_time_(base::Time::Now()) {
+ InitializeHistograms();
SetDefaultCookieableSchemes();
}
#endif
@@ -72,6 +79,10 @@
// Parse the string with the cookie time (very forgivingly).
static base::Time ParseCookieTime(const std::string& time_string);
+ // Returns true if a domain string represents a host-only cookie,
+ // i.e. it doesn't begin with a leading '.' character.
+ static bool DomainIsHostOnly(const std::string& domain_string);
+
// CookieStore implementation.
virtual bool SetCookieWithOptions(const GURL& url,
const std::string& cookie_line,
@@ -81,6 +92,19 @@
virtual void DeleteCookie(const GURL& url, const std::string& cookie_name);
virtual CookieMonster* GetCookieMonster() { return this; }
+ // Sets a cookie given explicit user-provided cookie attributes. The cookie
+ // name, value, domain, etc. are each provided as separate strings. This
+ // function expects each attribute to be well-formed. It will check for
+ // disallowed characters (e.g. the ';' character is disallowed within the
+ // cookie value attribute) and will return false without setting the cookie
+ // if such characters are found.
+ bool SetCookieWithDetails(const GURL& url,
+ const std::string& name,
+ const std::string& value,
+ const std::string& domain,
+ const std::string& path,
+ const base::Time& expiration_time,
+ bool secure, bool http_only);
// Exposed for unit testing.
bool SetCookieWithCreationTimeAndOptions(const GURL& url,
@@ -99,8 +123,8 @@
CookieList GetAllCookies();
// Returns all the cookies, for use in management UI, etc. Filters results
- // using given url scheme and host / domain. This does not mark the cookies
- // as having been accessed.
+ // using given url scheme, host / domain and path. This does not mark the
+ // cookies as having been accessed.
CookieList GetAllCookiesForURL(const GURL& url);
// Delete all of the cookies.
@@ -114,6 +138,12 @@
// one passed into the function via |delete_after|.
int DeleteAllCreatedAfter(const base::Time& delete_begin, bool sync_to_store);
+ // Delete all cookies that match the host of the given URL
+ // regardless of path. This includes all http_only and secure cookies,
+ // but does not include any domain cookies that may apply to this host.
+ // Returns the number of cookies deleted.
+ int DeleteAllForHost(const GURL& url);
+
// Delete one specific cookie.
bool DeleteCookie(const std::string& domain,
const CanonicalCookie& cookie,
@@ -122,6 +152,8 @@
// Override the default list of schemes that are allowed to be set in
// this cookie store. Calling his overrides the value of
// "enable_file_scheme_".
+ // If this this method is called, it must be called before first use of
+ // the instance (i.e. as part of the instance initialization process.)
void SetCookieableSchemes(const char* schemes[], size_t num_schemes);
// There are some unknowns about how to correctly handle file:// cookies,
@@ -150,6 +182,17 @@
// Should only be called by InitIfNecessary().
void InitStore();
+ // Checks that |cookies_| matches our invariants, and tries to repair any
+ // inconsistencies. (In other words, it does not have duplicate cookies).
+ void EnsureCookiesMapIsValid();
+
+ // Checks for any duplicate cookies for host |key|, which lie between
+ // |begin| and |end|. If any are found, all but the most recent are deleted.
+ // Returns the number of duplicate cookies that were deleted.
+ int TrimDuplicateCookiesForHost(const std::string& key,
+ CookieMap::iterator begin,
+ CookieMap::iterator end);
+
void SetDefaultCookieableSchemes();
void FindCookiesForHostAndDomain(const GURL& url,
@@ -164,8 +207,13 @@
void FindRawCookies(const std::string& key,
bool include_secure,
+ const std::string& path,
CookieList* list);
+ // Internal helper returning all cookies for a given URL. The caller is
+ // assumed to hold lock_ and having called InitIfNecessary().
+ CookieList InternalGetAllCookiesForURL(const GURL& url);
+
// Delete any cookies that are equivalent to |ecc| (same path, key, etc).
// If |skip_httponly| is true, httponly cookies will not be deleted. The
// return value with be true if |skip_httponly| skipped an httponly cookie.
@@ -178,9 +226,28 @@
CanonicalCookie* cc,
bool sync_to_store);
+ // Helper function that sets a canonical cookie, deleting equivalents and
+ // performing garbage collection.
+ bool SetCanonicalCookie(scoped_ptr<CanonicalCookie>* cc,
+ const std::string& cookie_domain,
+ const base::Time& creation_time,
+ const CookieOptions& options);
+
void InternalUpdateCookieAccessTime(CanonicalCookie* cc);
- void InternalDeleteCookie(CookieMap::iterator it, bool sync_to_store);
+ enum DeletionCause {
+ DELETE_COOKIE_EXPLICIT,
+ DELETE_COOKIE_OVERWRITE,
+ DELETE_COOKIE_EXPIRED,
+ DELETE_COOKIE_EVICTED,
+ DELETE_COOKIE_DUPLICATE_IN_BACKING_STORE,
+ DELETE_COOKIE_DONT_RECORD,
+ DELETE_COOKIE_LAST_ENTRY = DELETE_COOKIE_DONT_RECORD
+ };
+
+ // |deletion_cause| argument is for collecting statistics.
+ void InternalDeleteCookie(CookieMap::iterator it, bool sync_to_store,
+ DeletionCause deletion_cause);
// If the number of cookies for host |key|, or globally, are over preset
// maximums, garbage collects, first for the host and then globally, as
@@ -212,6 +279,27 @@
bool HasCookieableScheme(const GURL& url);
+ // Statistics support
+ // Record statistics every kRecordStatisticsIntervalSeconds of uptime.
+ static const int kRecordStatisticsIntervalSeconds = 10 * 60;
+
+ // This function should be called repeatedly, and will record
+ // statistics if a sufficient time period has passed.
+ void RecordPeriodicStats(const base::Time& current_time);
+
+ // Histogram variables; see CookieMonster::InitializeHistograms() in
+ // cookie_monster.cc for details.
+ scoped_refptr<Histogram> histogram_expiration_duration_minutes_;
+ scoped_refptr<Histogram> histogram_between_access_interval_minutes_;
+ scoped_refptr<Histogram> histogram_evicted_last_access_minutes_;
+ scoped_refptr<Histogram> histogram_count_;
+ scoped_refptr<Histogram> histogram_number_duplicate_db_cookies_;
+ scoped_refptr<Histogram> histogram_cookie_deletion_cause_;
+
+ // Initialize the above variables; should only be called from
+ // the constructor.
+ void InitializeHistograms();
+
CookieMap cookies_;
// Indicates whether the cookie store has been initialized. This happens
@@ -231,12 +319,125 @@
std::vector<std::string> cookieable_schemes_;
+ scoped_refptr<Delegate> delegate_;
+
// Lock for thread-safety
Lock lock_;
+ base::Time last_statistic_record_time_;
+
DISALLOW_COPY_AND_ASSIGN(CookieMonster);
};
+class CookieMonster::CanonicalCookie {
+ public:
+
+ // These constructors do no validation or canonicalization of their inputs;
+ // the resulting CanonicalCookies should not be relied on to be canonical
+ // unless the caller has done appropriate validation and canonicalization
+ // themselves.
+ CanonicalCookie() { }
+ CanonicalCookie(const std::string& name,
+ const std::string& value,
+ const std::string& domain,
+ const std::string& path,
+ bool secure,
+ bool httponly,
+ const base::Time& creation,
+ const base::Time& last_access,
+ bool has_expires,
+ const base::Time& expires)
+ : name_(name),
+ value_(value),
+ domain_(domain),
+ path_(path),
+ creation_date_(creation),
+ last_access_date_(last_access),
+ expiry_date_(expires),
+ has_expires_(has_expires),
+ secure_(secure),
+ httponly_(httponly) {
+ }
+
+ // This constructor does canonicalization but not validation.
+ // The result of this constructor should not be relied on in contexts
+ // in which pre-validation of the ParsedCookie has not been done.
+ CanonicalCookie(const GURL& url, const ParsedCookie& pc);
+
+ // Supports the default copy constructor.
+
+ // Creates a canonical cookie from unparsed attribute values.
+ // Canonicalizes and validates inputs. May return NULL if an attribute
+ // value is invalid.
+ static CanonicalCookie* Create(
+ const GURL& url, const std::string& name, const std::string& value,
+ const std::string& domain, const std::string& path,
+ const base::Time& creation_time, const base::Time& expiration_time,
+ bool secure, bool http_only);
+
+ const std::string& Name() const { return name_; }
+ const std::string& Value() const { return value_; }
+ const std::string& Domain() const { return domain_; }
+ const std::string& Path() const { return path_; }
+ const base::Time& CreationDate() const { return creation_date_; }
+ const base::Time& LastAccessDate() const { return last_access_date_; }
+ bool DoesExpire() const { return has_expires_; }
+ bool IsPersistent() const { return DoesExpire(); }
+ const base::Time& ExpiryDate() const { return expiry_date_; }
+ bool IsSecure() const { return secure_; }
+ bool IsHttpOnly() const { return httponly_; }
+
+ bool IsExpired(const base::Time& current) {
+ return has_expires_ && current >= expiry_date_;
+ }
+
+ // Are the cookies considered equivalent in the eyes of RFC 2965.
+ // The RFC says that name must match (case-sensitive), domain must
+ // match (case insensitive), and path must match (case sensitive).
+ // For the case insensitive domain compare, we rely on the domain
+ // having been canonicalized (in
+ // GetCookieDomainKeyWithString->CanonicalizeHost).
+ bool IsEquivalent(const CanonicalCookie& ecc) const {
+ // It seems like it would make sense to take secure and httponly into
+ // account, but the RFC doesn't specify this.
+ // NOTE: Keep this logic in-sync with TrimDuplicateCookiesForHost().
+ return (name_ == ecc.Name() && domain_ == ecc.Domain()
+ && path_ == ecc.Path());
+ }
+
+ void SetLastAccessDate(const base::Time& date) {
+ last_access_date_ = date;
+ }
+
+ bool IsOnPath(const std::string& url_path) const;
+
+ std::string DebugString() const;
+ private:
+ std::string name_;
+ std::string value_;
+ std::string domain_;
+ std::string path_;
+ base::Time creation_date_;
+ base::Time last_access_date_;
+ base::Time expiry_date_;
+ bool has_expires_;
+ bool secure_;
+ bool httponly_;
+};
+
+class CookieMonster::Delegate
+ : public base::RefCountedThreadSafe<CookieMonster::Delegate> {
+ public:
+ // Will be called when a cookie is added or removed. The function is passed
+ // the respective |cookie| which was added to or removed from the cookies.
+ // If |removed| is true, the cookie was deleted.
+ virtual void OnCookieChanged(const CookieMonster::CanonicalCookie& cookie,
+ bool removed) = 0;
+ protected:
+ friend class base::RefCountedThreadSafe<CookieMonster::Delegate>;
+ virtual ~Delegate() {}
+};
+
class CookieMonster::ParsedCookie {
public:
typedef std::pair<std::string, std::string> TokenValuePair;
@@ -269,14 +470,48 @@
bool IsSecure() const { return secure_index_ != 0; }
bool IsHttpOnly() const { return httponly_index_ != 0; }
- // Return the number of attributes, for example, returning 2 for:
+ // Returns the number of attributes, for example, returning 2 for:
// "BLAH=hah; path=/; domain=.google.com"
size_t NumberOfAttributes() const { return pairs_.size() - 1; }
// For debugging only!
std::string DebugString() const;
+ // Returns an iterator pointing to the first terminator character found in
+ // the given string.
+ static std::string::const_iterator FindFirstTerminator(const std::string& s);
+
+ // Given iterators pointing to the beginning and end of a string segment,
+ // returns as output arguments token_start and token_end to the start and end
+ // positions of a cookie attribute token name parsed from the segment, and
+ // updates the segment iterator to point to the next segment to be parsed.
+ // If no token is found, the function returns false.
+ static bool ParseToken(std::string::const_iterator* it,
+ const std::string::const_iterator& end,
+ std::string::const_iterator* token_start,
+ std::string::const_iterator* token_end);
+
+ // Given iterators pointing to the beginning and end of a string segment,
+ // returns as output arguments value_start and value_end to the start and end
+ // positions of a cookie attribute value parsed from the segment, and updates
+ // the segment iterator to point to the next segment to be parsed.
+ static void ParseValue(std::string::const_iterator* it,
+ const std::string::const_iterator& end,
+ std::string::const_iterator* value_start,
+ std::string::const_iterator* value_end);
+
+ // Same as the above functions, except the input is assumed to contain the
+ // desired token/value and nothing else.
+ static std::string ParseTokenString(const std::string& token);
+ static std::string ParseValueString(const std::string& value);
+
private:
+ static const char kTerminator[];
+ static const int kTerminatorLen;
+ static const char kWhitespace[];
+ static const char kValueSeparator[];
+ static const char kTokenSeparator[];
+
void ParseTokenValuePairs(const std::string& cookie_line);
void SetupAttributes();
@@ -296,74 +531,6 @@
DISALLOW_COPY_AND_ASSIGN(ParsedCookie);
};
-
-class CookieMonster::CanonicalCookie {
- public:
- CanonicalCookie() { }
- CanonicalCookie(const std::string& name,
- const std::string& value,
- const std::string& path,
- bool secure,
- bool httponly,
- const base::Time& creation,
- const base::Time& last_access,
- bool has_expires,
- const base::Time& expires)
- : name_(name),
- value_(value),
- path_(path),
- creation_date_(creation),
- last_access_date_(last_access),
- expiry_date_(expires),
- has_expires_(has_expires),
- secure_(secure),
- httponly_(httponly) {
- }
-
- // Supports the default copy constructor.
-
- const std::string& Name() const { return name_; }
- const std::string& Value() const { return value_; }
- const std::string& Path() const { return path_; }
- const base::Time& CreationDate() const { return creation_date_; }
- const base::Time& LastAccessDate() const { return last_access_date_; }
- bool DoesExpire() const { return has_expires_; }
- bool IsPersistent() const { return DoesExpire(); }
- const base::Time& ExpiryDate() const { return expiry_date_; }
- bool IsSecure() const { return secure_; }
- bool IsHttpOnly() const { return httponly_; }
-
- bool IsExpired(const base::Time& current) {
- return has_expires_ && current >= expiry_date_;
- }
-
- // Are the cookies considered equivalent in the eyes of the RFC.
- // This says that the domain and path should string match identically.
- bool IsEquivalent(const CanonicalCookie& ecc) const {
- // It seems like it would make sense to take secure and httponly into
- // account, but the RFC doesn't specify this.
- return name_ == ecc.Name() && path_ == ecc.Path();
- }
-
- void SetLastAccessDate(const base::Time& date) {
- last_access_date_ = date;
- }
-
- bool IsOnPath(const std::string& url_path) const;
-
- std::string DebugString() const;
- private:
- std::string name_;
- std::string value_;
- std::string path_;
- base::Time creation_date_;
- base::Time last_access_date_;
- base::Time expiry_date_;
- bool has_expires_;
- bool secure_;
- bool httponly_;
-};
-
typedef base::RefCountedThreadSafe<CookieMonster::PersistentCookieStore>
RefcountedPersistentCookieStore;
diff --git a/net/base/cookie_monster_perftest.cc b/net/base/cookie_monster_perftest.cc
index 9e76ac8..3d1e7a7 100644
--- a/net/base/cookie_monster_perftest.cc
+++ b/net/base/cookie_monster_perftest.cc
@@ -40,7 +40,7 @@
static const GURL kUrlGoogle("http://www.google.izzle");
TEST(CookieMonsterTest, TestAddCookiesOnSingleHost) {
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
std::vector<std::string> cookies;
for (int i = 0; i < kNumCookies; i++) {
cookies.push_back(StringPrintf("a%03d=b", i));
@@ -67,7 +67,7 @@
}
TEST(CookieMonsterTest, TestAddCookieOnManyHosts) {
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
std::string cookie(kCookieLine);
std::vector<GURL> gurls; // just wanna have ffffuunnn
for (int i = 0; i < kNumCookies; ++i) {
@@ -93,3 +93,71 @@
cm->DeleteAll(false);
timer3.Done();
}
+
+TEST(CookieMonsterTest, TestDomainTree) {
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
+ std::string cookie(kCookieLine);
+ std::string domain_base("top.com");
+
+ std::vector<GURL> gurl_list;
+
+ // Create a balanced binary tree of domains on which the cookie is set.
+ for (int i1=0; i1 < 2; i1++) {
+ std::string domain_base_1((i1 ? "a." : "b.") + domain_base);
+ gurl_list.push_back(GURL(StringPrintf("http://%s",
+ domain_base_1.c_str())));
+ for (int i2=0; i2 < 2; i2++) {
+ std::string domain_base_2((i1 ? "a." : "b.") + domain_base_1);
+ gurl_list.push_back(GURL(StringPrintf("http://%s",
+ domain_base_2.c_str())));
+ for (int i3=0; i3 < 2; i3++) {
+ std::string domain_base_3((i1 ? "a." : "b.") + domain_base_2);
+ gurl_list.push_back(GURL(StringPrintf("http://%s",
+ domain_base_3.c_str())));
+ for (int i4=0; i4 < 2; i4++) {
+ gurl_list.push_back(GURL(StringPrintf("http://%s.%s",
+ i4 ? "a" : "b",
+ domain_base_3.c_str())));
+ }
+ }
+ }
+ }
+
+ for (std::vector<GURL>::const_iterator it = gurl_list.begin();
+ it != gurl_list.end(); it++) {
+ EXPECT_TRUE(cm->SetCookie(*it, cookie));
+ }
+
+ GURL probe_gurl("http://b.a.b.a.top.com/");
+ PerfTimeLogger timer("Cookie_monster_query_domain_tree");
+ for (int i = 0; i < kNumCookies; i++) {
+ cm->GetCookies(probe_gurl);
+ }
+ timer.Done();
+
+ cm->DeleteAll(false);
+ gurl_list.clear();
+
+ // Create a line of 32 domain cookies such that all cookies stored
+ // by effective TLD+1 will apply to probe GURL.
+ // (TLD + 1 is the level above .com/org/net/etc, e.g. "top.com"
+ // or "google.com". "Effective" is added to include sites like
+ // bbc.co.uk, where the effetive TLD+1 is more than one level
+ // below the top level.)
+ gurl_list.push_back(GURL("http://a.top.com"));
+ gurl_list.push_back(GURL("http://b.a.top.com"));
+ gurl_list.push_back(GURL("http://a.b.a.top.com"));
+ gurl_list.push_back(GURL("http://b.a.b.a.top.com"));
+
+ for (int i = 0; i < 8; i++) {
+ for (std::vector<GURL>::const_iterator it = gurl_list.begin();
+ it != gurl_list.end(); it++) {
+ EXPECT_TRUE(cm->SetCookie(*it, StringPrintf("a%03d=b", i)));
+ }
+ }
+ PerfTimeLogger timer2("Cookie_monster_query_domain_line");
+ for (int i = 0; i < kNumCookies; i++) {
+ cm->GetCookies(probe_gurl);
+ }
+ timer2.Done();
+}
diff --git a/net/base/cookie_monster_unittest.cc b/net/base/cookie_monster_unittest.cc
index cdbf869..c61a782 100644
--- a/net/base/cookie_monster_unittest.cc
+++ b/net/base/cookie_monster_unittest.cc
@@ -9,6 +9,7 @@
#include "base/basictypes.h"
#include "base/platform_thread.h"
#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
#include "base/string_util.h"
#include "base/time.h"
#include "googleurl/src/gurl.h"
@@ -19,10 +20,233 @@
using base::TimeDelta;
namespace {
- class ParsedCookieTest : public testing::Test { };
- class CookieMonsterTest : public testing::Test { };
+
+class ParsedCookieTest : public testing::Test { };
+class CookieMonsterTest : public testing::Test { };
+
+// Describes a call to one of the 3 functions of PersistentCookieStore.
+struct CookieStoreCommand {
+ enum Type {
+ ADD,
+ UPDATE_ACCESS_TIME,
+ REMOVE,
+ };
+
+ CookieStoreCommand(Type type,
+ const std::string& key,
+ const net::CookieMonster::CanonicalCookie& cookie)
+ : type(type), key(key), cookie(cookie) {}
+
+ Type type;
+ std::string key; // Only applicable to the ADD command.
+ net::CookieMonster::CanonicalCookie cookie;
+};
+
+// Implementation of PersistentCookieStore that captures the
+// received commands and saves them to a list.
+// The result of calls to Load() can be configured using SetLoadExpectation().
+class MockPersistentCookieStore
+ : public net::CookieMonster::PersistentCookieStore {
+ public:
+ typedef std::vector<CookieStoreCommand> CommandList;
+
+ MockPersistentCookieStore() : load_return_value_(true) {
+ }
+
+ virtual bool Load(
+ std::vector<net::CookieMonster::KeyedCanonicalCookie>* out_cookies) {
+ bool ok = load_return_value_;
+ if (ok)
+ *out_cookies = load_result_;
+ return ok;
+ }
+
+ virtual void AddCookie(const std::string& key,
+ const net::CookieMonster::CanonicalCookie& cookie) {
+ commands_.push_back(
+ CookieStoreCommand(CookieStoreCommand::ADD, key, cookie));
+ }
+
+ virtual void UpdateCookieAccessTime(
+ const net::CookieMonster::CanonicalCookie& cookie) {
+ commands_.push_back(CookieStoreCommand(
+ CookieStoreCommand::UPDATE_ACCESS_TIME, std::string(), cookie));
+ }
+
+ virtual void DeleteCookie(
+ const net::CookieMonster::CanonicalCookie& cookie) {
+ commands_.push_back(
+ CookieStoreCommand(CookieStoreCommand::REMOVE, std::string(), cookie));
+ }
+
+ void SetLoadExpectation(
+ bool return_value,
+ const std::vector<net::CookieMonster::KeyedCanonicalCookie>& result) {
+ load_return_value_ = return_value;
+ load_result_ = result;
+ }
+
+ const CommandList& commands() const {
+ return commands_;
+ }
+
+ private:
+ CommandList commands_;
+
+ // Deferred result to use when Load() is called.
+ bool load_return_value_;
+ std::vector<net::CookieMonster::KeyedCanonicalCookie> load_result_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockPersistentCookieStore);
+};
+
+// Mock for CookieMonster::Delegate
+class MockCookieMonsterDelegate : public net::CookieMonster::Delegate {
+ public:
+ typedef std::pair<net::CookieMonster::CanonicalCookie, bool>
+ CookieNotification;
+
+ MockCookieMonsterDelegate() {}
+
+ virtual void OnCookieChanged(
+ const net::CookieMonster::CanonicalCookie& cookie,
+ bool removed) {
+ CookieNotification notification(cookie, removed);
+ changes_.push_back(notification);
+ }
+
+ const std::vector<CookieNotification>& changes() const { return changes_; }
+
+ void reset() { changes_.clear(); }
+
+ private:
+ virtual ~MockCookieMonsterDelegate() {}
+
+ std::vector<CookieNotification> changes_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockCookieMonsterDelegate);
+};
+
+// Helper to build a list of KeyedCanonicalCookies.
+void AddKeyedCookieToList(
+ const std::string& key,
+ const std::string& cookie_line,
+ const Time& creation_time,
+ std::vector<net::CookieMonster::KeyedCanonicalCookie>* out_list) {
+
+ // Parse the cookie line.
+ net::CookieMonster::ParsedCookie pc(cookie_line);
+ EXPECT_TRUE(pc.IsValid());
+
+ // This helper is simplistic in interpreting a parsed cookie, in order to
+ // avoid duplicated CookieMonster's CanonPath() and CanonExpiration()
+ // functions. Would be nice to export them, and re-use here.
+ EXPECT_FALSE(pc.HasMaxAge());
+ EXPECT_TRUE(pc.HasPath());
+ Time cookie_expires = pc.HasExpires() ?
+ net::CookieMonster::ParseCookieTime(pc.Expires()) : Time();
+ std::string cookie_path = pc.Path();
+
+ scoped_ptr<net::CookieMonster::CanonicalCookie> cookie(
+ new net::CookieMonster::CanonicalCookie(
+ pc.Name(), pc.Value(), key, cookie_path,
+ pc.IsSecure(), pc.IsHttpOnly(),
+ creation_time, creation_time,
+ !cookie_expires.is_null(),
+ cookie_expires));
+
+ out_list->push_back(
+ net::CookieMonster::KeyedCanonicalCookie(
+ key, cookie.release()));
}
+// Helper for DeleteAllForHost test; repopulates CM with same layout
+// each time.
+const char* kTopLevelDomainPlus1 = "http://www.harvard.edu";
+const char* kTopLevelDomainPlus2 = "http://www.math.harvard.edu";
+const char* kTopLevelDomainPlus2Secure = "https://www.math.harvard.edu";
+const char* kTopLevelDomainPlus3 =
+ "http://www.bourbaki.math.harvard.edu";
+const char* kOtherDomain = "http://www.mit.edu";
+
+void PopulateCmForDeleteAllForHost(scoped_refptr<net::CookieMonster> cm) {
+ GURL url_top_level_domain_plus_1(kTopLevelDomainPlus1);
+ GURL url_top_level_domain_plus_2(kTopLevelDomainPlus2);
+ GURL url_top_level_domain_plus_2_secure(kTopLevelDomainPlus2Secure);
+ GURL url_top_level_domain_plus_3(kTopLevelDomainPlus3);
+ GURL url_other(kOtherDomain);
+
+ cm->DeleteAll(true);
+
+ // Static population for probe:
+ // * Three levels of domain cookie (.b.a, .c.b.a, .d.c.b.a)
+ // * Three levels of host cookie (w.b.a, w.c.b.a, w.d.c.b.a)
+ // * http_only cookie (w.c.b.a)
+ // * Two secure cookies (.c.b.a, w.c.b.a)
+ // * Two domain path cookies (.c.b.a/dir1, .c.b.a/dir1/dir2)
+ // * Two host path cookies (w.c.b.a/dir1, w.c.b.a/dir1/dir2)
+
+ // Domain cookies
+ EXPECT_TRUE(cm->SetCookieWithDetails(url_top_level_domain_plus_1,
+ "dom_1", "X", ".harvard.edu", "/",
+ base::Time(), false, false));
+ EXPECT_TRUE(cm->SetCookieWithDetails(url_top_level_domain_plus_2,
+ "dom_2", "X", ".math.harvard.edu", "/",
+ base::Time(), false, false));
+ EXPECT_TRUE(cm->SetCookieWithDetails(url_top_level_domain_plus_3,
+ "dom_3", "X",
+ ".bourbaki.math.harvard.edu", "/",
+ base::Time(), false, false));
+
+ // Host cookies
+ EXPECT_TRUE(cm->SetCookieWithDetails(url_top_level_domain_plus_1,
+ "host_1", "X", "", "/",
+ base::Time(), false, false));
+ EXPECT_TRUE(cm->SetCookieWithDetails(url_top_level_domain_plus_2,
+ "host_2", "X", "", "/",
+ base::Time(), false, false));
+ EXPECT_TRUE(cm->SetCookieWithDetails(url_top_level_domain_plus_3,
+ "host_3", "X", "", "/",
+ base::Time(), false, false));
+
+ // Http_only cookie
+ EXPECT_TRUE(cm->SetCookieWithDetails(url_top_level_domain_plus_2,
+ "httpo_check", "X", "", "/",
+ base::Time(), false, true));
+
+ // Secure cookies
+ EXPECT_TRUE(cm->SetCookieWithDetails(url_top_level_domain_plus_2_secure,
+ "sec_dom", "X", ".math.harvard.edu",
+ "/", base::Time(), true, false));
+ EXPECT_TRUE(cm->SetCookieWithDetails(url_top_level_domain_plus_2_secure,
+ "sec_host", "X", "", "/",
+ base::Time(), true, false));
+
+ // Domain path cookies
+ EXPECT_TRUE(cm->SetCookieWithDetails(url_top_level_domain_plus_2,
+ "dom_path_1", "X",
+ ".math.harvard.edu", "/dir1",
+ base::Time(), false, false));
+ EXPECT_TRUE(cm->SetCookieWithDetails(url_top_level_domain_plus_2,
+ "dom_path_2", "X",
+ ".math.harvard.edu", "/dir1/dir2",
+ base::Time(), false, false));
+
+ // Host path cookies
+ EXPECT_TRUE(cm->SetCookieWithDetails(url_top_level_domain_plus_2,
+ "host_path_1", "X",
+ "", "/dir1",
+ base::Time(), false, false));
+ EXPECT_TRUE(cm->SetCookieWithDetails(url_top_level_domain_plus_2,
+ "host_path_2", "X",
+ "", "/dir1/dir2",
+ base::Time(), false, false));
+
+ EXPECT_EQ(13U, cm->GetAllCookies().size());
+}
+
+} // namespace
+
TEST(ParsedCookieTest, TestBasic) {
net::CookieMonster::ParsedCookie pc("a=b");
@@ -237,6 +461,33 @@
EXPECT_EQ("BB", pc3.Value());
}
+TEST(ParsedCookieTest, ParseTokensAndValues) {
+ EXPECT_EQ("hello",
+ net::CookieMonster::ParsedCookie::ParseTokenString(
+ "hello\nworld"));
+ EXPECT_EQ("fs!!@",
+ net::CookieMonster::ParsedCookie::ParseTokenString(
+ "fs!!@;helloworld"));
+ EXPECT_EQ("hello world\tgood",
+ net::CookieMonster::ParsedCookie::ParseTokenString(
+ "hello world\tgood\rbye"));
+ EXPECT_EQ("A",
+ net::CookieMonster::ParsedCookie::ParseTokenString(
+ "A=B=C;D=E"));
+ EXPECT_EQ("hello",
+ net::CookieMonster::ParsedCookie::ParseValueString(
+ "hello\nworld"));
+ EXPECT_EQ("fs!!@",
+ net::CookieMonster::ParsedCookie::ParseValueString(
+ "fs!!@;helloworld"));
+ EXPECT_EQ("hello world\tgood",
+ net::CookieMonster::ParsedCookie::ParseValueString(
+ "hello world\tgood\rbye"));
+ EXPECT_EQ("A=B=C",
+ net::CookieMonster::ParsedCookie::ParseValueString(
+ "A=B=C;D=E"));
+}
+
static const char kUrlGoogle[] = "http://www.google.izzle";
static const char kUrlGoogleSecure[] = "https://www.google.izzle";
static const char kUrlFtp[] = "ftp://ftp.google.izzle/";
@@ -246,7 +497,9 @@
TEST(CookieMonsterTest, DomainTest) {
GURL url_google(kUrlGoogle);
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<MockPersistentCookieStore> store(
+ new MockPersistentCookieStore);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(store, NULL));
EXPECT_TRUE(cm->SetCookie(url_google, "A=B"));
EXPECT_EQ("A=B", cm->GetCookies(url_google));
EXPECT_TRUE(cm->SetCookie(url_google, "C=D; domain=.google.izzle"));
@@ -272,23 +525,33 @@
EXPECT_EQ("C=D; E=F; G=H",
cm->GetCookies(GURL("http://bla.www.google.izzle")));
EXPECT_EQ("A=B; C=D; E=F; G=H", cm->GetCookies(url_google));
+
+ // Nothing was persisted to the backing store.
+ EXPECT_EQ(0u, store->commands().size());
}
// FireFox recognizes domains containing trailing periods as valid.
// IE and Safari do not. Assert the expected policy here.
TEST(CookieMonsterTest, DomainWithTrailingDotTest) {
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<MockPersistentCookieStore> store(
+ new MockPersistentCookieStore);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(store, NULL));
GURL url_google("http://www.google.com");
EXPECT_FALSE(cm->SetCookie(url_google, "a=1; domain=.www.google.com."));
EXPECT_FALSE(cm->SetCookie(url_google, "b=2; domain=.www.google.com.."));
EXPECT_EQ("", cm->GetCookies(url_google));
+
+ // Nothing was persisted to the backing store.
+ EXPECT_EQ(0u, store->commands().size());
}
// Test that cookies can bet set on higher level domains.
// http://b/issue?id=896491
TEST(CookieMonsterTest, ValidSubdomainTest) {
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<MockPersistentCookieStore> store(
+ new MockPersistentCookieStore);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(store, NULL));
GURL url_abcd("http://a.b.c.d.com");
GURL url_bcd("http://b.c.d.com");
GURL url_cd("http://c.d.com");
@@ -309,6 +572,9 @@
EXPECT_TRUE(cm->SetCookie(url_bcd, "X=cd; domain=.c.d.com"));
EXPECT_EQ("b=2; c=3; d=4; X=bcd; X=cd", cm->GetCookies(url_bcd));
EXPECT_EQ("c=3; d=4; X=cd", cm->GetCookies(url_cd));
+
+ // Nothing was persisted to the backing store.
+ EXPECT_EQ(0u, store->commands().size());
}
// Test that setting a cookie which specifies an invalid domain has
@@ -317,7 +583,10 @@
// http://b/issue?id=896472
TEST(CookieMonsterTest, InvalidDomainTest) {
{
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<MockPersistentCookieStore> store(
+ new MockPersistentCookieStore);
+
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(store, NULL));
GURL url_foobar("http://foo.bar.com");
// More specific sub-domain than allowed.
@@ -348,13 +617,16 @@
EXPECT_FALSE(cm->SetCookie(url_foobar, "o=15; domain=.foo.bar.com#sup"));
EXPECT_EQ("", cm->GetCookies(url_foobar));
+
+ // Nothing was persisted to the backing store.
+ EXPECT_EQ(0u, store->commands().size());
}
{
// Make sure the cookie code hasn't gotten its subdomain string handling
// reversed, missed a suffix check, etc. It's important here that the two
// hosts below have the same domain + registry.
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
GURL url_foocom("http://foo.com.com");
EXPECT_FALSE(cm->SetCookie(url_foocom, "a=1; domain=.foo.com.com.com"));
EXPECT_EQ("", cm->GetCookies(url_foocom));
@@ -366,7 +638,7 @@
// http://b/issue?id=889898
TEST(CookieMonsterTest, DomainWithoutLeadingDotTest) {
{ // The omission of dot results in setting a domain cookie.
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
GURL url_hosted("http://manage.hosted.filefront.com");
GURL url_filefront("http://www.filefront.com");
EXPECT_TRUE(cm->SetCookie(url_hosted, "sawAd=1; domain=filefront.com"));
@@ -375,7 +647,7 @@
}
{ // Even when the domains match exactly, don't consider it host cookie.
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
GURL url("http://www.google.com");
EXPECT_TRUE(cm->SetCookie(url, "a=1; domain=www.google.com"));
EXPECT_EQ("a=1", cm->GetCookies(url));
@@ -387,7 +659,7 @@
// Test that the domain specified in cookie string is treated case-insensitive
// http://b/issue?id=896475.
TEST(CookieMonsterTest, CaseInsensitiveDomainTest) {
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
GURL url_google("http://www.google.com");
EXPECT_TRUE(cm->SetCookie(url_google, "a=1; domain=.GOOGLE.COM"));
EXPECT_TRUE(cm->SetCookie(url_google, "b=2; domain=.wWw.gOOgLE.coM"));
@@ -397,13 +669,13 @@
TEST(CookieMonsterTest, TestIpAddress) {
GURL url_ip("http://1.2.3.4/weee");
{
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
EXPECT_TRUE(cm->SetCookie(url_ip, kValidCookieLine));
EXPECT_EQ("A=B", cm->GetCookies(url_ip));
}
{ // IP addresses should not be able to set domain cookies.
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
EXPECT_FALSE(cm->SetCookie(url_ip, "b=2; domain=.1.2.3.4"));
EXPECT_FALSE(cm->SetCookie(url_ip, "c=3; domain=.3.4"));
EXPECT_EQ("", cm->GetCookies(url_ip));
@@ -419,7 +691,7 @@
// Test host cookies, and setting of cookies on TLD.
TEST(CookieMonsterTest, TestNonDottedAndTLD) {
{
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
GURL url("http://com/");
// Allow setting on "com", (but only as a host cookie).
EXPECT_TRUE(cm->SetCookie(url, "a=1"));
@@ -433,7 +705,7 @@
}
{ // http://com. should be treated the same as http://com.
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
GURL url("http://com./index.html");
EXPECT_TRUE(cm->SetCookie(url, "a=1"));
EXPECT_EQ("a=1", cm->GetCookies(url));
@@ -441,7 +713,7 @@
}
{ // Should not be able to set host cookie from a subdomain.
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
GURL url("http://a.b");
EXPECT_FALSE(cm->SetCookie(url, "a=1; domain=.b"));
EXPECT_FALSE(cm->SetCookie(url, "b=2; domain=b"));
@@ -449,7 +721,7 @@
}
{ // Same test as above, but explicitly on a known TLD (com).
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
GURL url("http://google.com");
EXPECT_FALSE(cm->SetCookie(url, "a=1; domain=.com"));
EXPECT_FALSE(cm->SetCookie(url, "b=2; domain=com"));
@@ -457,7 +729,7 @@
}
{ // Make sure can't set cookie on TLD which is dotted.
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
GURL url("http://google.co.uk");
EXPECT_FALSE(cm->SetCookie(url, "a=1; domain=.co.uk"));
EXPECT_FALSE(cm->SetCookie(url, "b=2; domain=.uk"));
@@ -467,7 +739,7 @@
}
{ // Intranet URLs should only be able to set host cookies.
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
GURL url("http://b");
EXPECT_TRUE(cm->SetCookie(url, "a=1"));
EXPECT_FALSE(cm->SetCookie(url, "b=2; domain=.b"));
@@ -479,7 +751,7 @@
// Test reading/writing cookies when the domain ends with a period,
// as in "www.google.com."
TEST(CookieMonsterTest, TestHostEndsWithDot) {
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
GURL url("http://www.google.com");
GURL url_with_dot("http://www.google.com.");
EXPECT_TRUE(cm->SetCookie(url, "a=1"));
@@ -499,19 +771,19 @@
}
TEST(CookieMonsterTest, InvalidScheme) {
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
EXPECT_FALSE(cm->SetCookie(GURL(kUrlFtp), kValidCookieLine));
}
TEST(CookieMonsterTest, InvalidScheme_Read) {
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
EXPECT_TRUE(cm->SetCookie(GURL(kUrlGoogle), kValidDomainCookieLine));
EXPECT_EQ("", cm->GetCookies(GURL(kUrlFtp)));
}
TEST(CookieMonsterTest, PathTest) {
std::string url("http://www.google.izzle");
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
EXPECT_TRUE(cm->SetCookie(GURL(url), "A=B; path=/wee"));
EXPECT_EQ("A=B", cm->GetCookies(GURL(url + "/wee")));
EXPECT_EQ("A=B", cm->GetCookies(GURL(url + "/wee/")));
@@ -528,7 +800,7 @@
TEST(CookieMonsterTest, HttpOnlyTest) {
GURL url_google(kUrlGoogle);
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
net::CookieOptions options;
options.set_include_httponly();
@@ -562,6 +834,11 @@
const time_t epoch;
};
+struct DomainIsHostOnlyCase {
+ const char* str;
+ const bool is_host_only;
+};
+
} // namespace
TEST(CookieMonsterTest, TestCookieDateParsing) {
@@ -648,9 +925,24 @@
}
}
+TEST(CookieMonsterTest, TestDomainIsHostOnly) {
+ const DomainIsHostOnlyCase tests[] = {
+ { "", true },
+ { "www.google.com", true },
+ { ".google.com", false }
+ };
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ EXPECT_EQ(tests[i].is_host_only,
+ net::CookieMonster::DomainIsHostOnly(tests[i].str));
+ }
+}
+
TEST(CookieMonsterTest, TestCookieDeletion) {
GURL url_google(kUrlGoogle);
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<MockPersistentCookieStore> store(
+ new MockPersistentCookieStore);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(store, NULL));
// Create a session cookie.
EXPECT_TRUE(cm->SetCookie(url_google, kValidCookieLine));
@@ -673,38 +965,53 @@
EXPECT_TRUE(cm->SetCookie(url_google,
std::string(kValidCookieLine) +
"; expires=Mon, 18-Apr-22 22:50:13 GMT"));
+ ASSERT_EQ(1u, store->commands().size());
+ EXPECT_EQ(CookieStoreCommand::ADD, store->commands()[0].type);
+
EXPECT_EQ("A=B", cm->GetCookies(url_google));
// Delete it via Max-Age.
EXPECT_TRUE(cm->SetCookie(url_google,
std::string(kValidCookieLine) + "; max-age=0"));
+ ASSERT_EQ(2u, store->commands().size());
+ EXPECT_EQ(CookieStoreCommand::REMOVE, store->commands()[1].type);
EXPECT_EQ("", cm->GetCookies(url_google));
// Create a persistent cookie.
EXPECT_TRUE(cm->SetCookie(url_google,
std::string(kValidCookieLine) +
"; expires=Mon, 18-Apr-22 22:50:13 GMT"));
+ ASSERT_EQ(3u, store->commands().size());
+ EXPECT_EQ(CookieStoreCommand::ADD, store->commands()[2].type);
EXPECT_EQ("A=B", cm->GetCookies(url_google));
// Delete it via Expires.
EXPECT_TRUE(cm->SetCookie(url_google,
std::string(kValidCookieLine) +
"; expires=Mon, 18-Apr-1977 22:50:13 GMT"));
+ ASSERT_EQ(4u, store->commands().size());
+ EXPECT_EQ(CookieStoreCommand::REMOVE, store->commands()[3].type);
EXPECT_EQ("", cm->GetCookies(url_google));
// Create a persistent cookie.
EXPECT_TRUE(cm->SetCookie(url_google,
std::string(kValidCookieLine) +
"; expires=Mon, 18-Apr-22 22:50:13 GMT"));
+ ASSERT_EQ(5u, store->commands().size());
+ EXPECT_EQ(CookieStoreCommand::ADD, store->commands()[4].type);
EXPECT_EQ("A=B", cm->GetCookies(url_google));
// Delete it via Expires, with a unix epoch of 0.
EXPECT_TRUE(cm->SetCookie(url_google,
std::string(kValidCookieLine) +
"; expires=Thu, 1-Jan-1970 00:00:00 GMT"));
+ ASSERT_EQ(6u, store->commands().size());
+ EXPECT_EQ(CookieStoreCommand::REMOVE, store->commands()[5].type);
EXPECT_EQ("", cm->GetCookies(url_google));
}
TEST(CookieMonsterTest, TestCookieDeleteAll) {
GURL url_google(kUrlGoogle);
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<MockPersistentCookieStore> store(
+ new MockPersistentCookieStore);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(store, NULL));
net::CookieOptions options;
options.set_include_httponly();
@@ -716,11 +1023,26 @@
EXPECT_EQ(2, cm->DeleteAll(false));
EXPECT_EQ("", cm->GetCookiesWithOptions(url_google, options));
+
+ EXPECT_EQ(0u, store->commands().size());
+
+ // Create a persistent cookie.
+ EXPECT_TRUE(cm->SetCookie(url_google,
+ std::string(kValidCookieLine) +
+ "; expires=Mon, 18-Apr-22 22:50:13 GMT"));
+ ASSERT_EQ(1u, store->commands().size());
+ EXPECT_EQ(CookieStoreCommand::ADD, store->commands()[0].type);
+
+ EXPECT_EQ(1, cm->DeleteAll(true)); // sync_to_store = true.
+ ASSERT_EQ(2u, store->commands().size());
+ EXPECT_EQ(CookieStoreCommand::REMOVE, store->commands()[1].type);
+
+ EXPECT_EQ("", cm->GetCookiesWithOptions(url_google, options));
}
TEST(CookieMonsterTest, TestCookieDeleteAllCreatedAfterTimestamp) {
GURL url_google(kUrlGoogle);
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
Time now = Time::Now();
// Nothing has been added so nothing should be deleted.
@@ -748,7 +1070,7 @@
TEST(CookieMonsterTest, TestCookieDeleteAllCreatedBetweenTimestamps) {
GURL url_google(kUrlGoogle);
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
Time now = Time::Now();
// Nothing has been added so nothing should be deleted.
@@ -791,7 +1113,7 @@
TEST(CookieMonsterTest, TestSecure) {
GURL url_google(kUrlGoogle);
GURL url_google_secure(kUrlGoogleSecure);
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
EXPECT_TRUE(cm->SetCookie(url_google, "A=B"));
EXPECT_EQ("A=B", cm->GetCookies(url_google));
@@ -814,7 +1136,7 @@
static Time GetFirstCookieAccessDate(net::CookieMonster* cm) {
const net::CookieMonster::CookieList all_cookies(cm->GetAllCookies());
- return all_cookies.front().second.LastAccessDate();
+ return all_cookies.front().LastAccessDate();
}
static const int kLastAccessThresholdMilliseconds = 200;
@@ -822,7 +1144,7 @@
TEST(CookieMonsterTest, TestLastAccess) {
GURL url_google(kUrlGoogle);
scoped_refptr<net::CookieMonster> cm(
- new net::CookieMonster(kLastAccessThresholdMilliseconds));
+ new net::CookieMonster(NULL, NULL, kLastAccessThresholdMilliseconds));
EXPECT_TRUE(cm->SetCookie(url_google, "A=B"));
const Time last_access_date(GetFirstCookieAccessDate(cm));
@@ -850,7 +1172,7 @@
TEST(CookieMonsterTest, TestHostGarbageCollection) {
GURL url_google(kUrlGoogle);
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
// Add a bunch of cookies on a single host, should purge them.
for (int i = 0; i < 101; i++) {
std::string cookie = StringPrintf("a%03d=b", i);
@@ -865,7 +1187,7 @@
TEST(CookieMonsterTest, TestTotalGarbageCollection) {
scoped_refptr<net::CookieMonster> cm(
- new net::CookieMonster(kLastAccessThresholdMilliseconds));
+ new net::CookieMonster(NULL, NULL, kLastAccessThresholdMilliseconds));
// Add a bunch of cookies on a bunch of host, some should get purged.
const GURL sticky_cookie("http://a0000.izzle");
@@ -900,7 +1222,7 @@
TEST(CookieMonsterTest, NetUtilCookieTest) {
const GURL test_url("http://mojo.jojo.google.izzle/");
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
EXPECT_TRUE(cm->SetCookie(test_url, "foo=bar"));
std::string value = cm->GetCookies(test_url);
@@ -922,15 +1244,15 @@
net::CookieMonster::CookieList cookies = cm->GetAllCookies();
for (net::CookieMonster::CookieList::iterator it = cookies.begin();
it != cookies.end(); ++it)
- if (it->first == domain && it->second.Name() == name)
- return cm->DeleteCookie(domain, it->second, false);
+ if (it->Domain() == domain && it->Name() == name)
+ return cm->DeleteCookie(domain, *it, false);
return false;
}
TEST(CookieMonsterTest, TestDeleteSingleCookie) {
GURL url_google(kUrlGoogle);
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
EXPECT_TRUE(cm->SetCookie(url_google, "A=B"));
EXPECT_TRUE(cm->SetCookie(url_google, "C=D"));
@@ -945,8 +1267,8 @@
}
TEST(CookieMonsterTest, SetCookieableSchemes) {
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
- scoped_refptr<net::CookieMonster> cm_foo(new net::CookieMonster);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
+ scoped_refptr<net::CookieMonster> cm_foo(new net::CookieMonster(NULL, NULL));
// Only cm_foo should allow foo:// cookies.
const char* kSchemes[] = {"foo"};
@@ -962,12 +1284,11 @@
}
TEST(CookieMonsterTest, GetAllCookiesForURL) {
-
GURL url_google(kUrlGoogle);
GURL url_google_secure(kUrlGoogleSecure);
scoped_refptr<net::CookieMonster> cm(
- new net::CookieMonster(kLastAccessThresholdMilliseconds));
+ new net::CookieMonster(NULL, NULL, kLastAccessThresholdMilliseconds));
// Create an httponly cookie.
net::CookieOptions options;
@@ -980,49 +1301,96 @@
EXPECT_TRUE(cm->SetCookieWithOptions(url_google_secure,
"E=F; domain=.google.izzle; secure",
options));
+
const Time last_access_date(GetFirstCookieAccessDate(cm));
PlatformThread::Sleep(kLastAccessThresholdMilliseconds + 20);
- // Check raw cookies.
- net::CookieMonster::CookieList raw_cookies =
+ // Check cookies for url.
+ net::CookieMonster::CookieList cookies =
cm->GetAllCookiesForURL(url_google);
- net::CookieMonster::CookieList::iterator it = raw_cookies.begin();
+ net::CookieMonster::CookieList::iterator it = cookies.begin();
- ASSERT_TRUE(it != raw_cookies.end());
- EXPECT_EQ("www.google.izzle", it->first);
- EXPECT_EQ("A", it->second.Name());
+ ASSERT_TRUE(it != cookies.end());
+ EXPECT_EQ("www.google.izzle", it->Domain());
+ EXPECT_EQ("A", it->Name());
- ASSERT_TRUE(++it != raw_cookies.end());
- EXPECT_EQ(".google.izzle", it->first);
- EXPECT_EQ("C", it->second.Name());
+ ASSERT_TRUE(++it != cookies.end());
+ EXPECT_EQ(".google.izzle", it->Domain());
+ EXPECT_EQ("C", it->Name());
- ASSERT_TRUE(++it == raw_cookies.end());
+ ASSERT_TRUE(++it == cookies.end());
// Test secure cookies.
- raw_cookies = cm->GetAllCookiesForURL(url_google_secure);
- it = raw_cookies.begin();
+ cookies = cm->GetAllCookiesForURL(url_google_secure);
+ it = cookies.begin();
- ASSERT_TRUE(it != raw_cookies.end());
- EXPECT_EQ("www.google.izzle", it->first);
- EXPECT_EQ("A", it->second.Name());
+ ASSERT_TRUE(it != cookies.end());
+ EXPECT_EQ("www.google.izzle", it->Domain());
+ EXPECT_EQ("A", it->Name());
- ASSERT_TRUE(++it != raw_cookies.end());
- EXPECT_EQ(".google.izzle", it->first);
- EXPECT_EQ("C", it->second.Name());
+ ASSERT_TRUE(++it != cookies.end());
+ EXPECT_EQ(".google.izzle", it->Domain());
+ EXPECT_EQ("C", it->Name());
- ASSERT_TRUE(++it != raw_cookies.end());
- EXPECT_EQ(".google.izzle", it->first);
- EXPECT_EQ("E", it->second.Name());
+ ASSERT_TRUE(++it != cookies.end());
+ EXPECT_EQ(".google.izzle", it->Domain());
+ EXPECT_EQ("E", it->Name());
- ASSERT_TRUE(++it == raw_cookies.end());
+ ASSERT_TRUE(++it == cookies.end());
// Reading after a short wait should not update the access date.
- EXPECT_TRUE (last_access_date == GetFirstCookieAccessDate(cm));
+ EXPECT_TRUE(last_access_date == GetFirstCookieAccessDate(cm));
+}
+
+TEST(CookieMonsterTest, GetAllCookiesForURLPathMatching) {
+ GURL url_google(kUrlGoogle);
+ GURL url_google_foo("http://www.google.izzle/foo");
+ GURL url_google_bar("http://www.google.izzle/bar");
+
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
+ net::CookieOptions options;
+
+ EXPECT_TRUE(cm->SetCookieWithOptions(url_google_foo,
+ "A=B; path=/foo;",
+ options));
+ EXPECT_TRUE(cm->SetCookieWithOptions(url_google_bar,
+ "C=D; path=/bar;",
+ options));
+ EXPECT_TRUE(cm->SetCookieWithOptions(url_google,
+ "E=F;",
+ options));
+
+ net::CookieMonster::CookieList cookies =
+ cm->GetAllCookiesForURL(url_google_foo);
+ net::CookieMonster::CookieList::iterator it = cookies.begin();
+
+ ASSERT_TRUE(it != cookies.end());
+ EXPECT_EQ("A", it->Name());
+ EXPECT_EQ("/foo", it->Path());
+
+ ASSERT_TRUE(++it != cookies.end());
+ EXPECT_EQ("E", it->Name());
+ EXPECT_EQ("/", it->Path());
+
+ ASSERT_TRUE(++it == cookies.end());
+
+ cookies = cm->GetAllCookiesForURL(url_google_bar);
+ it = cookies.begin();
+
+ ASSERT_TRUE(it != cookies.end());
+ EXPECT_EQ("C", it->Name());
+ EXPECT_EQ("/bar", it->Path());
+
+ ASSERT_TRUE(++it != cookies.end());
+ EXPECT_EQ("E", it->Name());
+ EXPECT_EQ("/", it->Path());
+
+ ASSERT_TRUE(++it == cookies.end());
}
TEST(CookieMonsterTest, DeleteCookieByName) {
- scoped_refptr<net::CookieMonster> cm(new net::CookieMonster);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
GURL url_google(kUrlGoogle);
EXPECT_TRUE(cm->SetCookie(url_google, "A=A1; path=/"));
@@ -1039,9 +1407,375 @@
EXPECT_EQ(expected_size, cookies.size());
for (net::CookieMonster::CookieList::iterator it = cookies.begin();
it != cookies.end(); ++it) {
- EXPECT_NE("A1", it->second.Value());
- EXPECT_NE("A2", it->second.Value());
+ EXPECT_NE("A1", it->Value());
+ EXPECT_NE("A2", it->Value());
}
}
-// TODO test overwrite cookie
+// Test that overwriting persistent cookies deletes the old one from the
+// backing store.
+TEST(CookieMonsterTest, OverwritePersistentCookie) {
+ GURL url_google("http://www.google.com/");
+ GURL url_chromium("http://chromium.org");
+ scoped_refptr<MockPersistentCookieStore> store(
+ new MockPersistentCookieStore);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(store, NULL));
+
+ // Insert a cookie "a" for path "/path1"
+ EXPECT_TRUE(
+ cm->SetCookie(url_google, "a=val1; path=/path1; "
+ "expires=Mon, 18-Apr-22 22:50:13 GMT"));
+ ASSERT_EQ(1u, store->commands().size());
+ EXPECT_EQ(CookieStoreCommand::ADD, store->commands()[0].type);
+
+ // Insert a cookie "b" for path "/path1"
+ EXPECT_TRUE(
+ cm->SetCookie(url_google, "b=val1; path=/path1; "
+ "expires=Mon, 18-Apr-22 22:50:14 GMT"));
+ ASSERT_EQ(2u, store->commands().size());
+ EXPECT_EQ(CookieStoreCommand::ADD, store->commands()[1].type);
+
+ // Insert a cookie "b" for path "/path1", that is httponly. This should
+ // overwrite the non-http-only version.
+ net::CookieOptions allow_httponly;
+ allow_httponly.set_include_httponly();
+ EXPECT_TRUE(
+ cm->SetCookieWithOptions(url_google,
+ "b=val2; path=/path1; httponly; "
+ "expires=Mon, 18-Apr-22 22:50:14 GMT",
+ allow_httponly));
+ ASSERT_EQ(4u, store->commands().size());
+ EXPECT_EQ(CookieStoreCommand::REMOVE, store->commands()[2].type);
+ EXPECT_EQ(CookieStoreCommand::ADD, store->commands()[3].type);
+
+ // Insert a cookie "a" for path "/path1". This should overwrite.
+ EXPECT_TRUE(cm->SetCookie(url_google,
+ "a=val33; path=/path1; "
+ "expires=Mon, 18-Apr-22 22:50:14 GMT"));
+ ASSERT_EQ(6u, store->commands().size());
+ EXPECT_EQ(CookieStoreCommand::REMOVE, store->commands()[4].type);
+ EXPECT_EQ(CookieStoreCommand::ADD, store->commands()[5].type);
+
+ // Insert a cookie "a" for path "/path2". This should NOT overwrite
+ // cookie "a", since the path is different.
+ EXPECT_TRUE(cm->SetCookie(url_google,
+ "a=val9; path=/path2; "
+ "expires=Mon, 18-Apr-22 22:50:14 GMT"));
+ ASSERT_EQ(7u, store->commands().size());
+ EXPECT_EQ(CookieStoreCommand::ADD, store->commands()[6].type);
+
+ // Insert a cookie "a" for path "/path1", but this time for "chromium.org".
+ // Although the name and path match, the hostnames do not, so shouldn't
+ // overwrite.
+ EXPECT_TRUE(cm->SetCookie(url_chromium,
+ "a=val99; path=/path1; "
+ "expires=Mon, 18-Apr-22 22:50:14 GMT"));
+ ASSERT_EQ(8u, store->commands().size());
+ EXPECT_EQ(CookieStoreCommand::ADD, store->commands()[7].type);
+
+ EXPECT_EQ("a=val33", cm->GetCookies(GURL("http://www.google.com/path1")));
+ EXPECT_EQ("a=val9", cm->GetCookies(GURL("http://www.google.com/path2")));
+ EXPECT_EQ("a=val99", cm->GetCookies(GURL("http://chromium.org/path1")));
+}
+
+// Tests importing from a persistent cookie store that contains duplicate
+// equivalent cookies. This situation should be handled by removing the
+// duplicate cookie (both from the in-memory cache, and from the backing store).
+//
+// This is a regression test for: http://crbug.com/17855.
+TEST(CookieMonsterTest, DontImportDuplicateCookies) {
+ GURL url_google("http://www.google.com/");
+
+ scoped_refptr<MockPersistentCookieStore> store(
+ new MockPersistentCookieStore);
+
+ // We will fill some initial cookies into the PersistentCookieStore,
+ // to simulate a database with 4 duplicates.
+ std::vector<net::CookieMonster::KeyedCanonicalCookie> initial_cookies;
+
+ // Insert 4 cookies with name "X" on path "/", with varying creation
+ // dates. We expect only the most recent one to be preserved following
+ // the import.
+
+ AddKeyedCookieToList("www.google.com",
+ "X=1; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ Time::Now() + TimeDelta::FromDays(3),
+ &initial_cookies);
+
+ AddKeyedCookieToList("www.google.com",
+ "X=2; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ Time::Now() + TimeDelta::FromDays(1),
+ &initial_cookies);
+
+ // ===> This one is the WINNER (biggest creation time). <====
+ AddKeyedCookieToList("www.google.com",
+ "X=3; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ Time::Now() + TimeDelta::FromDays(4),
+ &initial_cookies);
+
+ AddKeyedCookieToList("www.google.com",
+ "X=4; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ Time::Now(),
+ &initial_cookies);
+
+ // Insert 2 cookies with name "X" on path "/2", with varying creation
+ // dates. We expect only the most recent one to be preserved the import.
+
+ // ===> This one is the WINNER (biggest creation time). <====
+ AddKeyedCookieToList("www.google.com",
+ "X=a1; path=/2; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ Time::Now() + TimeDelta::FromDays(9),
+ &initial_cookies);
+
+ AddKeyedCookieToList("www.google.com",
+ "X=a2; path=/2; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ Time::Now() + TimeDelta::FromDays(1),
+ &initial_cookies);
+
+ // Insert 1 cookie with name "Y" on path "/".
+ AddKeyedCookieToList("www.google.com",
+ "Y=a; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ Time::Now() + TimeDelta::FromDays(9),
+ &initial_cookies);
+
+ // Inject our initial cookies into the mock PersistentCookieStore.
+ store->SetLoadExpectation(true, initial_cookies);
+
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(store, NULL));
+
+ // Verify that duplicates were not imported for path "/".
+ // (If this had failed, GetCookies() would have also returned X=1, X=2, X=4).
+ EXPECT_EQ("X=3; Y=a", cm->GetCookies(GURL("http://www.google.com/")));
+
+ // Verify that same-named cookie on a different path ("/x2") didn't get
+ // messed up.
+ EXPECT_EQ("X=a1; X=3; Y=a",
+ cm->GetCookies(GURL("http://www.google.com/2/x")));
+
+ // Verify that the PersistentCookieStore was told to kill its 4 duplicates.
+ ASSERT_EQ(4u, store->commands().size());
+ EXPECT_EQ(CookieStoreCommand::REMOVE, store->commands()[0].type);
+ EXPECT_EQ(CookieStoreCommand::REMOVE, store->commands()[1].type);
+ EXPECT_EQ(CookieStoreCommand::REMOVE, store->commands()[2].type);
+ EXPECT_EQ(CookieStoreCommand::REMOVE, store->commands()[3].type);
+}
+
+TEST(CookieMonsterTest, Delegate) {
+ GURL url_google(kUrlGoogle);
+
+ scoped_refptr<MockPersistentCookieStore> store(
+ new MockPersistentCookieStore);
+ scoped_refptr<MockCookieMonsterDelegate> delegate(
+ new MockCookieMonsterDelegate);
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(store, delegate));
+
+ EXPECT_TRUE(cm->SetCookie(url_google, "A=B"));
+ EXPECT_TRUE(cm->SetCookie(url_google, "C=D"));
+ EXPECT_TRUE(cm->SetCookie(url_google, "E=F"));
+ EXPECT_EQ("A=B; C=D; E=F", cm->GetCookies(url_google));
+ ASSERT_EQ(3u, delegate->changes().size());
+ EXPECT_EQ(false, delegate->changes()[0].second);
+ EXPECT_EQ(url_google.host(), delegate->changes()[0].first.Domain());
+ EXPECT_EQ("A", delegate->changes()[0].first.Name());
+ EXPECT_EQ("B", delegate->changes()[0].first.Value());
+ EXPECT_EQ(url_google.host(), delegate->changes()[1].first.Domain());
+ EXPECT_EQ(false, delegate->changes()[1].second);
+ EXPECT_EQ("C", delegate->changes()[1].first.Name());
+ EXPECT_EQ("D", delegate->changes()[1].first.Value());
+ EXPECT_EQ(url_google.host(), delegate->changes()[2].first.Domain());
+ EXPECT_EQ(false, delegate->changes()[2].second);
+ EXPECT_EQ("E", delegate->changes()[2].first.Name());
+ EXPECT_EQ("F", delegate->changes()[2].first.Value());
+ delegate->reset();
+
+ EXPECT_TRUE(FindAndDeleteCookie(cm, url_google.host(), "C"));
+ EXPECT_EQ("A=B; E=F", cm->GetCookies(url_google));
+ ASSERT_EQ(1u, delegate->changes().size());
+ EXPECT_EQ(url_google.host(), delegate->changes()[0].first.Domain());
+ EXPECT_EQ(true, delegate->changes()[0].second);
+ EXPECT_EQ("C", delegate->changes()[0].first.Name());
+ EXPECT_EQ("D", delegate->changes()[0].first.Value());
+ delegate->reset();
+
+ EXPECT_FALSE(FindAndDeleteCookie(cm, "random.host", "E"));
+ EXPECT_EQ("A=B; E=F", cm->GetCookies(url_google));
+ EXPECT_EQ(0u, delegate->changes().size());
+
+ // Insert a cookie "a" for path "/path1"
+ EXPECT_TRUE(
+ cm->SetCookie(url_google, "a=val1; path=/path1; "
+ "expires=Mon, 18-Apr-22 22:50:13 GMT"));
+ ASSERT_EQ(1u, store->commands().size());
+ EXPECT_EQ(CookieStoreCommand::ADD, store->commands()[0].type);
+ ASSERT_EQ(1u, delegate->changes().size());
+ EXPECT_EQ(false, delegate->changes()[0].second);
+ EXPECT_EQ(url_google.host(), delegate->changes()[0].first.Domain());
+ EXPECT_EQ("a", delegate->changes()[0].first.Name());
+ EXPECT_EQ("val1", delegate->changes()[0].first.Value());
+ delegate->reset();
+
+ // Insert a cookie "a" for path "/path1", that is httponly. This should
+ // overwrite the non-http-only version.
+ net::CookieOptions allow_httponly;
+ allow_httponly.set_include_httponly();
+ EXPECT_TRUE(
+ cm->SetCookieWithOptions(url_google,
+ "a=val2; path=/path1; httponly; "
+ "expires=Mon, 18-Apr-22 22:50:14 GMT",
+ allow_httponly));
+ ASSERT_EQ(3u, store->commands().size());
+ EXPECT_EQ(CookieStoreCommand::REMOVE, store->commands()[1].type);
+ EXPECT_EQ(CookieStoreCommand::ADD, store->commands()[2].type);
+ ASSERT_EQ(2u, delegate->changes().size());
+ EXPECT_EQ(url_google.host(), delegate->changes()[0].first.Domain());
+ EXPECT_EQ(true, delegate->changes()[0].second);
+ EXPECT_EQ("a", delegate->changes()[0].first.Name());
+ EXPECT_EQ("val1", delegate->changes()[0].first.Value());
+ EXPECT_EQ(url_google.host(), delegate->changes()[1].first.Domain());
+ EXPECT_EQ(false, delegate->changes()[1].second);
+ EXPECT_EQ("a", delegate->changes()[1].first.Name());
+ EXPECT_EQ("val2", delegate->changes()[1].first.Value());
+ delegate->reset();
+}
+
+TEST(CookieMonsterTest, SetCookieWithDetails) {
+ GURL url_google(kUrlGoogle);
+ GURL url_google_foo("http://www.google.izzle/foo");
+ GURL url_google_bar("http://www.google.izzle/bar");
+ GURL url_google_secure(kUrlGoogleSecure);
+
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
+
+ EXPECT_TRUE(cm->SetCookieWithDetails(
+ url_google_foo, "A", "B", std::string(), "/foo", base::Time(),
+ false, false));
+ EXPECT_TRUE(cm->SetCookieWithDetails(
+ url_google_bar, "C", "D", "google.izzle", "/bar", base::Time(),
+ false, true));
+ EXPECT_TRUE(cm->SetCookieWithDetails(
+ url_google, "E", "F", std::string(), std::string(), base::Time(),
+ true, false));
+
+ // Test that malformed attributes fail to set the cookie.
+ EXPECT_FALSE(cm->SetCookieWithDetails(
+ url_google_foo, " A", "B", std::string(), "/foo", base::Time(),
+ false, false));
+ EXPECT_FALSE(cm->SetCookieWithDetails(
+ url_google_foo, "A;", "B", std::string(), "/foo", base::Time(),
+ false, false));
+ EXPECT_FALSE(cm->SetCookieWithDetails(
+ url_google_foo, "A=", "B", std::string(), "/foo", base::Time(),
+ false, false));
+ EXPECT_FALSE(cm->SetCookieWithDetails(
+ url_google_foo, "A", "B", "google.ozzzzzzle", "foo", base::Time(),
+ false, false));
+ EXPECT_FALSE(cm->SetCookieWithDetails(
+ url_google_foo, "A=", "B", std::string(), "foo", base::Time(),
+ false, false));
+
+ net::CookieMonster::CookieList cookies =
+ cm->GetAllCookiesForURL(url_google_foo);
+ net::CookieMonster::CookieList::iterator it = cookies.begin();
+
+ ASSERT_TRUE(it != cookies.end());
+ EXPECT_EQ("A", it->Name());
+ EXPECT_EQ("B", it->Value());
+ EXPECT_EQ("www.google.izzle", it->Domain());
+ EXPECT_EQ("/foo", it->Path());
+ EXPECT_FALSE(it->DoesExpire());
+ EXPECT_FALSE(it->IsSecure());
+ EXPECT_FALSE(it->IsHttpOnly());
+
+ ASSERT_TRUE(++it == cookies.end());
+
+ cookies = cm->GetAllCookiesForURL(url_google_bar);
+ it = cookies.begin();
+
+ ASSERT_TRUE(it != cookies.end());
+ EXPECT_EQ("C", it->Name());
+ EXPECT_EQ("D", it->Value());
+ EXPECT_EQ(".google.izzle", it->Domain());
+ EXPECT_EQ("/bar", it->Path());
+ EXPECT_FALSE(it->IsSecure());
+ EXPECT_TRUE(it->IsHttpOnly());
+
+ ASSERT_TRUE(++it == cookies.end());
+
+ cookies = cm->GetAllCookiesForURL(url_google_secure);
+ it = cookies.begin();
+
+ ASSERT_TRUE(it != cookies.end());
+ EXPECT_EQ("E", it->Name());
+ EXPECT_EQ("F", it->Value());
+ EXPECT_EQ("/", it->Path());
+ EXPECT_EQ("www.google.izzle", it->Domain());
+ EXPECT_TRUE(it->IsSecure());
+ EXPECT_FALSE(it->IsHttpOnly());
+
+ ASSERT_TRUE(++it == cookies.end());
+}
+
+
+
+TEST(CookieMonsterTest, DeleteAllForHost) {
+ scoped_refptr<net::CookieMonster> cm(new net::CookieMonster(NULL, NULL));
+
+ // Test probes:
+ // * Non-secure URL, mid-level (http://w.c.b.a)
+ // * Secure URL, mid-level (https://w.c.b.a)
+ // * URL with path, mid-level (https:/w.c.b.a/dir1/xx)
+ // All three tests should nuke only the midlevel host cookie,
+ // the http_only cookie, the host secure cookie, and the two host
+ // path cookies. http_only, secure, and paths are ignored by
+ // this call, and domain cookies arent touched.
+ PopulateCmForDeleteAllForHost(cm);
+ EXPECT_EQ("dom_1=X; dom_2=X; dom_3=X; host_3=X",
+ cm->GetCookies(GURL(kTopLevelDomainPlus3)));
+ EXPECT_EQ("dom_1=X; dom_2=X; host_2=X; sec_dom=X; sec_host=X",
+ cm->GetCookies(GURL(kTopLevelDomainPlus2Secure)));
+ EXPECT_EQ("dom_1=X; host_1=X", cm->GetCookies(GURL(kTopLevelDomainPlus1)));
+ EXPECT_EQ("dom_path_2=X; host_path_2=X; dom_path_1=X; host_path_1=X; "
+ "dom_1=X; dom_2=X; host_2=X; sec_dom=X; sec_host=X",
+ cm->GetCookies(GURL(kTopLevelDomainPlus2Secure +
+ std::string("/dir1/dir2/xxx"))));
+
+ EXPECT_EQ(5, cm->DeleteAllForHost(GURL(kTopLevelDomainPlus2)));
+ EXPECT_EQ(8U, cm->GetAllCookies().size());
+
+ EXPECT_EQ("dom_1=X; dom_2=X; dom_3=X; host_3=X",
+ cm->GetCookies(GURL(kTopLevelDomainPlus3)));
+ EXPECT_EQ("dom_1=X; dom_2=X; sec_dom=X",
+ cm->GetCookies(GURL(kTopLevelDomainPlus2Secure)));
+ EXPECT_EQ("dom_1=X; host_1=X", cm->GetCookies(GURL(kTopLevelDomainPlus1)));
+ EXPECT_EQ("dom_path_2=X; dom_path_1=X; dom_1=X; dom_2=X; sec_dom=X",
+ cm->GetCookies(GURL(kTopLevelDomainPlus2Secure +
+ std::string("/dir1/dir2/xxx"))));
+
+ PopulateCmForDeleteAllForHost(cm);
+ EXPECT_EQ(5, cm->DeleteAllForHost(GURL(kTopLevelDomainPlus2Secure)));
+ EXPECT_EQ(8U, cm->GetAllCookies().size());
+
+ EXPECT_EQ("dom_1=X; dom_2=X; dom_3=X; host_3=X",
+ cm->GetCookies(GURL(kTopLevelDomainPlus3)));
+ EXPECT_EQ("dom_1=X; dom_2=X; sec_dom=X",
+ cm->GetCookies(GURL(kTopLevelDomainPlus2Secure)));
+ EXPECT_EQ("dom_1=X; host_1=X", cm->GetCookies(GURL(kTopLevelDomainPlus1)));
+ EXPECT_EQ("dom_path_2=X; dom_path_1=X; dom_1=X; dom_2=X; sec_dom=X",
+ cm->GetCookies(GURL(kTopLevelDomainPlus2Secure +
+ std::string("/dir1/dir2/xxx"))));
+
+ PopulateCmForDeleteAllForHost(cm);
+ EXPECT_EQ(5, cm->DeleteAllForHost(GURL(kTopLevelDomainPlus2Secure +
+ std::string("/dir1/xxx"))));
+ EXPECT_EQ(8U, cm->GetAllCookies().size());
+
+ EXPECT_EQ("dom_1=X; dom_2=X; dom_3=X; host_3=X",
+ cm->GetCookies(GURL(kTopLevelDomainPlus3)));
+ EXPECT_EQ("dom_1=X; dom_2=X; sec_dom=X",
+ cm->GetCookies(GURL(kTopLevelDomainPlus2Secure)));
+ EXPECT_EQ("dom_1=X; host_1=X", cm->GetCookies(GURL(kTopLevelDomainPlus1)));
+ EXPECT_EQ("dom_path_2=X; dom_path_1=X; dom_1=X; dom_2=X; sec_dom=X",
+ cm->GetCookies(GURL(kTopLevelDomainPlus2Secure +
+ std::string("/dir1/dir2/xxx"))));
+
+}
diff --git a/net/base/cookie_options.h b/net/base/cookie_options.h
index e9301fe..9995a05 100644
--- a/net/base/cookie_options.h
+++ b/net/base/cookie_options.h
@@ -14,12 +14,22 @@
// Default is to exclude httponly, which means:
// - reading operations will not return httponly cookies.
// - writing operations will not write httponly cookies.
- CookieOptions() : exclude_httponly_(true) {}
+ CookieOptions()
+ : exclude_httponly_(true),
+ force_session_(false) {
+ }
+
void set_exclude_httponly() { exclude_httponly_ = true; }
void set_include_httponly() { exclude_httponly_ = false; }
bool exclude_httponly() const { return exclude_httponly_; }
+
+ // Forces a cookie to be saved as a session cookie.
+ void set_force_session() { force_session_ = true; }
+ bool force_session() const { return force_session_; }
+
private:
bool exclude_httponly_;
+ bool force_session_;
};
} // namespace net
diff --git a/net/base/cookie_policy.h b/net/base/cookie_policy.h
index d2df2f5..5e57c90 100644
--- a/net/base/cookie_policy.h
+++ b/net/base/cookie_policy.h
@@ -5,18 +5,30 @@
#ifndef NET_BASE_COOKIE_POLICY_H_
#define NET_BASE_COOKIE_POLICY_H_
+#include <string>
+
#include "net/base/completion_callback.h"
class GURL;
namespace net {
+// Alternative success codes for CookiePolicy::Can{Get,Set}Cookie(s).
+enum {
+ OK_FOR_SESSION_ONLY = 1, // The cookie may be set but not persisted.
+};
+
class CookiePolicy {
public:
- // Determines if the URL's cookies may be read. Returns OK if allowed to
- // read cookies for the given URL. Returns ERR_IO_PENDING to indicate that
- // the completion callback will be notified (asynchronously and on the
- // current thread) of the final result. Note: The completion callback must
+ // Determines if the URL's cookies may be read.
+ //
+ // Returns:
+ // OK - if allowed to read cookies
+ // ERR_ACCESS_DENIED - if not allowed to read cookies
+ // ERR_IO_PENDING - if the result will be determined asynchronously
+ //
+ // If the return value is ERR_IO_PENDING, then the given callback will be
+ // notified once the final result is determined. Note: The callback must
// remain valid until notified.
virtual int CanGetCookies(const GURL& url,
const GURL& first_party_for_cookies,
@@ -27,6 +39,19 @@
// the completion callback will be notified (asynchronously and on the
// current thread) of the final result. Note: The completion callback must
// remain valid until notified.
+
+ // Determines if the URL's cookies may be written.
+ //
+ // Returns:
+ // OK - if allowed to write cookies
+ // OK_FOR_SESSION_ONLY - if allowed to write cookies, but forces them to
+ // be stored as session cookies
+ // ERR_ACCESS_DENIED - if not allowed to write cookies
+ // ERR_IO_PENDING - if the result will be determined asynchronously
+ //
+ // If the return value is ERR_IO_PENDING, then the given callback will be
+ // notified once the final result is determined. Note: The callback must
+ // remain valid until notified.
virtual int CanSetCookie(const GURL& url,
const GURL& first_party_for_cookies,
const std::string& cookie_line,
diff --git a/net/base/dir_header.html b/net/base/dir_header.html
index 0383147..ad4ec0a 100644
--- a/net/base/dir_header.html
+++ b/net/base/dir_header.html
@@ -1,5 +1,7 @@
<!DOCTYPE html>
+
<html>
+
<head>
<script>
@@ -15,9 +17,13 @@
var row = document.createElement("tr");
var file_cell = document.createElement("td");
var link = document.createElement("a");
+
+ link.className = isdir ? "icon dir" : "icon file";
+
if (name == "..") {
link.href = root + "..";
link.innerText = document.getElementById("parentDirText").innerText;
+ link.className = "icon up";
size = "";
date_modified = "";
} else {
@@ -54,29 +60,84 @@
function onListingParsingError() {
var box = document.getElementById("listingParsingErrorBox");
- box.innerHTML = box.innerHTML.replace("LOCATION", document.location + "?raw");
+ box.innerHTML = box.innerHTML.replace("LOCATION", encodeURI(document.location)
+ + "?raw");
box.style.display = "";
}
</script>
-<title id="title"></title>
<style>
- h1 { white-space: nowrap; }
- td.detailsColumn { text-align: right; padding-left: 30px; white-space: nowrap; }
- #listingParsingErrorBox { border: 1px solid black; background: #fae691; padding: 10px; display: none }
+
+ h1 {
+ border-bottom: 1px solid #c0c0c0;
+ margin-bottom: 10px;
+ padding-bottom: 10px;
+ white-space: nowrap;
+ }
+
+ table {
+ border-collapse: collapse;
+ }
+
+ tr.header {
+ font-weight: bold;
+ }
+
+ td.detailsColumn {
+ padding-left: 2em;
+ text-align: right;
+ white-space: nowrap;
+ }
+
+ a.icon {
+ padding-left: 1.5em;
+ text-decoration: none;
+ }
+
+ a.icon:hover {
+ text-decoration: underline;
+ }
+
+ a.file {
+ background : url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAABnRSTlMAAAAAAABupgeRAAABHUlEQVR42o2RMW7DIBiF3498iHRJD5JKHurL+CRVBp+i2T16tTynF2gO0KSb5ZrBBl4HHDBuK/WXACH4eO9/CAAAbdvijzLGNE1TVZXfZuHg6XCAQESAZXbOKaXO57eiKG6ft9PrKQIkCQqFoIiQFBGlFIB5nvM8t9aOX2Nd18oDzjnPgCDpn/BH4zh2XZdlWVmWiUK4IgCBoFMUz9eP6zRN75cLgEQhcmTQIbl72O0f9865qLAAsURAAgKBJKEtgLXWvyjLuFsThCSstb8rBCaAQhDYWgIZ7myM+TUBjDHrHlZcbMYYk34cN0YSLcgS+wL0fe9TXDMbY33fR2AYBvyQ8L0Gk8MwREBrTfKe4TpTzwhArXWi8HI84h/1DfwI5mhxJamFAAAAAElFTkSuQmCC ") left top no-repeat;
+ }
+
+ a.dir {
+ background : url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAd5JREFUeNqMU79rFUEQ/vbuodFEEkzAImBpkUabFP4ldpaJhZXYm/RiZWsv/hkWFglBUyTIgyAIIfgIRjHv3r39MePM7N3LcbxAFvZ2b2bn22/mm3XMjF+HL3YW7q28YSIw8mBKoBihhhgCsoORot9d3/ywg3YowMXwNde/PzGnk2vn6PitrT+/PGeNaecg4+qNY3D43vy16A5wDDd4Aqg/ngmrjl/GoN0U5V1QquHQG3q+TPDVhVwyBffcmQGJmSVfyZk7R3SngI4JKfwDJ2+05zIg8gbiereTZRHhJ5KCMOwDFLjhoBTn2g0ghagfKeIYJDPFyibJVBtTREwq60SpYvh5++PpwatHsxSm9QRLSQpEVSd7/TYJUb49TX7gztpjjEffnoVw66+Ytovs14Yp7HaKmUXeX9rKUoMoLNW3srqI5fWn8JejrVkK0QcrkFLOgS39yoKUQe292WJ1guUHG8K2o8K00oO1BTvXoW4yasclUTgZYJY9aFNfAThX5CZRmczAV52oAPoupHhWRIUUAOoyUIlYVaAa/VbLbyiZUiyFbjQFNwiZQSGl4IDy9sO5Wrty0QLKhdZPxmgGcDo8ejn+c/6eiK9poz15Kw7Dr/vN/z6W7q++091/AQYA5mZ8GYJ9K0AAAAAASUVORK5CYII= ") left top no-repeat;
+ }
+
+ a.up {
+ background : url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmlJREFUeNpsU0toU0EUPfPysx/tTxuDH9SCWhUDooIbd7oRUUTMouqi2iIoCO6lceHWhegy4EJFinWjrlQUpVm0IIoFpVDEIthm0dpikpf3ZuZ6Z94nrXhhMjM3c8895977BBHB2PznK8WPtDgyWH5q77cPH8PpdXuhpQT4ifR9u5sfJb1bmw6VivahATDrxcRZ2njfoaMv+2j7mLDn93MPiNRMvGbL18L9IpF8h9/TN+EYkMffSiOXJ5+hkD+PdqcLpICWHOHc2CC+LEyA/K+cKQMnlQHJX8wqYG3MAJy88Wa4OLDvEqAEOpJd0LxHIMdHBziowSwVlF8D6QaicK01krw/JynwcKoEwZczewroTvZirlKJs5CqQ5CG8pb57FnJUA0LYCXMX5fibd+p8LWDDemcPZbzQyjvH+Ki1TlIciElA7ghwLKV4kRZstt2sANWRjYTAGzuP2hXZFpJ/GsxgGJ0ox1aoFWsDXyyxqCs26+ydmagFN/rRjymJ1898bzGzmQE0HCZpmk5A0RFIv8Pn0WYPsiu6t/Rsj6PauVTwffTSzGAGZhUG2F06hEc9ibS7OPMNp6ErYFlKavo7MkhmTqCxZ/jwzGA9Hx82H2BZSw1NTN9Gx8ycHkajU/7M+jInsDC7DiaEmo1bNl1AMr9ASFgqVu9MCTIzoGUimXVAnnaN0PdBBDCCYbEtMk6wkpQwIG0sn0PQIUF4GsTwLSIFKNqF6DVrQq+IWVrQDxAYQC/1SsYOI4pOxKZrfifiUSbDUisif7XlpGIPufXd/uvdvZm760M0no1FZcnrzUdjw7au3vu/BVgAFLXeuTxhTXVAAAAAElFTkSuQmCC ") left top no-repeat;
+ }
+
+ #listingParsingErrorBox {
+ border: 1px solid black;
+ background: #fae691;
+ padding: 10px;
+ display: none;
+ }
</style>
+
+<title id="title"></title>
+
</head>
+
<body>
+
<div id="listingParsingErrorBox" i18n-values=".innerHTML:listingParsingErrorBoxText"></div>
+
<span id="parentDirText" style="display:none" i18n-content="parentDirText"></span>
+
<h1 id="header" i18n-content="header"></h1>
-<hr/>
+
<table id="table">
-<tr style="font-weight: bold">
- <td i18n-content="headerName"></td>
- <td class="detailsColumn" i18n-content="headerSize"></td>
- <td class="detailsColumn" i18n-content="headerDateModified"></td>
-</tr>
+ <tr class="header">
+ <td i18n-content="headerName"></td>
+ <td class="detailsColumn" i18n-content="headerSize"></td>
+ <td class="detailsColumn" i18n-content="headerDateModified"></td>
+ </tr>
</table>
+
</body>
+
</html>
diff --git a/net/base/directory_lister.cc b/net/base/directory_lister.cc
index ef3130b..ab45e2f 100644
--- a/net/base/directory_lister.cc
+++ b/net/base/directory_lister.cc
@@ -41,18 +41,21 @@
// comparison function on the filenames for sorting in the user's locale.
static bool CompareFindInfo(const file_util::FileEnumerator::FindInfo& a,
const file_util::FileEnumerator::FindInfo& b) {
+ // Parent directory before all else.
+ if (file_util::IsDotDot(file_util::FileEnumerator::GetFilename(a)))
+ return true;
+ if (file_util::IsDotDot(file_util::FileEnumerator::GetFilename(b)))
+ return false;
+
+ // Directories before regular files.
bool a_is_directory = file_util::FileEnumerator::IsDirectory(a);
bool b_is_directory = file_util::FileEnumerator::IsDirectory(b);
if (a_is_directory != b_is_directory)
return a_is_directory;
-#if defined(OS_WIN)
- return file_util::LocaleAwareCompareFilenames(FilePath(a.cFileName),
- FilePath(b.cFileName));
-#elif defined(OS_POSIX)
- return file_util::LocaleAwareCompareFilenames(FilePath(a.filename),
- FilePath(b.filename));
-#endif
+ return file_util::LocaleAwareCompareFilenames(
+ file_util::FileEnumerator::GetFilename(a),
+ file_util::FileEnumerator::GetFilename(b));
}
DirectoryLister::DirectoryLister(const FilePath& dir,
diff --git a/net/base/directory_lister_unittest.cc b/net/base/directory_lister_unittest.cc
index 85e2b64..f3cd33b 100644
--- a/net/base/directory_lister_unittest.cc
+++ b/net/base/directory_lister_unittest.cc
@@ -35,17 +35,13 @@
!file_util::FileEnumerator::IsDirectory(file_list_[current])) {
continue;
}
+ EXPECT_FALSE(file_util::IsDotDot(
+ file_util::FileEnumerator::GetFilename(file_list_[current])));
EXPECT_EQ(file_util::FileEnumerator::IsDirectory(file_list_[previous]),
file_util::FileEnumerator::IsDirectory(file_list_[current]));
-#if defined(OS_WIN)
EXPECT_TRUE(file_util::LocaleAwareCompareFilenames(
- FilePath(file_list_[previous].cFileName),
- FilePath(file_list_[current].cFileName)));
-#elif defined(OS_POSIX)
- EXPECT_TRUE(file_util::LocaleAwareCompareFilenames(
- FilePath(file_list_[previous].filename),
- FilePath(file_list_[current].filename)));
-#endif
+ file_util::FileEnumerator::GetFilename(file_list_[previous]),
+ file_util::FileEnumerator::GetFilename(file_list_[current])));
}
}
}
diff --git a/net/base/dns_util.cc b/net/base/dns_util.cc
index db5f606..87d1866 100644
--- a/net/base/dns_util.cc
+++ b/net/base/dns_util.cc
@@ -70,4 +70,12 @@
return true;
}
+std::string TrimEndingDot(const std::string& host) {
+ std::string host_trimmed = host;
+ size_t len = host_trimmed.length();
+ if (len > 1 && host_trimmed[len - 1] == '.')
+ host_trimmed.erase(len - 1);
+ return host_trimmed;
+}
+
} // namespace net
diff --git a/net/base/dns_util.h b/net/base/dns_util.h
index 8eb98f2..f0d2051 100644
--- a/net/base/dns_util.h
+++ b/net/base/dns_util.h
@@ -20,6 +20,9 @@
// characters as given in RFC 3490, 4.1, 3(a)
bool IsSTD3ASCIIValidCharacter(char c);
+// Returns the hostname by trimming the ending dot, if one exists.
+std::string TrimEndingDot(const std::string& host);
+
} // namespace net
#endif // NET_BASE_DNS_UTIL_H_
diff --git a/net/base/effective_tld_names.cc b/net/base/effective_tld_names.cc
index 4a4b325..3618949 100644
--- a/net/base/effective_tld_names.cc
+++ b/net/base/effective_tld_names.cc
@@ -1,5 +1,5 @@
/* C++ code produced by gperf version 3.0.3 */
-/* Command-line: gperf -a -L C++ -C -c -o -t -k '*' -NFindDomain -D -m 2 effective_tld_names.gperf */
+/* Command-line: gperf -a -L C++ -C -c -o -t -k '*' -NFindDomain -D -m 5 effective_tld_names.gperf */
#if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \
&& ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \
@@ -42,12 +42,12 @@
int type; // 1: exception, 2: wildcard
};
-#define TOTAL_KEYWORDS 3637
+#define TOTAL_KEYWORDS 3698
#define MIN_WORD_LENGTH 2
#define MAX_WORD_LENGTH 43
-#define MIN_HASH_VALUE 3
-#define MAX_HASH_VALUE 54846
-/* maximum key range = 54844, duplicates = 0 */
+#define MIN_HASH_VALUE 10
+#define MAX_HASH_VALUE 63219
+/* maximum key range = 63210, duplicates = 0 */
class Perfect_Hash
{
@@ -62,35 +62,35 @@
{
static const unsigned short asso_values[] =
{
- 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847,
- 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847,
- 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847,
- 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847,
- 54847, 54847, 54847, 54847, 54847, 2, 18, 10, 9511, 3,
- 23, 0, 0, 0, 1, 1, 0, 9795, 61, 0,
- 0, 54847, 2, 54847, 3, 3905, 0, 54847, 0, 54847,
- 54847, 54847, 0, 15, 14, 12, 11, 10, 9, 5,
- 8, 7, 6, 54847, 54847, 54847, 54847, 54847, 54847, 54847,
- 54847, 54847, 54847, 54847, 54847, 54847, 54847, 1028, 6669, 713,
- 4, 14, 5079, 1392, 4218, 1087, 5578, 2938, 3327, 524,
- 96, 0, 1333, 1976, 64, 146, 154, 351, 443, 4591,
- 818, 25, 595, 3198, 22, 8347, 2, 1449, 0, 3,
- 1, 6029, 11607, 1285, 661, 13, 117, 6398, 1807, 13010,
- 1479, 12724, 2783, 13030, 1602, 4, 59, 102, 54847, 54847,
- 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847,
- 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847,
- 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847,
- 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847,
- 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847,
- 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847,
- 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847,
- 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847,
- 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847,
- 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847,
- 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847,
- 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847,
- 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847, 54847,
- 54847
+ 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220,
+ 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220,
+ 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220,
+ 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220,
+ 63220, 63220, 63220, 63220, 63220, 5, 8, 63220, 4, 3,
+ 4, 3, 5, 3, 4, 3, 3, 3, 3068, 20,
+ 63220, 4, 4795, 4, 63220, 4, 63220, 4, 63220, 3,
+ 63220, 63220, 63220, 19, 18, 9, 17, 16, 15, 8,
+ 14, 11, 10, 63220, 63220, 63220, 63220, 63220, 63220, 63220,
+ 63220, 63220, 63220, 63220, 63220, 63220, 63220, 506, 15138, 8462,
+ 11, 115, 12215, 3362, 1446, 939, 5031, 5868, 30, 365,
+ 270, 5, 137, 643, 97, 14, 26, 289, 1951, 3657,
+ 8038, 831, 6, 653, 3, 13576, 4, 1099, 49, 48,
+ 5, 38, 2900, 10663, 1564, 130, 13960, 3727, 3920, 13502,
+ 4843, 16568, 1644, 9521, 1597, 7, 22, 12, 63220, 63220,
+ 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220,
+ 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220,
+ 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220,
+ 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220,
+ 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220,
+ 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220,
+ 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220,
+ 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220,
+ 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220,
+ 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220,
+ 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220,
+ 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220,
+ 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220, 63220,
+ 63220
};
register int hval = len;
@@ -205,19 +205,19 @@
hval += asso_values[(unsigned char)str[7]];
/*FALLTHROUGH*/
case 7:
- hval += asso_values[(unsigned char)str[6]+1];
+ hval += asso_values[(unsigned char)str[6]];
/*FALLTHROUGH*/
case 6:
- hval += asso_values[(unsigned char)str[5]+19];
+ hval += asso_values[(unsigned char)str[5]+16];
/*FALLTHROUGH*/
case 5:
- hval += asso_values[(unsigned char)str[4]+2];
+ hval += asso_values[(unsigned char)str[4]];
/*FALLTHROUGH*/
case 4:
- hval += asso_values[(unsigned char)str[3]+4];
+ hval += asso_values[(unsigned char)str[3]+3];
/*FALLTHROUGH*/
case 3:
- hval += asso_values[(unsigned char)str[2]+12];
+ hval += asso_values[(unsigned char)str[2]+13];
/*FALLTHROUGH*/
case 2:
hval += asso_values[(unsigned char)str[1]];
@@ -234,1674 +234,2058 @@
{
static const struct DomainRule wordlist[] =
{
-#line 1519 "effective_tld_names.gperf"
- {"io", 0},
-#line 1180 "effective_tld_names.gperf"
- {"gov", 0},
-#line 1107 "effective_tld_names.gperf"
- {"gd", 0},
-#line 1441 "effective_tld_names.gperf"
- {"id", 2},
-#line 815 "effective_tld_names.gperf"
- {"edu", 0},
-#line 2235 "effective_tld_names.gperf"
- {"no", 0},
-#line 1112 "effective_tld_names.gperf"
- {"ge", 0},
-#line 1449 "effective_tld_names.gperf"
- {"ie", 0},
-#line 909 "effective_tld_names.gperf"
- {"ee", 0},
-#line 538 "effective_tld_names.gperf"
- {"co", 0},
-#line 1336 "effective_tld_names.gperf"
- {"gy", 0},
-#line 460 "effective_tld_names.gperf"
- {"cd", 0},
-#line 2090 "effective_tld_names.gperf"
- {"ne", 0},
-#line 2107 "effective_tld_names.gperf"
- {"net", 0},
-#line 1242 "effective_tld_names.gperf"
- {"gov.mo", 0},
-#line 1240 "effective_tld_names.gperf"
- {"gov.mk", 0},
-#line 1241 "effective_tld_names.gperf"
- {"gov.mn", 0},
-#line 1190 "effective_tld_names.gperf"
- {"gov.bm", 0},
-#line 1191 "effective_tld_names.gperf"
- {"gov.bo", 0},
-#line 869 "effective_tld_names.gperf"
- {"edu.mo", 0},
-#line 867 "effective_tld_names.gperf"
- {"edu.mk", 0},
-#line 868 "effective_tld_names.gperf"
- {"edu.mn", 0},
-#line 824 "effective_tld_names.gperf"
- {"edu.bm", 0},
-#line 825 "effective_tld_names.gperf"
- {"edu.bo", 0},
-#line 1198 "effective_tld_names.gperf"
- {"gov.cm", 0},
-#line 1200 "effective_tld_names.gperf"
- {"gov.co", 0},
-#line 1199 "effective_tld_names.gperf"
- {"gov.cn", 0},
-#line 740 "effective_tld_names.gperf"
- {"cy", 2},
-#line 588 "effective_tld_names.gperf"
- {"com", 0},
-#line 831 "effective_tld_names.gperf"
- {"edu.co", 0},
-#line 830 "effective_tld_names.gperf"
- {"edu.cn", 0},
-#line 2161 "effective_tld_names.gperf"
- {"net.mo", 0},
-#line 2160 "effective_tld_names.gperf"
- {"net.mk", 0},
-#line 1189 "effective_tld_names.gperf"
- {"gov.bf", 0},
-#line 2118 "effective_tld_names.gperf"
- {"net.bm", 0},
-#line 2119 "effective_tld_names.gperf"
- {"net.bo", 0},
-#line 823 "effective_tld_names.gperf"
- {"edu.bi", 0},
-#line 1281 "effective_tld_names.gperf"
- {"gr", 0},
-#line 1522 "effective_tld_names.gperf"
- {"ir", 0},
-#line 945 "effective_tld_names.gperf"
- {"er", 2},
-#line 1422 "effective_tld_names.gperf"
- {"hr", 0},
-#line 2125 "effective_tld_names.gperf"
- {"net.co", 0},
-#line 2124 "effective_tld_names.gperf"
- {"net.cn", 0},
-#line 829 "effective_tld_names.gperf"
- {"edu.ci", 0},
-#line 3617 "effective_tld_names.gperf"
- {"ye", 2},
-#line 646 "effective_tld_names.gperf"
- {"com.mo", 0},
-#line 645 "effective_tld_names.gperf"
- {"com.mk", 0},
-#line 2273 "effective_tld_names.gperf"
- {"nr", 0},
-#line 601 "effective_tld_names.gperf"
- {"com.bm", 0},
-#line 602 "effective_tld_names.gperf"
- {"com.bo", 0},
-#line 2241 "effective_tld_names.gperf"
- {"nom.co", 0},
-#line 720 "effective_tld_names.gperf"
- {"cr", 0},
-#line 609 "effective_tld_names.gperf"
- {"com.co", 0},
-#line 2123 "effective_tld_names.gperf"
- {"net.ci", 0},
-#line 608 "effective_tld_names.gperf"
- {"com.cn", 0},
-#line 1250 "effective_tld_names.gperf"
- {"gov.pk", 0},
-#line 1252 "effective_tld_names.gperf"
- {"gov.pn", 0},
-#line 1146 "effective_tld_names.gperf"
- {"gn", 0},
-#line 1460 "effective_tld_names.gperf"
- {"in", 0},
-#line 1501 "effective_tld_names.gperf"
- {"int", 0},
-#line 1402 "effective_tld_names.gperf"
- {"hn", 0},
-#line 600 "effective_tld_names.gperf"
- {"com.bi", 0},
-#line 879 "effective_tld_names.gperf"
- {"edu.pk", 0},
-#line 881 "effective_tld_names.gperf"
- {"edu.pn", 0},
-#line 1255 "effective_tld_names.gperf"
- {"gov.pt", 0},
-#line 607 "effective_tld_names.gperf"
- {"com.ci", 0},
-#line 884 "effective_tld_names.gperf"
- {"edu.pt", 0},
-#line 1170 "effective_tld_names.gperf"
- {"gop.pk", 0},
-#line 532 "effective_tld_names.gperf"
- {"cn", 0},
-#line 2172 "effective_tld_names.gperf"
- {"net.pk", 0},
-#line 2174 "effective_tld_names.gperf"
- {"net.pn", 0},
-#line 877 "effective_tld_names.gperf"
- {"edu.pf", 0},
-#line 1232 "effective_tld_names.gperf"
- {"gov.lk", 0},
-#line 1158 "effective_tld_names.gperf"
- {"gob.bo", 0},
-#line 1503 "effective_tld_names.gperf"
- {"int.bo", 0},
-#line 2177 "effective_tld_names.gperf"
- {"net.pt", 0},
-#line 577 "effective_tld_names.gperf"
- {"co.uz", 0},
-#line 861 "effective_tld_names.gperf"
- {"edu.lk", 0},
-#line 1234 "effective_tld_names.gperf"
- {"gov.lt", 0},
-#line 1505 "effective_tld_names.gperf"
- {"int.co", 0},
-#line 659 "effective_tld_names.gperf"
- {"com.pk", 0},
-#line 1244 "effective_tld_names.gperf"
- {"gov.mu", 0},
-#line 1300 "effective_tld_names.gperf"
- {"gs", 0},
-#line 1527 "effective_tld_names.gperf"
- {"is", 0},
-#line 948 "effective_tld_names.gperf"
- {"es", 0},
-#line 663 "effective_tld_names.gperf"
- {"com.pt", 0},
-#line 2154 "effective_tld_names.gperf"
- {"net.lk", 0},
-#line 1324 "effective_tld_names.gperf"
- {"gt", 2},
-#line 1534 "effective_tld_names.gperf"
- {"it", 0},
-#line 954 "effective_tld_names.gperf"
- {"et", 2},
-#line 1424 "effective_tld_names.gperf"
- {"ht", 0},
-#line 1201 "effective_tld_names.gperf"
- {"gov.cu", 0},
-#line 1504 "effective_tld_names.gperf"
- {"int.ci", 0},
-#line 657 "effective_tld_names.gperf"
- {"com.pf", 0},
-#line 832 "effective_tld_names.gperf"
- {"edu.cu", 0},
-#line 2162 "effective_tld_names.gperf"
- {"net.mu", 0},
-#line 640 "effective_tld_names.gperf"
- {"com.lk", 0},
-#line 1237 "effective_tld_names.gperf"
- {"gov.ma", 0},
-#line 2126 "effective_tld_names.gperf"
- {"net.cu", 0},
-#line 1187 "effective_tld_names.gperf"
- {"gov.ba", 0},
-#line 1165 "effective_tld_names.gperf"
- {"gob.pk", 0},
-#line 1478 "effective_tld_names.gperf"
- {"inf.mk", 0},
-#line 647 "effective_tld_names.gperf"
- {"com.mu", 0},
-#line 821 "effective_tld_names.gperf"
- {"edu.ba", 0},
-#line 1510 "effective_tld_names.gperf"
- {"int.pt", 0},
-#line 610 "effective_tld_names.gperf"
- {"com.cu", 0},
-#line 2158 "effective_tld_names.gperf"
- {"net.ma", 0},
-#line 1298 "effective_tld_names.gperf"
- {"grp.lk", 0},
-#line 2116 "effective_tld_names.gperf"
- {"net.ba", 0},
-#line 1152 "effective_tld_names.gperf"
- {"go.kr", 0},
-#line 2250 "effective_tld_names.gperf"
- {"nom.ro", 0},
-#line 1508 "effective_tld_names.gperf"
- {"int.lk", 0},
-#line 1151 "effective_tld_names.gperf"
- {"go.jp", 0},
-#line 665 "effective_tld_names.gperf"
- {"com.ro", 0},
-#line 813 "effective_tld_names.gperf"
- {"ed.jp", 0},
-#line 597 "effective_tld_names.gperf"
- {"com.ba", 0},
-#line 557 "effective_tld_names.gperf"
- {"co.kr", 0},
-#line 1153 "effective_tld_names.gperf"
- {"go.pw", 0},
-#line 2092 "effective_tld_names.gperf"
- {"ne.kr", 0},
-#line 556 "effective_tld_names.gperf"
- {"co.jp", 0},
-#line 814 "effective_tld_names.gperf"
- {"ed.pw", 0},
-#line 2091 "effective_tld_names.gperf"
- {"ne.jp", 0},
-#line 875 "effective_tld_names.gperf"
- {"edu.pa", 0},
-#line 566 "effective_tld_names.gperf"
- {"co.pw", 0},
-#line 2093 "effective_tld_names.gperf"
- {"ne.pw", 0},
-#line 2169 "effective_tld_names.gperf"
- {"net.pa", 0},
-#line 1229 "effective_tld_names.gperf"
- {"gov.la", 0},
-#line 2246 "effective_tld_names.gperf"
- {"nom.pa", 0},
-#line 858 "effective_tld_names.gperf"
- {"edu.la", 0},
-#line 1283 "effective_tld_names.gperf"
- {"gr.jp", 0},
-#line 655 "effective_tld_names.gperf"
- {"com.pa", 0},
-#line 1257 "effective_tld_names.gperf"
- {"gov.ru", 0},
-#line 2151 "effective_tld_names.gperf"
- {"net.la", 0},
-#line 886 "effective_tld_names.gperf"
- {"edu.ru", 0},
-#line 1479 "effective_tld_names.gperf"
- {"info", 0},
-#line 2268 "effective_tld_names.gperf"
- {"nov.ru", 0},
-#line 1477 "effective_tld_names.gperf"
- {"inf.cu", 0},
-#line 637 "effective_tld_names.gperf"
- {"com.la", 0},
-#line 2178 "effective_tld_names.gperf"
- {"net.ru", 0},
-#line 2350 "effective_tld_names.gperf"
- {"org", 0},
-#line 1163 "effective_tld_names.gperf"
- {"gob.pa", 0},
-#line 666 "effective_tld_names.gperf"
- {"com.ru", 0},
-#line 1325 "effective_tld_names.gperf"
- {"gu", 2},
-#line 960 "effective_tld_names.gperf"
- {"eu", 0},
-#line 1425 "effective_tld_names.gperf"
- {"hu", 0},
-#line 2414 "effective_tld_names.gperf"
- {"org.mo", 0},
-#line 2412 "effective_tld_names.gperf"
- {"org.mk", 0},
-#line 2413 "effective_tld_names.gperf"
- {"org.mn", 0},
-#line 2362 "effective_tld_names.gperf"
- {"org.bm", 0},
-#line 2363 "effective_tld_names.gperf"
- {"org.bo", 0},
-#line 2286 "effective_tld_names.gperf"
- {"nu", 0},
-#line 949 "effective_tld_names.gperf"
- {"es.kr", 0},
-#line 1423 "effective_tld_names.gperf"
- {"hs.kr", 0},
-#line 2370 "effective_tld_names.gperf"
- {"org.co", 0},
-#line 2369 "effective_tld_names.gperf"
- {"org.cn", 0},
-#line 732 "effective_tld_names.gperf"
- {"cu", 0},
-#line 1507 "effective_tld_names.gperf"
- {"int.la", 0},
-#line 2361 "effective_tld_names.gperf"
- {"org.bi", 0},
-#line 1188 "effective_tld_names.gperf"
- {"gov.bb", 0},
-#line 2368 "effective_tld_names.gperf"
- {"org.ci", 0},
-#line 822 "effective_tld_names.gperf"
- {"edu.bb", 0},
-#line 1265 "effective_tld_names.gperf"
- {"gov.st", 0},
-#line 1511 "effective_tld_names.gperf"
- {"int.ru", 0},
-#line 2339 "effective_tld_names.gperf"
- {"or.kr", 0},
-#line 894 "effective_tld_names.gperf"
- {"edu.st", 0},
-#line 2338 "effective_tld_names.gperf"
- {"or.jp", 0},
-#line 2117 "effective_tld_names.gperf"
- {"net.bb", 0},
-#line 3626 "effective_tld_names.gperf"
- {"yu", 2},
-#line 2186 "effective_tld_names.gperf"
- {"net.st", 0},
-#line 2426 "effective_tld_names.gperf"
- {"org.pk", 0},
-#line 2428 "effective_tld_names.gperf"
- {"org.pn", 0},
-#line 2342 "effective_tld_names.gperf"
- {"or.pw", 0},
-#line 598 "effective_tld_names.gperf"
- {"com.bb", 0},
-#line 2431 "effective_tld_names.gperf"
- {"org.pt", 0},
-#line 674 "effective_tld_names.gperf"
- {"com.st", 0},
-#line 2424 "effective_tld_names.gperf"
- {"org.pf", 0},
-#line 1445 "effective_tld_names.gperf"
- {"id.us", 0},
-#line 2404 "effective_tld_names.gperf"
- {"org.lk", 0},
-#line 2089 "effective_tld_names.gperf"
- {"nd.us", 0},
-#line 576 "effective_tld_names.gperf"
- {"co.us", 0},
-#line 737 "effective_tld_names.gperf"
- {"cv", 0},
-#line 2096 "effective_tld_names.gperf"
- {"ne.us", 0},
-#line 2415 "effective_tld_names.gperf"
- {"org.mu", 0},
-#line 1270 "effective_tld_names.gperf"
- {"gov.to", 0},
-#line 1269 "effective_tld_names.gperf"
- {"gov.tn", 0},
-#line 1230 "effective_tld_names.gperf"
- {"gov.lb", 0},
-#line 2294 "effective_tld_names.gperf"
- {"ny.us", 0},
-#line 897 "effective_tld_names.gperf"
- {"edu.to", 0},
-#line 859 "effective_tld_names.gperf"
- {"edu.lb", 0},
-#line 1271 "effective_tld_names.gperf"
- {"gov.tt", 0},
-#line 2371 "effective_tld_names.gperf"
- {"org.cu", 0},
-#line 1498 "effective_tld_names.gperf"
- {"ing.pa", 0},
-#line 898 "effective_tld_names.gperf"
- {"edu.tt", 0},
-#line 2191 "effective_tld_names.gperf"
- {"net.to", 0},
-#line 2190 "effective_tld_names.gperf"
- {"net.tn", 0},
-#line 2152 "effective_tld_names.gperf"
- {"net.lb", 0},
-#line 2409 "effective_tld_names.gperf"
- {"org.ma", 0},
-#line 2432 "effective_tld_names.gperf"
- {"org.ro", 0},
-#line 2192 "effective_tld_names.gperf"
- {"net.tt", 0},
-#line 2359 "effective_tld_names.gperf"
- {"org.ba", 0},
-#line 678 "effective_tld_names.gperf"
- {"com.to", 0},
-#line 677 "effective_tld_names.gperf"
- {"com.tn", 0},
-#line 638 "effective_tld_names.gperf"
- {"com.lb", 0},
-#line 1144 "effective_tld_names.gperf"
- {"gm", 0},
-#line 1455 "effective_tld_names.gperf"
- {"im", 0},
-#line 1400 "effective_tld_names.gperf"
- {"hm", 0},
-#line 679 "effective_tld_names.gperf"
- {"com.tt", 0},
-#line 1259 "effective_tld_names.gperf"
- {"gov.sa", 0},
-#line 1466 "effective_tld_names.gperf"
- {"in.us", 0},
-#line 888 "effective_tld_names.gperf"
- {"edu.sa", 0},
-#line 542 "effective_tld_names.gperf"
- {"co.at", 0},
-#line 530 "effective_tld_names.gperf"
- {"cm", 0},
-#line 1225 "effective_tld_names.gperf"
- {"gov.km", 0},
-#line 568 "effective_tld_names.gperf"
- {"co.rw", 0},
-#line 1226 "effective_tld_names.gperf"
- {"gov.kn", 0},
-#line 2180 "effective_tld_names.gperf"
- {"net.sa", 0},
-#line 854 "effective_tld_names.gperf"
- {"edu.km", 0},
-#line 855 "effective_tld_names.gperf"
- {"edu.kn", 0},
-#line 1231 "effective_tld_names.gperf"
- {"gov.lc", 0},
-#line 2422 "effective_tld_names.gperf"
- {"org.pa", 0},
-#line 1224 "effective_tld_names.gperf"
- {"gov.ki", 0},
-#line 860 "effective_tld_names.gperf"
- {"edu.lc", 0},
-#line 668 "effective_tld_names.gperf"
- {"com.sa", 0},
-#line 1514 "effective_tld_names.gperf"
- {"int.tt", 0},
-#line 2148 "effective_tld_names.gperf"
- {"net.kn", 0},
-#line 853 "effective_tld_names.gperf"
- {"edu.ki", 0},
-#line 2244 "effective_tld_names.gperf"
- {"nom.km", 0},
-#line 2153 "effective_tld_names.gperf"
- {"net.lc", 0},
-#line 156 "effective_tld_names.gperf"
- {"ao", 0},
-#line 634 "effective_tld_names.gperf"
- {"com.km", 0},
-#line 68 "effective_tld_names.gperf"
- {"ad", 0},
-#line 2147 "effective_tld_names.gperf"
- {"net.ki", 0},
-#line 2401 "effective_tld_names.gperf"
- {"org.la", 0},
-#line 2298 "effective_tld_names.gperf"
- {"nz", 2},
-#line 74 "effective_tld_names.gperf"
- {"ae", 0},
-#line 551 "effective_tld_names.gperf"
- {"co.im", 0},
-#line 639 "effective_tld_names.gperf"
- {"com.lc", 0},
-#line 731 "effective_tld_names.gperf"
- {"ct.us", 0},
-#line 743 "effective_tld_names.gperf"
- {"cz", 0},
-#line 633 "effective_tld_names.gperf"
- {"com.ki", 0},
-#line 2346 "effective_tld_names.gperf"
- {"or.us", 0},
-#line 1239 "effective_tld_names.gperf"
- {"gov.mg", 0},
-#line 2434 "effective_tld_names.gperf"
- {"org.ru", 0},
-#line 3645 "effective_tld_names.gperf"
- {"zm", 2},
-#line 866 "effective_tld_names.gperf"
- {"edu.mg", 0},
-#line 2321 "effective_tld_names.gperf"
- {"om", 2},
-#line 166 "effective_tld_names.gperf"
- {"ar", 2},
-#line 2245 "effective_tld_names.gperf"
- {"nom.mg", 0},
-#line 1969 "effective_tld_names.gperf"
- {"mo", 0},
-#line 1864 "effective_tld_names.gperf"
- {"md", 0},
-#line 644 "effective_tld_names.gperf"
- {"com.mg", 0},
-#line 1867 "effective_tld_names.gperf"
- {"me", 0},
-#line 2032 "effective_tld_names.gperf"
- {"my", 0},
-#line 1169 "effective_tld_names.gperf"
- {"gon.pk", 0},
-#line 145 "effective_tld_names.gperf"
- {"an", 0},
-#line 1193 "effective_tld_names.gperf"
- {"gov.bs", 0},
-#line 180 "effective_tld_names.gperf"
- {"arpa", 0},
-#line 827 "effective_tld_names.gperf"
- {"edu.bs", 0},
-#line 2333 "effective_tld_names.gperf"
- {"or.at", 0},
-#line 2360 "effective_tld_names.gperf"
- {"org.bb", 0},
-#line 1277 "effective_tld_names.gperf"
- {"gov.ws", 0},
-#line 808 "effective_tld_names.gperf"
- {"ec", 0},
-#line 2442 "effective_tld_names.gperf"
- {"org.st", 0},
-#line 2121 "effective_tld_names.gperf"
- {"net.bs", 0},
-#line 903 "effective_tld_names.gperf"
- {"edu.ws", 0},
-#line 2002 "effective_tld_names.gperf"
- {"mr", 0},
-#line 2087 "effective_tld_names.gperf"
- {"nc", 0},
-#line 1260 "effective_tld_names.gperf"
- {"gov.sb", 0},
-#line 457 "effective_tld_names.gperf"
- {"cc", 0},
-#line 889 "effective_tld_names.gperf"
- {"edu.sb", 0},
-#line 604 "effective_tld_names.gperf"
- {"com.bs", 0},
-#line 2199 "effective_tld_names.gperf"
- {"net.ws", 0},
-#line 197 "effective_tld_names.gperf"
- {"as", 0},
-#line 1442 "effective_tld_names.gperf"
- {"id.ir", 0},
-#line 228 "effective_tld_names.gperf"
- {"at", 0},
-#line 819 "effective_tld_names.gperf"
- {"edu.an", 0},
-#line 1254 "effective_tld_names.gperf"
- {"gov.ps", 0},
-#line 2181 "effective_tld_names.gperf"
- {"net.sb", 0},
-#line 1966 "effective_tld_names.gperf"
- {"mn", 0},
-#line 77 "effective_tld_names.gperf"
- {"aero", 0},
-#line 687 "effective_tld_names.gperf"
- {"com.ws", 0},
-#line 883 "effective_tld_names.gperf"
- {"edu.ps", 0},
-#line 553 "effective_tld_names.gperf"
- {"co.ir", 0},
-#line 1183 "effective_tld_names.gperf"
- {"gov.af", 0},
-#line 2114 "effective_tld_names.gperf"
- {"net.an", 0},
-#line 817 "effective_tld_names.gperf"
- {"edu.af", 0},
-#line 669 "effective_tld_names.gperf"
- {"com.sb", 0},
-#line 2176 "effective_tld_names.gperf"
- {"net.ps", 0},
-#line 2112 "effective_tld_names.gperf"
- {"net.ai", 0},
-#line 594 "effective_tld_names.gperf"
- {"com.an", 0},
-#line 1326 "effective_tld_names.gperf"
- {"gu.us", 0},
-#line 2110 "effective_tld_names.gperf"
- {"net.af", 0},
-#line 2447 "effective_tld_names.gperf"
- {"org.to", 0},
-#line 662 "effective_tld_names.gperf"
- {"com.ps", 0},
-#line 2446 "effective_tld_names.gperf"
- {"org.tn", 0},
-#line 2402 "effective_tld_names.gperf"
- {"org.lb", 0},
-#line 2005 "effective_tld_names.gperf"
- {"ms", 0},
-#line 592 "effective_tld_names.gperf"
- {"com.ai", 0},
-#line 2448 "effective_tld_names.gperf"
- {"org.tt", 0},
-#line 590 "effective_tld_names.gperf"
- {"com.af", 0},
-#line 2010 "effective_tld_names.gperf"
- {"mt", 2},
-#line 1261 "effective_tld_names.gperf"
- {"gov.sc", 0},
-#line 69 "effective_tld_names.gperf"
- {"ad.jp", 0},
-#line 890 "effective_tld_names.gperf"
- {"edu.sc", 0},
-#line 739 "effective_tld_names.gperf"
- {"cx", 0},
-#line 1256 "effective_tld_names.gperf"
- {"gov.rs", 0},
-#line 1238 "effective_tld_names.gperf"
- {"gov.me", 0},
-#line 2182 "effective_tld_names.gperf"
- {"net.sc", 0},
-#line 1276 "effective_tld_names.gperf"
- {"gov.vn", 0},
-#line 885 "effective_tld_names.gperf"
- {"edu.rs", 0},
-#line 865 "effective_tld_names.gperf"
- {"edu.me", 0},
-#line 902 "effective_tld_names.gperf"
- {"edu.vn", 0},
-#line 2435 "effective_tld_names.gperf"
- {"org.sa", 0},
-#line 564 "effective_tld_names.gperf"
- {"co.na", 0},
-#line 670 "effective_tld_names.gperf"
- {"com.sc", 0},
-#line 2159 "effective_tld_names.gperf"
- {"net.me", 0},
-#line 425 "effective_tld_names.gperf"
- {"c.la", 0},
-#line 2198 "effective_tld_names.gperf"
- {"net.vn", 0},
-#line 2397 "effective_tld_names.gperf"
- {"org.km", 0},
-#line 567 "effective_tld_names.gperf"
- {"co.rs", 0},
-#line 2398 "effective_tld_names.gperf"
- {"org.kn", 0},
-#line 2403 "effective_tld_names.gperf"
- {"org.lc", 0},
-#line 2197 "effective_tld_names.gperf"
- {"net.vi", 0},
-#line 686 "effective_tld_names.gperf"
- {"com.vn", 0},
-#line 2292 "effective_tld_names.gperf"
- {"nv.us", 0},
-#line 1465 "effective_tld_names.gperf"
- {"in.ua", 0},
-#line 2396 "effective_tld_names.gperf"
- {"org.ki", 0},
-#line 1156 "effective_tld_names.gperf"
- {"go.tz", 0},
-#line 685 "effective_tld_names.gperf"
- {"com.vi", 0},
-#line 876 "effective_tld_names.gperf"
- {"edu.pe", 0},
-#line 535 "effective_tld_names.gperf"
- {"cn.ua", 0},
-#line 2303 "effective_tld_names.gperf"
- {"od.ua", 0},
-#line 574 "effective_tld_names.gperf"
- {"co.tz", 0},
-#line 2094 "effective_tld_names.gperf"
- {"ne.tz", 0},
-#line 2170 "effective_tld_names.gperf"
- {"net.pe", 0},
-#line 1462 "effective_tld_names.gperf"
- {"in.na", 0},
-#line 1515 "effective_tld_names.gperf"
- {"int.vn", 0},
-#line 233 "effective_tld_names.gperf"
- {"au", 2},
-#line 2247 "effective_tld_names.gperf"
- {"nom.pe", 0},
-#line 2411 "effective_tld_names.gperf"
- {"org.mg", 0},
-#line 1463 "effective_tld_names.gperf"
- {"in.rs", 0},
-#line 656 "effective_tld_names.gperf"
- {"com.pe", 0},
-#line 1332 "effective_tld_names.gperf"
- {"gv.at", 0},
-#line 1263 "effective_tld_names.gperf"
- {"gov.sg", 0},
-#line 2233 "effective_tld_names.gperf"
- {"nm.us", 0},
-#line 892 "effective_tld_names.gperf"
- {"edu.sg", 0},
-#line 2184 "effective_tld_names.gperf"
- {"net.sg", 0},
-#line 1164 "effective_tld_names.gperf"
- {"gob.pe", 0},
-#line 2013 "effective_tld_names.gperf"
- {"mu", 0},
-#line 2365 "effective_tld_names.gperf"
- {"org.bs", 0},
-#line 672 "effective_tld_names.gperf"
- {"com.sg", 0},
-#line 2341 "effective_tld_names.gperf"
- {"or.na", 0},
-#line 2007 "effective_tld_names.gperf"
- {"ms.kr", 0},
-#line 1090 "effective_tld_names.gperf"
- {"ga", 0},
-#line 1150 "effective_tld_names.gperf"
- {"go.it", 0},
-#line 2249 "effective_tld_names.gperf"
- {"nom.re", 0},
-#line 2455 "effective_tld_names.gperf"
- {"org.ws", 0},
-#line 2037 "effective_tld_names.gperf"
- {"na", 0},
-#line 2237 "effective_tld_names.gperf"
- {"no.it", 0},
-#line 1113 "effective_tld_names.gperf"
- {"ge.it", 0},
-#line 664 "effective_tld_names.gperf"
- {"com.re", 0},
-#line 427 "effective_tld_names.gperf"
- {"ca", 0},
-#line 451 "effective_tld_names.gperf"
- {"cat", 0},
-#line 554 "effective_tld_names.gperf"
- {"co.it", 0},
-#line 2436 "effective_tld_names.gperf"
- {"org.sb", 0},
-#line 3649 "effective_tld_names.gperf"
- {"zt.ua", 0},
-#line 461 "effective_tld_names.gperf"
- {"ce.it", 0},
-#line 2357 "effective_tld_names.gperf"
- {"org.an", 0},
-#line 2430 "effective_tld_names.gperf"
- {"org.ps", 0},
-#line 2344 "effective_tld_names.gperf"
- {"or.tz", 0},
-#line 1126 "effective_tld_names.gperf"
- {"gi", 0},
-#line 2355 "effective_tld_names.gperf"
- {"org.ai", 0},
-#line 2353 "effective_tld_names.gperf"
- {"org.af", 0},
-#line 1282 "effective_tld_names.gperf"
- {"gr.it", 0},
-#line 169 "effective_tld_names.gperf"
- {"ar.us", 0},
-#line 2216 "effective_tld_names.gperf"
- {"ni", 2},
-#line 1973 "effective_tld_names.gperf"
- {"mo.us", 0},
-#line 2028 "effective_tld_names.gperf"
- {"mv", 2},
-#line 1866 "effective_tld_names.gperf"
- {"md.us", 0},
-#line 202 "effective_tld_names.gperf"
- {"asia", 0},
-#line 495 "effective_tld_names.gperf"
- {"ci", 0},
-#line 2406 "effective_tld_names.gperf"
- {"org.ls", 0},
-#line 1869 "effective_tld_names.gperf"
- {"me.us", 0},
-#line 721 "effective_tld_names.gperf"
- {"cr.it", 0},
-#line 1211 "effective_tld_names.gperf"
- {"gov.gn", 0},
-#line 130 "effective_tld_names.gperf"
- {"am", 0},
-#line 841 "effective_tld_names.gperf"
- {"edu.gn", 0},
-#line 928 "effective_tld_names.gperf"
- {"en.it", 0},
-#line 3630 "effective_tld_names.gperf"
- {"za", 2},
-#line 1210 "effective_tld_names.gperf"
- {"gov.gi", 0},
-#line 2132 "effective_tld_names.gperf"
- {"net.gn", 0},
-#line 840 "effective_tld_names.gperf"
- {"edu.gi", 0},
-#line 1223 "effective_tld_names.gperf"
- {"gov.kg", 0},
-#line 2437 "effective_tld_names.gperf"
- {"org.sc", 0},
-#line 534 "effective_tld_names.gperf"
- {"cn.it", 0},
-#line 852 "effective_tld_names.gperf"
- {"edu.kg", 0},
-#line 620 "effective_tld_names.gperf"
- {"com.gn", 0},
-#line 2088 "effective_tld_names.gperf"
- {"nc.us", 0},
-#line 2433 "effective_tld_names.gperf"
- {"org.rs", 0},
-#line 2410 "effective_tld_names.gperf"
- {"org.me", 0},
-#line 2146 "effective_tld_names.gperf"
- {"net.kg", 0},
-#line 2454 "effective_tld_names.gperf"
- {"org.vn", 0},
-#line 1157 "effective_tld_names.gperf"
- {"go.ug", 0},
-#line 1528 "effective_tld_names.gperf"
- {"is.it", 0},
-#line 198 "effective_tld_names.gperf"
- {"as.us", 0},
-#line 619 "effective_tld_names.gperf"
- {"com.gi", 0},
-#line 1181 "effective_tld_names.gperf"
- {"gov.ac", 0},
-#line 1965 "effective_tld_names.gperf"
- {"mm", 2},
-#line 540 "effective_tld_names.gperf"
- {"co.ag", 0},
-#line 256 "effective_tld_names.gperf"
- {"az", 0},
-#line 816 "effective_tld_names.gperf"
- {"edu.ac", 0},
-#line 632 "effective_tld_names.gperf"
- {"com.kg", 0},
-#line 2453 "effective_tld_names.gperf"
- {"org.vi", 0},
-#line 1968 "effective_tld_names.gperf"
- {"mn.us", 0},
-#line 575 "effective_tld_names.gperf"
- {"co.ug", 0},
-#line 729 "effective_tld_names.gperf"
- {"cs.it", 0},
-#line 2095 "effective_tld_names.gperf"
- {"ne.ug", 0},
-#line 730 "effective_tld_names.gperf"
- {"ct.it", 0},
-#line 2108 "effective_tld_names.gperf"
- {"net.ac", 0},
-#line 2337 "effective_tld_names.gperf"
- {"or.it", 0},
-#line 2295 "effective_tld_names.gperf"
- {"nyc.museum", 0},
-#line 589 "effective_tld_names.gperf"
- {"com.ac", 0},
-#line 2423 "effective_tld_names.gperf"
- {"org.pe", 0},
-#line 2008 "effective_tld_names.gperf"
- {"ms.us", 0},
-#line 2012 "effective_tld_names.gperf"
- {"mt.us", 0},
-#line 2034 "effective_tld_names.gperf"
- {"mz", 2},
-#line 738 "effective_tld_names.gperf"
- {"cv.ua", 0},
-#line 1975 "effective_tld_names.gperf"
- {"mobi", 0},
-#line 1275 "effective_tld_names.gperf"
- {"gov.vc", 0},
-#line 901 "effective_tld_names.gperf"
- {"edu.vc", 0},
-#line 2440 "effective_tld_names.gperf"
- {"org.sg", 0},
-#line 801 "effective_tld_names.gperf"
- {"e-burg.ru", 0},
-#line 32 "effective_tld_names.gperf"
- {"ac", 0},
-#line 1813 "effective_tld_names.gperf"
- {"ly", 0},
-#line 1243 "effective_tld_names.gperf"
- {"gov.mr", 0},
-#line 2196 "effective_tld_names.gperf"
- {"net.vc", 0},
-#line 1192 "effective_tld_names.gperf"
- {"gov.br", 0},
-#line 1089 "effective_tld_names.gperf"
- {"g12.br", 0},
-#line 826 "effective_tld_names.gperf"
- {"edu.br", 0},
-#line 2263 "effective_tld_names.gperf"
- {"not.br", 0},
-#line 1279 "effective_tld_names.gperf"
- {"gp", 0},
-#line 684 "effective_tld_names.gperf"
- {"com.vc", 0},
-#line 2120 "effective_tld_names.gperf"
- {"net.br", 0},
-#line 2272 "effective_tld_names.gperf"
- {"np", 2},
-#line 573 "effective_tld_names.gperf"
- {"co.tt", 0},
-#line 1790 "effective_tld_names.gperf"
- {"lr", 0},
-#line 2240 "effective_tld_names.gperf"
- {"nom.br", 0},
-#line 2345 "effective_tld_names.gperf"
- {"or.ug", 0},
-#line 2111 "effective_tld_names.gperf"
- {"net.ag", 0},
-#line 603 "effective_tld_names.gperf"
- {"com.br", 0},
-#line 2239 "effective_tld_names.gperf"
- {"nom.ag", 0},
-#line 1862 "effective_tld_names.gperf"
- {"mc", 0},
-#line 1253 "effective_tld_names.gperf"
- {"gov.pr", 0},
-#line 591 "effective_tld_names.gperf"
- {"com.ag", 0},
-#line 882 "effective_tld_names.gperf"
- {"edu.pr", 0},
-#line 1123 "effective_tld_names.gperf"
- {"gg", 0},
-#line 910 "effective_tld_names.gperf"
- {"eg", 2},
-#line 2288 "effective_tld_names.gperf"
- {"nu.it", 0},
-#line 1775 "effective_tld_names.gperf"
- {"local", 0},
-#line 1185 "effective_tld_names.gperf"
- {"gov.as", 0},
-#line 2175 "effective_tld_names.gperf"
- {"net.pr", 0},
-#line 2210 "effective_tld_names.gperf"
- {"ng", 0},
-#line 1233 "effective_tld_names.gperf"
- {"gov.lr", 0},
-#line 2166 "effective_tld_names.gperf"
- {"net.nf", 0},
-#line 254 "effective_tld_names.gperf"
- {"ax", 0},
-#line 466 "effective_tld_names.gperf"
- {"cg", 0},
-#line 862 "effective_tld_names.gperf"
- {"edu.lr", 0},
-#line 661 "effective_tld_names.gperf"
- {"com.pr", 0},
-#line 1405 "effective_tld_names.gperf"
- {"hof.no", 0},
-#line 1791 "effective_tld_names.gperf"
- {"ls", 0},
-#line 652 "effective_tld_names.gperf"
- {"com.nf", 0},
-#line 558 "effective_tld_names.gperf"
- {"co.lc", 0},
-#line 537 "effective_tld_names.gperf"
- {"cnt.br", 0},
-#line 2155 "effective_tld_names.gperf"
- {"net.lr", 0},
-#line 1792 "effective_tld_names.gperf"
- {"lt", 0},
-#line 2381 "effective_tld_names.gperf"
- {"org.gn", 0},
-#line 1041 "effective_tld_names.gperf"
- {"fo", 0},
-#line 641 "effective_tld_names.gperf"
- {"com.lr", 0},
-#line 2380 "effective_tld_names.gperf"
- {"org.gi", 0},
-#line 1091 "effective_tld_names.gperf"
- {"ga.us", 0},
-#line 1437 "effective_tld_names.gperf"
- {"ia.us", 0},
-#line 955 "effective_tld_names.gperf"
- {"etc.br", 0},
-#line 2395 "effective_tld_names.gperf"
- {"org.kg", 0},
-#line 1476 "effective_tld_names.gperf"
- {"inf.br", 0},
-#line 2030 "effective_tld_names.gperf"
- {"mx", 0},
-#line 2876 "effective_tld_names.gperf"
- {"sd", 0},
-#line 2285 "effective_tld_names.gperf"
- {"ntr.br", 0},
-#line 950 "effective_tld_names.gperf"
- {"esp.br", 0},
-#line 430 "effective_tld_names.gperf"
- {"ca.us", 0},
-#line 1197 "effective_tld_names.gperf"
- {"gov.cl", 0},
-#line 539 "effective_tld_names.gperf"
- {"co.ae", 0},
-#line 2879 "effective_tld_names.gperf"
- {"se", 0},
-#line 3065 "effective_tld_names.gperf"
- {"sy", 0},
-#line 1246 "effective_tld_names.gperf"
- {"gov.my", 0},
-#line 2351 "effective_tld_names.gperf"
- {"org.ac", 0},
-#line 1194 "effective_tld_names.gperf"
- {"gov.by", 0},
-#line 1056 "effective_tld_names.gperf"
- {"fr", 0},
-#line 2068 "effective_tld_names.gperf"
- {"nat.tn", 0},
-#line 872 "effective_tld_names.gperf"
- {"edu.my", 0},
-#line 2439 "effective_tld_names.gperf"
- {"org.se", 0},
-#line 952 "effective_tld_names.gperf"
- {"est.pr", 0},
-#line 44 "effective_tld_names.gperf"
- {"ac.kr", 0},
-#line 1383 "effective_tld_names.gperf"
- {"hi.us", 0},
-#line 43 "effective_tld_names.gperf"
- {"ac.jp", 0},
-#line 2165 "effective_tld_names.gperf"
- {"net.my", 0},
-#line 1251 "effective_tld_names.gperf"
- {"gov.pl", 0},
-#line 1174 "effective_tld_names.gperf"
- {"gos.pk", 0},
-#line 2993 "effective_tld_names.gperf"
- {"sr", 0},
-#line 880 "effective_tld_names.gperf"
- {"edu.pl", 0},
-#line 650 "effective_tld_names.gperf"
- {"com.my", 0},
-#line 1456 "effective_tld_names.gperf"
- {"im.it", 0},
-#line 605 "effective_tld_names.gperf"
- {"com.by", 0},
-#line 1182 "effective_tld_names.gperf"
- {"gov.ae", 0},
-#line 931 "effective_tld_names.gperf"
- {"eng.br", 0},
-#line 651 "effective_tld_names.gperf"
- {"com.na", 0},
-#line 2173 "effective_tld_names.gperf"
- {"net.pl", 0},
-#line 75 "effective_tld_names.gperf"
- {"ae.org", 0},
-#line 565 "effective_tld_names.gperf"
- {"co.pn", 0},
-#line 555 "effective_tld_names.gperf"
- {"co.je", 0},
-#line 2949 "effective_tld_names.gperf"
- {"sn", 0},
-#line 458 "effective_tld_names.gperf"
- {"cc.na", 0},
-#line 2248 "effective_tld_names.gperf"
- {"nom.pl", 0},
-#line 536 "effective_tld_names.gperf"
- {"cng.br", 0},
-#line 2109 "effective_tld_names.gperf"
- {"net.ae", 0},
-#line 1159 "effective_tld_names.gperf"
- {"gob.cl", 0},
-#line 660 "effective_tld_names.gperf"
- {"com.pl", 0},
-#line 2269 "effective_tld_names.gperf"
- {"novara.it", 0},
-#line 1236 "effective_tld_names.gperf"
- {"gov.ly", 0},
-#line 2958 "effective_tld_names.gperf"
- {"soc.lk", 0},
-#line 1524 "effective_tld_names.gperf"
- {"irc.pl", 0},
-#line 1149 "effective_tld_names.gperf"
- {"go.cr", 0},
-#line 864 "effective_tld_names.gperf"
- {"edu.ly", 0},
-#line 1288 "effective_tld_names.gperf"
- {"granvin.no", 0},
-#line 2452 "effective_tld_names.gperf"
- {"org.vc", 0},
-#line 812 "effective_tld_names.gperf"
- {"ed.cr", 0},
-#line 157 "effective_tld_names.gperf"
- {"ao.it", 0},
-#line 2157 "effective_tld_names.gperf"
- {"net.ly", 0},
-#line 257 "effective_tld_names.gperf"
- {"az.us", 0},
#line 547 "effective_tld_names.gperf"
- {"co.cr", 0},
-#line 2999 "effective_tld_names.gperf"
- {"st", 0},
-#line 1797 "effective_tld_names.gperf"
- {"lu", 0},
-#line 2364 "effective_tld_names.gperf"
- {"org.br", 0},
-#line 744 "effective_tld_names.gperf"
- {"cz.it", 0},
-#line 643 "effective_tld_names.gperf"
- {"com.ly", 0},
-#line 2354 "effective_tld_names.gperf"
- {"org.ag", 0},
-#line 1446 "effective_tld_names.gperf"
- {"idrett.no", 0},
-#line 51 "effective_tld_names.gperf"
- {"ac.pr", 0},
-#line 97 "effective_tld_names.gperf"
- {"ai", 0},
-#line 1818 "effective_tld_names.gperf"
- {"ma", 0},
-#line 168 "effective_tld_names.gperf"
- {"ar.it", 0},
-#line 1972 "effective_tld_names.gperf"
- {"mo.it", 0},
-#line 2429 "effective_tld_names.gperf"
- {"org.pr", 0},
-#line 1868 "effective_tld_names.gperf"
- {"me.it", 0},
-#line 1208 "effective_tld_names.gperf"
- {"gov.gg", 0},
-#line 1323 "effective_tld_names.gperf"
- {"gsm.pl", 0},
-#line 146 "effective_tld_names.gperf"
- {"an.it", 0},
-#line 1811 "effective_tld_names.gperf"
- {"lv", 0},
-#line 2405 "effective_tld_names.gperf"
- {"org.lr", 0},
-#line 2131 "effective_tld_names.gperf"
- {"net.gg", 0},
-#line 908 "effective_tld_names.gperf"
- {"edunet.tn", 0},
-#line 3401 "effective_tld_names.gperf"
- {"ws", 0},
-#line 3377 "effective_tld_names.gperf"
- {"web.co", 0},
-#line 559 "effective_tld_names.gperf"
- {"co.ls", 0},
-#line 229 "effective_tld_names.gperf"
- {"at.it", 0},
+ {"co", 0},
+#line 755 "effective_tld_names.gperf"
+ {"cz", 0},
+#line 1547 "effective_tld_names.gperf"
+ {"io", 0},
+#line 158 "effective_tld_names.gperf"
+ {"ao", 0},
+#line 261 "effective_tld_names.gperf"
+ {"az", 0},
+#line 469 "effective_tld_names.gperf"
+ {"cd", 0},
+#line 597 "effective_tld_names.gperf"
+ {"com", 0},
+#line 1464 "effective_tld_names.gperf"
+ {"id", 2},
+#line 69 "effective_tld_names.gperf"
+ {"ad", 0},
+#line 965 "effective_tld_names.gperf"
+ {"es", 0},
+#line 1556 "effective_tld_names.gperf"
+ {"is", 0},
+#line 200 "effective_tld_names.gperf"
+ {"as", 0},
+#line 827 "effective_tld_names.gperf"
+ {"edu", 0},
#line 715 "effective_tld_names.gperf"
- {"council.aero", 0},
-#line 1967 "effective_tld_names.gperf"
- {"mn.it", 0},
-#line 2336 "effective_tld_names.gperf"
- {"or.cr", 0},
-#line 3380 "effective_tld_names.gperf"
- {"web.pk", 0},
-#line 957 "effective_tld_names.gperf"
- {"eti.br", 0},
-#line 1264 "effective_tld_names.gperf"
- {"gov.sl", 0},
-#line 34 "effective_tld_names.gperf"
- {"ac.at", 0},
-#line 3040 "effective_tld_names.gperf"
- {"su", 0},
-#line 2418 "effective_tld_names.gperf"
- {"org.my", 0},
-#line 1245 "effective_tld_names.gperf"
- {"gov.mw", 0},
-#line 893 "effective_tld_names.gperf"
- {"edu.sl", 0},
-#line 3378 "effective_tld_names.gperf"
- {"web.lk", 0},
-#line 2006 "effective_tld_names.gperf"
- {"ms.it", 0},
-#line 54 "effective_tld_names.gperf"
- {"ac.rw", 0},
-#line 2419 "effective_tld_names.gperf"
- {"org.na", 0},
-#line 870 "effective_tld_names.gperf"
- {"edu.mw", 0},
-#line 2011 "effective_tld_names.gperf"
- {"mt.it", 0},
-#line 185 "effective_tld_names.gperf"
- {"art.museum", 0},
-#line 2185 "effective_tld_names.gperf"
- {"net.sl", 0},
-#line 1266 "effective_tld_names.gperf"
- {"gov.sy", 0},
-#line 2163 "effective_tld_names.gperf"
- {"net.mw", 0},
-#line 1205 "effective_tld_names.gperf"
- {"gov.ec", 0},
-#line 895 "effective_tld_names.gperf"
- {"edu.sy", 0},
-#line 2427 "effective_tld_names.gperf"
- {"org.pl", 0},
-#line 835 "effective_tld_names.gperf"
- {"edu.ec", 0},
-#line 673 "effective_tld_names.gperf"
+ {"coop", 0},
+#line 971 "effective_tld_names.gperf"
+ {"et", 2},
+#line 1563 "effective_tld_names.gperf"
+ {"it", 0},
+#line 233 "effective_tld_names.gperf"
+ {"at", 0},
+#line 533 "effective_tld_names.gperf"
+ {"cl", 0},
+#line 1475 "effective_tld_names.gperf"
+ {"il", 2},
+#line 115 "effective_tld_names.gperf"
+ {"al", 0},
+#line 684 "effective_tld_names.gperf"
{"com.sl", 0},
-#line 648 "effective_tld_names.gperf"
- {"com.mw", 0},
-#line 2187 "effective_tld_names.gperf"
- {"net.sy", 0},
-#line 2352 "effective_tld_names.gperf"
- {"org.ae", 0},
-#line 2129 "effective_tld_names.gperf"
- {"net.ec", 0},
-#line 429 "effective_tld_names.gperf"
- {"ca.na", 0},
-#line 40 "effective_tld_names.gperf"
- {"ac.im", 0},
-#line 1470 "effective_tld_names.gperf"
- {"ind.tn", 0},
-#line 675 "effective_tld_names.gperf"
- {"com.sy", 0},
-#line 613 "effective_tld_names.gperf"
- {"com.ec", 0},
-#line 72 "effective_tld_names.gperf"
- {"adv.br", 0},
-#line 1268 "effective_tld_names.gperf"
- {"gov.tl", 0},
-#line 3054 "effective_tld_names.gperf"
- {"sv", 2},
-#line 2878 "effective_tld_names.gperf"
- {"sd.us", 0},
-#line 2408 "effective_tld_names.gperf"
- {"org.ly", 0},
-#line 1509 "effective_tld_names.gperf"
- {"int.mw", 0},
-#line 3039 "effective_tld_names.gperf"
- {"stv.ru", 0},
-#line 1207 "effective_tld_names.gperf"
- {"gov.ge", 0},
-#line 1458 "effective_tld_names.gperf"
- {"imb.br", 0},
-#line 70 "effective_tld_names.gperf"
- {"adm.br", 0},
-#line 838 "effective_tld_names.gperf"
- {"edu.ge", 0},
-#line 1352 "effective_tld_names.gperf"
- {"hamaroy.no", 0},
-#line 552 "effective_tld_names.gperf"
- {"co.in", 0},
-#line 1247 "effective_tld_names.gperf"
- {"gov.ng", 0},
-#line 2130 "effective_tld_names.gperf"
- {"net.ge", 0},
-#line 873 "effective_tld_names.gperf"
- {"edu.ng", 0},
-#line 1037 "effective_tld_names.gperf"
- {"fm", 0},
-#line 182 "effective_tld_names.gperf"
- {"art.br", 0},
-#line 1280 "effective_tld_names.gperf"
- {"gq", 0},
-#line 1521 "effective_tld_names.gperf"
- {"iq", 0},
-#line 617 "effective_tld_names.gperf"
- {"com.ge", 0},
-#line 2167 "effective_tld_names.gperf"
- {"net.ng", 0},
-#line 85 "effective_tld_names.gperf"
- {"ag", 0},
-#line 1258 "effective_tld_names.gperf"
- {"gov.rw", 0},
-#line 2000 "effective_tld_names.gperf"
- {"mp", 0},
-#line 887 "effective_tld_names.gperf"
- {"edu.rw", 0},
-#line 1731 "effective_tld_names.gperf"
- {"lc", 0},
-#line 653 "effective_tld_names.gperf"
- {"com.ng", 0},
-#line 2946 "effective_tld_names.gperf"
- {"sm", 0},
-#line 2179 "effective_tld_names.gperf"
- {"net.rw", 0},
-#line 938 "effective_tld_names.gperf"
- {"ens.tn", 0},
-#line 1227 "effective_tld_names.gperf"
- {"gov.ky", 0},
-#line 667 "effective_tld_names.gperf"
- {"com.rw", 0},
-#line 2378 "effective_tld_names.gperf"
- {"org.gg", 0},
-#line 856 "effective_tld_names.gperf"
- {"edu.ky", 0},
-#line 42 "effective_tld_names.gperf"
- {"ac.ir", 0},
-#line 1899 "effective_tld_names.gperf"
- {"mg", 0},
-#line 2149 "effective_tld_names.gperf"
- {"net.ky", 0},
-#line 810 "effective_tld_names.gperf"
- {"ed.ao", 0},
-#line 3405 "effective_tld_names.gperf"
- {"wy.us", 0},
-#line 1109 "effective_tld_names.gperf"
- {"gda.pl", 0},
-#line 247 "effective_tld_names.gperf"
- {"av.it", 0},
-#line 2038 "effective_tld_names.gperf"
- {"na.it", 0},
-#line 3069 "effective_tld_names.gperf"
- {"sz", 0},
-#line 541 "effective_tld_names.gperf"
+#line 685 "effective_tld_names.gperf"
+ {"com.sn", 0},
+#line 1596 "effective_tld_names.gperf"
+ {"jo", 0},
+#line 908 "effective_tld_names.gperf"
+ {"edu.sl", 0},
+#line 909 "effective_tld_names.gperf"
+ {"edu.sn", 0},
+#line 550 "effective_tld_names.gperf"
{"co.ao", 0},
-#line 635 "effective_tld_names.gperf"
- {"com.ky", 0},
-#line 428 "effective_tld_names.gperf"
- {"ca.it", 0},
-#line 1512 "effective_tld_names.gperf"
- {"int.rw", 0},
-#line 563 "effective_tld_names.gperf"
- {"co.mw", 0},
-#line 837 "effective_tld_names.gperf"
- {"edu.es", 0},
-#line 3633 "effective_tld_names.gperf"
- {"za.org", 0},
-#line 2242 "effective_tld_names.gperf"
- {"nom.es", 0},
-#line 615 "effective_tld_names.gperf"
- {"com.es", 0},
-#line 1819 "effective_tld_names.gperf"
- {"ma.us", 0},
-#line 2441 "effective_tld_names.gperf"
- {"org.sl", 0},
-#line 2048 "effective_tld_names.gperf"
- {"name", 0},
-#line 2416 "effective_tld_names.gperf"
- {"org.mw", 0},
-#line 2366 "effective_tld_names.gperf"
- {"org.bw", 0},
-#line 52 "effective_tld_names.gperf"
- {"ac.rs", 0},
-#line 1160 "effective_tld_names.gperf"
- {"gob.es", 0},
-#line 1168 "effective_tld_names.gperf"
- {"gol.no", 0},
-#line 1408 "effective_tld_names.gperf"
- {"hol.no", 0},
-#line 2443 "effective_tld_names.gperf"
- {"org.sy", 0},
-#line 1903 "effective_tld_names.gperf"
- {"mi.us", 0},
-#line 2374 "effective_tld_names.gperf"
- {"org.ec", 0},
-#line 1184 "effective_tld_names.gperf"
- {"gov.al", 0},
-#line 2845 "effective_tld_names.gperf"
- {"sc", 0},
-#line 818 "effective_tld_names.gperf"
- {"edu.al", 0},
-#line 186 "effective_tld_names.gperf"
- {"art.pl", 0},
-#line 1535 "effective_tld_names.gperf"
- {"it.ao", 0},
-#line 59 "effective_tld_names.gperf"
- {"ac.tz", 0},
-#line 2113 "effective_tld_names.gperf"
- {"net.al", 0},
-#line 1443 "effective_tld_names.gperf"
- {"id.lv", 0},
-#line 2470 "effective_tld_names.gperf"
- {"osteroy.no", 0},
-#line 593 "effective_tld_names.gperf"
- {"com.al", 0},
-#line 3648 "effective_tld_names.gperf"
- {"zp.ua", 0},
-#line 1206 "effective_tld_names.gperf"
- {"gov.ee", 0},
-#line 50 "effective_tld_names.gperf"
- {"ac.pa", 0},
-#line 2884 "effective_tld_names.gperf"
- {"sec.ps", 0},
-#line 836 "effective_tld_names.gperf"
- {"edu.ee", 0},
-#line 1876 "effective_tld_names.gperf"
- {"med.pa", 0},
-#line 2377 "effective_tld_names.gperf"
- {"org.ge", 0},
-#line 2283 "effective_tld_names.gperf"
- {"nt.no", 0},
-#line 1376 "effective_tld_names.gperf"
- {"hemnes.no", 0},
-#line 1273 "effective_tld_names.gperf"
- {"gov.tw", 0},
-#line 2420 "effective_tld_names.gperf"
- {"org.ng", 0},
-#line 1448 "effective_tld_names.gperf"
- {"idv.tw", 0},
-#line 614 "effective_tld_names.gperf"
- {"com.ee", 0},
-#line 899 "effective_tld_names.gperf"
- {"edu.tw", 0},
-#line 962 "effective_tld_names.gperf"
- {"eu.int", 0},
-#line 1895 "effective_tld_names.gperf"
- {"meraker.no", 0},
-#line 2194 "effective_tld_names.gperf"
- {"net.tw", 0},
-#line 1706 "effective_tld_names.gperf"
- {"la", 0},
-#line 231 "effective_tld_names.gperf"
- {"atm.pl", 0},
-#line 1773 "effective_tld_names.gperf"
- {"lo.it", 0},
-#line 2031 "effective_tld_names.gperf"
- {"mx.na", 0},
#line 681 "effective_tld_names.gperf"
- {"com.tw", 0},
-#line 2275 "effective_tld_names.gperf"
- {"ns.ca", 0},
-#line 1733 "effective_tld_names.gperf"
- {"le.it", 0},
-#line 1416 "effective_tld_names.gperf"
- {"horten.no", 0},
-#line 2280 "effective_tld_names.gperf"
- {"nt.ca", 0},
-#line 143 "effective_tld_names.gperf"
- {"amursk.ru", 0},
-#line 2399 "effective_tld_names.gperf"
- {"org.ky", 0},
-#line 1755 "effective_tld_names.gperf"
- {"li", 0},
-#line 2325 "effective_tld_names.gperf"
- {"on.ca", 0},
-#line 1212 "effective_tld_names.gperf"
- {"gov.gr", 0},
-#line 1863 "effective_tld_names.gperf"
- {"mc.it", 0},
-#line 2376 "effective_tld_names.gperf"
- {"org.es", 0},
-#line 843 "effective_tld_names.gperf"
- {"edu.gr", 0},
-#line 2847 "effective_tld_names.gperf"
- {"sc.kr", 0},
-#line 2134 "effective_tld_names.gperf"
- {"net.gr", 0},
-#line 622 "effective_tld_names.gperf"
- {"com.gr", 0},
-#line 1536 "effective_tld_names.gperf"
- {"its.me", 0},
-#line 497 "effective_tld_names.gperf"
- {"cim.br", 0},
-#line 1793 "effective_tld_names.gperf"
- {"lt.it", 0},
-#line 2855 "effective_tld_names.gperf"
- {"sch.lk", 0},
-#line 60 "effective_tld_names.gperf"
- {"ac.ug", 0},
-#line 3403 "effective_tld_names.gperf"
- {"wv.us", 0},
-#line 984 "effective_tld_names.gperf"
- {"fe.it", 0},
-#line 1331 "effective_tld_names.gperf"
- {"gv.ao", 0},
-#line 2800 "effective_tld_names.gperf"
- {"sa", 0},
-#line 2957 "effective_tld_names.gperf"
- {"so.it", 0},
-#line 2356 "effective_tld_names.gperf"
- {"org.al", 0},
-#line 49 "effective_tld_names.gperf"
- {"ac.ng", 0},
-#line 2287 "effective_tld_names.gperf"
- {"nu.ca", 0},
-#line 2955 "effective_tld_names.gperf"
- {"snz.ru", 0},
-#line 997 "effective_tld_names.gperf"
- {"fi", 0},
-#line 1879 "effective_tld_names.gperf"
- {"med.sa", 0},
-#line 1057 "effective_tld_names.gperf"
- {"fr.it", 0},
-#line 1088 "effective_tld_names.gperf"
- {"g.se", 0},
-#line 1436 "effective_tld_names.gperf"
- {"i.se", 0},
-#line 803 "effective_tld_names.gperf"
- {"e.se", 0},
-#line 1342 "effective_tld_names.gperf"
- {"h.se", 0},
-#line 3407 "effective_tld_names.gperf"
- {"x.se", 0},
-#line 2375 "effective_tld_names.gperf"
- {"org.ee", 0},
-#line 2910 "effective_tld_names.gperf"
- {"si", 0},
-#line 1913 "effective_tld_names.gperf"
- {"mil", 0},
-#line 2036 "effective_tld_names.gperf"
- {"n.se", 0},
-#line 162 "effective_tld_names.gperf"
- {"aq", 0},
-#line 2995 "effective_tld_names.gperf"
- {"sr.it", 0},
-#line 426 "effective_tld_names.gperf"
- {"c.se", 0},
-#line 2063 "effective_tld_names.gperf"
- {"napoli.it", 0},
-#line 3402 "effective_tld_names.gperf"
- {"ws.na", 0},
-#line 595 "effective_tld_names.gperf"
- {"com.aw", 0},
-#line 973 "effective_tld_names.gperf"
- {"fam.pk", 0},
-#line 1918 "effective_tld_names.gperf"
- {"mil.bo", 0},
-#line 2450 "effective_tld_names.gperf"
- {"org.tw", 0},
-#line 1922 "effective_tld_names.gperf"
- {"mil.co", 0},
-#line 1921 "effective_tld_names.gperf"
- {"mil.cn", 0},
-#line 3610 "effective_tld_names.gperf"
- {"y.se", 0},
-#line 2135 "effective_tld_names.gperf"
- {"net.gy", 0},
-#line 809 "effective_tld_names.gperf"
- {"ecn.br", 0},
-#line 1401 "effective_tld_names.gperf"
- {"hm.no", 0},
-#line 2849 "effective_tld_names.gperf"
- {"sc.us", 0},
-#line 2001 "effective_tld_names.gperf"
- {"mq", 0},
-#line 623 "effective_tld_names.gperf"
- {"com.gy", 0},
-#line 1248 "effective_tld_names.gperf"
- {"gov.nr", 0},
-#line 2284 "effective_tld_names.gperf"
- {"nt.ro", 0},
-#line 874 "effective_tld_names.gperf"
- {"edu.nr", 0},
-#line 2997 "effective_tld_names.gperf"
- {"ss.it", 0},
-#line 3629 "effective_tld_names.gperf"
- {"z.se", 0},
-#line 3632 "effective_tld_names.gperf"
- {"za.net", 0},
-#line 1798 "effective_tld_names.gperf"
- {"lu.it", 0},
-#line 1780 "effective_tld_names.gperf"
- {"lom.no", 0},
-#line 2168 "effective_tld_names.gperf"
- {"net.nr", 0},
-#line 2300 "effective_tld_names.gperf"
- {"o.se", 0},
-#line 2978 "effective_tld_names.gperf"
- {"sortland.no", 0},
-#line 654 "effective_tld_names.gperf"
- {"com.nr", 0},
-#line 2383 "effective_tld_names.gperf"
- {"org.gr", 0},
-#line 210 "effective_tld_names.gperf"
- {"ass.km", 0},
-#line 1278 "effective_tld_names.gperf"
- {"government.aero", 0},
-#line 1468 "effective_tld_names.gperf"
- {"ind.br", 0},
-#line 1917 "effective_tld_names.gperf"
- {"mil.ba", 0},
-#line 1708 "effective_tld_names.gperf"
- {"la.us", 0},
-#line 1054 "effective_tld_names.gperf"
- {"fot.br", 0},
+ {"com.sc", 0},
+#line 568 "effective_tld_names.gperf"
+ {"co.ls", 0},
+#line 689 "effective_tld_names.gperf"
+ {"com.tn", 0},
+#line 688 "effective_tld_names.gperf"
+ {"com.tj", 0},
+#line 822 "effective_tld_names.gperf"
+ {"ed.ao", 0},
+#line 905 "effective_tld_names.gperf"
+ {"edu.sc", 0},
+#line 650 "effective_tld_names.gperf"
+ {"com.lr", 0},
+#line 912 "effective_tld_names.gperf"
+ {"edu.tj", 0},
#line 1124 "effective_tld_names.gperf"
- {"ggf.br", 0},
-#line 560 "effective_tld_names.gperf"
- {"co.ma", 0},
-#line 33 "effective_tld_names.gperf"
- {"ac.ae", 0},
-#line 1901 "effective_tld_names.gperf"
- {"mi.it", 0},
-#line 1108 "effective_tld_names.gperf"
- {"gd.cn", 0},
-#line 1368 "effective_tld_names.gperf"
- {"he.cn", 0},
-#line 3264 "effective_tld_names.gperf"
- {"uy", 2},
-#line 1712 "effective_tld_names.gperf"
- {"lahppi.no", 0},
-#line 990 "effective_tld_names.gperf"
- {"fet.no", 0},
-#line 2003 "effective_tld_names.gperf"
- {"mr.no", 0},
-#line 2692 "effective_tld_names.gperf"
- {"qa", 2},
-#line 440 "effective_tld_names.gperf"
- {"can.museum", 0},
-#line 2471 "effective_tld_names.gperf"
- {"ostre-toten.no", 0},
-#line 1308 "effective_tld_names.gperf"
- {"gs.jan-mayen.no", 0},
-#line 2996 "effective_tld_names.gperf"
- {"srv.br", 0},
-#line 1941 "effective_tld_names.gperf"
- {"mil.ru", 0},
-#line 2898 "effective_tld_names.gperf"
- {"sg", 0},
-#line 103 "effective_tld_names.gperf"
- {"air.museum", 0},
-#line 2857 "effective_tld_names.gperf"
- {"sch.sa", 0},
-#line 1106 "effective_tld_names.gperf"
- {"gc.ca", 0},
-#line 1403 "effective_tld_names.gperf"
- {"hn.cn", 0},
-#line 1796 "effective_tld_names.gperf"
- {"ltd.lk", 0},
-#line 1753 "effective_tld_names.gperf"
- {"lg.jp", 0},
-#line 1075 "effective_tld_names.gperf"
- {"fst.br", 0},
-#line 38 "effective_tld_names.gperf"
- {"ac.cr", 0},
-#line 3243 "effective_tld_names.gperf"
- {"us", 0},
+ {"gd", 0},
+#line 682 "effective_tld_names.gperf"
+ {"com.sd", 0},
+#line 875 "effective_tld_names.gperf"
+ {"edu.lr", 0},
+#line 1323 "effective_tld_names.gperf"
+ {"gs", 0},
+#line 648 "effective_tld_names.gperf"
+ {"com.lc", 0},
+#line 906 "effective_tld_names.gperf"
+ {"edu.sd", 0},
+#line 551 "effective_tld_names.gperf"
+ {"co.at", 0},
+#line 1564 "effective_tld_names.gperf"
+ {"it.ao", 0},
+#line 873 "effective_tld_names.gperf"
+ {"edu.lc", 0},
+#line 1447 "effective_tld_names.gperf"
+ {"ht", 0},
+#line 1347 "effective_tld_names.gperf"
+ {"gt", 2},
+#line 1154 "effective_tld_names.gperf"
+ {"gl", 0},
+#line 563 "effective_tld_names.gperf"
+ {"co.it", 0},
+#line 756 "effective_tld_names.gperf"
+ {"cz.it", 0},
+#line 159 "effective_tld_names.gperf"
+ {"ao.it", 0},
+#line 1200 "effective_tld_names.gperf"
+ {"gov", 0},
+#line 741 "effective_tld_names.gperf"
+ {"cs.it", 0},
+#line 1557 "effective_tld_names.gperf"
+ {"is.it", 0},
+#line 732 "effective_tld_names.gperf"
+ {"cr", 0},
+#line 962 "effective_tld_names.gperf"
+ {"er", 2},
+#line 1550 "effective_tld_names.gperf"
+ {"ir", 0},
+#line 168 "effective_tld_names.gperf"
+ {"ar", 2},
+#line 742 "effective_tld_names.gperf"
+ {"ct.it", 0},
+#line 234 "effective_tld_names.gperf"
+ {"at.it", 0},
+#line 534 "effective_tld_names.gperf"
+ {"cl.it", 0},
+#line 116 "effective_tld_names.gperf"
+ {"al.it", 0},
+#line 1288 "effective_tld_names.gperf"
+ {"gov.sl", 0},
+#line 127 "effective_tld_names.gperf"
+ {"alta.no", 0},
+#line 925 "effective_tld_names.gperf"
+ {"ee", 0},
+#line 1472 "effective_tld_names.gperf"
+ {"ie", 0},
+#line 75 "effective_tld_names.gperf"
+ {"ae", 0},
+#line 1285 "effective_tld_names.gperf"
+ {"gov.sc", 0},
+#line 1292 "effective_tld_names.gperf"
+ {"gov.tl", 0},
+#line 1293 "effective_tld_names.gperf"
+ {"gov.tn", 0},
+#line 1291 "effective_tld_names.gperf"
+ {"gov.tj", 0},
+#line 680 "effective_tld_names.gperf"
+ {"com.sb", 0},
+#line 1167 "effective_tld_names.gperf"
+ {"go.it", 0},
+#line 1254 "effective_tld_names.gperf"
+ {"gov.lr", 0},
+#line 2272 "effective_tld_names.gperf"
+ {"no", 0},
+#line 2336 "effective_tld_names.gperf"
+ {"nz", 2},
+#line 3678 "effective_tld_names.gperf"
+ {"ye", 2},
+#line 904 "effective_tld_names.gperf"
+ {"edu.sb", 0},
+#line 1286 "effective_tld_names.gperf"
+ {"gov.sd", 0},
+#line 1252 "effective_tld_names.gperf"
+ {"gov.lc", 0},
+#line 1445 "effective_tld_names.gperf"
+ {"hr", 0},
#line 1304 "effective_tld_names.gperf"
- {"gs.cn", 0},
-#line 1396 "effective_tld_names.gperf"
- {"hk", 0},
-#line 3620 "effective_tld_names.gperf"
- {"yn.cn", 0},
-#line 3055 "effective_tld_names.gperf"
- {"sv.it", 0},
-#line 932 "effective_tld_names.gperf"
- {"eng.pro", 0},
-#line 1943 "effective_tld_names.gperf"
- {"mil.st", 0},
-#line 161 "effective_tld_names.gperf"
- {"ap.it", 0},
-#line 522 "effective_tld_names.gperf"
- {"ck", 2},
-#line 439 "effective_tld_names.gperf"
- {"can.br", 0},
-#line 1215 "effective_tld_names.gperf"
- {"gov.im", 0},
-#line 1216 "effective_tld_names.gperf"
- {"gov.in", 0},
-#line 2421 "effective_tld_names.gperf"
- {"org.nr", 0},
-#line 847 "effective_tld_names.gperf"
- {"edu.in", 0},
-#line 1220 "effective_tld_names.gperf"
- {"gov.it", 0},
-#line 1118 "effective_tld_names.gperf"
- {"genova.it", 0},
+ {"gr", 0},
+#line 647 "effective_tld_names.gperf"
+ {"com.lb", 0},
+#line 622 "effective_tld_names.gperf"
+ {"com.ec", 0},
+#line 1579 "effective_tld_names.gperf"
+ {"je", 0},
+#line 872 "effective_tld_names.gperf"
+ {"edu.lb", 0},
+#line 848 "effective_tld_names.gperf"
+ {"edu.ec", 0},
+#line 548 "effective_tld_names.gperf"
+ {"co.ae", 0},
+#line 562 "effective_tld_names.gperf"
+ {"co.ir", 0},
+#line 2265 "effective_tld_names.gperf"
+ {"nl", 0},
+#line 671 "effective_tld_names.gperf"
+ {"com.pl", 0},
+#line 900 "effective_tld_names.gperf"
+ {"edu.rs", 0},
+#line 672 "effective_tld_names.gperf"
+ {"com.pr", 0},
+#line 1129 "effective_tld_names.gperf"
+ {"ge", 0},
+#line 1465 "effective_tld_names.gperf"
+ {"id.ir", 0},
+#line 895 "effective_tld_names.gperf"
+ {"edu.pl", 0},
+#line 896 "effective_tld_names.gperf"
+ {"edu.pn", 0},
+#line 897 "effective_tld_names.gperf"
+ {"edu.pr", 0},
+#line 624 "effective_tld_names.gperf"
+ {"com.es", 0},
+#line 1608 "effective_tld_names.gperf"
+ {"jp", 0},
+#line 189 "effective_tld_names.gperf"
+ {"art.sn", 0},
+#line 692 "effective_tld_names.gperf"
+ {"com.tw", 0},
+#line 733 "effective_tld_names.gperf"
+ {"cr.it", 0},
#line 850 "effective_tld_names.gperf"
- {"edu.it", 0},
-#line 2139 "effective_tld_names.gperf"
- {"net.im", 0},
-#line 2140 "effective_tld_names.gperf"
+ {"edu.es", 0},
+#line 170 "effective_tld_names.gperf"
+ {"ar.it", 0},
+#line 915 "effective_tld_names.gperf"
+ {"edu.tw", 0},
+#line 236 "effective_tld_names.gperf"
+ {"atm.pl", 0},
+#line 1302 "effective_tld_names.gperf"
+ {"gp", 0},
+#line 673 "effective_tld_names.gperf"
+ {"com.ps", 0},
+#line 470 "effective_tld_names.gperf"
+ {"ce.it", 0},
+#line 898 "effective_tld_names.gperf"
+ {"edu.ps", 0},
+#line 1284 "effective_tld_names.gperf"
+ {"gov.sb", 0},
+#line 220 "effective_tld_names.gperf"
+ {"asso.dz", 0},
+#line 2274 "effective_tld_names.gperf"
+ {"no.it", 0},
+#line 969 "effective_tld_names.gperf"
+ {"est.pr", 0},
+#line 1346 "effective_tld_names.gperf"
+ {"gsm.pl", 0},
+#line 1471 "effective_tld_names.gperf"
+ {"idv.tw", 0},
+#line 1177 "effective_tld_names.gperf"
+ {"gob.es", 0},
+#line 163 "effective_tld_names.gperf"
+ {"ap.it", 0},
+#line 1305 "effective_tld_names.gperf"
+ {"gr.it", 0},
+#line 1251 "effective_tld_names.gperf"
+ {"gov.lb", 0},
+#line 2310 "effective_tld_names.gperf"
+ {"nr", 0},
+#line 1226 "effective_tld_names.gperf"
+ {"gov.ec", 0},
+#line 716 "effective_tld_names.gperf"
+ {"coop.br", 0},
+#line 1280 "effective_tld_names.gperf"
+ {"gov.rs", 0},
+#line 1275 "effective_tld_names.gperf"
+ {"gov.pl", 0},
+#line 1276 "effective_tld_names.gperf"
+ {"gov.pn", 0},
+#line 1277 "effective_tld_names.gperf"
+ {"gov.pr", 0},
+#line 1130 "effective_tld_names.gperf"
+ {"ge.it", 0},
+#line 2124 "effective_tld_names.gperf"
+ {"ne", 0},
+#line 678 "effective_tld_names.gperf"
+ {"com.rw", 0},
+#line 902 "effective_tld_names.gperf"
+ {"edu.rw", 0},
+#line 1296 "effective_tld_names.gperf"
+ {"gov.tw", 0},
+#line 2309 "effective_tld_names.gperf"
+ {"np", 2},
+#line 541 "effective_tld_names.gperf"
+ {"cn", 0},
+#line 1278 "effective_tld_names.gperf"
+ {"gov.ps", 0},
+#line 1484 "effective_tld_names.gperf"
+ {"in", 0},
+#line 147 "effective_tld_names.gperf"
+ {"an", 0},
+#line 570 "effective_tld_names.gperf"
+ {"co.me", 0},
+#line 2285 "effective_tld_names.gperf"
+ {"nom.pl", 0},
+#line 744 "effective_tld_names.gperf"
+ {"cu", 0},
+#line 977 "effective_tld_names.gperf"
+ {"eu", 0},
+#line 2141 "effective_tld_names.gperf"
+ {"net", 0},
+#line 238 "effective_tld_names.gperf"
+ {"au", 2},
+#line 665 "effective_tld_names.gperf"
+ {"com.nr", 0},
+#line 188 "effective_tld_names.gperf"
+ {"art.pl", 0},
+#line 227 "effective_tld_names.gperf"
+ {"asso.re", 0},
+#line 2279 "effective_tld_names.gperf"
+ {"nom.es", 0},
+#line 889 "effective_tld_names.gperf"
+ {"edu.nr", 0},
+#line 3687 "effective_tld_names.gperf"
+ {"yu", 2},
+#line 574 "effective_tld_names.gperf"
+ {"co.pn", 0},
+#line 2222 "effective_tld_names.gperf"
+ {"net.sl", 0},
+#line 1425 "effective_tld_names.gperf"
+ {"hn", 0},
+#line 1163 "effective_tld_names.gperf"
+ {"gn", 0},
+#line 1528 "effective_tld_names.gperf"
+ {"int", 0},
+#line 1282 "effective_tld_names.gperf"
+ {"gov.rw", 0},
+#line 2219 "effective_tld_names.gperf"
+ {"net.sc", 0},
+#line 2227 "effective_tld_names.gperf"
+ {"net.tn", 0},
+#line 561 "effective_tld_names.gperf"
+ {"co.in", 0},
+#line 2226 "effective_tld_names.gperf"
+ {"net.tj", 0},
+#line 576 "effective_tld_names.gperf"
+ {"co.rs", 0},
+#line 2190 "effective_tld_names.gperf"
+ {"net.lr", 0},
+#line 1448 "effective_tld_names.gperf"
+ {"hu", 0},
+#line 1348 "effective_tld_names.gperf"
+ {"gu", 2},
+#line 2220 "effective_tld_names.gperf"
+ {"net.sd", 0},
+#line 2188 "effective_tld_names.gperf"
+ {"net.lc", 0},
+#line 543 "effective_tld_names.gperf"
+ {"cn.it", 0},
+#line 945 "effective_tld_names.gperf"
+ {"en.it", 0},
+#line 148 "effective_tld_names.gperf"
+ {"an.it", 0},
+#line 955 "effective_tld_names.gperf"
+ {"ens.tn", 0},
+#line 1541 "effective_tld_names.gperf"
+ {"int.tj", 0},
+#line 539 "effective_tld_names.gperf"
+ {"cm", 0},
+#line 1479 "effective_tld_names.gperf"
+ {"im", 0},
+#line 132 "effective_tld_names.gperf"
+ {"am", 0},
+#line 1272 "effective_tld_names.gperf"
+ {"gov.nr", 0},
+#line 3706 "effective_tld_names.gperf"
+ {"zm", 2},
+#line 1552 "effective_tld_names.gperf"
+ {"irc.pl", 0},
+#line 1503 "effective_tld_names.gperf"
+ {"info", 0},
+#line 655 "effective_tld_names.gperf"
+ {"com.ml", 0},
+#line 881 "effective_tld_names.gperf"
+ {"edu.ml", 0},
+#line 882 "effective_tld_names.gperf"
+ {"edu.mn", 0},
+#line 566 "effective_tld_names.gperf"
+ {"co.kr", 0},
+#line 1595 "effective_tld_names.gperf"
+ {"jm", 2},
+#line 675 "effective_tld_names.gperf"
+ {"com.re", 0},
+#line 966 "effective_tld_names.gperf"
+ {"es.kr", 0},
+#line 1520 "effective_tld_names.gperf"
+ {"info.ro", 0},
+#line 2218 "effective_tld_names.gperf"
+ {"net.sb", 0},
+#line 1423 "effective_tld_names.gperf"
+ {"hm", 0},
+#line 1161 "effective_tld_names.gperf"
+ {"gm", 0},
+#line 1507 "effective_tld_names.gperf"
+ {"info.co", 0},
+#line 2324 "effective_tld_names.gperf"
+ {"nu", 0},
+#line 623 "effective_tld_names.gperf"
+ {"com.ee", 0},
+#line 560 "effective_tld_names.gperf"
+ {"co.im", 0},
+#line 2187 "effective_tld_names.gperf"
+ {"net.lb", 0},
+#line 2164 "effective_tld_names.gperf"
+ {"net.ec", 0},
+#line 849 "effective_tld_names.gperf"
+ {"edu.ee", 0},
+#line 2210 "effective_tld_names.gperf"
+ {"net.pl", 0},
+#line 2211 "effective_tld_names.gperf"
+ {"net.pn", 0},
+#line 2212 "effective_tld_names.gperf"
+ {"net.pr", 0},
+#line 1169 "effective_tld_names.gperf"
+ {"go.kr", 0},
+#line 667 "effective_tld_names.gperf"
+ {"com.pe", 0},
+#line 1521 "effective_tld_names.gperf"
+ {"info.sd", 0},
+#line 1480 "effective_tld_names.gperf"
+ {"im.it", 0},
+#line 1446 "effective_tld_names.gperf"
+ {"hs.kr", 0},
+#line 891 "effective_tld_names.gperf"
+ {"edu.pe", 0},
+#line 949 "effective_tld_names.gperf"
+ {"eng.pro", 0},
+#line 2230 "effective_tld_names.gperf"
+ {"net.tw", 0},
+#line 571 "effective_tld_names.gperf"
+ {"co.mu", 0},
+#line 1262 "effective_tld_names.gperf"
+ {"gov.ml", 0},
+#line 1263 "effective_tld_names.gperf"
+ {"gov.mn", 0},
+#line 1265 "effective_tld_names.gperf"
+ {"gov.mr", 0},
+#line 2322 "effective_tld_names.gperf"
+ {"nt.ro", 0},
+#line 2213 "effective_tld_names.gperf"
+ {"net.ps", 0},
+#line 1126 "effective_tld_names.gperf"
+ {"gda.pl", 0},
+#line 1518 "effective_tld_names.gperf"
+ {"info.pl", 0},
+#line 2317 "effective_tld_names.gperf"
+ {"nt.au", 0},
+#line 125 "effective_tld_names.gperf"
+ {"algard.no", 0},
+#line 1181 "effective_tld_names.gperf"
+ {"gob.pe", 0},
+#line 1337 "effective_tld_names.gperf"
+ {"gs.oslo.no", 0},
+#line 2326 "effective_tld_names.gperf"
+ {"nu.it", 0},
+#line 1516 "effective_tld_names.gperf"
+ {"info.nr", 0},
+#line 1227 "effective_tld_names.gperf"
+ {"gov.ee", 0},
+#line 436 "effective_tld_names.gperf"
+ {"ca", 0},
+#line 564 "effective_tld_names.gperf"
+ {"co.je", 0},
+#line 659 "effective_tld_names.gperf"
+ {"com.mw", 0},
+#line 1270 "effective_tld_names.gperf"
+ {"gov.nc.tr", 0},
+#line 3691 "effective_tld_names.gperf"
+ {"za", 2},
+#line 885 "effective_tld_names.gperf"
+ {"edu.mw", 0},
+#line 2216 "effective_tld_names.gperf"
+ {"net.rw", 0},
+#line 602 "effective_tld_names.gperf"
+ {"com.al", 0},
+#line 603 "effective_tld_names.gperf"
+ {"com.an", 0},
+#line 250 "effective_tld_names.gperf"
+ {"auto.pl", 0},
+#line 565 "effective_tld_names.gperf"
+ {"co.jp", 0},
+#line 2286 "effective_tld_names.gperf"
+ {"nom.re", 0},
+#line 830 "effective_tld_names.gperf"
+ {"edu.al", 0},
+#line 831 "effective_tld_names.gperf"
+ {"edu.an", 0},
+#line 825 "effective_tld_names.gperf"
+ {"ed.jp", 0},
+#line 598 "effective_tld_names.gperf"
+ {"com.ac", 0},
+#line 70 "effective_tld_names.gperf"
+ {"ad.jp", 0},
+#line 1519 "effective_tld_names.gperf"
+ {"info.pr", 0},
+#line 828 "effective_tld_names.gperf"
+ {"edu.ac", 0},
+#line 1107 "effective_tld_names.gperf"
+ {"ga", 0},
+#line 1540 "effective_tld_names.gperf"
+ {"int.rw", 0},
+#line 460 "effective_tld_names.gperf"
+ {"cat", 0},
+#line 730 "effective_tld_names.gperf"
+ {"cpa.pro", 0},
+#line 2284 "effective_tld_names.gperf"
+ {"nom.pe", 0},
+#line 2205 "effective_tld_names.gperf"
+ {"net.nr", 0},
+#line 1168 "effective_tld_names.gperf"
+ {"go.jp", 0},
+#line 437 "effective_tld_names.gperf"
+ {"ca.it", 0},
+#line 1268 "effective_tld_names.gperf"
+ {"gov.mw", 0},
+#line 1487 "effective_tld_names.gperf"
+ {"in.rs", 0},
+#line 133 "effective_tld_names.gperf"
+ {"am.br", 0},
+#line 1204 "effective_tld_names.gperf"
+ {"gov.al", 0},
+#line 1201 "effective_tld_names.gperf"
+ {"gov.ac", 0},
+#line 1558 "effective_tld_names.gperf"
+ {"isa.us", 0},
+#line 2126 "effective_tld_names.gperf"
+ {"ne.kr", 0},
+#line 2070 "effective_tld_names.gperf"
+ {"na", 0},
+#line 1205 "effective_tld_names.gperf"
+ {"gov.as", 0},
+#line 1549 "effective_tld_names.gperf"
+ {"iq", 0},
+#line 164 "effective_tld_names.gperf"
+ {"aq", 0},
+#line 552 "effective_tld_names.gperf"
+ {"co.ba", 0},
+#line 604 "effective_tld_names.gperf"
+ {"com.aw", 0},
+#line 372 "effective_tld_names.gperf"
+ {"bo", 0},
+#line 431 "effective_tld_names.gperf"
+ {"bz", 0},
+#line 302 "effective_tld_names.gperf"
+ {"bd", 2},
+#line 414 "effective_tld_names.gperf"
+ {"bs", 0},
+#line 2196 "effective_tld_names.gperf"
+ {"net.ml", 0},
+#line 1306 "effective_tld_names.gperf"
+ {"gr.jp", 0},
+#line 569 "effective_tld_names.gperf"
+ {"co.ma", 0},
+#line 679 "effective_tld_names.gperf"
+ {"com.sa", 0},
+#line 416 "effective_tld_names.gperf"
+ {"bt", 2},
+#line 2275 "effective_tld_names.gperf"
+ {"nom.ad", 0},
+#line 878 "effective_tld_names.gperf"
+ {"edu.me", 0},
+#line 903 "effective_tld_names.gperf"
+ {"edu.sa", 0},
+#line 1303 "effective_tld_names.gperf"
+ {"gq", 0},
+#line 646 "effective_tld_names.gperf"
+ {"com.la", 0},
+#line 871 "effective_tld_names.gperf"
+ {"edu.la", 0},
+#line 649 "effective_tld_names.gperf"
+ {"com.lk", 0},
+#line 117 "effective_tld_names.gperf"
+ {"al.no", 0},
+#line 874 "effective_tld_names.gperf"
+ {"edu.lk", 0},
+#line 2071 "effective_tld_names.gperf"
+ {"na.it", 0},
+#line 2102 "effective_tld_names.gperf"
+ {"nat.tn", 0},
+#line 2207 "effective_tld_names.gperf"
+ {"net.pe", 0},
+#line 165 "effective_tld_names.gperf"
+ {"aq.it", 0},
+#line 373 "effective_tld_names.gperf"
+ {"bo.it", 0},
+#line 432 "effective_tld_names.gperf"
+ {"bz.it", 0},
+#line 1565 "effective_tld_names.gperf"
+ {"its.me", 0},
+#line 415 "effective_tld_names.gperf"
+ {"bs.it", 0},
+#line 1422 "effective_tld_names.gperf"
+ {"hl.no", 0},
+#line 390 "effective_tld_names.gperf"
+ {"br", 0},
+#line 1259 "effective_tld_names.gperf"
+ {"gov.me", 0},
+#line 1283 "effective_tld_names.gperf"
+ {"gov.sa", 0},
+#line 365 "effective_tld_names.gperf"
+ {"bl.it", 0},
+#line 304 "effective_tld_names.gperf"
+ {"be", 0},
+#line 2125 "effective_tld_names.gperf"
+ {"ne.jp", 0},
+#line 1250 "effective_tld_names.gperf"
+ {"gov.la", 0},
+#line 1253 "effective_tld_names.gperf"
+ {"gov.lk", 0},
+#line 181 "effective_tld_names.gperf"
+ {"arna.no", 0},
+#line 2200 "effective_tld_names.gperf"
+ {"net.mw", 0},
+#line 666 "effective_tld_names.gperf"
+ {"com.pa", 0},
+#line 890 "effective_tld_names.gperf"
+ {"edu.pa", 0},
+#line 2147 "effective_tld_names.gperf"
+ {"net.al", 0},
+#line 2148 "effective_tld_names.gperf"
+ {"net.an", 0},
+#line 670 "effective_tld_names.gperf"
+ {"com.pk", 0},
+#line 894 "effective_tld_names.gperf"
+ {"edu.pk", 0},
+#line 2142 "effective_tld_names.gperf"
+ {"net.ac", 0},
+#line 1537 "effective_tld_names.gperf"
+ {"int.mw", 0},
+#line 2321 "effective_tld_names.gperf"
+ {"nt.no", 0},
+#line 392 "effective_tld_names.gperf"
+ {"br.it", 0},
+#line 2267 "effective_tld_names.gperf"
+ {"nl.no", 0},
+#line 757 "effective_tld_names.gperf"
+ {"czeladz.pl", 0},
+#line 752 "effective_tld_names.gperf"
+ {"cy", 2},
+#line 1180 "effective_tld_names.gperf"
+ {"gob.pa", 0},
+#line 434 "effective_tld_names.gperf"
+ {"c.la", 0},
+#line 1182 "effective_tld_names.gperf"
+ {"gob.pk", 0},
+#line 1467 "effective_tld_names.gperf"
+ {"id.ly", 0},
+#line 1359 "effective_tld_names.gperf"
+ {"gy", 0},
+#line 1274 "effective_tld_names.gperf"
+ {"gov.pk", 0},
+#line 1202 "effective_tld_names.gperf"
+ {"gov.ae", 0},
+#line 1191 "effective_tld_names.gperf"
+ {"gos.pk", 0},
+#line 1512 "effective_tld_names.gperf"
+ {"info.la", 0},
+#line 1514 "effective_tld_names.gperf"
+ {"info.na", 0},
+#line 370 "effective_tld_names.gperf"
+ {"bn", 2},
+#line 2283 "effective_tld_names.gperf"
+ {"nom.pa", 0},
+#line 662 "effective_tld_names.gperf"
+ {"com.na", 0},
+#line 504 "effective_tld_names.gperf"
+ {"ci", 0},
+#line 99 "effective_tld_names.gperf"
+ {"ai", 0},
+#line 693 "effective_tld_names.gperf"
+ {"com.ua", 0},
+#line 2194 "effective_tld_names.gperf"
+ {"net.me", 0},
+#line 1494 "effective_tld_names.gperf"
+ {"ind.tn", 0},
+#line 2217 "effective_tld_names.gperf"
+ {"net.sa", 0},
+#line 916 "effective_tld_names.gperf"
+ {"edu.ua", 0},
+#line 230 "effective_tld_names.gperf"
+ {"asti.it", 0},
+#line 860 "effective_tld_names.gperf"
+ {"edu.in", 0},
+#line 144 "effective_tld_names.gperf"
+ {"amur.ru", 0},
+#line 2186 "effective_tld_names.gperf"
+ {"net.la", 0},
+#line 702 "effective_tld_names.gperf"
+ {"como.it", 0},
+#line 2189 "effective_tld_names.gperf"
+ {"net.lk", 0},
+#line 1143 "effective_tld_names.gperf"
+ {"gi", 0},
+#line 2303 "effective_tld_names.gperf"
+ {"notodden.no", 0},
+#line 639 "effective_tld_names.gperf"
+ {"com.is", 0},
+#line 371 "effective_tld_names.gperf"
+ {"bn.it", 0},
+#line 1534 "effective_tld_names.gperf"
+ {"int.la", 0},
+#line 862 "effective_tld_names.gperf"
+ {"edu.is", 0},
+#line 638 "effective_tld_names.gperf"
+ {"com.iq", 0},
+#line 1535 "effective_tld_names.gperf"
+ {"int.lk", 0},
+#line 861 "effective_tld_names.gperf"
+ {"edu.iq", 0},
+#line 368 "effective_tld_names.gperf"
+ {"bm", 0},
+#line 1561 "effective_tld_names.gperf"
+ {"isla.pr", 0},
+#line 537 "effective_tld_names.gperf"
+ {"club.aero", 0},
+#line 1297 "effective_tld_names.gperf"
+ {"gov.ua", 0},
+#line 654 "effective_tld_names.gperf"
+ {"com.mk", 0},
+#line 1237 "effective_tld_names.gperf"
+ {"gov.in", 0},
+#line 1239 "effective_tld_names.gperf"
+ {"gov.ir", 0},
+#line 880 "effective_tld_names.gperf"
+ {"edu.mk", 0},
+#line 1505 "effective_tld_names.gperf"
+ {"info.az", 0},
+#line 718 "effective_tld_names.gperf"
+ {"coop.km", 0},
+#line 1504 "effective_tld_names.gperf"
+ {"info.at", 0},
+#line 2252 "effective_tld_names.gperf"
+ {"ni", 2},
+#line 1240 "effective_tld_names.gperf"
+ {"gov.is", 0},
+#line 738 "effective_tld_names.gperf"
+ {"crew.aero", 0},
+#line 2206 "effective_tld_names.gperf"
+ {"net.pa", 0},
+#line 1308 "effective_tld_names.gperf"
+ {"gran.no", 0},
+#line 1160 "effective_tld_names.gperf"
+ {"gloppen.no", 0},
+#line 1424 "effective_tld_names.gperf"
+ {"hm.no", 0},
+#line 553 "effective_tld_names.gperf"
+ {"co.bi", 0},
+#line 1238 "effective_tld_names.gperf"
+ {"gov.iq", 0},
+#line 1525 "effective_tld_names.gperf"
+ {"ing.pa", 0},
+#line 2209 "effective_tld_names.gperf"
+ {"net.pk", 0},
+#line 2143 "effective_tld_names.gperf"
+ {"net.ae", 0},
+#line 1058 "effective_tld_names.gperf"
+ {"fo", 0},
+#line 2137 "effective_tld_names.gperf"
+ {"nesodden.no", 0},
+#line 1368 "effective_tld_names.gperf"
+ {"habmer.no", 0},
+#line 1258 "effective_tld_names.gperf"
+ {"gov.ma", 0},
+#line 2335 "effective_tld_names.gperf"
+ {"nysa.pl", 0},
+#line 1261 "effective_tld_names.gperf"
+ {"gov.mk", 0},
+#line 2314 "effective_tld_names.gperf"
+ {"nsn.us", 0},
+#line 219 "effective_tld_names.gperf"
+ {"asso.ci", 0},
+#line 690 "effective_tld_names.gperf"
+ {"com.to", 0},
+#line 1587 "effective_tld_names.gperf"
+ {"jet.uk", 1},
+#line 913 "effective_tld_names.gperf"
+ {"edu.to", 0},
+#line 2268 "effective_tld_names.gperf"
+ {"nls.uk", 1},
+#line 265 "effective_tld_names.gperf"
+ {"ba", 0},
+#line 573 "effective_tld_names.gperf"
+ {"co.na", 0},
+#line 28 "effective_tld_names.gperf"
+ {"aa.no", 0},
+#line 1073 "effective_tld_names.gperf"
+ {"fr", 0},
+#line 224 "effective_tld_names.gperf"
+ {"asso.km", 0},
+#line 204 "effective_tld_names.gperf"
+ {"aseral.no", 0},
+#line 1199 "effective_tld_names.gperf"
+ {"gouv.sn", 0},
+#line 1307 "effective_tld_names.gperf"
+ {"grajewo.pl", 0},
+#line 676 "effective_tld_names.gperf"
+ {"com.ro", 0},
+#line 1294 "effective_tld_names.gperf"
+ {"gov.to", 0},
+#line 1367 "effective_tld_names.gperf"
+ {"ha.no", 0},
+#line 2231 "effective_tld_names.gperf"
+ {"net.ua", 0},
+#line 266 "effective_tld_names.gperf"
+ {"ba.it", 0},
+#line 2175 "effective_tld_names.gperf"
{"net.in", 0},
-#line 1855 "effective_tld_names.gperf"
- {"mat.br", 0},
-#line 628 "effective_tld_names.gperf"
- {"com.io", 0},
-#line 86 "effective_tld_names.gperf"
- {"ag.it", 0},
-#line 2881 "effective_tld_names.gperf"
- {"se.net", 0},
-#line 1732 "effective_tld_names.gperf"
- {"lc.it", 0},
-#line 1946 "effective_tld_names.gperf"
- {"mil.to", 0},
-#line 1824 "effective_tld_names.gperf"
- {"magazine.aero", 0},
-#line 1980 "effective_tld_names.gperf"
- {"modalen.no", 0},
+#line 2177 "effective_tld_names.gperf"
+ {"net.ir", 0},
+#line 2256 "effective_tld_names.gperf"
+ {"nic.tj", 0},
+#line 141 "effective_tld_names.gperf"
+ {"amli.no", 0},
+#line 1399 "effective_tld_names.gperf"
+ {"hemnes.no", 0},
+#line 1074 "effective_tld_names.gperf"
+ {"fr.it", 0},
+#line 2178 "effective_tld_names.gperf"
+ {"net.is", 0},
+#line 2176 "effective_tld_names.gperf"
+ {"net.iq", 0},
+#line 1001 "effective_tld_names.gperf"
+ {"fe.it", 0},
+#line 1533 "effective_tld_names.gperf"
+ {"int.is", 0},
+#line 2193 "effective_tld_names.gperf"
+ {"net.ma", 0},
+#line 1548 "effective_tld_names.gperf"
+ {"ip6.arpa", 0},
+#line 1502 "effective_tld_names.gperf"
+ {"inf.mk", 0},
+#line 2195 "effective_tld_names.gperf"
+ {"net.mk", 0},
+#line 78 "effective_tld_names.gperf"
+ {"aero", 0},
+#line 1235 "effective_tld_names.gperf"
+ {"gov.ie", 0},
+#line 2287 "effective_tld_names.gperf"
+ {"nom.ro", 0},
+#line 1081 "effective_tld_names.gperf"
+ {"frei.no", 0},
+#line 601 "effective_tld_names.gperf"
+ {"com.ai", 0},
+#line 1119 "effective_tld_names.gperf"
+ {"gaular.no", 0},
+#line 816 "effective_tld_names.gperf"
+ {"e164.arpa", 0},
+#line 376 "effective_tld_names.gperf"
+ {"bodo.no", 0},
+#line 2228 "effective_tld_names.gperf"
+ {"net.to", 0},
+#line 2073 "effective_tld_names.gperf"
+ {"nacion.ar", 1},
+#line 1428 "effective_tld_names.gperf"
+ {"hof.no", 0},
+#line 1486 "effective_tld_names.gperf"
+ {"in.na", 0},
+#line 476 "effective_tld_names.gperf"
+ {"ch", 0},
+#line 1054 "effective_tld_names.gperf"
+ {"fm", 0},
+#line 1101 "effective_tld_names.gperf"
+ {"fusa.no", 0},
+#line 634 "effective_tld_names.gperf"
+ {"com.hn", 0},
+#line 635 "effective_tld_names.gperf"
+ {"com.hr", 0},
+#line 858 "effective_tld_names.gperf"
+ {"edu.hn", 0},
+#line 426 "effective_tld_names.gperf"
+ {"by", 0},
+#line 656 "effective_tld_names.gperf"
+ {"com.mo", 0},
+#line 883 "effective_tld_names.gperf"
+ {"edu.mo", 0},
+#line 1142 "effective_tld_names.gperf"
+ {"gh", 0},
+#line 1186 "effective_tld_names.gperf"
+ {"gon.pk", 0},
+#line 435 "effective_tld_names.gperf"
+ {"c.se", 0},
+#line 815 "effective_tld_names.gperf"
+ {"e.se", 0},
+#line 1459 "effective_tld_names.gperf"
+ {"i.se", 0},
+#line 27 "effective_tld_names.gperf"
+ {"a.se", 0},
+#line 3462 "effective_tld_names.gperf"
+ {"x.se", 0},
+#line 1178 "effective_tld_names.gperf"
+ {"gob.hn", 0},
+#line 3690 "effective_tld_names.gperf"
+ {"z.se", 0},
+#line 3671 "effective_tld_names.gperf"
+ {"y.se", 0},
+#line 477 "effective_tld_names.gperf"
+ {"ch.it", 0},
+#line 2271 "effective_tld_names.gperf"
+ {"nnov.ru", 0},
+#line 1365 "effective_tld_names.gperf"
+ {"h.se", 0},
+#line 1105 "effective_tld_names.gperf"
+ {"g.se", 0},
+#line 1264 "effective_tld_names.gperf"
+ {"gov.mo", 0},
+#line 2000 "effective_tld_names.gperf"
+ {"mo", 0},
+#line 2067 "effective_tld_names.gperf"
+ {"mz", 2},
+#line 1893 "effective_tld_names.gperf"
+ {"md", 0},
+#line 2036 "effective_tld_names.gperf"
+ {"ms", 0},
+#line 1615 "effective_tld_names.gperf"
+ {"jur.pro", 0},
+#line 2041 "effective_tld_names.gperf"
+ {"mt", 2},
+#line 329 "effective_tld_names.gperf"
+ {"bi", 0},
+#line 1995 "effective_tld_names.gperf"
+ {"ml", 0},
+#line 2006 "effective_tld_names.gperf"
+ {"mobi", 0},
+#line 3456 "effective_tld_names.gperf"
+ {"ws", 0},
+#line 417 "effective_tld_names.gperf"
+ {"bu.no", 0},
+#line 651 "effective_tld_names.gperf"
+ {"com.lv", 0},
+#line 1322 "effective_tld_names.gperf"
+ {"grue.no", 0},
+#line 876 "effective_tld_names.gperf"
+ {"edu.lv", 0},
+#line 951 "effective_tld_names.gperf"
+ {"engine.aero", 0},
+#line 2069 "effective_tld_names.gperf"
+ {"n.se", 0},
+#line 2146 "effective_tld_names.gperf"
+ {"net.ai", 0},
+#line 2003 "effective_tld_names.gperf"
+ {"mo.it", 0},
+#line 3319 "effective_tld_names.gperf"
+ {"uz", 0},
+#line 2037 "effective_tld_names.gperf"
+ {"ms.it", 0},
+#line 3296 "effective_tld_names.gperf"
+ {"us", 0},
+#line 2033 "effective_tld_names.gperf"
+ {"mr", 0},
+#line 2042 "effective_tld_names.gperf"
+ {"mt.it", 0},
+#line 330 "effective_tld_names.gperf"
+ {"bi.it", 0},
+#line 2288 "effective_tld_names.gperf"
+ {"nome.pt", 0},
+#line 1896 "effective_tld_names.gperf"
+ {"me", 0},
+#line 438 "effective_tld_names.gperf"
+ {"ca.na", 0},
+#line 440 "effective_tld_names.gperf"
+ {"caa.aero", 0},
+#line 1256 "effective_tld_names.gperf"
+ {"gov.lv", 0},
+#line 1055 "effective_tld_names.gperf"
+ {"fm.br", 0},
+#line 2031 "effective_tld_names.gperf"
+ {"mp", 0},
+#line 346 "effective_tld_names.gperf"
+ {"biz", 0},
+#line 2253 "effective_tld_names.gperf"
+ {"nic.ar", 1},
+#line 145 "effective_tld_names.gperf"
+ {"amursk.ru", 0},
+#line 3269 "effective_tld_names.gperf"
+ {"ud.it", 0},
+#line 101 "effective_tld_names.gperf"
+ {"aid.pl", 0},
+#line 2172 "effective_tld_names.gperf"
+ {"net.hn", 0},
+#line 3436 "effective_tld_names.gperf"
+ {"web.tj", 0},
+#line 1897 "effective_tld_names.gperf"
+ {"me.it", 0},
+#line 357 "effective_tld_names.gperf"
+ {"biz.tj", 0},
+#line 2197 "effective_tld_names.gperf"
+ {"net.mo", 0},
+#line 1133 "effective_tld_names.gperf"
+ {"gen.in", 0},
+#line 587 "effective_tld_names.gperf"
+ {"co.vi", 0},
+#line 954 "effective_tld_names.gperf"
+ {"enna.it", 0},
+#line 178 "effective_tld_names.gperf"
+ {"arendal.no", 0},
+#line 1127 "effective_tld_names.gperf"
+ {"gdansk.pl", 0},
+#line 737 "effective_tld_names.gperf"
+ {"cremona.it", 0},
+#line 1997 "effective_tld_names.gperf"
+ {"mn", 0},
+#line 952 "effective_tld_names.gperf"
+ {"engineer.aero", 0},
+#line 970 "effective_tld_names.gperf"
+ {"estate.museum", 0},
+#line 2044 "effective_tld_names.gperf"
+ {"mu", 0},
+#line 1193 "effective_tld_names.gperf"
+ {"gouv.ci", 0},
+#line 482 "effective_tld_names.gperf"
+ {"chel.ru", 0},
+#line 1493 "effective_tld_names.gperf"
+ {"ind.in", 0},
+#line 355 "effective_tld_names.gperf"
+ {"biz.pl", 0},
+#line 356 "effective_tld_names.gperf"
+ {"biz.pr", 0},
+#line 404 "effective_tld_names.gperf"
+ {"broker.aero", 0},
+#line 285 "effective_tld_names.gperf"
+ {"bamble.no", 0},
+#line 2191 "effective_tld_names.gperf"
+ {"net.lv", 0},
+#line 2098 "effective_tld_names.gperf"
+ {"nara.jp", 2},
+#line 2306 "effective_tld_names.gperf"
+ {"novara.it", 0},
+#line 1998 "effective_tld_names.gperf"
+ {"mn.it", 0},
+#line 1196 "effective_tld_names.gperf"
+ {"gouv.km", 0},
+#line 1996 "effective_tld_names.gperf"
+ {"mm", 2},
+#line 658 "effective_tld_names.gperf"
+ {"com.mv", 0},
+#line 749 "effective_tld_names.gperf"
+ {"cv", 0},
+#line 884 "effective_tld_names.gperf"
+ {"edu.mv", 0},
+#line 2038 "effective_tld_names.gperf"
+ {"ms.kr", 0},
+#line 697 "effective_tld_names.gperf"
+ {"com.vn", 0},
+#line 918 "effective_tld_names.gperf"
+ {"edu.vn", 0},
+#line 205 "effective_tld_names.gperf"
+ {"asia", 0},
+#line 695 "effective_tld_names.gperf"
+ {"com.vc", 0},
+#line 579 "effective_tld_names.gperf"
+ {"co.sz", 0},
+#line 1511 "effective_tld_names.gperf"
+ {"info.ki", 0},
+#line 917 "effective_tld_names.gperf"
+ {"edu.vc", 0},
+#line 1466 "effective_tld_names.gperf"
+ {"id.lv", 0},
+#line 1597 "effective_tld_names.gperf"
+ {"jobs", 0},
+#line 353 "effective_tld_names.gperf"
+ {"biz.nr", 0},
+#line 578 "effective_tld_names.gperf"
+ {"co.st", 0},
#line 1087 "effective_tld_names.gperf"
- {"g.bg", 0},
-#line 1434 "effective_tld_names.gperf"
- {"i.bg", 0},
-#line 802 "effective_tld_names.gperf"
+ {"froland.no", 0},
+#line 1605 "effective_tld_names.gperf"
+ {"journal.aero", 0},
+#line 2133 "effective_tld_names.gperf"
+ {"nel.uk", 1},
+#line 1267 "effective_tld_names.gperf"
+ {"gov.mv", 0},
+#line 252 "effective_tld_names.gperf"
+ {"av.it", 0},
+#line 407 "effective_tld_names.gperf"
+ {"brumunddal.no", 0},
+#line 1014 "effective_tld_names.gperf"
+ {"fi", 0},
+#line 1354 "effective_tld_names.gperf"
+ {"gv.ao", 0},
+#line 1299 "effective_tld_names.gperf"
+ {"gov.vn", 0},
+#line 637 "effective_tld_names.gperf"
+ {"com.io", 0},
+#line 1355 "effective_tld_names.gperf"
+ {"gv.at", 0},
+#line 1298 "effective_tld_names.gperf"
+ {"gov.vc", 0},
+#line 1847 "effective_tld_names.gperf"
+ {"ma", 0},
+#line 2081 "effective_tld_names.gperf"
+ {"name", 0},
+#line 328 "effective_tld_names.gperf"
+ {"bh", 0},
+#line 668 "effective_tld_names.gperf"
+ {"com.pf", 0},
+#line 2084 "effective_tld_names.gperf"
+ {"name.jo", 0},
+#line 892 "effective_tld_names.gperf"
+ {"edu.pf", 0},
+#line 1016 "effective_tld_names.gperf"
+ {"fi.it", 0},
+#line 633 "effective_tld_names.gperf"
+ {"com.hk", 0},
+#line 98 "effective_tld_names.gperf"
+ {"ah.no", 0},
+#line 198 "effective_tld_names.gperf"
+ {"arts.ro", 0},
+#line 857 "effective_tld_names.gperf"
+ {"edu.hk", 0},
+#line 2008 "effective_tld_names.gperf"
+ {"mobi.na", 0},
+#line 195 "effective_tld_names.gperf"
+ {"arts.co", 0},
+#line 1056 "effective_tld_names.gperf"
+ {"fm.no", 0},
+#line 3267 "effective_tld_names.gperf"
+ {"ua", 0},
+#line 975 "effective_tld_names.gperf"
+ {"etne.no", 0},
+#line 447 "effective_tld_names.gperf"
+ {"campobasso.it", 0},
+#line 2255 "effective_tld_names.gperf"
+ {"nic.in", 0},
+#line 264 "effective_tld_names.gperf"
+ {"b.se", 0},
+#line 1470 "effective_tld_names.gperf"
+ {"idv.hk", 0},
+#line 29 "effective_tld_names.gperf"
+ {"aarborte.no", 0},
+#line 1002 "effective_tld_names.gperf"
+ {"fed.us", 0},
+#line 1234 "effective_tld_names.gperf"
+ {"gov.hk", 0},
+#line 2032 "effective_tld_names.gperf"
+ {"mq", 0},
+#line 243 "effective_tld_names.gperf"
+ {"aurland.no", 0},
+#line 352 "effective_tld_names.gperf"
+ {"biz.mw", 0},
+#line 221 "effective_tld_names.gperf"
+ {"asso.fr", 0},
+#line 2199 "effective_tld_names.gperf"
+ {"net.mv", 0},
+#line 1524 "effective_tld_names.gperf"
+ {"info.vn", 0},
+#line 1070 "effective_tld_names.gperf"
+ {"fosnes.no", 0},
+#line 663 "effective_tld_names.gperf"
+ {"com.nf", 0},
+#line 2089 "effective_tld_names.gperf"
+ {"name.pr", 0},
+#line 2234 "effective_tld_names.gperf"
+ {"net.vn", 0},
+#line 1536 "effective_tld_names.gperf"
+ {"int.mv", 0},
+#line 1431 "effective_tld_names.gperf"
+ {"hol.no", 0},
+#line 1185 "effective_tld_names.gperf"
+ {"gol.no", 0},
+#line 2232 "effective_tld_names.gperf"
+ {"net.vc", 0},
+#line 211 "effective_tld_names.gperf"
+ {"asn.lv", 0},
+#line 953 "effective_tld_names.gperf"
+ {"england.museum", 0},
+#line 1543 "effective_tld_names.gperf"
+ {"int.vn", 0},
+#line 210 "effective_tld_names.gperf"
+ {"asmatart.museum", 0},
+#line 428 "effective_tld_names.gperf"
+ {"bygland.no", 0},
+#line 2034 "effective_tld_names.gperf"
+ {"mr.no", 0},
+#line 167 "effective_tld_names.gperf"
+ {"aquila.it", 0},
+#line 3437 "effective_tld_names.gperf"
+ {"wegrow.pl", 0},
+#line 1432 "effective_tld_names.gperf"
+ {"hole.no", 0},
+#line 1603 "effective_tld_names.gperf"
+ {"jorpeland.no", 0},
+#line 1093 "effective_tld_names.gperf"
+ {"fuel.aero", 0},
+#line 1909 "effective_tld_names.gperf"
+ {"med.sd", 0},
+#line 1433 "effective_tld_names.gperf"
+ {"holmestrand.no", 0},
+#line 2065 "effective_tld_names.gperf"
+ {"my", 0},
+#line 2171 "effective_tld_names.gperf"
+ {"net.hk", 0},
+#line 216 "effective_tld_names.gperf"
+ {"assisi.museum", 0},
+#line 149 "effective_tld_names.gperf"
+ {"ancona.it", 0},
+#line 3433 "effective_tld_names.gperf"
+ {"web.lk", 0},
+#line 990 "effective_tld_names.gperf"
+ {"fam.pk", 0},
+#line 1190 "effective_tld_names.gperf"
+ {"gorlice.pl", 0},
+#line 286 "effective_tld_names.gperf"
+ {"bar.pro", 0},
+#line 3417 "effective_tld_names.gperf"
+ {"wa.au", 0},
+#line 2026 "effective_tld_names.gperf"
+ {"moskenes.no", 0},
+#line 374 "effective_tld_names.gperf"
+ {"bo.nordland.no", 0},
+#line 1901 "effective_tld_names.gperf"
+ {"med.ec", 0},
+#line 1906 "effective_tld_names.gperf"
+ {"med.pl", 0},
+#line 3317 "effective_tld_names.gperf"
+ {"uy", 2},
+#line 1907 "effective_tld_names.gperf"
+ {"med.pro", 0},
+#line 599 "effective_tld_names.gperf"
+ {"com.af", 0},
+#line 829 "effective_tld_names.gperf"
+ {"edu.af", 0},
+#line 1588 "effective_tld_names.gperf"
+ {"jevnaker.no", 0},
+#line 3435 "effective_tld_names.gperf"
+ {"web.pk", 0},
+#line 354 "effective_tld_names.gperf"
+ {"biz.pk", 0},
+#line 2203 "effective_tld_names.gperf"
+ {"net.nf", 0},
+#line 1017 "effective_tld_names.gperf"
+ {"fie.ee", 0},
+#line 1203 "effective_tld_names.gperf"
+ {"gov.af", 0},
+#line 83 "effective_tld_names.gperf"
+ {"aerodrome.aero", 0},
+#line 3286 "effective_tld_names.gperf"
+ {"unbi.ba", 0},
+#line 2251 "effective_tld_names.gperf"
+ {"nhs.uk", 1},
+#line 1444 "effective_tld_names.gperf"
+ {"hoylandet.no", 0},
+#line 1930 "effective_tld_names.gperf"
+ {"mi.it", 0},
+#line 3292 "effective_tld_names.gperf"
+ {"unsa.ba", 0},
+#line 989 "effective_tld_names.gperf"
+ {"f.se", 0},
+#line 2088 "effective_tld_names.gperf"
+ {"name.na", 0},
+#line 1007 "effective_tld_names.gperf"
+ {"fet.no", 0},
+#line 1098 "effective_tld_names.gperf"
+ {"fuoisku.no", 0},
+#line 124 "effective_tld_names.gperf"
+ {"alesund.no", 0},
+#line 1454 "effective_tld_names.gperf"
+ {"hvaler.no", 0},
+#line 950 "effective_tld_names.gperf"
+ {"engerdal.no", 0},
+#line 1455 "effective_tld_names.gperf"
+ {"hyllestad.no", 0},
+#line 2104 "effective_tld_names.gperf"
+ {"national.museum", 0},
+#line 1024 "effective_tld_names.gperf"
+ {"fin.tn", 0},
+#line 172 "effective_tld_names.gperf"
+ {"arboretum.museum", 0},
+#line 1902 "effective_tld_names.gperf"
+ {"med.ee", 0},
+#line 2082 "effective_tld_names.gperf"
+ {"name.az", 0},
+#line 303 "effective_tld_names.gperf"
+ {"bd.se", 0},
+#line 2144 "effective_tld_names.gperf"
+ {"net.af", 0},
+#line 1042 "effective_tld_names.gperf"
+ {"fla.no", 0},
+#line 3314 "effective_tld_names.gperf"
+ {"utsira.no", 0},
+#line 3457 "effective_tld_names.gperf"
+ {"ws.na", 0},
+#line 403 "effective_tld_names.gperf"
+ {"broadcast.museum", 0},
+#line 736 "effective_tld_names.gperf"
+ {"creation.museum", 0},
+#line 696 "effective_tld_names.gperf"
+ {"com.vi", 0},
+#line 239 "effective_tld_names.gperf"
+ {"audnedaln.no", 0},
+#line 1023 "effective_tld_names.gperf"
+ {"fin.ec", 0},
+#line 734 "effective_tld_names.gperf"
+ {"crafts.museum", 0},
+#line 3298 "effective_tld_names.gperf"
+ {"us.na", 0},
+#line 109 "effective_tld_names.gperf"
+ {"airport.aero", 0},
+#line 2025 "effective_tld_names.gperf"
+ {"mosjoen.no", 0},
+#line 242 "effective_tld_names.gperf"
+ {"aure.no", 0},
+#line 1526 "effective_tld_names.gperf"
+ {"ingatlan.hu", 0},
+#line 1568 "effective_tld_names.gperf"
+ {"iveland.no", 0},
+#line 314 "effective_tld_names.gperf"
+ {"belluno.it", 0},
+#line 1375 "effective_tld_names.gperf"
+ {"hamaroy.no", 0},
+#line 1013 "effective_tld_names.gperf"
+ {"fhv.se", 0},
+#line 1732 "effective_tld_names.gperf"
+ {"kz", 0},
+#line 1496 "effective_tld_names.gperf"
+ {"indian.museum", 0},
+#line 1061 "effective_tld_names.gperf"
+ {"folldal.no", 0},
+#line 1940 "effective_tld_names.gperf"
+ {"mielec.pl", 0},
+#line 2095 "effective_tld_names.gperf"
+ {"nannestad.no", 0},
+#line 2093 "effective_tld_names.gperf"
+ {"namsos.no", 0},
+#line 686 "effective_tld_names.gperf"
+ {"com.st", 0},
+#line 910 "effective_tld_names.gperf"
+ {"edu.st", 0},
+#line 691 "effective_tld_names.gperf"
+ {"com.tt", 0},
+#line 1194 "effective_tld_names.gperf"
+ {"gouv.fr", 0},
+#line 914 "effective_tld_names.gperf"
+ {"edu.tt", 0},
+#line 930 "effective_tld_names.gperf"
+ {"eid.no", 0},
+#line 721 "effective_tld_names.gperf"
+ {"coop.tt", 0},
+#line 408 "effective_tld_names.gperf"
+ {"brunel.museum", 0},
+#line 1580 "effective_tld_names.gperf"
+ {"jefferson.museum", 0},
+#line 1908 "effective_tld_names.gperf"
+ {"med.sa", 0},
+#line 1559 "effective_tld_names.gperf"
+ {"isernia.it", 0},
+#line 1695 "effective_tld_names.gperf"
+ {"kr", 0},
+#line 1395 "effective_tld_names.gperf"
+ {"hellas.museum", 0},
+#line 1607 "effective_tld_names.gperf"
+ {"journalist.aero", 0},
+#line 1929 "effective_tld_names.gperf"
+ {"mh", 0},
+#line 1289 "effective_tld_names.gperf"
+ {"gov.st", 0},
+#line 1647 "effective_tld_names.gperf"
+ {"ke", 2},
+#line 334 "effective_tld_names.gperf"
+ {"bielawa.pl", 0},
+#line 1295 "effective_tld_names.gperf"
+ {"gov.tt", 0},
+#line 232 "effective_tld_names.gperf"
+ {"astronomy.museum", 0},
+#line 1255 "effective_tld_names.gperf"
+ {"gov.lt", 0},
+#line 1012 "effective_tld_names.gperf"
+ {"fhsk.se", 0},
+#line 1621 "effective_tld_names.gperf"
+ {"k12.ec", 0},
+#line 674 "effective_tld_names.gperf"
+ {"com.pt", 0},
+#line 899 "effective_tld_names.gperf"
+ {"edu.pt", 0},
+#line 324 "effective_tld_names.gperf"
+ {"beskidy.pl", 0},
+#line 1846 "effective_tld_names.gperf"
+ {"m.se", 0},
+#line 1697 "effective_tld_names.gperf"
+ {"kr.it", 0},
+#line 2233 "effective_tld_names.gperf"
+ {"net.vi", 0},
+#line 1400 "effective_tld_names.gperf"
+ {"hemsedal.no", 0},
+#line 714 "effective_tld_names.gperf"
+ {"convent.museum", 0},
+#line 3416 "effective_tld_names.gperf"
+ {"w.se", 0},
+#line 2011 "effective_tld_names.gperf"
+ {"modalen.no", 0},
+#line 976 "effective_tld_names.gperf"
+ {"etnedal.no", 0},
+#line 1919 "effective_tld_names.gperf"
+ {"meland.no", 0},
+#line 1905 "effective_tld_names.gperf"
+ {"med.pa", 0},
+#line 3442 "effective_tld_names.gperf"
+ {"wielun.pl", 0},
+#line 310 "effective_tld_names.gperf"
+ {"beiarn.no", 0},
+#line 1279 "effective_tld_names.gperf"
+ {"gov.pt", 0},
+#line 981 "effective_tld_names.gperf"
+ {"evenes.no", 0},
+#line 411 "effective_tld_names.gperf"
+ {"bruxelles.museum", 0},
+#line 3266 "effective_tld_names.gperf"
+ {"u.se", 0},
+#line 461 "effective_tld_names.gperf"
+ {"catania.it", 0},
+#line 289 "effective_tld_names.gperf"
+ {"bari.it", 0},
+#line 1301 "effective_tld_names.gperf"
+ {"government.aero", 0},
+#line 480 "effective_tld_names.gperf"
+ {"charter.aero", 0},
+#line 1675 "effective_tld_names.gperf"
+ {"kn", 0},
+#line 2223 "effective_tld_names.gperf"
+ {"net.st", 0},
+#line 2229 "effective_tld_names.gperf"
+ {"net.tt", 0},
+#line 530 "effective_tld_names.gperf"
+ {"civilwar.museum", 0},
+#line 1684 "effective_tld_names.gperf"
+ {"komi.ru", 0},
+#line 1035 "effective_tld_names.gperf"
+ {"fitjar.no", 0},
+#line 1542 "effective_tld_names.gperf"
+ {"int.tt", 0},
+#line 1672 "effective_tld_names.gperf"
+ {"km", 0},
+#line 1114 "effective_tld_names.gperf"
+ {"gamvik.no", 0},
+#line 450 "effective_tld_names.gperf"
+ {"canada.museum", 0},
+#line 1158 "effective_tld_names.gperf"
+ {"gliwice.pl", 0},
+#line 1034 "effective_tld_names.gperf"
+ {"firm.ro", 0},
+#line 2005 "effective_tld_names.gperf"
+ {"moareke.no", 0},
+#line 1030 "effective_tld_names.gperf"
+ {"firm.co", 0},
+#line 1386 "effective_tld_names.gperf"
+ {"hasvik.no", 0},
+#line 1523 "effective_tld_names.gperf"
+ {"info.tt", 0},
+#line 1942 "effective_tld_names.gperf"
+ {"mil", 0},
+#line 2214 "effective_tld_names.gperf"
+ {"net.pt", 0},
+#line 1331 "effective_tld_names.gperf"
+ {"gs.jan-mayen.no", 0},
+#line 335 "effective_tld_names.gperf"
+ {"biella.it", 0},
+#line 475 "effective_tld_names.gperf"
+ {"cg", 0},
+#line 926 "effective_tld_names.gperf"
+ {"eg", 2},
+#line 1538 "effective_tld_names.gperf"
+ {"int.pt", 0},
+#line 87 "effective_tld_names.gperf"
+ {"ag", 0},
+#line 1976 "effective_tld_names.gperf"
+ {"mil.tj", 0},
+#line 629 "effective_tld_names.gperf"
+ {"com.gn", 0},
+#line 631 "effective_tld_names.gperf"
+ {"com.gr", 0},
+#line 854 "effective_tld_names.gperf"
+ {"edu.gn", 0},
+#line 856 "effective_tld_names.gperf"
+ {"edu.gr", 0},
+#line 549 "effective_tld_names.gperf"
+ {"co.ag", 0},
+#line 1140 "effective_tld_names.gperf"
+ {"gg", 0},
+#line 935 "effective_tld_names.gperf"
+ {"eigersund.no", 0},
+#line 1317 "effective_tld_names.gperf"
+ {"grosseto.it", 0},
+#line 1575 "effective_tld_names.gperf"
+ {"jamison.museum", 0},
+#line 1497 "effective_tld_names.gperf"
+ {"indiana.museum", 0},
+#line 630 "effective_tld_names.gperf"
+ {"com.gp", 0},
+#line 1852 "effective_tld_names.gperf"
+ {"magadan.ru", 0},
+#line 1350 "effective_tld_names.gperf"
+ {"guernsey.museum", 0},
+#line 855 "effective_tld_names.gperf"
+ {"edu.gp", 0},
+#line 88 "effective_tld_names.gperf"
+ {"ag.it", 0},
+#line 1232 "effective_tld_names.gperf"
+ {"gov.gn", 0},
+#line 1233 "effective_tld_names.gperf"
+ {"gov.gr", 0},
+#line 1953 "effective_tld_names.gperf"
+ {"mil.ec", 0},
+#line 2116 "effective_tld_names.gperf"
+ {"naustdal.no", 0},
+#line 1971 "effective_tld_names.gperf"
+ {"mil.pl", 0},
+#line 708 "effective_tld_names.gperf"
+ {"consulado.st", 0},
+#line 1120 "effective_tld_names.gperf"
+ {"gausdal.no", 0},
+#line 2246 "effective_tld_names.gperf"
+ {"ng", 0},
+#line 1978 "effective_tld_names.gperf"
+ {"mil.tw", 0},
+#line 344 "effective_tld_names.gperf"
+ {"birkenes.no", 0},
+#line 1871 "effective_tld_names.gperf"
+ {"mari.ru", 0},
+#line 1189 "effective_tld_names.gperf"
+ {"gorizia.it", 0},
+#line 1546 "effective_tld_names.gperf"
+ {"intl.tn", 0},
+#line 2061 "effective_tld_names.gperf"
+ {"mv", 0},
+#line 979 "effective_tld_names.gperf"
+ {"eu.int", 0},
+#line 1600 "effective_tld_names.gperf"
+ {"jolster.no", 0},
+#line 940 "effective_tld_names.gperf"
+ {"elvendrell.museum", 0},
+#line 1369 "effective_tld_names.gperf"
+ {"hadsel.no", 0},
+#line 3295 "effective_tld_names.gperf"
+ {"urn.arpa", 0},
+#line 705 "effective_tld_names.gperf"
+ {"conf.lv", 0},
+#line 1973 "effective_tld_names.gperf"
+ {"mil.rw", 0},
+#line 1522 "effective_tld_names.gperf"
+ {"info.tn", 0},
+#line 1437 "effective_tld_names.gperf"
+ {"hornindal.no", 0},
+#line 2139 "effective_tld_names.gperf"
+ {"nesseby.no", 0},
+#line 2028 "effective_tld_names.gperf"
+ {"moss.no", 0},
+#line 2249 "effective_tld_names.gperf"
+ {"ngo.pl", 0},
+#line 351 "effective_tld_names.gperf"
+ {"biz.mv", 0},
+#line 3306 "effective_tld_names.gperf"
+ {"usgarden.museum", 0},
+#line 258 "effective_tld_names.gperf"
+ {"aw", 0},
+#line 2167 "effective_tld_names.gperf"
+ {"net.gn", 0},
+#line 2169 "effective_tld_names.gperf"
+ {"net.gr", 0},
+#line 3711 "effective_tld_names.gperf"
+ {"zw", 2},
+#line 626 "effective_tld_names.gperf"
+ {"com.ge", 0},
+#line 592 "effective_tld_names.gperf"
+ {"collection.museum", 0},
+#line 1918 "effective_tld_names.gperf"
+ {"meeres.museum", 0},
+#line 851 "effective_tld_names.gperf"
+ {"edu.ge", 0},
+#line 359 "effective_tld_names.gperf"
+ {"biz.vn", 0},
+#line 683 "effective_tld_names.gperf"
+ {"com.sg", 0},
+#line 583 "effective_tld_names.gperf"
+ {"co.tz", 0},
+#line 907 "effective_tld_names.gperf"
+ {"edu.sg", 0},
+#line 701 "effective_tld_names.gperf"
+ {"community.museum", 0},
+#line 575 "effective_tld_names.gperf"
+ {"co.pw", 0},
+#line 166 "effective_tld_names.gperf"
+ {"aquarium.museum", 0},
+#line 1356 "effective_tld_names.gperf"
+ {"gw", 0},
+#line 457 "effective_tld_names.gperf"
+ {"casino.hu", 0},
+#line 826 "effective_tld_names.gperf"
+ {"ed.pw", 0},
+#line 2168 "effective_tld_names.gperf"
+ {"net.gp", 0},
+#line 582 "effective_tld_names.gperf"
+ {"co.tt", 0},
+#line 698 "effective_tld_names.gperf"
+ {"com.ws", 0},
+#line 1576 "effective_tld_names.gperf"
+ {"jan-mayen.no", 0},
+#line 919 "effective_tld_names.gperf"
+ {"edu.ws", 0},
+#line 2134 "effective_tld_names.gperf"
+ {"nes.akershus.no", 0},
+#line 1729 "effective_tld_names.gperf"
+ {"ky", 0},
+#line 3301 "effective_tld_names.gperf"
+ {"usarts.museum", 0},
+#line 1173 "effective_tld_names.gperf"
+ {"go.tz", 0},
+#line 2718 "effective_tld_names.gperf"
+ {"ps", 0},
+#line 1686 "effective_tld_names.gperf"
+ {"kommune.no", 0},
+#line 1170 "effective_tld_names.gperf"
+ {"go.pw", 0},
+#line 1228 "effective_tld_names.gperf"
+ {"gov.ge", 0},
+#line 2722 "effective_tld_names.gperf"
+ {"pt", 0},
+#line 331 "effective_tld_names.gperf"
+ {"bialowieza.pl", 0},
+#line 739 "effective_tld_names.gperf"
+ {"crimea.ua", 0},
+#line 2594 "effective_tld_names.gperf"
+ {"pl", 0},
+#line 687 "effective_tld_names.gperf"
+ {"com.sy", 0},
+#line 1969 "effective_tld_names.gperf"
+ {"mil.pe", 0},
+#line 911 "effective_tld_names.gperf"
+ {"edu.sy", 0},
+#line 1287 "effective_tld_names.gperf"
+ {"gov.sg", 0},
+#line 652 "effective_tld_names.gperf"
+ {"com.ly", 0},
+#line 728 "effective_tld_names.gperf"
+ {"countryestate.museum", 0},
+#line 877 "effective_tld_names.gperf"
+ {"edu.ly", 0},
+#line 1300 "effective_tld_names.gperf"
+ {"gov.ws", 0},
+#line 1110 "effective_tld_names.gperf"
+ {"gallery.museum", 0},
+#line 554 "effective_tld_names.gperf"
+ {"co.bw", 0},
+#line 1008 "effective_tld_names.gperf"
+ {"fetsund.no", 0},
+#line 2606 "effective_tld_names.gperf"
+ {"po.it", 0},
+#line 2736 "effective_tld_names.gperf"
+ {"pz.it", 0},
+#line 2555 "effective_tld_names.gperf"
+ {"pd.it", 0},
+#line 2631 "effective_tld_names.gperf"
+ {"pr", 0},
+#line 572 "effective_tld_names.gperf"
+ {"co.mw", 0},
+#line 2701 "effective_tld_names.gperf"
+ {"pro", 0},
+#line 2723 "effective_tld_names.gperf"
+ {"pt.it", 0},
+#line 1661 "effective_tld_names.gperf"
+ {"ki", 0},
+#line 1290 "effective_tld_names.gperf"
+ {"gov.sy", 0},
+#line 2556 "effective_tld_names.gperf"
+ {"pe", 0},
+#line 108 "effective_tld_names.gperf"
+ {"airline.aero", 0},
+#line 1875 "effective_tld_names.gperf"
+ {"marker.no", 0},
+#line 1945 "effective_tld_names.gperf"
+ {"mil.al", 0},
+#line 1701 "effective_tld_names.gperf"
+ {"krakow.pl", 0},
+#line 1257 "effective_tld_names.gperf"
+ {"gov.ly", 0},
+#line 1943 "effective_tld_names.gperf"
+ {"mil.ac", 0},
+#line 863 "effective_tld_names.gperf"
+ {"edu.it", 0},
+#line 405 "effective_tld_names.gperf"
+ {"bronnoy.no", 0},
+#line 1611 "effective_tld_names.gperf"
+ {"judaica.museum", 0},
+#line 2632 "effective_tld_names.gperf"
+ {"pr.it", 0},
+#line 2627 "effective_tld_names.gperf"
+ {"pp.az", 0},
+#line 1044 "effective_tld_names.gperf"
+ {"flanders.museum", 0},
+#line 1164 "effective_tld_names.gperf"
+ {"gniezno.pl", 0},
+#line 2558 "effective_tld_names.gperf"
+ {"pe.it", 0},
+#line 1870 "effective_tld_names.gperf"
+ {"mari-el.ru", 0},
+#line 2128 "effective_tld_names.gperf"
+ {"ne.tz", 0},
+#line 2092 "effective_tld_names.gperf"
+ {"name.vn", 0},
+#line 1241 "effective_tld_names.gperf"
+ {"gov.it", 0},
+#line 2127 "effective_tld_names.gperf"
+ {"ne.pw", 0},
+#line 2602 "effective_tld_names.gperf"
+ {"plo.ps", 0},
+#line 137 "effective_tld_names.gperf"
+ {"american.museum", 0},
+#line 3434 "effective_tld_names.gperf"
+ {"web.nf", 0},
+#line 664 "effective_tld_names.gperf"
+ {"com.ng", 0},
+#line 2165 "effective_tld_names.gperf"
+ {"net.ge", 0},
+#line 621 "effective_tld_names.gperf"
+ {"com.dz", 0},
+#line 888 "effective_tld_names.gperf"
+ {"edu.ng", 0},
+#line 847 "effective_tld_names.gperf"
+ {"edu.dz", 0},
+#line 2704 "effective_tld_names.gperf"
+ {"pro.ec", 0},
+#line 1011 "effective_tld_names.gperf"
+ {"fhs.no", 0},
+#line 2221 "effective_tld_names.gperf"
+ {"net.sg", 0},
+#line 222 "effective_tld_names.gperf"
+ {"asso.gp", 0},
+#line 2708 "effective_tld_names.gperf"
+ {"pro.pr", 0},
+#line 577 "effective_tld_names.gperf"
+ {"co.rw", 0},
+#line 924 "effective_tld_names.gperf"
+ {"edunet.tn", 0},
+#line 2235 "effective_tld_names.gperf"
+ {"net.ws", 0},
+#line 76 "effective_tld_names.gperf"
+ {"ae.org", 0},
+#line 2603 "effective_tld_names.gperf"
+ {"pn", 0},
+#line 1451 "effective_tld_names.gperf"
+ {"humanities.museum", 0},
+#line 943 "effective_tld_names.gperf"
+ {"embroidery.museum", 0},
+#line 326 "effective_tld_names.gperf"
+ {"bg", 0},
+#line 1271 "effective_tld_names.gperf"
+ {"gov.ng", 0},
+#line 1225 "effective_tld_names.gperf"
+ {"gov.dz", 0},
+#line 1128 "effective_tld_names.gperf"
+ {"gdynia.pl", 0},
+#line 2261 "effective_tld_names.gperf"
+ {"nissedal.no", 0},
+#line 2224 "effective_tld_names.gperf"
+ {"net.sy", 0},
+#line 653 "effective_tld_names.gperf"
+ {"com.mg", 0},
+#line 958 "effective_tld_names.gperf"
+ {"environment.museum", 0},
+#line 879 "effective_tld_names.gperf"
+ {"edu.mg", 0},
+#line 142 "effective_tld_names.gperf"
+ {"amot.no", 0},
+#line 2192 "effective_tld_names.gperf"
+ {"net.ly", 0},
+#line 1606 "effective_tld_names.gperf"
+ {"journalism.museum", 0},
+#line 1853 "effective_tld_names.gperf"
+ {"magazine.aero", 0},
+#line 2604 "effective_tld_names.gperf"
+ {"pn.it", 0},
+#line 185 "effective_tld_names.gperf"
+ {"art.dz", 0},
+#line 327 "effective_tld_names.gperf"
+ {"bg.it", 0},
+#line 2725 "effective_tld_names.gperf"
+ {"pu.it", 0},
+#line 1623 "effective_tld_names.gperf"
+ {"kafjord.no", 0},
+#line 1090 "effective_tld_names.gperf"
+ {"frosta.no", 0},
+#line 661 "effective_tld_names.gperf"
+ {"com.my", 0},
+#line 2727 "effective_tld_names.gperf"
+ {"publ.pt", 0},
+#line 1855 "effective_tld_names.gperf"
+ {"mail.pl", 0},
+#line 887 "effective_tld_names.gperf"
+ {"edu.my", 0},
+#line 1260 "effective_tld_names.gperf"
+ {"gov.mg", 0},
+#line 1944 "effective_tld_names.gperf"
+ {"mil.ae", 0},
+#line 442 "effective_tld_names.gperf"
+ {"cagliari.it", 0},
+#line 455 "effective_tld_names.gperf"
+ {"casadelamoneda.museum", 0},
+#line 231 "effective_tld_names.gperf"
+ {"astrakhan.ru", 0},
+#line 1309 "effective_tld_names.gperf"
+ {"grandrapids.museum", 0},
+#line 1941 "effective_tld_names.gperf"
+ {"mielno.pl", 0},
+#line 1872 "effective_tld_names.gperf"
+ {"marine.ru", 0},
+#line 299 "effective_tld_names.gperf"
+ {"bauern.museum", 0},
+#line 2282 "effective_tld_names.gperf"
+ {"nom.mg", 0},
+#line 253 "effective_tld_names.gperf"
+ {"avellino.it", 0},
+#line 3450 "effective_tld_names.gperf"
+ {"wolomin.pl", 0},
+#line 600 "effective_tld_names.gperf"
+ {"com.ag", 0},
+#line 2247 "effective_tld_names.gperf"
+ {"ngo.lk", 0},
+#line 1269 "effective_tld_names.gperf"
+ {"gov.my", 0},
+#line 3693 "effective_tld_names.gperf"
+ {"za.net", 0},
+#line 628 "effective_tld_names.gperf"
+ {"com.gi", 0},
+#line 853 "effective_tld_names.gperf"
+ {"edu.gi", 0},
+#line 2204 "effective_tld_names.gperf"
+ {"net.ng", 0},
+#line 694 "effective_tld_names.gperf"
+ {"com.uz", 0},
+#line 2163 "effective_tld_names.gperf"
+ {"net.dz", 0},
+#line 2559 "effective_tld_names.gperf"
+ {"pe.kr", 0},
+#line 2529 "effective_tld_names.gperf"
+ {"pa", 0},
+#line 131 "effective_tld_names.gperf"
+ {"alvdal.no", 0},
+#line 80 "effective_tld_names.gperf"
+ {"aero.tt", 0},
+#line 322 "effective_tld_names.gperf"
+ {"berlin.museum", 0},
+#line 1957 "effective_tld_names.gperf"
+ {"mil.in", 0},
+#line 2316 "effective_tld_names.gperf"
+ {"nsw.edu.au", 0},
+#line 275 "effective_tld_names.gperf"
+ {"baikal.ru", 0},
+#line 1231 "effective_tld_names.gperf"
+ {"gov.gi", 0},
+#line 1099 "effective_tld_names.gperf"
+ {"fuossko.no", 0},
+#line 425 "effective_tld_names.gperf"
+ {"bw", 0},
+#line 2276 "effective_tld_names.gperf"
+ {"nom.ag", 0},
+#line 2531 "effective_tld_names.gperf"
+ {"pa.it", 0},
+#line 3685 "effective_tld_names.gperf"
+ {"yosemite.museum", 0},
+#line 1958 "effective_tld_names.gperf"
+ {"mil.iq", 0},
+#line 2029 "effective_tld_names.gperf"
+ {"mosvik.no", 0},
+#line 723 "effective_tld_names.gperf"
+ {"corporation.museum", 0},
+#line 1653 "effective_tld_names.gperf"
+ {"kh", 2},
+#line 1052 "effective_tld_names.gperf"
+ {"florida.museum", 0},
+#line 1383 "effective_tld_names.gperf"
+ {"hareid.no", 0},
+#line 636 "effective_tld_names.gperf"
+ {"com.ht", 0},
+#line 859 "effective_tld_names.gperf"
+ {"edu.ht", 0},
+#line 1032 "effective_tld_names.gperf"
+ {"firm.in", 0},
+#line 937 "effective_tld_names.gperf"
+ {"elblag.pl", 0},
+#line 2202 "effective_tld_names.gperf"
+ {"net.my", 0},
+#line 3694 "effective_tld_names.gperf"
+ {"za.org", 0},
+#line 1718 "effective_tld_names.gperf"
+ {"kuzbass.ru", 0},
+#line 1311 "effective_tld_names.gperf"
+ {"granvin.no", 0},
+#line 1620 "effective_tld_names.gperf"
+ {"k.se", 0},
+#line 2738 "effective_tld_names.gperf"
+ {"qa", 2},
+#line 996 "effective_tld_names.gperf"
+ {"farmers.museum", 0},
+#line 3626 "effective_tld_names.gperf"
+ {"xn--snes-poa.no", 0},
+#line 605 "effective_tld_names.gperf"
+ {"com.az", 0},
+#line 2101 "effective_tld_names.gperf"
+ {"narvik.no", 0},
+#line 138 "effective_tld_names.gperf"
+ {"americana.museum", 0},
+#line 832 "effective_tld_names.gperf"
+ {"edu.az", 0},
+#line 2145 "effective_tld_names.gperf"
+ {"net.ag", 0},
+#line 2628 "effective_tld_names.gperf"
+ {"pp.ru", 0},
+#line 1977 "effective_tld_names.gperf"
+ {"mil.to", 0},
+#line 406 "effective_tld_names.gperf"
+ {"bronnoysund.no", 0},
+#line 709 "effective_tld_names.gperf"
+ {"consultant.aero", 0},
+#line 980 "effective_tld_names.gperf"
+ {"evenassi.no", 0},
+#line 1135 "effective_tld_names.gperf"
+ {"genova.it", 0},
+#line 409 "effective_tld_names.gperf"
+ {"brussel.museum", 0},
+#line 186 "effective_tld_names.gperf"
+ {"art.ht", 0},
+#line 410 "effective_tld_names.gperf"
+ {"brussels.museum", 0},
+#line 1676 "effective_tld_names.gperf"
+ {"kobe.jp", 2},
+#line 1206 "effective_tld_names.gperf"
+ {"gov.az", 0},
+#line 1009 "effective_tld_names.gperf"
+ {"fg.it", 0},
+#line 2009 "effective_tld_names.gperf"
+ {"mobi.tt", 0},
+#line 214 "effective_tld_names.gperf"
+ {"assassination.museum", 0},
+#line 1498 "effective_tld_names.gperf"
+ {"indianapolis.museum", 0},
+#line 2734 "effective_tld_names.gperf"
+ {"py", 2},
+#line 960 "effective_tld_names.gperf"
+ {"epilepsy.museum", 0},
+#line 1198 "effective_tld_names.gperf"
+ {"gouv.rw", 0},
+#line 1384 "effective_tld_names.gperf"
+ {"harstad.no", 0},
+#line 140 "effective_tld_names.gperf"
+ {"americanart.museum", 0},
+#line 3606 "effective_tld_names.gperf"
+ {"xn--rst-0na.no", 0},
+#line 1006 "effective_tld_names.gperf"
+ {"ferrara.it", 0},
+#line 3677 "effective_tld_names.gperf"
+ {"yaroslavl.ru", 0},
+#line 1010 "effective_tld_names.gperf"
+ {"fh.se", 0},
+#line 2173 "effective_tld_names.gperf"
+ {"net.ht", 0},
+#line 2240 "effective_tld_names.gperf"
+ {"newport.museum", 0},
+#line 358 "effective_tld_names.gperf"
+ {"biz.tt", 0},
+#line 96 "effective_tld_names.gperf"
+ {"agro.pl", 0},
+#line 320 "effective_tld_names.gperf"
+ {"berkeley.museum", 0},
+#line 1027 "effective_tld_names.gperf"
+ {"finland.museum", 0},
+#line 453 "effective_tld_names.gperf"
+ {"carrier.museum", 0},
+#line 1495 "effective_tld_names.gperf"
+ {"inderoy.no", 0},
+#line 2726 "effective_tld_names.gperf"
+ {"pub.sa", 0},
+#line 1923 "effective_tld_names.gperf"
+ {"memorial.museum", 0},
+#line 1968 "effective_tld_names.gperf"
+ {"mil.no", 0},
+#line 3594 "effective_tld_names.gperf"
+ {"xn--rde-ula.no", 0},
+#line 2149 "effective_tld_names.gperf"
+ {"net.az", 0},
+#line 2584 "effective_tld_names.gperf"
+ {"pi.it", 0},
+#line 2707 "effective_tld_names.gperf"
+ {"pro.na", 0},
+#line 1485 "effective_tld_names.gperf"
+ {"in-addr.arpa", 0},
+#line 1529 "effective_tld_names.gperf"
+ {"int.az", 0},
+#line 594 "effective_tld_names.gperf"
+ {"coloradoplateau.museum", 0},
+#line 1956 "effective_tld_names.gperf"
+ {"mil.hn", 0},
+#line 3627 "effective_tld_names.gperf"
+ {"xn--snsa-roa.no", 0},
+#line 1567 "effective_tld_names.gperf"
+ {"ivanovo.ru", 0},
+#line 3119 "effective_tld_names.gperf"
+ {"sz", 0},
+#line 3557 "effective_tld_names.gperf"
+ {"xn--lrdal-sra.no", 0},
+#line 2924 "effective_tld_names.gperf"
+ {"sd", 0},
+#line 3294 "effective_tld_names.gperf"
+ {"uri.arpa", 0},
+#line 1601 "effective_tld_names.gperf"
+ {"jondal.no", 0},
+#line 740 "effective_tld_names.gperf"
+ {"crotone.it", 0},
+#line 3644 "effective_tld_names.gperf"
+ {"xn--troms-zua.no", 0},
+#line 3048 "effective_tld_names.gperf"
+ {"st", 0},
+#line 2989 "effective_tld_names.gperf"
+ {"sl", 0},
+#line 703 "effective_tld_names.gperf"
+ {"computer.museum", 0},
+#line 90 "effective_tld_names.gperf"
+ {"agents.aero", 0},
+#line 2713 "effective_tld_names.gperf"
+ {"prof.pr", 0},
+#line 1928 "effective_tld_names.gperf"
+ {"mg", 0},
+#line 3006 "effective_tld_names.gperf"
+ {"so.it", 0},
+#line 1964 "effective_tld_names.gperf"
+ {"mil.lv", 0},
+#line 3046 "effective_tld_names.gperf"
+ {"ss.it", 0},
+#line 1598 "effective_tld_names.gperf"
+ {"jobs.tt", 0},
+#line 3042 "effective_tld_names.gperf"
+ {"sr", 0},
+#line 3590 "effective_tld_names.gperf"
+ {"xn--p1ai", 0},
+#line 3593 "effective_tld_names.gperf"
+ {"xn--rdal-poa.no", 0},
+#line 2927 "effective_tld_names.gperf"
+ {"se", 0},
+#line 1066 "effective_tld_names.gperf"
+ {"forsand.no", 0},
+#line 3620 "effective_tld_names.gperf"
+ {"xn--slat-5na.no", 0},
+#line 3273 "effective_tld_names.gperf"
+ {"ug", 0},
+#line 3289 "effective_tld_names.gperf"
+ {"univ.sn", 0},
+#line 3044 "effective_tld_names.gperf"
+ {"sr.it", 0},
+#line 2091 "effective_tld_names.gperf"
+ {"name.tt", 0},
+#line 3029 "effective_tld_names.gperf"
+ {"sos.pl", 0},
+#line 433 "effective_tld_names.gperf"
+ {"c.bg", 0},
+#line 814 "effective_tld_names.gperf"
{"e.bg", 0},
-#line 1341 "effective_tld_names.gperf"
- {"h.bg", 0},
-#line 3406 "effective_tld_names.gperf"
+#line 1457 "effective_tld_names.gperf"
+ {"i.bg", 0},
+#line 26 "effective_tld_names.gperf"
+ {"a.bg", 0},
+#line 3461 "effective_tld_names.gperf"
{"x.bg", 0},
#line 21 "effective_tld_names.gperf"
{"6.bg", 0},
+#line 16 "effective_tld_names.gperf"
+ {"2.bg", 0},
#line 25 "effective_tld_names.gperf"
{"9.bg", 0},
#line 24 "effective_tld_names.gperf"
{"8.bg", 0},
+#line 3689 "effective_tld_names.gperf"
+ {"z.bg", 0},
+#line 3034 "effective_tld_names.gperf"
+ {"sp.it", 0},
#line 23 "effective_tld_names.gperf"
{"7.bg", 0},
#line 20 "effective_tld_names.gperf"
@@ -1910,8247 +2294,8025 @@
{"4.bg", 0},
#line 18 "effective_tld_names.gperf"
{"3.bg", 0},
-#line 16 "effective_tld_names.gperf"
- {"2.bg", 0},
-#line 2035 "effective_tld_names.gperf"
- {"n.bg", 0},
#line 15 "effective_tld_names.gperf"
{"1.bg", 0},
#line 14 "effective_tld_names.gperf"
{"0.bg", 0},
-#line 3365 "effective_tld_names.gperf"
- {"wa.us", 0},
-#line 424 "effective_tld_names.gperf"
- {"c.bg", 0},
-#line 3379 "effective_tld_names.gperf"
- {"web.nf", 0},
-#line 3219 "effective_tld_names.gperf"
- {"udm.ru", 0},
-#line 3609 "effective_tld_names.gperf"
- {"y.bg", 0},
-#line 1931 "effective_tld_names.gperf"
- {"mil.km", 0},
-#line 2217 "effective_tld_names.gperf"
- {"nic.ar", 1},
-#line 3386 "effective_tld_names.gperf"
- {"wi.us", 0},
-#line 1979 "effective_tld_names.gperf"
- {"mod.gi", 0},
-#line 238 "effective_tld_names.gperf"
- {"aurland.no", 0},
-#line 1344 "effective_tld_names.gperf"
- {"ha.no", 0},
-#line 27 "effective_tld_names.gperf"
- {"a.se", 0},
-#line 3628 "effective_tld_names.gperf"
- {"z.bg", 0},
-#line 2299 "effective_tld_names.gperf"
- {"o.bg", 0},
-#line 1935 "effective_tld_names.gperf"
- {"mil.mg", 0},
-#line 983 "effective_tld_names.gperf"
- {"fc.it", 0},
-#line 363 "effective_tld_names.gperf"
- {"bo", 0},
-#line 296 "effective_tld_names.gperf"
- {"bd", 2},
-#line 2987 "effective_tld_names.gperf"
- {"spb.ru", 0},
-#line 298 "effective_tld_names.gperf"
- {"be", 0},
-#line 148 "effective_tld_names.gperf"
- {"and.museum", 0},
-#line 1817 "effective_tld_names.gperf"
- {"m.se", 0},
-#line 417 "effective_tld_names.gperf"
- {"by", 0},
-#line 41 "effective_tld_names.gperf"
- {"ac.in", 0},
-#line 702 "effective_tld_names.gperf"
- {"control.aero", 0},
-#line 381 "effective_tld_names.gperf"
- {"br", 0},
-#line 599 "effective_tld_names.gperf"
- {"com.bh", 0},
-#line 431 "effective_tld_names.gperf"
- {"caa.aero", 0},
-#line 2818 "effective_tld_names.gperf"
- {"samara.ru", 0},
-#line 1249 "effective_tld_names.gperf"
- {"gov.ph", 0},
-#line 2388 "effective_tld_names.gperf"
- {"org.im", 0},
-#line 361 "effective_tld_names.gperf"
- {"bn", 2},
-#line 2389 "effective_tld_names.gperf"
- {"org.in", 0},
-#line 878 "effective_tld_names.gperf"
- {"edu.ph", 0},
-#line 61 "effective_tld_names.gperf"
- {"ac.vn", 0},
-#line 89 "effective_tld_names.gperf"
- {"agr.br", 0},
-#line 2171 "effective_tld_names.gperf"
- {"net.ph", 0},
-#line 1871 "effective_tld_names.gperf"
- {"med.br", 0},
-#line 2232 "effective_tld_names.gperf"
- {"nm.cn", 0},
-#line 1137 "effective_tld_names.gperf"
- {"gl", 0},
-#line 1451 "effective_tld_names.gperf"
- {"il", 2},
-#line 658 "effective_tld_names.gperf"
- {"com.ph", 0},
-#line 2228 "effective_tld_names.gperf"
- {"nl", 0},
-#line 405 "effective_tld_names.gperf"
- {"bs", 0},
-#line 150 "effective_tld_names.gperf"
- {"andebu.no", 0},
-#line 524 "effective_tld_names.gperf"
- {"cl", 0},
-#line 407 "effective_tld_names.gperf"
- {"bt", 2},
-#line 133 "effective_tld_names.gperf"
- {"ambulance.aero", 0},
-#line 953 "effective_tld_names.gperf"
- {"estate.museum", 0},
-#line 1222 "effective_tld_names.gperf"
- {"gov.jo", 0},
-#line 851 "effective_tld_names.gperf"
- {"edu.jo", 0},
-#line 2848 "effective_tld_names.gperf"
- {"sc.ug", 0},
-#line 1435 "effective_tld_names.gperf"
- {"i.ph", 0},
-#line 48 "effective_tld_names.gperf"
- {"ac.mw", 0},
-#line 704 "effective_tld_names.gperf"
- {"coop", 0},
-#line 3258 "effective_tld_names.gperf"
- {"ut.us", 0},
-#line 3266 "effective_tld_names.gperf"
- {"uz", 0},
-#line 2145 "effective_tld_names.gperf"
- {"net.jo", 0},
-#line 1340 "effective_tld_names.gperf"
- {"gz.cn", 0},
-#line 3608 "effective_tld_names.gperf"
- {"xz.cn", 0},
-#line 1471 "effective_tld_names.gperf"
- {"inderoy.no", 0},
-#line 2951 "effective_tld_names.gperf"
- {"snaase.no", 0},
-#line 631 "effective_tld_names.gperf"
- {"com.jo", 0},
-#line 1756 "effective_tld_names.gperf"
- {"li.it", 0},
-#line 1746 "effective_tld_names.gperf"
- {"lel.br", 0},
-#line 2236 "effective_tld_names.gperf"
- {"no.com", 0},
-#line 1971 "effective_tld_names.gperf"
- {"mo.cn", 0},
-#line 561 "effective_tld_names.gperf"
- {"co.me", 0},
-#line 1938 "effective_tld_names.gperf"
- {"mil.pe", 0},
-#line 1754 "effective_tld_names.gperf"
- {"lg.ua", 0},
-#line 2312 "effective_tld_names.gperf"
- {"ok.us", 0},
-#line 941 "effective_tld_names.gperf"
- {"environment.museum", 0},
-#line 2805 "effective_tld_names.gperf"
- {"sa.it", 0},
-#line 1877 "effective_tld_names.gperf"
- {"med.pl", 0},
-#line 533 "effective_tld_names.gperf"
- {"cn.com", 0},
-#line 2309 "effective_tld_names.gperf"
- {"og.ao", 0},
-#line 999 "effective_tld_names.gperf"
- {"fi.it", 0},
-#line 1116 "effective_tld_names.gperf"
- {"gen.in", 0},
-#line 147 "effective_tld_names.gperf"
- {"ancona.it", 0},
-#line 2911 "effective_tld_names.gperf"
- {"si.it", 0},
-#line 1962 "effective_tld_names.gperf"
- {"mk", 0},
-#line 163 "effective_tld_names.gperf"
- {"aq.it", 0},
-#line 1007 "effective_tld_names.gperf"
- {"fin.tn", 0},
-#line 1335 "effective_tld_names.gperf"
- {"gx.cn", 0},
-#line 1314 "effective_tld_names.gperf"
- {"gs.oslo.no", 0},
-#line 1875 "effective_tld_names.gperf"
- {"med.ly", 0},
-#line 2425 "effective_tld_names.gperf"
- {"org.ph", 0},
-#line 207 "effective_tld_names.gperf"
- {"asmatart.museum", 0},
-#line 578 "effective_tld_names.gperf"
- {"co.vi", 0},
-#line 2293 "effective_tld_names.gperf"
- {"nx.cn", 0},
-#line 1219 "effective_tld_names.gperf"
- {"gov.is", 0},
-#line 849 "effective_tld_names.gperf"
- {"edu.is", 0},
-#line 1421 "effective_tld_names.gperf"
- {"hoylandet.no", 0},
-#line 26 "effective_tld_names.gperf"
- {"a.bg", 0},
-#line 2143 "effective_tld_names.gperf"
- {"net.is", 0},
-#line 2886 "effective_tld_names.gperf"
- {"sel.no", 0},
-#line 630 "effective_tld_names.gperf"
- {"com.is", 0},
-#line 2327 "effective_tld_names.gperf"
- {"ontario.museum", 0},
-#line 2394 "effective_tld_names.gperf"
- {"org.jo", 0},
-#line 2188 "effective_tld_names.gperf"
- {"net.th", 0},
-#line 1930 "effective_tld_names.gperf"
- {"mil.kg", 0},
-#line 1727 "effective_tld_names.gperf"
- {"lavagis.no", 0},
-#line 1816 "effective_tld_names.gperf"
- {"m.bg", 0},
-#line 359 "effective_tld_names.gperf"
- {"bm", 0},
-#line 1506 "effective_tld_names.gperf"
- {"int.is", 0},
-#line 28 "effective_tld_names.gperf"
- {"aa.no", 0},
-#line 1347 "effective_tld_names.gperf"
- {"hagebostad.no", 0},
-#line 3000 "effective_tld_names.gperf"
- {"st.no", 0},
-#line 1500 "effective_tld_names.gperf"
- {"insurance.aero", 0},
-#line 587 "effective_tld_names.gperf"
- {"columbus.museum", 0},
-#line 1914 "effective_tld_names.gperf"
- {"mil.ac", 0},
-#line 3068 "effective_tld_names.gperf"
- {"syzran.ru", 0},
-#line 523 "effective_tld_names.gperf"
- {"ck.ua", 0},
-#line 1452 "effective_tld_names.gperf"
- {"il.us", 0},
-#line 3245 "effective_tld_names.gperf"
- {"us.na", 0},
-#line 2020 "effective_tld_names.gperf"
- {"mus.br", 0},
-#line 914 "effective_tld_names.gperf"
- {"eid.no", 0},
-#line 1214 "effective_tld_names.gperf"
- {"gov.ie", 0},
-#line 961 "effective_tld_names.gperf"
- {"eu.com", 0},
-#line 1426 "effective_tld_names.gperf"
- {"hu.com", 0},
-#line 422 "effective_tld_names.gperf"
- {"bz", 0},
-#line 975 "effective_tld_names.gperf"
- {"far.br", 0},
-#line 919 "effective_tld_names.gperf"
- {"eigersund.no", 0},
-#line 3215 "effective_tld_names.gperf"
- {"ua", 0},
-#line 3217 "effective_tld_names.gperf"
- {"ud.it", 0},
-#line 1343 "effective_tld_names.gperf"
- {"ha.cn", 0},
-#line 3370 "effective_tld_names.gperf"
- {"war.museum", 0},
-#line 2960 "effective_tld_names.gperf"
- {"software.aero", 0},
-#line 1705 "effective_tld_names.gperf"
- {"l.se", 0},
-#line 2985 "effective_tld_names.gperf"
- {"sp.it", 0},
-#line 304 "effective_tld_names.gperf"
- {"beiarn.no", 0},
-#line 1948 "effective_tld_names.gperf"
- {"mil.vc", 0},
-#line 1872 "effective_tld_names.gperf"
- {"med.ec", 0},
-#line 992 "effective_tld_names.gperf"
- {"fg.it", 0},
-#line 1382 "effective_tld_names.gperf"
- {"hi.cn", 0},
-#line 1919 "effective_tld_names.gperf"
- {"mil.br", 0},
-#line 1736 "effective_tld_names.gperf"
- {"lebesby.no", 0},
-#line 2850 "effective_tld_names.gperf"
- {"sch.ae", 0},
-#line 1795 "effective_tld_names.gperf"
- {"ltd.gi", 0},
-#line 2227 "effective_tld_names.gperf"
- {"nkz.ru", 0},
-#line 177 "effective_tld_names.gperf"
- {"arezzo.it", 0},
-#line 113 "effective_tld_names.gperf"
- {"al", 0},
-#line 1937 "effective_tld_names.gperf"
- {"mil.no", 0},
-#line 100 "effective_tld_names.gperf"
- {"aip.ee", 0},
-#line 717 "effective_tld_names.gperf"
- {"county.museum", 0},
-#line 2856 "effective_tld_names.gperf"
- {"sch.ly", 0},
-#line 2392 "effective_tld_names.gperf"
- {"org.is", 0},
-#line 2078 "effective_tld_names.gperf"
- {"nature.museum", 0},
-#line 109 "effective_tld_names.gperf"
- {"ak.us", 0},
-#line 1964 "effective_tld_names.gperf"
- {"ml", 2},
-#line 972 "effective_tld_names.gperf"
- {"f.se", 0},
-#line 1841 "effective_tld_names.gperf"
- {"mari-el.ru", 0},
-#line 2799 "effective_tld_names.gperf"
- {"s.se", 0},
-#line 934 "effective_tld_names.gperf"
- {"engine.aero", 0},
-#line 2851 "effective_tld_names.gperf"
- {"sch.gg", 0},
-#line 45 "effective_tld_names.gperf"
- {"ac.ma", 0},
-#line 2218 "effective_tld_names.gperf"
- {"nic.im", 0},
-#line 2219 "effective_tld_names.gperf"
- {"nic.in", 0},
-#line 1936 "effective_tld_names.gperf"
- {"mil.my", 0},
-#line 2332 "effective_tld_names.gperf"
- {"oppegard.no", 0},
-#line 1920 "effective_tld_names.gperf"
- {"mil.by", 0},
-#line 1039 "effective_tld_names.gperf"
- {"fm.no", 0},
-#line 2698 "effective_tld_names.gperf"
- {"qsl.br", 0},
-#line 167 "effective_tld_names.gperf"
- {"ar.com", 0},
-#line 37 "effective_tld_names.gperf"
- {"ac.cn", 0},
-#line 1940 "effective_tld_names.gperf"
- {"mil.pl", 0},
-#line 548 "effective_tld_names.gperf"
- {"co.gg", 0},
-#line 2802 "effective_tld_names.gperf"
- {"sa.cr", 0},
-#line 1915 "effective_tld_names.gperf"
- {"mil.ae", 0},
-#line 940 "effective_tld_names.gperf"
- {"entomology.museum", 0},
-#line 1542 "effective_tld_names.gperf"
- {"iz.hr", 0},
-#line 998 "effective_tld_names.gperf"
- {"fi.cr", 0},
-#line 3362 "effective_tld_names.gperf"
- {"w.se", 0},
-#line 134 "effective_tld_names.gperf"
- {"ambulance.museum", 0},
-#line 1772 "effective_tld_names.gperf"
- {"ln.cn", 0},
-#line 1221 "effective_tld_names.gperf"
- {"gov.je", 0},
-#line 3221 "effective_tld_names.gperf"
- {"ug", 0},
-#line 1040 "effective_tld_names.gperf"
- {"fnd.br", 0},
-#line 2144 "effective_tld_names.gperf"
- {"net.je", 0},
-#line 79 "effective_tld_names.gperf"
- {"aerobatic.aero", 0},
-#line 2085 "effective_tld_names.gperf"
- {"navuotna.no", 0},
-#line 1155 "effective_tld_names.gperf"
- {"go.tj", 0},
-#line 3056 "effective_tld_names.gperf"
- {"svalbard.no", 0},
-#line 1821 "effective_tld_names.gperf"
- {"mad.museum", 0},
-#line 1125 "effective_tld_names.gperf"
- {"gh", 0},
-#line 1771 "effective_tld_names.gperf"
- {"lk", 0},
-#line 260 "effective_tld_names.gperf"
- {"ba", 0},
-#line 364 "effective_tld_names.gperf"
- {"bo.it", 0},
-#line 572 "effective_tld_names.gperf"
- {"co.tj", 0},
-#line 3004 "effective_tld_names.gperf"
- {"stange.no", 0},
-#line 467 "effective_tld_names.gperf"
- {"ch", 0},
-#line 1873 "effective_tld_names.gperf"
- {"med.ee", 0},
-#line 936 "effective_tld_names.gperf"
- {"england.museum", 0},
-#line 1218 "effective_tld_names.gperf"
- {"gov.ir", 0},
-#line 1404 "effective_tld_names.gperf"
- {"hobol.no", 0},
-#line 2877 "effective_tld_names.gperf"
- {"sd.cn", 0},
-#line 2142 "effective_tld_names.gperf"
- {"net.ir", 0},
-#line 1310 "effective_tld_names.gperf"
- {"gs.nl.no", 0},
-#line 323 "effective_tld_names.gperf"
- {"bi", 0},
-#line 1430 "effective_tld_names.gperf"
- {"hurum.no", 0},
-#line 383 "effective_tld_names.gperf"
- {"br.it", 0},
-#line 2916 "effective_tld_names.gperf"
- {"siljan.no", 0},
-#line 1209 "effective_tld_names.gperf"
- {"gov.gh", 0},
-#line 2460 "effective_tld_names.gperf"
- {"orskog.no", 0},
-#line 839 "effective_tld_names.gperf"
- {"edu.gh", 0},
-#line 2349 "effective_tld_names.gperf"
- {"orenburg.ru", 0},
-#line 362 "effective_tld_names.gperf"
- {"bn.it", 0},
-#line 712 "effective_tld_names.gperf"
- {"corvette.museum", 0},
-#line 1704 "effective_tld_names.gperf"
- {"l.bg", 0},
-#line 1856 "effective_tld_names.gperf"
- {"matera.it", 0},
-#line 618 "effective_tld_names.gperf"
- {"com.gh", 0},
-#line 1313 "effective_tld_names.gperf"
- {"gs.ol.no", 0},
-#line 116 "effective_tld_names.gperf"
- {"al.us", 0},
-#line 2950 "effective_tld_names.gperf"
- {"sn.cn", 0},
-#line 406 "effective_tld_names.gperf"
- {"bs.it", 0},
-#line 525 "effective_tld_names.gperf"
- {"cl.it", 0},
-#line 1023 "effective_tld_names.gperf"
- {"fk", 2},
-#line 1148 "effective_tld_names.gperf"
- {"go.ci", 0},
-#line 1469 "effective_tld_names.gperf"
- {"ind.in", 0},
-#line 811 "effective_tld_names.gperf"
- {"ed.ci", 0},
-#line 1963 "effective_tld_names.gperf"
- {"mk.ua", 0},
-#line 1932 "effective_tld_names.gperf"
- {"mil.kr", 0},
-#line 911 "effective_tld_names.gperf"
- {"egersund.no", 0},
-#line 546 "effective_tld_names.gperf"
- {"co.ci", 0},
-#line 2921 "effective_tld_names.gperf"
- {"sk", 0},
-#line 1944 "effective_tld_names.gperf"
- {"mil.sy", 0},
-#line 1923 "effective_tld_names.gperf"
- {"mil.ec", 0},
-#line 1794 "effective_tld_names.gperf"
- {"ltd.co.im", 0},
-#line 2980 "effective_tld_names.gperf"
- {"sos.pl", 0},
-#line 1102 "effective_tld_names.gperf"
- {"gaular.no", 0},
-#line 1846 "effective_tld_names.gperf"
- {"marker.no", 0},
-#line 1924 "effective_tld_names.gperf"
- {"mil.ge", 0},
-#line 2393 "effective_tld_names.gperf"
- {"org.je", 0},
-#line 971 "effective_tld_names.gperf"
- {"f.bg", 0},
-#line 2798 "effective_tld_names.gperf"
- {"s.bg", 0},
-#line 62 "effective_tld_names.gperf"
- {"aca.pro", 0},
-#line 226 "effective_tld_names.gperf"
- {"astrakhan.ru", 0},
-#line 1942 "effective_tld_names.gperf"
- {"mil.rw", 0},
-#line 99 "effective_tld_names.gperf"
- {"aid.pl", 0},
-#line 3631 "effective_tld_names.gperf"
- {"za.com", 0},
-#line 2335 "effective_tld_names.gperf"
- {"or.ci", 0},
-#line 2391 "effective_tld_names.gperf"
- {"org.ir", 0},
-#line 336 "effective_tld_names.gperf"
- {"bir.ru", 0},
-#line 718 "effective_tld_names.gperf"
- {"cpa.pro", 0},
-#line 320 "effective_tld_names.gperf"
- {"bg", 0},
-#line 1333 "effective_tld_names.gperf"
- {"gw", 0},
-#line 1444 "effective_tld_names.gperf"
- {"id.ly", 0},
-#line 1472 "effective_tld_names.gperf"
- {"indian.museum", 0},
-#line 1539 "effective_tld_names.gperf"
- {"iveland.no", 0},
-#line 165 "effective_tld_names.gperf"
- {"aquila.it", 0},
-#line 1311 "effective_tld_names.gperf"
- {"gs.nt.no", 0},
-#line 970 "effective_tld_names.gperf"
- {"express.aero", 0},
-#line 1196 "effective_tld_names.gperf"
- {"gov.cd", 0},
-#line 2253 "effective_tld_names.gperf"
- {"nord-fron.no", 0},
-#line 2306 "effective_tld_names.gperf"
- {"odo.br", 0},
-#line 2379 "effective_tld_names.gperf"
- {"org.gh", 0},
-#line 2977 "effective_tld_names.gperf"
- {"sorreisa.no", 0},
-#line 3246 "effective_tld_names.gperf"
- {"usa.museum", 0},
-#line 964 "effective_tld_names.gperf"
- {"evenes.no", 0},
-#line 2040 "effective_tld_names.gperf"
- {"nacion.ar", 1},
-#line 3361 "effective_tld_names.gperf"
- {"w.bg", 0},
-#line 1315 "effective_tld_names.gperf"
- {"gs.rl.no", 0},
-#line 907 "effective_tld_names.gperf"
- {"educator.aero", 0},
-#line 2214 "effective_tld_names.gperf"
- {"nh.us", 0},
-#line 1167 "effective_tld_names.gperf"
- {"gok.pk", 0},
-#line 2693 "effective_tld_names.gperf"
- {"qc.ca", 0},
-#line 2067 "effective_tld_names.gperf"
- {"narvik.no", 0},
-#line 3650 "effective_tld_names.gperf"
- {"zw", 2},
-#line 570 "effective_tld_names.gperf"
- {"co.sz", 0},
-#line 1097 "effective_tld_names.gperf"
- {"gamvik.no", 0},
-#line 1757 "effective_tld_names.gperf"
- {"lib.ee", 0},
-#line 1845 "effective_tld_names.gperf"
- {"maritimo.museum", 0},
-#line 1878 "effective_tld_names.gperf"
- {"med.pro", 0},
-#line 2211 "effective_tld_names.gperf"
- {"ngo.lk", 0},
-#line 1844 "effective_tld_names.gperf"
- {"maritime.museum", 0},
-#line 46 "effective_tld_names.gperf"
- {"ac.me", 0},
-#line 1018 "effective_tld_names.gperf"
- {"fitjar.no", 0},
-#line 1274 "effective_tld_names.gperf"
- {"gov.ua", 0},
-#line 2310 "effective_tld_names.gperf"
- {"oh.us", 0},
-#line 900 "effective_tld_names.gperf"
- {"edu.ua", 0},
-#line 1787 "effective_tld_names.gperf"
- {"louvre.museum", 0},
-#line 719 "effective_tld_names.gperf"
- {"cq.cn", 0},
-#line 1843 "effective_tld_names.gperf"
- {"marine.ru", 0},
-#line 2195 "effective_tld_names.gperf"
- {"net.ua", 0},
-#line 2940 "effective_tld_names.gperf"
- {"sl", 0},
-#line 682 "effective_tld_names.gperf"
- {"com.ua", 0},
-#line 423 "effective_tld_names.gperf"
- {"bz.it", 0},
-#line 1101 "effective_tld_names.gperf"
- {"gateway.museum", 0},
-#line 454 "effective_tld_names.gperf"
- {"catering.aero", 0},
-#line 1947 "effective_tld_names.gperf"
- {"mil.tw", 0},
-#line 1301 "effective_tld_names.gperf"
- {"gs.aa.no", 0},
-#line 2691 "effective_tld_names.gperf"
- {"q.bg", 0},
-#line 499 "effective_tld_names.gperf"
- {"cinema.museum", 0},
-#line 1900 "effective_tld_names.gperf"
- {"mh", 0},
-#line 3644 "effective_tld_names.gperf"
- {"zlg.br", 0},
-#line 703 "effective_tld_names.gperf"
- {"convent.museum", 0},
-#line 925 "effective_tld_names.gperf"
- {"elverum.no", 0},
-#line 3624 "effective_tld_names.gperf"
- {"yosemite.museum", 0},
-#line 2880 "effective_tld_names.gperf"
- {"se.com", 0},
-#line 246 "effective_tld_names.gperf"
- {"automotive.museum", 0},
-#line 1319 "effective_tld_names.gperf"
- {"gs.tm.no", 0},
-#line 340 "effective_tld_names.gperf"
- {"biz", 0},
-#line 1789 "effective_tld_names.gperf"
- {"loyalist.museum", 0},
-#line 114 "effective_tld_names.gperf"
- {"al.it", 0},
-#line 1359 "effective_tld_names.gperf"
- {"haram.no", 0},
-#line 1262 "effective_tld_names.gperf"
- {"gov.sd", 0},
-#line 1996 "effective_tld_names.gperf"
- {"mosreg.ru", 0},
-#line 891 "effective_tld_names.gperf"
- {"edu.sd", 0},
-#line 2846 "effective_tld_names.gperf"
- {"sc.cn", 0},
-#line 2267 "effective_tld_names.gperf"
- {"notteroy.no", 0},
-#line 1006 "effective_tld_names.gperf"
- {"fin.ec", 0},
-#line 2183 "effective_tld_names.gperf"
- {"net.sd", 0},
-#line 968 "effective_tld_names.gperf"
- {"exhibition.museum", 0},
-#line 671 "effective_tld_names.gperf"
- {"com.sd", 0},
-#line 213 "effective_tld_names.gperf"
- {"assisi.museum", 0},
-#line 345 "effective_tld_names.gperf"
- {"biz.pk", 0},
-#line 170 "effective_tld_names.gperf"
- {"arboretum.museum", 0},
-#line 2276 "effective_tld_names.gperf"
- {"nsk.ru", 0},
-#line 1865 "effective_tld_names.gperf"
- {"md.ci", 0},
-#line 2261 "effective_tld_names.gperf"
- {"norilsk.ru", 0},
-#line 1122 "effective_tld_names.gperf"
- {"gf", 0},
-#line 1805 "effective_tld_names.gperf"
- {"lunner.no", 0},
-#line 3064 "effective_tld_names.gperf"
- {"sx.cn", 0},
-#line 1320 "effective_tld_names.gperf"
- {"gs.tr.no", 0},
-#line 2208 "effective_tld_names.gperf"
- {"nf", 0},
-#line 1431 "effective_tld_names.gperf"
- {"hvaler.no", 0},
-#line 2451 "effective_tld_names.gperf"
- {"org.ua", 0},
-#line 465 "effective_tld_names.gperf"
- {"cf", 0},
-#line 1203 "effective_tld_names.gperf"
- {"gov.dm", 0},
-#line 833 "effective_tld_names.gperf"
- {"edu.dm", 0},
-#line 569 "effective_tld_names.gperf"
- {"co.st", 0},
-#line 1994 "effective_tld_names.gperf"
- {"mosjoen.no", 0},
-#line 926 "effective_tld_names.gperf"
- {"embroidery.museum", 0},
-#line 1053 "effective_tld_names.gperf"
- {"fosnes.no", 0},
-#line 2127 "effective_tld_names.gperf"
- {"net.dm", 0},
-#line 1351 "effective_tld_names.gperf"
- {"hamar.no", 0},
-#line 1517 "effective_tld_names.gperf"
- {"interactive.museum", 0},
-#line 611 "effective_tld_names.gperf"
- {"com.dm", 0},
-#line 2919 "effective_tld_names.gperf"
- {"siracusa.it", 0},
-#line 1887 "effective_tld_names.gperf"
- {"medical.museum", 0},
-#line 3619 "effective_tld_names.gperf"
- {"yk.ca", 0},
-#line 1983 "effective_tld_names.gperf"
- {"modern.museum", 0},
-#line 1950 "effective_tld_names.gperf"
- {"milano.it", 0},
-#line 253 "effective_tld_names.gperf"
- {"aw", 0},
-#line 1407 "effective_tld_names.gperf"
- {"hokksund.no", 0},
-#line 545 "effective_tld_names.gperf"
- {"co.bw", 0},
-#line 1024 "effective_tld_names.gperf"
- {"fl.us", 0},
-#line 963 "effective_tld_names.gperf"
- {"evenassi.no", 0},
-#line 250 "effective_tld_names.gperf"
- {"aviation.museum", 0},
-#line 2029 "effective_tld_names.gperf"
- {"mw", 0},
-#line 1060 "effective_tld_names.gperf"
- {"frankfurt.museum", 0},
-#line 261 "effective_tld_names.gperf"
- {"ba.it", 0},
-#line 2061 "effective_tld_names.gperf"
- {"nannestad.no", 0},
-#line 232 "effective_tld_names.gperf"
- {"ato.br", 0},
-#line 468 "effective_tld_names.gperf"
- {"ch.it", 0},
-#line 2101 "effective_tld_names.gperf"
- {"nes.buskerud.no", 0},
-#line 1770 "effective_tld_names.gperf"
- {"livorno.it", 0},
-#line 2990 "effective_tld_names.gperf"
- {"spy.museum", 0},
-#line 1428 "effective_tld_names.gperf"
- {"humanities.museum", 0},
-#line 93 "effective_tld_names.gperf"
- {"agrinet.tn", 0},
-#line 2438 "effective_tld_names.gperf"
- {"org.sd", 0},
-#line 324 "effective_tld_names.gperf"
- {"bi.it", 0},
-#line 2975 "effective_tld_names.gperf"
- {"sor-varanger.no", 0},
-#line 3261 "effective_tld_names.gperf"
- {"utsira.no", 0},
-#line 3214 "effective_tld_names.gperf"
- {"u.se", 0},
-#line 727 "effective_tld_names.gperf"
- {"crimea.ua", 0},
-#line 76 "effective_tld_names.gperf"
- {"aejrie.no", 0},
-#line 2238 "effective_tld_names.gperf"
- {"nom.ad", 0},
-#line 2924 "effective_tld_names.gperf"
- {"skanland.no", 0},
-#line 2843 "effective_tld_names.gperf"
- {"savona.it", 0},
-#line 2954 "effective_tld_names.gperf"
- {"snoasa.no", 0},
-#line 1375 "effective_tld_names.gperf"
- {"hemne.no", 0},
-#line 2979 "effective_tld_names.gperf"
- {"sorum.no", 0},
-#line 1321 "effective_tld_names.gperf"
- {"gs.va.no", 0},
-#line 349 "effective_tld_names.gperf"
- {"biz.tt", 0},
-#line 2372 "effective_tld_names.gperf"
- {"org.dm", 0},
-#line 1399 "effective_tld_names.gperf"
- {"hl.no", 0},
-#line 201 "effective_tld_names.gperf"
- {"aseral.no", 0},
-#line 2230 "effective_tld_names.gperf"
- {"nl.no", 0},
-#line 2894 "effective_tld_names.gperf"
- {"settlers.museum", 0},
-#line 2099 "effective_tld_names.gperf"
- {"nel.uk", 1},
-#line 1810 "effective_tld_names.gperf"
- {"luzern.museum", 0},
-#line 1372 "effective_tld_names.gperf"
- {"hellas.museum", 0},
-#line 2084 "effective_tld_names.gperf"
- {"navigation.aero", 0},
-#line 2229 "effective_tld_names.gperf"
- {"nl.ca", 0},
-#line 58 "effective_tld_names.gperf"
- {"ac.tj", 0},
-#line 342 "effective_tld_names.gperf"
- {"biz.ki", 0},
-#line 3250 "effective_tld_names.gperf"
- {"usculture.museum", 0},
-#line 2893 "effective_tld_names.gperf"
- {"settlement.museum", 0},
-#line 1927 "effective_tld_names.gperf"
- {"mil.in", 0},
-#line 199 "effective_tld_names.gperf"
- {"ascoli-piceno.it", 0},
-#line 1747 "effective_tld_names.gperf"
- {"lenvik.no", 0},
-#line 2316 "effective_tld_names.gperf"
- {"ol.no", 0},
-#line 3015 "effective_tld_names.gperf"
- {"stavern.no", 0},
-#line 419 "effective_tld_names.gperf"
- {"bygland.no", 0},
-#line 2243 "effective_tld_names.gperf"
- {"nom.fr", 0},
-#line 616 "effective_tld_names.gperf"
- {"com.fr", 0},
-#line 1726 "effective_tld_names.gperf"
- {"latina.it", 0},
-#line 1213 "effective_tld_names.gperf"
- {"gov.hk", 0},
-#line 1447 "effective_tld_names.gperf"
- {"idv.hk", 0},
-#line 844 "effective_tld_names.gperf"
- {"edu.hk", 0},
-#line 845 "effective_tld_names.gperf"
- {"edu.hn", 0},
-#line 321 "effective_tld_names.gperf"
- {"bg.it", 0},
-#line 1110 "effective_tld_names.gperf"
- {"gdansk.pl", 0},
-#line 846 "effective_tld_names.gperf"
- {"edu.ht", 0},
-#line 3614 "effective_tld_names.gperf"
- {"yamal.ru", 0},
-#line 2136 "effective_tld_names.gperf"
- {"net.hk", 0},
-#line 2137 "effective_tld_names.gperf"
- {"net.hn", 0},
-#line 2326 "effective_tld_names.gperf"
- {"online.museum", 0},
-#line 39 "effective_tld_names.gperf"
- {"ac.gn", 0},
-#line 2138 "effective_tld_names.gperf"
- {"net.ht", 0},
-#line 408 "effective_tld_names.gperf"
- {"bu.no", 0},
-#line 624 "effective_tld_names.gperf"
- {"com.hk", 0},
-#line 625 "effective_tld_names.gperf"
- {"com.hn", 0},
-#line 627 "effective_tld_names.gperf"
- {"com.ht", 0},
-#line 164 "effective_tld_names.gperf"
- {"aquarium.museum", 0},
-#line 83 "effective_tld_names.gperf"
- {"af", 0},
-#line 91 "effective_tld_names.gperf"
- {"agriculture.museum", 0},
-#line 2009 "effective_tld_names.gperf"
- {"msk.ru", 0},
-#line 2899 "effective_tld_names.gperf"
- {"sh", 0},
-#line 2854 "effective_tld_names.gperf"
- {"sch.jo", 0},
-#line 36 "effective_tld_names.gperf"
- {"ac.ci", 0},
-#line 1161 "effective_tld_names.gperf"
- {"gob.hn", 0},
-#line 2062 "effective_tld_names.gperf"
- {"naples.it", 0},
-#line 3224 "effective_tld_names.gperf"
- {"uk", 2},
-#line 1397 "effective_tld_names.gperf"
- {"hk.cn", 0},
-#line 1820 "effective_tld_names.gperf"
- {"macerata.it", 0},
-#line 976 "effective_tld_names.gperf"
- {"fareast.ru", 0},
-#line 248 "effective_tld_names.gperf"
- {"avellino.it", 0},
-#line 3002 "effective_tld_names.gperf"
- {"stalbans.museum", 0},
-#line 1432 "effective_tld_names.gperf"
- {"hyllestad.no", 0},
-#line 259 "effective_tld_names.gperf"
- {"b.se", 0},
-#line 1998 "effective_tld_names.gperf"
- {"mosvik.no", 0},
-#line 350 "effective_tld_names.gperf"
- {"biz.vn", 0},
-#line 1951 "effective_tld_names.gperf"
- {"military.museum", 0},
-#line 2889 "effective_tld_names.gperf"
- {"seljord.no", 0},
-#line 188 "effective_tld_names.gperf"
- {"artcenter.museum", 0},
-#line 1982 "effective_tld_names.gperf"
- {"modena.it", 0},
-#line 3213 "effective_tld_names.gperf"
- {"u.bg", 0},
-#line 92 "effective_tld_names.gperf"
- {"agrigento.it", 0},
-#line 1939 "effective_tld_names.gperf"
- {"mil.ph", 0},
-#line 716 "effective_tld_names.gperf"
- {"countryestate.museum", 0},
-#line 543 "effective_tld_names.gperf"
- {"co.ba", 0},
-#line 1896 "effective_tld_names.gperf"
- {"mesaverde.museum", 0},
-#line 1450 "effective_tld_names.gperf"
- {"if.ua", 0},
-#line 1345 "effective_tld_names.gperf"
- {"habmer.no", 0},
-#line 724 "effective_tld_names.gperf"
- {"creation.museum", 0},
-#line 1929 "effective_tld_names.gperf"
- {"mil.jo", 0},
-#line 2270 "effective_tld_names.gperf"
- {"novosibirsk.ru", 0},
-#line 1984 "effective_tld_names.gperf"
- {"modum.no", 0},
-#line 2384 "effective_tld_names.gperf"
- {"org.hk", 0},
-#line 2385 "effective_tld_names.gperf"
- {"org.hn", 0},
-#line 2801 "effective_tld_names.gperf"
- {"sa.com", 0},
-#line 2959 "effective_tld_names.gperf"
- {"society.museum", 0},
-#line 2386 "effective_tld_names.gperf"
- {"org.ht", 0},
-#line 1529 "effective_tld_names.gperf"
- {"isa.us", 0},
-#line 2694 "effective_tld_names.gperf"
- {"qc.com", 0},
-#line 1287 "effective_tld_names.gperf"
- {"grane.no", 0},
-#line 2928 "effective_tld_names.gperf"
- {"ski.museum", 0},
-#line 1724 "effective_tld_names.gperf"
- {"larvik.no", 0},
-#line 2033 "effective_tld_names.gperf"
- {"mytis.ru", 0},
-#line 1953 "effective_tld_names.gperf"
- {"mincom.tn", 0},
-#line 56 "effective_tld_names.gperf"
- {"ac.sz", 0},
-#line 2220 "effective_tld_names.gperf"
- {"niepce.museum", 0},
-#line 174 "effective_tld_names.gperf"
- {"ardal.no", 0},
-#line 2308 "effective_tld_names.gperf"
- {"off.ai", 0},
-#line 115 "effective_tld_names.gperf"
- {"al.no", 0},
-#line 1567 "effective_tld_names.gperf"
- {"jo", 0},
-#line 2226 "effective_tld_names.gperf"
- {"nj.us", 0},
-#line 2277 "effective_tld_names.gperf"
- {"nsn.us", 0},
-#line 1550 "effective_tld_names.gperf"
- {"je", 0},
-#line 2387 "effective_tld_names.gperf"
- {"org.hu", 0},
-#line 463 "effective_tld_names.gperf"
- {"center.museum", 0},
-#line 104 "effective_tld_names.gperf"
- {"aircraft.aero", 0},
-#line 1370 "effective_tld_names.gperf"
- {"health.vn", 0},
-#line 295 "effective_tld_names.gperf"
- {"bc.ca", 0},
-#line 1783 "effective_tld_names.gperf"
- {"loppa.no", 0},
-#line 153 "effective_tld_names.gperf"
- {"anthro.museum", 0},
-#line 2462 "effective_tld_names.gperf"
- {"oryol.ru", 0},
-#line 1398 "effective_tld_names.gperf"
- {"hl.cn", 0},
-#line 1363 "effective_tld_names.gperf"
- {"hasvik.no", 0},
-#line 2929 "effective_tld_names.gperf"
- {"ski.no", 0},
-#line 922 "effective_tld_names.gperf"
- {"elburg.museum", 0},
-#line 2213 "effective_tld_names.gperf"
- {"ngo.pl", 0},
-#line 526 "effective_tld_names.gperf"
- {"clinton.museum", 0},
-#line 1833 "effective_tld_names.gperf"
- {"malvik.no", 0},
-#line 1393 "effective_tld_names.gperf"
- {"hitra.no", 0},
-#line 3019 "effective_tld_names.gperf"
- {"steigen.no", 0},
-#line 449 "effective_tld_names.gperf"
- {"castle.museum", 0},
-#line 200 "effective_tld_names.gperf"
- {"ascolipiceno.it", 0},
-#line 2102 "effective_tld_names.gperf"
- {"nesna.no", 0},
-#line 1954 "effective_tld_names.gperf"
- {"miners.museum", 0},
-#line 413 "effective_tld_names.gperf"
- {"bus.museum", 0},
-#line 2967 "effective_tld_names.gperf"
- {"somna.no", 0},
-#line 519 "effective_tld_names.gperf"
- {"civilisation.museum", 0},
-#line 549 "effective_tld_names.gperf"
- {"co.gy", 0},
-#line 3059 "effective_tld_names.gperf"
- {"svizzera.museum", 0},
-#line 3265 "effective_tld_names.gperf"
- {"uy.com", 0},
-#line 1309 "effective_tld_names.gperf"
- {"gs.mr.no", 0},
-#line 258 "effective_tld_names.gperf"
- {"b.bg", 0},
-#line 88 "effective_tld_names.gperf"
- {"agents.aero", 0},
-#line 2944 "effective_tld_names.gperf"
- {"slg.br", 0},
-#line 184 "effective_tld_names.gperf"
- {"art.ht", 0},
-#line 2836 "effective_tld_names.gperf"
- {"sarpsborg.no", 0},
-#line 3025 "effective_tld_names.gperf"
- {"stokke.no", 0},
-#line 82 "effective_tld_names.gperf"
- {"aeroport.fr", 0},
-#line 2328 "effective_tld_names.gperf"
- {"openair.museum", 0},
-#line 347 "effective_tld_names.gperf"
- {"biz.pr", 0},
-#line 1413 "effective_tld_names.gperf"
- {"honefoss.no", 0},
-#line 1815 "effective_tld_names.gperf"
- {"lyngen.no", 0},
-#line 1827 "effective_tld_names.gperf"
- {"maintenance.aero", 0},
-#line 1361 "effective_tld_names.gperf"
- {"harstad.no", 0},
-#line 3244 "effective_tld_names.gperf"
- {"us.com", 0},
-#line 1520 "effective_tld_names.gperf"
- {"ip6.arpa", 0},
-#line 360 "effective_tld_names.gperf"
- {"bmd.br", 0},
-#line 1737 "effective_tld_names.gperf"
- {"lebork.pl", 0},
-#line 2992 "effective_tld_names.gperf"
- {"square.museum", 0},
-#line 1743 "effective_tld_names.gperf"
- {"leirvik.no", 0},
-#line 450 "effective_tld_names.gperf"
- {"castres.museum", 0},
-#line 2943 "effective_tld_names.gperf"
- {"sld.pa", 0},
-#line 53 "effective_tld_names.gperf"
- {"ac.ru", 0},
-#line 2513 "effective_tld_names.gperf"
- {"pe", 0},
-#line 131 "effective_tld_names.gperf"
- {"am.br", 0},
-#line 2688 "effective_tld_names.gperf"
- {"py", 2},
-#line 1415 "effective_tld_names.gperf"
- {"horology.museum", 0},
-#line 1081 "effective_tld_names.gperf"
- {"fuoisku.no", 0},
-#line 1154 "effective_tld_names.gperf"
- {"go.th", 0},
-#line 2587 "effective_tld_names.gperf"
- {"pr", 0},
-#line 571 "effective_tld_names.gperf"
- {"co.th", 0},
-#line 346 "effective_tld_names.gperf"
- {"biz.pl", 0},
-#line 2559 "effective_tld_names.gperf"
- {"pn", 0},
-#line 3247 "effective_tld_names.gperf"
- {"usantiques.museum", 0},
-#line 1297 "effective_tld_names.gperf"
- {"grozny.ru", 0},
-#line 2953 "effective_tld_names.gperf"
- {"snillfjord.no", 0},
-#line 2853 "effective_tld_names.gperf"
- {"sch.je", 0},
-#line 1410 "effective_tld_names.gperf"
- {"holmestrand.no", 0},
-#line 1235 "effective_tld_names.gperf"
- {"gov.lv", 0},
-#line 863 "effective_tld_names.gperf"
- {"edu.lv", 0},
-#line 974 "effective_tld_names.gperf"
- {"family.museum", 0},
-#line 1417 "effective_tld_names.gperf"
- {"hotel.hu", 0},
-#line 2672 "effective_tld_names.gperf"
- {"ps", 0},
-#line 2156 "effective_tld_names.gperf"
- {"net.lv", 0},
-#line 2083 "effective_tld_names.gperf"
- {"naval.museum", 0},
-#line 2676 "effective_tld_names.gperf"
- {"pt", 0},
-#line 1566 "effective_tld_names.gperf"
- {"jm", 2},
-#line 1464 "effective_tld_names.gperf"
- {"in.th", 0},
-#line 642 "effective_tld_names.gperf"
- {"com.lv", 0},
-#line 1289 "effective_tld_names.gperf"
- {"gratangen.no", 0},
-#line 107 "effective_tld_names.gperf"
- {"airport.aero", 0},
-#line 2922 "effective_tld_names.gperf"
- {"sk.ca", 0},
-#line 2852 "effective_tld_names.gperf"
- {"sch.ir", 0},
-#line 459 "effective_tld_names.gperf"
- {"cci.fr", 0},
-#line 1882 "effective_tld_names.gperf"
- {"medecin.km", 0},
-#line 2081 "effective_tld_names.gperf"
- {"naumburg.museum", 0},
-#line 2861 "effective_tld_names.gperf"
- {"schokoladen.museum", 0},
-#line 2516 "effective_tld_names.gperf"
- {"pe.kr", 0},
-#line 2343 "effective_tld_names.gperf"
- {"or.th", 0},
-#line 562 "effective_tld_names.gperf"
- {"co.mu", 0},
-#line 1025 "effective_tld_names.gperf"
- {"fla.no", 0},
-#line 209 "effective_tld_names.gperf"
- {"asnes.no", 0},
-#line 2518 "effective_tld_names.gperf"
- {"per.la", 0},
-#line 1365 "effective_tld_names.gperf"
- {"haugesund.no", 0},
-#line 1807 "effective_tld_names.gperf"
- {"luster.no", 0},
-#line 382 "effective_tld_names.gperf"
- {"br.com", 0},
-#line 2806 "effective_tld_names.gperf"
- {"safety.aero", 0},
-#line 927 "effective_tld_names.gperf"
- {"emergency.aero", 0},
-#line 1058 "effective_tld_names.gperf"
- {"frana.no", 0},
-#line 1955 "effective_tld_names.gperf"
- {"mining.museum", 0},
-#line 2315 "effective_tld_names.gperf"
- {"oksnes.no", 0},
-#line 1000 "effective_tld_names.gperf"
- {"fie.ee", 0},
-#line 1768 "effective_tld_names.gperf"
- {"living.museum", 0},
-#line 1011 "effective_tld_names.gperf"
- {"finnoy.no", 0},
-#line 2076 "effective_tld_names.gperf"
- {"naturalsciences.museum", 0},
-#line 343 "effective_tld_names.gperf"
- {"biz.mw", 0},
-#line 741 "effective_tld_names.gperf"
- {"cyber.museum", 0},
-#line 1741 "effective_tld_names.gperf"
- {"leikanger.no", 0},
-#line 2340 "effective_tld_names.gperf"
- {"or.mu", 0},
-#line 1052 "effective_tld_names.gperf"
- {"forum.hu", 0},
-#line 87 "effective_tld_names.gperf"
- {"agdenes.no", 0},
-#line 2407 "effective_tld_names.gperf"
- {"org.lv", 0},
-#line 1429 "effective_tld_names.gperf"
- {"hurdal.no", 0},
-#line 986 "effective_tld_names.gperf"
- {"federation.aero", 0},
-#line 996 "effective_tld_names.gperf"
- {"fhv.se", 0},
-#line 2200 "effective_tld_names.gperf"
- {"neues.museum", 0},
-#line 1272 "effective_tld_names.gperf"
- {"gov.tv", 0},
-#line 531 "effective_tld_names.gperf"
- {"cmw.ru", 0},
-#line 1925 "effective_tld_names.gperf"
- {"mil.gh", 0},
-#line 2021 "effective_tld_names.gperf"
- {"museet.museum", 0},
-#line 1049 "effective_tld_names.gperf"
- {"forsand.no", 0},
-#line 2193 "effective_tld_names.gperf"
- {"net.tv", 0},
-#line 1070 "effective_tld_names.gperf"
- {"froland.no", 0},
-#line 301 "effective_tld_names.gperf"
- {"beauxarts.museum", 0},
-#line 2589 "effective_tld_names.gperf"
- {"pr.us", 0},
-#line 680 "effective_tld_names.gperf"
- {"com.tv", 0},
-#line 96 "effective_tld_names.gperf"
- {"ah.no", 0},
-#line 2942 "effective_tld_names.gperf"
- {"slattum.no", 0},
-#line 2817 "effective_tld_names.gperf"
- {"salzburg.museum", 0},
-#line 3248 "effective_tld_names.gperf"
- {"usarts.museum", 0},
-#line 626 "effective_tld_names.gperf"
- {"com.hr", 0},
-#line 281 "effective_tld_names.gperf"
- {"bar.pro", 0},
-#line 2972 "effective_tld_names.gperf"
- {"sor-aurdal.no", 0},
-#line 2858 "effective_tld_names.gperf"
- {"sch.uk", 2},
-#line 1880 "effective_tld_names.gperf"
- {"med.sd", 0},
-#line 462 "effective_tld_names.gperf"
- {"celtic.museum", 0},
-#line 227 "effective_tld_names.gperf"
- {"astronomy.museum", 0},
-#line 2456 "effective_tld_names.gperf"
- {"oristano.it", 0},
-#line 2962 "effective_tld_names.gperf"
- {"sogne.no", 0},
-#line 1063 "effective_tld_names.gperf"
- {"freemasonry.museum", 0},
-#line 249 "effective_tld_names.gperf"
- {"averoy.no", 0},
-#line 1019 "effective_tld_names.gperf"
- {"fj", 2},
-#line 445 "effective_tld_names.gperf"
- {"cartoonart.museum", 0},
-#line 2459 "effective_tld_names.gperf"
- {"orland.no", 0},
-#line 1759 "effective_tld_names.gperf"
- {"lierne.no", 0},
-#line 2863 "effective_tld_names.gperf"
- {"school.na", 0},
-#line 2461 "effective_tld_names.gperf"
- {"orsta.no", 0},
-#line 3052 "effective_tld_names.gperf"
- {"surrey.museum", 0},
-#line 1889 "effective_tld_names.gperf"
- {"meeres.museum", 0},
-#line 1543 "effective_tld_names.gperf"
- {"izhevsk.ru", 0},
-#line 1010 "effective_tld_names.gperf"
- {"finland.museum", 0},
-#line 456 "effective_tld_names.gperf"
- {"cbg.ru", 0},
-#line 357 "effective_tld_names.gperf"
- {"bl.uk", 1},
-#line 2860 "effective_tld_names.gperf"
- {"schoenbrunn.museum", 0},
-#line 842 "effective_tld_names.gperf"
- {"edu.gp", 0},
-#line 2814 "effective_tld_names.gperf"
- {"salerno.it", 0},
-#line 2133 "effective_tld_names.gperf"
- {"net.gp", 0},
-#line 270 "effective_tld_names.gperf"
- {"baikal.ru", 0},
-#line 621 "effective_tld_names.gperf"
- {"com.gp", 0},
-#line 2449 "effective_tld_names.gperf"
- {"org.tv", 0},
-#line 1533 "effective_tld_names.gperf"
- {"isleofman.museum", 0},
-#line 2019 "effective_tld_names.gperf"
- {"murmansk.ru", 0},
-#line 3226 "effective_tld_names.gperf"
- {"uk.net", 0},
-#line 2209 "effective_tld_names.gperf"
- {"nf.ca", 0},
-#line 1038 "effective_tld_names.gperf"
- {"fm.br", 0},
-#line 3395 "effective_tld_names.gperf"
- {"wolomin.pl", 0},
-#line 1083 "effective_tld_names.gperf"
- {"furniture.museum", 0},
-#line 149 "effective_tld_names.gperf"
- {"andasuolo.no", 0},
-#line 3227 "effective_tld_names.gperf"
- {"ulan-ude.ru", 0},
-#line 2274 "effective_tld_names.gperf"
- {"nrw.museum", 0},
-#line 2307 "effective_tld_names.gperf"
- {"of.no", 0},
-#line 1379 "effective_tld_names.gperf"
- {"heritage.museum", 0},
-#line 582 "effective_tld_names.gperf"
- {"coldwar.museum", 0},
-#line 3230 "effective_tld_names.gperf"
- {"ulm.museum", 0},
-#line 372 "effective_tld_names.gperf"
- {"bolzano.it", 0},
-#line 3254 "effective_tld_names.gperf"
- {"ushistory.museum", 0},
-#line 444 "effective_tld_names.gperf"
- {"carrier.museum", 0},
-#line 1573 "effective_tld_names.gperf"
- {"jor.br", 0},
-#line 55 "effective_tld_names.gperf"
- {"ac.se", 0},
-#line 1548 "effective_tld_names.gperf"
- {"jar.ru", 0},
-#line 1579 "effective_tld_names.gperf"
- {"jp", 0},
-#line 1358 "effective_tld_names.gperf"
- {"hapmir.no", 0},
-#line 2680 "effective_tld_names.gperf"
- {"pub.sa", 0},
-#line 2077 "effective_tld_names.gperf"
- {"naturbruksgymn.se", 0},
-#line 2520 "effective_tld_names.gperf"
- {"per.sg", 0},
-#line 482 "effective_tld_names.gperf"
- {"chieti.it", 0},
-#line 293 "effective_tld_names.gperf"
- {"bauern.museum", 0},
-#line 322 "effective_tld_names.gperf"
- {"bh", 0},
-#line 2511 "effective_tld_names.gperf"
- {"pc.pl", 0},
-#line 2486 "effective_tld_names.gperf"
- {"pa", 0},
-#line 2562 "effective_tld_names.gperf"
- {"po.it", 0},
-#line 2512 "effective_tld_names.gperf"
- {"pd.it", 0},
-#line 2515 "effective_tld_names.gperf"
- {"pe.it", 0},
-#line 1307 "effective_tld_names.gperf"
- {"gs.hm.no", 0},
-#line 1369 "effective_tld_names.gperf"
- {"health.museum", 0},
-#line 1385 "effective_tld_names.gperf"
- {"histoire.museum", 0},
-#line 1749 "effective_tld_names.gperf"
- {"lesja.no", 0},
-#line 2382 "effective_tld_names.gperf"
- {"org.gp", 0},
-#line 583 "effective_tld_names.gperf"
- {"collection.museum", 0},
-#line 2588 "effective_tld_names.gperf"
- {"pr.it", 0},
-#line 1328 "effective_tld_names.gperf"
- {"gulen.no", 0},
-#line 1414 "effective_tld_names.gperf"
- {"hornindal.no", 0},
-#line 90 "effective_tld_names.gperf"
- {"agrar.hu", 0},
-#line 2678 "effective_tld_names.gperf"
- {"ptz.ru", 0},
-#line 1823 "effective_tld_names.gperf"
- {"magadan.ru", 0},
-#line 2560 "effective_tld_names.gperf"
- {"pn.it", 0},
-#line 71 "effective_tld_names.gperf"
- {"adult.ht", 0},
-#line 1355 "effective_tld_names.gperf"
- {"hammerfest.no", 0},
-#line 1306 "effective_tld_names.gperf"
- {"gs.hl.no", 0},
-#line 1890 "effective_tld_names.gperf"
- {"meland.no", 0},
-#line 356 "effective_tld_names.gperf"
- {"bl.it", 0},
-#line 2961 "effective_tld_names.gperf"
- {"sogndal.no", 0},
-#line 1348 "effective_tld_names.gperf"
- {"halden.no", 0},
-#line 1147 "effective_tld_names.gperf"
- {"gniezno.pl", 0},
-#line 2677 "effective_tld_names.gperf"
- {"pt.it", 0},
-#line 2106 "effective_tld_names.gperf"
- {"nesset.no", 0},
-#line 344 "effective_tld_names.gperf"
- {"biz.nr", 0},
-#line 95 "effective_tld_names.gperf"
- {"ah.cn", 0},
-#line 1065 "effective_tld_names.gperf"
- {"freiburg.museum", 0},
-#line 1099 "effective_tld_names.gperf"
- {"gangwon.kr", 0},
-#line 1786 "effective_tld_names.gperf"
- {"loten.no", 0},
-#line 1874 "effective_tld_names.gperf"
- {"med.ht", 0},
-#line 313 "effective_tld_names.gperf"
- {"bergen.no", 0},
-#line 1143 "effective_tld_names.gperf"
- {"gloppen.no", 0},
-#line 3034 "effective_tld_names.gperf"
- {"strand.no", 0},
-#line 2812 "effective_tld_names.gperf"
- {"salat.no", 0},
-#line 331 "effective_tld_names.gperf"
- {"bievat.no", 0},
-#line 3066 "effective_tld_names.gperf"
- {"sydney.museum", 0},
-#line 2888 "effective_tld_names.gperf"
- {"selje.no", 0},
-#line 959 "effective_tld_names.gperf"
- {"etnedal.no", 0},
-#line 455 "effective_tld_names.gperf"
- {"cb.it", 0},
-#line 2882 "effective_tld_names.gperf"
- {"seaport.museum", 0},
-#line 1774 "effective_tld_names.gperf"
- {"loabat.no", 0},
-#line 2819 "effective_tld_names.gperf"
- {"samnanger.no", 0},
-#line 471 "effective_tld_names.gperf"
- {"charter.aero", 0},
-#line 57 "effective_tld_names.gperf"
- {"ac.th", 0},
-#line 2679 "effective_tld_names.gperf"
- {"pu.it", 0},
-#line 416 "effective_tld_names.gperf"
- {"bw", 0},
-#line 2529 "effective_tld_names.gperf"
- {"pg", 2},
-#line 1748 "effective_tld_names.gperf"
- {"lerdal.no", 0},
-#line 2519 "effective_tld_names.gperf"
- {"per.nf", 0},
-#line 2480 "effective_tld_names.gperf"
- {"oxford.museum", 0},
-#line 2583 "effective_tld_names.gperf"
- {"pp.az", 0},
-#line 1131 "effective_tld_names.gperf"
- {"giske.no", 0},
-#line 1096 "effective_tld_names.gperf"
- {"games.hu", 0},
-#line 35 "effective_tld_names.gperf"
- {"ac.be", 0},
-#line 1467 "effective_tld_names.gperf"
- {"incheon.kr", 0},
-#line 2862 "effective_tld_names.gperf"
- {"school.museum", 0},
-#line 132 "effective_tld_names.gperf"
- {"amber.museum", 0},
-#line 3013 "effective_tld_names.gperf"
- {"station.museum", 0},
-#line 208 "effective_tld_names.gperf"
- {"asn.lv", 0},
-#line 2673 "effective_tld_names.gperf"
- {"psc.br", 0},
-#line 2489 "effective_tld_names.gperf"
- {"pa.us", 0},
-#line 158 "effective_tld_names.gperf"
- {"aomori.jp", 2},
-#line 2685 "effective_tld_names.gperf"
- {"pv.it", 0},
-#line 3014 "effective_tld_names.gperf"
- {"stavanger.no", 0},
-#line 2212 "effective_tld_names.gperf"
- {"ngo.ph", 0},
-#line 274 "effective_tld_names.gperf"
- {"balestrand.no", 0},
-#line 2281 "effective_tld_names.gperf"
- {"nt.edu.au", 0},
-#line 700 "effective_tld_names.gperf"
- {"contemporary.museum", 0},
-#line 47 "effective_tld_names.gperf"
- {"ac.mu", 0},
-#line 309 "effective_tld_names.gperf"
- {"benevento.it", 0},
-#line 1730 "effective_tld_names.gperf"
- {"lb", 0},
-#line 379 "effective_tld_names.gperf"
- {"botany.museum", 0},
-#line 2331 "effective_tld_names.gperf"
- {"oppdal.no", 0},
-#line 1891 "effective_tld_names.gperf"
- {"meldal.no", 0},
-#line 1999 "effective_tld_names.gperf"
- {"motorcycle.museum", 0},
-#line 2835 "effective_tld_names.gperf"
- {"saratov.ru", 0},
-#line 550 "effective_tld_names.gperf"
- {"co.hu", 0},
-#line 127 "effective_tld_names.gperf"
- {"alto-adige.it", 0},
-#line 2690 "effective_tld_names.gperf"
- {"pz.it", 0},
-#line 2923 "effective_tld_names.gperf"
- {"skanit.no", 0},
-#line 985 "effective_tld_names.gperf"
- {"fed.us", 0},
-#line 1457 "effective_tld_names.gperf"
- {"imageandsound.museum", 0},
-#line 241 "effective_tld_names.gperf"
- {"austin.museum", 0},
-#line 1580 "effective_tld_names.gperf"
- {"jpn.com", 0},
-#line 399 "effective_tld_names.gperf"
- {"brunel.museum", 0},
-#line 242 "effective_tld_names.gperf"
- {"australia.museum", 0},
-#line 544 "effective_tld_names.gperf"
- {"co.bi", 0},
-#line 1710 "effective_tld_names.gperf"
- {"labor.museum", 0},
-#line 2831 "effective_tld_names.gperf"
- {"santacruz.museum", 0},
-#line 433 "effective_tld_names.gperf"
- {"cagliari.it", 0},
-#line 1356 "effective_tld_names.gperf"
- {"handson.museum", 0},
-#line 2510 "effective_tld_names.gperf"
- {"pc.it", 0},
-#line 2022 "effective_tld_names.gperf"
- {"museum", 0},
-#line 2844 "effective_tld_names.gperf"
- {"sb", 0},
-#line 287 "effective_tld_names.gperf"
- {"barum.no", 0},
-#line 1092 "effective_tld_names.gperf"
- {"gaivuotna.no", 0},
-#line 2483 "effective_tld_names.gperf"
- {"oystre-slidre.no", 0},
-#line 1926 "effective_tld_names.gperf"
- {"mil.hn", 0},
-#line 723 "effective_tld_names.gperf"
- {"cranbrook.museum", 0},
-#line 987 "effective_tld_names.gperf"
- {"fedje.no", 0},
-#line 1105 "effective_tld_names.gperf"
- {"gb.net", 0},
-#line 1902 "effective_tld_names.gperf"
- {"mi.th", 0},
-#line 2674 "effective_tld_names.gperf"
- {"psi.br", 0},
-#line 967 "effective_tld_names.gperf"
- {"exeter.museum", 0},
-#line 1974 "effective_tld_names.gperf"
- {"moareke.no", 0},
-#line 3241 "effective_tld_names.gperf"
- {"uri.arpa", 0},
-#line 2097 "effective_tld_names.gperf"
- {"nebraska.museum", 0},
-#line 1848 "effective_tld_names.gperf"
- {"marnardal.no", 0},
-#line 2023 "effective_tld_names.gperf"
- {"museum.no", 0},
-#line 2334 "effective_tld_names.gperf"
- {"or.bi", 0},
-#line 319 "effective_tld_names.gperf"
- {"bf", 0},
-#line 2065 "effective_tld_names.gperf"
- {"naroy.no", 0},
-#line 1047 "effective_tld_names.gperf"
- {"forli-cesena.it", 0},
-#line 1881 "effective_tld_names.gperf"
- {"medecin.fr", 0},
-#line 1293 "effective_tld_names.gperf"
- {"grong.no", 0},
-#line 191 "effective_tld_names.gperf"
- {"artgallery.museum", 0},
-#line 2256 "effective_tld_names.gperf"
- {"nordkapp.no", 0},
-#line 2592 "effective_tld_names.gperf"
- {"prd.km", 0},
-#line 786 "effective_tld_names.gperf"
- {"do", 2},
-#line 1767 "effective_tld_names.gperf"
- {"lipetsk.ru", 0},
-#line 2966 "effective_tld_names.gperf"
- {"solund.no", 0},
-#line 759 "effective_tld_names.gperf"
- {"de", 0},
-#line 3048 "effective_tld_names.gperf"
- {"sunndal.no", 0},
-#line 3408 "effective_tld_names.gperf"
- {"xj.cn", 0},
-#line 1061 "effective_tld_names.gperf"
- {"franziskaner.museum", 0},
-#line 1093 "effective_tld_names.gperf"
- {"gallery.museum", 0},
-#line 2027 "effective_tld_names.gperf"
- {"music.museum", 0},
-#line 2875 "effective_tld_names.gperf"
- {"scotland.museum", 0},
-#line 2059 "effective_tld_names.gperf"
- {"namsos.no", 0},
-#line 67 "effective_tld_names.gperf"
- {"act.gov.au", 0},
-#line 2593 "effective_tld_names.gperf"
- {"prd.mg", 0},
-#line 2488 "effective_tld_names.gperf"
- {"pa.it", 0},
-#line 2024 "effective_tld_names.gperf"
- {"museum.tt", 0},
-#line 3643 "effective_tld_names.gperf"
- {"zj.cn", 0},
-#line 3242 "effective_tld_names.gperf"
- {"urn.arpa", 0},
-#line 994 "effective_tld_names.gperf"
- {"fhs.no", 0},
-#line 1312 "effective_tld_names.gperf"
- {"gs.of.no", 0},
-#line 2900 "effective_tld_names.gperf"
- {"sh.cn", 0},
-#line 956 "effective_tld_names.gperf"
- {"ethnology.museum", 0},
-#line 1420 "effective_tld_names.gperf"
- {"hoyanger.no", 0},
-#line 3383 "effective_tld_names.gperf"
- {"western.museum", 0},
-#line 1172 "effective_tld_names.gperf"
- {"gorizia.it", 0},
-#line 1008 "effective_tld_names.gperf"
- {"fineart.museum", 0},
-#line 1750 "effective_tld_names.gperf"
- {"levanger.no", 0},
-#line 2540 "effective_tld_names.gperf"
- {"pi.it", 0},
-#line 1802 "effective_tld_names.gperf"
- {"lugansk.ua", 0},
-#line 982 "effective_tld_names.gperf"
- {"fauske.no", 0},
-#line 2105 "effective_tld_names.gperf"
- {"nesseby.no", 0},
-#line 2973 "effective_tld_names.gperf"
- {"sor-fron.no", 0},
-#line 434 "effective_tld_names.gperf"
- {"cahcesuolo.no", 0},
-#line 1418 "effective_tld_names.gperf"
- {"hotel.lk", 0},
-#line 1100 "effective_tld_names.gperf"
- {"garden.museum", 0},
-#line 396 "effective_tld_names.gperf"
- {"bronnoy.no", 0},
-#line 176 "effective_tld_names.gperf"
- {"arendal.no", 0},
-#line 2514 "effective_tld_names.gperf"
- {"pe.ca", 0},
-#line 2305 "effective_tld_names.gperf"
- {"odessa.ua", 0},
-#line 1884 "effective_tld_names.gperf"
- {"media.hu", 0},
-#line 3020 "effective_tld_names.gperf"
- {"steinkjer.no", 0},
-#line 1547 "effective_tld_names.gperf"
- {"jan-mayen.no", 0},
-#line 1885 "effective_tld_names.gperf"
- {"media.museum", 0},
-#line 155 "effective_tld_names.gperf"
- {"antiques.museum", 0},
-#line 3016 "effective_tld_names.gperf"
- {"stavropol.ru", 0},
-#line 1009 "effective_tld_names.gperf"
- {"finearts.museum", 0},
-#line 1339 "effective_tld_names.gperf"
- {"gyeongnam.kr", 0},
-#line 244 "effective_tld_names.gperf"
- {"author.aero", 0},
-#line 2897 "effective_tld_names.gperf"
- {"sf.no", 0},
-#line 1764 "effective_tld_names.gperf"
- {"lindas.no", 0},
-#line 951 "effective_tld_names.gperf"
- {"essex.museum", 0},
-#line 3006 "effective_tld_names.gperf"
- {"stargard.pl", 0},
-#line 1859 "effective_tld_names.gperf"
- {"mazury.pl", 0},
-#line 2017 "effective_tld_names.gperf"
- {"muncie.museum", 0},
-#line 312 "effective_tld_names.gperf"
- {"bergbau.museum", 0},
-#line 3037 "effective_tld_names.gperf"
- {"student.aero", 0},
-#line 2254 "effective_tld_names.gperf"
- {"nord-odal.no", 0},
-#line 1893 "effective_tld_names.gperf"
- {"meloy.no", 0},
-#line 402 "effective_tld_names.gperf"
- {"bruxelles.museum", 0},
-#line 1051 "effective_tld_names.gperf"
- {"fortworth.museum", 0},
-#line 2686 "effective_tld_names.gperf"
- {"pvt.ge", 0},
-#line 351 "effective_tld_names.gperf"
- {"bj", 0},
-#line 1835 "effective_tld_names.gperf"
- {"mandal.no", 0},
-#line 335 "effective_tld_names.gperf"
- {"bio.br", 0},
-#line 761 "effective_tld_names.gperf"
- {"de.us", 0},
-#line 1722 "effective_tld_names.gperf"
- {"lardal.no", 0},
-#line 2695 "effective_tld_names.gperf"
- {"qh.cn", 0},
-#line 1267 "effective_tld_names.gperf"
- {"gov.tj", 0},
-#line 2530 "effective_tld_names.gperf"
- {"pg.it", 0},
-#line 441 "effective_tld_names.gperf"
- {"canada.museum", 0},
-#line 896 "effective_tld_names.gperf"
- {"edu.tj", 0},
-#line 2047 "effective_tld_names.gperf"
- {"namdalseid.no", 0},
-#line 2189 "effective_tld_names.gperf"
- {"net.tj", 0},
-#line 2086 "effective_tld_names.gperf"
- {"nb.ca", 0},
-#line 1995 "effective_tld_names.gperf"
- {"moskenes.no", 0},
-#line 676 "effective_tld_names.gperf"
- {"com.tj", 0},
-#line 782 "effective_tld_names.gperf"
- {"dm", 0},
-#line 1042 "effective_tld_names.gperf"
- {"foggia.it", 0},
-#line 1806 "effective_tld_names.gperf"
- {"luroy.no", 0},
-#line 1475 "effective_tld_names.gperf"
- {"indianmarket.museum", 0},
-#line 1513 "effective_tld_names.gperf"
- {"int.tj", 0},
-#line 1956 "effective_tld_names.gperf"
- {"minnesota.museum", 0},
-#line 1354 "effective_tld_names.gperf"
- {"hammarfeasta.no", 0},
-#line 1322 "effective_tld_names.gperf"
- {"gs.vf.no", 0},
-#line 1291 "effective_tld_names.gperf"
- {"greta.fr", 0},
-#line 123 "effective_tld_names.gperf"
- {"algard.no", 0},
-#line 800 "effective_tld_names.gperf"
- {"dz", 0},
-#line 448 "effective_tld_names.gperf"
- {"casino.hu", 0},
-#line 805 "effective_tld_names.gperf"
- {"eastafrica.museum", 0},
-#line 2485 "effective_tld_names.gperf"
- {"p.se", 0},
-#line 1581 "effective_tld_names.gperf"
- {"js.cn", 0},
-#line 1454 "effective_tld_names.gperf"
- {"illustration.museum", 0},
-#line 2347 "effective_tld_names.gperf"
- {"oregon.museum", 0},
-#line 3017 "effective_tld_names.gperf"
- {"steam.museum", 0},
-#line 1378 "effective_tld_names.gperf"
- {"herad.no", 0},
-#line 944 "effective_tld_names.gperf"
- {"equipment.aero", 0},
-#line 2649 "effective_tld_names.gperf"
- {"pri.ee", 0},
-#line 2920 "effective_tld_names.gperf"
- {"sirdal.no", 0},
-#line 2969 "effective_tld_names.gperf"
- {"sondrio.it", 0},
-#line 476 "effective_tld_names.gperf"
- {"cherkassy.ua", 0},
-#line 2066 "effective_tld_names.gperf"
- {"narviika.no", 0},
-#line 1544 "effective_tld_names.gperf"
- {"j.bg", 0},
-#line 1934 "effective_tld_names.gperf"
- {"mil.lv", 0},
-#line 1586 "effective_tld_names.gperf"
- {"jur.pro", 0},
-#line 1411 "effective_tld_names.gperf"
- {"holtalen.no", 0},
-#line 329 "effective_tld_names.gperf"
- {"biella.it", 0},
-#line 378 "effective_tld_names.gperf"
- {"botanicgarden.museum", 0},
-#line 1111 "effective_tld_names.gperf"
- {"gdynia.pl", 0},
-#line 3044 "effective_tld_names.gperf"
- {"suldal.no", 0},
-#line 2445 "effective_tld_names.gperf"
- {"org.tj", 0},
-#line 1587 "effective_tld_names.gperf"
- {"jus.br", 0},
-#line 1317 "effective_tld_names.gperf"
- {"gs.st.no", 0},
-#line 1391 "effective_tld_names.gperf"
- {"history.museum", 0},
-#line 3225 "effective_tld_names.gperf"
- {"uk.com", 0},
-#line 66 "effective_tld_names.gperf"
- {"act.edu.au", 0},
-#line 397 "effective_tld_names.gperf"
- {"bronnoysund.no", 0},
-#line 701 "effective_tld_names.gperf"
- {"contemporaryart.museum", 0},
-#line 2586 "effective_tld_names.gperf"
- {"ppg.br", 0},
-#line 935 "effective_tld_names.gperf"
- {"engineer.aero", 0},
-#line 1723 "effective_tld_names.gperf"
- {"larsson.museum", 0},
-#line 2915 "effective_tld_names.gperf"
- {"sigdal.no", 0},
-#line 1003 "effective_tld_names.gperf"
- {"filatelia.museum", 0},
-#line 1784 "effective_tld_names.gperf"
- {"lorenskog.no", 0},
-#line 1993 "effective_tld_names.gperf"
- {"moscow.museum", 0},
-#line 722 "effective_tld_names.gperf"
- {"crafts.museum", 0},
-#line 979 "effective_tld_names.gperf"
- {"farmers.museum", 0},
-#line 2650 "effective_tld_names.gperf"
- {"principe.st", 0},
-#line 1050 "effective_tld_names.gperf"
- {"fortmissoula.museum", 0},
-#line 783 "effective_tld_names.gperf"
- {"dn.ua", 0},
-#line 297 "effective_tld_names.gperf"
- {"bd.se", 0},
-#line 793 "effective_tld_names.gperf"
- {"dr.na", 0},
-#line 520 "effective_tld_names.gperf"
- {"civilization.museum", 0},
-#line 151 "effective_tld_names.gperf"
- {"andoy.no", 0},
-#line 2933 "effective_tld_names.gperf"
- {"skjak.no", 0},
-#line 1769 "effective_tld_names.gperf"
- {"livinghistory.museum", 0},
-#line 2549 "effective_tld_names.gperf"
- {"pk", 0},
-#line 2016 "effective_tld_names.gperf"
- {"mulhouse.museum", 0},
-#line 2883 "effective_tld_names.gperf"
- {"sebastopol.ua", 0},
-#line 3634 "effective_tld_names.gperf"
- {"zachpomor.pl", 0},
-#line 375 "effective_tld_names.gperf"
- {"boston.museum", 0},
-#line 2252 "effective_tld_names.gperf"
- {"nord-aurdal.no", 0},
-#line 2231 "effective_tld_names.gperf"
- {"nls.uk", 1},
-#line 272 "effective_tld_names.gperf"
- {"balat.no", 0},
-#line 2577 "effective_tld_names.gperf"
- {"portland.museum", 0},
-#line 316 "effective_tld_names.gperf"
- {"berlin.museum", 0},
-#line 3260 "effective_tld_names.gperf"
- {"utazas.hu", 0},
-#line 2323 "effective_tld_names.gperf"
- {"omasvuotna.no", 0},
-#line 30 "effective_tld_names.gperf"
- {"ab.ca", 0},
-#line 2484 "effective_tld_names.gperf"
- {"p.bg", 0},
-#line 923 "effective_tld_names.gperf"
- {"elk.pl", 0},
-#line 1367 "effective_tld_names.gperf"
- {"hb.cn", 0},
-#line 2041 "effective_tld_names.gperf"
- {"nagano.jp", 2},
-#line 311 "effective_tld_names.gperf"
- {"bergamo.it", 0},
-#line 1044 "effective_tld_names.gperf"
- {"folldal.no", 0},
-#line 300 "effective_tld_names.gperf"
- {"beardu.no", 0},
-#line 916 "effective_tld_names.gperf"
- {"eidsberg.no", 0},
-#line 1860 "effective_tld_names.gperf"
- {"mb.ca", 0},
-#line 757 "effective_tld_names.gperf"
- {"dc.us", 0},
-#line 2467 "effective_tld_names.gperf"
- {"oskol.ru", 0},
-#line 3058 "effective_tld_names.gperf"
- {"svelvik.no", 0},
-#line 1765 "effective_tld_names.gperf"
- {"lindesnes.no", 0},
-#line 2074 "effective_tld_names.gperf"
- {"naturalhistory.museum", 0},
-#line 758 "effective_tld_names.gperf"
- {"ddr.museum", 0},
-#line 1292 "effective_tld_names.gperf"
- {"grimstad.no", 0},
-#line 1525 "effective_tld_names.gperf"
- {"irkutsk.ru", 0},
-#line 1728 "effective_tld_names.gperf"
- {"lavangen.no", 0},
-#line 141 "effective_tld_names.gperf"
- {"amsterdam.museum", 0},
-#line 2913 "effective_tld_names.gperf"
- {"siellak.no", 0},
-#line 1556 "effective_tld_names.gperf"
- {"jerusalem.museum", 0},
-#line 1576 "effective_tld_names.gperf"
- {"journal.aero", 0},
-#line 1045 "effective_tld_names.gperf"
- {"force.museum", 0},
-#line 1588 "effective_tld_names.gperf"
- {"jx.cn", 0},
-#line 2018 "effective_tld_names.gperf"
- {"muosat.no", 0},
-#line 1825 "effective_tld_names.gperf"
- {"magnitka.ru", 0},
-#line 1119 "effective_tld_names.gperf"
- {"geology.museum", 0},
-#line 2656 "effective_tld_names.gperf"
- {"pro", 0},
-#line 2808 "effective_tld_names.gperf"
- {"saintlouis.museum", 0},
-#line 2465 "effective_tld_names.gperf"
- {"osaka.jp", 2},
-#line 500 "effective_tld_names.gperf"
- {"circus.museum", 0},
-#line 437 "effective_tld_names.gperf"
- {"cambridge.museum", 0},
-#line 1305 "effective_tld_names.gperf"
- {"gs.fm.no", 0},
-#line 1302 "effective_tld_names.gperf"
- {"gs.ah.no", 0},
-#line 1763 "effective_tld_names.gperf"
- {"lincoln.museum", 0},
-#line 2550 "effective_tld_names.gperf"
- {"pl", 0},
-#line 1171 "effective_tld_names.gperf"
- {"gorge.museum", 0},
-#line 3021 "effective_tld_names.gperf"
- {"stjohn.museum", 0},
-#line 3647 "effective_tld_names.gperf"
- {"zoology.museum", 0},
-#line 905 "effective_tld_names.gperf"
- {"education.museum", 0},
-#line 768 "effective_tld_names.gperf"
- {"dep.no", 0},
-#line 1459 "effective_tld_names.gperf"
- {"imperia.it", 0},
-#line 2825 "effective_tld_names.gperf"
- {"sandnes.no", 0},
-#line 1114 "effective_tld_names.gperf"
- {"geelvinck.museum", 0},
-#line 3398 "effective_tld_names.gperf"
- {"workshop.museum", 0},
-#line 1360 "effective_tld_names.gperf"
- {"hareid.no", 0},
-#line 586 "effective_tld_names.gperf"
- {"columbia.museum", 0},
-#line 3010 "effective_tld_names.gperf"
- {"state.museum", 0},
-#line 204 "effective_tld_names.gperf"
- {"askim.no", 0},
-#line 1066 "effective_tld_names.gperf"
- {"freight.aero", 0},
-#line 2257 "effective_tld_names.gperf"
- {"nordre-land.no", 0},
-#line 3050 "effective_tld_names.gperf"
- {"surgut.ru", 0},
-#line 2813 "effective_tld_names.gperf"
- {"salem.museum", 0},
-#line 1020 "effective_tld_names.gperf"
- {"fj.cn", 0},
-#line 432 "effective_tld_names.gperf"
- {"cadaques.museum", 0},
-#line 1495 "effective_tld_names.gperf"
- {"info.tn", 0},
-#line 494 "effective_tld_names.gperf"
- {"chuvashia.ru", 0},
-#line 3237 "effective_tld_names.gperf"
- {"university.museum", 0},
-#line 294 "effective_tld_names.gperf"
- {"bb", 0},
-#line 1716 "effective_tld_names.gperf"
- {"lancashire.museum", 0},
-#line 3042 "effective_tld_names.gperf"
- {"suisse.museum", 0},
-#line 1779 "effective_tld_names.gperf"
- {"logistics.aero", 0},
-#line 1546 "effective_tld_names.gperf"
- {"jamison.museum", 0},
-#line 1801 "effective_tld_names.gperf"
- {"lucerne.museum", 0},
-#line 1284 "effective_tld_names.gperf"
- {"grajewo.pl", 0},
-#line 1346 "effective_tld_names.gperf"
- {"hadsel.no", 0},
-#line 404 "effective_tld_names.gperf"
- {"bryne.no", 0},
-#line 1059 "effective_tld_names.gperf"
- {"francaise.museum", 0},
-#line 1489 "effective_tld_names.gperf"
- {"info.nr", 0},
-#line 3071 "effective_tld_names.gperf"
- {"szczytno.pl", 0},
-#line 63 "effective_tld_names.gperf"
- {"academy.museum", 0},
-#line 2060 "effective_tld_names.gperf"
- {"namsskogan.no", 0},
-#line 189 "effective_tld_names.gperf"
- {"artdeco.museum", 0},
-#line 2945 "effective_tld_names.gperf"
- {"slupsk.pl", 0},
-#line 206 "effective_tld_names.gperf"
- {"askvoll.no", 0},
-#line 1782 "effective_tld_names.gperf"
- {"london.museum", 0},
-#line 1073 "effective_tld_names.gperf"
- {"frosta.no", 0},
-#line 2815 "effective_tld_names.gperf"
- {"saltdal.no", 0},
-#line 3220 "effective_tld_names.gperf"
- {"udmurtia.ru", 0},
-#line 175 "effective_tld_names.gperf"
- {"aremark.no", 0},
-#line 111 "effective_tld_names.gperf"
- {"aknoluokta.no", 0},
-#line 1830 "effective_tld_names.gperf"
- {"mallorca.museum", 0},
-#line 691 "effective_tld_names.gperf"
- {"como.it", 0},
-#line 1349 "effective_tld_names.gperf"
- {"halloffame.museum", 0},
-#line 1104 "effective_tld_names.gperf"
- {"gb.com", 0},
-#line 2255 "effective_tld_names.gperf"
- {"norddal.no", 0},
-#line 2499 "effective_tld_names.gperf"
- {"panama.museum", 0},
-#line 81 "effective_tld_names.gperf"
- {"aerodrome.aero", 0},
-#line 1440 "effective_tld_names.gperf"
- {"icnet.uk", 1},
-#line 2663 "effective_tld_names.gperf"
- {"pro.tt", 0},
-#line 2495 "effective_tld_names.gperf"
- {"palana.ru", 0},
-#line 1892 "effective_tld_names.gperf"
- {"melhus.no", 0},
-#line 1496 "effective_tld_names.gperf"
- {"info.tt", 0},
-#line 3376 "effective_tld_names.gperf"
- {"waw.pl", 0},
-#line 2925 "effective_tld_names.gperf"
- {"skaun.no", 0},
-#line 1178 "effective_tld_names.gperf"
- {"gouv.km", 0},
-#line 1055 "effective_tld_names.gperf"
- {"foundation.museum", 0},
-#line 966 "effective_tld_names.gperf"
- {"exchange.aero", 0},
-#line 2803 "effective_tld_names.gperf"
- {"sa.edu.au", 0},
-#line 498 "effective_tld_names.gperf"
- {"cincinnati.museum", 0},
-#line 395 "effective_tld_names.gperf"
- {"broker.aero", 0},
-#line 1762 "effective_tld_names.gperf"
- {"limanowa.pl", 0},
-#line 2291 "effective_tld_names.gperf"
- {"nuremberg.museum", 0},
-#line 3368 "effective_tld_names.gperf"
- {"wales.museum", 0},
-#line 1176 "effective_tld_names.gperf"
- {"gouv.fr", 0},
-#line 1531 "effective_tld_names.gperf"
- {"ishikawa.jp", 2},
-#line 2215 "effective_tld_names.gperf"
- {"nhs.uk", 1},
-#line 1371 "effective_tld_names.gperf"
- {"heimatunduhren.museum", 0},
-#line 3057 "effective_tld_names.gperf"
- {"sveio.no", 0},
-#line 181 "effective_tld_names.gperf"
- {"arq.br", 0},
-#line 403 "effective_tld_names.gperf"
- {"bryansk.ru", 0},
-#line 2482 "effective_tld_names.gperf"
- {"oygarden.no", 0},
-#line 452 "effective_tld_names.gperf"
- {"catania.it", 0},
-#line 280 "effective_tld_names.gperf"
- {"bamble.no", 0},
-#line 1711 "effective_tld_names.gperf"
- {"labour.museum", 0},
-#line 1778 "effective_tld_names.gperf"
- {"lodingen.no", 0},
-#line 3363 "effective_tld_names.gperf"
- {"wa.edu.au", 0},
-#line 2912 "effective_tld_names.gperf"
- {"sibenik.museum", 0},
-#line 1852 "effective_tld_names.gperf"
- {"masoy.no", 0},
-#line 129 "effective_tld_names.gperf"
- {"alvdal.no", 0},
-#line 2025 "effective_tld_names.gperf"
- {"museumcenter.museum", 0},
-#line 373 "effective_tld_names.gperf"
- {"bomlo.no", 0},
-#line 29 "effective_tld_names.gperf"
- {"aarborte.no", 0},
-#line 80 "effective_tld_names.gperf"
- {"aeroclub.aero", 0},
-#line 792 "effective_tld_names.gperf"
- {"dp.ua", 0},
-#line 203 "effective_tld_names.gperf"
- {"asker.no", 0},
-#line 585 "effective_tld_names.gperf"
- {"coloradoplateau.museum", 0},
-#line 1494 "effective_tld_names.gperf"
- {"info.sd", 0},
-#line 2015 "effective_tld_names.gperf"
- {"muenster.museum", 0},
-#line 742 "effective_tld_names.gperf"
- {"cymru.museum", 0},
-#line 2664 "effective_tld_names.gperf"
- {"pro.vn", 0},
-#line 3638 "effective_tld_names.gperf"
- {"zarow.pl", 0},
-#line 2551 "effective_tld_names.gperf"
- {"pl.ua", 0},
-#line 3381 "effective_tld_names.gperf"
- {"web.tj", 0},
-#line 488 "effective_tld_names.gperf"
- {"chita.ru", 0},
-#line 472 "effective_tld_names.gperf"
- {"chattanooga.museum", 0},
-#line 2965 "effective_tld_names.gperf"
- {"sologne.museum", 0},
-#line 481 "effective_tld_names.gperf"
- {"chicago.museum", 0},
-#line 2811 "effective_tld_names.gperf"
- {"salangen.no", 0},
-#line 937 "effective_tld_names.gperf"
- {"enna.it", 0},
-#line 2322 "effective_tld_names.gperf"
- {"omaha.museum", 0},
-#line 236 "effective_tld_names.gperf"
- {"aukra.no", 0},
-#line 1166 "effective_tld_names.gperf"
- {"gobiernoelectronico.ar", 1},
-#line 2259 "effective_tld_names.gperf"
- {"nore-og-uvdal.no", 0},
-#line 2531 "effective_tld_names.gperf"
- {"ph", 0},
-#line 2976 "effective_tld_names.gperf"
- {"sorfold.no", 0},
-#line 78 "effective_tld_names.gperf"
- {"aero.tt", 0},
-#line 1294 "effective_tld_names.gperf"
- {"grosseto.it", 0},
-#line 1482 "effective_tld_names.gperf"
- {"info.ec", 0},
-#line 2477 "effective_tld_names.gperf"
- {"other.nf", 0},
-#line 1558 "effective_tld_names.gperf"
- {"jet.uk", 1},
-#line 3382 "effective_tld_names.gperf"
- {"wegrow.pl", 0},
-#line 112 "effective_tld_names.gperf"
- {"akrehamn.no", 0},
-#line 3612 "effective_tld_names.gperf"
- {"yamagata.jp", 2},
-#line 31 "effective_tld_names.gperf"
- {"abo.pa", 0},
-#line 1568 "effective_tld_names.gperf"
- {"jobs", 0},
-#line 442 "effective_tld_names.gperf"
- {"capebreton.museum", 0},
-#line 1814 "effective_tld_names.gperf"
- {"lyngdal.no", 0},
-#line 3030 "effective_tld_names.gperf"
- {"store.ro", 0},
-#line 921 "effective_tld_names.gperf"
- {"elblag.pl", 0},
-#line 1128 "effective_tld_names.gperf"
- {"giessen.museum", 0},
-#line 991 "effective_tld_names.gperf"
- {"fetsund.no", 0},
-#line 3636 "effective_tld_names.gperf"
- {"zakopane.pl", 0},
-#line 3384 "effective_tld_names.gperf"
- {"westfalen.museum", 0},
#line 1145 "effective_tld_names.gperf"
- {"gmina.pl", 0},
-#line 376 "effective_tld_names.gperf"
- {"botanical.museum", 0},
-#line 3238 "effective_tld_names.gperf"
- {"unjarga.no", 0},
-#line 3031 "effective_tld_names.gperf"
- {"store.st", 0},
-#line 2827 "effective_tld_names.gperf"
- {"sandoy.no", 0},
-#line 748 "effective_tld_names.gperf"
- {"d.se", 0},
-#line 269 "effective_tld_names.gperf"
- {"baidar.no", 0},
-#line 435 "effective_tld_names.gperf"
- {"california.museum", 0},
-#line 1987 "effective_tld_names.gperf"
- {"money.museum", 0},
-#line 1518 "effective_tld_names.gperf"
- {"intl.tn", 0},
-#line 2202 "effective_tld_names.gperf"
- {"newjersey.museum", 0},
-#line 965 "effective_tld_names.gperf"
- {"evje-og-hornnes.no", 0},
-#line 1046 "effective_tld_names.gperf"
- {"forde.no", 0},
-#line 2658 "effective_tld_names.gperf"
- {"pro.br", 0},
-#line 353 "effective_tld_names.gperf"
- {"bjarkoy.no", 0},
-#line 2687 "effective_tld_names.gperf"
- {"pw", 0},
-#line 3641 "effective_tld_names.gperf"
- {"zgrad.ru", 0},
-#line 1985 "effective_tld_names.gperf"
- {"molde.no", 0},
-#line 2970 "effective_tld_names.gperf"
- {"songdalen.no", 0},
-#line 1578 "effective_tld_names.gperf"
- {"journalist.aero", 0},
-#line 2494 "effective_tld_names.gperf"
- {"palace.museum", 0},
-#line 491 "effective_tld_names.gperf"
- {"chukotka.ru", 0},
-#line 338 "effective_tld_names.gperf"
- {"birkenes.no", 0},
-#line 2662 "effective_tld_names.gperf"
- {"pro.pr", 0},
-#line 3252 "effective_tld_names.gperf"
- {"usenet.pl", 0},
-#line 1729 "effective_tld_names.gperf"
- {"law.pro", 0},
-#line 392 "effective_tld_names.gperf"
- {"british.museum", 0},
-#line 3622 "effective_tld_names.gperf"
- {"york.museum", 0},
-#line 1094 "effective_tld_names.gperf"
- {"galsa.no", 0},
-#line 1350 "effective_tld_names.gperf"
- {"halsa.no", 0},
-#line 518 "effective_tld_names.gperf"
- {"civilaviation.aero", 0},
-#line 1912 "effective_tld_names.gperf"
- {"mielno.pl", 0},
-#line 3232 "effective_tld_names.gperf"
- {"ulvik.no", 0},
-#line 1858 "effective_tld_names.gperf"
- {"mazowsze.pl", 0},
-#line 1959 "effective_tld_names.gperf"
- {"miyagi.jp", 2},
-#line 1034 "effective_tld_names.gperf"
- {"florence.it", 0},
-#line 2930 "effective_tld_names.gperf"
- {"skien.no", 0},
-#line 2476 "effective_tld_names.gperf"
- {"otago.museum", 0},
-#line 1337 "effective_tld_names.gperf"
- {"gyeongbuk.kr", 0},
-#line 2039 "effective_tld_names.gperf"
- {"naamesjevuemie.no", 0},
-#line 1717 "effective_tld_names.gperf"
- {"landes.museum", 0},
-#line 1117 "effective_tld_names.gperf"
- {"genoa.it", 0},
-#line 390 "effective_tld_names.gperf"
- {"bristol.museum", 0},
-#line 84 "effective_tld_names.gperf"
- {"afjord.no", 0},
-#line 939 "effective_tld_names.gperf"
- {"entertainment.aero", 0},
-#line 2492 "effective_tld_names.gperf"
- {"padova.it", 0},
-#line 3028 "effective_tld_names.gperf"
- {"stordal.no", 0},
-#line 225 "effective_tld_names.gperf"
- {"asti.it", 0},
-#line 2947 "effective_tld_names.gperf"
- {"smola.no", 0},
-#line 1978 "effective_tld_names.gperf"
- {"mobi.tt", 0},
-#line 2661 "effective_tld_names.gperf"
- {"pro.na", 0},
-#line 365 "effective_tld_names.gperf"
- {"bo.nordland.no", 0},
-#line 1538 "effective_tld_names.gperf"
- {"ivanovo.ru", 0},
-#line 3389 "effective_tld_names.gperf"
- {"wildlife.museum", 0},
-#line 3430 "effective_tld_names.gperf"
- {"xn--bod-2na.no", 0},
-#line 2479 "effective_tld_names.gperf"
- {"ovre-eiker.no", 0},
-#line 3623 "effective_tld_names.gperf"
- {"yorkshire.museum", 0},
-#line 904 "effective_tld_names.gperf"
- {"educ.ar", 1},
-#line 1488 "effective_tld_names.gperf"
- {"info.nf", 0},
-#line 1781 "effective_tld_names.gperf"
- {"lomza.pl", 0},
-#line 1028 "effective_tld_names.gperf"
- {"flatanger.no", 0},
-#line 190 "effective_tld_names.gperf"
- {"arteducation.museum", 0},
-#line 2458 "effective_tld_names.gperf"
- {"orkdal.no", 0},
-#line 762 "effective_tld_names.gperf"
- {"deatnu.no", 0},
-#line 1473 "effective_tld_names.gperf"
- {"indiana.museum", 0},
-#line 126 "effective_tld_names.gperf"
- {"altai.ru", 0},
-#line 780 "effective_tld_names.gperf"
- {"dk", 0},
-#line 389 "effective_tld_names.gperf"
- {"brindisi.it", 0},
-#line 187 "effective_tld_names.gperf"
- {"artanddesign.museum", 0},
-#line 1847 "effective_tld_names.gperf"
- {"marketplace.aero", 0},
-#line 2971 "effective_tld_names.gperf"
- {"sopot.pl", 0},
-#line 2576 "effective_tld_names.gperf"
- {"portal.museum", 0},
-#line 2952 "effective_tld_names.gperf"
- {"snasa.no", 0},
-#line 930 "effective_tld_names.gperf"
- {"enebakk.no", 0},
-#line 2557 "effective_tld_names.gperf"
- {"plc.ly", 0},
-#line 2840 "effective_tld_names.gperf"
- {"sauda.no", 0},
-#line 334 "effective_tld_names.gperf"
- {"bindal.no", 0},
-#line 1945 "effective_tld_names.gperf"
- {"mil.tj", 0},
-#line 1419 "effective_tld_names.gperf"
- {"house.museum", 0},
-#line 2556 "effective_tld_names.gperf"
- {"plc.co.im", 0},
-#line 1082 "effective_tld_names.gperf"
- {"fuossko.no", 0},
-#line 747 "effective_tld_names.gperf"
- {"d.bg", 0},
-#line 1412 "effective_tld_names.gperf"
- {"homebuilt.aero", 0},
-#line 1715 "effective_tld_names.gperf"
- {"lanbib.se", 0},
-#line 2075 "effective_tld_names.gperf"
- {"naturalhistorymuseum.museum", 0},
-#line 105 "effective_tld_names.gperf"
- {"airguard.museum", 0},
-#line 2948 "effective_tld_names.gperf"
- {"smolensk.ru", 0},
-#line 1540 "effective_tld_names.gperf"
- {"ivgu.no", 0},
-#line 765 "effective_tld_names.gperf"
- {"delaware.museum", 0},
-#line 252 "effective_tld_names.gperf"
- {"avoues.fr", 0},
-#line 2528 "effective_tld_names.gperf"
- {"pf", 0},
-#line 2544 "effective_tld_names.gperf"
- {"pilots.museum", 0},
-#line 1395 "effective_tld_names.gperf"
- {"hjelmeland.no", 0},
-#line 400 "effective_tld_names.gperf"
- {"brussel.museum", 0},
-#line 1103 "effective_tld_names.gperf"
- {"gausdal.no", 0},
-#line 2320 "effective_tld_names.gperf"
- {"olsztyn.pl", 0},
-#line 1285 "effective_tld_names.gperf"
- {"gran.no", 0},
-#line 251 "effective_tld_names.gperf"
- {"avocat.fr", 0},
-#line 223 "effective_tld_names.gperf"
- {"association.aero", 0},
-#line 2986 "effective_tld_names.gperf"
- {"space.museum", 0},
-#line 2659 "effective_tld_names.gperf"
- {"pro.ec", 0},
-#line 230 "effective_tld_names.gperf"
- {"atlanta.museum", 0},
-#line 275 "effective_tld_names.gperf"
- {"ballangen.no", 0},
-#line 3026 "effective_tld_names.gperf"
- {"stor-elvdal.no", 0},
-#line 352 "effective_tld_names.gperf"
- {"bj.cn", 0},
-#line 3249 "effective_tld_names.gperf"
- {"uscountryestate.museum", 0},
-#line 1911 "effective_tld_names.gperf"
- {"mielec.pl", 0},
-#line 3251 "effective_tld_names.gperf"
- {"usdecorativearts.museum", 0},
-#line 1290 "effective_tld_names.gperf"
- {"graz.museum", 0},
-#line 2982 "effective_tld_names.gperf"
- {"soundandvision.museum", 0},
-#line 1709 "effective_tld_names.gperf"
- {"laakesvuemie.no", 0},
-#line 1481 "effective_tld_names.gperf"
- {"info.co", 0},
-#line 1545 "effective_tld_names.gperf"
- {"jamal.ru", 0},
-#line 915 "effective_tld_names.gperf"
- {"eidfjord.no", 0},
-#line 277 "effective_tld_names.gperf"
- {"balsan.it", 0},
-#line 2043 "effective_tld_names.gperf"
- {"nagoya.jp", 2},
-#line 1991 "effective_tld_names.gperf"
- {"monza.it", 0},
-#line 1618 "effective_tld_names.gperf"
- {"ke", 2},
-#line 3409 "effective_tld_names.gperf"
- {"xn--55qx5d.cn", 0},
-#line 1700 "effective_tld_names.gperf"
- {"ky", 0},
-#line 401 "effective_tld_names.gperf"
- {"brussels.museum", 0},
-#line 120 "effective_tld_names.gperf"
- {"alaska.museum", 0},
-#line 754 "effective_tld_names.gperf"
- {"database.museum", 0},
-#line 1666 "effective_tld_names.gperf"
- {"kr", 0},
-#line 1021 "effective_tld_names.gperf"
- {"fjaler.no", 0},
-#line 2832 "effective_tld_names.gperf"
- {"santafe.museum", 0},
-#line 447 "effective_tld_names.gperf"
- {"caserta.it", 0},
-#line 1986 "effective_tld_names.gperf"
- {"moma.museum", 0},
-#line 1646 "effective_tld_names.gperf"
- {"kn", 0},
-#line 993 "effective_tld_names.gperf"
- {"fh.se", 0},
-#line 942 "effective_tld_names.gperf"
- {"environmentalconservation.museum", 0},
-#line 3073 "effective_tld_names.gperf"
- {"szkola.pl", 0},
-#line 1836 "effective_tld_names.gperf"
- {"mansion.museum", 0},
-#line 2204 "effective_tld_names.gperf"
- {"newport.museum", 0},
-#line 438 "effective_tld_names.gperf"
- {"campobasso.it", 0},
-#line 760 "effective_tld_names.gperf"
- {"de.com", 0},
-#line 981 "effective_tld_names.gperf"
- {"farsund.no", 0},
-#line 918 "effective_tld_names.gperf"
- {"eidsvoll.no", 0},
-#line 2262 "effective_tld_names.gperf"
- {"north.museum", 0},
-#line 1327 "effective_tld_names.gperf"
- {"guernsey.museum", 0},
-#line 1329 "effective_tld_names.gperf"
- {"gunma.jp", 2},
-#line 2892 "effective_tld_names.gperf"
- {"services.aero", 0},
-#line 1988 "effective_tld_names.gperf"
- {"monmouth.museum", 0},
-#line 3635 "effective_tld_names.gperf"
- {"zagan.pl", 0},
-#line 989 "effective_tld_names.gperf"
- {"ferrara.it", 0},
-#line 794 "effective_tld_names.gperf"
- {"drammen.no", 0},
-#line 3625 "effective_tld_names.gperf"
- {"youth.museum", 0},
-#line 221 "effective_tld_names.gperf"
- {"asso.mc", 0},
-#line 1530 "effective_tld_names.gperf"
- {"isernia.it", 0},
-#line 2968 "effective_tld_names.gperf"
- {"sondre-land.no", 0},
-#line 2823 "effective_tld_names.gperf"
- {"sandefjord.no", 0},
-#line 917 "effective_tld_names.gperf"
- {"eidskog.no", 0},
-#line 692 "effective_tld_names.gperf"
- {"computer.museum", 0},
-#line 436 "effective_tld_names.gperf"
- {"caltanissetta.it", 0},
-#line 1837 "effective_tld_names.gperf"
- {"mansions.museum", 0},
-#line 3640 "effective_tld_names.gperf"
- {"zgorzelec.pl", 0},
-#line 1381 "effective_tld_names.gperf"
- {"heroy.nordland.no", 0},
-#line 1523 "effective_tld_names.gperf"
- {"iraq.museum", 0},
-#line 220 "effective_tld_names.gperf"
- {"asso.km", 0},
-#line 1132 "effective_tld_names.gperf"
- {"gjemnes.no", 0},
-#line 1366 "effective_tld_names.gperf"
- {"hawaii.museum", 0},
-#line 3552 "effective_tld_names.gperf"
- {"xn--sandy-yua.no", 0},
-#line 3424 "effective_tld_names.gperf"
- {"xn--bidr-5nac.no", 0},
-#line 2457 "effective_tld_names.gperf"
- {"orkanger.no", 0},
-#line 2874 "effective_tld_names.gperf"
- {"scientist.aero", 0},
-#line 3216 "effective_tld_names.gperf"
- {"uba.ar", 1},
-#line 912 "effective_tld_names.gperf"
- {"egyptian.museum", 0},
-#line 1299 "effective_tld_names.gperf"
- {"grue.no", 0},
-#line 306 "effective_tld_names.gperf"
- {"belgorod.ru", 0},
-#line 3400 "effective_tld_names.gperf"
- {"wroclaw.pl", 0},
-#line 2963 "effective_tld_names.gperf"
- {"sokndal.no", 0},
-#line 2318 "effective_tld_names.gperf"
- {"olecko.pl", 0},
-#line 217 "effective_tld_names.gperf"
- {"asso.fr", 0},
-#line 2057 "effective_tld_names.gperf"
- {"name.tt", 0},
-#line 1831 "effective_tld_names.gperf"
- {"malopolska.pl", 0},
-#line 2271 "effective_tld_names.gperf"
- {"nowaruda.pl", 0},
-#line 276 "effective_tld_names.gperf"
- {"ballooning.aero", 0},
-#line 1701 "effective_tld_names.gperf"
- {"ky.us", 0},
-#line 2469 "effective_tld_names.gperf"
- {"osoyro.no", 0},
-#line 2468 "effective_tld_names.gperf"
- {"oslo.no", 0},
-#line 2258 "effective_tld_names.gperf"
- {"nordreisa.no", 0},
-#line 2329 "effective_tld_names.gperf"
- {"opoczno.pl", 0},
-#line 2497 "effective_tld_names.gperf"
- {"palermo.it", 0},
-#line 1559 "effective_tld_names.gperf"
- {"jevnaker.no", 0},
-#line 235 "effective_tld_names.gperf"
- {"augustow.pl", 0},
-#line 2864 "effective_tld_names.gperf"
- {"schweiz.museum", 0},
-#line 2891 "effective_tld_names.gperf"
- {"seoul.kr", 0},
-#line 179 "effective_tld_names.gperf"
- {"arna.no", 0},
-#line 2517 "effective_tld_names.gperf"
- {"penza.ru", 0},
-#line 1643 "effective_tld_names.gperf"
- {"km", 0},
-#line 1808 "effective_tld_names.gperf"
- {"lutsk.ua", 0},
-#line 1904 "effective_tld_names.gperf"
- {"miasta.pl", 0},
-#line 1565 "effective_tld_names.gperf"
- {"jl.cn", 0},
-#line 279 "effective_tld_names.gperf"
- {"baltimore.museum", 0},
-#line 492 "effective_tld_names.gperf"
- {"chungbuk.kr", 0},
-#line 302 "effective_tld_names.gperf"
- {"bedzin.pl", 0},
-#line 106 "effective_tld_names.gperf"
- {"airline.aero", 0},
-#line 804 "effective_tld_names.gperf"
- {"e164.arpa", 0},
-#line 1577 "effective_tld_names.gperf"
- {"journalism.museum", 0},
-#line 1679 "effective_tld_names.gperf"
- {"ks.us", 0},
-#line 1703 "effective_tld_names.gperf"
- {"kz", 0},
-#line 521 "effective_tld_names.gperf"
- {"civilwar.museum", 0},
-#line 3001 "effective_tld_names.gperf"
- {"stadt.museum", 0},
-#line 1179 "effective_tld_names.gperf"
- {"gouv.rw", 0},
-#line 108 "effective_tld_names.gperf"
- {"airtraffic.aero", 0},
-#line 806 "effective_tld_names.gperf"
- {"eastcoast.museum", 0},
-#line 1849 "effective_tld_names.gperf"
- {"maryland.museum", 0},
-#line 2989 "effective_tld_names.gperf"
- {"sport.hu", 0},
-#line 290 "effective_tld_names.gperf"
- {"bashkiria.ru", 0},
-#line 289 "effective_tld_names.gperf"
- {"basel.museum", 0},
-#line 2053 "effective_tld_names.gperf"
- {"name.my", 0},
-#line 1406 "effective_tld_names.gperf"
- {"hokkaido.jp", 2},
-#line 267 "effective_tld_names.gperf"
- {"bahccavuotna.no", 0},
+ {"giessen.museum", 0},
+#line 3670 "effective_tld_names.gperf"
+ {"y.bg", 0},
+#line 1573 "effective_tld_names.gperf"
+ {"j.bg", 0},
+#line 529 "effective_tld_names.gperf"
+ {"civilization.museum", 0},
#line 2304 "effective_tld_names.gperf"
- {"odda.no", 0},
-#line 3038 "effective_tld_names.gperf"
- {"stuttgart.museum", 0},
-#line 224 "effective_tld_names.gperf"
- {"association.museum", 0},
-#line 65 "effective_tld_names.gperf"
- {"accident-prevention.aero", 0},
-#line 2100 "effective_tld_names.gperf"
- {"nes.akershus.no", 0},
-#line 387 "effective_tld_names.gperf"
- {"bremanger.no", 0},
-#line 3371 "effective_tld_names.gperf"
+ {"notteroy.no", 0},
+#line 528 "effective_tld_names.gperf"
+ {"civilisation.museum", 0},
+#line 1364 "effective_tld_names.gperf"
+ {"h.bg", 0},
+#line 1104 "effective_tld_names.gperf"
+ {"g.bg", 0},
+#line 3426 "effective_tld_names.gperf"
{"warmia.pl", 0},
-#line 1834 "effective_tld_names.gperf"
- {"manchester.museum", 0},
-#line 1908 "effective_tld_names.gperf"
- {"midsund.no", 0},
-#line 3404 "effective_tld_names.gperf"
- {"www.ro", 0},
-#line 1338 "effective_tld_names.gperf"
- {"gyeonggi.kr", 0},
-#line 2926 "effective_tld_names.gperf"
- {"skedsmo.no", 0},
-#line 1136 "effective_tld_names.gperf"
- {"gjovik.no", 0},
-#line 2866 "effective_tld_names.gperf"
- {"science.museum", 0},
-#line 728 "effective_tld_names.gperf"
- {"crotone.it", 0},
-#line 1851 "effective_tld_names.gperf"
- {"masfjorden.no", 0},
-#line 1493 "effective_tld_names.gperf"
- {"info.ro", 0},
-#line 3255 "effective_tld_names.gperf"
- {"ushuaia.museum", 0},
-#line 1886 "effective_tld_names.gperf"
- {"media.pl", 0},
-#line 1026 "effective_tld_names.gperf"
- {"flakstad.no", 0},
-#line 1669 "effective_tld_names.gperf"
- {"kr.ua", 0},
-#line 3267 "effective_tld_names.gperf"
- {"uzhgorod.ua", 0},
-#line 1593 "effective_tld_names.gperf"
- {"k12.vi", 0},
-#line 1516 "effective_tld_names.gperf"
- {"intelligence.museum", 0},
-#line 2683 "effective_tld_names.gperf"
- {"pubol.museum", 0},
-#line 1095 "effective_tld_names.gperf"
- {"game.tw", 0},
-#line 327 "effective_tld_names.gperf"
- {"bible.museum", 0},
-#line 1776 "effective_tld_names.gperf"
- {"localhistory.museum", 0},
-#line 2098 "effective_tld_names.gperf"
- {"nedre-eiker.no", 0},
-#line 3539 "effective_tld_names.gperf"
- {"xn--risa-5na.no", 0},
-#line 1067 "effective_tld_names.gperf"
- {"fribourg.museum", 0},
-#line 2582 "effective_tld_names.gperf"
- {"poznan.pl", 0},
-#line 2464 "effective_tld_names.gperf"
- {"os.hordaland.no", 0},
-#line 770 "effective_tld_names.gperf"
- {"design.aero", 0},
-#line 1002 "effective_tld_names.gperf"
- {"figueres.museum", 0},
-#line 3532 "effective_tld_names.gperf"
- {"xn--rady-ira.no", 0},
-#line 1678 "effective_tld_names.gperf"
- {"ks.ua", 0},
-#line 308 "effective_tld_names.gperf"
- {"belluno.it", 0},
-#line 943 "effective_tld_names.gperf"
- {"epilepsy.museum", 0},
-#line 2872 "effective_tld_names.gperf"
- {"sciences.museum", 0},
-#line 421 "effective_tld_names.gperf"
- {"bytom.pl", 0},
-#line 205 "effective_tld_names.gperf"
- {"askoy.no", 0},
-#line 3506 "effective_tld_names.gperf"
- {"xn--lury-ira.no", 0},
-#line 1115 "effective_tld_names.gperf"
- {"gemological.museum", 0},
-#line 3540 "effective_tld_names.gperf"
- {"xn--risr-ira.no", 0},
-#line 3397 "effective_tld_names.gperf"
- {"works.aero", 0},
-#line 1714 "effective_tld_names.gperf"
- {"lakas.hu", 0},
-#line 2046 "effective_tld_names.gperf"
- {"nalchik.ru", 0},
-#line 3567 "effective_tld_names.gperf"
- {"xn--snsa-roa.no", 0},
-#line 1062 "effective_tld_names.gperf"
- {"fredrikstad.no", 0},
-#line 958 "effective_tld_names.gperf"
- {"etne.no", 0},
-#line 1632 "effective_tld_names.gperf"
- {"ki", 0},
-#line 1668 "effective_tld_names.gperf"
- {"kr.it", 0},
-#line 288 "effective_tld_names.gperf"
- {"baseball.museum", 0},
-#line 3499 "effective_tld_names.gperf"
- {"xn--linds-pra.no", 0},
-#line 3133 "effective_tld_names.gperf"
- {"to", 0},
-#line 3090 "effective_tld_names.gperf"
- {"td", 0},
-#line 1195 "effective_tld_names.gperf"
- {"gov.bz", 0},
-#line 3536 "effective_tld_names.gperf"
- {"xn--rennesy-v1a.no", 0},
-#line 1409 "effective_tld_names.gperf"
- {"hole.no", 0},
-#line 3007 "effective_tld_names.gperf"
- {"starnberg.museum", 0},
-#line 828 "effective_tld_names.gperf"
- {"edu.bz", 0},
-#line 2122 "effective_tld_names.gperf"
- {"net.bz", 0},
-#line 3155 "effective_tld_names.gperf"
- {"tr", 2},
-#line 337 "effective_tld_names.gperf"
- {"birdart.museum", 0},
-#line 1870 "effective_tld_names.gperf"
- {"mecon.ar", 1},
-#line 606 "effective_tld_names.gperf"
- {"com.bz", 0},
-#line 1799 "effective_tld_names.gperf"
- {"lubin.pl", 0},
-#line 3477 "effective_tld_names.gperf"
- {"xn--karmy-yua.no", 0},
-#line 142 "effective_tld_names.gperf"
- {"amur.ru", 0},
-#line 3130 "effective_tld_names.gperf"
- {"tn", 0},
-#line 332 "effective_tld_names.gperf"
- {"bilbao.museum", 0},
-#line 1981 "effective_tld_names.gperf"
- {"modelling.aero", 0},
-#line 2584 "effective_tld_names.gperf"
- {"pp.ru", 0},
-#line 2566 "effective_tld_names.gperf"
- {"pol.ht", 0},
-#line 154 "effective_tld_names.gperf"
- {"anthropology.museum", 0},
-#line 1690 "effective_tld_names.gperf"
- {"kv.ua", 0},
-#line 3257 "effective_tld_names.gperf"
- {"ustka.pl", 0},
-#line 282 "effective_tld_names.gperf"
- {"barcelona.museum", 0},
-#line 980 "effective_tld_names.gperf"
- {"farmstead.museum", 0},
-#line 1828 "effective_tld_names.gperf"
- {"malatvuopmi.no", 0},
-#line 579 "effective_tld_names.gperf"
- {"coal.museum", 0},
-#line 3187 "effective_tld_names.gperf"
- {"tt", 0},
-#line 3018 "effective_tld_names.gperf"
- {"steiermark.museum", 0},
-#line 237 "effective_tld_names.gperf"
- {"aure.no", 0},
-#line 1069 "effective_tld_names.gperf"
- {"frogn.no", 0},
-#line 1822 "effective_tld_names.gperf"
- {"madrid.museum", 0},
-#line 1316 "effective_tld_names.gperf"
- {"gs.sf.no", 0},
-#line 1644 "effective_tld_names.gperf"
- {"km.ua", 0},
-#line 2282 "effective_tld_names.gperf"
- {"nt.gov.au", 0},
-#line 2289 "effective_tld_names.gperf"
- {"nuernberg.museum", 0},
-#line 1894 "effective_tld_names.gperf"
- {"memorial.museum", 0},
-#line 3412 "effective_tld_names.gperf"
- {"xn--andy-ira.no", 0},
-#line 2265 "effective_tld_names.gperf"
- {"notaires.km", 0},
-#line 1571 "effective_tld_names.gperf"
- {"jolster.no", 0},
-#line 2699 "effective_tld_names.gperf"
- {"quebec.museum", 0},
-#line 2829 "effective_tld_names.gperf"
- {"sanok.pl", 0},
-#line 707 "effective_tld_names.gperf"
- {"coop.km", 0},
-#line 1853 "effective_tld_names.gperf"
- {"massa-carrara.it", 0},
-#line 1622 "effective_tld_names.gperf"
- {"kg", 0},
-#line 384 "effective_tld_names.gperf"
- {"brand.se", 0},
-#line 2766 "effective_tld_names.gperf"
- {"ro", 0},
-#line 2223 "effective_tld_names.gperf"
- {"nikolaev.ua", 0},
-#line 2720 "effective_tld_names.gperf"
- {"re", 0},
-#line 1777 "effective_tld_names.gperf"
- {"lodi.it", 0},
-#line 3429 "effective_tld_names.gperf"
- {"xn--bmlo-gra.no", 0},
-#line 1033 "effective_tld_names.gperf"
- {"flora.no", 0},
-#line 3396 "effective_tld_names.gperf"
- {"workinggroup.aero", 0},
-#line 3294 "effective_tld_names.gperf"
- {"ve", 2},
-#line 871 "effective_tld_names.gperf"
- {"edu.mx", 0},
-#line 3140 "effective_tld_names.gperf"
- {"tom.ru", 0},
-#line 1202 "effective_tld_names.gperf"
- {"gov.cx", 0},
-#line 1713 "effective_tld_names.gperf"
- {"lajolla.museum", 0},
-#line 2042 "effective_tld_names.gperf"
- {"nagasaki.jp", 2},
-#line 2164 "effective_tld_names.gperf"
- {"net.mx", 0},
-#line 2725 "effective_tld_names.gperf"
- {"rec.co", 0},
-#line 2367 "effective_tld_names.gperf"
- {"org.bz", 0},
-#line 649 "effective_tld_names.gperf"
- {"com.mx", 0},
-#line 772 "effective_tld_names.gperf"
- {"detroit.museum", 0},
-#line 314 "effective_tld_names.gperf"
- {"berkeley.museum", 0},
-#line 3486 "effective_tld_names.gperf"
- {"xn--ksnes-uua.no", 0},
-#line 1910 "effective_tld_names.gperf"
- {"mie.jp", 2},
-#line 1572 "effective_tld_names.gperf"
- {"jondal.no", 0},
-#line 3344 "effective_tld_names.gperf"
- {"vn", 0},
-#line 415 "effective_tld_names.gperf"
- {"bushey.museum", 0},
-#line 775 "effective_tld_names.gperf"
- {"dinosaur.museum", 0},
-#line 1162 "effective_tld_names.gperf"
- {"gob.mx", 0},
-#line 2466 "effective_tld_names.gperf"
- {"osen.no", 0},
-#line 2785 "effective_tld_names.gperf"
- {"rs", 0},
-#line 2964 "effective_tld_names.gperf"
- {"sola.no", 0},
-#line 3195 "effective_tld_names.gperf"
- {"tv", 0},
-#line 1480 "effective_tld_names.gperf"
- {"info.az", 0},
-#line 370 "effective_tld_names.gperf"
- {"bologna.it", 0},
-#line 211 "effective_tld_names.gperf"
- {"assassination.museum", 0},
-#line 778 "effective_tld_names.gperf"
- {"divttasvuotna.no", 0},
-#line 2727 "effective_tld_names.gperf"
- {"rec.ro", 0},
-#line 1623 "effective_tld_names.gperf"
- {"kg.kr", 0},
-#line 159 "effective_tld_names.gperf"
- {"aosta.it", 0},
-#line 3041 "effective_tld_names.gperf"
- {"suedtirol.it", 0},
-#line 2722 "effective_tld_names.gperf"
- {"re.kr", 0},
-#line 3119 "effective_tld_names.gperf"
- {"tm", 0},
-#line 709 "effective_tld_names.gperf"
- {"coop.tt", 0},
-#line 3132 "effective_tld_names.gperf"
- {"tn.us", 0},
-#line 1228 "effective_tld_names.gperf"
- {"gov.kz", 0},
-#line 3611 "effective_tld_names.gperf"
- {"yakutia.ru", 0},
-#line 857 "effective_tld_names.gperf"
- {"edu.kz", 0},
-#line 2279 "effective_tld_names.gperf"
- {"nsw.gov.au", 0},
-#line 291 "effective_tld_names.gperf"
- {"baths.museum", 0},
-#line 2150 "effective_tld_names.gperf"
- {"net.kz", 0},
-#line 1839 "effective_tld_names.gperf"
- {"manx.museum", 0},
-#line 3122 "effective_tld_names.gperf"
- {"tm.km", 0},
-#line 139 "effective_tld_names.gperf"
- {"amli.no", 0},
-#line 3391 "effective_tld_names.gperf"
- {"windmill.museum", 0},
-#line 3212 "effective_tld_names.gperf"
- {"tz", 0},
-#line 636 "effective_tld_names.gperf"
- {"com.kz", 0},
-#line 2591 "effective_tld_names.gperf"
- {"prd.fr", 0},
-#line 2324 "effective_tld_names.gperf"
- {"omsk.ru", 0},
-#line 2554 "effective_tld_names.gperf"
- {"plants.museum", 0},
-#line 771 "effective_tld_names.gperf"
- {"design.museum", 0},
-#line 3012 "effective_tld_names.gperf"
- {"stathelle.no", 0},
-#line 2787 "effective_tld_names.gperf"
- {"ru", 0},
-#line 2417 "effective_tld_names.gperf"
- {"org.mx", 0},
-#line 1976 "effective_tld_names.gperf"
- {"mobi.gp", 0},
-#line 1483 "effective_tld_names.gperf"
- {"info.ht", 0},
-#line 1121 "effective_tld_names.gperf"
- {"georgia.museum", 0},
-#line 1958 "effective_tld_names.gperf"
- {"missoula.museum", 0},
-#line 243 "effective_tld_names.gperf"
- {"austrheim.no", 0},
-#line 2014 "effective_tld_names.gperf"
- {"muenchen.museum", 0},
-#line 3358 "effective_tld_names.gperf"
- {"vu", 0},
-#line 1800 "effective_tld_names.gperf"
- {"lucca.it", 0},
-#line 705 "effective_tld_names.gperf"
- {"coop.br", 0},
-#line 711 "effective_tld_names.gperf"
- {"corporation.museum", 0},
-#line 2481 "effective_tld_names.gperf"
- {"oyer.no", 0},
-#line 2444 "effective_tld_names.gperf"
- {"org.sz", 0},
-#line 3088 "effective_tld_names.gperf"
- {"tc", 0},
-#line 1842 "effective_tld_names.gperf"
- {"mari.ru", 0},
-#line 1484 "effective_tld_names.gperf"
- {"info.hu", 0},
-#line 1186 "effective_tld_names.gperf"
- {"gov.az", 0},
-#line 1761 "effective_tld_names.gperf"
- {"lillesand.no", 0},
-#line 3554 "effective_tld_names.gperf"
- {"xn--sgne-gra.no", 0},
-#line 377 "effective_tld_names.gperf"
- {"botanicalgarden.museum", 0},
-#line 820 "effective_tld_names.gperf"
- {"edu.az", 0},
-#line 1592 "effective_tld_names.gperf"
- {"k12.ec", 0},
-#line 2869 "effective_tld_names.gperf"
- {"sciencecenter.museum", 0},
-#line 2682 "effective_tld_names.gperf"
- {"public.museum", 0},
-#line 2115 "effective_tld_names.gperf"
- {"net.az", 0},
-#line 101 "effective_tld_names.gperf"
- {"air-surveillance.aero", 0},
-#line 1027 "effective_tld_names.gperf"
- {"flanders.museum", 0},
-#line 1499 "effective_tld_names.gperf"
- {"ingatlan.hu", 0},
-#line 596 "effective_tld_names.gperf"
- {"com.az", 0},
-#line 3043 "effective_tld_names.gperf"
- {"sula.no", 0},
-#line 2839 "effective_tld_names.gperf"
- {"satx.museum", 0},
-#line 172 "effective_tld_names.gperf"
- {"archaeology.museum", 0},
-#line 3092 "effective_tld_names.gperf"
- {"te.ua", 0},
-#line 212 "effective_tld_names.gperf"
- {"assedic.fr", 0},
-#line 1949 "effective_tld_names.gperf"
- {"milan.it", 0},
-#line 3094 "effective_tld_names.gperf"
- {"tel", 0},
-#line 1502 "effective_tld_names.gperf"
- {"int.az", 0},
-#line 3126 "effective_tld_names.gperf"
- {"tm.pl", 0},
-#line 144 "effective_tld_names.gperf"
- {"amusement.aero", 0},
-#line 3507 "effective_tld_names.gperf"
- {"xn--mely-ira.no", 0},
-#line 2697 "effective_tld_names.gperf"
- {"qld.gov.au", 0},
-#line 446 "effective_tld_names.gperf"
- {"casadelamoneda.museum", 0},
-#line 2765 "effective_tld_names.gperf"
- {"rnu.tn", 0},
-#line 2828 "effective_tld_names.gperf"
- {"sanfrancisco.museum", 0},
-#line 110 "effective_tld_names.gperf"
- {"akita.jp", 2},
-#line 2400 "effective_tld_names.gperf"
- {"org.kz", 0},
-#line 464 "effective_tld_names.gperf"
- {"certification.aero", 0},
-#line 3357 "effective_tld_names.gperf"
- {"vt.us", 0},
-#line 789 "effective_tld_names.gperf"
- {"donna.no", 0},
-#line 2870 "effective_tld_names.gperf"
- {"sciencecenters.museum", 0},
-#line 414 "effective_tld_names.gperf"
- {"busan.kr", 0},
-#line 1177 "effective_tld_names.gperf"
- {"gouv.ht", 0},
-#line 410 "effective_tld_names.gperf"
- {"building.museum", 0},
-#line 736 "effective_tld_names.gperf"
- {"cuneo.it", 0},
-#line 2558 "effective_tld_names.gperf"
- {"plo.ps", 0},
-#line 2502 "effective_tld_names.gperf"
- {"paris.museum", 0},
-#line 2534 "effective_tld_names.gperf"
- {"pharmacy.museum", 0},
-#line 708 "effective_tld_names.gperf"
- {"coop.mw", 0},
-#line 2478 "effective_tld_names.gperf"
- {"overhalla.no", 0},
-#line 1738 "effective_tld_names.gperf"
- {"lecce.it", 0},
-#line 3291 "effective_tld_names.gperf"
- {"vc", 0},
-#line 3433 "effective_tld_names.gperf"
- {"xn--brum-voa.no", 0},
-#line 3134 "effective_tld_names.gperf"
- {"to.it", 0},
-#line 2684 "effective_tld_names.gperf"
- {"pulawy.pl", 0},
-#line 348 "effective_tld_names.gperf"
- {"biz.tj", 0},
-#line 3091 "effective_tld_names.gperf"
- {"te.it", 0},
-#line 2585 "effective_tld_names.gperf"
- {"pp.se", 0},
-#line 2491 "effective_tld_names.gperf"
- {"paderborn.museum", 0},
-#line 1303 "effective_tld_names.gperf"
- {"gs.bu.no", 0},
-#line 2358 "effective_tld_names.gperf"
- {"org.az", 0},
-#line 1048 "effective_tld_names.gperf"
- {"forlicesena.it", 0},
-#line 3156 "effective_tld_names.gperf"
- {"tr.it", 0},
-#line 1030 "effective_tld_names.gperf"
- {"flesberg.no", 0},
-#line 3009 "effective_tld_names.gperf"
- {"stat.no", 0},
-#line 3131 "effective_tld_names.gperf"
- {"tn.it", 0},
-#line 527 "effective_tld_names.gperf"
- {"clock.museum", 0},
-#line 496 "effective_tld_names.gperf"
- {"cieszyn.pl", 0},
-#line 1645 "effective_tld_names.gperf"
- {"kms.ru", 0},
-#line 2668 "effective_tld_names.gperf"
- {"project.museum", 0},
-#line 2463 "effective_tld_names.gperf"
- {"os.hedmark.no", 0},
-#line 3563 "effective_tld_names.gperf"
- {"xn--smna-gra.no", 0},
-#line 160 "effective_tld_names.gperf"
- {"aoste.it", 0},
-#line 714 "effective_tld_names.gperf"
- {"costume.museum", 0},
-#line 3184 "effective_tld_names.gperf"
- {"ts.it", 0},
-#line 2833 "effective_tld_names.gperf"
- {"saotome.st", 0},
-#line 1883 "effective_tld_names.gperf"
- {"media.aero", 0},
-#line 1555 "effective_tld_names.gperf"
- {"jeonnam.kr", 0},
-#line 3616 "effective_tld_names.gperf"
- {"yaroslavl.ru", 0},
-#line 779 "effective_tld_names.gperf"
- {"dj", 0},
-#line 3345 "effective_tld_names.gperf"
- {"vn.ua", 0},
-#line 3372 "effective_tld_names.gperf"
- {"warszawa.pl", 0},
-#line 3497 "effective_tld_names.gperf"
- {"xn--lgrd-poac.no", 0},
-#line 725 "effective_tld_names.gperf"
- {"cremona.it", 0},
-#line 1766 "effective_tld_names.gperf"
- {"linz.museum", 0},
-#line 2890 "effective_tld_names.gperf"
- {"sendai.jp", 2},
-#line 1439 "effective_tld_names.gperf"
- {"ibestad.no", 0},
-#line 1752 "effective_tld_names.gperf"
- {"lezajsk.pl", 0},
-#line 2934 "effective_tld_names.gperf"
- {"skjervoy.no", 0},
-#line 3204 "effective_tld_names.gperf"
- {"tx.us", 0},
-#line 1734 "effective_tld_names.gperf"
- {"leangaviika.no", 0},
-#line 3355 "effective_tld_names.gperf"
- {"vrn.ru", 0},
-#line 2004 "effective_tld_names.gperf"
- {"mragowo.pl", 0},
-#line 3062 "effective_tld_names.gperf"
- {"swiebodzin.pl", 0},
-#line 3199 "effective_tld_names.gperf"
- {"tv.na", 0},
-#line 1296 "effective_tld_names.gperf"
- {"group.aero", 0},
-#line 3097 "effective_tld_names.gperf"
- {"teramo.it", 0},
-#line 698 "effective_tld_names.gperf"
- {"consultant.aero", 0},
-#line 264 "effective_tld_names.gperf"
- {"badajoz.museum", 0},
-#line 2767 "effective_tld_names.gperf"
- {"ro.it", 0},
-#line 2278 "effective_tld_names.gperf"
- {"nsw.edu.au", 0},
-#line 763 "effective_tld_names.gperf"
- {"decorativearts.museum", 0},
-#line 1015 "effective_tld_names.gperf"
- {"firm.in", 0},
-#line 2721 "effective_tld_names.gperf"
- {"re.it", 0},
-#line 3269 "effective_tld_names.gperf"
- {"va", 0},
-#line 1380 "effective_tld_names.gperf"
- {"heroy.more-og-romsdal.no", 0},
-#line 785 "effective_tld_names.gperf"
- {"dni.us", 0},
-#line 3295 "effective_tld_names.gperf"
- {"ve.it", 0},
-#line 477 "effective_tld_names.gperf"
- {"chernigov.ua", 0},
-#line 791 "effective_tld_names.gperf"
- {"dovre.no", 0},
-#line 906 "effective_tld_names.gperf"
- {"educational.museum", 0},
-#line 1084 "effective_tld_names.gperf"
- {"fusa.no", 0},
-#line 3104 "effective_tld_names.gperf"
- {"tg", 0},
-#line 3320 "effective_tld_names.gperf"
- {"vi", 0},
-#line 2574 "effective_tld_names.gperf"
- {"porsgrunn.no", 0},
-#line 3354 "effective_tld_names.gperf"
- {"vr.it", 0},
-#line 2490 "effective_tld_names.gperf"
- {"pacific.museum", 0},
-#line 2761 "effective_tld_names.gperf"
- {"rn.it", 0},
-#line 580 "effective_tld_names.gperf"
- {"coastaldefence.museum", 0},
-#line 3256 "effective_tld_names.gperf"
- {"uslivinghistory.museum", 0},
-#line 3585 "effective_tld_names.gperf"
- {"xn--tysvr-vra.no", 0},
-#line 3235 "effective_tld_names.gperf"
- {"undersea.museum", 0},
-#line 1591 "effective_tld_names.gperf"
- {"k.se", 0},
-#line 1318 "effective_tld_names.gperf"
- {"gs.svalbard.no", 0},
-#line 1139 "effective_tld_names.gperf"
- {"glass.museum", 0},
-#line 3198 "effective_tld_names.gperf"
- {"tv.it", 0},
-#line 2834 "effective_tld_names.gperf"
- {"sapporo.jp", 2},
-#line 3356 "effective_tld_names.gperf"
- {"vt.it", 0},
-#line 3218 "effective_tld_names.gperf"
- {"udine.it", 0},
-#line 2792 "effective_tld_names.gperf"
- {"rv.ua", 0},
-#line 3027 "effective_tld_names.gperf"
- {"stord.no", 0},
-#line 3070 "effective_tld_names.gperf"
- {"szczecin.pl", 0},
-#line 317 "effective_tld_names.gperf"
- {"bern.museum", 0},
-#line 2260 "effective_tld_names.gperf"
- {"norfolk.museum", 0},
-#line 1698 "effective_tld_names.gperf"
- {"kvitsoy.no", 0},
-#line 3369 "effective_tld_names.gperf"
- {"wallonie.museum", 0},
-#line 1036 "effective_tld_names.gperf"
- {"floro.no", 0},
-#line 3432 "effective_tld_names.gperf"
- {"xn--brnnysund-m8ac.no", 0},
-#line 1739 "effective_tld_names.gperf"
- {"lecco.it", 0},
-#line 690 "effective_tld_names.gperf"
- {"community.museum", 0},
-#line 478 "effective_tld_names.gperf"
- {"chernovtsy.ua", 0},
-#line 3306 "effective_tld_names.gperf"
- {"verran.no", 0},
-#line 2696 "effective_tld_names.gperf"
- {"qld.edu.au", 0},
-#line 1526 "effective_tld_names.gperf"
- {"iron.museum", 0},
-#line 1120 "effective_tld_names.gperf"
- {"geometre-expert.fr", 0},
-#line 398 "effective_tld_names.gperf"
- {"brumunddal.no", 0},
-#line 2724 "effective_tld_names.gperf"
- {"rec.br", 0},
-#line 1022 "effective_tld_names.gperf"
- {"fjell.no", 0},
-#line 3313 "effective_tld_names.gperf"
- {"vet.br", 0},
-#line 1127 "effective_tld_names.gperf"
- {"giehtavuoatna.no", 0},
-#line 3035 "effective_tld_names.gperf"
- {"stranda.no", 0},
-#line 769 "effective_tld_names.gperf"
- {"depot.museum", 0},
-#line 2917 "effective_tld_names.gperf"
- {"silk.museum", 0},
-#line 2553 "effective_tld_names.gperf"
- {"plantation.museum", 0},
-#line 271 "effective_tld_names.gperf"
- {"bajddar.no", 0},
-#line 3189 "effective_tld_names.gperf"
- {"tur.br", 0},
-#line 2104 "effective_tld_names.gperf"
- {"nesoddtangen.no", 0},
-#line 2726 "effective_tld_names.gperf"
- {"rec.nf", 0},
-#line 2885 "effective_tld_names.gperf"
- {"sejny.pl", 0},
-#line 3318 "effective_tld_names.gperf"
- {"vg", 0},
-#line 218 "effective_tld_names.gperf"
- {"asso.gp", 0},
-#line 121 "effective_tld_names.gperf"
- {"alessandria.it", 0},
-#line 420 "effective_tld_names.gperf"
- {"bykle.no", 0},
-#line 1173 "effective_tld_names.gperf"
- {"gorlice.pl", 0},
-#line 3272 "effective_tld_names.gperf"
- {"va.us", 0},
-#line 2901 "effective_tld_names.gperf"
- {"shell.museum", 0},
-#line 1692 "effective_tld_names.gperf"
- {"kvalsund.no", 0},
-#line 3359 "effective_tld_names.gperf"
- {"vv.it", 0},
-#line 3642 "effective_tld_names.gperf"
- {"zhitomir.ua", 0},
-#line 2936 "effective_tld_names.gperf"
- {"skoczow.pl", 0},
-#line 2748 "effective_tld_names.gperf"
- {"ri.us", 0},
-#line 1537 "effective_tld_names.gperf"
- {"ivano-frankivsk.ua", 0},
-#line 3322 "effective_tld_names.gperf"
- {"vi.us", 0},
-#line 2708 "effective_tld_names.gperf"
- {"raholt.no", 0},
-#line 2760 "effective_tld_names.gperf"
- {"rm.it", 0},
-#line 1217 "effective_tld_names.gperf"
- {"gov.iq", 0},
-#line 1960 "effective_tld_names.gperf"
- {"miyazaki.jp", 2},
-#line 3613 "effective_tld_names.gperf"
- {"yamaguchi.jp", 2},
-#line 848 "effective_tld_names.gperf"
- {"edu.iq", 0},
-#line 3129 "effective_tld_names.gperf"
- {"tmp.br", 0},
-#line 2141 "effective_tld_names.gperf"
- {"net.iq", 0},
-#line 629 "effective_tld_names.gperf"
- {"com.iq", 0},
-#line 2056 "effective_tld_names.gperf"
- {"name.tj", 0},
-#line 2896 "effective_tld_names.gperf"
- {"sex.pl", 0},
-#line 284 "effective_tld_names.gperf"
- {"bari.it", 0},
-#line 2264 "effective_tld_names.gperf"
- {"notaires.fr", 0},
-#line 3558 "effective_tld_names.gperf"
- {"xn--sknit-yqa.no", 0},
-#line 1832 "effective_tld_names.gperf"
- {"malselv.no", 0},
-#line 3089 "effective_tld_names.gperf"
- {"tcm.museum", 0},
-#line 1590 "effective_tld_names.gperf"
- {"k.bg", 0},
-#line 798 "effective_tld_names.gperf"
- {"durham.museum", 0},
-#line 386 "effective_tld_names.gperf"
- {"brasil.museum", 0},
-#line 1735 "effective_tld_names.gperf"
- {"leasing.aero", 0},
-#line 2794 "effective_tld_names.gperf"
- {"ryazan.ru", 0},
-#line 3033 "effective_tld_names.gperf"
- {"stpetersburg.museum", 0},
-#line 1373 "effective_tld_names.gperf"
- {"helsinki.museum", 0},
-#line 2571 "effective_tld_names.gperf"
- {"pordenone.it", 0},
-#line 266 "effective_tld_names.gperf"
- {"bahcavuotna.no", 0},
-#line 2762 "effective_tld_names.gperf"
- {"rnd.ru", 0},
-#line 2719 "effective_tld_names.gperf"
- {"rc.it", 0},
-#line 1745 "effective_tld_names.gperf"
- {"leksvik.no", 0},
-#line 3292 "effective_tld_names.gperf"
- {"vc.it", 0},
-#line 1031 "effective_tld_names.gperf"
- {"flight.aero", 0},
-#line 3076 "effective_tld_names.gperf"
- {"ta.it", 0},
-#line 125 "effective_tld_names.gperf"
- {"alta.no", 0},
-#line 73 "effective_tld_names.gperf"
- {"adygeya.ru", 0},
-#line 3223 "effective_tld_names.gperf"
- {"uhren.museum", 0},
-#line 753 "effective_tld_names.gperf"
- {"dallas.museum", 0},
-#line 374 "effective_tld_names.gperf"
- {"bonn.museum", 0},
-#line 2206 "effective_tld_names.gperf"
- {"newspaper.museum", 0},
-#line 1064 "effective_tld_names.gperf"
- {"frei.no", 0},
-#line 2348 "effective_tld_names.gperf"
- {"oregontrail.museum", 0},
-#line 265 "effective_tld_names.gperf"
- {"baghdad.museum", 0},
-#line 3516 "effective_tld_names.gperf"
- {"xn--mot-tla.no", 0},
-#line 3157 "effective_tld_names.gperf"
- {"tr.no", 0},
-#line 1952 "effective_tld_names.gperf"
- {"mill.museum", 0},
-#line 2390 "effective_tld_names.gperf"
- {"org.iq", 0},
-#line 735 "effective_tld_names.gperf"
- {"culture.museum", 0},
-#line 3253 "effective_tld_names.gperf"
- {"usgarden.museum", 0},
-#line 1072 "effective_tld_names.gperf"
- {"frosinone.it", 0},
-#line 2914 "effective_tld_names.gperf"
- {"siena.it", 0},
-#line 2500 "effective_tld_names.gperf"
- {"parachuting.aero", 0},
-#line 3162 "effective_tld_names.gperf"
- {"tranby.no", 0},
-#line 1990 "effective_tld_names.gperf"
- {"montreal.museum", 0},
-#line 2052 "effective_tld_names.gperf"
- {"name.mk", 0},
-#line 3615 "effective_tld_names.gperf"
- {"yamanashi.jp", 2},
-#line 2050 "effective_tld_names.gperf"
- {"name.hr", 0},
-#line 2049 "effective_tld_names.gperf"
- {"name.az", 0},
-#line 2764 "effective_tld_names.gperf"
- {"rns.tn", 0},
-#line 1607 "effective_tld_names.gperf"
- {"karlsoy.no", 0},
-#line 988 "effective_tld_names.gperf"
- {"fermo.it", 0},
-#line 3639 "effective_tld_names.gperf"
- {"zgora.pl", 0},
-#line 2702 "effective_tld_names.gperf"
- {"ra.it", 0},
-#line 3154 "effective_tld_names.gperf"
- {"tp.it", 0},
-#line 3270 "effective_tld_names.gperf"
- {"va.it", 0},
-#line 1667 "effective_tld_names.gperf"
- {"kr.com", 0},
-#line 196 "effective_tld_names.gperf"
- {"artsandcrafts.museum", 0},
-#line 3574 "effective_tld_names.gperf"
- {"xn--srum-gra.no", 0},
-#line 2509 "effective_tld_names.gperf"
- {"pb.ao", 0},
-#line 3637 "effective_tld_names.gperf"
- {"zaporizhzhe.ua", 0},
-#line 2266 "effective_tld_names.gperf"
- {"notodden.no", 0},
-#line 2747 "effective_tld_names.gperf"
- {"ri.it", 0},
-#line 2234 "effective_tld_names.gperf"
- {"nnov.ru", 0},
-#line 3321 "effective_tld_names.gperf"
- {"vi.it", 0},
-#line 1599 "effective_tld_names.gperf"
- {"kaluga.ru", 0},
-#line 1134 "effective_tld_names.gperf"
- {"gjerstad.no", 0},
-#line 1785 "effective_tld_names.gperf"
- {"losangeles.museum", 0},
-#line 122 "effective_tld_names.gperf"
- {"alesund.no", 0},
-#line 3036 "effective_tld_names.gperf"
- {"stryn.no", 0},
-#line 173 "effective_tld_names.gperf"
- {"architecture.museum", 0},
-#line 1029 "effective_tld_names.gperf"
- {"flekkefjord.no", 0},
-#line 2505 "effective_tld_names.gperf"
- {"parti.se", 0},
-#line 3387 "effective_tld_names.gperf"
- {"wielun.pl", 0},
-#line 2804 "effective_tld_names.gperf"
- {"sa.gov.au", 0},
-#line 2660 "effective_tld_names.gperf"
- {"pro.ht", 0},
-#line 3075 "effective_tld_names.gperf"
- {"t.se", 0},
-#line 1080 "effective_tld_names.gperf"
- {"fundacio.museum", 0},
-#line 3123 "effective_tld_names.gperf"
- {"tm.mc", 0},
-#line 2841 "effective_tld_names.gperf"
- {"sauherad.no", 0},
-#line 699 "effective_tld_names.gperf"
- {"consulting.aero", 0},
-#line 3556 "effective_tld_names.gperf"
- {"xn--skjervy-v1a.no", 0},
-#line 2073 "effective_tld_names.gperf"
- {"nativeamerican.museum", 0},
-#line 3125 "effective_tld_names.gperf"
- {"tm.no", 0},
-#line 286 "effective_tld_names.gperf"
- {"barlettaandriatrani.it", 0},
-#line 285 "effective_tld_names.gperf"
- {"barletta-andria-trani.it", 0},
-#line 3236 "effective_tld_names.gperf"
- {"union.aero", 0},
-#line 2735 "effective_tld_names.gperf"
- {"rel.pl", 0},
-#line 118 "effective_tld_names.gperf"
- {"alaheadju.no", 0},
-#line 795 "effective_tld_names.gperf"
- {"drangedal.no", 0},
-#line 3364 "effective_tld_names.gperf"
- {"wa.gov.au", 0},
-#line 411 "effective_tld_names.gperf"
- {"burghof.museum", 0},
-#line 219 "effective_tld_names.gperf"
- {"asso.ht", 0},
-#line 1485 "effective_tld_names.gperf"
- {"info.ki", 0},
-#line 1016 "effective_tld_names.gperf"
- {"firm.nf", 0},
-#line 2918 "effective_tld_names.gperf"
- {"simbirsk.ru", 0},
-#line 3544 "effective_tld_names.gperf"
- {"xn--rros-gra.no", 0},
-#line 2746 "effective_tld_names.gperf"
- {"rg.it", 0},
-#line 3169 "effective_tld_names.gperf"
- {"trd.br", 0},
-#line 268 "effective_tld_names.gperf"
- {"bahn.museum", 0},
-#line 1133 "effective_tld_names.gperf"
- {"gjerdrum.no", 0},
-#line 977 "effective_tld_names.gperf"
- {"farm.museum", 0},
-#line 1909 "effective_tld_names.gperf"
- {"midtre-gauldal.no", 0},
-#line 2070 "effective_tld_names.gperf"
- {"national.museum", 0},
-#line 2474 "effective_tld_names.gperf"
- {"ostrowiec.pl", 0},
-#line 2524 "effective_tld_names.gperf"
- {"perugia.it", 0},
-#line 2868 "effective_tld_names.gperf"
- {"scienceandindustry.museum", 0},
-#line 1961 "effective_tld_names.gperf"
- {"mjondalen.no", 0},
-#line 3360 "effective_tld_names.gperf"
- {"vyatka.ru", 0},
-#line 1541 "effective_tld_names.gperf"
- {"iwate.jp", 2},
-#line 367 "effective_tld_names.gperf"
- {"bodo.no", 0},
-#line 2503 "effective_tld_names.gperf"
- {"parliament.uk", 1},
-#line 307 "effective_tld_names.gperf"
- {"bellevue.museum", 0},
-#line 262 "effective_tld_names.gperf"
- {"babia-gora.pl", 0},
-#line 2701 "effective_tld_names.gperf"
- {"r.se", 0},
-#line 3067 "effective_tld_names.gperf"
- {"sykkylven.no", 0},
-#line 2826 "effective_tld_names.gperf"
- {"sandnessjoen.no", 0},
-#line 3060 "effective_tld_names.gperf"
- {"sweden.museum", 0},
-#line 1665 "effective_tld_names.gperf"
- {"kostroma.ru", 0},
-#line 2935 "effective_tld_names.gperf"
- {"sklep.pl", 0},
-#line 1803 "effective_tld_names.gperf"
- {"lukow.pl", 0},
-#line 493 "effective_tld_names.gperf"
- {"chungnam.kr", 0},
-#line 2290 "effective_tld_names.gperf"
- {"nuoro.it", 0},
-#line 135 "effective_tld_names.gperf"
- {"american.museum", 0},
-#line 799 "effective_tld_names.gperf"
- {"dyroy.no", 0},
-#line 2902 "effective_tld_names.gperf"
- {"sherbrooke.museum", 0},
-#line 1758 "effective_tld_names.gperf"
- {"lier.no", 0},
-#line 1907 "effective_tld_names.gperf"
- {"midatlantic.museum", 0},
-#line 3029 "effective_tld_names.gperf"
- {"store.nf", 0},
-#line 3117 "effective_tld_names.gperf"
- {"tk", 0},
-#line 2988 "effective_tld_names.gperf"
- {"spjelkavik.no", 0},
-#line 3127 "effective_tld_names.gperf"
- {"tm.ro", 0},
-#line 3584 "effective_tld_names.gperf"
- {"xn--troms-zua.no", 0},
-#line 2938 "effective_tld_names.gperf"
- {"skole.museum", 0},
-#line 2927 "effective_tld_names.gperf"
- {"skedsmokorset.no", 0},
-#line 710 "effective_tld_names.gperf"
- {"copenhagen.museum", 0},
-#line 3074 "effective_tld_names.gperf"
- {"t.bg", 0},
-#line 1554 "effective_tld_names.gperf"
- {"jeonbuk.kr", 0},
-#line 3444 "effective_tld_names.gperf"
- {"xn--finny-yua.no", 0},
-#line 3473 "effective_tld_names.gperf"
- {"xn--io0a7i.cn", 0},
-#line 2783 "effective_tld_names.gperf"
- {"royken.no", 0},
-#line 1013 "effective_tld_names.gperf"
- {"firm.co", 0},
-#line 64 "effective_tld_names.gperf"
- {"accident-investigation.aero", 0},
-#line 1624 "effective_tld_names.gperf"
- {"kh", 2},
-#line 1933 "effective_tld_names.gperf"
- {"mil.kz", 0},
-#line 3144 "effective_tld_names.gperf"
- {"torino.it", 0},
-#line 1604 "effective_tld_names.gperf"
- {"karate.museum", 0},
-#line 788 "effective_tld_names.gperf"
- {"donetsk.ua", 0},
-#line 750 "effective_tld_names.gperf"
- {"daejeon.kr", 0},
-#line 2781 "effective_tld_names.gperf"
- {"rovigo.it", 0},
-#line 2887 "effective_tld_names.gperf"
- {"selbu.no", 0},
-#line 3603 "effective_tld_names.gperf"
- {"xn--yer-zna.no", 0},
-#line 695 "effective_tld_names.gperf"
- {"conference.aero", 0},
-#line 1574 "effective_tld_names.gperf"
- {"jorpeland.no", 0},
-#line 380 "effective_tld_names.gperf"
- {"bozen.it", 0},
-#line 3305 "effective_tld_names.gperf"
- {"verona.it", 0},
-#line 3005 "effective_tld_names.gperf"
- {"starachowice.pl", 0},
-#line 766 "effective_tld_names.gperf"
- {"delmenhorst.museum", 0},
-#line 3262 "effective_tld_names.gperf"
- {"uvic.museum", 0},
-#line 2706 "effective_tld_names.gperf"
- {"ragusa.it", 0},
-#line 119 "effective_tld_names.gperf"
- {"aland.fi", 0},
-#line 1130 "effective_tld_names.gperf"
- {"gildeskal.no", 0},
-#line 140 "effective_tld_names.gperf"
- {"amot.no", 0},
-#line 2737 "effective_tld_names.gperf"
- {"rennebu.no", 0},
-#line 1492 "effective_tld_names.gperf"
- {"info.pr", 0},
-#line 2646 "effective_tld_names.gperf"
- {"presse.ci", 0},
-#line 1916 "effective_tld_names.gperf"
- {"mil.az", 0},
-#line 1685 "effective_tld_names.gperf"
- {"kurgan.ru", 0},
-#line 3478 "effective_tld_names.gperf"
- {"xn--kfjord-iua.no", 0},
-#line 3118 "effective_tld_names.gperf"
- {"tl", 0},
-#line 2700 "effective_tld_names.gperf"
- {"r.bg", 0},
-#line 1850 "effective_tld_names.gperf"
- {"marylhurst.museum", 0},
-#line 3268 "effective_tld_names.gperf"
- {"v.bg", 0},
-#line 2937 "effective_tld_names.gperf"
- {"skodje.no", 0},
-#line 913 "effective_tld_names.gperf"
- {"ehime.jp", 2},
-#line 1631 "effective_tld_names.gperf"
- {"khv.ru", 0},
-#line 2572 "effective_tld_names.gperf"
- {"porsanger.no", 0},
-#line 98 "effective_tld_names.gperf"
- {"aichi.jp", 2},
-#line 1175 "effective_tld_names.gperf"
- {"gouv.ci", 0},
-#line 2745 "effective_tld_names.gperf"
- {"retina.ar", 1},
-#line 2671 "effective_tld_names.gperf"
- {"przeworsk.pl", 0},
-#line 194 "effective_tld_names.gperf"
- {"arts.nf", 0},
-#line 3271 "effective_tld_names.gperf"
- {"va.no", 0},
-#line 1552 "effective_tld_names.gperf"
- {"jeju.kr", 0},
-#line 470 "effective_tld_names.gperf"
- {"championship.aero", 0},
-#line 2740 "effective_tld_names.gperf"
- {"res.aero", 0},
-#line 1742 "effective_tld_names.gperf"
- {"leirfjord.no", 0},
-#line 1140 "effective_tld_names.gperf"
- {"gliding.aero", 0},
-#line 3618 "effective_tld_names.gperf"
- {"yekaterinburg.ru", 0},
-#line 1594 "effective_tld_names.gperf"
- {"kafjord.no", 0},
-#line 933 "effective_tld_names.gperf"
- {"engerdal.no", 0},
-#line 1699 "effective_tld_names.gperf"
- {"kw", 2},
-#line 3231 "effective_tld_names.gperf"
- {"ulsan.kr", 0},
-#line 1497 "effective_tld_names.gperf"
- {"info.vn", 0},
-#line 2689 "effective_tld_names.gperf"
- {"pyatigorsk.ru", 0},
-#line 1740 "effective_tld_names.gperf"
- {"legnica.pl", 0},
-#line 787 "effective_tld_names.gperf"
- {"dolls.museum", 0},
-#line 193 "effective_tld_names.gperf"
- {"arts.museum", 0},
-#line 3490 "effective_tld_names.gperf"
- {"xn--l-1fa.no", 0},
-#line 3300 "effective_tld_names.gperf"
- {"venice.it", 0},
-#line 1085 "effective_tld_names.gperf"
- {"fylkesbibl.no", 0},
-#line 2541 "effective_tld_names.gperf"
- {"piacenza.it", 0},
-#line 1330 "effective_tld_names.gperf"
- {"guovdageaidnu.no", 0},
-#line 3504 "effective_tld_names.gperf"
- {"xn--lt-liac.no", 0},
-#line 1001 "effective_tld_names.gperf"
- {"field.museum", 0},
-#line 1433 "effective_tld_names.gperf"
- {"hyogo.jp", 2},
-#line 475 "effective_tld_names.gperf"
- {"chelyabinsk.ru", 0},
-#line 1005 "effective_tld_names.gperf"
- {"film.museum", 0},
-#line 3045 "effective_tld_names.gperf"
- {"suli.hu", 0},
-#line 3298 "effective_tld_names.gperf"
- {"vegarshei.no", 0},
-#line 929 "effective_tld_names.gperf"
- {"encyclopedic.museum", 0},
-#line 2555 "effective_tld_names.gperf"
- {"plaza.museum", 0},
-#line 3124 "effective_tld_names.gperf"
- {"tm.mg", 0},
-#line 1491 "effective_tld_names.gperf"
- {"info.pl", 0},
-#line 696 "effective_tld_names.gperf"
- {"congresodelalengua3.ar", 1},
-#line 1357 "effective_tld_names.gperf"
- {"hanggliding.aero", 0},
-#line 3316 "effective_tld_names.gperf"
- {"vevelstad.no", 0},
-#line 2573 "effective_tld_names.gperf"
- {"porsangu.no", 0},
-#line 3209 "effective_tld_names.gperf"
- {"tysnes.no", 0},
-#line 1575 "effective_tld_names.gperf"
- {"joshkar-ola.ru", 0},
-#line 706 "effective_tld_names.gperf"
- {"coop.ht", 0},
-#line 1487 "effective_tld_names.gperf"
- {"info.na", 0},
-#line 3086 "effective_tld_names.gperf"
- {"tatarstan.ru", 0},
-#line 2569 "effective_tld_names.gperf"
- {"pomorskie.pl", 0},
-#line 3550 "effective_tld_names.gperf"
- {"xn--s-1fa.no", 0},
-#line 2675 "effective_tld_names.gperf"
- {"pskov.ru", 0},
-#line 3145 "effective_tld_names.gperf"
- {"torino.museum", 0},
-#line 1888 "effective_tld_names.gperf"
- {"medizinhistorisches.museum", 0},
-#line 474 "effective_tld_names.gperf"
- {"cheltenham.museum", 0},
-#line 3172 "effective_tld_names.gperf"
- {"trento.it", 0},
#line 192 "effective_tld_names.gperf"
- {"arts.co", 0},
-#line 214 "effective_tld_names.gperf"
- {"assn.lk", 0},
-#line 1606 "effective_tld_names.gperf"
- {"karikatur.museum", 0},
-#line 1551 "effective_tld_names.gperf"
- {"jefferson.museum", 0},
-#line 3211 "effective_tld_names.gperf"
- {"tyumen.ru", 0},
-#line 3171 "effective_tld_names.gperf"
- {"trentino.it", 0},
-#line 2533 "effective_tld_names.gperf"
- {"pharmaciens.km", 0},
-#line 2837 "effective_tld_names.gperf"
- {"saskatchewan.museum", 0},
-#line 2772 "effective_tld_names.gperf"
- {"rollag.no", 0},
-#line 2330 "effective_tld_names.gperf"
- {"opole.pl", 0},
-#line 2103 "effective_tld_names.gperf"
- {"nesodden.no", 0},
-#line 443 "effective_tld_names.gperf"
- {"cargo.aero", 0},
-#line 1625 "effective_tld_names.gperf"
- {"kh.ua", 0},
-#line 1017 "effective_tld_names.gperf"
- {"firm.ro", 0},
-#line 136 "effective_tld_names.gperf"
- {"americana.museum", 0},
-#line 1532 "effective_tld_names.gperf"
- {"isla.pr", 0},
-#line 366 "effective_tld_names.gperf"
- {"bo.telemark.no", 0},
-#line 2496 "effective_tld_names.gperf"
- {"paleo.museum", 0},
-#line 2991 "effective_tld_names.gperf"
- {"spydeberg.no", 0},
-#line 3008 "effective_tld_names.gperf"
- {"starostwo.gov.pl", 0},
-#line 3166 "effective_tld_names.gperf"
- {"travel", 0},
-#line 1744 "effective_tld_names.gperf"
- {"leka.no", 0},
-#line 3593 "effective_tld_names.gperf"
- {"xn--vestvgy-ixa6o.no", 0},
-#line 3385 "effective_tld_names.gperf"
- {"whaling.museum", 0},
-#line 3606 "effective_tld_names.gperf"
- {"xn--zf0ao64a.tw", 0},
-#line 2788 "effective_tld_names.gperf"
- {"ru.com", 0},
-#line 2221 "effective_tld_names.gperf"
- {"nieruchomosci.pl", 0},
-#line 746 "effective_tld_names.gperf"
- {"czest.pl", 0},
-#line 3311 "effective_tld_names.gperf"
- {"vestre-toten.no", 0},
-#line 764 "effective_tld_names.gperf"
- {"defense.tn", 0},
-#line 138 "effective_tld_names.gperf"
- {"americanart.museum", 0},
-#line 1563 "effective_tld_names.gperf"
- {"jfk.museum", 0},
-#line 3436 "effective_tld_names.gperf"
- {"xn--comunicaes-v6a2o.museum", 0},
-#line 3210 "effective_tld_names.gperf"
- {"tysvar.no", 0},
-#line 2575 "effective_tld_names.gperf"
- {"port.fr", 0},
-#line 3308 "effective_tld_names.gperf"
- {"vestby.no", 0},
-#line 3548 "effective_tld_names.gperf"
- {"xn--ryken-vua.no", 0},
-#line 3414 "effective_tld_names.gperf"
- {"xn--asky-ira.no", 0},
-#line 3515 "effective_tld_names.gperf"
- {"xn--mosjen-eya.no", 0},
-#line 3106 "effective_tld_names.gperf"
- {"th", 0},
-#line 3163 "effective_tld_names.gperf"
- {"tranoy.no", 0},
-#line 283 "effective_tld_names.gperf"
- {"bardu.no", 0},
-#line 355 "effective_tld_names.gperf"
- {"bjugn.no", 0},
-#line 1453 "effective_tld_names.gperf"
- {"ilawa.pl", 0},
-#line 310 "effective_tld_names.gperf"
- {"berg.no", 0},
-#line 2984 "effective_tld_names.gperf"
- {"southwest.museum", 0},
-#line 3502 "effective_tld_names.gperf"
- {"xn--lrdal-sra.no", 0},
-#line 2251 "effective_tld_names.gperf"
- {"nome.pt", 0},
-#line 1392 "effective_tld_names.gperf"
- {"historyofscience.museum", 0},
-#line 1760 "effective_tld_names.gperf"
- {"lillehammer.no", 0},
-#line 2548 "effective_tld_names.gperf"
- {"pittsburgh.museum", 0},
-#line 3024 "effective_tld_names.gperf"
- {"stockholm.museum", 0},
-#line 3168 "effective_tld_names.gperf"
- {"travel.tt", 0},
-#line 1560 "effective_tld_names.gperf"
- {"jewelry.museum", 0},
-#line 3293 "effective_tld_names.gperf"
- {"vdonsk.ru", 0},
-#line 3022 "effective_tld_names.gperf"
- {"stjordal.no", 0},
-#line 2317 "effective_tld_names.gperf"
- {"olawa.pl", 0},
-#line 3573 "effective_tld_names.gperf"
- {"xn--srreisa-q1a.no", 0},
-#line 3417 "effective_tld_names.gperf"
- {"xn--b-5ga.nordland.no", 0},
+ {"artdeco.museum", 0},
+#line 1072 "effective_tld_names.gperf"
+ {"foundation.museum", 0},
#line 2998 "effective_tld_names.gperf"
- {"sshn.se", 0},
-#line 745 "effective_tld_names.gperf"
- {"czeladz.pl", 0},
+ {"sn", 0},
+#line 993 "effective_tld_names.gperf"
+ {"fareast.ru", 0},
+#line 3090 "effective_tld_names.gperf"
+ {"su", 0},
+#line 580 "effective_tld_names.gperf"
+ {"co.th", 0},
+#line 347 "effective_tld_names.gperf"
+ {"biz.at", 0},
+#line 313 "effective_tld_names.gperf"
+ {"bellevue.museum", 0},
+#line 3431 "effective_tld_names.gperf"
+ {"waw.pl", 0},
+#line 1371 "effective_tld_names.gperf"
+ {"halden.no", 0},
+#line 1877 "effective_tld_names.gperf"
+ {"marnardal.no", 0},
+#line 1065 "effective_tld_names.gperf"
+ {"forlicesena.it", 0},
+#line 2575 "effective_tld_names.gperf"
+ {"ph", 0},
+#line 2068 "effective_tld_names.gperf"
+ {"n.bg", 0},
+#line 3624 "effective_tld_names.gperf"
+ {"xn--snase-nra.no", 0},
+#line 1171 "effective_tld_names.gperf"
+ {"go.th", 0},
+#line 2626 "effective_tld_names.gperf"
+ {"poznan.pl", 0},
+#line 336 "effective_tld_names.gperf"
+ {"bieszczady.pl", 0},
+#line 2995 "effective_tld_names.gperf"
+ {"sm", 0},
+#line 2014 "effective_tld_names.gperf"
+ {"modern.museum", 0},
+#line 1439 "effective_tld_names.gperf"
+ {"horten.no", 0},
+#line 3613 "effective_tld_names.gperf"
+ {"xn--seral-lra.no", 0},
+#line 2062 "effective_tld_names.gperf"
+ {"mw", 0},
+#line 1638 "effective_tld_names.gperf"
+ {"karpacz.pl", 0},
+#line 2528 "effective_tld_names.gperf"
+ {"p.se", 0},
+#line 998 "effective_tld_names.gperf"
+ {"farsund.no", 0},
+#line 1427 "effective_tld_names.gperf"
+ {"hobol.no", 0},
+#line 492 "effective_tld_names.gperf"
+ {"children.museum", 0},
+#line 1966 "effective_tld_names.gperf"
+ {"mil.mv", 0},
+#line 2138 "effective_tld_names.gperf"
+ {"nesoddtangen.no", 0},
#line 2932 "effective_tld_names.gperf"
- {"skiptvet.no", 0},
-#line 273 "effective_tld_names.gperf"
- {"bale.museum", 0},
-#line 1928 "effective_tld_names.gperf"
- {"mil.iq", 0},
-#line 3150 "effective_tld_names.gperf"
- {"tourism.tn", 0},
-#line 3496 "effective_tld_names.gperf"
- {"xn--lesund-hua.no", 0},
-#line 245 "effective_tld_names.gperf"
- {"auto.pl", 0},
-#line 1474 "effective_tld_names.gperf"
- {"indianapolis.museum", 0},
-#line 2738 "effective_tld_names.gperf"
- {"rennesoy.no", 0},
-#line 688 "effective_tld_names.gperf"
- {"communication.museum", 0},
-#line 3072 "effective_tld_names.gperf"
- {"szex.hu", 0},
-#line 3319 "effective_tld_names.gperf"
- {"vgs.no", 0},
-#line 2824 "effective_tld_names.gperf"
- {"sandiego.museum", 0},
-#line 3562 "effective_tld_names.gperf"
- {"xn--smla-hra.no", 0},
-#line 3229 "effective_tld_names.gperf"
- {"ullensvang.no", 0},
-#line 1898 "effective_tld_names.gperf"
- {"metro.tokyo.jp", 1},
-#line 2751 "effective_tld_names.gperf"
- {"rimini.it", 0},
-#line 3160 "effective_tld_names.gperf"
- {"trainer.aero", 0},
-#line 3545 "effective_tld_names.gperf"
- {"xn--rskog-uua.no", 0},
-#line 2770 "effective_tld_names.gperf"
- {"rockart.museum", 0},
-#line 1804 "effective_tld_names.gperf"
- {"lund.no", 0},
-#line 1905 "effective_tld_names.gperf"
- {"michigan.museum", 0},
-#line 3202 "effective_tld_names.gperf"
- {"tw", 0},
-#line 3051 "effective_tld_names.gperf"
- {"surnadal.no", 0},
-#line 3346 "effective_tld_names.gperf"
- {"voagat.no", 0},
-#line 2523 "effective_tld_names.gperf"
- {"perso.tn", 0},
-#line 255 "effective_tld_names.gperf"
- {"axis.museum", 0},
-#line 689 "effective_tld_names.gperf"
- {"communications.museum", 0},
-#line 3394 "effective_tld_names.gperf"
- {"wodzislaw.pl", 0},
-#line 807 "effective_tld_names.gperf"
- {"ebiz.tw", 0},
-#line 195 "effective_tld_names.gperf"
- {"arts.ro", 0},
-#line 683 "effective_tld_names.gperf"
- {"com.uz", 0},
-#line 2838 "effective_tld_names.gperf"
- {"sassari.it", 0},
-#line 2778 "effective_tld_names.gperf"
- {"roros.no", 0},
-#line 2651 "effective_tld_names.gperf"
- {"priv.at", 0},
-#line 3557 "effective_tld_names.gperf"
- {"xn--skjk-soa.no", 0},
-#line 3301 "effective_tld_names.gperf"
- {"vennesla.no", 0},
-#line 333 "effective_tld_names.gperf"
- {"bill.museum", 0},
-#line 469 "effective_tld_names.gperf"
- {"chambagri.fr", 0},
-#line 2741 "effective_tld_names.gperf"
- {"res.in", 0},
-#line 2645 "effective_tld_names.gperf"
- {"press.se", 0},
-#line 1977 "effective_tld_names.gperf"
- {"mobi.na", 0},
-#line 318 "effective_tld_names.gperf"
- {"beskidy.pl", 0},
-#line 3373 "effective_tld_names.gperf"
- {"washingtondc.museum", 0},
-#line 3047 "effective_tld_names.gperf"
- {"sund.no", 0},
-#line 3185 "effective_tld_names.gperf"
- {"tsaritsyn.ru", 0},
-#line 3422 "effective_tld_names.gperf"
- {"xn--bhcavuotna-s4a.no", 0},
-#line 796 "effective_tld_names.gperf"
- {"drobak.no", 0},
-#line 2956 "effective_tld_names.gperf"
- {"so.gov.pl", 0},
-#line 1612 "effective_tld_names.gperf"
- {"katowice.pl", 0},
-#line 1077 "effective_tld_names.gperf"
- {"fukui.jp", 2},
-#line 1997 "effective_tld_names.gperf"
- {"moss.no", 0},
-#line 2297 "effective_tld_names.gperf"
- {"nysa.pl", 0},
-#line 3287 "effective_tld_names.gperf"
- {"varese.it", 0},
-#line 1014 "effective_tld_names.gperf"
- {"firm.ht", 0},
-#line 2525 "effective_tld_names.gperf"
- {"pesaro-urbino.it", 0},
-#line 693 "effective_tld_names.gperf"
- {"computerhistory.museum", 0},
-#line 1897 "effective_tld_names.gperf"
- {"messina.it", 0},
-#line 2994 "effective_tld_names.gperf"
- {"sr.gov.pl", 0},
-#line 2475 "effective_tld_names.gperf"
- {"ostrowwlkp.pl", 0},
-#line 2793 "effective_tld_names.gperf"
- {"rw", 0},
-#line 1652 "effective_tld_names.gperf"
- {"koenig.ru", 0},
-#line 1086 "effective_tld_names.gperf"
+ {"sec.ps", 0},
+#line 493 "effective_tld_names.gperf"
+ {"childrens.museum", 0},
+#line 1722 "effective_tld_names.gperf"
+ {"kvam.no", 0},
+#line 1885 "effective_tld_names.gperf"
+ {"matera.it", 0},
+#line 1336 "effective_tld_names.gperf"
+ {"gs.ol.no", 0},
+#line 1979 "effective_tld_names.gperf"
+ {"mil.vc", 0},
+#line 218 "effective_tld_names.gperf"
+ {"asso.bj", 0},
+#line 1103 "effective_tld_names.gperf"
{"fyresdal.no", 0},
-#line 128 "effective_tld_names.gperf"
+#line 2847 "effective_tld_names.gperf"
+ {"sa", 0},
+#line 396 "effective_tld_names.gperf"
+ {"bremanger.no", 0},
+#line 395 "effective_tld_names.gperf"
+ {"brasil.museum", 0},
+#line 3630 "effective_tld_names.gperf"
+ {"xn--sr-odal-q1a.no", 0},
+#line 2564 "effective_tld_names.gperf"
+ {"perm.ru", 0},
+#line 1043 "effective_tld_names.gperf"
+ {"flakstad.no", 0},
+#line 1332 "effective_tld_names.gperf"
+ {"gs.mr.no", 0},
+#line 95 "effective_tld_names.gperf"
+ {"agrinet.tn", 0},
+#line 1488 "effective_tld_names.gperf"
+ {"in.th", 0},
+#line 1553 "effective_tld_names.gperf"
+ {"iris.arpa", 0},
+#line 3302 "effective_tld_names.gperf"
+ {"uscountryestate.museum", 0},
+#line 2007 "effective_tld_names.gperf"
+ {"mobi.gp", 0},
+#line 1242 "effective_tld_names.gperf"
+ {"gov.je", 0},
+#line 1851 "effective_tld_names.gperf"
+ {"madrid.museum", 0},
+#line 2853 "effective_tld_names.gperf"
+ {"sa.it", 0},
+#line 1159 "effective_tld_names.gperf"
+ {"glogow.pl", 0},
+#line 282 "effective_tld_names.gperf"
+ {"balsan.it", 0},
+#line 3424 "effective_tld_names.gperf"
+ {"wallonie.museum", 0},
+#line 1089 "effective_tld_names.gperf"
+ {"frosinone.it", 0},
+#line 1338 "effective_tld_names.gperf"
+ {"gs.rl.no", 0},
+#line 535 "effective_tld_names.gperf"
+ {"clinton.museum", 0},
+#line 1586 "effective_tld_names.gperf"
+ {"jessheim.no", 0},
+#line 508 "effective_tld_names.gperf"
+ {"cinema.museum", 0},
+#line 3571 "effective_tld_names.gperf"
+ {"xn--mli-tla.no", 0},
+#line 3049 "effective_tld_names.gperf"
+ {"st.no", 0},
+#line 2855 "effective_tld_names.gperf"
+ {"saga.jp", 2},
+#line 3554 "effective_tld_names.gperf"
+ {"xn--linds-pra.no", 0},
+#line 123 "effective_tld_names.gperf"
+ {"alessandria.it", 0},
+#line 2140 "effective_tld_names.gperf"
+ {"nesset.no", 0},
+#line 2013 "effective_tld_names.gperf"
+ {"modena.it", 0},
+#line 1326 "effective_tld_names.gperf"
+ {"gs.bu.no", 0},
+#line 2292 "effective_tld_names.gperf"
+ {"norddal.no", 0},
+#line 1933 "effective_tld_names.gperf"
+ {"miasta.pl", 0},
+#line 1878 "effective_tld_names.gperf"
+ {"maryland.museum", 0},
+#line 77 "effective_tld_names.gperf"
+ {"aejrie.no", 0},
+#line 3305 "effective_tld_names.gperf"
+ {"usenet.pl", 0},
+#line 2589 "effective_tld_names.gperf"
+ {"pisa.it", 0},
+#line 1452 "effective_tld_names.gperf"
+ {"hurdal.no", 0},
+#line 338 "effective_tld_names.gperf"
+ {"bilbao.museum", 0},
+#line 2586 "effective_tld_names.gperf"
+ {"pila.pl", 0},
+#line 1862 "effective_tld_names.gperf"
+ {"malvik.no", 0},
+#line 2179 "effective_tld_names.gperf"
+ {"net.je", 0},
+#line 2561 "effective_tld_names.gperf"
+ {"per.la", 0},
+#line 3601 "effective_tld_names.gperf"
+ {"xn--rland-uua.no", 0},
+#line 3664 "effective_tld_names.gperf"
+ {"xn--yer-zna.no", 0},
+#line 3628 "effective_tld_names.gperf"
+ {"xn--sr-aurdal-l8a.no", 0},
+#line 1925 "effective_tld_names.gperf"
+ {"mesaverde.museum", 0},
+#line 2848 "effective_tld_names.gperf"
+ {"sa.au", 0},
+#line 3115 "effective_tld_names.gperf"
+ {"sy", 0},
+#line 3007 "effective_tld_names.gperf"
+ {"soc.lk", 0},
+#line 1324 "effective_tld_names.gperf"
+ {"gs.aa.no", 0},
+#line 360 "effective_tld_names.gperf"
+ {"bj", 0},
+#line 1937 "effective_tld_names.gperf"
+ {"midsund.no", 0},
+#line 1981 "effective_tld_names.gperf"
+ {"milano.it", 0},
+#line 1622 "effective_tld_names.gperf"
+ {"k12.vi", 0},
+#line 263 "effective_tld_names.gperf"
+ {"b.bg", 0},
+#line 3575 "effective_tld_names.gperf"
+ {"xn--mot-tla.no", 0},
+#line 3013 "effective_tld_names.gperf"
+ {"sola.no", 0},
+#line 1434 "effective_tld_names.gperf"
+ {"holtalen.no", 0},
+#line 2994 "effective_tld_names.gperf"
+ {"slupsk.pl", 0},
+#line 3568 "effective_tld_names.gperf"
+ {"xn--mjndalen-64a.no", 0},
+#line 2262 "effective_tld_names.gperf"
+ {"nittedal.no", 0},
+#line 1374 "effective_tld_names.gperf"
+ {"hamar.no", 0},
+#line 1665 "effective_tld_names.gperf"
+ {"kirkenes.no", 0},
+#line 382 "effective_tld_names.gperf"
+ {"bomlo.no", 0},
+#line 2731 "effective_tld_names.gperf"
+ {"pv.it", 0},
+#line 3675 "effective_tld_names.gperf"
+ {"yamal.ru", 0},
+#line 2706 "effective_tld_names.gperf"
+ {"pro.mv", 0},
+#line 1599 "effective_tld_names.gperf"
+ {"jogasz.hu", 0},
+#line 1469 "effective_tld_names.gperf"
+ {"idrett.no", 0},
+#line 1574 "effective_tld_names.gperf"
+ {"jamal.ru", 0},
+#line 2958 "effective_tld_names.gperf"
+ {"si", 0},
+#line 1477 "effective_tld_names.gperf"
+ {"ilawa.pl", 0},
+#line 1376 "effective_tld_names.gperf"
+ {"hamburg.museum", 0},
+#line 2710 "effective_tld_names.gperf"
+ {"pro.vn", 0},
+#line 3696 "effective_tld_names.gperf"
+ {"zagan.pl", 0},
+#line 2714 "effective_tld_names.gperf"
+ {"project.museum", 0},
+#line 2715 "effective_tld_names.gperf"
+ {"promocion.ar", 1},
+#line 1109 "effective_tld_names.gperf"
+ {"gaivuotna.no", 0},
+#line 1334 "effective_tld_names.gperf"
+ {"gs.nt.no", 0},
+#line 384 "effective_tld_names.gperf"
+ {"boston.museum", 0},
+#line 1333 "effective_tld_names.gperf"
+ {"gs.nl.no", 0},
+#line 2743 "effective_tld_names.gperf"
+ {"qld.edu.au", 0},
+#line 267 "effective_tld_names.gperf"
+ {"babia-gora.pl", 0},
+#line 2959 "effective_tld_names.gperf"
+ {"si.it", 0},
+#line 176 "effective_tld_names.gperf"
+ {"ardal.no", 0},
+#line 729 "effective_tld_names.gperf"
+ {"county.museum", 0},
+#line 531 "effective_tld_names.gperf"
+ {"ck", 2},
+#line 3599 "effective_tld_names.gperf"
+ {"xn--risa-5na.no", 0},
+#line 1874 "effective_tld_names.gperf"
+ {"maritimo.museum", 0},
+#line 2080 "effective_tld_names.gperf"
+ {"namdalseid.no", 0},
+#line 558 "effective_tld_names.gperf"
+ {"co.gy", 0},
+#line 868 "effective_tld_names.gperf"
+ {"edu.kn", 0},
+#line 1849 "effective_tld_names.gperf"
+ {"macerata.it", 0},
+#line 483 "effective_tld_names.gperf"
+ {"cheltenham.museum", 0},
+#line 1419 "effective_tld_names.gperf"
+ {"hk", 0},
+#line 367 "effective_tld_names.gperf"
+ {"blog.br", 0},
+#line 1987 "effective_tld_names.gperf"
+ {"minnesota.museum", 0},
+#line 3300 "effective_tld_names.gperf"
+ {"usantiques.museum", 0},
+#line 2629 "effective_tld_names.gperf"
+ {"pp.se", 0},
+#line 3047 "effective_tld_names.gperf"
+ {"sshn.se", 0},
+#line 1315 "effective_tld_names.gperf"
+ {"grimstad.no", 0},
+#line 1247 "effective_tld_names.gperf"
+ {"gov.kn", 0},
+#line 3708 "effective_tld_names.gperf"
+ {"zoology.museum", 0},
+#line 3000 "effective_tld_names.gperf"
+ {"snaase.no", 0},
+#line 458 "effective_tld_names.gperf"
+ {"castle.museum", 0},
+#line 1873 "effective_tld_names.gperf"
+ {"maritime.museum", 0},
+#line 3093 "effective_tld_names.gperf"
+ {"sula.no", 0},
+#line 1571 "effective_tld_names.gperf"
+ {"iz.hr", 0},
+#line 1398 "effective_tld_names.gperf"
+ {"hemne.no", 0},
+#line 472 "effective_tld_names.gperf"
+ {"center.museum", 0},
+#line 2746 "effective_tld_names.gperf"
+ {"quebec.museum", 0},
+#line 235 "effective_tld_names.gperf"
+ {"atlanta.museum", 0},
+#line 1003 "effective_tld_names.gperf"
+ {"federation.aero", 0},
+#line 1192 "effective_tld_names.gperf"
+ {"gouv.bj", 0},
+#line 245 "effective_tld_names.gperf"
+ {"austevoll.no", 0},
+#line 3555 "effective_tld_names.gperf"
+ {"xn--lns-qla.museum", 0},
+#line 2238 "effective_tld_names.gperf"
+ {"newjersey.museum", 0},
+#line 1443 "effective_tld_names.gperf"
+ {"hoyanger.no", 0},
+#line 459 "effective_tld_names.gperf"
+ {"castres.museum", 0},
+#line 3003 "effective_tld_names.gperf"
+ {"snoasa.no", 0},
+#line 2027 "effective_tld_names.gperf"
+ {"mosreg.ru", 0},
+#line 1904 "effective_tld_names.gperf"
+ {"med.ly", 0},
+#line 2294 "effective_tld_names.gperf"
+ {"nordre-land.no", 0},
+#line 1926 "effective_tld_names.gperf"
+ {"messina.it", 0},
+#line 130 "effective_tld_names.gperf"
{"altoadige.it", 0},
-#line 2526 "effective_tld_names.gperf"
- {"pesarourbino.it", 0},
-#line 2648 "effective_tld_names.gperf"
- {"presse.km", 0},
-#line 1074 "effective_tld_names.gperf"
- {"froya.no", 0},
-#line 2728 "effective_tld_names.gperf"
- {"recreation.aero", 0},
-#line 3061 "effective_tld_names.gperf"
- {"swidnica.pl", 0},
-#line 3081 "effective_tld_names.gperf"
- {"taranto.it", 0},
-#line 3309 "effective_tld_names.gperf"
- {"vestnes.no", 0},
-#line 1589 "effective_tld_names.gperf"
- {"k-uralsk.ru", 0},
-#line 2506 "effective_tld_names.gperf"
- {"pasadena.museum", 0},
-#line 3186 "effective_tld_names.gperf"
- {"tsk.ru", 0},
-#line 2981 "effective_tld_names.gperf"
- {"sosnowiec.pl", 0},
-#line 3011 "effective_tld_names.gperf"
- {"stateofdelaware.museum", 0},
-#line 3259 "effective_tld_names.gperf"
- {"utah.museum", 0},
-#line 3505 "effective_tld_names.gperf"
- {"xn--lten-gra.no", 0},
-#line 3455 "effective_tld_names.gperf"
- {"xn--gls-elac.no", 0},
+#line 398 "effective_tld_names.gperf"
+ {"brindisi.it", 0},
+#line 1136 "effective_tld_names.gperf"
+ {"geology.museum", 0},
+#line 503 "effective_tld_names.gperf"
+ {"chuvashia.ru", 0},
+#line 1020 "effective_tld_names.gperf"
+ {"filatelia.museum", 0},
+#line 1036 "effective_tld_names.gperf"
+ {"fj", 2},
+#line 1025 "effective_tld_names.gperf"
+ {"fineart.museum", 0},
+#line 3077 "effective_tld_names.gperf"
+ {"stordal.no", 0},
+#line 988 "effective_tld_names.gperf"
+ {"f.bg", 0},
+#line 1026 "effective_tld_names.gperf"
+ {"finearts.museum", 0},
+#line 640 "effective_tld_names.gperf"
+ {"com.jo", 0},
+#line 348 "effective_tld_names.gperf"
+ {"biz.az", 0},
+#line 864 "effective_tld_names.gperf"
+ {"edu.jo", 0},
+#line 2608 "effective_tld_names.gperf"
+ {"podlasie.pl", 0},
#line 388 "effective_tld_names.gperf"
- {"brescia.it", 0},
-#line 3103 "effective_tld_names.gperf"
- {"tf", 0},
-#line 1694 "effective_tld_names.gperf"
- {"kvanangen.no", 0},
-#line 1680 "effective_tld_names.gperf"
- {"kuban.ru", 0},
-#line 3388 "effective_tld_names.gperf"
- {"wiki.br", 0},
-#line 3591 "effective_tld_names.gperf"
- {"xn--vard-jra.no", 0},
-#line 777 "effective_tld_names.gperf"
- {"divtasvuodna.no", 0},
-#line 3590 "effective_tld_names.gperf"
- {"xn--vads-jra.no", 0},
-#line 1204 "effective_tld_names.gperf"
- {"gov.dz", 0},
-#line 834 "effective_tld_names.gperf"
- {"edu.dz", 0},
-#line 2055 "effective_tld_names.gperf"
- {"name.pr", 0},
-#line 2128 "effective_tld_names.gperf"
- {"net.dz", 0},
-#line 2473 "effective_tld_names.gperf"
- {"ostroleka.pl", 0},
-#line 215 "effective_tld_names.gperf"
- {"asso.ci", 0},
-#line 784 "effective_tld_names.gperf"
- {"dnepropetrovsk.ua", 0},
-#line 612 "effective_tld_names.gperf"
- {"com.dz", 0},
-#line 2903 "effective_tld_names.gperf"
- {"shiga.jp", 2},
-#line 3513 "effective_tld_names.gperf"
+ {"botany.museum", 0},
+#line 2183 "effective_tld_names.gperf"
+ {"net.kn", 0},
+#line 1149 "effective_tld_names.gperf"
+ {"gjemnes.no", 0},
+#line 2694 "effective_tld_names.gperf"
+ {"pri.ee", 0},
+#line 559 "effective_tld_names.gperf"
+ {"co.hu", 0},
+#line 2598 "effective_tld_names.gperf"
+ {"plants.museum", 0},
+#line 1985 "effective_tld_names.gperf"
+ {"miners.museum", 0},
+#line 2327 "effective_tld_names.gperf"
+ {"nuernberg.museum", 0},
+#line 3638 "effective_tld_names.gperf"
+ {"xn--tjme-hra.no", 0},
+#line 337 "effective_tld_names.gperf"
+ {"bievat.no", 0},
+#line 3287 "effective_tld_names.gperf"
+ {"undersea.museum", 0},
+#line 128 "effective_tld_names.gperf"
+ {"altai.ru", 0},
+#line 160 "effective_tld_names.gperf"
+ {"aomori.jp", 2},
+#line 1243 "effective_tld_names.gperf"
+ {"gov.jo", 0},
+#line 3600 "effective_tld_names.gperf"
+ {"xn--risr-ira.no", 0},
+#line 129 "effective_tld_names.gperf"
+ {"alto-adige.it", 0},
+#line 1310 "effective_tld_names.gperf"
+ {"grane.no", 0},
+#line 2001 "effective_tld_names.gperf"
+ {"mo-i-rana.no", 0},
+#line 1974 "effective_tld_names.gperf"
+ {"mil.st", 0},
+#line 1651 "effective_tld_names.gperf"
+ {"kg", 0},
+#line 713 "effective_tld_names.gperf"
+ {"control.aero", 0},
+#line 1633 "effective_tld_names.gperf"
+ {"karate.museum", 0},
+#line 2947 "effective_tld_names.gperf"
+ {"sh", 0},
+#line 1887 "effective_tld_names.gperf"
+ {"mazowsze.pl", 0},
+#line 2992 "effective_tld_names.gperf"
+ {"sld.pa", 0},
+#line 1517 "effective_tld_names.gperf"
+ {"info.pk", 0},
+#line 89 "effective_tld_names.gperf"
+ {"agdenes.no", 0},
+#line 1645 "effective_tld_names.gperf"
+ {"kazimierz-dolny.pl", 0},
+#line 72 "effective_tld_names.gperf"
+ {"adult.ht", 0},
+#line 2846 "effective_tld_names.gperf"
+ {"s.se", 0},
+#line 462 "effective_tld_names.gperf"
+ {"catanzaro.it", 0},
+#line 1401 "effective_tld_names.gperf"
+ {"herad.no", 0},
+#line 254 "effective_tld_names.gperf"
+ {"averoy.no", 0},
+#line 1049 "effective_tld_names.gperf"
+ {"flog.br", 0},
+#line 479 "effective_tld_names.gperf"
+ {"championship.aero", 0},
+#line 2538 "effective_tld_names.gperf"
+ {"palana.ru", 0},
+#line 315 "effective_tld_names.gperf"
+ {"benevento.it", 0},
+#line 251 "effective_tld_names.gperf"
+ {"automotive.museum", 0},
+#line 2956 "effective_tld_names.gperf"
+ {"shop.pl", 0},
+#line 246 "effective_tld_names.gperf"
+ {"austin.museum", 0},
+#line 340 "effective_tld_names.gperf"
+ {"bindal.no", 0},
+#line 2010 "effective_tld_names.gperf"
+ {"mod.gi", 0},
+#line 1982 "effective_tld_names.gperf"
+ {"military.museum", 0},
+#line 1080 "effective_tld_names.gperf"
+ {"freemasonry.museum", 0},
+#line 1150 "effective_tld_names.gperf"
+ {"gjerdrum.no", 0},
+#line 2180 "effective_tld_names.gperf"
+ {"net.jo", 0},
+#line 2728 "effective_tld_names.gperf"
+ {"public.museum", 0},
+#line 379 "effective_tld_names.gperf"
+ {"bologna.it", 0},
+#line 1328 "effective_tld_names.gperf"
+ {"gs.fm.no", 0},
+#line 2937 "effective_tld_names.gperf"
+ {"seljord.no", 0},
+#line 2289 "effective_tld_names.gperf"
+ {"nord-aurdal.no", 0},
+#line 2328 "effective_tld_names.gperf"
+ {"nuoro.it", 0},
+#line 936 "effective_tld_names.gperf"
+ {"eisenbahn.museum", 0},
+#line 456 "effective_tld_names.gperf"
+ {"caserta.it", 0},
+#line 3010 "effective_tld_names.gperf"
+ {"sogndal.no", 0},
+#line 3633 "effective_tld_names.gperf"
+ {"xn--srreisa-q1a.no", 0},
+#line 1344 "effective_tld_names.gperf"
+ {"gs.va.no", 0},
+#line 1148 "effective_tld_names.gperf"
+ {"giske.no", 0},
+#line 1053 "effective_tld_names.gperf"
+ {"floro.no", 0},
+#line 446 "effective_tld_names.gperf"
+ {"cambridge.museum", 0},
+#line 2866 "effective_tld_names.gperf"
+ {"samara.ru", 0},
+#line 255 "effective_tld_names.gperf"
+ {"aviation.museum", 0},
+#line 1417 "effective_tld_names.gperf"
+ {"hjartdal.no", 0},
+#line 2136 "effective_tld_names.gperf"
+ {"nesna.no", 0},
+#line 1857 "effective_tld_names.gperf"
+ {"malatvuopmi.no", 0},
+#line 143 "effective_tld_names.gperf"
+ {"amsterdam.museum", 0},
+#line 2291 "effective_tld_names.gperf"
+ {"nord-odal.no", 0},
+#line 3467 "effective_tld_names.gperf"
+ {"xn--andy-ira.no", 0},
+#line 280 "effective_tld_names.gperf"
+ {"ballangen.no", 0},
+#line 724 "effective_tld_names.gperf"
+ {"corvette.museum", 0},
+#line 1728 "effective_tld_names.gperf"
+ {"kw", 2},
+#line 3084 "effective_tld_names.gperf"
+ {"strand.no", 0},
+#line 2693 "effective_tld_names.gperf"
+ {"presse.ml", 0},
+#line 1117 "effective_tld_names.gperf"
+ {"garden.museum", 0},
+#line 217 "effective_tld_names.gperf"
+ {"assn.lk", 0},
+#line 393 "effective_tld_names.gperf"
+ {"brand.se", 0},
+#line 1388 "effective_tld_names.gperf"
+ {"haugesund.no", 0},
+#line 995 "effective_tld_names.gperf"
+ {"farmequipment.museum", 0},
+#line 1845 "effective_tld_names.gperf"
+ {"m.bg", 0},
+#line 1138 "effective_tld_names.gperf"
+ {"georgia.museum", 0},
+#line 1325 "effective_tld_names.gperf"
+ {"gs.ah.no", 0},
+#line 973 "effective_tld_names.gperf"
+ {"ethnology.museum", 0},
+#line 2699 "effective_tld_names.gperf"
+ {"priv.no", 0},
+#line 190 "effective_tld_names.gperf"
+ {"artanddesign.museum", 0},
+#line 3415 "effective_tld_names.gperf"
+ {"w.bg", 0},
+#line 747 "effective_tld_names.gperf"
+ {"culture.museum", 0},
+#line 3064 "effective_tld_names.gperf"
+ {"stavern.no", 0},
+#line 1652 "effective_tld_names.gperf"
+ {"kg.kr", 0},
+#line 1920 "effective_tld_names.gperf"
+ {"meldal.no", 0},
+#line 1903 "effective_tld_names.gperf"
+ {"med.ht", 0},
+#line 491 "effective_tld_names.gperf"
+ {"chieti.it", 0},
+#line 3265 "effective_tld_names.gperf"
+ {"u.bg", 0},
+#line 1389 "effective_tld_names.gperf"
+ {"hawaii.museum", 0},
+#line 2118 "effective_tld_names.gperf"
+ {"navigation.aero", 0},
+#line 2700 "effective_tld_names.gperf"
+ {"priv.pl", 0},
+#line 3562 "effective_tld_names.gperf"
+ {"xn--mely-ira.no", 0},
+#line 642 "effective_tld_names.gperf"
+ {"com.ki", 0},
+#line 538 "effective_tld_names.gperf"
+ {"club.tw", 0},
+#line 866 "effective_tld_names.gperf"
+ {"edu.ki", 0},
+#line 2295 "effective_tld_names.gperf"
+ {"nordreisa.no", 0},
+#line 2930 "effective_tld_names.gperf"
+ {"seaport.museum", 0},
+#line 1636 "effective_tld_names.gperf"
+ {"karlsoy.no", 0},
+#line 139 "effective_tld_names.gperf"
+ {"americanantiques.museum", 0},
+#line 2709 "effective_tld_names.gperf"
+ {"pro.tt", 0},
+#line 3592 "effective_tld_names.gperf"
+ {"xn--rady-ira.no", 0},
+#line 1091 "effective_tld_names.gperf"
+ {"froya.no", 0},
+#line 3104 "effective_tld_names.gperf"
+ {"sv", 2},
+#line 3551 "effective_tld_names.gperf"
+ {"xn--lesund-hua.no", 0},
+#line 1245 "effective_tld_names.gperf"
+ {"gov.ki", 0},
+#line 3023 "effective_tld_names.gperf"
+ {"sor-odal.no", 0},
+#line 1038 "effective_tld_names.gperf"
+ {"fjaler.no", 0},
+#line 1589 "effective_tld_names.gperf"
+ {"jewelry.museum", 0},
+#line 2549 "effective_tld_names.gperf"
+ {"pasadena.museum", 0},
+#line 487 "effective_tld_names.gperf"
+ {"chernovtsy.ua", 0},
+#line 1562 "effective_tld_names.gperf"
+ {"isleofman.museum", 0},
+#line 991 "effective_tld_names.gperf"
+ {"family.museum", 0},
+#line 3105 "effective_tld_names.gperf"
+ {"sv.it", 0},
+#line 1864 "effective_tld_names.gperf"
+ {"mandal.no", 0},
+#line 1402 "effective_tld_names.gperf"
+ {"heritage.museum", 0},
+#line 1416 "effective_tld_names.gperf"
+ {"hitra.no", 0},
+#line 3641 "effective_tld_names.gperf"
+ {"xn--trany-yua.no", 0},
+#line 316 "effective_tld_names.gperf"
+ {"berg.no", 0},
+#line 153 "effective_tld_names.gperf"
+ {"andoy.no", 0},
+#line 1658 "effective_tld_names.gperf"
+ {"kherson.ua", 0},
+#line 934 "effective_tld_names.gperf"
+ {"eidsvoll.no", 0},
+#line 1418 "effective_tld_names.gperf"
+ {"hjelmeland.no", 0},
+#line 2117 "effective_tld_names.gperf"
+ {"naval.museum", 0},
+#line 249 "effective_tld_names.gperf"
+ {"author.aero", 0},
+#line 430 "effective_tld_names.gperf"
+ {"bytom.pl", 0},
+#line 1040 "effective_tld_names.gperf"
+ {"fk", 2},
+#line 963 "effective_tld_names.gperf"
+ {"erotica.hu", 0},
+#line 1414 "effective_tld_names.gperf"
+ {"history.museum", 0},
+#line 1954 "effective_tld_names.gperf"
+ {"mil.ge", 0},
+#line 2182 "effective_tld_names.gperf"
+ {"net.ki", 0},
+#line 1050 "effective_tld_names.gperf"
+ {"flora.no", 0},
+#line 3118 "effective_tld_names.gperf"
+ {"syzran.ru", 0},
+#line 177 "effective_tld_names.gperf"
+ {"aremark.no", 0},
+#line 471 "effective_tld_names.gperf"
+ {"celtic.museum", 0},
+#line 1988 "effective_tld_names.gperf"
+ {"missile.museum", 0},
+#line 3612 "effective_tld_names.gperf"
+ {"xn--sandy-yua.no", 0},
+#line 758 "effective_tld_names.gperf"
+ {"czest.pl", 0},
+#line 312 "effective_tld_names.gperf"
+ {"belgorod.ru", 0},
+#line 527 "effective_tld_names.gperf"
+ {"civilaviation.aero", 0},
+#line 745 "effective_tld_names.gperf"
+ {"cultural.museum", 0},
+#line 3085 "effective_tld_names.gperf"
+ {"stranda.no", 0},
+#line 1975 "effective_tld_names.gperf"
+ {"mil.sy", 0},
+#line 2573 "effective_tld_names.gperf"
+ {"pg", 2},
+#line 277 "effective_tld_names.gperf"
+ {"balat.no", 0},
+#line 1382 "effective_tld_names.gperf"
+ {"haram.no", 0},
+#line 632 "effective_tld_names.gperf"
+ {"com.gy", 0},
+#line 2588 "effective_tld_names.gperf"
+ {"pilots.museum", 0},
+#line 523 "effective_tld_names.gperf"
+ {"city.sapporo.jp", 1},
+#line 1229 "effective_tld_names.gperf"
+ {"gov.gg", 0},
+#line 1340 "effective_tld_names.gperf"
+ {"gs.st.no", 0},
+#line 1671 "effective_tld_names.gperf"
+ {"klodzko.pl", 0},
+#line 3576 "effective_tld_names.gperf"
+ {"xn--msy-ula0h.no", 0},
+#line 2934 "effective_tld_names.gperf"
+ {"sel.no", 0},
+#line 2574 "effective_tld_names.gperf"
+ {"pg.it", 0},
+#line 2562 "effective_tld_names.gperf"
+ {"per.nf", 0},
+#line 2957 "effective_tld_names.gperf"
+ {"show.aero", 0},
+#line 505 "effective_tld_names.gperf"
+ {"cieszyn.pl", 0},
+#line 2046 "effective_tld_names.gperf"
+ {"muenster.museum", 0},
+#line 725 "effective_tld_names.gperf"
+ {"cosenza.it", 0},
+#line 1134 "effective_tld_names.gperf"
+ {"genoa.it", 0},
+#line 3025 "effective_tld_names.gperf"
+ {"sorfold.no", 0},
+#line 1408 "effective_tld_names.gperf"
+ {"histoire.museum", 0},
+#line 212 "effective_tld_names.gperf"
+ {"asnes.no", 0},
+#line 1861 "effective_tld_names.gperf"
+ {"malselv.no", 0},
+#line 2290 "effective_tld_names.gperf"
+ {"nord-fron.no", 0},
+#line 2074 "effective_tld_names.gperf"
+ {"nagano.jp", 2},
+#line 399 "effective_tld_names.gperf"
+ {"bristol.museum", 0},
+#line 3438 "effective_tld_names.gperf"
+ {"western.museum", 0},
+#line 274 "effective_tld_names.gperf"
+ {"baidar.no", 0},
+#line 3307 "effective_tld_names.gperf"
+ {"ushistory.museum", 0},
+#line 2035 "effective_tld_names.gperf"
+ {"mragowo.pl", 0},
+#line 2696 "effective_tld_names.gperf"
+ {"priv.at", 0},
+#line 1656 "effective_tld_names.gperf"
+ {"khakassia.ru", 0},
+#line 421 "effective_tld_names.gperf"
+ {"buryatia.ru", 0},
+#line 463 "effective_tld_names.gperf"
+ {"catering.aero", 0},
+#line 319 "effective_tld_names.gperf"
+ {"bergen.no", 0},
+#line 2166 "effective_tld_names.gperf"
+ {"net.gg", 0},
+#line 3041 "effective_tld_names.gperf"
+ {"square.museum", 0},
+#line 1612 "effective_tld_names.gperf"
+ {"judygarland.museum", 0},
+#line 1028 "effective_tld_names.gperf"
+ {"finnoy.no", 0},
+#line 2542 "effective_tld_names.gperf"
+ {"panama.museum", 0},
+#line 1965 "effective_tld_names.gperf"
+ {"mil.mg", 0},
+#line 2891 "effective_tld_names.gperf"
+ {"savona.it", 0},
+#line 2733 "effective_tld_names.gperf"
+ {"pw", 0},
+#line 1883 "effective_tld_names.gperf"
+ {"massacarrara.it", 0},
+#line 2170 "effective_tld_names.gperf"
+ {"net.gy", 0},
+#line 3098 "effective_tld_names.gperf"
+ {"sunndal.no", 0},
+#line 1379 "effective_tld_names.gperf"
+ {"handson.museum", 0},
+#line 1004 "effective_tld_names.gperf"
+ {"fedje.no", 0},
+#line 2076 "effective_tld_names.gperf"
+ {"nagoya.jp", 2},
+#line 1993 "effective_tld_names.gperf"
+ {"mk", 0},
+#line 1967 "effective_tld_names.gperf"
+ {"mil.my", 0},
+#line 3310 "effective_tld_names.gperf"
+ {"ustka.pl", 0},
+#line 2049 "effective_tld_names.gperf"
+ {"muosat.no", 0},
+#line 3276 "effective_tld_names.gperf"
+ {"uk", 2},
+#line 2302 "effective_tld_names.gperf"
+ {"notaires.km", 0},
+#line 279 "effective_tld_names.gperf"
+ {"balestrand.no", 0},
+#line 3618 "effective_tld_names.gperf"
+ {"xn--sknit-yqa.no", 0},
+#line 1650 "effective_tld_names.gperf"
+ {"ketrzyn.pl", 0},
+#line 957 "effective_tld_names.gperf"
+ {"entomology.museum", 0},
+#line 2320 "effective_tld_names.gperf"
+ {"nt.gov.au", 0},
+#line 1899 "effective_tld_names.gperf"
+ {"mecon.ar", 1},
+#line 1692 "effective_tld_names.gperf"
+ {"konyvelo.hu", 0},
+#line 3293 "effective_tld_names.gperf"
+ {"upow.gov.pl", 0},
+#line 2298 "effective_tld_names.gperf"
+ {"norilsk.ru", 0},
+#line 964 "effective_tld_names.gperf"
+ {"erotika.hu", 0},
+#line 1353 "effective_tld_names.gperf"
+ {"guovdageaidnu.no", 0},
+#line 1931 "effective_tld_names.gperf"
+ {"mi.th", 0},
+#line 309 "effective_tld_names.gperf"
+ {"beeldengeluid.museum", 0},
+#line 2601 "effective_tld_names.gperf"
+ {"plc.ly", 0},
+#line 754 "effective_tld_names.gperf"
+ {"cymru.museum", 0},
+#line 2066 "effective_tld_names.gperf"
+ {"mytis.ru", 0},
+#line 247 "effective_tld_names.gperf"
+ {"australia.museum", 0},
+#line 268 "effective_tld_names.gperf"
+ {"badaddja.no", 0},
+#line 2242 "effective_tld_names.gperf"
+ {"newspaper.museum", 0},
+#line 1939 "effective_tld_names.gperf"
+ {"mie.jp", 2},
+#line 1075 "effective_tld_names.gperf"
+ {"frana.no", 0},
+#line 2094 "effective_tld_names.gperf"
+ {"namsskogan.no", 0},
+#line 1627 "effective_tld_names.gperf"
+ {"kalmykia.ru", 0},
+#line 517 "effective_tld_names.gperf"
+ {"city.kyoto.jp", 1},
+#line 321 "effective_tld_names.gperf"
+ {"berlevag.no", 0},
+#line 1634 "effective_tld_names.gperf"
+ {"karelia.ru", 0},
+#line 3604 "effective_tld_names.gperf"
+ {"xn--rros-gra.no", 0},
+#line 2099 "effective_tld_names.gperf"
+ {"naroy.no", 0},
+#line 3065 "effective_tld_names.gperf"
+ {"stavropol.ru", 0},
+#line 1946 "effective_tld_names.gperf"
+ {"mil.az", 0},
+#line 2965 "effective_tld_names.gperf"
+ {"siljan.no", 0},
+#line 1153 "effective_tld_names.gperf"
+ {"gjovik.no", 0},
+#line 947 "effective_tld_names.gperf"
+ {"enebakk.no", 0},
+#line 3320 "effective_tld_names.gperf"
+ {"uzhgorod.ua", 0},
+#line 2052 "effective_tld_names.gperf"
+ {"museet.museum", 0},
+#line 1915 "effective_tld_names.gperf"
+ {"media.pl", 0},
+#line 997 "effective_tld_names.gperf"
+ {"farmstead.museum", 0},
+#line 3572 "effective_tld_names.gperf"
{"xn--mlselv-iua.no", 0},
#line 1619 "effective_tld_names.gperf"
+ {"k.bg", 0},
+#line 2236 "effective_tld_names.gperf"
+ {"neues.museum", 0},
+#line 333 "effective_tld_names.gperf"
+ {"bible.museum", 0},
+#line 1351 "effective_tld_names.gperf"
+ {"gulen.no", 0},
+#line 3026 "effective_tld_names.gperf"
+ {"sorreisa.no", 0},
+#line 3596 "effective_tld_names.gperf"
+ {"xn--rennesy-v1a.no", 0},
+#line 956 "effective_tld_names.gperf"
+ {"entertainment.aero", 0},
+#line 1578 "effective_tld_names.gperf"
+ {"jaworzno.pl", 0},
+#line 1648 "effective_tld_names.gperf"
{"kemerovo.ru", 0},
-#line 1142 "effective_tld_names.gperf"
- {"glogow.pl", 0},
-#line 1486 "effective_tld_names.gperf"
- {"info.la", 0},
-#line 755 "effective_tld_names.gperf"
- {"davvenjarga.no", 0},
-#line 3566 "effective_tld_names.gperf"
- {"xn--snes-poa.no", 0},
-#line 3410 "effective_tld_names.gperf"
- {"xn--55qx5d.hk", 0},
-#line 326 "effective_tld_names.gperf"
- {"bialystok.pl", 0},
-#line 371 "effective_tld_names.gperf"
- {"bolt.hu", 0},
-#line 2790 "effective_tld_names.gperf"
- {"ruovat.no", 0},
-#line 2809 "effective_tld_names.gperf"
- {"saitama.jp", 2},
-#line 790 "effective_tld_names.gperf"
- {"donostia.museum", 0},
-#line 3161 "effective_tld_names.gperf"
- {"trana.no", 0},
-#line 2816 "effective_tld_names.gperf"
- {"salvadordali.museum", 0},
-#line 3555 "effective_tld_names.gperf"
- {"xn--skierv-uta.no", 0},
-#line 2225 "effective_tld_names.gperf"
- {"nittedal.no", 0},
-#line 1135 "effective_tld_names.gperf"
- {"gjesdal.no", 0},
-#line 3448 "effective_tld_names.gperf"
- {"xn--frde-gra.no", 0},
-#line 2058 "effective_tld_names.gperf"
- {"name.vn", 0},
-#line 1637 "effective_tld_names.gperf"
- {"kirov.ru", 0},
-#line 1561 "effective_tld_names.gperf"
- {"jewish.museum", 0},
-#line 1334 "effective_tld_names.gperf"
- {"gwangju.kr", 0},
-#line 1906 "effective_tld_names.gperf"
- {"microlight.aero", 0},
-#line 3206 "effective_tld_names.gperf"
- {"tydal.no", 0},
-#line 2532 "effective_tld_names.gperf"
- {"pharmacien.fr", 0},
-#line 3431 "effective_tld_names.gperf"
- {"xn--brnny-wuac.no", 0},
-#line 2871 "effective_tld_names.gperf"
- {"sciencehistory.museum", 0},
-#line 2522 "effective_tld_names.gperf"
- {"perso.ht", 0},
-#line 2045 "effective_tld_names.gperf"
- {"naklo.pl", 0},
-#line 2373 "effective_tld_names.gperf"
- {"org.dz", 0},
-#line 3063 "effective_tld_names.gperf"
- {"swinoujscie.pl", 0},
-#line 1353 "effective_tld_names.gperf"
- {"hamburg.museum", 0},
-#line 2974 "effective_tld_names.gperf"
- {"sor-odal.no", 0},
-#line 751 "effective_tld_names.gperf"
- {"dagestan.ru", 0},
-#line 1608 "effective_tld_names.gperf"
- {"karmoy.no", 0},
-#line 767 "effective_tld_names.gperf"
- {"denmark.museum", 0},
-#line 1286 "effective_tld_names.gperf"
- {"grandrapids.museum", 0},
-#line 94 "effective_tld_names.gperf"
- {"agro.pl", 0},
-#line 409 "effective_tld_names.gperf"
- {"budejju.no", 0},
-#line 222 "effective_tld_names.gperf"
- {"asso.re", 0},
-#line 2538 "effective_tld_names.gperf"
- {"phoenix.museum", 0},
-#line 3393 "effective_tld_names.gperf"
- {"wloclawek.pl", 0},
-#line 920 "effective_tld_names.gperf"
- {"eisenbahn.museum", 0},
-#line 3053 "effective_tld_names.gperf"
- {"suwalki.pl", 0},
-#line 697 "effective_tld_names.gperf"
- {"consulado.st", 0},
-#line 1621 "effective_tld_names.gperf"
- {"ketrzyn.pl", 0},
-#line 2654 "effective_tld_names.gperf"
- {"priv.no", 0},
-#line 3284 "effective_tld_names.gperf"
- {"vantaa.museum", 0},
-#line 947 "effective_tld_names.gperf"
- {"erotika.hu", 0},
-#line 1670 "effective_tld_names.gperf"
- {"kraanghke.no", 0},
-#line 3113 "effective_tld_names.gperf"
- {"tj", 0},
-#line 2508 "effective_tld_names.gperf"
- {"pavia.it", 0},
-#line 3338 "effective_tld_names.gperf"
- {"viterbo.it", 0},
-#line 3508 "effective_tld_names.gperf"
- {"xn--merker-kua.no", 0},
-#line 2054 "effective_tld_names.gperf"
- {"name.na", 0},
-#line 2581 "effective_tld_names.gperf"
- {"powiat.pl", 0},
-#line 3547 "effective_tld_names.gperf"
- {"xn--rsta-fra.no", 0},
-#line 3564 "effective_tld_names.gperf"
- {"xn--snase-nra.no", 0},
-#line 3583 "effective_tld_names.gperf"
- {"xn--trna-woa.no", 0},
-#line 3582 "effective_tld_names.gperf"
- {"xn--trgstad-r1a.no", 0},
-#line 341 "effective_tld_names.gperf"
- {"biz.az", 0},
-#line 3453 "effective_tld_names.gperf"
- {"xn--givuotna-8ya.no", 0},
-#line 2643 "effective_tld_names.gperf"
- {"press.ma", 0},
-#line 3446 "effective_tld_names.gperf"
- {"xn--fl-zia.no", 0},
-#line 3179 "effective_tld_names.gperf"
- {"tromso.no", 0},
-#line 3500 "effective_tld_names.gperf"
- {"xn--lns-qla.museum", 0},
-#line 2710 "effective_tld_names.gperf"
- {"railway.museum", 0},
-#line 2759 "effective_tld_names.gperf"
- {"rl.no", 0},
-#line 3197 "effective_tld_names.gperf"
- {"tv.br", 0},
-#line 1295 "effective_tld_names.gperf"
- {"groundhandling.aero", 0},
-#line 1004 "effective_tld_names.gperf"
- {"film.hu", 0},
-#line 183 "effective_tld_names.gperf"
- {"art.dz", 0},
-#line 2859 "effective_tld_names.gperf"
- {"schlesisches.museum", 0},
-#line 1719 "effective_tld_names.gperf"
- {"lans.museum", 0},
-#line 3601 "effective_tld_names.gperf"
- {"xn--vry-yla5g.no", 0},
-#line 234 "effective_tld_names.gperf"
- {"audnedaln.no", 0},
-#line 3531 "effective_tld_names.gperf"
- {"xn--porsgu-sta26f.no", 0},
-#line 480 "effective_tld_names.gperf"
- {"chiba.jp", 2},
-#line 3178 "effective_tld_names.gperf"
- {"tromsa.no", 0},
-#line 3621 "effective_tld_names.gperf"
- {"yokohama.jp", 2},
-#line 3390 "effective_tld_names.gperf"
- {"williamsburg.museum", 0},
-#line 3435 "effective_tld_names.gperf"
- {"xn--ciqpn.hk", 0},
-#line 3440 "effective_tld_names.gperf"
- {"xn--dnna-gra.no", 0},
-#line 2472 "effective_tld_names.gperf"
- {"ostroda.pl", 0},
-#line 2079 "effective_tld_names.gperf"
- {"naturhistorisches.museum", 0},
-#line 124 "effective_tld_names.gperf"
- {"alstahaug.no", 0},
-#line 1068 "effective_tld_names.gperf"
- {"frog.museum", 0},
-#line 3366 "effective_tld_names.gperf"
- {"wakayama.jp", 2},
-#line 3479 "effective_tld_names.gperf"
- {"xn--klbu-woa.no", 0},
-#line 2810 "effective_tld_names.gperf"
- {"sakhalin.ru", 0},
-#line 368 "effective_tld_names.gperf"
- {"bokn.no", 0},
-#line 3288 "effective_tld_names.gperf"
- {"varggat.no", 0},
-#line 2222 "effective_tld_names.gperf"
- {"niigata.jp", 2},
-#line 946 "effective_tld_names.gperf"
- {"erotica.hu", 0},
-#line 2301 "effective_tld_names.gperf"
- {"oceanographic.museum", 0},
-#line 3096 "effective_tld_names.gperf"
- {"television.museum", 0},
-#line 2644 "effective_tld_names.gperf"
- {"press.museum", 0},
-#line 529 "effective_tld_names.gperf"
- {"club.tw", 0},
-#line 3146 "effective_tld_names.gperf"
- {"torsken.no", 0},
-#line 473 "effective_tld_names.gperf"
- {"chel.ru", 0},
-#line 1840 "effective_tld_names.gperf"
- {"marburg.museum", 0},
-#line 2895 "effective_tld_names.gperf"
- {"sex.hu", 0},
-#line 486 "effective_tld_names.gperf"
- {"chiropractic.museum", 0},
-#line 3392 "effective_tld_names.gperf"
- {"wlocl.pl", 0},
-#line 2545 "effective_tld_names.gperf"
- {"pisa.it", 0},
-#line 117 "effective_tld_names.gperf"
- {"alabama.museum", 0},
-#line 1384 "effective_tld_names.gperf"
- {"hiroshima.jp", 2},
-#line 2578 "effective_tld_names.gperf"
- {"portlligat.museum", 0},
-#line 1829 "effective_tld_names.gperf"
- {"malbork.pl", 0},
-#line 2080 "effective_tld_names.gperf"
- {"natuurwetenschappen.museum", 0},
-#line 3483 "effective_tld_names.gperf"
- {"xn--krdsherad-m8a.no", 0},
-#line 3575 "effective_tld_names.gperf"
- {"xn--stjrdal-s1a.no", 0},
-#line 3442 "effective_tld_names.gperf"
- {"xn--dyry-ira.no", 0},
-#line 3577 "effective_tld_names.gperf"
- {"xn--stre-toten-zcb.no", 0},
-#line 1812 "effective_tld_names.gperf"
- {"lviv.ua", 0},
-#line 797 "effective_tld_names.gperf"
- {"dudinka.ru", 0},
-#line 1695 "effective_tld_names.gperf"
- {"kvinesdal.no", 0},
-#line 2786 "effective_tld_names.gperf"
- {"rs.ba", 0},
-#line 3522 "effective_tld_names.gperf"
- {"xn--nry-yla5g.no", 0},
-#line 2784 "effective_tld_names.gperf"
- {"royrvik.no", 0},
-#line 3120 "effective_tld_names.gperf"
- {"tm.fr", 0},
-#line 3570 "effective_tld_names.gperf"
- {"xn--sr-odal-q1a.no", 0},
-#line 1615 "effective_tld_names.gperf"
- {"kazan.ru", 0},
-#line 1854 "effective_tld_names.gperf"
- {"massacarrara.it", 0},
-#line 239 "effective_tld_names.gperf"
- {"aurskog-holand.no", 0},
-#line 924 "effective_tld_names.gperf"
- {"elvendrell.museum", 0},
-#line 2842 "effective_tld_names.gperf"
- {"savannahga.museum", 0},
-#line 305 "effective_tld_names.gperf"
- {"belau.pw", 0},
-#line 240 "effective_tld_names.gperf"
- {"austevoll.no", 0},
-#line 1989 "effective_tld_names.gperf"
- {"monticello.museum", 0},
-#line 2493 "effective_tld_names.gperf"
- {"padua.it", 0},
-#line 1362 "effective_tld_names.gperf"
- {"harvestcelebration.museum", 0},
-#line 3143 "effective_tld_names.gperf"
- {"topology.museum", 0},
-#line 1490 "effective_tld_names.gperf"
- {"info.pk", 0},
-#line 2527 "effective_tld_names.gperf"
- {"pescara.it", 0},
-#line 1957 "effective_tld_names.gperf"
- {"missile.museum", 0},
-#line 2931 "effective_tld_names.gperf"
- {"skierva.no", 0},
-#line 1386 "effective_tld_names.gperf"
- {"historical.museum", 0},
-#line 1389 "effective_tld_names.gperf"
- {"historisch.museum", 0},
-#line 3472 "effective_tld_names.gperf"
- {"xn--indery-fya.no", 0},
-#line 3529 "effective_tld_names.gperf"
- {"xn--ostery-fya.no", 0},
-#line 2867 "effective_tld_names.gperf"
- {"scienceandhistory.museum", 0},
-#line 1671 "effective_tld_names.gperf"
- {"kragero.no", 0},
-#line 2501 "effective_tld_names.gperf"
- {"paragliding.aero", 0},
-#line 1681 "effective_tld_names.gperf"
- {"kumamoto.jp", 2},
-#line 3530 "effective_tld_names.gperf"
- {"xn--osyro-wua.no", 0},
-#line 3128 "effective_tld_names.gperf"
- {"tm.se", 0},
-#line 418 "effective_tld_names.gperf"
- {"bydgoszcz.pl", 0},
-#line 263 "effective_tld_names.gperf"
- {"badaddja.no", 0},
-#line 3233 "effective_tld_names.gperf"
- {"um.gov.pl", 0},
-#line 2647 "effective_tld_names.gperf"
- {"presse.fr", 0},
-#line 3032 "effective_tld_names.gperf"
- {"storfjord.no", 0},
-#line 1390 "effective_tld_names.gperf"
- {"historisches.museum", 0},
-#line 3375 "effective_tld_names.gperf"
- {"watchandclock.museum", 0},
-#line 3491 "effective_tld_names.gperf"
- {"xn--laheadju-7ya.no", 0},
-#line 483 "effective_tld_names.gperf"
- {"children.museum", 0},
-#line 1838 "effective_tld_names.gperf"
- {"mantova.it", 0},
-#line 2820 "effective_tld_names.gperf"
- {"sande.more-og-romsdal.no", 0},
-#line 3607 "effective_tld_names.gperf"
- {"xn--zf0avx.hk", 0},
-#line 2590 "effective_tld_names.gperf"
- {"prato.it", 0},
-#line 1636 "effective_tld_names.gperf"
- {"kirkenes.no", 0},
-#line 1138 "effective_tld_names.gperf"
- {"glas.museum", 0},
-#line 1826 "effective_tld_names.gperf"
- {"mail.pl", 0},
-#line 3207 "effective_tld_names.gperf"
- {"tynset.no", 0},
-#line 2734 "effective_tld_names.gperf"
- {"rel.ht", 0},
-#line 3276 "effective_tld_names.gperf"
- {"vagan.no", 0},
-#line 3324 "effective_tld_names.gperf"
- {"vibovalentia.it", 0},
-#line 152 "effective_tld_names.gperf"
- {"annefrank.museum", 0},
-#line 3304 "effective_tld_names.gperf"
- {"verdal.no", 0},
-#line 2201 "effective_tld_names.gperf"
- {"newhampshire.museum", 0},
-#line 1071 "effective_tld_names.gperf"
- {"from.hr", 0},
-#line 484 "effective_tld_names.gperf"
- {"childrens.museum", 0},
-#line 2543 "effective_tld_names.gperf"
- {"pilot.aero", 0},
-#line 3523 "effective_tld_names.gperf"
- {"xn--nttery-byae.no", 0},
-#line 2314 "effective_tld_names.gperf"
- {"okinawa.jp", 2},
-#line 2521 "effective_tld_names.gperf"
- {"perm.ru", 0},
-#line 216 "effective_tld_names.gperf"
- {"asso.dz", 0},
-#line 2941 "effective_tld_names.gperf"
- {"slask.pl", 0},
-#line 1689 "effective_tld_names.gperf"
- {"kuzbass.ru", 0},
-#line 171 "effective_tld_names.gperf"
- {"archaeological.museum", 0},
-#line 412 "effective_tld_names.gperf"
- {"buryatia.ru", 0},
-#line 2546 "effective_tld_names.gperf"
- {"pistoia.it", 0},
-#line 1043 "effective_tld_names.gperf"
- {"folkebibl.no", 0},
-#line 1582 "effective_tld_names.gperf"
- {"judaica.museum", 0},
-#line 1388 "effective_tld_names.gperf"
- {"historichouses.museum", 0},
-#line 2757 "effective_tld_names.gperf"
- {"risor.no", 0},
-#line 2504 "effective_tld_names.gperf"
- {"parma.it", 0},
-#line 1809 "effective_tld_names.gperf"
- {"luxembourg.museum", 0},
-#line 3560 "effective_tld_names.gperf"
- {"xn--slat-5na.no", 0},
-#line 3646 "effective_tld_names.gperf"
- {"zoological.museum", 0},
-#line 3538 "effective_tld_names.gperf"
- {"xn--rholt-mra.no", 0},
-#line 354 "effective_tld_names.gperf"
- {"bjerkreim.no", 0},
-#line 3228 "effective_tld_names.gperf"
- {"ullensaker.no", 0},
-#line 733 "effective_tld_names.gperf"
- {"cultural.museum", 0},
-#line 756 "effective_tld_names.gperf"
- {"davvesiida.no", 0},
-#line 3191 "effective_tld_names.gperf"
- {"turen.tn", 0},
-#line 1595 "effective_tld_names.gperf"
- {"kagawa.jp", 2},
-#line 2026 "effective_tld_names.gperf"
- {"museumvereniging.museum", 0},
-#line 1076 "effective_tld_names.gperf"
- {"fuel.aero", 0},
-#line 3569 "effective_tld_names.gperf"
- {"xn--sr-fron-q1a.no", 0},
-#line 3471 "effective_tld_names.gperf"
- {"xn--hylandet-54a.no", 0},
-#line 2753 "effective_tld_names.gperf"
- {"ringebu.no", 0},
-#line 3581 "effective_tld_names.gperf"
- {"xn--trany-yua.no", 0},
-#line 3579 "effective_tld_names.gperf"
- {"xn--tn0ag.hk", 0},
-#line 3460 "effective_tld_names.gperf"
- {"xn--hbmer-xqa.no", 0},
-#line 2296 "effective_tld_names.gperf"
- {"nyny.museum", 0},
-#line 3315 "effective_tld_names.gperf"
- {"veterinaire.km", 0},
-#line 1629 "effective_tld_names.gperf"
- {"kherson.ua", 0},
-#line 3510 "effective_tld_names.gperf"
- {"xn--mk0axi.hk", 0},
-#line 1141 "effective_tld_names.gperf"
- {"gliwice.pl", 0},
-#line 3234 "effective_tld_names.gperf"
- {"unbi.ba", 0},
-#line 3329 "effective_tld_names.gperf"
- {"vik.no", 0},
-#line 726 "effective_tld_names.gperf"
- {"crew.aero", 0},
-#line 3514 "effective_tld_names.gperf"
- {"xn--moreke-jua.no", 0},
-#line 3203 "effective_tld_names.gperf"
- {"tw.cn", 0},
-#line 3553 "effective_tld_names.gperf"
- {"xn--seral-lra.no", 0},
-#line 3541 "effective_tld_names.gperf"
- {"xn--rland-uua.no", 0},
-#line 3049 "effective_tld_names.gperf"
- {"surgeonshall.museum", 0},
-#line 3303 "effective_tld_names.gperf"
- {"vercelli.it", 0},
-#line 1970 "effective_tld_names.gperf"
- {"mo-i-rana.no", 0},
-#line 3317 "effective_tld_names.gperf"
- {"vf.no", 0},
-#line 3222 "effective_tld_names.gperf"
- {"ug.gov.pl", 0},
-#line 330 "effective_tld_names.gperf"
- {"bieszczady.pl", 0},
-#line 3594 "effective_tld_names.gperf"
- {"xn--vg-yiab.no", 0},
-#line 3196 "effective_tld_names.gperf"
- {"tv.bo", 0},
-#line 3273 "effective_tld_names.gperf"
- {"vaapste.no", 0},
-#line 1992 "effective_tld_names.gperf"
- {"mordovia.ru", 0},
-#line 2044 "effective_tld_names.gperf"
- {"nakhodka.ru", 0},
-#line 299 "effective_tld_names.gperf"
- {"bearalvahki.no", 0},
-#line 178 "effective_tld_names.gperf"
- {"arkhangelsk.ru", 0},
-#line 1638 "effective_tld_names.gperf"
- {"kirovograd.ua", 0},
-#line 2302 "effective_tld_names.gperf"
- {"oceanographique.museum", 0},
-#line 3142 "effective_tld_names.gperf"
- {"tonsberg.no", 0},
-#line 2782 "effective_tld_names.gperf"
- {"rovno.ua", 0},
-#line 1627 "effective_tld_names.gperf"
- {"khakassia.ru", 0},
-#line 137 "effective_tld_names.gperf"
- {"americanantiques.museum", 0},
-#line 315 "effective_tld_names.gperf"
- {"berlevag.no", 0},
-#line 303 "effective_tld_names.gperf"
- {"beeldengeluid.museum", 0},
-#line 3418 "effective_tld_names.gperf"
- {"xn--b-5ga.telemark.no", 0},
-#line 22 "effective_tld_names.gperf"
- {"6bone.pl", 0},
-#line 2652 "effective_tld_names.gperf"
- {"priv.hu", 0},
-#line 1569 "effective_tld_names.gperf"
- {"jobs.tt", 0},
-#line 2716 "effective_tld_names.gperf"
- {"rauma.no", 0},
-#line 3312 "effective_tld_names.gperf"
- {"vestvagoy.no", 0},
-#line 2865 "effective_tld_names.gperf"
- {"science-fiction.museum", 0},
-#line 1672 "effective_tld_names.gperf"
- {"krakow.pl", 0},
-#line 581 "effective_tld_names.gperf"
- {"cody.museum", 0},
-#line 2564 "effective_tld_names.gperf"
- {"podlasie.pl", 0},
-#line 3290 "effective_tld_names.gperf"
- {"vb.it", 0},
-#line 1377 "effective_tld_names.gperf"
- {"hemsedal.no", 0},
-#line 2904 "effective_tld_names.gperf"
- {"shimane.jp", 2},
-#line 2769 "effective_tld_names.gperf"
- {"rochester.museum", 0},
-#line 3177 "effective_tld_names.gperf"
- {"trolley.museum", 0},
-#line 2082 "effective_tld_names.gperf"
- {"naustdal.no", 0},
-#line 2795 "effective_tld_names.gperf"
- {"rybnik.pl", 0},
-#line 3480 "effective_tld_names.gperf"
- {"xn--koluokta-7ya57h.no", 0},
-#line 2669 "effective_tld_names.gperf"
- {"promocion.ar", 1},
#line 1438 "effective_tld_names.gperf"
- {"ibaraki.jp", 2},
-#line 3239 "effective_tld_names.gperf"
- {"unsa.ba", 0},
-#line 734 "effective_tld_names.gperf"
- {"culturalcenter.museum", 0},
-#line 2064 "effective_tld_names.gperf"
- {"nara.jp", 2},
-#line 394 "effective_tld_names.gperf"
- {"broadcast.museum", 0},
-#line 1032 "effective_tld_names.gperf"
- {"flog.br", 0},
-#line 339 "effective_tld_names.gperf"
- {"birthplace.museum", 0},
-#line 3174 "effective_tld_names.gperf"
- {"trieste.it", 0},
-#line 3492 "effective_tld_names.gperf"
- {"xn--langevg-jxa.no", 0},
-#line 1605 "effective_tld_names.gperf"
- {"karelia.ru", 0},
-#line 3348 "effective_tld_names.gperf"
- {"volgograd.ru", 0},
-#line 3349 "effective_tld_names.gperf"
- {"volkenkunde.museum", 0},
-#line 1564 "effective_tld_names.gperf"
- {"jgora.pl", 0},
-#line 3282 "effective_tld_names.gperf"
- {"valley.museum", 0},
-#line 1129 "effective_tld_names.gperf"
- {"gifu.jp", 2},
-#line 3423 "effective_tld_names.gperf"
- {"xn--bhccavuotna-k7a.no", 0},
-#line 3165 "effective_tld_names.gperf"
- {"trapani.it", 0},
-#line 489 "effective_tld_names.gperf"
- {"chocolate.museum", 0},
-#line 1641 "effective_tld_names.gperf"
- {"klepp.no", 0},
-#line 2311 "effective_tld_names.gperf"
- {"oita.jp", 2},
-#line 3310 "effective_tld_names.gperf"
- {"vestre-slidre.no", 0},
-#line 3114 "effective_tld_names.gperf"
- {"tj.cn", 0},
-#line 2873 "effective_tld_names.gperf"
- {"sciencesnaturelles.museum", 0},
-#line 3139 "effective_tld_names.gperf"
- {"tolga.no", 0},
-#line 3152 "effective_tld_names.gperf"
- {"toyama.jp", 2},
-#line 3542 "effective_tld_names.gperf"
- {"xn--rlingen-mxa.no", 0},
-#line 2752 "effective_tld_names.gperf"
- {"rindal.no", 0},
-#line 1861 "effective_tld_names.gperf"
- {"mbone.pl", 0},
-#line 3595 "effective_tld_names.gperf"
- {"xn--vgan-qoa.no", 0},
-#line 3326 "effective_tld_names.gperf"
- {"vic.gov.au", 0},
-#line 3121 "effective_tld_names.gperf"
- {"tm.hu", 0},
-#line 3093 "effective_tld_names.gperf"
- {"technology.museum", 0},
-#line 3589 "effective_tld_names.gperf"
- {"xn--unjrga-rta.no", 0},
-#line 2780 "effective_tld_names.gperf"
- {"rotorcraft.aero", 0},
-#line 3289 "effective_tld_names.gperf"
- {"varoy.no", 0},
-#line 3158 "effective_tld_names.gperf"
- {"trader.aero", 0},
-#line 3587 "effective_tld_names.gperf"
- {"xn--uc0atv.tw", 0},
-#line 969 "effective_tld_names.gperf"
- {"experts-comptables.fr", 0},
-#line 2051 "effective_tld_names.gperf"
- {"name.jo", 0},
-#line 1387 "effective_tld_names.gperf"
- {"historicalsociety.museum", 0},
-#line 1654 "effective_tld_names.gperf"
- {"komforb.se", 0},
-#line 1857 "effective_tld_names.gperf"
- {"matta-varjjat.no", 0},
-#line 2498 "effective_tld_names.gperf"
- {"palmsprings.museum", 0},
-#line 2796 "effective_tld_names.gperf"
- {"rygge.no", 0},
-#line 3167 "effective_tld_names.gperf"
- {"travel.pl", 0},
-#line 328 "effective_tld_names.gperf"
- {"bielawa.pl", 0},
-#line 1686 "effective_tld_names.gperf"
- {"kursk.ru", 0},
-#line 3281 "effective_tld_names.gperf"
- {"valle.no", 0},
-#line 3498 "effective_tld_names.gperf"
- {"xn--lhppi-xqa.no", 0},
-#line 3149 "effective_tld_names.gperf"
- {"tourism.pl", 0},
-#line 3079 "effective_tld_names.gperf"
- {"tananger.no", 0},
-#line 3173 "effective_tld_names.gperf"
- {"treviso.it", 0},
-#line 3512 "effective_tld_names.gperf"
- {"xn--mli-tla.no", 0},
-#line 1697 "effective_tld_names.gperf"
- {"kviteseid.no", 0},
-#line 2666 "effective_tld_names.gperf"
- {"production.aero", 0},
-#line 2771 "effective_tld_names.gperf"
- {"rodoy.no", 0},
-#line 1651 "effective_tld_names.gperf"
- {"koeln.museum", 0},
-#line 2755 "effective_tld_names.gperf"
- {"ringsaker.no", 0},
-#line 3314 "effective_tld_names.gperf"
- {"veterinaire.fr", 0},
-#line 2670 "effective_tld_names.gperf"
- {"pruszkow.pl", 0},
-#line 1394 "effective_tld_names.gperf"
- {"hjartdal.no", 0},
-#line 3458 "effective_tld_names.gperf"
- {"xn--h-2fa.no", 0},
-#line 752 "effective_tld_names.gperf"
- {"dali.museum", 0},
-#line 3600 "effective_tld_names.gperf"
- {"xn--vrggt-xqad.no", 0},
-#line 694 "effective_tld_names.gperf"
- {"conf.lv", 0},
-#line 1691 "effective_tld_names.gperf"
- {"kvafjord.no", 0},
-#line 3559 "effective_tld_names.gperf"
- {"xn--sknland-fxa.no", 0},
-#line 3580 "effective_tld_names.gperf"
- {"xn--tnsberg-q1a.no", 0},
-#line 1718 "effective_tld_names.gperf"
- {"langevag.no", 0},
-#line 1613 "effective_tld_names.gperf"
- {"kautokeino.no", 0},
-#line 3467 "effective_tld_names.gperf"
- {"xn--hobl-ira.no", 0},
-#line 3546 "effective_tld_names.gperf"
- {"xn--rst-0na.no", 0},
-#line 325 "effective_tld_names.gperf"
- {"bialowieza.pl", 0},
-#line 1609 "effective_tld_names.gperf"
- {"karpacz.pl", 0},
-#line 3568 "effective_tld_names.gperf"
- {"xn--sr-aurdal-l8a.no", 0},
-#line 2905 "effective_tld_names.gperf"
- {"shizuoka.jp", 2},
-#line 3325 "effective_tld_names.gperf"
- {"vic.edu.au", 0},
-#line 2567 "effective_tld_names.gperf"
- {"polkowice.pl", 0},
-#line 1078 "effective_tld_names.gperf"
- {"fukuoka.jp", 2},
-#line 2653 "effective_tld_names.gperf"
- {"priv.me", 0},
-#line 3427 "effective_tld_names.gperf"
- {"xn--bjddar-pta.no", 0},
-#line 3503 "effective_tld_names.gperf"
- {"xn--lrenskog-54a.no", 0},
-#line 2906 "effective_tld_names.gperf"
- {"shop.ht", 0},
-#line 3476 "effective_tld_names.gperf"
- {"xn--jrpeland-54a.no", 0},
-#line 3111 "effective_tld_names.gperf"
- {"tingvoll.no", 0},
-#line 3474 "effective_tld_names.gperf"
- {"xn--io0a7i.hk", 0},
-#line 3336 "effective_tld_names.gperf"
- {"virtual.museum", 0},
-#line 485 "effective_tld_names.gperf"
- {"childrensgarden.museum", 0},
-#line 3449 "effective_tld_names.gperf"
- {"xn--frna-woa.no", 0},
-#line 2712 "effective_tld_names.gperf"
- {"rakkestad.no", 0},
-#line 2907 "effective_tld_names.gperf"
- {"shop.hu", 0},
-#line 2776 "effective_tld_names.gperf"
- {"romsa.no", 0},
-#line 3141 "effective_tld_names.gperf"
- {"tomsk.ru", 0},
-#line 3107 "effective_tld_names.gperf"
- {"theater.museum", 0},
-#line 3493 "effective_tld_names.gperf"
- {"xn--lcvr32d.hk", 0},
-#line 3085 "effective_tld_names.gperf"
- {"tas.gov.au", 0},
-#line 3399 "effective_tld_names.gperf"
- {"wroc.pl", 0},
-#line 3602 "effective_tld_names.gperf"
- {"xn--wcvs22d.hk", 0},
-#line 2552 "effective_tld_names.gperf"
- {"planetarium.museum", 0},
-#line 3277 "effective_tld_names.gperf"
- {"vagsoy.no", 0},
-#line 995 "effective_tld_names.gperf"
- {"fhsk.se", 0},
-#line 3543 "effective_tld_names.gperf"
- {"xn--rmskog-bya.no", 0},
-#line 1684 "effective_tld_names.gperf"
- {"kunstunddesign.museum", 0},
-#line 490 "effective_tld_names.gperf"
- {"christiansburg.museum", 0},
-#line 3481 "effective_tld_names.gperf"
- {"xn--krager-gya.no", 0},
-#line 1098 "effective_tld_names.gperf"
- {"gangaviika.no", 0},
-#line 2561 "effective_tld_names.gperf"
- {"po.gov.pl", 0},
-#line 2830 "effective_tld_names.gperf"
- {"santabarbara.museum", 0},
-#line 1751 "effective_tld_names.gperf"
- {"lewismiller.museum", 0},
-#line 3450 "effective_tld_names.gperf"
- {"xn--frya-hra.no", 0},
-#line 2777 "effective_tld_names.gperf"
- {"romskog.no", 0},
+ {"horology.museum", 0},
+#line 928 "effective_tld_names.gperf"
+ {"egyptian.museum", 0},
+#line 2597 "effective_tld_names.gperf"
+ {"plantation.museum", 0},
+#line 751 "effective_tld_names.gperf"
+ {"cx", 0},
+#line 259 "effective_tld_names.gperf"
+ {"ax", 0},
+#line 343 "effective_tld_names.gperf"
+ {"birdart.museum", 0},
#line 1584 "effective_tld_names.gperf"
- {"juedisches.museum", 0},
-#line 1659 "effective_tld_names.gperf"
- {"kongsberg.no", 0},
-#line 2807 "effective_tld_names.gperf"
- {"saga.jp", 2},
-#line 385 "effective_tld_names.gperf"
- {"brandywinevalley.museum", 0},
-#line 2983 "effective_tld_names.gperf"
- {"southcarolina.museum", 0},
-#line 2713 "effective_tld_names.gperf"
- {"ralingen.no", 0},
-#line 504 "effective_tld_names.gperf"
- {"city.hu", 0},
-#line 3136 "effective_tld_names.gperf"
- {"tokke.no", 0},
+ {"jeonnam.kr", 0},
+#line 524 "effective_tld_names.gperf"
+ {"city.sendai.jp", 1},
+#line 586 "effective_tld_names.gperf"
+ {"co.uz", 0},
+#line 585 "effective_tld_names.gperf"
+ {"co.us", 0},
+#line 3290 "effective_tld_names.gperf"
+ {"university.museum", 0},
+#line 262 "effective_tld_names.gperf"
+ {"az.us", 0},
+#line 1468 "effective_tld_names.gperf"
+ {"id.us", 0},
+#line 201 "effective_tld_names.gperf"
+ {"as.us", 0},
+#line 717 "effective_tld_names.gperf"
+ {"coop.ht", 0},
+#line 1152 "effective_tld_names.gperf"
+ {"gjesdal.no", 0},
+#line 743 "effective_tld_names.gperf"
+ {"ct.us", 0},
+#line 1476 "effective_tld_names.gperf"
+ {"il.us", 0},
+#line 118 "effective_tld_names.gperf"
+ {"al.us", 0},
+#line 1440 "effective_tld_names.gperf"
+ {"hotel.hu", 0},
+#line 1881 "effective_tld_names.gperf"
+ {"masoy.no", 0},
+#line 445 "effective_tld_names.gperf"
+ {"caltanissetta.it", 0},
+#line 413 "effective_tld_names.gperf"
+ {"bryne.no", 0},
+#line 2614 "effective_tld_names.gperf"
+ {"pomorze.pl", 0},
+#line 134 "effective_tld_names.gperf"
+ {"amber.museum", 0},
+#line 748 "effective_tld_names.gperf"
+ {"cuneo.it", 0},
+#line 171 "effective_tld_names.gperf"
+ {"ar.us", 0},
#line 2705 "effective_tld_names.gperf"
- {"radoy.no", 0},
-#line 358 "effective_tld_names.gperf"
- {"blog.br", 0},
+ {"pro.ht", 0},
+#line 669 "effective_tld_names.gperf"
+ {"com.ph", 0},
+#line 1582 "effective_tld_names.gperf"
+ {"jelenia-gora.pl", 0},
+#line 893 "effective_tld_names.gperf"
+ {"edu.ph", 0},
+#line 2946 "effective_tld_names.gperf"
+ {"sg", 0},
+#line 3642 "effective_tld_names.gperf"
+ {"xn--trgstad-r1a.no", 0},
+#line 2123 "effective_tld_names.gperf"
+ {"nd.us", 0},
+#line 939 "effective_tld_names.gperf"
+ {"elk.pl", 0},
+#line 1005 "effective_tld_names.gperf"
+ {"fermo.it", 0},
+#line 194 "effective_tld_names.gperf"
+ {"artgallery.museum", 0},
+#line 3101 "effective_tld_names.gperf"
+ {"surnadal.no", 0},
+#line 3615 "effective_tld_names.gperf"
+ {"xn--skierv-uta.no", 0},
+#line 223 "effective_tld_names.gperf"
+ {"asso.ht", 0},
+#line 710 "effective_tld_names.gperf"
+ {"consulting.aero", 0},
+#line 704 "effective_tld_names.gperf"
+ {"computerhistory.museum", 0},
+#line 1670 "effective_tld_names.gperf"
+ {"klepp.no", 0},
+#line 2021 "effective_tld_names.gperf"
+ {"montreal.museum", 0},
+#line 2702 "effective_tld_names.gperf"
+ {"pro.az", 0},
+#line 389 "effective_tld_names.gperf"
+ {"bozen.it", 0},
+#line 1273 "effective_tld_names.gperf"
+ {"gov.ph", 0},
+#line 3075 "effective_tld_names.gperf"
+ {"stor-elvdal.no", 0},
+#line 3634 "effective_tld_names.gperf"
+ {"xn--srum-gra.no", 0},
+#line 2130 "effective_tld_names.gperf"
+ {"ne.us", 0},
+#line 1097 "effective_tld_names.gperf"
+ {"fundacio.museum", 0},
+#line 2534 "effective_tld_names.gperf"
+ {"paderborn.museum", 0},
+#line 1490 "effective_tld_names.gperf"
+ {"in.us", 0},
#line 17 "effective_tld_names.gperf"
{"2000.hu", 0},
-#line 3347 "effective_tld_names.gperf"
- {"volda.no", 0},
-#line 3299 "effective_tld_names.gperf"
- {"venezia.it", 0},
-#line 2789 "effective_tld_names.gperf"
- {"rubtsovsk.ru", 0},
-#line 3333 "effective_tld_names.gperf"
- {"vindafjord.no", 0},
-#line 3337 "effective_tld_names.gperf"
- {"virtuel.museum", 0},
-#line 3428 "effective_tld_names.gperf"
- {"xn--blt-elab.no", 0},
-#line 1620 "effective_tld_names.gperf"
- {"kepno.pl", 0},
-#line 292 "effective_tld_names.gperf"
- {"batsfjord.no", 0},
-#line 3475 "effective_tld_names.gperf"
- {"xn--jlster-bya.no", 0},
-#line 2742 "effective_tld_names.gperf"
- {"research.aero", 0},
-#line 2207 "effective_tld_names.gperf"
- {"newyork.museum", 0},
-#line 2773 "effective_tld_names.gperf"
- {"roma.it", 0},
-#line 3296 "effective_tld_names.gperf"
- {"vefsn.no", 0},
-#line 3083 "effective_tld_names.gperf"
- {"tarnobrzeg.pl", 0},
-#line 1682 "effective_tld_names.gperf"
- {"kunst.museum", 0},
-#line 2563 "effective_tld_names.gperf"
- {"podhale.pl", 0},
-#line 1721 "effective_tld_names.gperf"
- {"laquila.it", 0},
-#line 3604 "effective_tld_names.gperf"
- {"xn--ygarden-p1a.no", 0},
-#line 978 "effective_tld_names.gperf"
- {"farmequipment.museum", 0},
-#line 3525 "effective_tld_names.gperf"
- {"xn--od0alg.cn", 0},
-#line 3084 "effective_tld_names.gperf"
- {"tas.edu.au", 0},
-#line 3200 "effective_tld_names.gperf"
- {"tvedestrand.no", 0},
-#line 3330 "effective_tld_names.gperf"
- {"viking.museum", 0},
-#line 453 "effective_tld_names.gperf"
- {"catanzaro.it", 0},
-#line 2657 "effective_tld_names.gperf"
- {"pro.az", 0},
-#line 1687 "effective_tld_names.gperf"
- {"kustanai.ru", 0},
-#line 1553 "effective_tld_names.gperf"
- {"jelenia-gora.pl", 0},
-#line 1653 "effective_tld_names.gperf"
- {"kolobrzeg.pl", 0},
-#line 3627 "effective_tld_names.gperf"
- {"yuzhno-sakhalinsk.ru", 0},
-#line 3183 "effective_tld_names.gperf"
- {"trysil.no", 0},
-#line 2723 "effective_tld_names.gperf"
- {"realestate.pl", 0},
-#line 3351 "effective_tld_names.gperf"
- {"voronezh.ru", 0},
-#line 1461 "effective_tld_names.gperf"
- {"in-addr.arpa", 0},
-#line 2707 "effective_tld_names.gperf"
- {"rahkkeravju.no", 0},
-#line 1688 "effective_tld_names.gperf"
- {"kutno.pl", 0},
-#line 3561 "effective_tld_names.gperf"
- {"xn--slt-elab.no", 0},
-#line 3511 "effective_tld_names.gperf"
- {"xn--mlatvuopmi-s4a.no", 0},
-#line 2568 "effective_tld_names.gperf"
- {"poltava.ua", 0},
-#line 3181 "effective_tld_names.gperf"
- {"trust.museum", 0},
-#line 2224 "effective_tld_names.gperf"
- {"nissedal.no", 0},
-#line 528 "effective_tld_names.gperf"
- {"club.aero", 0},
-#line 3164 "effective_tld_names.gperf"
- {"transport.museum", 0},
-#line 1601 "effective_tld_names.gperf"
- {"kanagawa.jp", 2},
-#line 1661 "effective_tld_names.gperf"
- {"konin.pl", 0},
-#line 1374 "effective_tld_names.gperf"
- {"hembygdsforbund.museum", 0},
-#line 1696 "effective_tld_names.gperf"
- {"kvinnherad.no", 0},
-#line 1585 "effective_tld_names.gperf"
- {"juif.museum", 0},
-#line 1628 "effective_tld_names.gperf"
- {"kharkov.ua", 0},
-#line 1673 "effective_tld_names.gperf"
- {"krasnoyarsk.ru", 0},
-#line 3208 "effective_tld_names.gperf"
- {"tysfjord.no", 0},
-#line 3331 "effective_tld_names.gperf"
- {"vikna.no", 0},
-#line 2537 "effective_tld_names.gperf"
- {"philately.museum", 0},
-#line 1655 "effective_tld_names.gperf"
- {"komi.ru", 0},
-#line 1657 "effective_tld_names.gperf"
- {"kommune.no", 0},
-#line 3462 "effective_tld_names.gperf"
- {"xn--hery-ira.nordland.no", 0},
-#line 3263 "effective_tld_names.gperf"
- {"uw.gov.pl", 0},
-#line 2763 "effective_tld_names.gperf"
- {"rnrt.tn", 0},
-#line 2072 "effective_tld_names.gperf"
- {"nationalheritage.museum", 0},
-#line 487 "effective_tld_names.gperf"
- {"chirurgiens-dentistes.fr", 0},
-#line 2487 "effective_tld_names.gperf"
- {"pa.gov.pl", 0},
-#line 2655 "effective_tld_names.gperf"
- {"priv.pl", 0},
-#line 2071 "effective_tld_names.gperf"
- {"nationalfirearms.museum", 0},
-#line 1648 "effective_tld_names.gperf"
- {"kobierzyce.pl", 0},
-#line 3421 "effective_tld_names.gperf"
- {"xn--berlevg-jxa.no", 0},
-#line 2711 "effective_tld_names.gperf"
- {"raisa.no", 0},
-#line 3416 "effective_tld_names.gperf"
- {"xn--avery-yua.no", 0},
-#line 2743 "effective_tld_names.gperf"
- {"research.museum", 0},
-#line 278 "effective_tld_names.gperf"
- {"balsfjord.no", 0},
-#line 3586 "effective_tld_names.gperf"
- {"xn--uc0atv.hk", 0},
-#line 3447 "effective_tld_names.gperf"
- {"xn--flor-jra.no", 0},
-#line 2205 "effective_tld_names.gperf"
- {"news.hu", 0},
-#line 1583 "effective_tld_names.gperf"
- {"judygarland.museum", 0},
-#line 1570 "effective_tld_names.gperf"
- {"jogasz.hu", 0},
-#line 3457 "effective_tld_names.gperf"
- {"xn--gmqw5a.hk", 0},
-#line 3328 "effective_tld_names.gperf"
- {"video.hu", 0},
-#line 3509 "effective_tld_names.gperf"
- {"xn--mjndalen-64a.no", 0},
-#line 3190 "effective_tld_names.gperf"
- {"turek.pl", 0},
-#line 2665 "effective_tld_names.gperf"
- {"prochowice.pl", 0},
-#line 3549 "effective_tld_names.gperf"
- {"xn--ryrvik-bya.no", 0},
-#line 2775 "effective_tld_names.gperf"
- {"rome.it", 0},
-#line 1012 "effective_tld_names.gperf"
- {"firenze.it", 0},
-#line 1079 "effective_tld_names.gperf"
- {"fukushima.jp", 2},
-#line 1427 "effective_tld_names.gperf"
- {"huissier-justice.fr", 0},
-#line 2642 "effective_tld_names.gperf"
- {"press.aero", 0},
-#line 2774 "effective_tld_names.gperf"
- {"roma.museum", 0},
-#line 3297 "effective_tld_names.gperf"
- {"vega.no", 0},
-#line 2681 "effective_tld_names.gperf"
- {"publ.pt", 0},
-#line 3572 "effective_tld_names.gperf"
- {"xn--srfold-bya.no", 0},
-#line 1617 "effective_tld_names.gperf"
- {"kchr.ru", 0},
-#line 1707 "effective_tld_names.gperf"
- {"la-spezia.it", 0},
-#line 3286 "effective_tld_names.gperf"
- {"vardo.no", 0},
-#line 3588 "effective_tld_names.gperf"
- {"xn--uc0ay4a.hk", 0},
-#line 3578 "effective_tld_names.gperf"
- {"xn--tjme-hra.no", 0},
-#line 2733 "effective_tld_names.gperf"
- {"reklam.hu", 0},
-#line 3519 "effective_tld_names.gperf"
- {"xn--muost-0qa.no", 0},
-#line 1611 "effective_tld_names.gperf"
- {"kaszuby.pl", 0},
-#line 3307 "effective_tld_names.gperf"
- {"versailles.museum", 0},
-#line 3194 "effective_tld_names.gperf"
- {"tuva.ru", 0},
-#line 3413 "effective_tld_names.gperf"
- {"xn--aroport-bya.ci", 0},
-#line 2542 "effective_tld_names.gperf"
- {"pila.pl", 0},
-#line 2717 "effective_tld_names.gperf"
- {"ravenna.it", 0},
-#line 3528 "effective_tld_names.gperf"
- {"xn--oppegrd-ixa.no", 0},
-#line 2791 "effective_tld_names.gperf"
- {"russia.museum", 0},
-#line 2768 "effective_tld_names.gperf"
- {"roan.no", 0},
-#line 2709 "effective_tld_names.gperf"
- {"railroad.museum", 0},
-#line 1600 "effective_tld_names.gperf"
- {"kamchatka.ru", 0},
-#line 713 "effective_tld_names.gperf"
- {"cosenza.it", 0},
-#line 1035 "effective_tld_names.gperf"
- {"florida.museum", 0},
-#line 3534 "effective_tld_names.gperf"
- {"xn--rde-ula.no", 0},
-#line 1675 "effective_tld_names.gperf"
- {"kristiansund.no", 0},
-#line 773 "effective_tld_names.gperf"
- {"dgca.aero", 0},
-#line 3095 "effective_tld_names.gperf"
- {"telekommunikation.museum", 0},
-#line 3192 "effective_tld_names.gperf"
- {"turin.it", 0},
-#line 3332 "effective_tld_names.gperf"
- {"village.museum", 0},
-#line 2640 "effective_tld_names.gperf"
- {"preservation.museum", 0},
-#line 3484 "effective_tld_names.gperf"
- {"xn--krehamn-dxa.no", 0},
-#line 1650 "effective_tld_names.gperf"
- {"koebenhavn.museum", 0},
-#line 369 "effective_tld_names.gperf"
- {"boleslawiec.pl", 0},
-#line 3551 "effective_tld_names.gperf"
- {"xn--sandnessjen-ogb.no", 0},
-#line 1557 "effective_tld_names.gperf"
- {"jessheim.no", 0},
-#line 391 "effective_tld_names.gperf"
- {"british-library.uk", 1},
-#line 2704 "effective_tld_names.gperf"
- {"radom.pl", 0},
-#line 2758 "effective_tld_names.gperf"
- {"rissa.no", 0},
-#line 393 "effective_tld_names.gperf"
- {"britishcolumbia.museum", 0},
-#line 2744 "effective_tld_names.gperf"
- {"resistance.museum", 0},
-#line 3494 "effective_tld_names.gperf"
- {"xn--ldingen-q1a.no", 0},
-#line 1562 "effective_tld_names.gperf"
- {"jewishart.museum", 0},
-#line 2547 "effective_tld_names.gperf"
- {"pisz.pl", 0},
-#line 2565 "effective_tld_names.gperf"
- {"pol.dz", 0},
-#line 3275 "effective_tld_names.gperf"
- {"vaga.no", 0},
-#line 3148 "effective_tld_names.gperf"
- {"touch.museum", 0},
-#line 3465 "effective_tld_names.gperf"
- {"xn--hmmrfeasta-s4ac.no", 0},
-#line 2908 "effective_tld_names.gperf"
- {"shop.pl", 0},
-#line 3188 "effective_tld_names.gperf"
- {"tula.ru", 0},
-#line 3078 "effective_tld_names.gperf"
- {"tana.no", 0},
-#line 1674 "effective_tld_names.gperf"
- {"kristiansand.no", 0},
-#line 1788 "effective_tld_names.gperf"
- {"lowicz.pl", 0},
-#line 3571 "effective_tld_names.gperf"
- {"xn--sr-varanger-ggb.no", 0},
-#line 2736 "effective_tld_names.gperf"
- {"rendalen.no", 0},
-#line 3599 "effective_tld_names.gperf"
- {"xn--vre-eiker-k8a.no", 0},
-#line 3112 "effective_tld_names.gperf"
- {"tinn.no", 0},
-#line 3080 "effective_tld_names.gperf"
- {"tank.museum", 0},
-#line 3098 "effective_tld_names.gperf"
- {"terni.it", 0},
-#line 1693 "effective_tld_names.gperf"
- {"kvam.no", 0},
-#line 2714 "effective_tld_names.gperf"
- {"rana.no", 0},
-#line 3182 "effective_tld_names.gperf"
- {"trustee.museum", 0},
-#line 1598 "effective_tld_names.gperf"
- {"kalmykia.ru", 0},
-#line 2797 "effective_tld_names.gperf"
- {"rzeszow.pl", 0},
-#line 1603 "effective_tld_names.gperf"
- {"karasjok.no", 0},
-#line 3109 "effective_tld_names.gperf"
- {"time.no", 0},
-#line 3468 "effective_tld_names.gperf"
- {"xn--holtlen-hxa.no", 0},
-#line 2507 "effective_tld_names.gperf"
- {"passenger-association.aero", 0},
-#line 1702 "effective_tld_names.gperf"
- {"kyoto.jp", 2},
-#line 3279 "effective_tld_names.gperf"
- {"valer.hedmark.no", 0},
-#line 2319 "effective_tld_names.gperf"
- {"olkusz.pl", 0},
-#line 3159 "effective_tld_names.gperf"
- {"trading.aero", 0},
-#line 3274 "effective_tld_names.gperf"
- {"vadso.no", 0},
-#line 2539 "effective_tld_names.gperf"
- {"photography.museum", 0},
-#line 1725 "effective_tld_names.gperf"
- {"laspezia.it", 0},
-#line 3108 "effective_tld_names.gperf"
- {"time.museum", 0},
-#line 3487 "effective_tld_names.gperf"
- {"xn--kvfjord-nxa.no", 0},
-#line 3374 "effective_tld_names.gperf"
- {"watch-and-clock.museum", 0},
-#line 1649 "effective_tld_names.gperf"
- {"kochi.jp", 2},
-#line 2821 "effective_tld_names.gperf"
- {"sande.vestfold.no", 0},
-#line 3023 "effective_tld_names.gperf"
- {"stjordalshalsen.no", 0},
-#line 3176 "effective_tld_names.gperf"
- {"trogstad.no", 0},
-#line 2779 "effective_tld_names.gperf"
- {"rost.no", 0},
-#line 749 "effective_tld_names.gperf"
- {"daegu.kr", 0},
-#line 3334 "effective_tld_names.gperf"
- {"vinnica.ua", 0},
-#line 2313 "effective_tld_names.gperf"
- {"okayama.jp", 2},
-#line 3100 "effective_tld_names.gperf"
- {"test.ru", 0},
-#line 2739 "effective_tld_names.gperf"
- {"repbody.aero", 0},
-#line 3046 "effective_tld_names.gperf"
- {"sumy.ua", 0},
-#line 2715 "effective_tld_names.gperf"
- {"randaberg.no", 0},
-#line 508 "effective_tld_names.gperf"
- {"city.kyoto.jp", 1},
-#line 3469 "effective_tld_names.gperf"
- {"xn--hpmir-xqa.no", 0},
-#line 3302 "effective_tld_names.gperf"
- {"verbania.it", 0},
-#line 1720 "effective_tld_names.gperf"
- {"lapy.pl", 0},
-#line 3350 "effective_tld_names.gperf"
- {"vologda.ru", 0},
-#line 1639 "effective_tld_names.gperf"
- {"kitakyushu.jp", 2},
-#line 3201 "effective_tld_names.gperf"
- {"tver.ru", 0},
-#line 1677 "effective_tld_names.gperf"
- {"krokstadelva.no", 0},
-#line 3170 "effective_tld_names.gperf"
- {"tree.museum", 0},
-#line 2909 "effective_tld_names.gperf"
- {"show.aero", 0},
-#line 3441 "effective_tld_names.gperf"
- {"xn--drbak-wua.no", 0},
-#line 3240 "effective_tld_names.gperf"
- {"upow.gov.pl", 0},
-#line 3533 "effective_tld_names.gperf"
- {"xn--rdal-poa.no", 0},
-#line 1662 "effective_tld_names.gperf"
- {"konskowola.pl", 0},
-#line 3110 "effective_tld_names.gperf"
- {"timekeeping.museum", 0},
-#line 1663 "effective_tld_names.gperf"
- {"konyvelo.hu", 0},
-#line 2703 "effective_tld_names.gperf"
- {"rade.no", 0},
-#line 2730 "effective_tld_names.gperf"
- {"reggio-emilia.it", 0},
-#line 2939 "effective_tld_names.gperf"
- {"skydiving.aero", 0},
-#line 102 "effective_tld_names.gperf"
- {"air-traffic-control.aero", 0},
-#line 3003 "effective_tld_names.gperf"
- {"stalowa-wola.pl", 0},
-#line 3077 "effective_tld_names.gperf"
- {"tambov.ru", 0},
-#line 3175 "effective_tld_names.gperf"
- {"troandin.no", 0},
-#line 1616 "effective_tld_names.gperf"
- {"kazimierz-dolny.pl", 0},
-#line 776 "effective_tld_names.gperf"
- {"discovery.museum", 0},
-#line 2579 "effective_tld_names.gperf"
- {"posts-and-telecommunications.museum", 0},
-#line 1664 "effective_tld_names.gperf"
- {"kopervik.no", 0},
-#line 3115 "effective_tld_names.gperf"
- {"tjeldsund.no", 0},
-#line 2641 "effective_tld_names.gperf"
- {"presidio.museum", 0},
-#line 3425 "effective_tld_names.gperf"
- {"xn--bievt-0qa.no", 0},
-#line 3470 "effective_tld_names.gperf"
- {"xn--hyanger-q1a.no", 0},
-#line 1630 "effective_tld_names.gperf"
- {"khmelnitskiy.ua", 0},
-#line 1642 "effective_tld_names.gperf"
- {"klodzko.pl", 0},
-#line 1634 "effective_tld_names.gperf"
- {"kids.us", 0},
-#line 3445 "effective_tld_names.gperf"
- {"xn--fjord-lra.no", 0},
-#line 3283 "effective_tld_names.gperf"
- {"vang.no", 0},
-#line 3153 "effective_tld_names.gperf"
- {"tozsde.hu", 0},
-#line 3205 "effective_tld_names.gperf"
- {"tychy.pl", 0},
-#line 3419 "effective_tld_names.gperf"
- {"xn--bdddj-mrabd.no", 0},
-#line 3335 "effective_tld_names.gperf"
- {"virginia.museum", 0},
+#line 2225 "effective_tld_names.gperf"
+ {"net.th", 0},
+#line 1442 "effective_tld_names.gperf"
+ {"house.museum", 0},
#line 1626 "effective_tld_names.gperf"
- {"khabarovsk.ru", 0},
-#line 3116 "effective_tld_names.gperf"
- {"tjome.no", 0},
-#line 3082 "effective_tld_names.gperf"
- {"targi.pl", 0},
-#line 1676 "effective_tld_names.gperf"
- {"krodsherad.no", 0},
-#line 2754 "effective_tld_names.gperf"
- {"ringerike.no", 0},
-#line 3488 "effective_tld_names.gperf"
- {"xn--kvitsy-fya.no", 0},
-#line 1640 "effective_tld_names.gperf"
- {"klabu.no", 0},
-#line 3466 "effective_tld_names.gperf"
- {"xn--hnefoss-q1a.no", 0},
-#line 3278 "effective_tld_names.gperf"
- {"vaksdal.no", 0},
-#line 1660 "effective_tld_names.gperf"
- {"kongsvinger.no", 0},
-#line 2570 "effective_tld_names.gperf"
- {"pomorze.pl", 0},
-#line 3597 "effective_tld_names.gperf"
- {"xn--vler-qoa.hedmark.no", 0},
-#line 3147 "effective_tld_names.gperf"
- {"tottori.jp", 2},
-#line 3524 "effective_tld_names.gperf"
- {"xn--nvuotna-hwa.no", 0},
-#line 3526 "effective_tld_names.gperf"
- {"xn--od0alg.hk", 0},
-#line 2749 "effective_tld_names.gperf"
- {"rieti.it", 0},
-#line 2580 "effective_tld_names.gperf"
- {"potenza.it", 0},
-#line 3439 "effective_tld_names.gperf"
- {"xn--davvenjrga-y4a.no", 0},
-#line 1633 "effective_tld_names.gperf"
- {"kids.museum", 0},
-#line 3459 "effective_tld_names.gperf"
- {"xn--h1aegh.museum", 0},
-#line 3596 "effective_tld_names.gperf"
- {"xn--vgsy-qoa0j.no", 0},
-#line 3517 "effective_tld_names.gperf"
- {"xn--msy-ula0h.no", 0},
-#line 3151 "effective_tld_names.gperf"
- {"town.museum", 0},
-#line 2750 "effective_tld_names.gperf"
- {"riik.ee", 0},
-#line 3352 "effective_tld_names.gperf"
- {"voss.no", 0},
-#line 1658 "effective_tld_names.gperf"
- {"komvux.se", 0},
-#line 3339 "effective_tld_names.gperf"
- {"vlaanderen.museum", 0},
-#line 3452 "effective_tld_names.gperf"
- {"xn--gildeskl-g0a.no", 0},
-#line 3101 "effective_tld_names.gperf"
- {"texas.museum", 0},
-#line 3341 "effective_tld_names.gperf"
- {"vladimir.ru", 0},
-#line 3105 "effective_tld_names.gperf"
- {"tgory.pl", 0},
-#line 3280 "effective_tld_names.gperf"
- {"valer.ostfold.no", 0},
-#line 3451 "effective_tld_names.gperf"
- {"xn--ggaviika-8ya47h.no", 0},
-#line 1614 "effective_tld_names.gperf"
- {"kawasaki.jp", 2},
-#line 1596 "effective_tld_names.gperf"
- {"kagoshima.jp", 2},
-#line 2822 "effective_tld_names.gperf"
- {"sande.xn--mre-og-romsdal-qqb.no", 0},
-#line 1364 "effective_tld_names.gperf"
- {"hattfjelldal.no", 0},
-#line 2203 "effective_tld_names.gperf"
- {"newmexico.museum", 0},
-#line 1549 "effective_tld_names.gperf"
- {"jaworzno.pl", 0},
-#line 774 "effective_tld_names.gperf"
- {"dielddanuorri.no", 0},
-#line 2667 "effective_tld_names.gperf"
- {"prof.pr", 0},
-#line 1602 "effective_tld_names.gperf"
- {"karasjohka.no", 0},
-#line 3193 "effective_tld_names.gperf"
- {"turystyka.pl", 0},
-#line 512 "effective_tld_names.gperf"
- {"city.osaka.jp", 1},
-#line 3099 "effective_tld_names.gperf"
- {"ternopil.ua", 0},
-#line 3180 "effective_tld_names.gperf"
- {"trondheim.no", 0},
-#line 2615 "effective_tld_names.gperf"
- {"pref.kyoto.jp", 1},
-#line 3605 "effective_tld_names.gperf"
- {"xn--ystre-slidre-ujb.no", 0},
-#line 1635 "effective_tld_names.gperf"
- {"kiev.ua", 0},
-#line 3367 "effective_tld_names.gperf"
- {"walbrzych.pl", 0},
-#line 3420 "effective_tld_names.gperf"
- {"xn--bearalvhki-y4a.no", 0},
-#line 3464 "effective_tld_names.gperf"
- {"xn--hgebostad-g3a.no", 0},
-#line 1597 "effective_tld_names.gperf"
{"kalisz.pl", 0},
-#line 781 "effective_tld_names.gperf"
- {"dlugoleka.pl", 0},
-#line 3565 "effective_tld_names.gperf"
- {"xn--sndre-land-0cb.no", 0},
-#line 2718 "effective_tld_names.gperf"
- {"rawa-maz.pl", 0},
-#line 3135 "effective_tld_names.gperf"
- {"tochigi.jp", 2},
-#line 3434 "effective_tld_names.gperf"
- {"xn--btsfjord-9za.no", 0},
-#line 2732 "effective_tld_names.gperf"
- {"reggioemilia.it", 0},
-#line 2609 "effective_tld_names.gperf"
- {"pref.iwate.jp", 1},
-#line 3343 "effective_tld_names.gperf"
- {"vlog.br", 0},
-#line 1647 "effective_tld_names.gperf"
- {"kobe.jp", 2},
-#line 3592 "effective_tld_names.gperf"
- {"xn--vegrshei-c0a.no", 0},
-#line 3535 "effective_tld_names.gperf"
- {"xn--rdy-0nab.no", 0},
-#line 3461 "effective_tld_names.gperf"
- {"xn--hcesuolo-7ya35b.no", 0},
-#line 511 "effective_tld_names.gperf"
- {"city.okayama.jp", 1},
-#line 3520 "effective_tld_names.gperf"
- {"xn--mxtq1m.hk", 0},
-#line 3489 "effective_tld_names.gperf"
- {"xn--kvnangen-k0a.no", 0},
-#line 1683 "effective_tld_names.gperf"
- {"kunstsammlung.museum", 0},
-#line 2614 "effective_tld_names.gperf"
- {"pref.kumamoto.jp", 1},
-#line 2603 "effective_tld_names.gperf"
- {"pref.gunma.jp", 1},
-#line 3521 "effective_tld_names.gperf"
- {"xn--nmesjevuemie-tcba.no", 0},
-#line 2634 "effective_tld_names.gperf"
- {"pref.tottori.jp", 1},
+#line 1912 "effective_tld_names.gperf"
+ {"media.aero", 0},
+#line 3471 "effective_tld_names.gperf"
+ {"xn--avery-yua.no", 0},
+#line 1018 "effective_tld_names.gperf"
+ {"field.museum", 0},
+#line 1869 "effective_tld_names.gperf"
+ {"marburg.museum", 0},
#line 3495 "effective_tld_names.gperf"
- {"xn--leagaviika-52b.no", 0},
-#line 3518 "effective_tld_names.gperf"
- {"xn--mtta-vrjjat-k7af.no", 0},
-#line 3527 "effective_tld_names.gperf"
- {"xn--od0aq3b.hk", 0},
-#line 2596 "effective_tld_names.gperf"
- {"pref.aomori.jp", 1},
-#line 502 "effective_tld_names.gperf"
- {"city.fukuoka.jp", 1},
-#line 516 "effective_tld_names.gperf"
- {"city.shizuoka.jp", 1},
-#line 3411 "effective_tld_names.gperf"
- {"xn--9dbhblg6di.museum", 0},
-#line 515 "effective_tld_names.gperf"
- {"city.sendai.jp", 1},
-#line 507 "effective_tld_names.gperf"
- {"city.kobe.jp", 1},
-#line 2598 "effective_tld_names.gperf"
- {"pref.ehime.jp", 1},
-#line 3454 "effective_tld_names.gperf"
- {"xn--gjvik-wua.no", 0},
-#line 3426 "effective_tld_names.gperf"
- {"xn--bjarky-fya.no", 0},
-#line 3323 "effective_tld_names.gperf"
- {"vibo-valentia.it", 0},
-#line 2635 "effective_tld_names.gperf"
- {"pref.toyama.jp", 1},
-#line 2731 "effective_tld_names.gperf"
- {"reggiocalabria.it", 0},
-#line 2069 "effective_tld_names.gperf"
- {"national-library-scotland.uk", 1},
-#line 3285 "effective_tld_names.gperf"
- {"vanylven.no", 0},
-#line 3137 "effective_tld_names.gperf"
- {"tokushima.jp", 2},
-#line 2630 "effective_tld_names.gperf"
- {"pref.shimane.jp", 1},
-#line 509 "effective_tld_names.gperf"
- {"city.nagoya.jp", 1},
-#line 3353 "effective_tld_names.gperf"
- {"vossevangen.no", 0},
-#line 2729 "effective_tld_names.gperf"
- {"reggio-calabria.it", 0},
-#line 2599 "effective_tld_names.gperf"
- {"pref.fukui.jp", 1},
-#line 3342 "effective_tld_names.gperf"
- {"vladivostok.ru", 0},
-#line 3576 "effective_tld_names.gperf"
- {"xn--stjrdalshalsen-sqb.no", 0},
-#line 2626 "effective_tld_names.gperf"
- {"pref.osaka.jp", 1},
-#line 2535 "effective_tld_names.gperf"
- {"philadelphia.museum", 0},
-#line 2606 "effective_tld_names.gperf"
- {"pref.hyogo.jp", 1},
-#line 2629 "effective_tld_names.gperf"
- {"pref.shiga.jp", 1},
-#line 3138 "effective_tld_names.gperf"
- {"tokyo.jp", 2},
-#line 514 "effective_tld_names.gperf"
- {"city.sapporo.jp", 1},
-#line 1610 "effective_tld_names.gperf"
- {"kartuzy.pl", 0},
-#line 2616 "effective_tld_names.gperf"
- {"pref.mie.jp", 1},
-#line 3501 "effective_tld_names.gperf"
- {"xn--loabt-0qa.no", 0},
-#line 3340 "effective_tld_names.gperf"
- {"vladikavkaz.ru", 0},
-#line 510 "effective_tld_names.gperf"
- {"city.niigata.jp", 1},
-#line 2595 "effective_tld_names.gperf"
- {"pref.akita.jp", 1},
-#line 501 "effective_tld_names.gperf"
- {"city.chiba.jp", 1},
-#line 479 "effective_tld_names.gperf"
- {"chesapeakebay.museum", 0},
-#line 3415 "effective_tld_names.gperf"
- {"xn--aurskog-hland-jnb.no", 0},
-#line 3102 "effective_tld_names.gperf"
- {"textile.museum", 0},
-#line 513 "effective_tld_names.gperf"
- {"city.saitama.jp", 1},
-#line 3327 "effective_tld_names.gperf"
- {"vicenza.it", 0},
-#line 2623 "effective_tld_names.gperf"
- {"pref.oita.jp", 1},
-#line 2607 "effective_tld_names.gperf"
- {"pref.ibaraki.jp", 1},
-#line 2624 "effective_tld_names.gperf"
- {"pref.okayama.jp", 1},
-#line 3463 "effective_tld_names.gperf"
- {"xn--hery-ira.xn--mre-og-romsdal-qqb.no", 0},
-#line 2536 "effective_tld_names.gperf"
- {"philadelphiaarea.museum", 0},
-#line 2613 "effective_tld_names.gperf"
- {"pref.kochi.jp", 1},
-#line 584 "effective_tld_names.gperf"
- {"colonialwilliamsburg.museum", 0},
-#line 2600 "effective_tld_names.gperf"
- {"pref.fukuoka.jp", 1},
-#line 2631 "effective_tld_names.gperf"
- {"pref.shizuoka.jp", 1},
-#line 2621 "effective_tld_names.gperf"
- {"pref.nara.jp", 1},
-#line 3456 "effective_tld_names.gperf"
- {"xn--gmq050i.hk", 0},
-#line 517 "effective_tld_names.gperf"
- {"city.yokohama.jp", 1},
-#line 3437 "effective_tld_names.gperf"
- {"xn--correios-e-telecomunicaes-ghc29a.museum", 0},
-#line 3438 "effective_tld_names.gperf"
- {"xn--czrw28b.tw", 0},
-#line 2617 "effective_tld_names.gperf"
- {"pref.miyagi.jp", 1},
-#line 2619 "effective_tld_names.gperf"
- {"pref.nagano.jp", 1},
-#line 2627 "effective_tld_names.gperf"
- {"pref.saga.jp", 1},
-#line 2632 "effective_tld_names.gperf"
- {"pref.tochigi.jp", 1},
-#line 1656 "effective_tld_names.gperf"
- {"kommunalforbund.se", 0},
-#line 3087 "effective_tld_names.gperf"
- {"taxi.aero", 0},
-#line 506 "effective_tld_names.gperf"
- {"city.kitakyushu.jp", 1},
-#line 2622 "effective_tld_names.gperf"
- {"pref.niigata.jp", 1},
-#line 2597 "effective_tld_names.gperf"
- {"pref.chiba.jp", 1},
-#line 2756 "effective_tld_names.gperf"
- {"riodejaneiro.museum", 0},
-#line 2601 "effective_tld_names.gperf"
- {"pref.fukushima.jp", 1},
-#line 2628 "effective_tld_names.gperf"
- {"pref.saitama.jp", 1},
-#line 2625 "effective_tld_names.gperf"
- {"pref.okinawa.jp", 1},
-#line 3537 "effective_tld_names.gperf"
- {"xn--rhkkervju-01af.no", 0},
-#line 3598 "effective_tld_names.gperf"
- {"xn--vler-qoa.xn--stfold-9xa.no", 0},
-#line 503 "effective_tld_names.gperf"
- {"city.hiroshima.jp", 1},
-#line 2602 "effective_tld_names.gperf"
- {"pref.gifu.jp", 1},
-#line 2633 "effective_tld_names.gperf"
- {"pref.tokushima.jp", 1},
-#line 2594 "effective_tld_names.gperf"
- {"pref.aichi.jp", 1},
-#line 2618 "effective_tld_names.gperf"
- {"pref.miyazaki.jp", 1},
-#line 2605 "effective_tld_names.gperf"
- {"pref.hokkaido.jp", 1},
-#line 505 "effective_tld_names.gperf"
- {"city.kawasaki.jp", 1},
+ {"xn--dnna-gra.no", 0},
+#line 3695 "effective_tld_names.gperf"
+ {"zachpomor.pl", 0},
+#line 1349 "effective_tld_names.gperf"
+ {"gu.us", 0},
+#line 557 "effective_tld_names.gperf"
+ {"co.gg", 0},
+#line 350 "effective_tld_names.gperf"
+ {"biz.ki", 0},
+#line 1724 "effective_tld_names.gperf"
+ {"kvinesdal.no", 0},
+#line 1865 "effective_tld_names.gperf"
+ {"mansion.museum", 0},
+#line 1866 "effective_tld_names.gperf"
+ {"mansions.museum", 0},
+#line 2329 "effective_tld_names.gperf"
+ {"nuremberg.museum", 0},
+#line 1509 "effective_tld_names.gperf"
+ {"info.ht", 0},
+#line 466 "effective_tld_names.gperf"
+ {"cc", 0},
+#line 820 "effective_tld_names.gperf"
+ {"ec", 0},
+#line 32 "effective_tld_names.gperf"
+ {"ac", 0},
+#line 2020 "effective_tld_names.gperf"
+ {"monticello.museum", 0},
+#line 2208 "effective_tld_names.gperf"
+ {"net.ph", 0},
+#line 1162 "effective_tld_names.gperf"
+ {"gmina.pl", 0},
+#line 1404 "effective_tld_names.gperf"
+ {"heroy.nordland.no", 0},
+#line 617 "effective_tld_names.gperf"
+ {"com.cn", 0},
+#line 843 "effective_tld_names.gperf"
+ {"edu.cn", 0},
#line 2637 "effective_tld_names.gperf"
- {"pref.yamagata.jp", 1},
-#line 3482 "effective_tld_names.gperf"
- {"xn--kranghke-b0a.no", 0},
-#line 2620 "effective_tld_names.gperf"
- {"pref.nagasaki.jp", 1},
+ {"prd.mg", 0},
+#line 567 "effective_tld_names.gperf"
+ {"co.lc", 0},
+#line 2609 "effective_tld_names.gperf"
+ {"pol.dz", 0},
+#line 3452 "effective_tld_names.gperf"
+ {"works.aero", 0},
+#line 1922 "effective_tld_names.gperf"
+ {"meloy.no", 0},
+#line 34 "effective_tld_names.gperf"
+ {"ac.at", 0},
+#line 1176 "effective_tld_names.gperf"
+ {"gob.cl", 0},
+#line 2535 "effective_tld_names.gperf"
+ {"padova.it", 0},
+#line 1156 "effective_tld_names.gperf"
+ {"glass.museum", 0},
+#line 1113 "effective_tld_names.gperf"
+ {"games.hu", 0},
+#line 1218 "effective_tld_names.gperf"
+ {"gov.cl", 0},
+#line 1220 "effective_tld_names.gperf"
+ {"gov.cn", 0},
+#line 1986 "effective_tld_names.gperf"
+ {"mining.museum", 0},
+#line 2270 "effective_tld_names.gperf"
+ {"nm.us", 0},
+#line 1112 "effective_tld_names.gperf"
+ {"game.tw", 0},
+#line 439 "effective_tld_names.gperf"
+ {"ca.us", 0},
+#line 1460 "effective_tld_names.gperf"
+ {"ia.us", 0},
+#line 1217 "effective_tld_names.gperf"
+ {"gov.cd", 0},
+#line 2121 "effective_tld_names.gperf"
+ {"nc", 0},
+#line 51 "effective_tld_names.gperf"
+ {"ac.pr", 0},
+#line 1992 "effective_tld_names.gperf"
+ {"mjondalen.no", 0},
+#line 3710 "effective_tld_names.gperf"
+ {"zt.ua", 0},
+#line 33 "effective_tld_names.gperf"
+ {"ac.ae", 0},
+#line 42 "effective_tld_names.gperf"
+ {"ac.ir", 0},
+#line 2563 "effective_tld_names.gperf"
+ {"per.sg", 0},
+#line 1312 "effective_tld_names.gperf"
+ {"gratangen.no", 0},
+#line 753 "effective_tld_names.gperf"
+ {"cyber.museum", 0},
+#line 3643 "effective_tld_names.gperf"
+ {"xn--trna-woa.no", 0},
+#line 1108 "effective_tld_names.gperf"
+ {"ga.us", 0},
+#line 1118 "effective_tld_names.gperf"
+ {"gateway.museum", 0},
+#line 2929 "effective_tld_names.gperf"
+ {"se.net", 0},
+#line 1681 "effective_tld_names.gperf"
+ {"koenig.ru", 0},
+#line 226 "effective_tld_names.gperf"
+ {"asso.nc", 0},
+#line 418 "effective_tld_names.gperf"
+ {"budejju.no", 0},
+#line 1709 "effective_tld_names.gperf"
+ {"kuban.ru", 0},
+#line 1637 "effective_tld_names.gperf"
+ {"karmoy.no", 0},
+#line 1669 "effective_tld_names.gperf"
+ {"klabu.no", 0},
+#line 281 "effective_tld_names.gperf"
+ {"ballooning.aero", 0},
+#line 2940 "effective_tld_names.gperf"
+ {"services.aero", 0},
+#line 2862 "effective_tld_names.gperf"
+ {"salerno.it", 0},
+#line 1700 "effective_tld_names.gperf"
+ {"kragero.no", 0},
+#line 3709 "effective_tld_names.gperf"
+ {"zp.ua", 0},
+#line 581 "effective_tld_names.gperf"
+ {"co.tj", 0},
+#line 35 "effective_tld_names.gperf"
+ {"ac.be", 0},
+#line 1059 "effective_tld_names.gperf"
+ {"foggia.it", 0},
+#line 2132 "effective_tld_names.gperf"
+ {"nedre-eiker.no", 0},
+#line 1510 "effective_tld_names.gperf"
+ {"info.hu", 0},
+#line 454 "effective_tld_names.gperf"
+ {"cartoonart.museum", 0},
+#line 46 "effective_tld_names.gperf"
+ {"ac.me", 0},
+#line 1481 "effective_tld_names.gperf"
+ {"imageandsound.museum", 0},
+#line 1172 "effective_tld_names.gperf"
+ {"go.tj", 0},
+#line 2159 "effective_tld_names.gperf"
+ {"net.cn", 0},
+#line 3570 "effective_tld_names.gperf"
+ {"xn--mlatvuopmi-s4a.no", 0},
+#line 2527 "effective_tld_names.gperf"
+ {"p.bg", 0},
+#line 41 "effective_tld_names.gperf"
+ {"ac.in", 0},
+#line 52 "effective_tld_names.gperf"
+ {"ac.rs", 0},
+#line 3285 "effective_tld_names.gperf"
+ {"um.gov.pl", 0},
+#line 3058 "effective_tld_names.gperf"
+ {"stat.no", 0},
+#line 332 "effective_tld_names.gperf"
+ {"bialystok.pl", 0},
+#line 1690 "effective_tld_names.gperf"
+ {"konin.pl", 0},
+#line 419 "effective_tld_names.gperf"
+ {"building.museum", 0},
+#line 544 "effective_tld_names.gperf"
+ {"cn.ua", 0},
+#line 1489 "effective_tld_names.gperf"
+ {"in.ua", 0},
+#line 155 "effective_tld_names.gperf"
+ {"anthro.museum", 0},
+#line 1717 "effective_tld_names.gperf"
+ {"kutno.pl", 0},
+#line 44 "effective_tld_names.gperf"
+ {"ac.kr", 0},
+#line 1679 "effective_tld_names.gperf"
+ {"koebenhavn.museum", 0},
+#line 2931 "effective_tld_names.gperf"
+ {"sebastopol.ua", 0},
+#line 40 "effective_tld_names.gperf"
+ {"ac.im", 0},
+#line 1184 "effective_tld_names.gperf"
+ {"gok.pk", 0},
+#line 62 "effective_tld_names.gperf"
+ {"aca.pro", 0},
+#line 412 "effective_tld_names.gperf"
+ {"bryansk.ru", 0},
+#line 1343 "effective_tld_names.gperf"
+ {"gs.tr.no", 0},
+#line 2131 "effective_tld_names.gperf"
+ {"nebraska.museum", 0},
#line 2610 "effective_tld_names.gperf"
- {"pref.kagawa.jp", 1},
-#line 2636 "effective_tld_names.gperf"
- {"pref.wakayama.jp", 1},
-#line 2608 "effective_tld_names.gperf"
- {"pref.ishikawa.jp", 1},
-#line 2611 "effective_tld_names.gperf"
- {"pref.kagoshima.jp", 1},
-#line 2612 "effective_tld_names.gperf"
- {"pref.kanagawa.jp", 1},
-#line 2604 "effective_tld_names.gperf"
- {"pref.hiroshima.jp", 1},
-#line 2639 "effective_tld_names.gperf"
- {"pref.yamanashi.jp", 1},
-#line 2638 "effective_tld_names.gperf"
- {"pref.yamaguchi.jp", 1},
+ {"pol.ht", 0},
+#line 47 "effective_tld_names.gperf"
+ {"ac.mu", 0},
+#line 1373 "effective_tld_names.gperf"
+ {"halsa.no", 0},
+#line 1111 "effective_tld_names.gperf"
+ {"galsa.no", 0},
+#line 295 "effective_tld_names.gperf"
+ {"basel.museum", 0},
+#line 1100 "effective_tld_names.gperf"
+ {"furniture.museum", 0},
+#line 2737 "effective_tld_names.gperf"
+ {"q.bg", 0},
+#line 1195 "effective_tld_names.gperf"
+ {"gouv.ht", 0},
+#line 43 "effective_tld_names.gperf"
+ {"ac.jp", 0},
+#line 1921 "effective_tld_names.gperf"
+ {"melhus.no", 0},
+#line 1644 "effective_tld_names.gperf"
+ {"kazan.ru", 0},
+#line 2012 "effective_tld_names.gperf"
+ {"modelling.aero", 0},
+#line 50 "effective_tld_names.gperf"
+ {"ac.pa", 0},
+#line 2332 "effective_tld_names.gperf"
+ {"ny.us", 0},
+#line 3676 "effective_tld_names.gperf"
+ {"yamanashi.jp", 2},
+#line 931 "effective_tld_names.gperf"
+ {"eidfjord.no", 0},
+#line 1406 "effective_tld_names.gperf"
+ {"hi.us", 0},
+#line 486 "effective_tld_names.gperf"
+ {"chernigov.ua", 0},
+#line 53 "effective_tld_names.gperf"
+ {"ac.ru", 0},
+#line 3517 "effective_tld_names.gperf"
+ {"xn--hery-ira.nordland.no", 0},
+#line 522 "effective_tld_names.gperf"
+ {"city.saitama.jp", 1},
+#line 1858 "effective_tld_names.gperf"
+ {"malbork.pl", 0},
+#line 3589 "effective_tld_names.gperf"
+ {"xn--osyro-wua.no", 0},
+#line 3440 "effective_tld_names.gperf"
+ {"whaling.museum", 0},
+#line 1694 "effective_tld_names.gperf"
+ {"kostroma.ru", 0},
+#line 1914 "effective_tld_names.gperf"
+ {"media.museum", 0},
+#line 3087 "effective_tld_names.gperf"
+ {"student.aero", 0},
+#line 45 "effective_tld_names.gperf"
+ {"ac.ma", 0},
+#line 3700 "effective_tld_names.gperf"
+ {"zgora.pl", 0},
+#line 1508 "effective_tld_names.gperf"
+ {"info.ec", 0},
+#line 1593 "effective_tld_names.gperf"
+ {"jgora.pl", 0},
+#line 3097 "effective_tld_names.gperf"
+ {"sund.no", 0},
+#line 270 "effective_tld_names.gperf"
+ {"baghdad.museum", 0},
+#line 1342 "effective_tld_names.gperf"
+ {"gs.tm.no", 0},
+#line 1151 "effective_tld_names.gperf"
+ {"gjerstad.no", 0},
+#line 1146 "effective_tld_names.gperf"
+ {"gifu.jp", 2},
+#line 1041 "effective_tld_names.gperf"
+ {"fl.us", 0},
+#line 3068 "effective_tld_names.gperf"
+ {"steigen.no", 0},
+#line 1962 "effective_tld_names.gperf"
+ {"mil.kr", 0},
+#line 209 "effective_tld_names.gperf"
+ {"askvoll.no", 0},
+#line 276 "effective_tld_names.gperf"
+ {"bajddar.no", 0},
+#line 1316 "effective_tld_names.gperf"
+ {"grong.no", 0},
+#line 2685 "effective_tld_names.gperf"
+ {"presidio.museum", 0},
+#line 1716 "effective_tld_names.gperf"
+ {"kustanai.ru", 0},
+#line 3446 "effective_tld_names.gperf"
+ {"windmill.museum", 0},
+#line 616 "effective_tld_names.gperf"
+ {"com.ci", 0},
+#line 842 "effective_tld_names.gperf"
+ {"edu.ci", 0},
+#line 2106 "effective_tld_names.gperf"
+ {"nationalheritage.museum", 0},
+#line 513 "effective_tld_names.gperf"
+ {"city.hu", 0},
+#line 1924 "effective_tld_names.gperf"
+ {"meraker.no", 0},
+#line 1642 "effective_tld_names.gperf"
+ {"kautokeino.no", 0},
+#line 3459 "effective_tld_names.gperf"
+ {"www.ro", 0},
+#line 377 "effective_tld_names.gperf"
+ {"bokn.no", 0},
+#line 1067 "effective_tld_names.gperf"
+ {"fortmissoula.museum", 0},
+#line 1980 "effective_tld_names.gperf"
+ {"milan.it", 0},
+#line 2732 "effective_tld_names.gperf"
+ {"pvt.ge", 0},
+#line 1913 "effective_tld_names.gperf"
+ {"media.hu", 0},
+#line 269 "effective_tld_names.gperf"
+ {"badajoz.museum", 0},
+#line 1704 "effective_tld_names.gperf"
+ {"kristiansund.no", 0},
+#line 94 "effective_tld_names.gperf"
+ {"agrigento.it", 0},
+#line 1045 "effective_tld_names.gperf"
+ {"flatanger.no", 0},
+#line 1867 "effective_tld_names.gperf"
+ {"mantova.it", 0},
+#line 2023 "effective_tld_names.gperf"
+ {"mordovia.ru", 0},
+#line 1935 "effective_tld_names.gperf"
+ {"microlight.aero", 0},
+#line 1959 "effective_tld_names.gperf"
+ {"mil.jo", 0},
+#line 2712 "effective_tld_names.gperf"
+ {"production.aero", 0},
+#line 1088 "effective_tld_names.gperf"
+ {"from.hr", 0},
+#line 3581 "effective_tld_names.gperf"
+ {"xn--nry-yla5g.no", 0},
+#line 2297 "effective_tld_names.gperf"
+ {"norfolk.museum", 0},
+#line 1680 "effective_tld_names.gperf"
+ {"koeln.museum", 0},
+#line 3419 "effective_tld_names.gperf"
+ {"wa.gov.au", 0},
+#line 641 "effective_tld_names.gperf"
+ {"com.kg", 0},
+#line 865 "effective_tld_names.gperf"
+ {"edu.kg", 0},
+#line 107 "effective_tld_names.gperf"
+ {"airguard.museum", 0},
+#line 3071 "effective_tld_names.gperf"
+ {"stjordal.no", 0},
+#line 618 "effective_tld_names.gperf"
+ {"com.co", 0},
+#line 2961 "effective_tld_names.gperf"
+ {"siedlce.pl", 0},
+#line 844 "effective_tld_names.gperf"
+ {"edu.co", 0},
+#line 61 "effective_tld_names.gperf"
+ {"ac.vn", 0},
+#line 2158 "effective_tld_names.gperf"
+ {"net.ci", 0},
+#line 2593 "effective_tld_names.gperf"
+ {"pk", 0},
+#line 162 "effective_tld_names.gperf"
+ {"aoste.it", 0},
+#line 1706 "effective_tld_names.gperf"
+ {"krokstadelva.no", 0},
+#line 2063 "effective_tld_names.gperf"
+ {"mx", 0},
+#line 644 "effective_tld_names.gperf"
+ {"com.ky", 0},
+#line 869 "effective_tld_names.gperf"
+ {"edu.ky", 0},
+#line 1531 "effective_tld_names.gperf"
+ {"int.ci", 0},
+#line 1244 "effective_tld_names.gperf"
+ {"gov.kg", 0},
+#line 3410 "effective_tld_names.gperf"
+ {"vt.it", 0},
+#line 1703 "effective_tld_names.gperf"
+ {"kristiansand.no", 0},
+#line 3347 "effective_tld_names.gperf"
+ {"ve", 2},
+#line 467 "effective_tld_names.gperf"
+ {"cc.na", 0},
+#line 1000 "effective_tld_names.gperf"
+ {"fc.it", 0},
+#line 2004 "effective_tld_names.gperf"
+ {"mo.us", 0},
+#line 494 "effective_tld_names.gperf"
+ {"childrensgarden.museum", 0},
+#line 1895 "effective_tld_names.gperf"
+ {"md.us", 0},
+#line 2250 "effective_tld_names.gperf"
+ {"nh.us", 0},
+#line 2039 "effective_tld_names.gperf"
+ {"ms.us", 0},
+#line 1221 "effective_tld_names.gperf"
+ {"gov.co", 0},
+#line 2043 "effective_tld_names.gperf"
+ {"mt.us", 0},
+#line 3702 "effective_tld_names.gperf"
+ {"zgrad.ru", 0},
+#line 2615 "effective_tld_names.gperf"
+ {"pordenone.it", 0},
+#line 1248 "effective_tld_names.gperf"
+ {"gov.ky", 0},
+#line 3350 "effective_tld_names.gperf"
+ {"vega.no", 0},
+#line 3408 "effective_tld_names.gperf"
+ {"vr.it", 0},
+#line 2278 "effective_tld_names.gperf"
+ {"nom.co", 0},
+#line 3348 "effective_tld_names.gperf"
+ {"ve.it", 0},
+#line 3053 "effective_tld_names.gperf"
+ {"stange.no", 0},
+#line 3055 "effective_tld_names.gperf"
+ {"stargard.pl", 0},
+#line 3311 "effective_tld_names.gperf"
+ {"ut.us", 0},
+#line 1898 "effective_tld_names.gperf"
+ {"me.us", 0},
+#line 3288 "effective_tld_names.gperf"
+ {"union.aero", 0},
+#line 67 "effective_tld_names.gperf"
+ {"act.edu.au", 0},
+#line 2886 "effective_tld_names.gperf"
+ {"sassari.it", 0},
+#line 3665 "effective_tld_names.gperf"
+ {"xn--ygarden-p1a.no", 0},
+#line 3398 "effective_tld_names.gperf"
+ {"vn", 0},
+#line 645 "effective_tld_names.gperf"
+ {"com.kz", 0},
+#line 3412 "effective_tld_names.gperf"
+ {"vu", 0},
+#line 870 "effective_tld_names.gperf"
+ {"edu.kz", 0},
+#line 3549 "effective_tld_names.gperf"
+ {"xn--ldingen-q1a.no", 0},
+#line 2181 "effective_tld_names.gperf"
+ {"net.kg", 0},
+#line 3647 "effective_tld_names.gperf"
+ {"xn--uc0atv.tw", 0},
+#line 3103 "effective_tld_names.gperf"
+ {"suwalki.pl", 0},
+#line 3497 "effective_tld_names.gperf"
+ {"xn--dyry-ira.no", 0},
+#line 2160 "effective_tld_names.gperf"
+ {"net.co", 0},
+#line 1029 "effective_tld_names.gperf"
+ {"firenze.it", 0},
+#line 2050 "effective_tld_names.gperf"
+ {"murmansk.ru", 0},
+#line 1710 "effective_tld_names.gperf"
+ {"kumamoto.jp", 2},
+#line 1249 "effective_tld_names.gperf"
+ {"gov.kz", 0},
+#line 2845 "effective_tld_names.gperf"
+ {"s.bg", 0},
+#line 1532 "effective_tld_names.gperf"
+ {"int.co", 0},
+#line 2184 "effective_tld_names.gperf"
+ {"net.ky", 0},
+#line 1499 "effective_tld_names.gperf"
+ {"indianmarket.museum", 0},
+#line 1394 "effective_tld_names.gperf"
+ {"heimatunduhren.museum", 0},
+#line 1999 "effective_tld_names.gperf"
+ {"mn.us", 0},
+#line 2883 "effective_tld_names.gperf"
+ {"saratov.ru", 0},
+#line 1392 "effective_tld_names.gperf"
+ {"health.museum", 0},
+#line 2964 "effective_tld_names.gperf"
+ {"sigdal.no", 0},
+#line 1880 "effective_tld_names.gperf"
+ {"masfjorden.no", 0},
+#line 161 "effective_tld_names.gperf"
+ {"aosta.it", 0},
+#line 1618 "effective_tld_names.gperf"
+ {"k-uralsk.ru", 0},
+#line 3697 "effective_tld_names.gperf"
+ {"zakopane.pl", 0},
+#line 1572 "effective_tld_names.gperf"
+ {"izhevsk.ru", 0},
+#line 999 "effective_tld_names.gperf"
+ {"fauske.no", 0},
+#line 485 "effective_tld_names.gperf"
+ {"cherkassy.ua", 0},
+#line 1891 "effective_tld_names.gperf"
+ {"mc", 0},
+#line 3322 "effective_tld_names.gperf"
+ {"va", 0},
+#line 2090 "effective_tld_names.gperf"
+ {"name.tj", 0},
+#line 3667 "effective_tld_names.gperf"
+ {"xn--zf0ao64a.tw", 0},
+#line 1569 "effective_tld_names.gperf"
+ {"ivgu.no", 0},
+#line 591 "effective_tld_names.gperf"
+ {"coldwar.museum", 0},
+#line 3328 "effective_tld_names.gperf"
+ {"vaga.no", 0},
+#line 2185 "effective_tld_names.gperf"
+ {"net.kz", 0},
+#line 1892 "effective_tld_names.gperf"
+ {"mc.it", 0},
+#line 3323 "effective_tld_names.gperf"
+ {"va.it", 0},
+#line 3081 "effective_tld_names.gperf"
+ {"store.st", 0},
+#line 3094 "effective_tld_names.gperf"
+ {"suldal.no", 0},
+#line 3270 "effective_tld_names.gperf"
+ {"udine.it", 0},
+#line 3291 "effective_tld_names.gperf"
+ {"unjarga.no", 0},
+#line 1848 "effective_tld_names.gperf"
+ {"ma.us", 0},
+#line 3427 "effective_tld_names.gperf"
+ {"warszawa.pl", 0},
+#line 2330 "effective_tld_names.gperf"
+ {"nv.us", 0},
+#line 3052 "effective_tld_names.gperf"
+ {"stalowa-wola.pl", 0},
+#line 3420 "effective_tld_names.gperf"
+ {"wa.us", 0},
+#line 3080 "effective_tld_names.gperf"
+ {"store.ro", 0},
+#line 122 "effective_tld_names.gperf"
+ {"alaska.museum", 0},
+#line 2873 "effective_tld_names.gperf"
+ {"sandnes.no", 0},
+#line 1714 "effective_tld_names.gperf"
+ {"kurgan.ru", 0},
+#line 2296 "effective_tld_names.gperf"
+ {"nore-og-uvdal.no", 0},
+#line 2083 "effective_tld_names.gperf"
+ {"name.hr", 0},
+#line 401 "effective_tld_names.gperf"
+ {"british.museum", 0},
+#line 3076 "effective_tld_names.gperf"
+ {"stord.no", 0},
+#line 2625 "effective_tld_names.gperf"
+ {"powiat.pl", 0},
+#line 3673 "effective_tld_names.gperf"
+ {"yamagata.jp", 2},
+#line 3531 "effective_tld_names.gperf"
+ {"xn--jrpeland-54a.no", 0},
+#line 2716 "effective_tld_names.gperf"
+ {"pruszkow.pl", 0},
+#line 3526 "effective_tld_names.gperf"
+ {"xn--hylandet-54a.no", 0},
+#line 1723 "effective_tld_names.gperf"
+ {"kvanangen.no", 0},
+#line 3008 "effective_tld_names.gperf"
+ {"society.museum", 0},
+#line 2884 "effective_tld_names.gperf"
+ {"sarpsborg.no", 0},
+#line 3699 "effective_tld_names.gperf"
+ {"zarow.pl", 0},
+#line 3063 "effective_tld_names.gperf"
+ {"stavanger.no", 0},
+#line 3017 "effective_tld_names.gperf"
+ {"sondre-land.no", 0},
+#line 2867 "effective_tld_names.gperf"
+ {"samnanger.no", 0},
+#line 2729 "effective_tld_names.gperf"
+ {"pubol.museum", 0},
+#line 2072 "effective_tld_names.gperf"
+ {"naamesjevuemie.no", 0},
+#line 1157 "effective_tld_names.gperf"
+ {"gliding.aero", 0},
+#line 56 "effective_tld_names.gperf"
+ {"ac.sz", 0},
+#line 2620 "effective_tld_names.gperf"
+ {"portal.museum", 0},
+#line 3373 "effective_tld_names.gperf"
+ {"vi", 0},
+#line 2243 "effective_tld_names.gperf"
+ {"newyork.museum", 0},
+#line 311 "effective_tld_names.gperf"
+ {"belau.pw", 0},
+#line 3460 "effective_tld_names.gperf"
+ {"wy.us", 0},
+#line 750 "effective_tld_names.gperf"
+ {"cv.ua", 0},
+#line 2605 "effective_tld_names.gperf"
+ {"po.gov.pl", 0},
+#line 3374 "effective_tld_names.gperf"
+ {"vi.it", 0},
+#line 2619 "effective_tld_names.gperf"
+ {"port.fr", 0},
+#line 2942 "effective_tld_names.gperf"
+ {"settlers.museum", 0},
+#line 55 "effective_tld_names.gperf"
+ {"ac.se", 0},
+#line 3091 "effective_tld_names.gperf"
+ {"suedtirol.it", 0},
+#line 2112 "effective_tld_names.gperf"
+ {"nature.museum", 0},
+#line 2551 "effective_tld_names.gperf"
+ {"pavia.it", 0},
+#line 1932 "effective_tld_names.gperf"
+ {"mi.us", 0},
+#line 2864 "effective_tld_names.gperf"
+ {"salvadordali.museum", 0},
+#line 3441 "effective_tld_names.gperf"
+ {"wi.us", 0},
+#line 3423 "effective_tld_names.gperf"
+ {"wales.museum", 0},
+#line 3281 "effective_tld_names.gperf"
+ {"ullensvang.no", 0},
+#line 1820 "effective_tld_names.gperf"
+ {"ls", 0},
+#line 2856 "effective_tld_names.gperf"
+ {"saintlouis.museum", 0},
+#line 1821 "effective_tld_names.gperf"
+ {"lt", 0},
+#line 2590 "effective_tld_names.gperf"
+ {"pistoia.it", 0},
+#line 3324 "effective_tld_names.gperf"
+ {"va.no", 0},
+#line 1632 "effective_tld_names.gperf"
+ {"karasjok.no", 0},
+#line 3011 "effective_tld_names.gperf"
+ {"sogne.no", 0},
+#line 2970 "effective_tld_names.gperf"
+ {"sk", 0},
+#line 3014 "effective_tld_names.gperf"
+ {"sologne.museum", 0},
+#line 727 "effective_tld_names.gperf"
+ {"council.aero", 0},
+#line 1393 "effective_tld_names.gperf"
+ {"health.vn", 0},
+#line 2018 "effective_tld_names.gperf"
+ {"money.museum", 0},
+#line 1802 "effective_tld_names.gperf"
+ {"lo.it", 0},
+#line 1063 "effective_tld_names.gperf"
+ {"forde.no", 0},
+#line 1819 "effective_tld_names.gperf"
+ {"lr", 0},
+#line 1609 "effective_tld_names.gperf"
+ {"jpn.com", 0},
+#line 1822 "effective_tld_names.gperf"
+ {"lt.it", 0},
+#line 2064 "effective_tld_names.gperf"
+ {"mx.na", 0},
+#line 3018 "effective_tld_names.gperf"
+ {"sondrio.it", 0},
+#line 677 "effective_tld_names.gperf"
+ {"com.ru", 0},
+#line 901 "effective_tld_names.gperf"
+ {"edu.ru", 0},
+#line 3092 "effective_tld_names.gperf"
+ {"suisse.museum", 0},
+#line 2859 "effective_tld_names.gperf"
+ {"salangen.no", 0},
+#line 2996 "effective_tld_names.gperf"
+ {"smola.no", 0},
#line 3443 "effective_tld_names.gperf"
+ {"wiki.br", 0},
+#line 2863 "effective_tld_names.gperf"
+ {"saltdal.no", 0},
+#line 2689 "effective_tld_names.gperf"
+ {"press.se", 0},
+#line 3056 "effective_tld_names.gperf"
+ {"starnberg.museum", 0},
+#line 1860 "effective_tld_names.gperf"
+ {"malopolska.pl", 0},
+#line 1804 "effective_tld_names.gperf"
+ {"local", 0},
+#line 3027 "effective_tld_names.gperf"
+ {"sortland.no", 0},
+#line 1762 "effective_tld_names.gperf"
+ {"le.it", 0},
+#line 1281 "effective_tld_names.gperf"
+ {"gov.ru", 0},
+#line 2621 "effective_tld_names.gperf"
+ {"portland.museum", 0},
+#line 2624 "effective_tld_names.gperf"
+ {"potenza.it", 0},
+#line 3560 "effective_tld_names.gperf"
+ {"xn--lten-gra.no", 0},
+#line 2305 "effective_tld_names.gperf"
+ {"nov.ru", 0},
+#line 1826 "effective_tld_names.gperf"
+ {"lu", 0},
+#line 2016 "effective_tld_names.gperf"
+ {"molde.no", 0},
+#line 291 "effective_tld_names.gperf"
+ {"barlettaandriatrani.it", 0},
+#line 290 "effective_tld_names.gperf"
+ {"barletta-andria-trani.it", 0},
+#line 288 "effective_tld_names.gperf"
+ {"bardu.no", 0},
+#line 1708 "effective_tld_names.gperf"
+ {"ks.us", 0},
+#line 3061 "effective_tld_names.gperf"
+ {"stathelle.no", 0},
+#line 2100 "effective_tld_names.gperf"
+ {"narviika.no", 0},
+#line 1827 "effective_tld_names.gperf"
+ {"lu.it", 0},
+#line 2530 "effective_tld_names.gperf"
+ {"pa.gov.pl", 0},
+#line 3541 "effective_tld_names.gperf"
+ {"xn--ksnes-uua.no", 0},
+#line 2969 "effective_tld_names.gperf"
+ {"sirdal.no", 0},
+#line 657 "effective_tld_names.gperf"
+ {"com.mu", 0},
+#line 2607 "effective_tld_names.gperf"
+ {"podhale.pl", 0},
+#line 1329 "effective_tld_names.gperf"
+ {"gs.hl.no", 0},
+#line 2215 "effective_tld_names.gperf"
+ {"net.ru", 0},
+#line 2613 "effective_tld_names.gperf"
+ {"pomorskie.pl", 0},
+#line 3016 "effective_tld_names.gperf"
+ {"somna.no", 0},
+#line 1539 "effective_tld_names.gperf"
+ {"int.ru", 0},
+#line 1051 "effective_tld_names.gperf"
+ {"florence.it", 0},
+#line 596 "effective_tld_names.gperf"
+ {"columbus.museum", 0},
+#line 92 "effective_tld_names.gperf"
+ {"agrar.hu", 0},
+#line 1266 "effective_tld_names.gperf"
+ {"gov.mu", 0},
+#line 3280 "effective_tld_names.gperf"
+ {"ullensaker.no", 0},
+#line 2587 "effective_tld_names.gperf"
+ {"pilot.aero", 0},
+#line 2684 "effective_tld_names.gperf"
+ {"preservation.museum", 0},
+#line 2616 "effective_tld_names.gperf"
+ {"porsanger.no", 0},
+#line 2665 "effective_tld_names.gperf"
+ {"pref.nara.jp", 1},
+#line 1735 "effective_tld_names.gperf"
+ {"la", 0},
+#line 3661 "effective_tld_names.gperf"
+ {"xn--vry-yla5g.no", 0},
+#line 3655 "effective_tld_names.gperf"
+ {"xn--vgan-qoa.no", 0},
+#line 3278 "effective_tld_names.gperf"
+ {"uk.net", 0},
+#line 2941 "effective_tld_names.gperf"
+ {"settlement.museum", 0},
+#line 2617 "effective_tld_names.gperf"
+ {"porsangu.no", 0},
+#line 3346 "effective_tld_names.gperf"
+ {"vdonsk.ru", 0},
+#line 2935 "effective_tld_names.gperf"
+ {"selbu.no", 0},
+#line 2618 "effective_tld_names.gperf"
+ {"porsgrunn.no", 0},
+#line 1078 "effective_tld_names.gperf"
+ {"franziskaner.museum", 0},
+#line 3432 "effective_tld_names.gperf"
+ {"web.co", 0},
+#line 2860 "effective_tld_names.gperf"
+ {"salat.no", 0},
+#line 2854 "effective_tld_names.gperf"
+ {"safety.aero", 0},
+#line 3086 "effective_tld_names.gperf"
+ {"stryn.no", 0},
+#line 2198 "effective_tld_names.gperf"
+ {"net.mu", 0},
+#line 1039 "effective_tld_names.gperf"
+ {"fjell.no", 0},
+#line 2596 "effective_tld_names.gperf"
+ {"planetarium.museum", 0},
+#line 2936 "effective_tld_names.gperf"
+ {"selje.no", 0},
+#line 1021 "effective_tld_names.gperf"
+ {"film.hu", 0},
+#line 1031 "effective_tld_names.gperf"
+ {"firm.ht", 0},
+#line 74 "effective_tld_names.gperf"
+ {"adygeya.ru", 0},
+#line 3686 "effective_tld_names.gperf"
+ {"youth.museum", 0},
+#line 2257 "effective_tld_names.gperf"
+ {"niepce.museum", 0},
+#line 1330 "effective_tld_names.gperf"
+ {"gs.hm.no", 0},
+#line 518 "effective_tld_names.gperf"
+ {"city.nagoya.jp", 1},
+#line 500 "effective_tld_names.gperf"
+ {"chukotka.ru", 0},
+#line 2541 "effective_tld_names.gperf"
+ {"palmsprings.museum", 0},
+#line 2686 "effective_tld_names.gperf"
+ {"press.aero", 0},
+#line 627 "effective_tld_names.gperf"
+ {"com.gh", 0},
+#line 706 "effective_tld_names.gperf"
+ {"conference.aero", 0},
+#line 852 "effective_tld_names.gperf"
+ {"edu.gh", 0},
+#line 584 "effective_tld_names.gperf"
+ {"co.ug", 0},
+#line 114 "effective_tld_names.gperf"
+ {"akrehamn.no", 0},
+#line 3069 "effective_tld_names.gperf"
+ {"steinkjer.no", 0},
+#line 1144 "effective_tld_names.gperf"
+ {"giehtavuoatna.no", 0},
+#line 1174 "effective_tld_names.gperf"
+ {"go.ug", 0},
+#line 1707 "effective_tld_names.gperf"
+ {"ks.ua", 0},
+#line 1842 "effective_tld_names.gperf"
+ {"ly", 0},
+#line 1230 "effective_tld_names.gperf"
+ {"gov.gh", 0},
+#line 1970 "effective_tld_names.gperf"
+ {"mil.ph", 0},
+#line 3070 "effective_tld_names.gperf"
+ {"stjohn.museum", 0},
+#line 3406 "effective_tld_names.gperf"
+ {"voss.no", 0},
+#line 3413 "effective_tld_names.gperf"
+ {"vv.it", 0},
+#line 2687 "effective_tld_names.gperf"
+ {"press.ma", 0},
+#line 1698 "effective_tld_names.gperf"
+ {"kr.ua", 0},
+#line 2659 "effective_tld_names.gperf"
+ {"pref.kyoto.jp", 1},
+#line 1086 "effective_tld_names.gperf"
+ {"frogn.no", 0},
+#line 3066 "effective_tld_names.gperf"
+ {"steam.museum", 0},
+#line 3074 "effective_tld_names.gperf"
+ {"stokke.no", 0},
+#line 1784 "effective_tld_names.gperf"
+ {"li", 0},
+#line 1352 "effective_tld_names.gperf"
+ {"gunma.jp", 2},
+#line 2612 "effective_tld_names.gperf"
+ {"poltava.ua", 0},
+#line 2545 "effective_tld_names.gperf"
+ {"paris.museum", 0},
+#line 2997 "effective_tld_names.gperf"
+ {"smolensk.ru", 0},
+#line 3458 "effective_tld_names.gperf"
+ {"wv.us", 0},
+#line 3005 "effective_tld_names.gperf"
+ {"so.gov.pl", 0},
+#line 481 "effective_tld_names.gperf"
+ {"chattanooga.museum", 0},
+#line 2129 "effective_tld_names.gperf"
+ {"ne.ug", 0},
+#line 3614 "effective_tld_names.gperf"
+ {"xn--sgne-gra.no", 0},
+#line 2293 "effective_tld_names.gperf"
+ {"nordkapp.no", 0},
+#line 2248 "effective_tld_names.gperf"
+ {"ngo.ph", 0},
+#line 1785 "effective_tld_names.gperf"
+ {"li.it", 0},
+#line 3498 "effective_tld_names.gperf"
{"xn--eveni-0qa01ga.no", 0},
+#line 3514 "effective_tld_names.gperf"
+ {"xn--h1aegh.museum", 0},
+#line 375 "effective_tld_names.gperf"
+ {"bo.telemark.no", 0},
+#line 3033 "effective_tld_names.gperf"
+ {"southwest.museum", 0},
+#line 2547 "effective_tld_names.gperf"
+ {"parma.it", 0},
+#line 3043 "effective_tld_names.gperf"
+ {"sr.gov.pl", 0},
+#line 1077 "effective_tld_names.gperf"
+ {"frankfurt.museum", 0},
+#line 1730 "effective_tld_names.gperf"
+ {"ky.us", 0},
+#line 1951 "effective_tld_names.gperf"
+ {"mil.cn", 0},
+#line 3274 "effective_tld_names.gperf"
+ {"ug.gov.pl", 0},
+#line 1019 "effective_tld_names.gperf"
+ {"figueres.museum", 0},
+#line 1766 "effective_tld_names.gperf"
+ {"lebork.pl", 0},
+#line 3102 "effective_tld_names.gperf"
+ {"surrey.museum", 0},
+#line 2744 "effective_tld_names.gperf"
+ {"qld.gov.au", 0},
+#line 3359 "effective_tld_names.gperf"
+ {"verran.no", 0},
+#line 1673 "effective_tld_names.gperf"
+ {"km.ua", 0},
+#line 3038 "effective_tld_names.gperf"
+ {"sport.hu", 0},
+#line 3379 "effective_tld_names.gperf"
+ {"vic.edu.au", 0},
+#line 3019 "effective_tld_names.gperf"
+ {"songdalen.no", 0},
+#line 2633 "effective_tld_names.gperf"
+ {"pr.us", 0},
+#line 3650 "effective_tld_names.gperf"
+ {"xn--vads-jra.no", 0},
+#line 3451 "effective_tld_names.gperf"
+ {"workinggroup.aero", 0},
+#line 3354 "effective_tld_names.gperf"
+ {"vennesla.no", 0},
+#line 1585 "effective_tld_names.gperf"
+ {"jerusalem.museum", 0},
+#line 151 "effective_tld_names.gperf"
+ {"andasuolo.no", 0},
+#line 228 "effective_tld_names.gperf"
+ {"association.aero", 0},
+#line 707 "effective_tld_names.gperf"
+ {"congresodelalengua3.ar", 1},
+#line 1726 "effective_tld_names.gperf"
+ {"kviteseid.no", 0},
+#line 187 "effective_tld_names.gperf"
+ {"art.museum", 0},
+#line 3072 "effective_tld_names.gperf"
+ {"stjordalshalsen.no", 0},
+#line 2566 "effective_tld_names.gperf"
+ {"perso.sn", 0},
+#line 3651 "effective_tld_names.gperf"
+ {"xn--vard-jra.no", 0},
+#line 2567 "effective_tld_names.gperf"
+ {"perso.tn", 0},
+#line 3574 "effective_tld_names.gperf"
+ {"xn--mosjen-eya.no", 0},
+#line 3107 "effective_tld_names.gperf"
+ {"sveio.no", 0},
+#line 1787 "effective_tld_names.gperf"
+ {"lier.no", 0},
+#line 3031 "effective_tld_names.gperf"
+ {"soundandvision.museum", 0},
+#line 1786 "effective_tld_names.gperf"
+ {"lib.ee", 0},
+#line 3645 "effective_tld_names.gperf"
+ {"xn--tysvr-vra.no", 0},
+#line 1825 "effective_tld_names.gperf"
+ {"ltd.lk", 0},
+#line 3369 "effective_tld_names.gperf"
+ {"vevelstad.no", 0},
+#line 203 "effective_tld_names.gperf"
+ {"ascolipiceno.it", 0},
+#line 2879 "effective_tld_names.gperf"
+ {"santacruz.museum", 0},
+#line 1147 "effective_tld_names.gperf"
+ {"gildeskal.no", 0},
+#line 202 "effective_tld_names.gperf"
+ {"ascoli-piceno.it", 0},
+#line 423 "effective_tld_names.gperf"
+ {"busan.kr", 0},
+#line 1809 "effective_tld_names.gperf"
+ {"lom.no", 0},
+#line 3684 "effective_tld_names.gperf"
+ {"yorkshire.museum", 0},
+#line 1711 "effective_tld_names.gperf"
+ {"kunst.museum", 0},
+#line 3316 "effective_tld_names.gperf"
+ {"uw.gov.pl", 0},
+#line 2640 "effective_tld_names.gperf"
+ {"pref.aomori.jp", 1},
+#line 1362 "effective_tld_names.gperf"
+ {"gyeongnam.kr", 0},
+#line 2688 "effective_tld_names.gperf"
+ {"press.museum", 0},
+#line 3275 "effective_tld_names.gperf"
+ {"uhren.museum", 0},
+#line 2889 "effective_tld_names.gperf"
+ {"sauherad.no", 0},
+#line 1396 "effective_tld_names.gperf"
+ {"helsinki.museum", 0},
+#line 59 "effective_tld_names.gperf"
+ {"ac.tz", 0},
+#line 424 "effective_tld_names.gperf"
+ {"bushey.museum", 0},
+#line 1734 "effective_tld_names.gperf"
+ {"l.se", 0},
+#line 452 "effective_tld_names.gperf"
+ {"cargo.aero", 0},
+#line 1666 "effective_tld_names.gperf"
+ {"kirov.ru", 0},
+#line 3393 "effective_tld_names.gperf"
+ {"vlaanderen.museum", 0},
+#line 3583 "effective_tld_names.gperf"
+ {"xn--nvuotna-hwa.no", 0},
+#line 813 "effective_tld_names.gperf"
+ {"e-burg.ru", 0},
+#line 1806 "effective_tld_names.gperf"
+ {"lodi.it", 0},
+#line 474 "effective_tld_names.gperf"
+ {"cf", 0},
+#line 85 "effective_tld_names.gperf"
+ {"af", 0},
+#line 1938 "effective_tld_names.gperf"
+ {"midtre-gauldal.no", 0},
+#line 3508 "effective_tld_names.gperf"
+ {"xn--givuotna-8ya.no", 0},
+#line 625 "effective_tld_names.gperf"
+ {"com.fr", 0},
+#line 595 "effective_tld_names.gperf"
+ {"columbia.museum", 0},
+#line 1646 "effective_tld_names.gperf"
+ {"kchr.ru", 0},
+#line 2554 "effective_tld_names.gperf"
+ {"pc.pl", 0},
+#line 3116 "effective_tld_names.gperf"
+ {"sydney.museum", 0},
+#line 1139 "effective_tld_names.gperf"
+ {"gf", 0},
+#line 2553 "effective_tld_names.gperf"
+ {"pc.it", 0},
+#line 2658 "effective_tld_names.gperf"
+ {"pref.kumamoto.jp", 1},
+#line 385 "effective_tld_names.gperf"
+ {"botanical.museum", 0},
+#line 1441 "effective_tld_names.gperf"
+ {"hotel.lk", 0},
+#line 1403 "effective_tld_names.gperf"
+ {"heroy.more-og-romsdal.no", 0},
+#line 48 "effective_tld_names.gperf"
+ {"ac.mw", 0},
+#line 306 "effective_tld_names.gperf"
+ {"beardu.no", 0},
+#line 2579 "effective_tld_names.gperf"
+ {"philadelphia.museum", 0},
+#line 1079 "effective_tld_names.gperf"
+ {"fredrikstad.no", 0},
+#line 2532 "effective_tld_names.gperf"
+ {"pa.us", 0},
+#line 502 "effective_tld_names.gperf"
+ {"chungnam.kr", 0},
+#line 2595 "effective_tld_names.gperf"
+ {"pl.ua", 0},
+#line 556 "effective_tld_names.gperf"
+ {"co.cr", 0},
+#line 2244 "effective_tld_names.gperf"
+ {"nf", 0},
+#line 824 "effective_tld_names.gperf"
+ {"ed.cr", 0},
+#line 2280 "effective_tld_names.gperf"
+ {"nom.fr", 0},
+#line 1166 "effective_tld_names.gperf"
+ {"go.cr", 0},
+#line 1577 "effective_tld_names.gperf"
+ {"jar.ru", 0},
+#line 2692 "effective_tld_names.gperf"
+ {"presse.km", 0},
+#line 2730 "effective_tld_names.gperf"
+ {"pulawy.pl", 0},
+#line 3271 "effective_tld_names.gperf"
+ {"udm.ru", 0},
+#line 3469 "effective_tld_names.gperf"
+ {"xn--asky-ira.no", 0},
+#line 54 "effective_tld_names.gperf"
+ {"ac.rw", 0},
+#line 519 "effective_tld_names.gperf"
+ {"city.niigata.jp", 1},
+#line 49 "effective_tld_names.gperf"
+ {"ac.ng", 0},
+#line 1712 "effective_tld_names.gperf"
+ {"kunstsammlung.museum", 0},
+#line 3669 "effective_tld_names.gperf"
+ {"xz.cn", 0},
+#line 1188 "effective_tld_names.gperf"
+ {"gorge.museum", 0},
+#line 120 "effective_tld_names.gperf"
+ {"alaheadju.no", 0},
+#line 1610 "effective_tld_names.gperf"
+ {"js.cn", 0},
+#line 3062 "effective_tld_names.gperf"
+ {"station.museum", 0},
+#line 1363 "effective_tld_names.gperf"
+ {"gz.cn", 0},
+#line 1688 "effective_tld_names.gperf"
+ {"kongsberg.no", 0},
+#line 1125 "effective_tld_names.gperf"
+ {"gd.cn", 0},
+#line 1327 "effective_tld_names.gperf"
+ {"gs.cn", 0},
+#line 1594 "effective_tld_names.gperf"
+ {"jl.cn", 0},
+#line 2299 "effective_tld_names.gperf"
+ {"north.museum", 0},
+#line 1421 "effective_tld_names.gperf"
+ {"hl.cn", 0},
+#line 1720 "effective_tld_names.gperf"
+ {"kvafjord.no", 0},
+#line 1590 "effective_tld_names.gperf"
+ {"jewish.museum", 0},
+#line 174 "effective_tld_names.gperf"
+ {"archaeology.museum", 0},
+#line 3351 "effective_tld_names.gperf"
+ {"vegarshei.no", 0},
+#line 3538 "effective_tld_names.gperf"
+ {"xn--krdsherad-m8a.no", 0},
+#line 1581 "effective_tld_names.gperf"
+ {"jeju.kr", 0},
+#line 1840 "effective_tld_names.gperf"
+ {"lv", 0},
+#line 1515 "effective_tld_names.gperf"
+ {"info.nf", 0},
+#line 3453 "effective_tld_names.gperf"
+ {"workshop.museum", 0},
+#line 2667 "effective_tld_names.gperf"
+ {"pref.oita.jp", 1},
+#line 1631 "effective_tld_names.gperf"
+ {"karasjohka.no", 0},
+#line 1834 "effective_tld_names.gperf"
+ {"lunner.no", 0},
+#line 1677 "effective_tld_names.gperf"
+ {"kobierzyce.pl", 0},
+#line 2949 "effective_tld_names.gperf"
+ {"shell.museum", 0},
+#line 229 "effective_tld_names.gperf"
+ {"association.museum", 0},
+#line 1391 "effective_tld_names.gperf"
+ {"he.cn", 0},
+#line 2119 "effective_tld_names.gperf"
+ {"navuotna.no", 0},
+#line 3284 "effective_tld_names.gperf"
+ {"ulvik.no", 0},
+#line 1727 "effective_tld_names.gperf"
+ {"kvitsoy.no", 0},
+#line 2962 "effective_tld_names.gperf"
+ {"siellak.no", 0},
+#line 2030 "effective_tld_names.gperf"
+ {"motorcycle.museum", 0},
+#line 3358 "effective_tld_names.gperf"
+ {"verona.it", 0},
+#line 1741 "effective_tld_names.gperf"
+ {"lahppi.no", 0},
+#line 150 "effective_tld_names.gperf"
+ {"and.museum", 0},
+#line 497 "effective_tld_names.gperf"
+ {"chita.ru", 0},
+#line 3701 "effective_tld_names.gperf"
+ {"zgorzelec.pl", 0},
+#line 2260 "effective_tld_names.gperf"
+ {"nikolaev.ua", 0},
+#line 2539 "effective_tld_names.gperf"
+ {"paleo.museum", 0},
+#line 3635 "effective_tld_names.gperf"
+ {"xn--stjrdal-s1a.no", 0},
+#line 3283 "effective_tld_names.gperf"
+ {"ulsan.kr", 0},
+#line 2852 "effective_tld_names.gperf"
+ {"sa.gov.au", 0},
+#line 3548 "effective_tld_names.gperf"
+ {"xn--lcvr32d.hk", 0},
+#line 2963 "effective_tld_names.gperf"
+ {"siena.it", 0},
+#line 3681 "effective_tld_names.gperf"
+ {"yn.cn", 0},
+#line 422 "effective_tld_names.gperf"
+ {"bus.museum", 0},
+#line 3598 "effective_tld_names.gperf"
+ {"xn--rholt-mra.no", 0},
+#line 1799 "effective_tld_names.gperf"
+ {"livorno.it", 0},
+#line 1426 "effective_tld_names.gperf"
+ {"hn.cn", 0},
+#line 1788 "effective_tld_names.gperf"
+ {"lierne.no", 0},
+#line 3648 "effective_tld_names.gperf"
+ {"xn--uc0ay4a.hk", 0},
+#line 305 "effective_tld_names.gperf"
+ {"bearalvahki.no", 0},
+#line 1960 "effective_tld_names.gperf"
+ {"mil.kg", 0},
+#line 325 "effective_tld_names.gperf"
+ {"bf", 0},
+#line 2973 "effective_tld_names.gperf"
+ {"skanland.no", 0},
+#line 1667 "effective_tld_names.gperf"
+ {"kirovograd.ua", 0},
+#line 3371 "effective_tld_names.gperf"
+ {"vg", 0},
+#line 2312 "effective_tld_names.gperf"
+ {"ns.ca", 0},
+#line 2318 "effective_tld_names.gperf"
+ {"nt.ca", 0},
+#line 1952 "effective_tld_names.gperf"
+ {"mil.co", 0},
+#line 2266 "effective_tld_names.gperf"
+ {"nl.ca", 0},
+#line 1654 "effective_tld_names.gperf"
+ {"kh.ua", 0},
+#line 3001 "effective_tld_names.gperf"
+ {"snasa.no", 0},
+#line 1635 "effective_tld_names.gperf"
+ {"karikatur.museum", 0},
+#line 2333 "effective_tld_names.gperf"
+ {"nyc.museum", 0},
+#line 1624 "effective_tld_names.gperf"
+ {"kagawa.jp", 2},
+#line 2926 "effective_tld_names.gperf"
+ {"sd.us", 0},
+#line 941 "effective_tld_names.gperf"
+ {"elverum.no", 0},
+#line 2672 "effective_tld_names.gperf"
+ {"pref.saitama.jp", 1},
+#line 1916 "effective_tld_names.gperf"
+ {"medical.museum", 0},
+#line 2890 "effective_tld_names.gperf"
+ {"savannahga.museum", 0},
+#line 3338 "effective_tld_names.gperf"
+ {"vanylven.no", 0},
+#line 2581 "effective_tld_names.gperf"
+ {"philately.museum", 0},
+#line 449 "effective_tld_names.gperf"
+ {"can.museum", 0},
+#line 920 "effective_tld_names.gperf"
+ {"educ.ar", 1},
+#line 2269 "effective_tld_names.gperf"
+ {"nm.cn", 0},
+#line 1740 "effective_tld_names.gperf"
+ {"labour.museum", 0},
+#line 699 "effective_tld_names.gperf"
+ {"communication.museum", 0},
+#line 1366 "effective_tld_names.gperf"
+ {"ha.cn", 0},
+#line 3546 "effective_tld_names.gperf"
+ {"xn--laheadju-7ya.no", 0},
+#line 700 "effective_tld_names.gperf"
+ {"communications.museum", 0},
+#line 3532 "effective_tld_names.gperf"
+ {"xn--karmy-yua.no", 0},
+#line 1769 "effective_tld_names.gperf"
+ {"legnica.pl", 0},
+#line 2319 "effective_tld_names.gperf"
+ {"nt.edu.au", 0},
+#line 1744 "effective_tld_names.gperf"
+ {"lanbib.se", 0},
+#line 1879 "effective_tld_names.gperf"
+ {"marylhurst.museum", 0},
+#line 1963 "effective_tld_names.gperf"
+ {"mil.kz", 0},
+#line 2967 "effective_tld_names.gperf"
+ {"simbirsk.ru", 0},
+#line 3392 "effective_tld_names.gperf"
+ {"viterbo.it", 0},
+#line 3405 "effective_tld_names.gperf"
+ {"voronezh.ru", 0},
+#line 1755 "effective_tld_names.gperf"
+ {"latina.it", 0},
+#line 731 "effective_tld_names.gperf"
+ {"cq.cn", 0},
+#line 2325 "effective_tld_names.gperf"
+ {"nu.ca", 0},
+#line 2565 "effective_tld_names.gperf"
+ {"perso.ht", 0},
+#line 2258 "effective_tld_names.gperf"
+ {"nieruchomosci.pl", 0},
+#line 555 "effective_tld_names.gperf"
+ {"co.ci", 0},
+#line 823 "effective_tld_names.gperf"
+ {"ed.ci", 0},
+#line 1048 "effective_tld_names.gperf"
+ {"flight.aero", 0},
+#line 2642 "effective_tld_names.gperf"
+ {"pref.ehime.jp", 1},
+#line 156 "effective_tld_names.gperf"
+ {"anthropology.museum", 0},
+#line 1591 "effective_tld_names.gperf"
+ {"jewishart.museum", 0},
+#line 1165 "effective_tld_names.gperf"
+ {"go.ci", 0},
+#line 2263 "effective_tld_names.gperf"
+ {"nj.us", 0},
+#line 157 "effective_tld_names.gperf"
+ {"antiques.museum", 0},
+#line 3308 "effective_tld_names.gperf"
+ {"ushuaia.museum", 0},
+#line 938 "effective_tld_names.gperf"
+ {"elburg.museum", 0},
+#line 3024 "effective_tld_names.gperf"
+ {"sor-varanger.no", 0},
+#line 2893 "effective_tld_names.gperf"
+ {"sc", 0},
+#line 2075 "effective_tld_names.gperf"
+ {"nagasaki.jp", 2},
+#line 297 "effective_tld_names.gperf"
+ {"baths.museum", 0},
+#line 1859 "effective_tld_names.gperf"
+ {"mallorca.museum", 0},
+#line 1816 "effective_tld_names.gperf"
+ {"louvre.museum", 0},
+#line 1854 "effective_tld_names.gperf"
+ {"magnitka.ru", 0},
+#line 984 "effective_tld_names.gperf"
+ {"exeter.museum", 0},
+#line 1639 "effective_tld_names.gperf"
+ {"kartuzy.pl", 0},
+#line 2875 "effective_tld_names.gperf"
+ {"sandoy.no", 0},
+#line 3067 "effective_tld_names.gperf"
+ {"steiermark.museum", 0},
+#line 1719 "effective_tld_names.gperf"
+ {"kv.ua", 0},
+#line 2241 "effective_tld_names.gperf"
+ {"news.hu", 0},
+#line 1990 "effective_tld_names.gperf"
+ {"miyagi.jp", 2},
+#line 342 "effective_tld_names.gperf"
+ {"bir.ru", 0},
+#line 1765 "effective_tld_names.gperf"
+ {"lebesby.no", 0},
+#line 3646 "effective_tld_names.gperf"
+ {"xn--uc0atv.hk", 0},
+#line 1378 "effective_tld_names.gperf"
+ {"hammerfest.no", 0},
+#line 1655 "effective_tld_names.gperf"
+ {"khabarovsk.ru", 0},
+#line 380 "effective_tld_names.gperf"
+ {"bolt.hu", 0},
+#line 1083 "effective_tld_names.gperf"
+ {"freight.aero", 0},
+#line 1405 "effective_tld_names.gperf"
+ {"hi.cn", 0},
+#line 3282 "effective_tld_names.gperf"
+ {"ulm.museum", 0},
+#line 2813 "effective_tld_names.gperf"
+ {"ro", 0},
+#line 2832 "effective_tld_names.gperf"
+ {"rs", 0},
+#line 1604 "effective_tld_names.gperf"
+ {"joshkar-ola.ru", 0},
+#line 2634 "effective_tld_names.gperf"
+ {"prato.it", 0},
+#line 2580 "effective_tld_names.gperf"
+ {"philadelphiaarea.museum", 0},
+#line 207 "effective_tld_names.gperf"
+ {"askim.no", 0},
+#line 3110 "effective_tld_names.gperf"
+ {"sweden.museum", 0},
+#line 3649 "effective_tld_names.gperf"
+ {"xn--unjrga-rta.no", 0},
+#line 397 "effective_tld_names.gperf"
+ {"brescia.it", 0},
+#line 798 "effective_tld_names.gperf"
+ {"do", 2},
+#line 812 "effective_tld_names.gperf"
+ {"dz", 0},
+#line 2814 "effective_tld_names.gperf"
+ {"ro.it", 0},
+#line 57 "effective_tld_names.gperf"
+ {"ac.th", 0},
+#line 2301 "effective_tld_names.gperf"
+ {"notaires.fr", 0},
+#line 620 "effective_tld_names.gperf"
+ {"com.dm", 0},
+#line 846 "effective_tld_names.gperf"
+ {"edu.dm", 0},
+#line 2767 "effective_tld_names.gperf"
+ {"re", 0},
+#line 1064 "effective_tld_names.gperf"
+ {"forli-cesena.it", 0},
+#line 929 "effective_tld_names.gperf"
+ {"ehime.jp", 2},
+#line 3326 "effective_tld_names.gperf"
+ {"vaapste.no", 0},
+#line 1068 "effective_tld_names.gperf"
+ {"fortworth.museum", 0},
+#line 2078 "effective_tld_names.gperf"
+ {"naklo.pl", 0},
+#line 363 "effective_tld_names.gperf"
+ {"bjerkreim.no", 0},
+#line 1790 "effective_tld_names.gperf"
+ {"lillesand.no", 0},
+#line 1224 "effective_tld_names.gperf"
+ {"gov.dm", 0},
+#line 2880 "effective_tld_names.gperf"
+ {"santafe.museum", 0},
+#line 771 "effective_tld_names.gperf"
+ {"de", 0},
+#line 182 "effective_tld_names.gperf"
+ {"arpa", 0},
+#line 2895 "effective_tld_names.gperf"
+ {"sc.kr", 0},
+#line 2768 "effective_tld_names.gperf"
+ {"re.it", 0},
+#line 2674 "effective_tld_names.gperf"
+ {"pref.shimane.jp", 1},
+#line 1076 "effective_tld_names.gperf"
+ {"francaise.museum", 0},
+#line 1491 "effective_tld_names.gperf"
+ {"incheon.kr", 0},
+#line 3299 "effective_tld_names.gperf"
+ {"usa.museum", 0},
+#line 3309 "effective_tld_names.gperf"
+ {"uslivinghistory.museum", 0},
+#line 3009 "effective_tld_names.gperf"
+ {"software.aero", 0},
+#line 3279 "effective_tld_names.gperf"
+ {"ulan-ude.ru", 0},
+#line 3032 "effective_tld_names.gperf"
+ {"southcarolina.museum", 0},
+#line 3355 "effective_tld_names.gperf"
+ {"verbania.it", 0},
+#line 443 "effective_tld_names.gperf"
+ {"cahcesuolo.no", 0},
+#line 2834 "effective_tld_names.gperf"
+ {"ru", 0},
+#line 39 "effective_tld_names.gperf"
+ {"ac.gn", 0},
+#line 2812 "effective_tld_names.gperf"
+ {"rnu.tn", 0},
+#line 3558 "effective_tld_names.gperf"
+ {"xn--lrenskog-54a.no", 0},
+#line 3123 "effective_tld_names.gperf"
+ {"szkola.pl", 0},
+#line 248 "effective_tld_names.gperf"
+ {"austrheim.no", 0},
+#line 1319 "effective_tld_names.gperf"
+ {"group.aero", 0},
+#line 2808 "effective_tld_names.gperf"
+ {"rn.it", 0},
+#line 3340 "effective_tld_names.gperf"
+ {"varese.it", 0},
+#line 2811 "effective_tld_names.gperf"
+ {"rns.tn", 0},
+#line 105 "effective_tld_names.gperf"
+ {"air.museum", 0},
+#line 2546 "effective_tld_names.gperf"
+ {"parliament.uk", 1},
+#line 2162 "effective_tld_names.gperf"
+ {"net.dm", 0},
+#line 65 "effective_tld_names.gperf"
+ {"accident-prevention.aero", 0},
+#line 3122 "effective_tld_names.gperf"
+ {"szex.hu", 0},
+#line 794 "effective_tld_names.gperf"
+ {"dm", 0},
+#line 2807 "effective_tld_names.gperf"
+ {"rm.it", 0},
+#line 111 "effective_tld_names.gperf"
+ {"ak.us", 0},
+#line 97 "effective_tld_names.gperf"
+ {"ah.cn", 0},
+#line 3040 "effective_tld_names.gperf"
+ {"spydeberg.no", 0},
+#line 489 "effective_tld_names.gperf"
+ {"chiba.jp", 2},
+#line 3454 "effective_tld_names.gperf"
+ {"wroc.pl", 0},
+#line 2769 "effective_tld_names.gperf"
+ {"re.kr", 0},
+#line 3388 "effective_tld_names.gperf"
+ {"vinnica.ua", 0},
+#line 540 "effective_tld_names.gperf"
+ {"cmw.ru", 0},
+#line 2978 "effective_tld_names.gperf"
+ {"ski.no", 0},
+#line 982 "effective_tld_names.gperf"
+ {"evje-og-hornnes.no", 0},
+#line 2678 "effective_tld_names.gperf"
+ {"pref.tottori.jp", 1},
+#line 441 "effective_tld_names.gperf"
+ {"cadaques.museum", 0},
+#line 3352 "effective_tld_names.gperf"
+ {"venezia.it", 0},
+#line 208 "effective_tld_names.gperf"
+ {"askoy.no", 0},
+#line 2002 "effective_tld_names.gperf"
+ {"mo.cn", 0},
+#line 1674 "effective_tld_names.gperf"
+ {"kms.ru", 0},
+#line 2390 "effective_tld_names.gperf"
+ {"org", 0},
+#line 1839 "effective_tld_names.gperf"
+ {"luzern.museum", 0},
+#line 2749 "effective_tld_names.gperf"
+ {"ra.it", 0},
+#line 1972 "effective_tld_names.gperf"
+ {"mil.ru", 0},
+#line 3605 "effective_tld_names.gperf"
+ {"xn--rskog-uua.no", 0},
+#line 2484 "effective_tld_names.gperf"
+ {"org.sl", 0},
+#line 2485 "effective_tld_names.gperf"
+ {"org.sn", 0},
+#line 2373 "effective_tld_names.gperf"
+ {"or.at", 0},
+#line 2480 "effective_tld_names.gperf"
+ {"org.sc", 0},
+#line 2490 "effective_tld_names.gperf"
+ {"org.tn", 0},
+#line 2489 "effective_tld_names.gperf"
+ {"org.tj", 0},
+#line 2446 "effective_tld_names.gperf"
+ {"org.lr", 0},
+#line 2481 "effective_tld_names.gperf"
+ {"org.sd", 0},
+#line 2444 "effective_tld_names.gperf"
+ {"org.lc", 0},
+#line 2377 "effective_tld_names.gperf"
+ {"or.it", 0},
+#line 1776 "effective_tld_names.gperf"
+ {"lenvik.no", 0},
+#line 933 "effective_tld_names.gperf"
+ {"eidskog.no", 0},
+#line 2447 "effective_tld_names.gperf"
+ {"org.ls", 0},
+#line 2833 "effective_tld_names.gperf"
+ {"rs.ba", 0},
+#line 3529 "effective_tld_names.gperf"
+ {"xn--io0a7i.hk", 0},
+#line 521 "effective_tld_names.gperf"
+ {"city.osaka.jp", 1},
+#line 191 "effective_tld_names.gperf"
+ {"artcenter.museum", 0},
+#line 2952 "effective_tld_names.gperf"
+ {"shimane.jp", 2},
+#line 2479 "effective_tld_names.gperf"
+ {"org.sb", 0},
+#line 2806 "effective_tld_names.gperf"
+ {"rl.no", 0},
+#line 241 "effective_tld_names.gperf"
+ {"aukra.no", 0},
+#line 3362 "effective_tld_names.gperf"
+ {"vestnes.no", 0},
+#line 1830 "effective_tld_names.gperf"
+ {"lucerne.museum", 0},
+#line 2443 "effective_tld_names.gperf"
+ {"org.lb", 0},
+#line 2415 "effective_tld_names.gperf"
+ {"org.ec", 0},
+#line 2315 "effective_tld_names.gperf"
+ {"nsw.au", 0},
+#line 1758 "effective_tld_names.gperf"
+ {"law.pro", 0},
+#line 2476 "effective_tld_names.gperf"
+ {"org.rs", 0},
+#line 2470 "effective_tld_names.gperf"
+ {"org.pl", 0},
+#line 2471 "effective_tld_names.gperf"
+ {"org.pn", 0},
+#line 2472 "effective_tld_names.gperf"
+ {"org.pr", 0},
+#line 2861 "effective_tld_names.gperf"
+ {"salem.museum", 0},
+#line 2417 "effective_tld_names.gperf"
+ {"org.es", 0},
+#line 1888 "effective_tld_names.gperf"
+ {"mazury.pl", 0},
+#line 2493 "effective_tld_names.gperf"
+ {"org.tw", 0},
+#line 1357 "effective_tld_names.gperf"
+ {"gwangju.kr", 0},
+#line 2905 "effective_tld_names.gperf"
+ {"sch.sa", 0},
+#line 2473 "effective_tld_names.gperf"
+ {"org.ps", 0},
+#line 2903 "effective_tld_names.gperf"
+ {"sch.lk", 0},
+#line 2888 "effective_tld_names.gperf"
+ {"sauda.no", 0},
+#line 3088 "effective_tld_names.gperf"
+ {"stuttgart.museum", 0},
+#line 3015 "effective_tld_names.gperf"
+ {"solund.no", 0},
+#line 3372 "effective_tld_names.gperf"
+ {"vgs.no", 0},
+#line 1702 "effective_tld_names.gperf"
+ {"krasnoyarsk.ru", 0},
+#line 2108 "effective_tld_names.gperf"
+ {"naturalhistory.museum", 0},
+#line 2360 "effective_tld_names.gperf"
+ {"om", 2},
+#line 3525 "effective_tld_names.gperf"
+ {"xn--hyanger-q1a.no", 0},
+#line 362 "effective_tld_names.gperf"
+ {"bjarkoy.no", 0},
+#line 3095 "effective_tld_names.gperf"
+ {"suli.hu", 0},
+#line 197 "effective_tld_names.gperf"
+ {"arts.nf", 0},
+#line 660 "effective_tld_names.gperf"
+ {"com.mx", 0},
+#line 886 "effective_tld_names.gperf"
+ {"edu.mx", 0},
+#line 2464 "effective_tld_names.gperf"
+ {"org.nr", 0},
+#line 1015 "effective_tld_names.gperf"
+ {"fi.cr", 0},
+#line 2954 "effective_tld_names.gperf"
+ {"shop.ht", 0},
+#line 1179 "effective_tld_names.gperf"
+ {"gob.mx", 0},
+#line 2482 "effective_tld_names.gperf"
+ {"org.se", 0},
+#line 2820 "effective_tld_names.gperf"
+ {"roma.it", 0},
+#line 2898 "effective_tld_names.gperf"
+ {"sch.ae", 0},
+#line 1321 "effective_tld_names.gperf"
+ {"grp.lk", 0},
+#line 1187 "effective_tld_names.gperf"
+ {"gop.pk", 0},
+#line 532 "effective_tld_names.gperf"
+ {"ck.ua", 0},
+#line 2815 "effective_tld_names.gperf"
+ {"roan.no", 0},
+#line 2379 "effective_tld_names.gperf"
+ {"or.kr", 0},
+#line 2454 "effective_tld_names.gperf"
+ {"org.ml", 0},
+#line 2455 "effective_tld_names.gperf"
+ {"org.mn", 0},
+#line 2671 "effective_tld_names.gperf"
+ {"pref.saga.jp", 1},
+#line 3364 "effective_tld_names.gperf"
+ {"vestre-toten.no", 0},
+#line 3002 "effective_tld_names.gperf"
+ {"snillfjord.no", 0},
+#line 2911 "effective_tld_names.gperf"
+ {"school.na", 0},
+#line 2416 "effective_tld_names.gperf"
+ {"org.ee", 0},
+#line 3012 "effective_tld_names.gperf"
+ {"sokndal.no", 0},
+#line 2380 "effective_tld_names.gperf"
+ {"or.mu", 0},
+#line 2308 "effective_tld_names.gperf"
+ {"nowaruda.pl", 0},
+#line 1197 "effective_tld_names.gperf"
+ {"gouv.ml", 0},
+#line 2466 "effective_tld_names.gperf"
+ {"org.pe", 0},
+#line 2794 "effective_tld_names.gperf"
+ {"ri.it", 0},
+#line 1850 "effective_tld_names.gperf"
+ {"mad.museum", 0},
+#line 2906 "effective_tld_names.gperf"
+ {"sch.uk", 2},
+#line 3272 "effective_tld_names.gperf"
+ {"udmurtia.ru", 0},
+#line 2900 "effective_tld_names.gperf"
+ {"sch.ir", 0},
+#line 1782 "effective_tld_names.gperf"
+ {"lg.jp", 0},
+#line 3321 "effective_tld_names.gperf"
+ {"v.bg", 0},
+#line 2787 "effective_tld_names.gperf"
+ {"res.aero", 0},
+#line 2378 "effective_tld_names.gperf"
+ {"or.jp", 0},
+#line 3639 "effective_tld_names.gperf"
+ {"xn--tn0ag.hk", 0},
+#line 2459 "effective_tld_names.gperf"
+ {"org.mw", 0},
+#line 2782 "effective_tld_names.gperf"
+ {"rel.pl", 0},
+#line 2396 "effective_tld_names.gperf"
+ {"org.al", 0},
+#line 2397 "effective_tld_names.gperf"
+ {"org.an", 0},
+#line 1818 "effective_tld_names.gperf"
+ {"loyalist.museum", 0},
+#line 2391 "effective_tld_names.gperf"
+ {"org.ac", 0},
+#line 366 "effective_tld_names.gperf"
+ {"bl.uk", 1},
+#line 1236 "effective_tld_names.gperf"
+ {"gov.im", 0},
+#line 3585 "effective_tld_names.gperf"
+ {"xn--od0alg.hk", 0},
+#line 2788 "effective_tld_names.gperf"
+ {"res.in", 0},
+#line 2201 "effective_tld_names.gperf"
+ {"net.mx", 0},
+#line 1657 "effective_tld_names.gperf"
+ {"kharkov.ua", 0},
+#line 2955 "effective_tld_names.gperf"
+ {"shop.hu", 0},
+#line 2342 "effective_tld_names.gperf"
+ {"odda.no", 0},
+#line 2724 "effective_tld_names.gperf"
+ {"ptz.ru", 0},
+#line 2355 "effective_tld_names.gperf"
+ {"ol.no", 0},
+#line 2611 "effective_tld_names.gperf"
+ {"polkowice.pl", 0},
+#line 3057 "effective_tld_names.gperf"
+ {"starostwo.gov.pl", 0},
+#line 206 "effective_tld_names.gperf"
+ {"asker.no", 0},
+#line 2761 "effective_tld_names.gperf"
+ {"rana.no", 0},
+#line 2981 "effective_tld_names.gperf"
+ {"skiptvet.no", 0},
+#line 2663 "effective_tld_names.gperf"
+ {"pref.nagano.jp", 1},
+#line 944 "effective_tld_names.gperf"
+ {"emergency.aero", 0},
+#line 429 "effective_tld_names.gperf"
+ {"bykle.no", 0},
+#line 3303 "effective_tld_names.gperf"
+ {"usculture.museum", 0},
+#line 2509 "effective_tld_names.gperf"
+ {"osen.no", 0},
+#line 1955 "effective_tld_names.gperf"
+ {"mil.gh", 0},
+#line 2451 "effective_tld_names.gperf"
+ {"org.me", 0},
+#line 2478 "effective_tld_names.gperf"
+ {"org.sa", 0},
+#line 1803 "effective_tld_names.gperf"
+ {"loabat.no", 0},
+#line 3404 "effective_tld_names.gperf"
+ {"vologda.ru", 0},
+#line 1894 "effective_tld_names.gperf"
+ {"md.ci", 0},
+#line 2442 "effective_tld_names.gperf"
+ {"org.la", 0},
+#line 3050 "effective_tld_names.gperf"
+ {"stadt.museum", 0},
+#line 2445 "effective_tld_names.gperf"
+ {"org.lk", 0},
+#line 2600 "effective_tld_names.gperf"
+ {"plc.co.im", 0},
+#line 3397 "effective_tld_names.gperf"
+ {"vlog.br", 0},
+#line 3616 "effective_tld_names.gperf"
+ {"xn--skjervy-v1a.no", 0},
+#line 2622 "effective_tld_names.gperf"
+ {"portlligat.museum", 0},
+#line 2174 "effective_tld_names.gperf"
+ {"net.im", 0},
+#line 805 "effective_tld_names.gperf"
+ {"dr.na", 0},
+#line 1936 "effective_tld_names.gperf"
+ {"midatlantic.museum", 0},
+#line 3100 "effective_tld_names.gperf"
+ {"surgut.ru", 0},
+#line 2548 "effective_tld_names.gperf"
+ {"parti.se", 0},
+#line 2465 "effective_tld_names.gperf"
+ {"org.pa", 0},
+#line 2469 "effective_tld_names.gperf"
+ {"org.pk", 0},
+#line 2392 "effective_tld_names.gperf"
+ {"org.ae", 0},
+#line 1377 "effective_tld_names.gperf"
+ {"hammarfeasta.no", 0},
+#line 3357 "effective_tld_names.gperf"
+ {"verdal.no", 0},
+#line 1429 "effective_tld_names.gperf"
+ {"hokkaido.jp", 2},
+#line 2770 "effective_tld_names.gperf"
+ {"realestate.pl", 0},
+#line 1453 "effective_tld_names.gperf"
+ {"hurum.no", 0},
+#line 2511 "effective_tld_names.gperf"
+ {"oslo.no", 0},
+#line 1699 "effective_tld_names.gperf"
+ {"kraanghke.no", 0},
+#line 102 "effective_tld_names.gperf"
+ {"aip.ee", 0},
+#line 2697 "effective_tld_names.gperf"
+ {"priv.hu", 0},
+#line 588 "effective_tld_names.gperf"
+ {"coal.museum", 0},
+#line 2311 "effective_tld_names.gperf"
+ {"nrw.museum", 0},
+#line 2363 "effective_tld_names.gperf"
+ {"omsk.ru", 0},
+#line 2774 "effective_tld_names.gperf"
+ {"rec.ro", 0},
+#line 3515 "effective_tld_names.gperf"
+ {"xn--hbmer-xqa.no", 0},
+#line 2544 "effective_tld_names.gperf"
+ {"paragliding.aero", 0},
+#line 1753 "effective_tld_names.gperf"
+ {"larvik.no", 0},
+#line 1833 "effective_tld_names.gperf"
+ {"lund.no", 0},
+#line 927 "effective_tld_names.gperf"
+ {"egersund.no", 0},
+#line 2462 "effective_tld_names.gperf"
+ {"org.na", 0},
+#line 84 "effective_tld_names.gperf"
+ {"aeroport.fr", 0},
+#line 2748 "effective_tld_names.gperf"
+ {"r.se", 0},
+#line 2494 "effective_tld_names.gperf"
+ {"org.ua", 0},
+#line 3425 "effective_tld_names.gperf"
+ {"war.museum", 0},
+#line 2515 "effective_tld_names.gperf"
+ {"ostroda.pl", 0},
+#line 2430 "effective_tld_names.gperf"
+ {"org.in", 0},
+#line 2432 "effective_tld_names.gperf"
+ {"org.ir", 0},
+#line 3418 "effective_tld_names.gperf"
+ {"wa.edu.au", 0},
+#line 2933 "effective_tld_names.gperf"
+ {"sejny.pl", 0},
+#line 1551 "effective_tld_names.gperf"
+ {"iraq.museum", 0},
+#line 2433 "effective_tld_names.gperf"
+ {"org.is", 0},
+#line 1836 "effective_tld_names.gperf"
+ {"luster.no", 0},
+#line 2431 "effective_tld_names.gperf"
+ {"org.iq", 0},
+#line 760 "effective_tld_names.gperf"
+ {"d.se", 0},
+#line 3707 "effective_tld_names.gperf"
+ {"zoological.museum", 0},
+#line 2450 "effective_tld_names.gperf"
+ {"org.ma", 0},
+#line 2453 "effective_tld_names.gperf"
+ {"org.mk", 0},
+#line 2350 "effective_tld_names.gperf"
+ {"oita.jp", 2},
+#line 240 "effective_tld_names.gperf"
+ {"augustow.pl", 0},
+#line 1478 "effective_tld_names.gperf"
+ {"illustration.museum", 0},
+#line 2374 "effective_tld_names.gperf"
+ {"or.bi", 0},
+#line 2592 "effective_tld_names.gperf"
+ {"pittsburgh.museum", 0},
+#line 2647 "effective_tld_names.gperf"
+ {"pref.gunma.jp", 1},
+#line 1660 "effective_tld_names.gperf"
+ {"khv.ru", 0},
+#line 3059 "effective_tld_names.gperf"
+ {"state.museum", 0},
+#line 2910 "effective_tld_names.gperf"
+ {"school.museum", 0},
+#line 612 "effective_tld_names.gperf"
+ {"com.br", 0},
+#line 2514 "effective_tld_names.gperf"
+ {"ostre-toten.no", 0},
+#line 2679 "effective_tld_names.gperf"
+ {"pref.toyama.jp", 1},
+#line 839 "effective_tld_names.gperf"
+ {"edu.br", 0},
+#line 71 "effective_tld_names.gperf"
+ {"adm.br", 0},
+#line 237 "effective_tld_names.gperf"
+ {"ato.br", 0},
+#line 2653 "effective_tld_names.gperf"
+ {"pref.iwate.jp", 1},
+#line 613 "effective_tld_names.gperf"
+ {"com.bs", 0},
+#line 2524 "effective_tld_names.gperf"
+ {"oyer.no", 0},
+#line 840 "effective_tld_names.gperf"
+ {"edu.bs", 0},
+#line 73 "effective_tld_names.gperf"
+ {"adv.br", 0},
+#line 1106 "effective_tld_names.gperf"
+ {"g12.br", 0},
+#line 2491 "effective_tld_names.gperf"
+ {"org.to", 0},
+#line 3705 "effective_tld_names.gperf"
+ {"zlg.br", 0},
+#line 464 "effective_tld_names.gperf"
+ {"cb.it", 0},
+#line 2792 "effective_tld_names.gperf"
+ {"retina.ar", 1},
+#line 2381 "effective_tld_names.gperf"
+ {"or.na", 0},
+#line 3363 "effective_tld_names.gperf"
+ {"vestre-slidre.no", 0},
+#line 808 "effective_tld_names.gperf"
+ {"drobak.no", 0},
+#line 1213 "effective_tld_names.gperf"
+ {"gov.br", 0},
+#line 1689 "effective_tld_names.gperf"
+ {"kongsvinger.no", 0},
+#line 607 "effective_tld_names.gperf"
+ {"com.bb", 0},
+#line 183 "effective_tld_names.gperf"
+ {"arq.br", 0},
+#line 1779 "effective_tld_names.gperf"
+ {"levanger.no", 0},
+#line 834 "effective_tld_names.gperf"
+ {"edu.bb", 0},
+#line 1659 "effective_tld_names.gperf"
+ {"khmelnitskiy.ua", 0},
+#line 1214 "effective_tld_names.gperf"
+ {"gov.bs", 0},
+#line 2537 "effective_tld_names.gperf"
+ {"palace.museum", 0},
+#line 1361 "effective_tld_names.gperf"
+ {"gyeonggi.kr", 0},
+#line 2475 "effective_tld_names.gperf"
+ {"org.ro", 0},
+#line 525 "effective_tld_names.gperf"
+ {"city.shizuoka.jp", 1},
+#line 2277 "effective_tld_names.gperf"
+ {"nom.br", 0},
+#line 806 "effective_tld_names.gperf"
+ {"drammen.no", 0},
+#line 184 "effective_tld_names.gperf"
+ {"art.br", 0},
+#line 394 "effective_tld_names.gperf"
+ {"brandywinevalley.museum", 0},
+#line 972 "effective_tld_names.gperf"
+ {"etc.br", 0},
+#line 590 "effective_tld_names.gperf"
+ {"cody.museum", 0},
+#line 2300 "effective_tld_names.gperf"
+ {"not.br", 0},
+#line 1208 "effective_tld_names.gperf"
+ {"gov.bb", 0},
+#line 1934 "effective_tld_names.gperf"
+ {"michigan.museum", 0},
+#line 1856 "effective_tld_names.gperf"
+ {"maintenance.aero", 0},
+#line 2096 "effective_tld_names.gperf"
+ {"naples.it", 0},
+#line 3030 "effective_tld_names.gperf"
+ {"sosnowiec.pl", 0},
+#line 3657 "effective_tld_names.gperf"
+ {"xn--vler-qoa.hedmark.no", 0},
+#line 3329 "effective_tld_names.gperf"
+ {"vagan.no", 0},
+#line 1555 "effective_tld_names.gperf"
+ {"iron.museum", 0},
+#line 2872 "effective_tld_names.gperf"
+ {"sandiego.museum", 0},
+#line 2024 "effective_tld_names.gperf"
+ {"moscow.museum", 0},
+#line 2395 "effective_tld_names.gperf"
+ {"org.ai", 0},
+#line 1705 "effective_tld_names.gperf"
+ {"krodsherad.no", 0},
+#line 1500 "effective_tld_names.gperf"
+ {"inf.br", 0},
+#line 2155 "effective_tld_names.gperf"
+ {"net.br", 0},
+#line 545 "effective_tld_names.gperf"
+ {"cng.br", 0},
+#line 948 "effective_tld_names.gperf"
+ {"eng.br", 0},
+#line 1545 "effective_tld_names.gperf"
+ {"interactive.museum", 0},
+#line 2338 "effective_tld_names.gperf"
+ {"o.se", 0},
+#line 2512 "effective_tld_names.gperf"
+ {"osoyro.no", 0},
+#line 546 "effective_tld_names.gperf"
+ {"cnt.br", 0},
+#line 2156 "effective_tld_names.gperf"
+ {"net.bs", 0},
+#line 2502 "effective_tld_names.gperf"
+ {"orland.no", 0},
+#line 2938 "effective_tld_names.gperf"
+ {"sendai.jp", 2},
+#line 1381 "effective_tld_names.gperf"
+ {"hapmir.no", 0},
+#line 1033 "effective_tld_names.gperf"
+ {"firm.nf", 0},
+#line 3376 "effective_tld_names.gperf"
+ {"vibo-valentia.it", 0},
+#line 1482 "effective_tld_names.gperf"
+ {"imb.br", 0},
+#line 1616 "effective_tld_names.gperf"
+ {"jus.br", 0},
+#line 1824 "effective_tld_names.gperf"
+ {"ltd.gi", 0},
+#line 2151 "effective_tld_names.gperf"
+ {"net.bb", 0},
+#line 2426 "effective_tld_names.gperf"
+ {"org.hn", 0},
+#line 961 "effective_tld_names.gperf"
+ {"equipment.aero", 0},
+#line 2456 "effective_tld_names.gperf"
+ {"org.mo", 0},
+#line 719 "effective_tld_names.gperf"
+ {"coop.mv", 0},
+#line 3528 "effective_tld_names.gperf"
+ {"xn--io0a7i.cn", 0},
+#line 2690 "effective_tld_names.gperf"
+ {"presse.ci", 0},
+#line 2525 "effective_tld_names.gperf"
+ {"oygarden.no", 0},
+#line 1506 "effective_tld_names.gperf"
+ {"info.bb", 0},
+#line 2960 "effective_tld_names.gperf"
+ {"sibenik.museum", 0},
+#line 2874 "effective_tld_names.gperf"
+ {"sandnessjoen.no", 0},
+#line 3360 "effective_tld_names.gperf"
+ {"versailles.museum", 0},
+#line 3089 "effective_tld_names.gperf"
+ {"stv.ru", 0},
+#line 2107 "effective_tld_names.gperf"
+ {"nativeamerican.museum", 0},
+#line 2448 "effective_tld_names.gperf"
+ {"org.lv", 0},
+#line 2015 "effective_tld_names.gperf"
+ {"modum.no", 0},
+#line 2784 "effective_tld_names.gperf"
+ {"rennebu.no", 0},
+#line 1733 "effective_tld_names.gperf"
+ {"l.bg", 0},
+#line 1756 "effective_tld_names.gperf"
+ {"lavagis.no", 0},
+#line 296 "effective_tld_names.gperf"
+ {"bashkiria.ru", 0},
+#line 2254 "effective_tld_names.gperf"
+ {"nic.im", 0},
+#line 3336 "effective_tld_names.gperf"
+ {"vang.no", 0},
+#line 3524 "effective_tld_names.gperf"
+ {"xn--hpmir-xqa.no", 0},
+#line 293 "effective_tld_names.gperf"
+ {"barum.no", 0},
+#line 3617 "effective_tld_names.gperf"
+ {"xn--skjk-soa.no", 0},
+#line 68 "effective_tld_names.gperf"
+ {"act.gov.au", 0},
+#line 2830 "effective_tld_names.gperf"
+ {"royken.no", 0},
+#line 3036 "effective_tld_names.gperf"
+ {"spb.ru", 0},
+#line 2972 "effective_tld_names.gperf"
+ {"skanit.no", 0},
+#line 2109 "effective_tld_names.gperf"
+ {"naturalhistorymuseum.museum", 0},
+#line 2944 "effective_tld_names.gperf"
+ {"sex.pl", 0},
+#line 2742 "effective_tld_names.gperf"
+ {"qld.au", 0},
+#line 498 "effective_tld_names.gperf"
+ {"chocolate.museum", 0},
+#line 818 "effective_tld_names.gperf"
+ {"eastcoast.museum", 0},
+#line 298 "effective_tld_names.gperf"
+ {"batsfjord.no", 0},
+#line 1436 "effective_tld_names.gperf"
+ {"honefoss.no", 0},
+#line 300 "effective_tld_names.gperf"
+ {"bb", 0},
+#line 968 "effective_tld_names.gperf"
+ {"essex.museum", 0},
+#line 606 "effective_tld_names.gperf"
+ {"com.ba", 0},
+#line 833 "effective_tld_names.gperf"
+ {"edu.ba", 0},
+#line 1757 "effective_tld_names.gperf"
+ {"lavangen.no", 0},
+#line 2822 "effective_tld_names.gperf"
+ {"rome.it", 0},
+#line 3108 "effective_tld_names.gperf"
+ {"svelvik.no", 0},
+#line 1882 "effective_tld_names.gperf"
+ {"massa-carrara.it", 0},
+#line 427 "effective_tld_names.gperf"
+ {"bydgoszcz.pl", 0},
+#line 179 "effective_tld_names.gperf"
+ {"arezzo.it", 0},
+#line 1380 "effective_tld_names.gperf"
+ {"hanggliding.aero", 0},
+#line 520 "effective_tld_names.gperf"
+ {"city.okayama.jp", 1},
+#line 3578 "effective_tld_names.gperf"
+ {"xn--muost-0qa.no", 0},
+#line 1207 "effective_tld_names.gperf"
+ {"gov.ba", 0},
+#line 776 "effective_tld_names.gperf"
+ {"defense.tn", 0},
+#line 1456 "effective_tld_names.gperf"
+ {"hyogo.jp", 2},
+#line 2817 "effective_tld_names.gperf"
+ {"rockart.museum", 0},
+#line 1513 "effective_tld_names.gperf"
+ {"info.mv", 0},
+#line 1738 "effective_tld_names.gperf"
+ {"laakesvuemie.no", 0},
+#line 31 "effective_tld_names.gperf"
+ {"abo.pa", 0},
+#line 2572 "effective_tld_names.gperf"
+ {"pf", 0},
+#line 1807 "effective_tld_names.gperf"
+ {"lodingen.no", 0},
+#line 1664 "effective_tld_names.gperf"
+ {"kiev.ua", 0},
+#line 2019 "effective_tld_names.gperf"
+ {"monmouth.museum", 0},
+#line 2773 "effective_tld_names.gperf"
+ {"rec.nf", 0},
+#line 609 "effective_tld_names.gperf"
+ {"com.bi", 0},
+#line 836 "effective_tld_names.gperf"
+ {"edu.bi", 0},
+#line 1994 "effective_tld_names.gperf"
+ {"mk.ua", 0},
+#line 2912 "effective_tld_names.gperf"
+ {"schweiz.museum", 0},
+#line 3004 "effective_tld_names.gperf"
+ {"snz.ru", 0},
+#line 2458 "effective_tld_names.gperf"
+ {"org.mv", 0},
+#line 2695 "effective_tld_names.gperf"
+ {"principe.st", 0},
+#line 3330 "effective_tld_names.gperf"
+ {"vagsoy.no", 0},
+#line 3377 "effective_tld_names.gperf"
+ {"vibovalentia.it", 0},
+#line 3584 "effective_tld_names.gperf"
+ {"xn--od0alg.cn", 0},
+#line 1777 "effective_tld_names.gperf"
+ {"lerdal.no", 0},
+#line 509 "effective_tld_names.gperf"
+ {"circus.museum", 0},
+#line 2497 "effective_tld_names.gperf"
+ {"org.vn", 0},
+#line 3334 "effective_tld_names.gperf"
+ {"valle.no", 0},
+#line 2495 "effective_tld_names.gperf"
+ {"org.vc", 0},
+#line 3683 "effective_tld_names.gperf"
+ {"york.museum", 0},
+#line 1318 "effective_tld_names.gperf"
+ {"groundhandling.aero", 0},
+#line 364 "effective_tld_names.gperf"
+ {"bjugn.no", 0},
+#line 1492 "effective_tld_names.gperf"
+ {"ind.br", 0},
+#line 2150 "effective_tld_names.gperf"
+ {"net.ba", 0},
+#line 506 "effective_tld_names.gperf"
+ {"cim.br", 0},
+#line 1409 "effective_tld_names.gperf"
+ {"historical.museum", 0},
+#line 387 "effective_tld_names.gperf"
+ {"botanicgarden.museum", 0},
+#line 308 "effective_tld_names.gperf"
+ {"bedzin.pl", 0},
+#line 2750 "effective_tld_names.gperf"
+ {"rade.no", 0},
+#line 797 "effective_tld_names.gperf"
+ {"dni.us", 0},
+#line 3510 "effective_tld_names.gperf"
+ {"xn--gls-elac.no", 0},
+#line 1811 "effective_tld_names.gperf"
+ {"london.museum", 0},
+#line 2097 "effective_tld_names.gperf"
+ {"napoli.it", 0},
+#line 1132 "effective_tld_names.gperf"
+ {"gemological.museum", 0},
+#line 746 "effective_tld_names.gperf"
+ {"culturalcenter.museum", 0},
+#line 3335 "effective_tld_names.gperf"
+ {"valley.museum", 0},
+#line 2467 "effective_tld_names.gperf"
+ {"org.pf", 0},
+#line 2425 "effective_tld_names.gperf"
+ {"org.hk", 0},
+#line 2785 "effective_tld_names.gperf"
+ {"rennesoy.no", 0},
+#line 1560 "effective_tld_names.gperf"
+ {"ishikawa.jp", 2},
+#line 1828 "effective_tld_names.gperf"
+ {"lubin.pl", 0},
+#line 3099 "effective_tld_names.gperf"
+ {"surgeonshall.museum", 0},
+#line 244 "effective_tld_names.gperf"
+ {"aurskog-holand.no", 0},
+#line 2017 "effective_tld_names.gperf"
+ {"moma.museum", 0},
+#line 611 "effective_tld_names.gperf"
+ {"com.bo", 0},
+#line 1613 "effective_tld_names.gperf"
+ {"juedisches.museum", 0},
+#line 838 "effective_tld_names.gperf"
+ {"edu.bo", 0},
+#line 2334 "effective_tld_names.gperf"
+ {"nyny.museum", 0},
+#line 1602 "effective_tld_names.gperf"
+ {"jor.br", 0},
+#line 3656 "effective_tld_names.gperf"
+ {"xn--vgsy-qoa0j.no", 0},
+#line 1071 "effective_tld_names.gperf"
+ {"fot.br", 0},
+#line 1175 "effective_tld_names.gperf"
+ {"gob.bo", 0},
+#line 3682 "effective_tld_names.gperf"
+ {"yokohama.jp", 2},
+#line 1092 "effective_tld_names.gperf"
+ {"fst.br", 0},
+#line 448 "effective_tld_names.gperf"
+ {"can.br", 0},
+#line 2717 "effective_tld_names.gperf"
+ {"przeworsk.pl", 0},
+#line 3096 "effective_tld_names.gperf"
+ {"sumy.ua", 0},
+#line 1212 "effective_tld_names.gperf"
+ {"gov.bo", 0},
+#line 3662 "effective_tld_names.gperf"
+ {"xn--wcvs22d.hk", 0},
+#line 2666 "effective_tld_names.gperf"
+ {"pref.niigata.jp", 1},
+#line 2673 "effective_tld_names.gperf"
+ {"pref.shiga.jp", 1},
+#line 784 "effective_tld_names.gperf"
+ {"detroit.museum", 0},
+#line 942 "effective_tld_names.gperf"
+ {"embaixada.st", 0},
+#line 809 "effective_tld_names.gperf"
+ {"dudinka.ru", 0},
+#line 1721 "effective_tld_names.gperf"
+ {"kvalsund.no", 0},
+#line 3610 "effective_tld_names.gperf"
+ {"xn--s-1fa.no", 0},
+#line 86 "effective_tld_names.gperf"
+ {"afjord.no", 0},
+#line 1814 "effective_tld_names.gperf"
+ {"losangeles.museum", 0},
+#line 3545 "effective_tld_names.gperf"
+ {"xn--l-1fa.no", 0},
+#line 2323 "effective_tld_names.gperf"
+ {"ntr.br", 0},
+#line 1751 "effective_tld_names.gperf"
+ {"lardal.no", 0},
+#line 1876 "effective_tld_names.gperf"
+ {"marketplace.aero", 0},
+#line 1793 "effective_tld_names.gperf"
+ {"lindas.no", 0},
+#line 1794 "effective_tld_names.gperf"
+ {"lindesnes.no", 0},
+#line 119 "effective_tld_names.gperf"
+ {"alabama.museum", 0},
+#line 3608 "effective_tld_names.gperf"
+ {"xn--ryken-vua.no", 0},
+#line 923 "effective_tld_names.gperf"
+ {"educator.aero", 0},
+#line 2087 "effective_tld_names.gperf"
+ {"name.my", 0},
+#line 3559 "effective_tld_names.gperf"
+ {"xn--lt-liac.no", 0},
+#line 1544 "effective_tld_names.gperf"
+ {"intelligence.museum", 0},
+#line 1800 "effective_tld_names.gperf"
+ {"lk", 0},
+#line 2154 "effective_tld_names.gperf"
+ {"net.bo", 0},
+#line 2393 "effective_tld_names.gperf"
+ {"org.af", 0},
+#line 3552 "effective_tld_names.gperf"
+ {"xn--lgrd-poac.no", 0},
+#line 2798 "effective_tld_names.gperf"
+ {"rimini.it", 0},
+#line 381 "effective_tld_names.gperf"
+ {"bolzano.it", 0},
+#line 2980 "effective_tld_names.gperf"
+ {"skierva.no", 0},
+#line 1530 "effective_tld_names.gperf"
+ {"int.bo", 0},
+#line 283 "effective_tld_names.gperf"
+ {"balsfjord.no", 0},
+#line 3185 "effective_tld_names.gperf"
+ {"to", 0},
+#line 3264 "effective_tld_names.gperf"
+ {"tz", 0},
+#line 3141 "effective_tld_names.gperf"
+ {"td", 0},
+#line 2557 "effective_tld_names.gperf"
+ {"pe.ca", 0},
+#line 3239 "effective_tld_names.gperf"
+ {"tt", 0},
+#line 3170 "effective_tld_names.gperf"
+ {"tl", 0},
+#line 3500 "effective_tld_names.gperf"
+ {"xn--fjord-lra.no", 0},
+#line 383 "effective_tld_names.gperf"
+ {"bonn.museum", 0},
+#line 2060 "effective_tld_names.gperf"
+ {"music.museum", 0},
+#line 386 "effective_tld_names.gperf"
+ {"botanicalgarden.museum", 0},
+#line 3186 "effective_tld_names.gperf"
+ {"to.it", 0},
+#line 3523 "effective_tld_names.gperf"
+ {"xn--holtlen-hxa.no", 0},
+#line 3236 "effective_tld_names.gperf"
+ {"ts.it", 0},
+#line 3207 "effective_tld_names.gperf"
+ {"tr", 2},
+#line 2122 "effective_tld_names.gperf"
+ {"nc.us", 0},
+#line 2366 "effective_tld_names.gperf"
+ {"ontario.museum", 0},
+#line 1628 "effective_tld_names.gperf"
+ {"kaluga.ru", 0},
+#line 2635 "effective_tld_names.gperf"
+ {"prd.fr", 0},
+#line 1991 "effective_tld_names.gperf"
+ {"miyazaki.jp", 2},
+#line 126 "effective_tld_names.gperf"
+ {"alstahaug.no", 0},
+#line 3208 "effective_tld_names.gperf"
+ {"tr.it", 0},
+#line 341 "effective_tld_names.gperf"
+ {"bio.br", 0},
+#line 2842 "effective_tld_names.gperf"
+ {"rybnik.pl", 0},
+#line 273 "effective_tld_names.gperf"
+ {"bahn.museum", 0},
+#line 3142 "effective_tld_names.gperf"
+ {"te.it", 0},
+#line 196 "effective_tld_names.gperf"
+ {"arts.museum", 0},
+#line 1778 "effective_tld_names.gperf"
+ {"lesja.no", 0},
+#line 1725 "effective_tld_names.gperf"
+ {"kvinnherad.no", 0},
+#line 3206 "effective_tld_names.gperf"
+ {"tp.it", 0},
+#line 2775 "effective_tld_names.gperf"
+ {"recreation.aero", 0},
+#line 1746 "effective_tld_names.gperf"
+ {"landes.museum", 0},
+#line 1641 "effective_tld_names.gperf"
+ {"katowice.pl", 0},
+#line 369 "effective_tld_names.gperf"
+ {"bmd.br", 0},
+#line 3561 "effective_tld_names.gperf"
+ {"xn--lury-ira.no", 0},
+#line 378 "effective_tld_names.gperf"
+ {"boleslawiec.pl", 0},
+#line 287 "effective_tld_names.gperf"
+ {"barcelona.museum", 0},
+#line 3182 "effective_tld_names.gperf"
+ {"tn", 0},
+#line 1115 "effective_tld_names.gperf"
+ {"gangaviika.no", 0},
+#line 79 "effective_tld_names.gperf"
+ {"aero.mv", 0},
+#line 1691 "effective_tld_names.gperf"
+ {"konskowola.pl", 0},
+#line 2871 "effective_tld_names.gperf"
+ {"sandefjord.no", 0},
+#line 2496 "effective_tld_names.gperf"
+ {"org.vi", 0},
+#line 2264 "effective_tld_names.gperf"
+ {"nkz.ru", 0},
+#line 3587 "effective_tld_names.gperf"
+ {"xn--oppegrd-ixa.no", 0},
+#line 3183 "effective_tld_names.gperf"
+ {"tn.it", 0},
+#line 765 "effective_tld_names.gperf"
+ {"dallas.museum", 0},
+#line 1742 "effective_tld_names.gperf"
+ {"lajolla.museum", 0},
+#line 3171 "effective_tld_names.gperf"
+ {"tm", 0},
+#line 2877 "effective_tld_names.gperf"
+ {"sanok.pl", 0},
+#line 3337 "effective_tld_names.gperf"
+ {"vantaa.museum", 0},
+#line 2793 "effective_tld_names.gperf"
+ {"rg.it", 0},
+#line 3349 "effective_tld_names.gperf"
+ {"vefsn.no", 0},
+#line 349 "effective_tld_names.gperf"
+ {"biz.bb", 0},
+#line 1155 "effective_tld_names.gperf"
+ {"glas.museum", 0},
+#line 2583 "effective_tld_names.gperf"
+ {"photography.museum", 0},
+#line 3178 "effective_tld_names.gperf"
+ {"tm.pl", 0},
+#line 2486 "effective_tld_names.gperf"
+ {"org.st", 0},
+#line 2492 "effective_tld_names.gperf"
+ {"org.tt", 0},
+#line 1412 "effective_tld_names.gperf"
+ {"historisch.museum", 0},
+#line 3386 "effective_tld_names.gperf"
+ {"village.museum", 0},
+#line 2051 "effective_tld_names.gperf"
+ {"mus.br", 0},
+#line 1797 "effective_tld_names.gperf"
+ {"living.museum", 0},
+#line 515 "effective_tld_names.gperf"
+ {"city.kitakyushu.jp", 1},
+#line 1752 "effective_tld_names.gperf"
+ {"larsson.museum", 0},
+#line 2105 "effective_tld_names.gperf"
+ {"nationalfirearms.museum", 0},
+#line 2987 "effective_tld_names.gperf"
+ {"skole.museum", 0},
+#line 2797 "effective_tld_names.gperf"
+ {"riik.ee", 0},
+#line 339 "effective_tld_names.gperf"
+ {"bill.museum", 0},
+#line 2365 "effective_tld_names.gperf"
+ {"online.museum", 0},
+#line 1663 "effective_tld_names.gperf"
+ {"kids.us", 0},
+#line 271 "effective_tld_names.gperf"
+ {"bahcavuotna.no", 0},
+#line 2474 "effective_tld_names.gperf"
+ {"org.pt", 0},
+#line 3342 "effective_tld_names.gperf"
+ {"varoy.no", 0},
+#line 974 "effective_tld_names.gperf"
+ {"eti.br", 0},
+#line 3607 "effective_tld_names.gperf"
+ {"xn--rsta-fra.no", 0},
+#line 3573 "effective_tld_names.gperf"
+ {"xn--moreke-jua.no", 0},
+#line 1069 "effective_tld_names.gperf"
+ {"forum.hu", 0},
+#line 3563 "effective_tld_names.gperf"
+ {"xn--merker-kua.no", 0},
+#line 2259 "effective_tld_names.gperf"
+ {"niigata.jp", 2},
+#line 3126 "effective_tld_names.gperf"
+ {"ta.it", 0},
+#line 786 "effective_tld_names.gperf"
+ {"dielddanuorri.no", 0},
+#line 1413 "effective_tld_names.gperf"
+ {"historisches.museum", 0},
+#line 2840 "effective_tld_names.gperf"
+ {"rw", 0},
+#line 1057 "effective_tld_names.gperf"
+ {"fnd.br", 0},
+#line 1989 "effective_tld_names.gperf"
+ {"missoula.museum", 0},
+#line 146 "effective_tld_names.gperf"
+ {"amusement.aero", 0},
+#line 323 "effective_tld_names.gperf"
+ {"bern.museum", 0},
+#line 58 "effective_tld_names.gperf"
+ {"ac.tj", 0},
+#line 1209 "effective_tld_names.gperf"
+ {"gov.bf", 0},
+#line 3246 "effective_tld_names.gperf"
+ {"tuva.ru", 0},
+#line 2053 "effective_tld_names.gperf"
+ {"museum", 0},
+#line 2582 "effective_tld_names.gperf"
+ {"phoenix.museum", 0},
+#line 1407 "effective_tld_names.gperf"
+ {"hiroshima.jp", 2},
+#line 2826 "effective_tld_names.gperf"
+ {"rost.no", 0},
+#line 1116 "effective_tld_names.gperf"
+ {"gangwon.kr", 0},
+#line 3179 "effective_tld_names.gperf"
+ {"tm.ro", 0},
+#line 2764 "effective_tld_names.gperf"
+ {"ravenna.it", 0},
+#line 959 "effective_tld_names.gperf"
+ {"environmentalconservation.museum", 0},
+#line 1884 "effective_tld_names.gperf"
+ {"mat.br", 0},
+#line 720 "effective_tld_names.gperf"
+ {"coop.mw", 0},
+#line 2057 "effective_tld_names.gperf"
+ {"museum.tt", 0},
+#line 3391 "effective_tld_names.gperf"
+ {"virtuel.museum", 0},
+#line 1715 "effective_tld_names.gperf"
+ {"kursk.ru", 0},
+#line 3501 "effective_tld_names.gperf"
+ {"xn--fl-zia.no", 0},
+#line 1791 "effective_tld_names.gperf"
+ {"limanowa.pl", 0},
+#line 3209 "effective_tld_names.gperf"
+ {"tr.no", 0},
+#line 2904 "effective_tld_names.gperf"
+ {"sch.ly", 0},
+#line 2348 "effective_tld_names.gperf"
+ {"og.ao", 0},
+#line 2925 "effective_tld_names.gperf"
+ {"sd.cn", 0},
+#line 1731 "effective_tld_names.gperf"
+ {"kyoto.jp", 2},
+#line 1770 "effective_tld_names.gperf"
+ {"leikanger.no", 0},
+#line 2048 "effective_tld_names.gperf"
+ {"muncie.museum", 0},
+#line 3629 "effective_tld_names.gperf"
+ {"xn--sr-fron-q1a.no", 0},
+#line 1630 "effective_tld_names.gperf"
+ {"kanagawa.jp", 2},
+#line 3117 "effective_tld_names.gperf"
+ {"sykkylven.no", 0},
+#line 1411 "effective_tld_names.gperf"
+ {"historichouses.museum", 0},
+#line 2422 "effective_tld_names.gperf"
+ {"org.gn", 0},
+#line 2424 "effective_tld_names.gperf"
+ {"org.gr", 0},
+#line 1335 "effective_tld_names.gperf"
+ {"gs.of.no", 0},
+#line 1900 "effective_tld_names.gperf"
+ {"med.br", 0},
+#line 2343 "effective_tld_names.gperf"
+ {"odessa.ua", 0},
+#line 2578 "effective_tld_names.gperf"
+ {"pharmacy.museum", 0},
+#line 1387 "effective_tld_names.gperf"
+ {"hattfjelldal.no", 0},
+#line 2423 "effective_tld_names.gperf"
+ {"org.gp", 0},
+#line 2056 "effective_tld_names.gperf"
+ {"museum.no", 0},
+#line 994 "effective_tld_names.gperf"
+ {"farm.museum", 0},
+#line 1815 "effective_tld_names.gperf"
+ {"loten.no", 0},
+#line 3145 "effective_tld_names.gperf"
+ {"tel", 0},
+#line 3463 "effective_tld_names.gperf"
+ {"xj.cn", 0},
+#line 3704 "effective_tld_names.gperf"
+ {"zj.cn", 0},
+#line 3268 "effective_tld_names.gperf"
+ {"uba.ar", 1},
+#line 1844 "effective_tld_names.gperf"
+ {"lyngen.no", 0},
+#line 3174 "effective_tld_names.gperf"
+ {"tm.km", 0},
+#line 3177 "effective_tld_names.gperf"
+ {"tm.no", 0},
+#line 1739 "effective_tld_names.gperf"
+ {"labor.museum", 0},
+#line 2755 "effective_tld_names.gperf"
+ {"raholt.no", 0},
+#line 3602 "effective_tld_names.gperf"
+ {"xn--rlingen-mxa.no", 0},
+#line 2999 "effective_tld_names.gperf"
+ {"sn.cn", 0},
+#line 3411 "effective_tld_names.gperf"
+ {"vt.us", 0},
+#line 2086 "effective_tld_names.gperf"
+ {"name.mv", 0},
+#line 3390 "effective_tld_names.gperf"
+ {"virtual.museum", 0},
+#line 1022 "effective_tld_names.gperf"
+ {"film.museum", 0},
+#line 1570 "effective_tld_names.gperf"
+ {"iwate.jp", 2},
+#line 3421 "effective_tld_names.gperf"
+ {"wakayama.jp", 2},
+#line 2850 "effective_tld_names.gperf"
+ {"sa.cr", 0},
+#line 1754 "effective_tld_names.gperf"
+ {"laspezia.it", 0},
+#line 501 "effective_tld_names.gperf"
+ {"chungbuk.kr", 0},
+#line 3327 "effective_tld_names.gperf"
+ {"vadso.no", 0},
+#line 3381 "effective_tld_names.gperf"
+ {"vicenza.it", 0},
+#line 2367 "effective_tld_names.gperf"
+ {"openair.museum", 0},
+#line 2945 "effective_tld_names.gperf"
+ {"sf.no", 0},
+#line 2418 "effective_tld_names.gperf"
+ {"org.ge", 0},
+#line 2384 "effective_tld_names.gperf"
+ {"or.tz", 0},
+#line 2382 "effective_tld_names.gperf"
+ {"or.pw", 0},
+#line 3230 "effective_tld_names.gperf"
+ {"tromsa.no", 0},
+#line 3128 "effective_tld_names.gperf"
+ {"tana.no", 0},
+#line 2483 "effective_tld_names.gperf"
+ {"org.sg", 0},
+#line 787 "effective_tld_names.gperf"
+ {"dinosaur.museum", 0},
+#line 2498 "effective_tld_names.gperf"
+ {"org.ws", 0},
+#line 2513 "effective_tld_names.gperf"
+ {"osteroy.no", 0},
+#line 3507 "effective_tld_names.gperf"
+ {"xn--gildeskl-g0a.no", 0},
+#line 3082 "effective_tld_names.gperf"
+ {"storfjord.no", 0},
+#line 768 "effective_tld_names.gperf"
+ {"davvesiida.no", 0},
+#line 1483 "effective_tld_names.gperf"
+ {"imperia.it", 0},
+#line 3313 "effective_tld_names.gperf"
+ {"utazas.hu", 0},
+#line 3513 "effective_tld_names.gperf"
+ {"xn--h-2fa.no", 0},
+#line 3380 "effective_tld_names.gperf"
+ {"vic.gov.au", 0},
+#line 778 "effective_tld_names.gperf"
+ {"delmenhorst.museum", 0},
+#line 2487 "effective_tld_names.gperf"
+ {"org.sy", 0},
+#line 2449 "effective_tld_names.gperf"
+ {"org.ly", 0},
+#line 992 "effective_tld_names.gperf"
+ {"far.br", 0},
+#line 2655 "effective_tld_names.gperf"
+ {"pref.kagoshima.jp", 1},
+#line 100 "effective_tld_names.gperf"
+ {"aichi.jp", 2},
+#line 2741 "effective_tld_names.gperf"
+ {"qh.cn", 0},
+#line 782 "effective_tld_names.gperf"
+ {"design.aero", 0},
+#line 1614 "effective_tld_names.gperf"
+ {"juif.museum", 0},
+#line 3218 "effective_tld_names.gperf"
+ {"travel", 0},
+#line 2990 "effective_tld_names.gperf"
+ {"slask.pl", 0},
+#line 3220 "effective_tld_names.gperf"
+ {"travel.tt", 0},
+#line 507 "effective_tld_names.gperf"
+ {"cincinnati.museum", 0},
+#line 444 "effective_tld_names.gperf"
+ {"california.museum", 0},
+#line 785 "effective_tld_names.gperf"
+ {"dgca.aero", 0},
+#line 1831 "effective_tld_names.gperf"
+ {"lugansk.ua", 0},
+#line 3344 "effective_tld_names.gperf"
+ {"vc", 0},
+#line 3422 "effective_tld_names.gperf"
+ {"walbrzych.pl", 0},
+#line 3172 "effective_tld_names.gperf"
+ {"tm.fr", 0},
+#line 1983 "effective_tld_names.gperf"
+ {"mill.museum", 0},
+#line 3240 "effective_tld_names.gperf"
+ {"tula.ru", 0},
+#line 1372 "effective_tld_names.gperf"
+ {"halloffame.museum", 0},
+#line 3158 "effective_tld_names.gperf"
+ {"th", 0},
+#line 2463 "effective_tld_names.gperf"
+ {"org.ng", 0},
+#line 2414 "effective_tld_names.gperf"
+ {"org.dz", 0},
+#line 3407 "effective_tld_names.gperf"
+ {"vossevangen.no", 0},
+#line 2488 "effective_tld_names.gperf"
+ {"org.sz", 0},
+#line 2639 "effective_tld_names.gperf"
+ {"pref.akita.jp", 1},
+#line 1868 "effective_tld_names.gperf"
+ {"manx.museum", 0},
+#line 1764 "effective_tld_names.gperf"
+ {"leasing.aero", 0},
+#line 2670 "effective_tld_names.gperf"
+ {"pref.osaka.jp", 1},
+#line 1835 "effective_tld_names.gperf"
+ {"luroy.no", 0},
+#line 3021 "effective_tld_names.gperf"
+ {"sor-aurdal.no", 0},
+#line 3263 "effective_tld_names.gperf"
+ {"tyumen.ru", 0},
+#line 3345 "effective_tld_names.gperf"
+ {"vc.it", 0},
+#line 3219 "effective_tld_names.gperf"
+ {"travel.pl", 0},
+#line 3125 "effective_tld_names.gperf"
+ {"t.se", 0},
+#line 2984 "effective_tld_names.gperf"
+ {"sklep.pl", 0},
+#line 278 "effective_tld_names.gperf"
+ {"bale.museum", 0},
+#line 3325 "effective_tld_names.gperf"
+ {"va.us", 0},
+#line 2571 "effective_tld_names.gperf"
+ {"pescara.it", 0},
+#line 2452 "effective_tld_names.gperf"
+ {"org.mg", 0},
+#line 1320 "effective_tld_names.gperf"
+ {"grozny.ru", 0},
+#line 1687 "effective_tld_names.gperf"
+ {"komvux.se", 0},
+#line 817 "effective_tld_names.gperf"
+ {"eastafrica.museum", 0},
+#line 1843 "effective_tld_names.gperf"
+ {"lyngdal.no", 0},
+#line 1462 "effective_tld_names.gperf"
+ {"ibestad.no", 0},
+#line 2461 "effective_tld_names.gperf"
+ {"org.my", 0},
+#line 361 "effective_tld_names.gperf"
+ {"bj.cn", 0},
+#line 1345 "effective_tld_names.gperf"
+ {"gs.vf.no", 0},
+#line 3231 "effective_tld_names.gperf"
+ {"tromso.no", 0},
+#line 2394 "effective_tld_names.gperf"
+ {"org.ag", 0},
+#line 2421 "effective_tld_names.gperf"
+ {"org.gi", 0},
+#line 2851 "effective_tld_names.gperf"
+ {"sa.edu.au", 0},
+#line 3536 "effective_tld_names.gperf"
+ {"xn--krager-gya.no", 0},
+#line 921 "effective_tld_names.gperf"
+ {"education.museum", 0},
+#line 3588 "effective_tld_names.gperf"
+ {"xn--ostery-fya.no", 0},
+#line 2837 "effective_tld_names.gperf"
+ {"ruovat.no", 0},
+#line 2503 "effective_tld_names.gperf"
+ {"orskog.no", 0},
+#line 2683 "effective_tld_names.gperf"
+ {"pref.yamanashi.jp", 1},
+#line 3533 "effective_tld_names.gperf"
+ {"xn--kfjord-iua.no", 0},
+#line 3399 "effective_tld_names.gperf"
+ {"vn.ua", 0},
+#line 3212 "effective_tld_names.gperf"
+ {"trainer.aero", 0},
+#line 3039 "effective_tld_names.gperf"
+ {"spy.museum", 0},
+#line 3135 "effective_tld_names.gperf"
+ {"tas.edu.au", 0},
+#line 3672 "effective_tld_names.gperf"
+ {"yakutia.ru", 0},
+#line 2591 "effective_tld_names.gperf"
+ {"pisz.pl", 0},
+#line 1863 "effective_tld_names.gperf"
+ {"manchester.museum", 0},
+#line 3201 "effective_tld_names.gperf"
+ {"tourism.pl", 0},
+#line 345 "effective_tld_names.gperf"
+ {"birthplace.museum", 0},
+#line 1420 "effective_tld_names.gperf"
+ {"hk.cn", 0},
+#line 3455 "effective_tld_names.gperf"
+ {"wroclaw.pl", 0},
+#line 1640 "effective_tld_names.gperf"
+ {"kaszuby.pl", 0},
+#line 2427 "effective_tld_names.gperf"
+ {"org.ht", 0},
+#line 1917 "effective_tld_names.gperf"
+ {"medizinhistorisches.museum", 0},
+#line 3137 "effective_tld_names.gperf"
+ {"tatarstan.ru", 0},
+#line 3227 "effective_tld_names.gperf"
+ {"troandin.no", 0},
+#line 1949 "effective_tld_names.gperf"
+ {"mil.br", 0},
+#line 2691 "effective_tld_names.gperf"
+ {"presse.fr", 0},
+#line 2398 "effective_tld_names.gperf"
+ {"org.az", 0},
+#line 3247 "effective_tld_names.gperf"
+ {"tv", 0},
+#line 3202 "effective_tld_names.gperf"
+ {"tourism.tn", 0},
+#line 3400 "effective_tld_names.gperf"
+ {"voagat.no", 0},
+#line 3261 "effective_tld_names.gperf"
+ {"tysnes.no", 0},
+#line 3375 "effective_tld_names.gperf"
+ {"vi.us", 0},
+#line 512 "effective_tld_names.gperf"
+ {"city.hiroshima.jp", 1},
+#line 2747 "effective_tld_names.gperf"
+ {"r.bg", 0},
+#line 2914 "effective_tld_names.gperf"
+ {"science.museum", 0},
+#line 2920 "effective_tld_names.gperf"
+ {"sciences.museum", 0},
+#line 1141 "effective_tld_names.gperf"
+ {"ggf.br", 0},
+#line 1625 "effective_tld_names.gperf"
+ {"kagoshima.jp", 2},
+#line 783 "effective_tld_names.gperf"
+ {"design.museum", 0},
+#line 3250 "effective_tld_names.gperf"
+ {"tv.it", 0},
+#line 2983 "effective_tld_names.gperf"
+ {"skjervoy.no", 0},
+#line 791 "effective_tld_names.gperf"
+ {"dj", 0},
+#line 3521 "effective_tld_names.gperf"
+ {"xn--hnefoss-q1a.no", 0},
+#line 526 "effective_tld_names.gperf"
+ {"city.yokohama.jp", 1},
+#line 759 "effective_tld_names.gperf"
+ {"d.bg", 0},
+#line 3680 "effective_tld_names.gperf"
+ {"yk.ca", 0},
+#line 1037 "effective_tld_names.gperf"
+ {"fj.cn", 0},
+#line 2568 "effective_tld_names.gperf"
+ {"perugia.it", 0},
+#line 3248 "effective_tld_names.gperf"
+ {"tv.bo", 0},
+#line 135 "effective_tld_names.gperf"
+ {"ambulance.aero", 0},
+#line 1527 "effective_tld_names.gperf"
+ {"insurance.aero", 0},
+#line 3022 "effective_tld_names.gperf"
+ {"sor-fron.no", 0},
+#line 1927 "effective_tld_names.gperf"
+ {"metro.tokyo.jp", 1},
+#line 3249 "effective_tld_names.gperf"
+ {"tv.br", 0},
+#line 3403 "effective_tld_names.gperf"
+ {"volkenkunde.museum", 0},
+#line 2585 "effective_tld_names.gperf"
+ {"piacenza.it", 0},
+#line 983 "effective_tld_names.gperf"
+ {"exchange.aero", 0},
+#line 2819 "effective_tld_names.gperf"
+ {"rollag.no", 0},
+#line 2948 "effective_tld_names.gperf"
+ {"sh.cn", 0},
+#line 2781 "effective_tld_names.gperf"
+ {"rel.ht", 0},
+#line 1060 "effective_tld_names.gperf"
+ {"folkebibl.no", 0},
+#line 2783 "effective_tld_names.gperf"
+ {"rendalen.no", 0},
+#line 3164 "effective_tld_names.gperf"
+ {"tinn.no", 0},
+#line 2838 "effective_tld_names.gperf"
+ {"russia.museum", 0},
+#line 514 "effective_tld_names.gperf"
+ {"city.kawasaki.jp", 1},
+#line 3447 "effective_tld_names.gperf"
+ {"wlocl.pl", 0},
+#line 1084 "effective_tld_names.gperf"
+ {"fribourg.museum", 0},
+#line 1773 "effective_tld_names.gperf"
+ {"leka.no", 0},
+#line 922 "effective_tld_names.gperf"
+ {"educational.museum", 0},
+#line 1122 "effective_tld_names.gperf"
+ {"gb.net", 0},
+#line 614 "effective_tld_names.gperf"
+ {"com.by", 0},
+#line 2552 "effective_tld_names.gperf"
+ {"pb.ao", 0},
+#line 121 "effective_tld_names.gperf"
+ {"aland.fi", 0},
+#line 2901 "effective_tld_names.gperf"
+ {"sch.je", 0},
+#line 3494 "effective_tld_names.gperf"
+ {"xn--davvenjrga-y4a.no", 0},
+#line 2313 "effective_tld_names.gperf"
+ {"nsk.ru", 0},
+#line 3698 "effective_tld_names.gperf"
+ {"zaporizhzhe.ua", 0},
+#line 1215 "effective_tld_names.gperf"
+ {"gov.by", 0},
+#line 1643 "effective_tld_names.gperf"
+ {"kawasaki.jp", 2},
+#line 2703 "effective_tld_names.gperf"
+ {"pro.br", 0},
+#line 3161 "effective_tld_names.gperf"
+ {"time.no", 0},
+#line 2337 "effective_tld_names.gperf"
+ {"o.bg", 0},
+#line 2721 "effective_tld_names.gperf"
+ {"pskov.ru", 0},
+#line 2828 "effective_tld_names.gperf"
+ {"rovigo.it", 0},
+#line 3180 "effective_tld_names.gperf"
+ {"tm.se", 0},
+#line 2719 "effective_tld_names.gperf"
+ {"psc.br", 0},
+#line 2630 "effective_tld_names.gperf"
+ {"ppg.br", 0},
+#line 1736 "effective_tld_names.gperf"
+ {"la-spezia.it", 0},
+#line 3304 "effective_tld_names.gperf"
+ {"usdecorativearts.museum", 0},
+#line 807 "effective_tld_names.gperf"
+ {"drangedal.no", 0},
+#line 615 "effective_tld_names.gperf"
+ {"com.bz", 0},
+#line 3262 "effective_tld_names.gperf"
+ {"tysvar.no", 0},
+#line 841 "effective_tld_names.gperf"
+ {"edu.bz", 0},
+#line 3382 "effective_tld_names.gperf"
+ {"video.hu", 0},
+#line 803 "effective_tld_names.gperf"
+ {"dovre.no", 0},
+#line 1131 "effective_tld_names.gperf"
+ {"geelvinck.museum", 0},
+#line 1370 "effective_tld_names.gperf"
+ {"hagebostad.no", 0},
+#line 1947 "effective_tld_names.gperf"
+ {"mil.ba", 0},
+#line 1760 "effective_tld_names.gperf"
+ {"lc", 0},
+#line 2756 "effective_tld_names.gperf"
+ {"railroad.museum", 0},
+#line 1683 "effective_tld_names.gperf"
+ {"komforb.se", 0},
+#line 619 "effective_tld_names.gperf"
+ {"com.cu", 0},
+#line 726 "effective_tld_names.gperf"
+ {"costume.museum", 0},
+#line 845 "effective_tld_names.gperf"
+ {"edu.cu", 0},
+#line 2675 "effective_tld_names.gperf"
+ {"pref.shizuoka.jp", 1},
+#line 1216 "effective_tld_names.gperf"
+ {"gov.bz", 0},
+#line 3127 "effective_tld_names.gperf"
+ {"tambov.ru", 0},
+#line 2383 "effective_tld_names.gperf"
+ {"or.th", 0},
+#line 2536 "effective_tld_names.gperf"
+ {"padua.it", 0},
+#line 2370 "effective_tld_names.gperf"
+ {"opole.pl", 0},
+#line 3527 "effective_tld_names.gperf"
+ {"xn--indery-fya.no", 0},
+#line 1761 "effective_tld_names.gperf"
+ {"lc.it", 0},
+#line 3253 "effective_tld_names.gperf"
+ {"tver.ru", 0},
+#line 1222 "effective_tld_names.gperf"
+ {"gov.cu", 0},
+#line 3216 "effective_tld_names.gperf"
+ {"transport.museum", 0},
+#line 3659 "effective_tld_names.gperf"
+ {"xn--vre-eiker-k8a.no", 0},
+#line 1737 "effective_tld_names.gperf"
+ {"la.us", 0},
+#line 2521 "effective_tld_names.gperf"
+ {"overhalla.no", 0},
+#line 2654 "effective_tld_names.gperf"
+ {"pref.kagawa.jp", 1},
+#line 2799 "effective_tld_names.gperf"
+ {"rindal.no", 0},
+#line 3505 "effective_tld_names.gperf"
+ {"xn--frya-hra.no", 0},
+#line 2680 "effective_tld_names.gperf"
+ {"pref.wakayama.jp", 1},
+#line 3229 "effective_tld_names.gperf"
+ {"trolley.museum", 0},
+#line 3401 "effective_tld_names.gperf"
+ {"volda.no", 0},
+#line 1339 "effective_tld_names.gperf"
+ {"gs.sf.no", 0},
+#line 307 "effective_tld_names.gperf"
+ {"beauxarts.museum", 0},
+#line 64 "effective_tld_names.gperf"
+ {"accident-investigation.aero", 0},
+#line 2157 "effective_tld_names.gperf"
+ {"net.bz", 0},
+#line 2923 "effective_tld_names.gperf"
+ {"scotland.museum", 0},
+#line 3217 "effective_tld_names.gperf"
+ {"trapani.it", 0},
+#line 3465 "effective_tld_names.gperf"
+ {"xn--55qx5d.hk", 0},
+#line 2643 "effective_tld_names.gperf"
+ {"pref.fukui.jp", 1},
+#line 136 "effective_tld_names.gperf"
+ {"ambulance.museum", 0},
+#line 2434 "effective_tld_names.gperf"
+ {"org.je", 0},
+#line 1501 "effective_tld_names.gperf"
+ {"inf.cu", 0},
+#line 2161 "effective_tld_names.gperf"
+ {"net.cu", 0},
+#line 3028 "effective_tld_names.gperf"
+ {"sorum.no", 0},
+#line 792 "effective_tld_names.gperf"
+ {"dk", 0},
+#line 2979 "effective_tld_names.gperf"
+ {"skien.no", 0},
+#line 1458 "effective_tld_names.gperf"
+ {"i.ph", 0},
+#line 643 "effective_tld_names.gperf"
+ {"com.km", 0},
+#line 2735 "effective_tld_names.gperf"
+ {"pyatigorsk.ru", 0},
+#line 867 "effective_tld_names.gperf"
+ {"edu.km", 0},
+#line 2760 "effective_tld_names.gperf"
+ {"ralingen.no", 0},
+#line 3621 "effective_tld_names.gperf"
+ {"xn--slt-elab.no", 0},
+#line 3224 "effective_tld_names.gperf"
+ {"trento.it", 0},
+#line 3223 "effective_tld_names.gperf"
+ {"trentino.it", 0},
+#line 213 "effective_tld_names.gperf"
+ {"ass.km", 0},
+#line 1246 "effective_tld_names.gperf"
+ {"gov.km", 0},
+#line 2054 "effective_tld_names.gperf"
+ {"museum.mv", 0},
+#line 3225 "effective_tld_names.gperf"
+ {"treviso.it", 0},
+#line 2681 "effective_tld_names.gperf"
+ {"pref.yamagata.jp", 1},
+#line 2868 "effective_tld_names.gperf"
+ {"sande.more-og-romsdal.no", 0},
+#line 3339 "effective_tld_names.gperf"
+ {"vardo.no", 0},
+#line 1948 "effective_tld_names.gperf"
+ {"mil.bo", 0},
+#line 1911 "effective_tld_names.gperf"
+ {"medecin.km", 0},
+#line 2281 "effective_tld_names.gperf"
+ {"nom.km", 0},
+#line 1430 "effective_tld_names.gperf"
+ {"hokksund.no", 0},
+#line 91 "effective_tld_names.gperf"
+ {"agr.br", 0},
+#line 1183 "effective_tld_names.gperf"
+ {"gobiernoelectronico.ar", 1},
+#line 763 "effective_tld_names.gperf"
+ {"dagestan.ru", 0},
+#line 473 "effective_tld_names.gperf"
+ {"certification.aero", 0},
+#line 2986 "effective_tld_names.gperf"
+ {"skodje.no", 0},
+#line 3251 "effective_tld_names.gperf"
+ {"tv.na", 0},
+#line 66 "effective_tld_names.gperf"
+ {"act.au", 0},
+#line 3499 "effective_tld_names.gperf"
+ {"xn--finny-yua.no", 0},
+#line 1886 "effective_tld_names.gperf"
+ {"matta-varjjat.no", 0},
+#line 2902 "effective_tld_names.gperf"
+ {"sch.jo", 0},
+#line 2763 "effective_tld_names.gperf"
+ {"rauma.no", 0},
+#line 2668 "effective_tld_names.gperf"
+ {"pref.okayama.jp", 1},
+#line 2356 "effective_tld_names.gperf"
+ {"olawa.pl", 0},
+#line 2804 "effective_tld_names.gperf"
+ {"risor.no", 0},
+#line 2829 "effective_tld_names.gperf"
+ {"rovno.ua", 0},
+#line 2825 "effective_tld_names.gperf"
+ {"roros.no", 0},
+#line 775 "effective_tld_names.gperf"
+ {"decorativearts.museum", 0},
+#line 1678 "effective_tld_names.gperf"
+ {"kochi.jp", 2},
+#line 3622 "effective_tld_names.gperf"
+ {"xn--smla-hra.no", 0},
+#line 3383 "effective_tld_names.gperf"
+ {"vik.no", 0},
+#line 2499 "effective_tld_names.gperf"
+ {"oristano.it", 0},
+#line 60 "effective_tld_names.gperf"
+ {"ac.ug", 0},
+#line 2745 "effective_tld_names.gperf"
+ {"qsl.br", 0},
+#line 3156 "effective_tld_names.gperf"
+ {"tg", 0},
+#line 294 "effective_tld_names.gperf"
+ {"baseball.museum", 0},
+#line 3503 "effective_tld_names.gperf"
+ {"xn--frde-gra.no", 0},
+#line 2439 "effective_tld_names.gperf"
+ {"org.kn", 0},
+#line 2892 "effective_tld_names.gperf"
+ {"sb", 0},
+#line 3159 "effective_tld_names.gperf"
+ {"theater.museum", 0},
+#line 946 "effective_tld_names.gperf"
+ {"encyclopedic.museum", 0},
+#line 2656 "effective_tld_names.gperf"
+ {"pref.kanagawa.jp", 1},
+#line 2993 "effective_tld_names.gperf"
+ {"slg.br", 0},
+#line 3353 "effective_tld_names.gperf"
+ {"venice.it", 0},
+#line 3196 "effective_tld_names.gperf"
+ {"torino.it", 0},
+#line 3402 "effective_tld_names.gperf"
+ {"volgograd.ru", 0},
+#line 2921 "effective_tld_names.gperf"
+ {"sciencesnaturelles.museum", 0},
+#line 2751 "effective_tld_names.gperf"
+ {"radom.pl", 0},
+#line 3120 "effective_tld_names.gperf"
+ {"szczecin.pl", 0},
+#line 3482 "effective_tld_names.gperf"
+ {"xn--bjddar-pta.no", 0},
+#line 3045 "effective_tld_names.gperf"
+ {"srv.br", 0},
+#line 3535 "effective_tld_names.gperf"
+ {"xn--koluokta-7ya57h.no", 0},
+#line 2991 "effective_tld_names.gperf"
+ {"slattum.no", 0},
+#line 802 "effective_tld_names.gperf"
+ {"donostia.museum", 0},
+#line 3439 "effective_tld_names.gperf"
+ {"westfalen.museum", 0},
+#line 2818 "effective_tld_names.gperf"
+ {"rodoy.no", 0},
+#line 2111 "effective_tld_names.gperf"
+ {"naturbruksgymn.se", 0},
+#line 2435 "effective_tld_names.gperf"
+ {"org.jo", 0},
+#line 3254 "effective_tld_names.gperf"
+ {"tw", 0},
+#line 3509 "effective_tld_names.gperf"
+ {"xn--gjvik-wua.no", 0},
+#line 3148 "effective_tld_names.gperf"
+ {"teramo.it", 0},
+#line 3368 "effective_tld_names.gperf"
+ {"veterinaire.km", 0},
+#line 3150 "effective_tld_names.gperf"
+ {"ternopil.ua", 0},
+#line 2698 "effective_tld_names.gperf"
+ {"priv.me", 0},
+#line 3037 "effective_tld_names.gperf"
+ {"spjelkavik.no", 0},
+#line 2040 "effective_tld_names.gperf"
+ {"msk.ru", 0},
+#line 2526 "effective_tld_names.gperf"
+ {"oystre-slidre.no", 0},
+#line 1798 "effective_tld_names.gperf"
+ {"livinghistory.museum", 0},
+#line 3365 "effective_tld_names.gperf"
+ {"vestvagoy.no", 0},
+#line 3389 "effective_tld_names.gperf"
+ {"virginia.museum", 0},
+#line 3176 "effective_tld_names.gperf"
+ {"tm.mg", 0},
+#line 801 "effective_tld_names.gperf"
+ {"donna.no", 0},
+#line 3341 "effective_tld_names.gperf"
+ {"varggat.no", 0},
+#line 2844 "effective_tld_names.gperf"
+ {"rzeszow.pl", 0},
+#line 777 "effective_tld_names.gperf"
+ {"delaware.museum", 0},
+#line 3234 "effective_tld_names.gperf"
+ {"trustee.museum", 0},
+#line 175 "effective_tld_names.gperf"
+ {"architecture.museum", 0},
+#line 3595 "effective_tld_names.gperf"
+ {"xn--rdy-0nab.no", 0},
+#line 3197 "effective_tld_names.gperf"
+ {"torino.museum", 0},
+#line 1617 "effective_tld_names.gperf"
+ {"jx.cn", 0},
+#line 1358 "effective_tld_names.gperf"
+ {"gx.cn", 0},
+#line 2899 "effective_tld_names.gperf"
+ {"sch.gg", 0},
+#line 2387 "effective_tld_names.gperf"
+ {"oregon.museum", 0},
+#line 2752 "effective_tld_names.gperf"
+ {"radoy.no", 0},
+#line 799 "effective_tld_names.gperf"
+ {"dolls.museum", 0},
+#line 2331 "effective_tld_names.gperf"
+ {"nx.cn", 0},
#line 3485 "effective_tld_names.gperf"
- {"xn--krjohka-hwab49j.no", 0}
+ {"xn--bod-2na.no", 0},
+#line 3151 "effective_tld_names.gperf"
+ {"test.ru", 0},
+#line 3205 "effective_tld_names.gperf"
+ {"tozsde.hu", 0},
+#line 1341 "effective_tld_names.gperf"
+ {"gs.svalbard.no", 0},
+#line 2887 "effective_tld_names.gperf"
+ {"satx.museum", 0},
+#line 1668 "effective_tld_names.gperf"
+ {"kitakyushu.jp", 2},
+#line 3054 "effective_tld_names.gperf"
+ {"starachowice.pl", 0},
+#line 1062 "effective_tld_names.gperf"
+ {"force.museum", 0},
+#line 3539 "effective_tld_names.gperf"
+ {"xn--krehamn-dxa.no", 0},
+#line 1473 "effective_tld_names.gperf"
+ {"if.ua", 0},
+#line 2437 "effective_tld_names.gperf"
+ {"org.ki", 0},
+#line 3464 "effective_tld_names.gperf"
+ {"xn--55qx5d.cn", 0},
+#line 38 "effective_tld_names.gperf"
+ {"ac.cr", 0},
+#line 2047 "effective_tld_names.gperf"
+ {"mulhouse.museum", 0},
+#line 3504 "effective_tld_names.gperf"
+ {"xn--frna-woa.no", 0},
+#line 3652 "effective_tld_names.gperf"
+ {"xn--vegrshei-c0a.no", 0},
+#line 2720 "effective_tld_names.gperf"
+ {"psi.br", 0},
+#line 22 "effective_tld_names.gperf"
+ {"6bone.pl", 0},
+#line 1474 "effective_tld_names.gperf"
+ {"iki.fi", 0},
+#line 1085 "effective_tld_names.gperf"
+ {"frog.museum", 0},
+#line 2800 "effective_tld_names.gperf"
+ {"ringebu.no", 0},
+#line 2505 "effective_tld_names.gperf"
+ {"oryol.ru", 0},
+#line 2045 "effective_tld_names.gperf"
+ {"muenchen.museum", 0},
+#line 2354 "effective_tld_names.gperf"
+ {"oksnes.no", 0},
+#line 511 "effective_tld_names.gperf"
+ {"city.fukuoka.jp", 1},
+#line 37 "effective_tld_names.gperf"
+ {"ac.cn", 0},
+#line 3331 "effective_tld_names.gperf"
+ {"vaksdal.no", 0},
+#line 2516 "effective_tld_names.gperf"
+ {"ostroleka.pl", 0},
+#line 3490 "effective_tld_names.gperf"
+ {"xn--ciqpn.hk", 0},
+#line 3409 "effective_tld_names.gperf"
+ {"vrn.ru", 0},
+#line 2823 "effective_tld_names.gperf"
+ {"romsa.no", 0},
+#line 1662 "effective_tld_names.gperf"
+ {"kids.museum", 0},
+#line 2419 "effective_tld_names.gperf"
+ {"org.gg", 0},
+#line 711 "effective_tld_names.gperf"
+ {"contemporary.museum", 0},
+#line 3210 "effective_tld_names.gperf"
+ {"trader.aero", 0},
+#line 2540 "effective_tld_names.gperf"
+ {"palermo.it", 0},
+#line 3449 "effective_tld_names.gperf"
+ {"wodzislaw.pl", 0},
+#line 3569 "effective_tld_names.gperf"
+ {"xn--mk0axi.hk", 0},
+#line 3414 "effective_tld_names.gperf"
+ {"vyatka.ru", 0},
+#line 490 "effective_tld_names.gperf"
+ {"chicago.museum", 0},
+#line 2055 "effective_tld_names.gperf"
+ {"museum.mw", 0},
+#line 1123 "effective_tld_names.gperf"
+ {"gc.ca", 0},
+#line 2803 "effective_tld_names.gperf"
+ {"riodejaneiro.museum", 0},
+#line 2077 "effective_tld_names.gperf"
+ {"nakhodka.ru", 0},
+#line 2816 "effective_tld_names.gperf"
+ {"rochester.museum", 0},
+#line 2858 "effective_tld_names.gperf"
+ {"sakhalin.ru", 0},
+#line 3444 "effective_tld_names.gperf"
+ {"wildlife.museum", 0},
+#line 2897 "effective_tld_names.gperf"
+ {"sc.us", 0},
+#line 3235 "effective_tld_names.gperf"
+ {"trysil.no", 0},
+#line 3591 "effective_tld_names.gperf"
+ {"xn--porsgu-sta26f.no", 0},
+#line 2507 "effective_tld_names.gperf"
+ {"os.hordaland.no", 0},
+#line 811 "effective_tld_names.gperf"
+ {"dyroy.no", 0},
+#line 1314 "effective_tld_names.gperf"
+ {"greta.fr", 0},
+#line 2085 "effective_tld_names.gperf"
+ {"name.mk", 0},
+#line 2966 "effective_tld_names.gperf"
+ {"silk.museum", 0},
+#line 1463 "effective_tld_names.gperf"
+ {"icnet.uk", 1},
+#line 3660 "effective_tld_names.gperf"
+ {"xn--vrggt-xqad.no", 0},
+#line 2657 "effective_tld_names.gperf"
+ {"pref.kochi.jp", 1},
+#line 3653 "effective_tld_names.gperf"
+ {"xn--vestvgy-ixa6o.no", 0},
+#line 2922 "effective_tld_names.gperf"
+ {"scientist.aero", 0},
+#line 3165 "effective_tld_names.gperf"
+ {"tj", 0},
+#line 3215 "effective_tld_names.gperf"
+ {"tranoy.no", 0},
+#line 3556 "effective_tld_names.gperf"
+ {"xn--loabt-0qa.no", 0},
+#line 3502 "effective_tld_names.gperf"
+ {"xn--flor-jra.no", 0},
+#line 3124 "effective_tld_names.gperf"
+ {"t.bg", 0},
+#line 1750 "effective_tld_names.gperf"
+ {"laquila.it", 0},
+#line 36 "effective_tld_names.gperf"
+ {"ac.ci", 0},
+#line 173 "effective_tld_names.gperf"
+ {"archaeological.museum", 0},
+#line 2810 "effective_tld_names.gperf"
+ {"rnrt.tn", 0},
+#line 2645 "effective_tld_names.gperf"
+ {"pref.fukushima.jp", 1},
+#line 2664 "effective_tld_names.gperf"
+ {"pref.nagasaki.jp", 1},
+#line 773 "effective_tld_names.gperf"
+ {"de.us", 0},
+#line 3378 "effective_tld_names.gperf"
+ {"vic.au", 0},
+#line 712 "effective_tld_names.gperf"
+ {"contemporaryart.museum", 0},
+#line 2916 "effective_tld_names.gperf"
+ {"scienceandindustry.museum", 0},
+#line 284 "effective_tld_names.gperf"
+ {"baltimore.museum", 0},
+#line 301 "effective_tld_names.gperf"
+ {"bc.ca", 0},
+#line 3488 "effective_tld_names.gperf"
+ {"xn--brum-voa.no", 0},
+#line 2919 "effective_tld_names.gperf"
+ {"sciencehistory.museum", 0},
+#line 112 "effective_tld_names.gperf"
+ {"akita.jp", 2},
+#line 1772 "effective_tld_names.gperf"
+ {"leirvik.no", 0},
+#line 317 "effective_tld_names.gperf"
+ {"bergamo.it", 0},
+#line 3623 "effective_tld_names.gperf"
+ {"xn--smna-gra.no", 0},
+#line 762 "effective_tld_names.gperf"
+ {"daejeon.kr", 0},
+#line 3396 "effective_tld_names.gperf"
+ {"vladivostok.ru", 0},
+#line 2805 "effective_tld_names.gperf"
+ {"rissa.no", 0},
+#line 1781 "effective_tld_names.gperf"
+ {"lezajsk.pl", 0},
+#line 2766 "effective_tld_names.gperf"
+ {"rc.it", 0},
+#line 3516 "effective_tld_names.gperf"
+ {"xn--hcesuolo-7ya35b.no", 0},
+#line 2022 "effective_tld_names.gperf"
+ {"monza.it", 0},
+#line 789 "effective_tld_names.gperf"
+ {"divtasvuodna.no", 0},
+#line 516 "effective_tld_names.gperf"
+ {"city.kobe.jp", 1},
+#line 3553 "effective_tld_names.gperf"
+ {"xn--lhppi-xqa.no", 0},
+#line 2386 "effective_tld_names.gperf"
+ {"or.us", 0},
+#line 1219 "effective_tld_names.gperf"
+ {"gov.cm", 0},
+#line 3129 "effective_tld_names.gperf"
+ {"tananger.no", 0},
+#line 1950 "effective_tld_names.gperf"
+ {"mil.by", 0},
+#line 1649 "effective_tld_names.gperf"
+ {"kepno.pl", 0},
+#line 1823 "effective_tld_names.gperf"
+ {"ltd.co.im", 0},
+#line 225 "effective_tld_names.gperf"
+ {"asso.mc", 0},
+#line 2079 "effective_tld_names.gperf"
+ {"nalchik.ru", 0},
+#line 790 "effective_tld_names.gperf"
+ {"divttasvuotna.no", 0},
+#line 2388 "effective_tld_names.gperf"
+ {"oregontrail.museum", 0},
+#line 3544 "effective_tld_names.gperf"
+ {"xn--kvnangen-k0a.no", 0},
+#line 1805 "effective_tld_names.gperf"
+ {"localhistory.museum", 0},
+#line 2468 "effective_tld_names.gperf"
+ {"org.ph", 0},
+#line 804 "effective_tld_names.gperf"
+ {"dp.ua", 0},
+#line 2650 "effective_tld_names.gperf"
+ {"pref.hyogo.jp", 1},
+#line 779 "effective_tld_names.gperf"
+ {"denmark.museum", 0},
+#line 1789 "effective_tld_names.gperf"
+ {"lillehammer.no", 0},
+#line 3131 "effective_tld_names.gperf"
+ {"taranto.it", 0},
+#line 1047 "effective_tld_names.gperf"
+ {"flesberg.no", 0},
+#line 3387 "effective_tld_names.gperf"
+ {"vindafjord.no", 0},
+#line 2550 "effective_tld_names.gperf"
+ {"passenger-association.aero", 0},
+#line 2677 "effective_tld_names.gperf"
+ {"pref.tokushima.jp", 1},
+#line 3370 "effective_tld_names.gperf"
+ {"vf.no", 0},
+#line 2648 "effective_tld_names.gperf"
+ {"pref.hiroshima.jp", 1},
+#line 795 "effective_tld_names.gperf"
+ {"dn.ua", 0},
+#line 800 "effective_tld_names.gperf"
+ {"donetsk.ua", 0},
+#line 1890 "effective_tld_names.gperf"
+ {"mbone.pl", 0},
+#line 3169 "effective_tld_names.gperf"
+ {"tk", 0},
+#line 3312 "effective_tld_names.gperf"
+ {"utah.museum", 0},
+#line 257 "effective_tld_names.gperf"
+ {"avoues.fr", 0},
+#line 2638 "effective_tld_names.gperf"
+ {"pref.aichi.jp", 1},
+#line 2762 "effective_tld_names.gperf"
+ {"randaberg.no", 0},
+#line 3703 "effective_tld_names.gperf"
+ {"zhitomir.ua", 0},
+#line 3237 "effective_tld_names.gperf"
+ {"tsaritsyn.ru", 0},
+#line 1554 "effective_tld_names.gperf"
+ {"irkutsk.ru", 0},
+#line 2795 "effective_tld_names.gperf"
+ {"ri.us", 0},
+#line 1223 "effective_tld_names.gperf"
+ {"gov.cx", 0},
+#line 2341 "effective_tld_names.gperf"
+ {"od.ua", 0},
+#line 2757 "effective_tld_names.gperf"
+ {"railway.museum", 0},
+#line 2410 "effective_tld_names.gperf"
+ {"org.cn", 0},
+#line 496 "effective_tld_names.gperf"
+ {"chirurgiens-dentistes.fr", 0},
+#line 3428 "effective_tld_names.gperf"
+ {"washingtondc.museum", 0},
+#line 3079 "effective_tld_names.gperf"
+ {"store.nf", 0},
+#line 1783 "effective_tld_names.gperf"
+ {"lg.ua", 0},
+#line 3167 "effective_tld_names.gperf"
+ {"tjeldsund.no", 0},
+#line 468 "effective_tld_names.gperf"
+ {"cci.fr", 0},
+#line 1682 "effective_tld_names.gperf"
+ {"kolobrzeg.pl", 0},
+#line 3051 "effective_tld_names.gperf"
+ {"stalbans.museum", 0},
+#line 3133 "effective_tld_names.gperf"
+ {"tarnobrzeg.pl", 0},
+#line 1747 "effective_tld_names.gperf"
+ {"langevag.no", 0},
+#line 154 "effective_tld_names.gperf"
+ {"annefrank.museum", 0},
+#line 2915 "effective_tld_names.gperf"
+ {"scienceandhistory.museum", 0},
+#line 932 "effective_tld_names.gperf"
+ {"eidsberg.no", 0},
+#line 3121 "effective_tld_names.gperf"
+ {"szczytno.pl", 0},
+#line 2881 "effective_tld_names.gperf"
+ {"saotome.st", 0},
+#line 1961 "effective_tld_names.gperf"
+ {"mil.km", 0},
+#line 2577 "effective_tld_names.gperf"
+ {"pharmaciens.km", 0},
+#line 987 "effective_tld_names.gperf"
+ {"express.aero", 0},
+#line 2570 "effective_tld_names.gperf"
+ {"pesarourbino.it", 0},
+#line 2569 "effective_tld_names.gperf"
+ {"pesaro-urbino.it", 0},
+#line 3480 "effective_tld_names.gperf"
+ {"xn--bievt-0qa.no", 0},
+#line 2669 "effective_tld_names.gperf"
+ {"pref.okinawa.jp", 1},
+#line 3522 "effective_tld_names.gperf"
+ {"xn--hobl-ira.no", 0},
+#line 1713 "effective_tld_names.gperf"
+ {"kunstunddesign.museum", 0},
+#line 1461 "effective_tld_names.gperf"
+ {"ibaraki.jp", 2},
+#line 1813 "effective_tld_names.gperf"
+ {"lorenskog.no", 0},
+#line 2909 "effective_tld_names.gperf"
+ {"schokoladen.museum", 0},
+#line 3173 "effective_tld_names.gperf"
+ {"tm.hu", 0},
+#line 3668 "effective_tld_names.gperf"
+ {"xn--zf0avx.hk", 0},
+#line 215 "effective_tld_names.gperf"
+ {"assedic.fr", 0},
+#line 3258 "effective_tld_names.gperf"
+ {"tydal.no", 0},
+#line 3213 "effective_tld_names.gperf"
+ {"trana.no", 0},
+#line 608 "effective_tld_names.gperf"
+ {"com.bh", 0},
+#line 835 "effective_tld_names.gperf"
+ {"edu.bh", 0},
+#line 3106 "effective_tld_names.gperf"
+ {"svalbard.no", 0},
+#line 1210 "effective_tld_names.gperf"
+ {"gov.bh", 0},
+#line 420 "effective_tld_names.gperf"
+ {"burghof.museum", 0},
+#line 2759 "effective_tld_names.gperf"
+ {"rakkestad.no", 0},
+#line 3259 "effective_tld_names.gperf"
+ {"tynset.no", 0},
+#line 2772 "effective_tld_names.gperf"
+ {"rec.co", 0},
+#line 3252 "effective_tld_names.gperf"
+ {"tvedestrand.no", 0},
+#line 2504 "effective_tld_names.gperf"
+ {"orsta.no", 0},
+#line 2409 "effective_tld_names.gperf"
+ {"org.ci", 0},
+#line 1801 "effective_tld_names.gperf"
+ {"ln.cn", 0},
+#line 2988 "effective_tld_names.gperf"
+ {"skydiving.aero", 0},
+#line 2971 "effective_tld_names.gperf"
+ {"sk.ca", 0},
+#line 2152 "effective_tld_names.gperf"
+ {"net.bh", 0},
+#line 2349 "effective_tld_names.gperf"
+ {"oh.us", 0},
+#line 2758 "effective_tld_names.gperf"
+ {"raisa.no", 0},
+#line 735 "effective_tld_names.gperf"
+ {"cranbrook.museum", 0},
+#line 3020 "effective_tld_names.gperf"
+ {"sopot.pl", 0},
+#line 3688 "effective_tld_names.gperf"
+ {"yuzhno-sakhalinsk.ru", 0},
+#line 3640 "effective_tld_names.gperf"
+ {"xn--tnsberg-q1a.no", 0},
+#line 2307 "effective_tld_names.gperf"
+ {"novosibirsk.ru", 0},
+#line 2436 "effective_tld_names.gperf"
+ {"org.kg", 0},
+#line 2411 "effective_tld_names.gperf"
+ {"org.co", 0},
+#line 2440 "effective_tld_names.gperf"
+ {"org.ky", 0},
+#line 2523 "effective_tld_names.gperf"
+ {"oxford.museum", 0},
+#line 495 "effective_tld_names.gperf"
+ {"chiropractic.museum", 0},
+#line 2753 "effective_tld_names.gperf"
+ {"ragusa.it", 0},
+#line 2951 "effective_tld_names.gperf"
+ {"shiga.jp", 2},
+#line 2560 "effective_tld_names.gperf"
+ {"penza.ru", 0},
+#line 292 "effective_tld_names.gperf"
+ {"barreau.bj", 0},
+#line 2939 "effective_tld_names.gperf"
+ {"seoul.kr", 0},
+#line 1808 "effective_tld_names.gperf"
+ {"logistics.aero", 0},
+#line 1095 "effective_tld_names.gperf"
+ {"fukuoka.jp", 2},
+#line 3035 "effective_tld_names.gperf"
+ {"space.museum", 0},
+#line 2652 "effective_tld_names.gperf"
+ {"pref.ishikawa.jp", 1},
+#line 3472 "effective_tld_names.gperf"
+ {"xn--b-5ga.nordland.no", 0},
+#line 2441 "effective_tld_names.gperf"
+ {"org.kz", 0},
+#line 3384 "effective_tld_names.gperf"
+ {"viking.museum", 0},
+#line 2636 "effective_tld_names.gperf"
+ {"prd.km", 0},
+#line 193 "effective_tld_names.gperf"
+ {"arteducation.museum", 0},
+#line 3567 "effective_tld_names.gperf"
+ {"xn--mgberp4a5d4ar", 0},
+#line 113 "effective_tld_names.gperf"
+ {"aknoluokta.no", 0},
+#line 3394 "effective_tld_names.gperf"
+ {"vladikavkaz.ru", 0},
+#line 2839 "effective_tld_names.gperf"
+ {"rv.ua", 0},
+#line 3244 "effective_tld_names.gperf"
+ {"turin.it", 0},
+#line 3111 "effective_tld_names.gperf"
+ {"swidnica.pl", 0},
+#line 3226 "effective_tld_names.gperf"
+ {"trieste.it", 0},
+#line 2882 "effective_tld_names.gperf"
+ {"sapporo.jp", 2},
+#line 2517 "effective_tld_names.gperf"
+ {"ostrowiec.pl", 0},
+#line 3506 "effective_tld_names.gperf"
+ {"xn--ggaviika-8ya47h.no", 0},
+#line 796 "effective_tld_names.gperf"
+ {"dnepropetrovsk.ua", 0},
+#line 510 "effective_tld_names.gperf"
+ {"city.chiba.jp", 1},
+#line 821 "effective_tld_names.gperf"
+ {"ecn.br", 0},
+#line 2831 "effective_tld_names.gperf"
+ {"royrvik.no", 0},
+#line 180 "effective_tld_names.gperf"
+ {"arkhangelsk.ru", 0},
+#line 2518 "effective_tld_names.gperf"
+ {"ostrowwlkp.pl", 0},
+#line 3204 "effective_tld_names.gperf"
+ {"toyama.jp", 2},
+#line 3448 "effective_tld_names.gperf"
+ {"wloclawek.pl", 0},
+#line 3136 "effective_tld_names.gperf"
+ {"tas.gov.au", 0},
+#line 2599 "effective_tld_names.gperf"
+ {"plaza.museum", 0},
+#line 536 "effective_tld_names.gperf"
+ {"clock.museum", 0},
+#line 2974 "effective_tld_names.gperf"
+ {"skaun.no", 0},
+#line 1137 "effective_tld_names.gperf"
+ {"geometre-expert.fr", 0},
+#line 1837 "effective_tld_names.gperf"
+ {"lutsk.ua", 0},
+#line 318 "effective_tld_names.gperf"
+ {"bergbau.museum", 0},
+#line 3147 "effective_tld_names.gperf"
+ {"television.museum", 0},
+#line 3582 "effective_tld_names.gperf"
+ {"xn--nttery-byae.no", 0},
+#line 2977 "effective_tld_names.gperf"
+ {"ski.museum", 0},
+#line 1629 "effective_tld_names.gperf"
+ {"kamchatka.ru", 0},
+#line 2843 "effective_tld_names.gperf"
+ {"rygge.no", 0},
+#line 2779 "effective_tld_names.gperf"
+ {"reggioemilia.it", 0},
+#line 2777 "effective_tld_names.gperf"
+ {"reggio-emilia.it", 0},
+#line 3109 "effective_tld_names.gperf"
+ {"svizzera.museum", 0},
+#line 2660 "effective_tld_names.gperf"
+ {"pref.mie.jp", 1},
+#line 3385 "effective_tld_names.gperf"
+ {"vikna.no", 0},
+#line 2896 "effective_tld_names.gperf"
+ {"sc.ug", 0},
+#line 3343 "effective_tld_names.gperf"
+ {"vb.it", 0},
+#line 3243 "effective_tld_names.gperf"
+ {"turen.tn", 0},
+#line 2644 "effective_tld_names.gperf"
+ {"pref.fukuoka.jp", 1},
+#line 3366 "effective_tld_names.gperf"
+ {"vet.br", 0},
+#line 1749 "effective_tld_names.gperf"
+ {"lapy.pl", 0},
+#line 2477 "effective_tld_names.gperf"
+ {"org.ru", 0},
+#line 767 "effective_tld_names.gperf"
+ {"davvenjarga.no", 0},
+#line 3233 "effective_tld_names.gperf"
+ {"trust.museum", 0},
+#line 2796 "effective_tld_names.gperf"
+ {"rieti.it", 0},
+#line 3184 "effective_tld_names.gperf"
+ {"tn.us", 0},
+#line 3260 "effective_tld_names.gperf"
+ {"tysfjord.no", 0},
+#line 2508 "effective_tld_names.gperf"
+ {"osaka.jp", 2},
+#line 2885 "effective_tld_names.gperf"
+ {"saskatchewan.museum", 0},
+#line 3356 "effective_tld_names.gperf"
+ {"vercelli.it", 0},
+#line 3139 "effective_tld_names.gperf"
+ {"tc", 0},
+#line 2245 "effective_tld_names.gperf"
+ {"nf.ca", 0},
+#line 2457 "effective_tld_names.gperf"
+ {"org.mu", 0},
+#line 2739 "effective_tld_names.gperf"
+ {"qc.ca", 0},
+#line 3149 "effective_tld_names.gperf"
+ {"terni.it", 0},
+#line 2809 "effective_tld_names.gperf"
+ {"rnd.ru", 0},
+#line 3143 "effective_tld_names.gperf"
+ {"te.ua", 0},
+#line 1046 "effective_tld_names.gperf"
+ {"flekkefjord.no", 0},
+#line 2982 "effective_tld_names.gperf"
+ {"skjak.no", 0},
+#line 793 "effective_tld_names.gperf"
+ {"dlugoleka.pl", 0},
+#line 3211 "effective_tld_names.gperf"
+ {"trading.aero", 0},
+#line 3114 "effective_tld_names.gperf"
+ {"sx.cn", 0},
+#line 199 "effective_tld_names.gperf"
+ {"artsandcrafts.museum", 0},
+#line 3530 "effective_tld_names.gperf"
+ {"xn--jlster-bya.no", 0},
+#line 3228 "effective_tld_names.gperf"
+ {"trogstad.no", 0},
+#line 2385 "effective_tld_names.gperf"
+ {"or.ug", 0},
+#line 2420 "effective_tld_names.gperf"
+ {"org.gh", 0},
+#line 2519 "effective_tld_names.gperf"
+ {"otago.museum", 0},
+#line 3175 "effective_tld_names.gperf"
+ {"tm.mc", 0},
+#line 93 "effective_tld_names.gperf"
+ {"agriculture.museum", 0},
+#line 1771 "effective_tld_names.gperf"
+ {"leirfjord.no", 0},
+#line 1910 "effective_tld_names.gperf"
+ {"medecin.fr", 0},
+#line 484 "effective_tld_names.gperf"
+ {"chelyabinsk.ru", 0},
+#line 3445 "effective_tld_names.gperf"
+ {"williamsburg.museum", 0},
+#line 589 "effective_tld_names.gperf"
+ {"coastaldefence.museum", 0},
+#line 2114 "effective_tld_names.gperf"
+ {"natuurwetenschappen.museum", 0},
+#line 1743 "effective_tld_names.gperf"
+ {"lakas.hu", 0},
+#line 1759 "effective_tld_names.gperf"
+ {"lb", 0},
+#line 2894 "effective_tld_names.gperf"
+ {"sc.cn", 0},
+#line 3566 "effective_tld_names.gperf"
+ {"xn--mgbaam7a8h", 0},
+#line 3332 "effective_tld_names.gperf"
+ {"valer.hedmark.no", 0},
+#line 169 "effective_tld_names.gperf"
+ {"ar.com", 0},
+#line 465 "effective_tld_names.gperf"
+ {"cbg.ru", 0},
+#line 82 "effective_tld_names.gperf"
+ {"aeroclub.aero", 0},
+#line 2273 "effective_tld_names.gperf"
+ {"no.com", 0},
+#line 103 "effective_tld_names.gperf"
+ {"air-surveillance.aero", 0},
+#line 3157 "effective_tld_names.gperf"
+ {"tgory.pl", 0},
+#line 3579 "effective_tld_names.gperf"
+ {"xn--mxtq1m.hk", 0},
+#line 2522 "effective_tld_names.gperf"
+ {"ovre-eiker.no", 0},
+#line 2824 "effective_tld_names.gperf"
+ {"romskog.no", 0},
+#line 542 "effective_tld_names.gperf"
+ {"cn.com", 0},
+#line 272 "effective_tld_names.gperf"
+ {"bahccavuotna.no", 0},
+#line 978 "effective_tld_names.gperf"
+ {"eu.com", 0},
+#line 1449 "effective_tld_names.gperf"
+ {"hu.com", 0},
+#line 1817 "effective_tld_names.gperf"
+ {"lowicz.pl", 0},
+#line 3619 "effective_tld_names.gperf"
+ {"xn--sknland-fxa.no", 0},
+#line 1763 "effective_tld_names.gperf"
+ {"leangaviika.no", 0},
+#line 2428 "effective_tld_names.gperf"
+ {"org.hu", 0},
+#line 2907 "effective_tld_names.gperf"
+ {"schlesisches.museum", 0},
+#line 1792 "effective_tld_names.gperf"
+ {"lincoln.museum", 0},
+#line 2501 "effective_tld_names.gperf"
+ {"orkdal.no", 0},
+#line 2789 "effective_tld_names.gperf"
+ {"research.aero", 0},
+#line 3367 "effective_tld_names.gperf"
+ {"veterinaire.fr", 0},
+#line 3692 "effective_tld_names.gperf"
+ {"za.com", 0},
+#line 3198 "effective_tld_names.gperf"
+ {"torsken.no", 0},
+#line 2376 "effective_tld_names.gperf"
+ {"or.cr", 0},
+#line 3199 "effective_tld_names.gperf"
+ {"tottori.jp", 2},
+#line 3666 "effective_tld_names.gperf"
+ {"xn--ystre-slidre-ujb.no", 0},
+#line 3543 "effective_tld_names.gperf"
+ {"xn--kvitsy-fya.no", 0},
+#line 1410 "effective_tld_names.gperf"
+ {"historicalsociety.museum", 0},
+#line 770 "effective_tld_names.gperf"
+ {"ddr.museum", 0},
+#line 2369 "effective_tld_names.gperf"
+ {"opoczno.pl", 0},
+#line 2500 "effective_tld_names.gperf"
+ {"orkanger.no", 0},
+#line 391 "effective_tld_names.gperf"
+ {"br.com", 0},
+#line 1841 "effective_tld_names.gperf"
+ {"lviv.ua", 0},
+#line 3163 "effective_tld_names.gperf"
+ {"tingvoll.no", 0},
+#line 260 "effective_tld_names.gperf"
+ {"axis.museum", 0},
+#line 3654 "effective_tld_names.gperf"
+ {"xn--vg-yiab.no", 0},
+#line 1693 "effective_tld_names.gperf"
+ {"kopervik.no", 0},
+#line 1775 "effective_tld_names.gperf"
+ {"lel.br", 0},
+#line 2836 "effective_tld_names.gperf"
+ {"rubtsovsk.ru", 0},
+#line 2346 "effective_tld_names.gperf"
+ {"of.no", 0},
+#line 3168 "effective_tld_names.gperf"
+ {"tjome.no", 0},
+#line 3547 "effective_tld_names.gperf"
+ {"xn--langevg-jxa.no", 0},
+#line 2364 "effective_tld_names.gperf"
+ {"on.ca", 0},
+#line 2790 "effective_tld_names.gperf"
+ {"research.museum", 0},
+#line 3586 "effective_tld_names.gperf"
+ {"xn--od0aq3b.hk", 0},
+#line 985 "effective_tld_names.gperf"
+ {"exhibition.museum", 0},
+#line 3232 "effective_tld_names.gperf"
+ {"trondheim.no", 0},
+#line 2506 "effective_tld_names.gperf"
+ {"os.hedmark.no", 0},
+#line 2345 "effective_tld_names.gperf"
+ {"of.by", 0},
+#line 2646 "effective_tld_names.gperf"
+ {"pref.gifu.jp", 1},
+#line 2802 "effective_tld_names.gperf"
+ {"ringsaker.no", 0},
+#line 2361 "effective_tld_names.gperf"
+ {"omaha.museum", 0},
+#line 2375 "effective_tld_names.gperf"
+ {"or.ci", 0},
+#line 81 "effective_tld_names.gperf"
+ {"aerobatic.aero", 0},
+#line 2876 "effective_tld_names.gperf"
+ {"sanfrancisco.museum", 0},
+#line 2237 "effective_tld_names.gperf"
+ {"newhampshire.museum", 0},
+#line 2510 "effective_tld_names.gperf"
+ {"oskol.ru", 0},
+#line 3192 "effective_tld_names.gperf"
+ {"tom.ru", 0},
+#line 2827 "effective_tld_names.gperf"
+ {"rotorcraft.aero", 0},
+#line 2533 "effective_tld_names.gperf"
+ {"pacific.museum", 0},
+#line 2791 "effective_tld_names.gperf"
+ {"resistance.museum", 0},
+#line 2801 "effective_tld_names.gperf"
+ {"ringerike.no", 0},
+#line 2543 "effective_tld_names.gperf"
+ {"parachuting.aero", 0},
+#line 3297 "effective_tld_names.gperf"
+ {"us.com", 0},
+#line 2968 "effective_tld_names.gperf"
+ {"siracusa.it", 0},
+#line 2347 "effective_tld_names.gperf"
+ {"off.ai", 0},
+#line 499 "effective_tld_names.gperf"
+ {"christiansburg.museum", 0},
+#line 2917 "effective_tld_names.gperf"
+ {"sciencecenter.museum", 0},
+#line 2918 "effective_tld_names.gperf"
+ {"sciencecenters.museum", 0},
+#line 2413 "effective_tld_names.gperf"
+ {"org.dm", 0},
+#line 1390 "effective_tld_names.gperf"
+ {"hb.cn", 0},
+#line 1832 "effective_tld_names.gperf"
+ {"lukow.pl", 0},
+#line 2943 "effective_tld_names.gperf"
+ {"sex.hu", 0},
+#line 2058 "effective_tld_names.gperf"
+ {"museumcenter.museum", 0},
+#line 3491 "effective_tld_names.gperf"
+ {"xn--comunicaes-v6a2o.museum", 0},
+#line 2985 "effective_tld_names.gperf"
+ {"skoczow.pl", 0},
+#line 30 "effective_tld_names.gperf"
+ {"ab.ca", 0},
+#line 2351 "effective_tld_names.gperf"
+ {"ok.us", 0},
+#line 106 "effective_tld_names.gperf"
+ {"aircraft.aero", 0},
+#line 2120 "effective_tld_names.gperf"
+ {"nb.ca", 0},
+#line 3191 "effective_tld_names.gperf"
+ {"tolga.no", 0},
+#line 2975 "effective_tld_names.gperf"
+ {"skedsmo.no", 0},
+#line 63 "effective_tld_names.gperf"
+ {"academy.museum", 0},
+#line 2641 "effective_tld_names.gperf"
+ {"pref.chiba.jp", 1},
+#line 2676 "effective_tld_names.gperf"
+ {"pref.tochigi.jp", 1},
+#line 2821 "effective_tld_names.gperf"
+ {"roma.museum", 0},
+#line 2239 "effective_tld_names.gperf"
+ {"newmexico.museum", 0},
+#line 3245 "effective_tld_names.gperf"
+ {"turystyka.pl", 0},
+#line 3318 "effective_tld_names.gperf"
+ {"uy.com", 0},
+#line 3134 "effective_tld_names.gperf"
+ {"tas.au", 0},
+#line 3625 "effective_tld_names.gperf"
+ {"xn--sndre-land-0cb.no", 0},
+#line 3152 "effective_tld_names.gperf"
+ {"test.tj", 0},
+#line 1094 "effective_tld_names.gperf"
+ {"fukui.jp", 2},
+#line 2460 "effective_tld_names.gperf"
+ {"org.mx", 0},
+#line 1748 "effective_tld_names.gperf"
+ {"lans.museum", 0},
+#line 3519 "effective_tld_names.gperf"
+ {"xn--hgebostad-g3a.no", 0},
+#line 152 "effective_tld_names.gperf"
+ {"andebu.no", 0},
+#line 2878 "effective_tld_names.gperf"
+ {"santabarbara.museum", 0},
+#line 488 "effective_tld_names.gperf"
+ {"chesapeakebay.museum", 0},
+#line 2429 "effective_tld_names.gperf"
+ {"org.im", 0},
+#line 1745 "effective_tld_names.gperf"
+ {"lancashire.museum", 0},
+#line 3138 "effective_tld_names.gperf"
+ {"taxi.aero", 0},
+#line 780 "effective_tld_names.gperf"
+ {"dep.no", 0},
+#line 1313 "effective_tld_names.gperf"
+ {"graz.museum", 0},
+#line 3060 "effective_tld_names.gperf"
+ {"stateofdelaware.museum", 0},
+#line 610 "effective_tld_names.gperf"
+ {"com.bm", 0},
+#line 1838 "effective_tld_names.gperf"
+ {"luxembourg.museum", 0},
+#line 837 "effective_tld_names.gperf"
+ {"edu.bm", 0},
+#line 967 "effective_tld_names.gperf"
+ {"esp.br", 0},
+#line 2353 "effective_tld_names.gperf"
+ {"okinawa.jp", 2},
+#line 3155 "effective_tld_names.gperf"
+ {"tf", 0},
+#line 1211 "effective_tld_names.gperf"
+ {"gov.bm", 0},
+#line 1696 "effective_tld_names.gperf"
+ {"kr.com", 0},
+#line 774 "effective_tld_names.gperf"
+ {"deatnu.no", 0},
+#line 2576 "effective_tld_names.gperf"
+ {"pharmacien.fr", 0},
+#line 2754 "effective_tld_names.gperf"
+ {"rahkkeravju.no", 0},
+#line 1768 "effective_tld_names.gperf"
+ {"lecco.it", 0},
+#line 2771 "effective_tld_names.gperf"
+ {"rec.br", 0},
+#line 2786 "effective_tld_names.gperf"
+ {"repbody.aero", 0},
+#line 3611 "effective_tld_names.gperf"
+ {"xn--sandnessjen-ogb.no", 0},
+#line 2869 "effective_tld_names.gperf"
+ {"sande.vestfold.no", 0},
+#line 3479 "effective_tld_names.gperf"
+ {"xn--bidr-5nac.no", 0},
+#line 1767 "effective_tld_names.gperf"
+ {"lecce.it", 0},
+#line 2153 "effective_tld_names.gperf"
+ {"net.bm", 0},
+#line 1435 "effective_tld_names.gperf"
+ {"homebuilt.aero", 0},
+#line 722 "effective_tld_names.gperf"
+ {"copenhagen.museum", 0},
+#line 2841 "effective_tld_names.gperf"
+ {"ryazan.ru", 0},
+#line 3679 "effective_tld_names.gperf"
+ {"yekaterinburg.ru", 0},
+#line 3132 "effective_tld_names.gperf"
+ {"targi.pl", 0},
+#line 3534 "effective_tld_names.gperf"
+ {"xn--klbu-woa.no", 0},
+#line 2344 "effective_tld_names.gperf"
+ {"odo.br", 0},
+#line 3395 "effective_tld_names.gperf"
+ {"vladimir.ru", 0},
+#line 3257 "effective_tld_names.gperf"
+ {"tychy.pl", 0},
+#line 3511 "effective_tld_names.gperf"
+ {"xn--gmq050i.hk", 0},
+#line 2405 "effective_tld_names.gperf"
+ {"org.br", 0},
+#line 1984 "effective_tld_names.gperf"
+ {"mincom.tn", 0},
+#line 2406 "effective_tld_names.gperf"
+ {"org.bs", 0},
+#line 2950 "effective_tld_names.gperf"
+ {"sherbrooke.museum", 0},
+#line 2400 "effective_tld_names.gperf"
+ {"org.bb", 0},
+#line 2110 "effective_tld_names.gperf"
+ {"naturalsciences.museum", 0},
+#line 2407 "effective_tld_names.gperf"
+ {"org.bw", 0},
+#line 593 "effective_tld_names.gperf"
+ {"colonialwilliamsburg.museum", 0},
+#line 1889 "effective_tld_names.gperf"
+ {"mb.ca", 0},
+#line 3112 "effective_tld_names.gperf"
+ {"swiebodzin.pl", 0},
+#line 1829 "effective_tld_names.gperf"
+ {"lucca.it", 0},
+#line 3193 "effective_tld_names.gperf"
+ {"tomsk.ru", 0},
+#line 764 "effective_tld_names.gperf"
+ {"dali.museum", 0},
+#line 3496 "effective_tld_names.gperf"
+ {"xn--drbak-wua.no", 0},
+#line 2682 "effective_tld_names.gperf"
+ {"pref.yamaguchi.jp", 1},
+#line 1774 "effective_tld_names.gperf"
+ {"leksvik.no", 0},
+#line 1810 "effective_tld_names.gperf"
+ {"lomza.pl", 0},
+#line 3144 "effective_tld_names.gperf"
+ {"technology.museum", 0},
+#line 3473 "effective_tld_names.gperf"
+ {"xn--b-5ga.telemark.no", 0},
+#line 3333 "effective_tld_names.gperf"
+ {"valer.ostfold.no", 0},
+#line 1385 "effective_tld_names.gperf"
+ {"harvestcelebration.museum", 0},
+#line 1812 "effective_tld_names.gperf"
+ {"loppa.no", 0},
+#line 2399 "effective_tld_names.gperf"
+ {"org.ba", 0},
+#line 256 "effective_tld_names.gperf"
+ {"avocat.fr", 0},
+#line 1780 "effective_tld_names.gperf"
+ {"lewismiller.museum", 0},
+#line 3468 "effective_tld_names.gperf"
+ {"xn--aroport-bya.ci", 0},
+#line 2402 "effective_tld_names.gperf"
+ {"org.bi", 0},
+#line 3188 "effective_tld_names.gperf"
+ {"tokke.no", 0},
+#line 3487 "effective_tld_names.gperf"
+ {"xn--brnnysund-m8ac.no", 0},
+#line 2059 "effective_tld_names.gperf"
+ {"museumvereniging.museum", 0},
+#line 769 "effective_tld_names.gperf"
+ {"dc.us", 0},
+#line 2661 "effective_tld_names.gperf"
+ {"pref.miyagi.jp", 1},
+#line 3242 "effective_tld_names.gperf"
+ {"turek.pl", 0},
+#line 2362 "effective_tld_names.gperf"
+ {"omasvuotna.no", 0},
+#line 761 "effective_tld_names.gperf"
+ {"daegu.kr", 0},
+#line 2404 "effective_tld_names.gperf"
+ {"org.bo", 0},
+#line 2113 "effective_tld_names.gperf"
+ {"naturhistorisches.museum", 0},
+#line 3636 "effective_tld_names.gperf"
+ {"xn--stjrdalshalsen-sqb.no", 0},
+#line 3674 "effective_tld_names.gperf"
+ {"yamaguchi.jp", 2},
+#line 2857 "effective_tld_names.gperf"
+ {"saitama.jp", 2},
+#line 2928 "effective_tld_names.gperf"
+ {"se.com", 0},
+#line 3637 "effective_tld_names.gperf"
+ {"xn--stre-toten-zcb.no", 0},
+#line 788 "effective_tld_names.gperf"
+ {"discovery.museum", 0},
+#line 2623 "effective_tld_names.gperf"
+ {"posts-and-telecommunications.museum", 0},
+#line 2849 "effective_tld_names.gperf"
+ {"sa.com", 0},
+#line 2765 "effective_tld_names.gperf"
+ {"rawa-maz.pl", 0},
+#line 2649 "effective_tld_names.gperf"
+ {"pref.hokkaido.jp", 1},
+#line 2908 "effective_tld_names.gperf"
+ {"schoenbrunn.museum", 0},
+#line 3162 "effective_tld_names.gperf"
+ {"timekeeping.museum", 0},
+#line 1566 "effective_tld_names.gperf"
+ {"ivano-frankivsk.ua", 0},
+#line 2115 "effective_tld_names.gperf"
+ {"naumburg.museum", 0},
+#line 766 "effective_tld_names.gperf"
+ {"database.museum", 0},
+#line 3475 "effective_tld_names.gperf"
+ {"xn--bearalvhki-y4a.no", 0},
+#line 2359 "effective_tld_names.gperf"
+ {"olsztyn.pl", 0},
+#line 2371 "effective_tld_names.gperf"
+ {"oppdal.no", 0},
+#line 2711 "effective_tld_names.gperf"
+ {"prochowice.pl", 0},
+#line 1082 "effective_tld_names.gperf"
+ {"freiburg.museum", 0},
+#line 1685 "effective_tld_names.gperf"
+ {"kommunalforbund.se", 0},
+#line 1592 "effective_tld_names.gperf"
+ {"jfk.museum", 0},
+#line 1360 "effective_tld_names.gperf"
+ {"gyeongbuk.kr", 0},
+#line 3632 "effective_tld_names.gperf"
+ {"xn--srfold-bya.no", 0},
+#line 3315 "effective_tld_names.gperf"
+ {"uvic.museum", 0},
+#line 3512 "effective_tld_names.gperf"
+ {"xn--gmqw5a.hk", 0},
+#line 3631 "effective_tld_names.gperf"
+ {"xn--sr-varanger-ggb.no", 0},
+#line 3221 "effective_tld_names.gperf"
+ {"trd.br", 0},
+#line 3361 "effective_tld_names.gperf"
+ {"vestby.no", 0},
+#line 1096 "effective_tld_names.gperf"
+ {"fukushima.jp", 2},
+#line 1450 "effective_tld_names.gperf"
+ {"huissier-justice.fr", 0},
+#line 451 "effective_tld_names.gperf"
+ {"capebreton.museum", 0},
+#line 400 "effective_tld_names.gperf"
+ {"british-library.uk", 1},
+#line 3130 "effective_tld_names.gperf"
+ {"tank.museum", 0},
+#line 3256 "effective_tld_names.gperf"
+ {"tx.us", 0},
+#line 3255 "effective_tld_names.gperf"
+ {"tw.cn", 0},
+#line 2135 "effective_tld_names.gperf"
+ {"nes.buskerud.no", 0},
+#line 3609 "effective_tld_names.gperf"
+ {"xn--ryrvik-bya.no", 0},
+#line 3073 "effective_tld_names.gperf"
+ {"stockholm.museum", 0},
+#line 3222 "effective_tld_names.gperf"
+ {"tree.museum", 0},
+#line 2340 "effective_tld_names.gperf"
+ {"oceanographique.museum", 0},
+#line 1796 "effective_tld_names.gperf"
+ {"lipetsk.ru", 0},
+#line 3486 "effective_tld_names.gperf"
+ {"xn--brnny-wuac.no", 0},
+#line 2520 "effective_tld_names.gperf"
+ {"other.nf", 0},
+#line 3241 "effective_tld_names.gperf"
+ {"tur.br", 0},
+#line 2408 "effective_tld_names.gperf"
+ {"org.bz", 0},
+#line 2412 "effective_tld_names.gperf"
+ {"org.cu", 0},
+#line 2662 "effective_tld_names.gperf"
+ {"pref.miyazaki.jp", 1},
+#line 3187 "effective_tld_names.gperf"
+ {"tochigi.jp", 2},
+#line 3277 "effective_tld_names.gperf"
+ {"uk.com", 0},
+#line 2357 "effective_tld_names.gperf"
+ {"olecko.pl", 0},
+#line 3154 "effective_tld_names.gperf"
+ {"textile.museum", 0},
+#line 2372 "effective_tld_names.gperf"
+ {"oppegard.no", 0},
+#line 2438 "effective_tld_names.gperf"
+ {"org.km", 0},
+#line 3153 "effective_tld_names.gperf"
+ {"texas.museum", 0},
+#line 3160 "effective_tld_names.gperf"
+ {"time.museum", 0},
+#line 781 "effective_tld_names.gperf"
+ {"depot.museum", 0},
+#line 3550 "effective_tld_names.gperf"
+ {"xn--leagaviika-52b.no", 0},
+#line 2651 "effective_tld_names.gperf"
+ {"pref.ibaraki.jp", 1},
+#line 3166 "effective_tld_names.gperf"
+ {"tj.cn", 0},
+#line 2358 "effective_tld_names.gperf"
+ {"olkusz.pl", 0},
+#line 2976 "effective_tld_names.gperf"
+ {"skedsmokorset.no", 0},
+#line 1397 "effective_tld_names.gperf"
+ {"hembygdsforbund.museum", 0},
+#line 478 "effective_tld_names.gperf"
+ {"chambagri.fr", 0},
+#line 2103 "effective_tld_names.gperf"
+ {"national-library-scotland.uk", 1},
+#line 3190 "effective_tld_names.gperf"
+ {"tokyo.jp", 2},
+#line 3483 "effective_tld_names.gperf"
+ {"xn--blt-elab.no", 0},
+#line 1102 "effective_tld_names.gperf"
+ {"fylkesbibl.no", 0},
+#line 2953 "effective_tld_names.gperf"
+ {"shizuoka.jp", 2},
+#line 3203 "effective_tld_names.gperf"
+ {"town.museum", 0},
+#line 3238 "effective_tld_names.gperf"
+ {"tsk.ru", 0},
+#line 3663 "effective_tld_names.gperf"
+ {"xn--wgbh1c", 0},
+#line 2368 "effective_tld_names.gperf"
+ {"operaunite.com", 0},
+#line 402 "effective_tld_names.gperf"
+ {"britishcolumbia.museum", 0},
+#line 3658 "effective_tld_names.gperf"
+ {"xn--vler-qoa.xn--stfold-9xa.no", 0},
+#line 3484 "effective_tld_names.gperf"
+ {"xn--bmlo-gra.no", 0},
+#line 810 "effective_tld_names.gperf"
+ {"durham.museum", 0},
+#line 3200 "effective_tld_names.gperf"
+ {"touch.museum", 0},
+#line 3113 "effective_tld_names.gperf"
+ {"swinoujscie.pl", 0},
+#line 3214 "effective_tld_names.gperf"
+ {"tranby.no", 0},
+#line 3537 "effective_tld_names.gperf"
+ {"xn--kranghke-b0a.no", 0},
+#line 3140 "effective_tld_names.gperf"
+ {"tcm.museum", 0},
+#line 3195 "effective_tld_names.gperf"
+ {"topology.museum", 0},
+#line 3146 "effective_tld_names.gperf"
+ {"telekommunikation.museum", 0},
+#line 819 "effective_tld_names.gperf"
+ {"ebiz.tw", 0},
+#line 3542 "effective_tld_names.gperf"
+ {"xn--kvfjord-nxa.no", 0},
+#line 2401 "effective_tld_names.gperf"
+ {"org.bh", 0},
+#line 3564 "effective_tld_names.gperf"
+ {"xn--mgba3a4f16a.ir", 0},
+#line 3565 "effective_tld_names.gperf"
+ {"xn--mgba3a4fra.ir", 0},
+#line 2780 "effective_tld_names.gperf"
+ {"reklam.hu", 0},
+#line 1415 "effective_tld_names.gperf"
+ {"historyofscience.museum", 0},
+#line 3518 "effective_tld_names.gperf"
+ {"xn--hery-ira.xn--mre-og-romsdal-qqb.no", 0},
+#line 2740 "effective_tld_names.gperf"
+ {"qc.com", 0},
+#line 3476 "effective_tld_names.gperf"
+ {"xn--berlevg-jxa.no", 0},
+#line 3493 "effective_tld_names.gperf"
+ {"xn--czrw28b.tw", 0},
+#line 3194 "effective_tld_names.gperf"
+ {"tonsberg.no", 0},
+#line 1583 "effective_tld_names.gperf"
+ {"jeonbuk.kr", 0},
+#line 3577 "effective_tld_names.gperf"
+ {"xn--mtta-vrjjat-k7af.no", 0},
+#line 3477 "effective_tld_names.gperf"
+ {"xn--bhcavuotna-s4a.no", 0},
+#line 772 "effective_tld_names.gperf"
+ {"de.com", 0},
+#line 3489 "effective_tld_names.gperf"
+ {"xn--btsfjord-9za.no", 0},
+#line 2835 "effective_tld_names.gperf"
+ {"ru.com", 0},
+#line 3481 "effective_tld_names.gperf"
+ {"xn--bjarky-fya.no", 0},
+#line 2352 "effective_tld_names.gperf"
+ {"okayama.jp", 2},
+#line 3474 "effective_tld_names.gperf"
+ {"xn--bdddj-mrabd.no", 0},
+#line 2339 "effective_tld_names.gperf"
+ {"oceanographic.museum", 0},
+#line 3078 "effective_tld_names.gperf"
+ {"store.bb", 0},
+#line 1795 "effective_tld_names.gperf"
+ {"linz.museum", 0},
+#line 3083 "effective_tld_names.gperf"
+ {"stpetersburg.museum", 0},
+#line 3429 "effective_tld_names.gperf"
+ {"watch-and-clock.museum", 0},
+#line 3466 "effective_tld_names.gperf"
+ {"xn--9dbhblg6di.museum", 0},
+#line 1121 "effective_tld_names.gperf"
+ {"gb.com", 0},
+#line 110 "effective_tld_names.gperf"
+ {"airtraffic.aero", 0},
+#line 3430 "effective_tld_names.gperf"
+ {"watchandclock.museum", 0},
+#line 2913 "effective_tld_names.gperf"
+ {"science-fiction.museum", 0},
+#line 3520 "effective_tld_names.gperf"
+ {"xn--hmmrfeasta-s4ac.no", 0},
+#line 2403 "effective_tld_names.gperf"
+ {"org.bm", 0},
+#line 3603 "effective_tld_names.gperf"
+ {"xn--rmskog-bya.no", 0},
+#line 3597 "effective_tld_names.gperf"
+ {"xn--rhkkervju-01af.no", 0},
+#line 2870 "effective_tld_names.gperf"
+ {"sande.xn--mre-og-romsdal-qqb.no", 0},
+#line 2389 "effective_tld_names.gperf"
+ {"orenburg.ru", 0},
+#line 3181 "effective_tld_names.gperf"
+ {"tmp.br", 0},
+#line 104 "effective_tld_names.gperf"
+ {"air-traffic-control.aero", 0},
+#line 3470 "effective_tld_names.gperf"
+ {"xn--aurskog-hland-jnb.no", 0},
+#line 3189 "effective_tld_names.gperf"
+ {"tokushima.jp", 2},
+#line 2778 "effective_tld_names.gperf"
+ {"reggiocalabria.it", 0},
+#line 2776 "effective_tld_names.gperf"
+ {"reggio-calabria.it", 0},
+#line 3540 "effective_tld_names.gperf"
+ {"xn--krjohka-hwab49j.no", 0},
+#line 3492 "effective_tld_names.gperf"
+ {"xn--correios-e-telecomunicaes-ghc29a.museum", 0},
+#line 2865 "effective_tld_names.gperf"
+ {"salzburg.museum", 0},
+#line 3580 "effective_tld_names.gperf"
+ {"xn--nmesjevuemie-tcba.no", 0},
+#line 3478 "effective_tld_names.gperf"
+ {"xn--bhccavuotna-k7a.no", 0},
+#line 986 "effective_tld_names.gperf"
+ {"experts-comptables.fr", 0}
};
static const short lookup[] =
{
- -1, -1, -1, 0, 1, -1, 2, 3,
- -1, -1, -1, -1, 4, -1, -1, 5,
- 6, 7, 8, -1, -1, -1, -1, -1,
- 9, -1, -1, 10, 11, 12, 13, 14,
- 15, 16, 17, 18, -1, -1, -1, 19,
- 20, 21, 22, 23, 24, 25, -1, 26,
- -1, 27, 28, -1, -1, 29, -1, 30,
- -1, 31, 32, 33, 34, 35, -1, -1,
- 36, -1, 37, 38, 39, 40, -1, 41,
- -1, 42, 43, 44, -1, 45, 46, 47,
- 48, 49, 50, -1, -1, -1, -1, -1,
- 51, -1, -1, 52, 53, 54, -1, -1,
- 55, 56, 57, 58, 59, 60, 61, -1,
- 62, 63, -1, 64, -1, -1, -1, -1,
- 65, -1, -1, 66, -1, 67, -1, -1,
- 68, -1, 69, 70, -1, -1, -1, 71,
- 72, -1, 73, 74, -1, 75, -1, 76,
- 77, -1, -1, 78, -1, 79, 80, -1,
- -1, -1, -1, 81, 82, 83, 84, -1,
- -1, 85, 86, -1, 87, 88, 89, 90,
- -1, 91, 92, -1, -1, 93, -1, -1,
- -1, 94, -1, -1, -1, 95, 96, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 97, -1, -1, 98, 99, -1, -1, 100,
- 101, 102, -1, -1, 103, -1, -1, -1,
- -1, -1, -1, 104, -1, -1, -1, 105,
- -1, -1, 106, -1, -1, 107, 108, -1,
- -1, -1, -1, -1, 109, -1, 110, -1,
- 111, -1, 112, -1, -1, -1, -1, 113,
- 114, -1, 115, -1, -1, -1, -1, -1,
- -1, -1, 116, -1, -1, 117, -1, 118,
- 119, -1, -1, 120, -1, 121, -1, -1,
- 122, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 123, -1, -1, -1, -1,
- 124, -1, 125, -1, -1, -1, -1, -1,
- 126, -1, -1, -1, -1, 127, -1, -1,
- 128, -1, 129, -1, -1, -1, 130, -1,
+ -1, -1, 0, 1, 2, 3, 4, -1,
+ 5, 6, 7, 8, 9, 10, 11, 12,
+ -1, -1, -1, -1, -1, 13, -1, -1,
+ 14, 15, 16, 17, -1, 18, 19, -1,
+ 20, 21, -1, -1, -1, 22, 23, 24,
+ -1, 25, -1, 26, 27, 28, -1, 29,
+ 30, 31, 32, -1, -1, 33, 34, 35,
+ 36, 37, -1, 38, -1, 39, 40, -1,
+ 41, 42, -1, -1, 43, 44, -1, -1,
+ -1, 45, -1, -1, -1, -1, -1, -1,
+ -1, 46, 47, -1, 48, -1, -1, 49,
+ -1, -1, 50, -1, 51, -1, 52, 53,
+ 54, 55, -1, -1, -1, -1, 56, -1,
+ -1, 57, 58, -1, -1, 59, 60, 61,
+ -1, 62, 63, 64, -1, -1, -1, -1,
+ -1, 65, 66, 67, -1, 68, 69, 70,
+ 71, 72, 73, 74, 75, 76, -1, -1,
+ -1, 77, -1, 78, 79, -1, 80, -1,
+ 81, -1, -1, 82, 83, -1, 84, 85,
+ 86, -1, 87, 88, 89, 90, 91, -1,
+ 92, 93, 94, 95, -1, -1, -1, -1,
+ 96, 97, 98, 99, -1, 100, 101, -1,
+ 102, 103, -1, 104, 105, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 106, 107,
+ -1, -1, -1, -1, 108, -1, -1, -1,
+ -1, -1, -1, -1, 109, 110, -1, -1,
+ 111, 112, 113, 114, -1, 115, -1, -1,
+ 116, -1, -1, 117, 118, 119, 120, -1,
+ -1, -1, 121, -1, 122, -1, -1, -1,
+ -1, 123, 124, 125, -1, 126, -1, 127,
+ -1, -1, 128, -1, -1, -1, -1, -1,
+ 129, 130, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, 131, -1, -1,
- -1, -1, 132, -1, -1, 133, -1, 134,
- -1, -1, 135, -1, -1, -1, -1, -1,
- -1, 136, -1, -1, -1, -1, 137, 138,
- -1, -1, 139, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 140,
- -1, -1, -1, 141, -1, -1, -1, -1,
- -1, 142, -1, 143, 144, 145, 146, 147,
- 148, 149, -1, -1, -1, -1, 150, -1,
- 151, 152, -1, 153, -1, 154, -1, 155,
- 156, -1, -1, -1, -1, -1, 157, -1,
- -1, 158, -1, -1, -1, -1, -1, -1,
- 159, 160, 161, -1, -1, 162, -1, -1,
- -1, 163, 164, -1, -1, -1, -1, 165,
- -1, -1, -1, 166, 167, -1, -1, -1,
- -1, -1, -1, -1, 168, -1, 169, 170,
- -1, -1, 171, -1, -1, -1, -1, 172,
- -1, 173, -1, -1, -1, -1, -1, -1,
- 174, -1, -1, -1, -1, 175, -1, 176,
- -1, -1, -1, -1, -1, -1, 177, -1,
- -1, -1, -1, 178, -1, -1, -1, -1,
- 179, -1, -1, 180, -1, 181, -1, -1,
- -1, 182, 183, -1, 184, 185, -1, -1,
- 186, -1, 187, -1, -1, 188, 189, 190,
- -1, -1, 191, -1, -1, -1, 192, -1,
- -1, -1, -1, -1, 193, -1, 194, 195,
- -1, -1, -1, -1, -1, -1, 196, 197,
- 198, -1, 199, -1, -1, -1, -1, -1,
- 200, -1, 201, 202, -1, -1, 203, 204,
- -1, 205, -1, -1, 206, -1, -1, 207,
- -1, -1, -1, 208, -1, -1, -1, 209,
- -1, 210, -1, -1, 211, -1, -1, -1,
- -1, -1, 212, -1, 213, 214, -1, -1,
- -1, 215, 216, -1, -1, 217, -1, -1,
- -1, 218, -1, -1, -1, -1, 219, -1,
- 220, 221, -1, -1, -1, 222, 223, 224,
- 225, -1, -1, -1, -1, -1, -1, 226,
- -1, -1, -1, 227, -1, 228, -1, -1,
- 229, 230, 231, -1, -1, -1, 232, -1,
- -1, -1, 233, 234, 235, -1, -1, 236,
- -1, -1, 237, 238, -1, -1, 239, 240,
- -1, 241, -1, 242, 243, -1, -1, -1,
- -1, 244, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 245, -1, -1, -1, -1,
+ -1, -1, -1, 132, 133, 134, 135, -1,
+ -1, -1, -1, -1, -1, 136, -1, -1,
+ -1, -1, 137, -1, -1, -1, 138, 139,
+ 140, 141, 142, -1, 143, 144, -1, 145,
+ 146, -1, -1, -1, -1, -1, -1, -1,
+ -1, 147, -1, -1, -1, 148, -1, 149,
+ 150, 151, -1, -1, -1, -1, 152, -1,
+ 153, -1, 154, -1, 155, 156, 157, -1,
+ 158, 159, -1, 160, 161, -1, 162, -1,
+ -1, -1, 163, -1, -1, -1, -1, -1,
+ -1, -1, 164, 165, -1, 166, -1, -1,
+ -1, -1, 167, -1, 168, -1, -1, -1,
+ -1, -1, 169, -1, 170, 171, -1, -1,
+ 172, -1, -1, 173, -1, -1, -1, -1,
+ -1, -1, -1, -1, 174, -1, 175, 176,
+ -1, -1, -1, -1, -1, 177, 178, -1,
+ 179, -1, -1, -1, -1, 180, -1, -1,
+ -1, 181, 182, 183, -1, 184, -1, 185,
+ 186, -1, -1, -1, 187, 188, -1, -1,
+ -1, -1, -1, 189, 190, 191, -1, 192,
+ -1, 193, -1, -1, -1, -1, -1, -1,
+ -1, -1, 194, 195, 196, -1, 197, -1,
+ -1, 198, 199, 200, -1, -1, 201, 202,
+ -1, 203, 204, 205, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 206, 207, 208,
+ -1, -1, -1, 209, -1, 210, -1, 211,
+ 212, 213, -1, -1, -1, -1, -1, -1,
+ 214, -1, -1, -1, -1, -1, 215, 216,
+ -1, -1, -1, -1, 217, -1, 218, -1,
+ -1, 219, -1, -1, -1, -1, -1, 220,
+ -1, 221, -1, -1, -1, -1, 222, 223,
+ 224, -1, -1, -1, 225, -1, -1, -1,
+ -1, 226, -1, -1, 227, 228, 229, 230,
+ 231, -1, 232, 233, -1, -1, 234, 235,
+ 236, -1, -1, 237, -1, 238, -1, -1,
+ -1, -1, -1, -1, -1, 239, -1, 240,
+ 241, -1, -1, -1, -1, -1, -1, 242,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 246, 247, 248,
- -1, -1, -1, 249, -1, -1, -1, 250,
+ 243, 244, -1, -1, -1, 245, -1, -1,
+ -1, -1, -1, -1, -1, -1, 246, -1,
+ -1, -1, -1, -1, 247, -1, -1, -1,
+ -1, -1, -1, 248, -1, -1, -1, -1,
+ 249, -1, 250, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, 251, -1, -1,
+ -1, 252, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 253, 254, -1,
+ -1, -1, -1, -1, -1, 255, -1, -1,
+ -1, -1, 256, 257, -1, -1, 258, -1,
+ -1, -1, -1, 259, 260, 261, -1, -1,
+ -1, -1, 262, -1, -1, 263, 264, -1,
+ -1, 265, -1, -1, 266, -1, -1, -1,
+ 267, 268, 269, 270, -1, -1, 271, -1,
+ -1, -1, -1, -1, -1, -1, 272, -1,
+ 273, -1, -1, -1, -1, -1, 274, -1,
+ -1, -1, 275, -1, -1, 276, -1, -1,
+ 277, -1, -1, -1, -1, 278, -1, -1,
+ -1, -1, -1, 279, -1, -1, -1, -1,
+ 280, -1, 281, -1, -1, -1, -1, -1,
+ -1, -1, -1, 282, 283, -1, -1, 284,
+ -1, -1, -1, -1, 285, -1, -1, 286,
+ 287, -1, -1, 288, -1, -1, 289, -1,
+ -1, -1, -1, -1, 290, -1, -1, -1,
+ -1, -1, 291, -1, 292, -1, 293, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 252, -1, 253, -1, -1, 254, -1, 255,
- -1, -1, -1, 256, -1, -1, -1, 257,
- 258, -1, -1, -1, -1, -1, -1, 259,
- -1, -1, -1, -1, 260, 261, -1, -1,
- 262, 263, -1, -1, 264, -1, -1, 265,
- 266, -1, -1, -1, 267, -1, -1, -1,
- -1, 268, -1, -1, 269, 270, 271, 272,
- -1, -1, -1, -1, -1, 273, -1, 274,
- -1, -1, 275, 276, -1, -1, 277, 278,
- -1, 279, 280, 281, -1, -1, 282, -1,
- 283, -1, -1, -1, 284, -1, -1, -1,
- 285, -1, 286, -1, -1, 287, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 288,
- 289, 290, 291, -1, -1, -1, -1, -1,
- 292, 293, 294, 295, -1, -1, -1, -1,
- -1, 296, -1, 297, 298, -1, 299, -1,
- -1, 300, -1, -1, -1, -1, -1, -1,
- 301, 302, -1, -1, -1, -1, -1, -1,
- 303, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 304, -1, -1, 305, -1, -1,
- 306, -1, 307, 308, -1, 309, -1, -1,
- 310, -1, -1, 311, -1, 312, -1, -1,
- -1, 313, -1, -1, -1, -1, 314, -1,
- -1, -1, 315, -1, 316, 317, -1, -1,
- 318, -1, 319, 320, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 321,
- 322, 323, 324, -1, -1, 325, 326, -1,
- -1, -1, 327, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 328, -1, -1, -1,
- 329, -1, 330, -1, -1, 331, -1, -1,
- 332, -1, -1, -1, -1, 333, -1, -1,
- -1, -1, 334, -1, 335, -1, -1, -1,
- -1, -1, -1, 336, 337, 338, -1, 339,
- -1, -1, -1, -1, -1, 340, 341, -1,
- -1, -1, -1, -1, -1, -1, 342, -1,
+ 294, -1, -1, -1, -1, -1, -1, 295,
+ -1, -1, -1, -1, -1, 296, -1, -1,
+ -1, -1, -1, 297, -1, -1, -1, -1,
+ -1, 298, -1, 299, 300, 301, -1, -1,
+ -1, -1, -1, 302, -1, -1, 303, -1,
+ -1, -1, -1, 304, -1, 305, -1, 306,
+ -1, 307, 308, -1, 309, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 343, -1, -1, 344, -1, -1, -1, -1,
- 345, -1, -1, -1, -1, -1, -1, -1,
+ 310, -1, -1, -1, -1, -1, -1, -1,
+ -1, 311, 312, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 346, -1, -1, -1, -1, 347,
- -1, -1, -1, -1, -1, -1, 348, -1,
- -1, -1, -1, -1, -1, 349, 350, -1,
- 351, -1, -1, 352, -1, -1, 353, -1,
- -1, 354, -1, -1, -1, -1, -1, 355,
- -1, -1, 356, 357, -1, -1, 358, 359,
- 360, -1, -1, -1, 361, 362, -1, 363,
- -1, -1, 364, -1, 365, -1, -1, -1,
- -1, -1, -1, -1, -1, 366, -1, -1,
- 367, -1, -1, -1, -1, -1, -1, -1,
- -1, 368, -1, -1, -1, -1, -1, 369,
- -1, 370, -1, 371, -1, -1, 372, -1,
- -1, 373, -1, -1, -1, 374, 375, 376,
- -1, -1, 377, 378, -1, -1, 379, 380,
- -1, 381, -1, -1, -1, 382, -1, 383,
- 384, 385, -1, -1, -1, -1, -1, -1,
- 386, -1, -1, 387, 388, -1, -1, -1,
- -1, -1, -1, 389, -1, -1, -1, -1,
- -1, -1, 390, 391, -1, 392, 393, 394,
- -1, -1, -1, -1, -1, 395, -1, -1,
- -1, -1, -1, -1, -1, -1, 396, -1,
- 397, -1, -1, 398, -1, -1, 399, 400,
- -1, 401, 402, -1, 403, -1, -1, 404,
- -1, 405, 406, 407, -1, 408, -1, -1,
- 409, -1, 410, 411, 412, -1, -1, 413,
- 414, 415, -1, -1, -1, 416, -1, -1,
- -1, 417, -1, -1, 418, -1, 419, -1,
+ -1, -1, -1, -1, -1, 313, -1, -1,
+ -1, -1, 314, -1, -1, -1, -1, -1,
+ -1, -1, -1, 315, -1, -1, -1, -1,
+ 316, -1, -1, -1, -1, -1, 317, -1,
+ -1, -1, -1, -1, -1, -1, 318, 319,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 420,
- 421, -1, -1, -1, -1, -1, 422, -1,
+ -1, -1, -1, -1, -1, 320, -1, -1,
+ -1, -1, 321, -1, -1, -1, -1, -1,
+ 322, -1, -1, -1, -1, -1, -1, -1,
+ 323, -1, -1, 324, -1, -1, -1, -1,
+ -1, -1, -1, 325, 326, 327, -1, 328,
+ -1, 329, -1, -1, -1, -1, -1, -1,
+ -1, -1, 330, -1, 331, -1, 332, 333,
+ -1, -1, -1, -1, -1, -1, -1, 334,
+ -1, 335, -1, -1, -1, -1, 336, -1,
+ -1, -1, -1, -1, 337, -1, -1, -1,
+ 338, -1, -1, -1, 339, 340, 341, -1,
+ -1, -1, 342, -1, -1, -1, -1, 343,
+ 344, -1, -1, -1, 345, -1, 346, -1,
+ -1, -1, -1, -1, -1, -1, 347, -1,
+ -1, 348, -1, -1, -1, -1, -1, -1,
+ -1, 349, -1, -1, 350, 351, -1, 352,
+ -1, -1, 353, -1, -1, -1, -1, -1,
+ -1, -1, 354, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 355, 356,
+ -1, -1, -1, -1, -1, -1, 357, -1,
+ 358, -1, 359, 360, -1, 361, 362, 363,
+ 364, -1, 365, -1, 366, -1, -1, -1,
+ -1, 367, -1, -1, -1, -1, -1, -1,
+ -1, -1, 368, 369, 370, 371, -1, -1,
+ -1, -1, -1, 372, -1, -1, -1, 373,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 423, -1, -1, -1, -1, -1, -1,
- -1, 424, 425, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 426, -1, -1,
+ -1, -1, -1, -1, -1, 374, 375, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 376, -1, -1, -1,
+ 377, -1, 378, -1, -1, -1, -1, -1,
+ 379, 380, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 381, -1,
+ -1, 382, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 383, 384,
+ -1, -1, -1, -1, -1, -1, -1, 385,
+ -1, -1, -1, -1, -1, 386, -1, -1,
+ 387, -1, -1, 388, -1, -1, -1, -1,
+ -1, -1, 389, 390, -1, -1, -1, -1,
+ -1, -1, 391, -1, -1, -1, -1, -1,
+ 392, -1, -1, -1, -1, 393, 394, 395,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 396, 397, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 398, -1, 399,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 400, -1, -1, -1, -1, -1, 401,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 402, 403, 404,
+ -1, -1, -1, -1, 405, -1, -1, -1,
+ 406, 407, -1, -1, -1, -1, -1, -1,
+ -1, 408, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 409, 410, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 411, -1, -1, -1, -1, -1, -1, -1,
+ 412, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 413, -1,
+ -1, -1, -1, -1, -1, -1, 414, -1,
+ -1, -1, -1, 415, -1, -1, -1, -1,
+ 416, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 417, -1, -1,
+ -1, 418, -1, 419, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 420, -1, -1, -1, -1, -1,
+ 421, 422, 423, -1, -1, -1, -1, 424,
+ -1, -1, -1, -1, -1, -1, 425, 426,
-1, -1, -1, -1, -1, 427, -1, -1,
- -1, -1, -1, 428, -1, -1, -1, -1,
- -1, -1, -1, 429, -1, -1, 430, -1,
- -1, -1, -1, -1, 431, -1, 432, -1,
- 433, -1, -1, 434, -1, 435, -1, 436,
- -1, -1, -1, 437, -1, -1, -1, 438,
- -1, -1, -1, 439, -1, -1, -1, 440,
- -1, 441, -1, -1, -1, -1, -1, -1,
- -1, 442, -1, -1, 443, 444, -1, 445,
- -1, -1, -1, -1, 446, -1, -1, 447,
- -1, -1, -1, -1, 448, 449, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 450,
- 451, -1, -1, 452, -1, -1, -1, -1,
- 453, -1, -1, 454, -1, -1, -1, -1,
- -1, -1, 455, -1, 456, 457, -1, -1,
- 458, -1, -1, -1, 459, 460, -1, 461,
- -1, -1, -1, 462, -1, -1, 463, 464,
- 465, -1, -1, 466, -1, -1, -1, -1,
- -1, 467, -1, -1, -1, -1, 468, -1,
- -1, 469, 470, 471, 472, 473, -1, -1,
- -1, 474, -1, -1, -1, -1, 475, -1,
- -1, -1, -1, 476, -1, -1, -1, -1,
- -1, 477, -1, -1, -1, -1, -1, -1,
- -1, 478, -1, -1, -1, -1, 479, 480,
- -1, -1, 481, 482, -1, -1, -1, 483,
- -1, 484, -1, -1, -1, 485, -1, 486,
- 487, -1, -1, -1, 488, 489, 490, 491,
+ -1, 428, -1, -1, -1, -1, -1, -1,
+ -1, -1, 429, -1, -1, -1, 430, 431,
+ 432, 433, 434, -1, -1, -1, 435, 436,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 492, -1, -1, 493, -1, -1,
- 494, 495, -1, 496, 497, 498, -1, -1,
- -1, -1, -1, -1, -1, 499, 500, -1,
- 501, -1, -1, -1, 502, -1, 503, 504,
- -1, -1, -1, -1, -1, -1, -1, 505,
- 506, 507, -1, -1, -1, -1, -1, 508,
- -1, -1, -1, 509, -1, -1, 510, 511,
- -1, 512, 513, 514, -1, -1, -1, -1,
- -1, 515, -1, 516, -1, -1, -1, 517,
- 518, 519, 520, -1, 521, -1, 522, -1,
- -1, -1, -1, 523, 524, 525, -1, -1,
- -1, -1, -1, -1, 526, -1, -1, -1,
- -1, -1, -1, -1, -1, 527, 528, 529,
- -1, -1, -1, -1, 530, 531, -1, -1,
- 532, 533, 534, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 535, -1, -1, 536,
- 537, -1, 538, 539, -1, -1, 540, -1,
- -1, -1, -1, -1, -1, 541, -1, -1,
- -1, -1, 542, 543, -1, -1, -1, -1,
+ -1, 437, 438, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 439,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 440, 441, -1, -1, -1,
+ -1, -1, -1, -1, -1, 442, -1, -1,
+ -1, -1, -1, 443, 444, -1, -1, -1,
+ -1, 445, -1, -1, 446, -1, -1, 447,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 448, -1, 449, -1, 450, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 451, -1, -1, -1, -1, 452, -1, 453,
+ -1, 454, -1, -1, -1, 455, -1, 456,
+ -1, -1, -1, -1, -1, -1, -1, 457,
+ -1, -1, -1, -1, -1, 458, -1, 459,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 460, -1, 461, -1, -1, -1,
+ -1, -1, -1, 462, 463, -1, -1, 464,
+ -1, -1, -1, -1, -1, -1, -1, 465,
+ -1, 466, -1, -1, -1, 467, -1, -1,
+ -1, 468, -1, 469, -1, -1, -1, 470,
+ -1, -1, -1, -1, -1, -1, -1, 471,
+ -1, -1, -1, -1, -1, 472, -1, 473,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 474, 475, -1,
+ -1, -1, -1, -1, -1, -1, -1, 476,
+ 477, 478, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 479, -1, -1, -1, -1, -1, 480, -1,
+ 481, -1, -1, 482, -1, -1, 483, -1,
+ -1, 484, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 485, -1, -1, -1, -1,
+ -1, -1, 486, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 487, -1, -1, -1,
+ 488, -1, -1, -1, -1, -1, -1, -1,
+ 489, -1, -1, -1, 490, -1, -1, -1,
+ -1, -1, -1, -1, -1, 491, -1, -1,
+ -1, -1, 492, -1, -1, -1, -1, 493,
+ -1, -1, 494, -1, 495, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 496, 497,
+ -1, 498, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 499, -1, -1,
+ -1, -1, -1, 500, -1, -1, -1, -1,
+ 501, -1, -1, -1, -1, -1, -1, -1,
+ 502, -1, 503, -1, -1, -1, -1, -1,
+ -1, -1, -1, 504, -1, -1, -1, -1,
+ -1, -1, -1, 505, -1, -1, -1, -1,
+ -1, -1, -1, 506, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 507, -1, -1, -1, 508, -1, 509, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 510, -1, -1, -1, -1, -1,
+ -1, -1, 511, -1, -1, -1, -1, -1,
+ 512, -1, 513, -1, 514, -1, 515, -1,
+ -1, 516, 517, -1, -1, 518, -1, -1,
+ -1, -1, -1, 519, -1, -1, 520, -1,
+ -1, -1, 521, -1, -1, -1, -1, -1,
+ -1, -1, 522, -1, -1, -1, -1, 523,
+ -1, -1, -1, 524, -1, -1, 525, -1,
+ -1, -1, -1, -1, -1, -1, 526, 527,
+ 528, 529, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 530, -1, -1, -1, -1, 531, 532, -1,
+ -1, -1, 533, -1, -1, -1, -1, -1,
+ 534, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 535, -1, -1, -1,
+ -1, -1, -1, -1, -1, 536, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 537,
+ -1, -1, 538, -1, -1, 539, -1, 540,
+ -1, -1, 541, -1, -1, 542, -1, 543,
544, 545, -1, -1, -1, -1, -1, -1,
+ 546, 547, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 546, -1, 547, -1, -1, -1,
- -1, -1, -1, 548, 549, -1, 550, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 551, -1, -1, 552, -1, -1, -1,
- 553, 554, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 555, -1, -1, -1,
- -1, -1, 556, -1, -1, -1, -1, -1,
- -1, 557, 558, -1, -1, -1, -1, -1,
- -1, -1, -1, 559, -1, -1, 560, -1,
- -1, -1, -1, -1, 561, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 562, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 563, -1,
- -1, 564, -1, -1, -1, -1, 565, -1,
- -1, 566, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 567,
+ 548, 549, -1, -1, 550, -1, 551, -1,
+ 552, -1, 553, -1, -1, -1, -1, -1,
+ -1, -1, -1, 554, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 568, -1, -1, -1, -1, 569, 570,
- 571, -1, -1, 572, -1, 573, 574, 575,
- 576, -1, 577, 578, -1, 579, -1, -1,
- 580, -1, -1, -1, 581, -1, -1, -1,
- 582, -1, -1, -1, 583, -1, -1, 584,
- -1, 585, -1, -1, 586, 587, -1, -1,
- -1, 588, -1, -1, 589, -1, -1, -1,
- -1, -1, -1, 590, -1, -1, 591, 592,
- -1, -1, -1, 593, -1, 594, -1, -1,
- -1, -1, 595, -1, -1, -1, -1, -1,
- -1, 596, 597, -1, -1, -1, -1, 598,
- -1, -1, -1, -1, -1, -1, 599, -1,
- -1, -1, 600, -1, 601, 602, -1, -1,
- -1, -1, -1, 603, -1, 604, 605, 606,
- -1, -1, -1, 607, -1, -1, 608, 609,
- 610, 611, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 612, -1,
- -1, 613, -1, -1, -1, -1, 614, -1,
- -1, -1, -1, -1, -1, -1, -1, 615,
- -1, 616, 617, 618, -1, 619, -1, -1,
- 620, -1, -1, -1, -1, 621, -1, 622,
- -1, -1, -1, -1, 623, -1, -1, 624,
- 625, -1, -1, -1, 626, 627, -1, -1,
+ -1, 555, -1, -1, -1, -1, -1, -1,
+ 556, -1, -1, -1, -1, -1, -1, -1,
+ -1, 557, -1, -1, -1, 558, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 628, -1, -1, -1, -1, 629, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 630, -1, -1, -1, 631, 632, -1,
- -1, 633, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 634, -1, -1, 635,
- -1, -1, -1, 636, -1, -1, 637, -1,
- -1, -1, -1, -1, -1, 638, 639, 640,
- -1, -1, 641, -1, 642, -1, 643, 644,
- -1, -1, -1, 645, -1, -1, -1, 646,
- -1, 647, -1, 648, 649, -1, -1, -1,
+ -1, -1, 559, -1, -1, 560, -1, 561,
+ -1, -1, -1, 562, -1, -1, -1, -1,
+ -1, 563, -1, -1, 564, -1, -1, -1,
+ -1, 565, -1, -1, -1, -1, -1, -1,
+ -1, 566, -1, -1, -1, 567, 568, 569,
+ -1, -1, -1, 570, -1, -1, -1, -1,
+ -1, -1, -1, -1, 571, -1, -1, -1,
+ -1, -1, -1, -1, -1, 572, -1, 573,
+ -1, 574, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 650, -1, -1, -1, -1, -1, -1, -1,
- -1, 651, -1, 652, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 575, -1, -1, -1, -1, 576, -1,
+ -1, -1, -1, 577, -1, -1, -1, -1,
+ -1, -1, 578, -1, -1, -1, -1, -1,
+ -1, -1, 579, -1, 580, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 581,
+ -1, -1, -1, 582, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 583, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 584, -1, -1,
+ -1, 585, 586, -1, -1, 587, -1, -1,
+ -1, 588, 589, 590, -1, -1, -1, 591,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 592, -1, 593, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 653, 654, -1, -1,
- -1, -1, -1, 655, -1, -1, -1, 656,
- 657, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 658, -1, -1, 659, -1, -1,
- 660, -1, 661, -1, -1, -1, 662, 663,
- 664, -1, 665, -1, -1, -1, -1, -1,
- 666, 667, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 668, -1, -1, 669, -1,
- -1, -1, 670, -1, -1, -1, 671, -1,
- 672, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 673, 674,
- 675, 676, -1, -1, -1, -1, -1, 677,
- 678, -1, -1, -1, -1, -1, -1, 679,
- -1, -1, -1, -1, -1, 680, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 681, 682, -1, -1, -1, -1, -1,
- 683, -1, -1, -1, 684, 685, 686, -1,
- 687, -1, 688, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 689,
- -1, -1, 690, -1, -1, -1, -1, -1,
- -1, -1, -1, 691, 692, -1, 693, -1,
- -1, -1, 694, -1, -1, -1, 695, -1,
- -1, -1, 696, -1, 697, 698, -1, -1,
- -1, -1, 699, -1, -1, -1, -1, -1,
+ 594, -1, -1, -1, 595, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 596,
+ -1, -1, -1, -1, -1, 597, -1, 598,
+ 599, -1, -1, -1, -1, -1, 600, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 700, -1, -1, -1, -1, -1, -1, 701,
+ -1, -1, -1, 601, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 702, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 703, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 704, -1, -1, -1, -1, 705,
- -1, 706, 707, -1, 708, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 709, -1, -1, -1,
+ -1, -1, -1, -1, 602, -1, -1, -1,
+ -1, 603, -1, 604, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 710, -1, -1, 711, 712, -1, -1, -1,
+ -1, -1, -1, -1, 605, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 606, -1,
+ -1, -1, -1, 607, -1, -1, -1, 608,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 609, -1, -1, 610, -1, -1, -1, -1,
+ 611, -1, -1, -1, -1, -1, -1, -1,
+ 612, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 613, -1,
+ -1, -1, -1, -1, -1, 614, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 713, 714, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 715, 716,
-1, -1, -1, -1, -1, -1, -1, -1,
- 717, -1, -1, 718, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 719, -1, -1,
- 720, -1, -1, -1, -1, -1, 721, -1,
- -1, -1, -1, -1, -1, 722, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 723,
- 724, -1, 725, -1, -1, -1, 726, -1,
- -1, -1, 727, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 728, 729,
- 730, 731, 732, -1, -1, -1, 733, -1,
- 734, 735, -1, 736, -1, 737, -1, -1,
- 738, -1, -1, -1, 739, -1, -1, -1,
- -1, -1, -1, 740, -1, -1, -1, 741,
- -1, -1, -1, -1, 742, 743, -1, -1,
+ 615, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 616, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 617, -1, -1, -1, -1, 618, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 619, -1, -1, -1, -1,
+ -1, -1, 620, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 621,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 622, -1, 623, -1, -1, 624,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 625, -1, -1, 626, -1, -1, -1, 627,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 628, -1, -1, -1, 629, -1, -1,
+ -1, 630, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 631, -1, -1, -1,
+ 632, -1, -1, -1, -1, 633, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 634,
+ -1, -1, -1, -1, -1, 635, -1, 636,
+ 637, -1, -1, -1, -1, 638, -1, -1,
+ 639, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 640, -1, -1, -1, -1, -1, -1, 641,
+ 642, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 643, -1, -1,
+ -1, -1, -1, 644, -1, -1, -1, 645,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 646, -1, -1, -1,
+ -1, -1, -1, -1, 647, 648, 649, -1,
+ -1, -1, -1, 650, -1, -1, -1, -1,
+ 651, -1, -1, -1, -1, -1, -1, -1,
+ -1, 652, -1, -1, -1, -1, -1, 653,
+ -1, -1, -1, -1, -1, 654, -1, -1,
+ -1, 655, -1, 656, -1, -1, -1, -1,
+ -1, -1, -1, -1, 657, -1, 658, -1,
+ -1, -1, -1, -1, -1, -1, 659, -1,
+ 660, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 661, -1, -1, -1,
+ -1, -1, -1, 662, -1, -1, -1, 663,
+ -1, 664, -1, -1, -1, 665, -1, -1,
+ -1, -1, -1, -1, 666, -1, -1, 667,
+ -1, 668, -1, -1, -1, 669, -1, -1,
+ -1, -1, -1, 670, 671, -1, -1, 672,
+ 673, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 674, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 675, -1, -1, -1,
+ -1, -1, 676, -1, 677, -1, -1, 678,
+ -1, -1, -1, -1, -1, -1, 679, -1,
+ -1, -1, -1, -1, 680, -1, -1, -1,
+ -1, -1, -1, 681, -1, -1, -1, -1,
+ 682, -1, -1, -1, -1, -1, -1, -1,
+ 683, 684, 685, -1, -1, 686, -1, 687,
+ -1, -1, -1, 688, -1, -1, -1, -1,
+ -1, -1, -1, 689, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 690, -1, 691, -1, -1, -1,
+ -1, -1, 692, -1, -1, -1, -1, 693,
+ -1, -1, -1, -1, -1, -1, 694, -1,
+ 695, 696, -1, 697, -1, -1, -1, -1,
+ -1, -1, -1, -1, 698, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 699, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 700, -1, -1, -1,
+ -1, 701, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 702, -1, -1, -1, 703, -1,
+ -1, -1, 704, -1, -1, -1, -1, -1,
+ -1, -1, -1, 705, -1, -1, -1, -1,
+ -1, -1, -1, -1, 706, -1, -1, -1,
+ -1, -1, -1, -1, -1, 707, -1, -1,
+ -1, 708, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 709, -1, -1, -1, -1,
+ 710, -1, -1, -1, 711, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 712, -1,
+ -1, -1, -1, -1, -1, -1, -1, 713,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 714, -1, 715, -1, -1, -1, -1,
+ -1, -1, -1, -1, 716, 717, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 718,
+ 719, 720, 721, -1, -1, -1, -1, 722,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 723, 724, -1,
+ -1, -1, -1, 725, 726, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 727, -1,
+ -1, -1, -1, -1, -1, 728, -1, 729,
+ -1, -1, 730, -1, -1, -1, 731, 732,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 733, 734, -1, -1, 735, -1,
+ 736, -1, -1, -1, -1, -1, -1, -1,
+ -1, 737, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 738, 739, -1, -1, -1,
+ 740, -1, 741, -1, -1, -1, -1, -1,
+ -1, -1, -1, 742, -1, -1, 743, -1,
744, -1, -1, -1, -1, -1, 745, -1,
- -1, -1, 746, -1, 747, -1, -1, -1,
- -1, 748, -1, -1, -1, -1, 749, -1,
- -1, -1, -1, -1, 750, -1, -1, -1,
- -1, 751, 752, -1, -1, -1, -1, 753,
- -1, -1, 754, -1, -1, -1, -1, -1,
- 755, -1, -1, -1, 756, -1, -1, -1,
- 757, -1, 758, -1, 759, -1, -1, -1,
- -1, -1, 760, -1, -1, 761, -1, -1,
- -1, 762, 763, 764, -1, -1, 765, -1,
+ -1, -1, -1, 746, -1, -1, -1, 747,
+ -1, -1, -1, -1, 748, 749, -1, -1,
+ -1, 750, -1, -1, -1, 751, -1, 752,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 766, -1,
+ -1, -1, 753, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 754, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 755, 756,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 757, 758, 759, -1, -1, -1, -1,
+ -1, -1, -1, -1, 760, 761, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 762, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 767, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 768,
- 769, -1, -1, -1, 770, -1, -1, -1,
- -1, 771, -1, 772, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 773,
- -1, -1, -1, -1, -1, -1, 774, -1,
- -1, 775, -1, -1, 776, 777, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 778, -1, -1, -1, -1,
+ -1, -1, -1, -1, 763, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 779, -1, 780, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 781, -1,
- -1, -1, 782, -1, -1, -1, -1, 783,
- -1, -1, -1, -1, -1, 784, 785, -1,
- -1, -1, -1, -1, 786, -1, -1, -1,
- -1, -1, -1, -1, -1, 787, -1, -1,
- -1, -1, -1, -1, -1, -1, 788, -1,
- -1, -1, 789, -1, -1, -1, -1, -1,
- -1, 790, -1, -1, -1, 791, -1, -1,
- 792, -1, -1, -1, 793, -1, -1, -1,
- -1, -1, 794, -1, -1, -1, -1, -1,
- -1, -1, -1, 795, -1, -1, -1, 796,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 797, -1, -1, -1, -1, -1, -1,
- 798, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 799, -1, -1, -1, -1,
- -1, 800, -1, -1, -1, -1, -1, 801,
- -1, -1, 802, -1, -1, -1, -1, -1,
- -1, -1, -1, 803, 804, -1, -1, 805,
- -1, 806, 807, 808, -1, -1, -1, -1,
- 809, -1, -1, 810, -1, -1, 811, -1,
- -1, -1, -1, 812, -1, 813, -1, -1,
- -1, -1, -1, 814, -1, 815, -1, -1,
- -1, -1, 816, -1, -1, 817, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 818,
- -1, -1, -1, -1, -1, -1, -1, 819,
- -1, -1, -1, -1, 820, -1, -1, -1,
- -1, -1, -1, -1, -1, 821, -1, 822,
- -1, -1, -1, -1, -1, -1, -1, 823,
- -1, -1, -1, -1, 824, -1, 825, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 826, 827, 828, 829, 830, 831, 832, 833,
- 834, 835, 836, 837, 838, 839, 840, 841,
- 842, -1, -1, -1, -1, -1, 843, -1,
+ -1, -1, -1, -1, 764, -1, -1, 765,
+ -1, 766, -1, -1, 767, 768, -1, 769,
+ -1, -1, 770, 771, 772, -1, -1, -1,
+ 773, -1, -1, -1, -1, -1, 774, -1,
+ -1, -1, -1, -1, -1, -1, 775, -1,
+ 776, -1, -1, -1, 777, 778, -1, -1,
+ 779, -1, -1, 780, 781, 782, -1, 783,
+ -1, 784, -1, -1, 785, -1, 786, -1,
+ 787, -1, -1, -1, 788, -1, -1, -1,
+ -1, 789, -1, -1, -1, 790, -1, -1,
+ -1, -1, -1, 791, -1, -1, 792, 793,
+ -1, -1, -1, -1, -1, 794, 795, -1,
+ 796, -1, -1, 797, 798, -1, 799, 800,
+ -1, -1, -1, -1, 801, -1, -1, -1,
+ -1, 802, 803, -1, 804, -1, -1, -1,
+ -1, -1, -1, -1, 805, -1, -1, -1,
+ -1, 806, 807, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 808, -1, -1, -1,
+ -1, 809, -1, -1, -1, 810, -1, -1,
+ -1, -1, 811, -1, -1, 812, 813, -1,
+ -1, -1, -1, 814, -1, -1, -1, -1,
+ -1, -1, 815, 816, -1, -1, 817, -1,
+ -1, -1, 818, -1, -1, -1, -1, -1,
+ -1, 819, 820, -1, 821, -1, -1, -1,
+ -1, -1, 822, 823, 824, -1, -1, -1,
+ 825, -1, 826, -1, -1, -1, -1, 827,
+ -1, -1, -1, -1, 828, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 844, -1, -1, -1, -1,
+ -1, 829, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 830, -1, -1, -1, -1,
+ -1, 831, 832, -1, -1, -1, -1, -1,
+ -1, 833, -1, -1, 834, -1, -1, -1,
+ -1, -1, -1, 835, -1, -1, -1, 836,
+ -1, -1, -1, -1, -1, 837, -1, -1,
+ -1, 838, -1, -1, 839, 840, -1, -1,
+ 841, -1, 842, 843, -1, -1, 844, -1,
+ -1, 845, 846, -1, 847, -1, -1, -1,
+ 848, -1, -1, -1, -1, 849, -1, -1,
+ -1, -1, -1, -1, 850, 851, -1, 852,
+ -1, -1, 853, 854, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 855,
+ -1, -1, -1, -1, -1, 856, 857, 858,
-1, -1, -1, -1, -1, -1, -1, -1,
- 845, -1, -1, 846, -1, -1, -1, 847,
- -1, -1, -1, -1, -1, -1, -1, 848,
- -1, -1, -1, 849, 850, -1, -1, 851,
- -1, 852, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 859, -1, -1, -1, 860,
+ -1, 861, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 862, -1, -1, -1,
+ 863, 864, -1, -1, -1, -1, -1, -1,
+ 865, -1, -1, 866, -1, 867, -1, 868,
+ -1, -1, -1, 869, -1, -1, -1, 870,
+ -1, -1, -1, 871, 872, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 853, -1, -1, -1, -1, 854, -1,
+ -1, 873, -1, -1, -1, -1, 874, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 855, -1, -1,
+ -1, -1, -1, 875, -1, -1, -1, -1,
+ 876, 877, -1, 878, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 879,
+ -1, -1, -1, 880, 881, -1, -1, -1,
+ 882, 883, -1, 884, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 856, -1,
- -1, -1, -1, 857, -1, -1, -1, -1,
- 858, -1, -1, -1, 859, -1, -1, -1,
- 860, -1, -1, -1, -1, -1, 861, -1,
- -1, 862, -1, 863, -1, -1, -1, -1,
- -1, 864, -1, -1, -1, -1, -1, 865,
+ -1, -1, 885, -1, -1, -1, 886, 887,
+ 888, 889, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 890, -1,
+ -1, 891, -1, -1, -1, -1, -1, -1,
+ -1, 892, -1, -1, 893, -1, -1, -1,
+ -1, -1, -1, 894, -1, 895, -1, -1,
+ -1, -1, 896, 897, -1, -1, -1, -1,
+ -1, 898, -1, -1, -1, -1, -1, -1,
+ -1, -1, 899, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 900, -1, -1, -1, -1, -1, 901, -1,
+ -1, -1, -1, -1, -1, 902, -1, -1,
+ 903, 904, 905, 906, -1, 907, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 866, -1, -1, -1, -1, -1,
+ -1, -1, 908, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 867, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 868, -1,
- -1, -1, 869, -1, -1, -1, -1, -1,
- 870, -1, -1, -1, 871, -1, 872, -1,
- 873, 874, -1, -1, 875, -1, 876, -1,
- -1, -1, -1, 877, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 878, -1,
- -1, -1, -1, -1, -1, -1, 879, -1,
- 880, 881, 882, -1, -1, -1, -1, -1,
- -1, -1, 883, -1, -1, -1, 884, -1,
- -1, -1, 885, 886, -1, -1, -1, 887,
- -1, -1, 888, 889, 890, -1, 891, -1,
- -1, -1, -1, -1, -1, -1, 892, -1,
- -1, -1, 893, -1, 894, -1, -1, 895,
- -1, -1, 896, 897, 898, -1, -1, -1,
- 899, -1, 900, -1, -1, -1, 901, 902,
- -1, -1, -1, -1, -1, 903, -1, -1,
- -1, -1, -1, -1, 904, 905, -1, -1,
+ -1, 909, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 910, -1,
+ -1, -1, 911, -1, 912, -1, 913, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 914, -1, -1, -1, -1,
+ 915, 916, 917, -1, -1, -1, -1, 918,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 919, -1, -1, 920, -1, -1,
+ -1, -1, -1, -1, 921, -1, -1, -1,
+ -1, -1, -1, -1, 922, -1, -1, 923,
+ -1, -1, -1, -1, 924, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 925, -1, -1, -1, -1, -1, 926,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 927, 928,
+ -1, -1, 929, 930, 931, -1, -1, -1,
+ -1, -1, -1, -1, 932, -1, -1, 933,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 934, -1, -1, -1,
+ -1, -1, 935, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 936,
+ -1, 937, 938, -1, -1, 939, -1, 940,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 906, -1, -1, -1, -1,
+ -1, 941, -1, 942, 943, -1, -1, -1,
+ -1, -1, -1, -1, -1, 944, 945, -1,
+ -1, -1, -1, -1, -1, -1, 946, -1,
+ -1, -1, 947, -1, -1, -1, -1, -1,
+ -1, -1, 948, -1, -1, -1, -1, -1,
+ -1, -1, 949, -1, -1, -1, -1, -1,
+ -1, 950, 951, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 952,
-1, -1, -1, -1, -1, -1, -1, -1,
- 907, -1, -1, -1, 908, -1, -1, -1,
- 909, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 910, -1, -1,
- -1, 911, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 912, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 913, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 914, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 915, -1,
- -1, 916, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 917, -1, -1,
- -1, 918, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 919, -1, -1, -1, -1,
- 920, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 921,
- -1, 922, -1, -1, 923, -1, 924, -1,
- -1, 925, 926, -1, 927, -1, -1, -1,
- -1, -1, 928, -1, 929, 930, 931, -1,
- -1, -1, -1, -1, -1, 932, -1, -1,
- -1, -1, -1, -1, -1, 933, -1, -1,
- -1, -1, -1, -1, -1, 934, -1, -1,
- -1, -1, -1, 935, -1, -1, -1, 936,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 937, 938, -1, -1, 939, -1,
- -1, -1, -1, -1, 940, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 941, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 942, 943, -1, 944, -1, -1,
- -1, -1, -1, -1, 945, 946, -1, -1,
- -1, 947, -1, -1, -1, -1, -1, -1,
- 948, -1, -1, 949, -1, -1, -1, -1,
- 950, -1, -1, -1, 951, -1, -1, 952,
-1, -1, -1, -1, -1, -1, 953, -1,
- -1, -1, -1, -1, 954, -1, -1, -1,
- -1, -1, 955, -1, 956, -1, -1, -1,
- -1, -1, -1, 957, -1, -1, -1, -1,
- -1, 958, 959, -1, 960, 961, -1, -1,
- -1, -1, -1, 962, -1, 963, -1, -1,
- -1, -1, -1, -1, -1, -1, 964, -1,
- -1, -1, -1, -1, -1, 965, -1, -1,
- -1, -1, -1, -1, 966, -1, 967, -1,
- -1, 968, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 969, -1, -1,
- -1, -1, -1, 970, -1, 971, -1, -1,
- -1, 972, -1, -1, -1, -1, -1, -1,
- 973, -1, -1, -1, -1, -1, -1, -1,
+ -1, 954, -1, -1, -1, -1, -1, -1,
+ 955, 956, -1, -1, -1, -1, -1, -1,
+ 957, -1, -1, -1, -1, -1, 958, 959,
-1, -1, -1, -1, -1, -1, -1, -1,
- 974, -1, 975, -1, -1, -1, -1, -1,
- -1, 976, -1, -1, 977, -1, -1, -1,
+ -1, 960, 961, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 978,
- -1, -1, 979, -1, -1, -1, 980, -1,
- 981, -1, -1, -1, -1, -1, -1, -1,
- 982, -1, -1, -1, 983, -1, -1, -1,
- -1, 984, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 985,
- 986, -1, -1, -1, -1, -1, 987, -1,
- -1, -1, -1, 988, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 989, -1, -1, -1, 990,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 991, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 992,
- -1, -1, 993, -1, -1, -1, -1, -1,
+ 962, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 963, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 994, -1, -1,
- -1, -1, -1, -1, -1, -1, 995, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 996, -1, -1, -1, -1, -1, 997,
- -1, -1, -1, 998, -1, -1, 999, -1,
- -1, -1, 1000, 1001, -1, -1, 1002, 1003,
+ -1, -1, 964, -1, 965, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1004, -1, -1, -1, -1, 1005, -1,
- -1, -1, -1, 1006, -1, -1, -1, -1,
- -1, -1, -1, -1, 1007, -1, -1, -1,
- -1, 1008, -1, -1, -1, -1, -1, 1009,
- -1, -1, -1, -1, -1, -1, 1010, -1,
- -1, -1, -1, 1011, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1012,
- -1, -1, -1, -1, 1013, -1, -1, -1,
+ 966, -1, -1, -1, -1, -1, 967, -1,
+ -1, 968, 969, -1, -1, 970, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 1014, 1015, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 1016, -1, -1, 1017,
- -1, 1018, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 1019, -1, -1, -1,
- -1, 1020, -1, -1, 1021, -1, -1, -1,
- -1, 1022, -1, 1023, 1024, -1, -1, 1025,
- -1, -1, -1, -1, 1026, -1, -1, -1,
- -1, 1027, -1, -1, 1028, -1, -1, 1029,
- -1, 1030, -1, -1, -1, -1, -1, -1,
- -1, 1031, 1032, -1, -1, -1, -1, 1033,
- -1, 1034, -1, -1, -1, 1035, 1036, -1,
+ -1, -1, -1, -1, 971, -1, -1, -1,
+ -1, 972, -1, -1, 973, -1, -1, 974,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1037, -1, -1, -1, -1, 1038,
- 1039, -1, -1, -1, -1, -1, -1, 1040,
- -1, -1, -1, -1, -1, -1, 1041, 1042,
+ -1, -1, 975, -1, -1, -1, -1, -1,
+ 976, -1, 977, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1043,
- -1, -1, -1, 1044, 1045, -1, -1, -1,
- -1, -1, -1, 1046, 1047, -1, -1, 1048,
- -1, -1, -1, -1, 1049, -1, -1, -1,
- -1, -1, -1, -1, -1, 1050, -1, -1,
- 1051, -1, -1, -1, -1, -1, -1, -1,
+ 978, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1052, 1053, -1, 1054, -1, 1055, -1,
+ -1, -1, -1, 979, -1, 980, -1, -1,
+ -1, -1, 981, -1, -1, -1, 982, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1056, -1, -1, -1, -1, 1057, -1,
- -1, -1, -1, -1, -1, 1058, -1, -1,
- -1, -1, -1, -1, 1059, -1, -1, -1,
- 1060, -1, 1061, 1062, 1063, -1, -1, -1,
- -1, -1, 1064, -1, -1, -1, -1, -1,
- -1, -1, 1065, 1066, -1, -1, -1, -1,
- -1, 1067, -1, -1, -1, -1, 1068, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 1069, -1, -1, -1, -1, -1, -1, -1,
+ 983, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1070, -1,
- -1, -1, -1, -1, -1, 1071, -1, -1,
- -1, -1, -1, 1072, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 1073, 1074, -1, -1,
- -1, 1075, -1, -1, -1, -1, -1, -1,
+ -1, 984, -1, -1, -1, -1, 985, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1076,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 1077, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 1078, 1079, 1080, -1, -1,
- -1, -1, -1, -1, -1, 1081, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1082, -1, 1083, -1, -1, -1, -1,
- -1, -1, -1, 1084, -1, -1, -1, 1085,
- 1086, 1087, -1, -1, -1, -1, -1, -1,
- -1, -1, 1088, 1089, -1, -1, -1, 1090,
- 1091, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 1092, 1093, -1, -1, -1,
- -1, -1, -1, 1094, 1095, 1096, -1, -1,
- -1, 1097, -1, -1, -1, 1098, 1099, -1,
- -1, -1, -1, -1, -1, 1100, -1, 1101,
- -1, -1, 1102, 1103, -1, 1104, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1105, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1106, -1,
- -1, -1, -1, 1107, -1, 1108, -1, 1109,
- -1, -1, -1, -1, -1, 1110, -1, 1111,
- -1, -1, -1, 1112, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1113,
- 1114, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1115, -1, -1, 1116, -1, -1,
- -1, -1, -1, -1, -1, -1, 1117, -1,
- -1, 1118, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1119,
- -1, 1120, -1, -1, -1, -1, -1, 1121,
- -1, -1, -1, -1, 1122, 1123, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 1124, 1125, -1, -1, -1, -1, -1, -1,
- 1126, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 1127, -1, -1,
- -1, -1, 1128, -1, -1, -1, -1, 1129,
+ -1, -1, -1, -1, -1, -1, -1, 986,
+ -1, -1, -1, 987, 988, -1, -1, -1,
+ 989, 990, -1, 991, -1, -1, -1, -1,
+ -1, -1, -1, 992, -1, 993, -1, 994,
+ -1, -1, -1, 995, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1130, -1, -1, 1131, -1, 1132,
+ -1, 996, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1133,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1134, -1,
- -1, 1135, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 1136, -1, -1,
- -1, -1, -1, -1, 1137, -1, -1, -1,
- -1, -1, -1, -1, 1138, -1, -1, -1,
+ -1, -1, 997, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 998,
+ 999, 1000, -1, -1, -1, -1, -1, -1,
+ -1, 1001, 1002, -1, 1003, -1, 1004, 1005,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 1139, -1, -1,
- 1140, -1, -1, -1, -1, 1141, -1, -1,
- -1, -1, 1142, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 1143, -1, -1,
- -1, 1144, -1, 1145, -1, -1, -1, -1,
- 1146, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 1147, -1, 1148, -1,
- -1, -1, -1, -1, 1149, -1, -1, 1150,
- -1, -1, -1, -1, -1, -1, -1, 1151,
- -1, -1, -1, 1152, -1, -1, 1153, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1006,
+ 1007, -1, -1, -1, -1, -1, -1, -1,
+ 1008, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1009, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1010, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1011, -1, 1012, -1, -1,
+ -1, -1, -1, -1, -1, 1013, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1014, -1,
+ -1, -1, -1, 1015, 1016, 1017, 1018, 1019,
+ 1020, 1021, 1022, 1023, 1024, 1025, 1026, 1027,
+ 1028, 1029, 1030, 1031, 1032, -1, 1033, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1034, 1035,
+ -1, -1, 1036, -1, -1, -1, -1, 1037,
+ 1038, 1039, -1, -1, -1, -1, -1, -1,
+ -1, 1040, 1041, -1, 1042, -1, -1, -1,
+ -1, -1, -1, 1043, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1044, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1045, -1,
+ 1046, 1047, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1048, -1, -1, -1, -1,
+ 1049, -1, -1, -1, -1, -1, 1050, -1,
+ -1, -1, -1, -1, -1, 1051, -1, -1,
+ 1052, -1, -1, -1, -1, -1, -1, 1053,
+ -1, -1, 1054, 1055, -1, -1, 1056, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1057,
+ -1, -1, 1058, -1, -1, -1, -1, -1,
+ -1, -1, 1059, -1, -1, -1, -1, -1,
+ -1, 1060, -1, -1, 1061, -1, 1062, 1063,
+ -1, -1, -1, -1, -1, -1, 1064, -1,
+ -1, -1, 1065, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1066, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1067,
+ -1, -1, 1068, -1, -1, -1, -1, -1,
+ 1069, -1, -1, 1070, -1, -1, -1, 1071,
+ -1, 1072, 1073, -1, -1, -1, 1074, -1,
+ 1075, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1076, 1077, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1078, -1, -1, -1, -1, -1, 1079,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1080, -1, -1, -1, -1,
+ -1, -1, -1, 1081, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1082,
+ -1, -1, -1, 1083, -1, -1, -1, -1,
+ -1, -1, 1084, 1085, 1086, -1, -1, -1,
+ -1, -1, -1, 1087, 1088, -1, -1, -1,
+ 1089, -1, -1, -1, -1, 1090, -1, -1,
+ -1, -1, -1, -1, -1, 1091, -1, -1,
+ -1, 1092, -1, -1, -1, -1, 1093, -1,
+ 1094, 1095, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1096, -1,
+ -1, -1, -1, -1, 1097, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1098, 1099, -1, -1, -1, -1, 1100, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1101, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1102, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1103, -1,
+ 1104, -1, -1, -1, -1, 1105, -1, 1106,
+ 1107, 1108, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1109, -1, -1,
+ -1, -1, -1, -1, -1, 1110, -1, -1,
+ -1, 1111, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1112, 1113, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1114, 1115, 1116,
+ -1, 1117, 1118, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1119, -1, -1, -1, -1, -1, 1120, 1121,
+ 1122, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1123, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1124, -1, -1, -1, 1125,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1126, -1, 1127, 1128, 1129, -1,
+ -1, -1, -1, -1, -1, -1, 1130, -1,
+ -1, -1, -1, -1, -1, -1, 1131, -1,
+ 1132, -1, 1133, -1, -1, 1134, -1, 1135,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1136, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1137, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1138,
+ 1139, 1140, 1141, -1, -1, -1, 1142, -1,
+ 1143, -1, -1, -1, -1, -1, -1, 1144,
+ -1, -1, -1, -1, 1145, 1146, 1147, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1148, 1149, -1, -1, -1,
+ 1150, -1, -1, 1151, -1, -1, 1152, 1153,
-1, -1, -1, 1154, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1155, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1155, -1, 1156, -1, -1, -1,
+ -1, -1, -1, -1, 1156, -1, 1157, -1,
+ -1, -1, -1, -1, -1, -1, 1158, 1159,
+ -1, -1, 1160, -1, -1, -1, -1, 1161,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1157, -1, 1158, -1, -1, -1, -1,
- -1, -1, 1159, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 1160, -1, -1,
- -1, -1, 1161, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1162, -1, -1, -1, -1, -1, 1163,
- 1164, -1, -1, -1, 1165, -1, 1166, 1167,
- -1, -1, -1, -1, -1, 1168, -1, 1169,
- -1, -1, -1, -1, -1, 1170, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1162, 1163,
+ -1, 1164, -1, 1165, -1, -1, -1, -1,
+ -1, 1166, -1, -1, -1, -1, -1, 1167,
+ -1, -1, -1, -1, -1, -1, 1168, -1,
+ 1169, -1, -1, -1, -1, -1, -1, 1170,
-1, -1, -1, -1, -1, 1171, -1, -1,
- -1, -1, 1172, -1, 1173, 1174, -1, -1,
- -1, -1, 1175, -1, -1, -1, -1, 1176,
- -1, 1177, -1, -1, 1178, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 1179, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 1180, 1181, -1,
- 1182, -1, -1, 1183, -1, -1, 1184, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 1185, -1, -1, 1186,
- 1187, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1172, -1, -1,
+ -1, -1, -1, 1173, -1, -1, 1174, -1,
+ -1, -1, -1, -1, -1, -1, 1175, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1188, -1, -1, -1, -1, -1,
- -1, 1189, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1176, 1177,
+ -1, -1, -1, 1178, -1, 1179, -1, 1180,
-1, -1, -1, -1, -1, -1, -1, -1,
- 1190, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1191, 1192,
- -1, -1, -1, 1193, -1, -1, 1194, -1,
- -1, 1195, -1, -1, -1, -1, -1, -1,
- -1, 1196, 1197, -1, -1, -1, 1198, -1,
+ -1, -1, -1, -1, -1, 1181, -1, -1,
+ -1, 1182, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1183, -1, -1,
+ 1184, -1, -1, -1, 1185, -1, 1186, -1,
+ -1, 1187, -1, -1, -1, 1188, 1189, -1,
+ 1190, 1191, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1199, 1200, -1, 1201, -1, -1,
- -1, -1, 1202, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1192, -1, -1,
+ -1, 1193, -1, -1, -1, -1, -1, -1,
+ 1194, -1, -1, -1, 1195, -1, -1, -1,
+ -1, -1, -1, 1196, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1197, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1203, -1,
- -1, -1, -1, -1, -1, 1204, -1, -1,
- -1, -1, -1, -1, -1, 1205, -1, -1,
- -1, -1, -1, -1, -1, 1206, -1, -1,
+ -1, -1, 1198, -1, 1199, -1, -1, -1,
+ -1, -1, 1200, -1, -1, -1, -1, -1,
+ 1201, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1202, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1203,
+ -1, -1, 1204, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1205, 1206, -1, 1207, -1,
+ -1, -1, -1, 1208, 1209, 1210, -1, -1,
+ -1, -1, 1211, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1212, 1213, -1, -1, -1,
+ -1, 1214, -1, -1, -1, 1215, -1, 1216,
+ -1, -1, -1, -1, 1217, -1, -1, -1,
+ -1, -1, -1, -1, 1218, -1, 1219, -1,
+ -1, -1, -1, -1, 1220, -1, -1, -1,
+ -1, -1, -1, -1, 1221, -1, 1222, -1,
+ 1223, -1, -1, 1224, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1225,
+ -1, -1, -1, -1, -1, 1226, -1, -1,
+ -1, -1, 1227, 1228, -1, -1, -1, 1229,
+ -1, -1, -1, 1230, -1, -1, -1, 1231,
+ -1, 1232, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1233, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1234, -1, 1235, -1,
+ -1, 1236, -1, -1, -1, -1, -1, -1,
+ 1237, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 1207, -1, -1,
- -1, -1, -1, -1, -1, -1, 1208, -1,
- -1, -1, -1, 1209, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1238,
+ -1, 1239, -1, 1240, -1, -1, -1, -1,
+ -1, -1, -1, 1241, -1, -1, -1, -1,
+ -1, 1242, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1243, -1,
+ -1, -1, -1, 1244, -1, 1245, -1, -1,
+ 1246, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 1210, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1247, 1248,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1211, -1, -1, -1, -1, -1, 1212,
- -1, -1, -1, 1213, -1, -1, -1, -1,
- -1, -1, -1, 1214, -1, -1, -1, -1,
- -1, -1, -1, -1, 1215, -1, -1, 1216,
- -1, -1, -1, 1217, 1218, 1219, -1, -1,
- -1, -1, -1, -1, -1, -1, 1220, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 1221, -1, -1, -1,
- -1, -1, -1, 1222, -1, -1, -1, -1,
- -1, -1, 1223, 1224, -1, -1, -1, -1,
- 1225, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1226,
- -1, -1, -1, -1, -1, -1, -1, 1227,
- -1, 1228, -1, -1, -1, -1, -1, -1,
- 1229, -1, 1230, -1, -1, -1, -1, -1,
- 1231, -1, -1, -1, -1, 1232, -1, -1,
- 1233, -1, -1, -1, -1, -1, 1234, -1,
- -1, 1235, -1, -1, 1236, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1237, -1, -1, -1, -1, -1,
- -1, -1, -1, 1238, -1, -1, -1, -1,
- 1239, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1240, -1, -1, -1, -1, 1241,
- -1, -1, 1242, 1243, -1, -1, -1, 1244,
- 1245, -1, -1, -1, -1, 1246, -1, -1,
- 1247, -1, -1, -1, 1248, 1249, -1, 1250,
- -1, -1, -1, -1, -1, 1251, -1, 1252,
+ -1, -1, 1249, -1, -1, -1, -1, 1250,
+ -1, -1, -1, -1, 1251, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1252,
-1, -1, -1, -1, -1, -1, -1, 1253,
- 1254, 1255, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 1256, -1, -1, -1, -1,
- -1, -1, 1257, -1, 1258, -1, -1, -1,
+ -1, 1254, -1, -1, -1, 1255, -1, -1,
+ 1256, -1, 1257, -1, -1, -1, -1, -1,
+ 1258, -1, -1, -1, -1, -1, 1259, -1,
+ -1, -1, -1, -1, -1, 1260, -1, -1,
+ -1, -1, -1, 1261, -1, -1, 1262, -1,
+ 1263, -1, -1, -1, -1, 1264, -1, -1,
+ -1, 1265, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1259, 1260, 1261, -1, -1, -1, 1262,
- 1263, -1, 1264, -1, -1, 1265, -1, -1,
+ -1, 1266, -1, -1, 1267, -1, -1, -1,
+ -1, -1, 1268, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1269, -1, -1, -1,
+ 1270, -1, 1271, -1, -1, -1, 1272, -1,
+ -1, 1273, 1274, -1, 1275, -1, -1, -1,
+ 1276, -1, -1, 1277, -1, 1278, -1, 1279,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 1266, -1, -1, -1, -1,
- -1, -1, -1, -1, 1267, 1268, 1269, 1270,
- -1, 1271, -1, -1, -1, 1272, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 1273, -1, -1, -1,
- 1274, -1, -1, -1, -1, -1, -1, -1,
+ 1280, 1281, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1282, -1,
+ -1, -1, -1, -1, 1283, -1, 1284, -1,
+ -1, -1, 1285, 1286, -1, -1, -1, -1,
+ -1, -1, 1287, -1, -1, -1, -1, 1288,
+ -1, -1, -1, -1, -1, -1, -1, 1289,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 1275, -1, -1, -1,
- -1, 1276, -1, -1, -1, -1, 1277, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 1278, -1, -1, -1, -1,
- -1, -1, -1, -1, 1279, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1280,
- -1, -1, -1, 1281, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1282, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1283, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 1284, -1, 1285, -1, 1286, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1287,
+ -1, -1, -1, 1290, -1, -1, -1, -1,
+ -1, 1291, -1, -1, -1, -1, 1292, -1,
+ -1, -1, -1, -1, -1, -1, 1293, -1,
+ 1294, -1, 1295, -1, 1296, -1, 1297, -1,
+ -1, -1, 1298, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1299, 1300, -1,
+ -1, 1301, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1302, -1, -1,
+ 1303, -1, -1, -1, -1, 1304, 1305, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1288, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 1289, -1, -1, -1, -1,
- 1290, -1, -1, -1, 1291, -1, -1, -1,
- 1292, 1293, -1, -1, -1, -1, 1294, -1,
- -1, 1295, -1, 1296, -1, -1, -1, -1,
- 1297, -1, -1, 1298, -1, 1299, -1, -1,
- -1, 1300, -1, -1, -1, 1301, -1, -1,
- -1, -1, -1, 1302, -1, -1, -1, -1,
- -1, -1, -1, -1, 1303, -1, -1, -1,
- -1, -1, -1, 1304, -1, -1, -1, -1,
- -1, -1, -1, 1305, -1, -1, -1, -1,
+ -1, 1306, -1, -1, -1, 1307, -1, -1,
+ -1, -1, -1, -1, -1, 1308, -1, -1,
+ -1, -1, -1, -1, 1309, 1310, -1, 1311,
+ -1, -1, -1, -1, 1312, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1313, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1306,
+ -1, -1, 1314, -1, -1, 1315, -1, -1,
+ 1316, -1, -1, -1, -1, -1, -1, -1,
+ 1317, 1318, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1319, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1320, -1, -1, -1,
+ -1, -1, 1321, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 1307, -1, -1, -1, 1308, -1, -1, 1309,
- -1, 1310, -1, 1311, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 1312, -1, -1,
- -1, -1, -1, 1313, -1, -1, -1, -1,
- -1, -1, -1, 1314, -1, -1, -1, -1,
- -1, -1, -1, 1315, -1, -1, -1, -1,
- -1, 1316, -1, -1, -1, -1, -1, -1,
- -1, -1, 1317, -1, -1, -1, -1, 1318,
+ 1322, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1323, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1324, -1, 1325, -1,
+ 1326, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1327, -1, -1, -1,
+ -1, 1328, -1, -1, -1, -1, -1, -1,
+ 1329, -1, -1, -1, -1, 1330, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1331, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1332, -1,
+ 1333, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1319, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1320, -1,
- -1, 1321, -1, -1, -1, -1, -1, -1,
- -1, -1, 1322, 1323, 1324, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1325, -1, -1, -1, -1, 1326, -1,
- -1, -1, -1, -1, -1, 1327, -1, 1328,
- -1, -1, 1329, -1, 1330, 1331, 1332, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1333, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1334,
- -1, -1, 1335, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1334, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1335, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, 1336, -1, -1, -1, -1, -1,
- 1337, -1, -1, -1, -1, -1, -1, -1,
- 1338, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 1339, -1, -1, -1, -1,
- -1, -1, 1340, -1, -1, -1, 1341, -1,
- -1, -1, 1342, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1343, -1,
+ -1, -1, -1, -1, 1337, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1338, -1, -1,
+ -1, -1, 1339, -1, -1, 1340, -1, -1,
+ 1341, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1342, -1, -1,
+ -1, -1, -1, -1, 1343, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, 1344,
- 1345, -1, -1, -1, -1, -1, -1, -1,
- 1346, -1, -1, -1, -1, -1, 1347, -1,
+ -1, -1, -1, 1345, 1346, -1, -1, -1,
+ -1, 1347, -1, -1, -1, 1348, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1348, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 1349, -1, -1, -1,
- -1, 1350, -1, -1, -1, -1, -1, -1,
+ -1, 1349, 1350, -1, -1, -1, -1, -1,
+ -1, 1351, -1, -1, -1, 1352, 1353, -1,
+ -1, 1354, -1, -1, -1, -1, -1, -1,
+ -1, 1355, -1, 1356, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1351, -1, -1, 1352, -1, -1, 1353,
- -1, -1, -1, -1, 1354, -1, -1, 1355,
- -1, -1, -1, -1, -1, 1356, -1, -1,
- 1357, 1358, -1, -1, -1, -1, -1, -1,
- 1359, 1360, 1361, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1357, -1, -1, -1, -1, 1358, -1, -1,
+ -1, -1, -1, -1, -1, 1359, -1, -1,
+ -1, -1, -1, -1, 1360, -1, 1361, -1,
+ -1, -1, 1362, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1363, -1, -1, -1,
+ -1, -1, -1, 1364, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1365, -1, 1366, -1, -1, -1, -1,
+ 1367, -1, -1, -1, 1368, 1369, -1, -1,
+ 1370, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1371, -1, -1, -1, -1, -1,
+ 1372, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1373, -1, -1, -1, -1, 1374,
+ -1, -1, 1375, 1376, -1, -1, -1, 1377,
+ -1, -1, 1378, 1379, -1, -1, 1380, -1,
+ -1, -1, -1, -1, 1381, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1382, -1,
+ -1, 1383, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1384, -1, -1, 1385, -1,
+ -1, -1, -1, 1386, -1, -1, -1, -1,
+ -1, -1, -1, 1387, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 1362, -1, -1, -1, -1, -1, 1363, -1,
- -1, -1, 1364, -1, -1, -1, -1, -1,
- -1, 1365, -1, -1, -1, -1, -1, -1,
- -1, -1, 1366, -1, -1, -1, -1, -1,
- -1, -1, 1367, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1368, -1,
- 1369, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 1370, -1, -1, -1,
- 1371, -1, -1, -1, -1, -1, -1, -1,
- 1372, -1, -1, -1, -1, -1, 1373, -1,
- -1, -1, -1, -1, -1, -1, 1374, -1,
- -1, 1375, -1, -1, -1, -1, -1, -1,
- 1376, 1377, -1, -1, 1378, -1, -1, -1,
- -1, -1, -1, -1, 1379, -1, 1380, -1,
- -1, 1381, -1, -1, -1, -1, -1, -1,
- -1, -1, 1382, -1, -1, -1, 1383, 1384,
- -1, -1, 1385, 1386, -1, -1, -1, 1387,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1388, -1, -1, -1, -1, -1,
- 1389, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1390, -1, 1391, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1392, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1393, -1, -1, 1394, 1395, -1,
- -1, -1, -1, -1, -1, 1396, -1, -1,
- 1397, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 1398, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1399,
- -1, -1, -1, -1, -1, -1, -1, 1400,
+ -1, -1, 1388, 1389, -1, -1, -1, -1,
+ -1, -1, -1, 1390, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1391, -1, 1392,
+ -1, -1, 1393, -1, -1, -1, -1, 1394,
+ -1, -1, -1, -1, 1395, -1, -1, -1,
+ -1, -1, -1, 1396, -1, 1397, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1398, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1401,
- -1, -1, -1, -1, -1, 1402, 1403, 1404,
- -1, 1405, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 1406, -1, -1, -1, -1,
+ -1, -1, 1399, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1400, -1, -1, 1401, -1,
+ -1, -1, -1, 1402, -1, 1403, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1407, 1408,
- 1409, -1, -1, -1, -1, -1, -1, -1,
- 1410, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1411,
- -1, -1, -1, -1, -1, -1, 1412, -1,
- -1, -1, -1, -1, -1, 1413, -1, -1,
+ -1, -1, -1, 1404, -1, -1, -1, -1,
+ -1, -1, 1405, 1406, -1, -1, -1, 1407,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1414, -1, -1, -1, -1, 1415, 1416,
- -1, -1, -1, -1, 1417, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1408, -1, -1,
+ -1, -1, -1, 1409, -1, -1, -1, 1410,
+ -1, -1, 1411, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1412, 1413, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1414, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1418, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 1419, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1415, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1416, -1, -1, -1, -1, 1417,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1418, -1, -1, -1, -1, 1419,
-1, -1, -1, -1, 1420, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1421, 1422, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 1423, -1, 1424, 1425, -1, -1, -1, 1426,
- -1, -1, -1, 1427, -1, -1, -1, -1,
- 1428, -1, -1, -1, -1, -1, 1429, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 1430, 1431, 1432, -1, 1433, -1, -1, -1,
- 1434, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 1435, -1, 1436,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 1437, 1438, -1, -1, -1, 1439, -1, -1,
- -1, -1, -1, 1440, 1441, -1, -1, -1,
- -1, -1, 1442, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 1443, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 1444, -1, -1, -1, -1,
- -1, -1, 1445, -1, -1, 1446, 1447, -1,
- -1, -1, 1448, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1421, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1449,
+ -1, -1, -1, -1, 1422, 1423, 1424, -1,
+ -1, -1, -1, -1, -1, 1425, -1, -1,
+ -1, -1, -1, -1, 1426, -1, -1, -1,
+ -1, -1, -1, -1, 1427, -1, -1, -1,
+ -1, 1428, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1429,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 1450, -1, 1451, -1, -1,
- -1, -1, -1, -1, -1, -1, 1452, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1430, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1431, -1, 1432, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1433, 1434, -1, -1,
+ -1, 1435, -1, -1, -1, -1, 1436, -1,
+ -1, 1437, -1, -1, -1, 1438, -1, -1,
+ -1, -1, -1, 1439, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1440, -1, 1441, -1,
+ -1, 1442, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1443, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1444, -1, -1, 1445, -1, -1,
+ 1446, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1447, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1448, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1449, -1,
+ -1, -1, -1, -1, -1, -1, 1450, -1,
+ -1, -1, 1451, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1452, -1, -1,
-1, -1, -1, -1, 1453, -1, -1, -1,
- -1, 1454, -1, -1, 1455, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1456, -1, -1, -1, -1, 1457,
- 1458, -1, -1, -1, -1, -1, 1459, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1454, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1455, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 1460, 1461, -1, -1, 1462, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1463, 1464, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 1465, 1466, 1467, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 1468, -1, -1, -1, -1, 1469, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1456, -1, -1, -1,
+ -1, -1, 1457, -1, -1, -1, -1, -1,
+ -1, 1458, -1, 1459, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1460, -1, -1, -1,
+ -1, 1461, -1, -1, -1, 1462, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1463, -1, -1, -1, -1,
+ 1464, -1, -1, -1, -1, -1, -1, 1465,
+ -1, -1, -1, -1, -1, 1466, -1, -1,
+ -1, 1467, -1, 1468, -1, -1, 1469, -1,
-1, 1470, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1471, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1472, -1,
+ -1, -1, -1, -1, -1, 1473, -1, -1,
+ -1, -1, -1, -1, -1, 1474, -1, -1,
+ 1475, 1476, -1, -1, -1, 1477, -1, -1,
+ -1, 1478, -1, -1, -1, -1, -1, -1,
+ 1479, 1480, 1481, -1, -1, -1, -1, -1,
+ 1482, 1483, -1, 1484, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1485, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ 1486, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1487, -1,
+ 1488, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1489, 1490, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1471, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1491, -1, 1492, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1472, -1, -1, -1, -1, -1, 1473,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 1474, -1, -1, -1, 1475, -1, -1, -1,
- -1, -1, 1476, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 1477, -1, -1, -1,
- -1, 1478, -1, 1479, -1, -1, 1480, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 1481, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 1482, -1, -1,
- 1483, -1, -1, -1, -1, -1, -1, -1,
- 1484, 1485, -1, 1486, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 1487, -1, -1,
- -1, -1, -1, -1, -1, 1488, -1, 1489,
- -1, -1, -1, -1, 1490, -1, -1, 1491,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 1492, -1, -1, -1, -1,
- 1493, -1, -1, -1, -1, 1494, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 1495, -1, -1, 1496, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1497, -1, -1, -1, -1, -1,
- -1, -1, -1, 1498, 1499, -1, -1, 1500,
- -1, -1, -1, 1501, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 1502, -1, 1503,
- 1504, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1505, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1493, -1,
+ -1, -1, 1494, -1, 1495, -1, -1, 1496,
+ -1, -1, -1, 1497, -1, -1, -1, -1,
+ -1, -1, 1498, -1, -1, 1499, -1, 1500,
+ -1, -1, 1501, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1502, -1, -1, -1,
+ -1, -1, -1, -1, 1503, 1504, -1, 1505,
-1, -1, -1, -1, -1, -1, 1506, -1,
- 1507, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1508,
- -1, -1, -1, -1, -1, -1, -1, 1509,
- -1, -1, -1, -1, -1, -1, 1510, -1,
- -1, 1511, -1, -1, 1512, -1, -1, -1,
- -1, 1513, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 1514, -1, 1515,
- -1, -1, -1, -1, 1516, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1517, 1518,
- -1, -1, 1519, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1520, -1,
+ 1507, -1, -1, -1, -1, 1508, 1509, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1521, -1, -1, -1, -1, -1,
- -1, 1522, -1, -1, 1523, -1, -1, -1,
- -1, 1524, -1, 1525, -1, -1, -1, -1,
- -1, 1526, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 1527, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1528,
- -1, 1529, 1530, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 1531, -1, -1, -1,
+ 1510, -1, -1, -1, 1511, -1, -1, -1,
+ 1512, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1513, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1532, -1,
+ -1, -1, 1514, -1, -1, -1, -1, 1515,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 1533, -1, -1,
- 1534, -1, -1, -1, -1, -1, -1, 1535,
- -1, 1536, -1, -1, -1, -1, -1, -1,
- -1, 1537, -1, -1, -1, -1, -1, -1,
- 1538, -1, -1, -1, -1, -1, -1, 1539,
- -1, -1, -1, -1, 1540, 1541, -1, -1,
- -1, 1542, -1, -1, -1, -1, 1543, -1,
+ -1, -1, -1, -1, -1, 1516, -1, -1,
+ 1517, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1518, -1, -1, -1, 1519, -1,
+ 1520, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1544, -1,
+ -1, 1521, 1522, -1, -1, -1, -1, -1,
+ 1523, -1, -1, 1524, -1, -1, -1, 1525,
-1, -1, -1, -1, -1, -1, -1, -1,
+ 1526, -1, -1, -1, -1, 1527, -1, 1528,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1545, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1546, -1,
- -1, -1, -1, -1, -1, -1, -1, 1547,
- 1548, -1, -1, -1, 1549, -1, -1, -1,
- -1, 1550, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1529, -1, -1, -1, 1530, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1551,
+ 1531, 1532, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1552, -1, -1, -1, -1, -1, -1,
- -1, 1553, 1554, -1, -1, 1555, -1, 1556,
- -1, -1, 1557, -1, -1, -1, -1, 1558,
- 1559, -1, -1, 1560, -1, -1, 1561, -1,
- -1, -1, -1, -1, 1562, -1, -1, -1,
- 1563, -1, 1564, -1, -1, -1, -1, -1,
+ 1533, -1, -1, -1, -1, 1534, -1, -1,
+ -1, 1535, -1, 1536, 1537, -1, 1538, -1,
+ -1, -1, 1539, -1, -1, 1540, -1, -1,
+ -1, -1, -1, -1, 1541, 1542, -1, -1,
+ -1, 1543, -1, -1, -1, -1, -1, 1544,
+ -1, -1, 1545, -1, 1546, -1, -1, 1547,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1565, -1, -1, -1, 1566, -1,
- 1567, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 1568, -1, -1,
+ -1, 1548, -1, -1, 1549, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1550, -1, -1, 1551, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 1569, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 1570, -1, -1,
+ -1, -1, -1, -1, -1, 1552, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1553, 1554,
+ -1, -1, -1, -1, -1, -1, 1555, 1556,
+ -1, -1, -1, 1557, 1558, -1, -1, -1,
+ -1, -1, -1, 1559, -1, -1, -1, -1,
+ -1, -1, 1560, -1, 1561, 1562, -1, -1,
+ -1, -1, 1563, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1564, 1565, -1, 1566,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 1571, 1572, 1573, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1574,
+ -1, -1, -1, 1567, 1568, 1569, -1, -1,
+ -1, 1570, 1571, 1572, -1, -1, -1, -1,
+ 1573, -1, -1, -1, -1, -1, -1, 1574,
+ -1, -1, 1575, -1, 1576, -1, -1, 1577,
+ -1, -1, 1578, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ 1579, -1, 1580, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 1575, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1576, -1,
- -1, 1577, -1, -1, -1, -1, 1578, -1,
- -1, -1, -1, 1579, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1580, -1, -1, -1, -1, -1,
+ -1, -1, 1581, -1, -1, -1, 1582, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1583, 1584, 1585, -1, 1586, -1,
+ -1, 1587, -1, 1588, -1, 1589, -1, 1590,
+ 1591, -1, 1592, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1581,
- -1, -1, -1, -1, -1, 1582, -1, -1,
- -1, -1, 1583, -1, -1, -1, -1, 1584,
+ -1, 1593, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1585, -1, -1, -1, -1, 1586,
- 1587, 1588, -1, -1, 1589, -1, -1, -1,
- 1590, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 1591, -1, -1, -1,
+ -1, -1, -1, -1, 1594, -1, -1, 1595,
+ 1596, 1597, -1, -1, -1, -1, -1, 1598,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1592, -1, -1, -1, -1, -1,
- 1593, 1594, -1, -1, -1, -1, 1595, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1596, 1597,
- -1, -1, -1, 1598, -1, -1, -1, -1,
- 1599, -1, 1600, -1, -1, -1, -1, -1,
+ -1, 1599, -1, -1, 1600, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, 1601, -1, -1,
- -1, -1, -1, -1, -1, 1602, -1, -1,
- -1, -1, 1603, -1, -1, 1604, -1, -1,
+ -1, -1, -1, -1, 1602, -1, 1603, -1,
+ -1, -1, -1, -1, 1604, -1, -1, -1,
+ 1605, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1606, -1, 1607, -1, 1608, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1609,
+ -1, -1, -1, -1, 1610, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1611,
-1, -1, -1, -1, -1, -1, -1, -1,
- 1605, 1606, -1, -1, -1, -1, -1, 1607,
- -1, -1, -1, -1, -1, -1, 1608, -1,
+ -1, 1612, -1, -1, -1, -1, -1, -1,
+ 1613, -1, -1, 1614, -1, -1, -1, 1615,
-1, -1, -1, -1, -1, -1, -1, -1,
- 1609, -1, -1, -1, -1, -1, 1610, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 1611, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1612,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 1613, 1614, 1615, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1616, -1,
- -1, -1, -1, 1617, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1618, -1, -1, 1619, 1620, -1, -1,
- -1, -1, -1, 1621, -1, -1, -1, -1,
- -1, 1622, -1, 1623, -1, -1, -1, -1,
- -1, -1, 1624, -1, -1, 1625, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1626, -1, -1, -1, -1, -1,
- -1, 1627, -1, -1, -1, -1, 1628, -1,
- -1, -1, -1, -1, -1, 1629, -1, -1,
- -1, 1630, -1, -1, -1, -1, -1, -1,
- -1, 1631, -1, 1632, -1, -1, -1, -1,
+ -1, 1616, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1617, -1, 1618, 1619, 1620,
+ 1621, 1622, -1, -1, -1, -1, -1, -1,
+ 1623, -1, -1, 1624, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 1633, -1, -1, -1, -1,
- -1, -1, 1634, -1, -1, -1, -1, 1635,
- -1, -1, -1, 1636, -1, -1, -1, 1637,
- -1, -1, -1, -1, 1638, -1, -1, -1,
- 1639, -1, -1, -1, -1, 1640, -1, -1,
- -1, -1, 1641, -1, -1, -1, 1642, -1,
- -1, -1, -1, -1, 1643, 1644, 1645, -1,
- -1, -1, -1, -1, -1, -1, 1646, -1,
- -1, -1, -1, -1, 1647, -1, -1, -1,
- 1648, 1649, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 1650, -1, -1,
- -1, -1, 1651, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1652, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 1653, 1654, -1, -1, 1655, -1, -1, 1656,
- -1, -1, -1, -1, -1, -1, -1, 1657,
- -1, -1, -1, -1, 1658, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1659, -1, -1, -1, -1, -1,
+ 1625, 1626, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1627, -1, 1628, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1629, 1630,
+ -1, -1, -1, -1, -1, 1631, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 1660, -1, -1, -1, 1661, -1, 1662, -1,
- -1, -1, -1, -1, -1, -1, -1, 1663,
- 1664, -1, -1, 1665, -1, 1666, -1, -1,
- 1667, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1668, -1, -1, -1, 1669, -1,
- 1670, -1, -1, -1, -1, -1, -1, 1671,
- 1672, -1, -1, -1, -1, -1, -1, 1673,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1632, -1,
+ -1, -1, -1, -1, -1, 1633, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1634, -1, -1, -1, -1, -1, -1,
+ 1635, -1, -1, -1, -1, 1636, -1, 1637,
+ -1, -1, 1638, -1, -1, -1, 1639, 1640,
+ -1, 1641, 1642, 1643, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1644, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1645, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1646, -1, -1, -1, -1, -1, 1647, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1648, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1649, -1, -1, 1650, -1, -1, -1, -1,
+ -1, 1651, -1, -1, -1, -1, -1, 1652,
+ -1, -1, -1, -1, -1, -1, -1, 1653,
+ -1, -1, -1, -1, -1, -1, 1654, -1,
+ -1, -1, -1, -1, -1, -1, 1655, -1,
+ -1, -1, 1656, -1, -1, -1, -1, -1,
+ 1657, -1, -1, -1, 1658, -1, 1659, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1660, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1661, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1662,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1663, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 1674, -1, -1, 1675, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1676, -1,
- -1, -1, -1, -1, 1677, 1678, -1, -1,
+ -1, -1, -1, -1, 1664, -1, -1, -1,
+ -1, -1, 1665, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1666, -1, 1667,
+ 1668, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1669,
+ -1, -1, -1, -1, -1, -1, -1, 1670,
+ -1, -1, -1, 1671, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1679,
- -1, -1, 1680, 1681, -1, -1, -1, -1,
+ -1, -1, 1672, -1, -1, -1, -1, 1673,
+ -1, -1, 1674, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1675, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1676, -1, -1, -1, -1, -1, 1677, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1682, -1, 1683, -1, -1, -1, -1,
- -1, -1, 1684, -1, -1, -1, -1, -1,
- 1685, -1, -1, -1, 1686, -1, -1, 1687,
- -1, 1688, -1, -1, 1689, -1, -1, -1,
- 1690, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1691, -1,
- -1, -1, -1, -1, -1, 1692, -1, 1693,
+ -1, -1, 1678, 1679, -1, -1, 1680, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1694, -1, -1, -1, -1, -1,
- -1, 1695, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 1696, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1697,
+ -1, 1681, -1, -1, -1, -1, -1, -1,
+ 1682, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1698,
- -1, -1, -1, -1, 1699, -1, -1, 1700,
- -1, 1701, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 1702, -1, -1,
- -1, -1, -1, -1, 1703, -1, 1704, -1,
- 1705, 1706, -1, -1, -1, -1, -1, -1,
- -1, -1, 1707, -1, 1708, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1709, -1,
+ -1, 1683, -1, -1, -1, -1, 1684, -1,
+ -1, -1, -1, -1, -1, -1, 1685, -1,
+ -1, -1, -1, 1686, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1687, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1688, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1689,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1710, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1711,
+ -1, -1, -1, -1, 1690, -1, -1, -1,
+ -1, -1, 1691, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1712, -1,
- -1, 1713, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 1714, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1692,
+ 1693, -1, -1, -1, -1, -1, -1, -1,
+ 1694, 1695, -1, -1, -1, -1, 1696, -1,
+ -1, -1, 1697, 1698, -1, 1699, -1, -1,
+ 1700, 1701, -1, -1, 1702, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 1715, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1716,
- -1, -1, -1, -1, -1, -1, 1717, 1718,
- -1, -1, -1, -1, -1, -1, 1719, -1,
- -1, -1, -1, -1, -1, 1720, -1, -1,
- -1, 1721, -1, -1, -1, -1, 1722, -1,
- -1, -1, -1, -1, -1, 1723, -1, -1,
- 1724, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1703, -1, -1, -1, -1, -1,
+ 1704, 1705, 1706, -1, 1707, -1, -1, 1708,
+ -1, -1, -1, -1, -1, -1, 1709, 1710,
+ -1, -1, 1711, -1, -1, -1, 1712, -1,
+ 1713, -1, -1, -1, 1714, 1715, -1, 1716,
+ -1, -1, -1, -1, -1, -1, 1717, -1,
+ -1, -1, -1, 1718, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1719, -1, -1, 1720,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1721, 1722, -1, 1723, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1724,
-1, -1, -1, -1, -1, 1725, -1, -1,
- -1, -1, -1, -1, 1726, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1726, -1, 1727,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1727,
- -1, -1, 1728, 1729, -1, -1, -1, -1,
- -1, -1, 1730, 1731, 1732, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1728, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1729, -1, -1, -1,
+ 1730, -1, 1731, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1732, -1,
-1, -1, -1, -1, 1733, -1, -1, -1,
+ -1, 1734, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1734, -1, 1735, -1, -1, -1,
+ -1, -1, -1, 1735, 1736, -1, -1, -1,
+ -1, 1737, -1, 1738, -1, -1, -1, -1,
+ -1, -1, -1, 1739, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 1736, -1, -1, -1, 1737,
- 1738, -1, -1, -1, -1, -1, -1, 1739,
- -1, -1, -1, -1, 1740, 1741, -1, -1,
- -1, -1, 1742, -1, -1, -1, -1, -1,
- -1, 1743, -1, -1, 1744, -1, -1, -1,
- 1745, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 1746, -1, -1, 1747,
- -1, -1, -1, 1748, -1, -1, 1749, -1,
- -1, -1, -1, -1, -1, -1, 1750, -1,
+ -1, -1, -1, 1740, -1, -1, -1, -1,
+ -1, -1, 1741, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1742, -1, -1, 1743,
+ -1, -1, -1, -1, -1, -1, -1, 1744,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1751, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1752,
+ -1, -1, -1, -1, -1, -1, 1745, -1,
+ 1746, 1747, -1, 1748, -1, 1749, -1, -1,
+ -1, 1750, -1, 1751, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1752, 1753,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 1753, -1, 1754, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1754, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1755, -1, -1, -1, -1, -1, -1,
- -1, -1, 1756, -1, 1757, -1, -1, -1,
- -1, 1758, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1759,
+ 1755, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1756, -1, -1, -1, -1,
+ -1, 1757, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1758,
+ -1, -1, -1, 1759, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1760, -1, -1, 1761,
+ -1, -1, 1762, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1763, -1, -1, -1,
+ -1, -1, -1, -1, 1764, 1765, -1, -1,
+ -1, -1, 1766, -1, -1, -1, -1, -1,
+ -1, -1, 1767, -1, 1768, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1769, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 1760, -1, -1, 1761, 1762,
- -1, -1, -1, -1, -1, 1763, -1, -1,
- -1, 1764, -1, -1, -1, 1765, -1, -1,
- -1, -1, -1, 1766, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1767, -1,
- -1, -1, -1, -1, -1, -1, 1768, 1769,
- -1, 1770, -1, 1771, -1, -1, -1, -1,
+ 1770, -1, 1771, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1772, -1, -1, -1, -1, -1, -1,
- -1, -1, 1773, -1, -1, -1, -1, -1,
- -1, -1, 1774, -1, -1, 1775, 1776, -1,
- -1, -1, 1777, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1772, 1773, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1774,
+ -1, -1, -1, -1, -1, -1, 1775, -1,
+ -1, -1, -1, -1, 1776, -1, -1, 1777,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1778, -1, 1779, -1, -1, -1,
+ -1, -1, -1, 1778, -1, -1, -1, 1779,
+ -1, -1, -1, -1, -1, -1, 1780, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 1780, -1, -1,
- -1, -1, 1781, -1, -1, -1, -1, -1,
- -1, 1782, -1, 1783, 1784, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 1785, -1, -1,
- 1786, -1, -1, -1, -1, 1787, -1, -1,
+ -1, -1, 1781, -1, 1782, 1783, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1788, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1789, -1, -1, -1, 1790, -1,
- -1, -1, 1791, -1, -1, -1, -1, -1,
- -1, 1792, -1, -1, -1, 1793, -1, -1,
+ -1, -1, 1784, -1, -1, -1, 1785, -1,
+ 1786, -1, -1, -1, -1, 1787, 1788, -1,
+ 1789, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1790, -1, 1791, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1792, -1, -1,
+ -1, -1, -1, 1793, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, 1794,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 1795, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1795, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, 1796, -1, -1, -1,
- 1797, -1, -1, -1, -1, -1, 1798, -1,
- -1, -1, 1799, -1, -1, -1, -1, -1,
+ 1797, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1798, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1799, -1, -1,
+ -1, -1, -1, -1, -1, 1800, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 1800, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1801, 1802,
- -1, -1, -1, 1803, -1, -1, -1, -1,
- 1804, -1, -1, -1, -1, 1805, -1, -1,
- -1, 1806, 1807, -1, 1808, 1809, -1, -1,
- -1, -1, -1, -1, -1, -1, 1810, -1,
- -1, -1, 1811, -1, 1812, 1813, 1814, -1,
- -1, 1815, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1816, -1, -1, -1, -1, -1, -1,
+ 1801, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1802, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1817, -1,
- 1818, -1, -1, 1819, -1, -1, -1, -1,
- -1, -1, 1820, -1, -1, -1, -1, -1,
+ 1803, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1804, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1805, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1806, -1, -1, -1, -1, 1807,
+ -1, -1, -1, -1, -1, -1, 1808, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1809, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1810, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1811, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1812, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1813, -1, -1, -1, -1, 1814, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1815,
+ -1, 1816, -1, -1, -1, -1, -1, 1817,
+ -1, -1, -1, -1, -1, 1818, 1819, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1820,
-1, -1, -1, -1, 1821, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1822, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1822, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1823, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 1823, -1, -1, -1, -1,
- -1, -1, -1, 1824, -1, 1825, -1, 1826,
- -1, -1, 1827, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1828, -1,
- -1, -1, -1, -1, 1829, 1830, -1, -1,
- 1831, -1, -1, -1, -1, -1, -1, 1832,
- -1, -1, -1, -1, 1833, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1824, -1,
+ -1, -1, -1, -1, 1825, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1834,
+ -1, -1, -1, -1, -1, -1, -1, 1826,
+ -1, 1827, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1828, -1, -1, -1, -1,
+ -1, 1829, -1, -1, 1830, -1, -1, 1831,
+ -1, -1, -1, -1, -1, -1, 1832, -1,
+ -1, 1833, -1, -1, 1834, -1, -1, -1,
+ -1, -1, 1835, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1835, -1,
- 1836, -1, -1, 1837, -1, -1, -1, -1,
- -1, -1, 1838, 1839, -1, -1, -1, -1,
- -1, -1, -1, 1840, -1, -1, -1, -1,
- 1841, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1842, -1, -1, -1, 1843, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 1844, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1845, -1,
- -1, -1, -1, -1, 1846, -1, -1, -1,
- -1, -1, -1, 1847, -1, -1, -1, -1,
- -1, -1, -1, -1, 1848, -1, -1, -1,
- -1, -1, -1, -1, 1849, -1, 1850, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 1851, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1836, -1, -1, 1837,
+ -1, -1, -1, -1, -1, 1838, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1839, -1,
+ -1, -1, 1840, -1, 1841, -1, -1, -1,
+ -1, -1, 1842, -1, -1, -1, -1, -1,
+ 1843, -1, -1, 1844, -1, -1, -1, 1845,
+ -1, -1, -1, -1, -1, 1846, -1, -1,
+ -1, -1, -1, 1847, -1, 1848, -1, -1,
+ 1849, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1850, 1851, -1,
-1, -1, 1852, -1, -1, -1, -1, -1,
- -1, -1, 1853, -1, -1, -1, -1, -1,
- -1, -1, 1854, -1, -1, 1855, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1853, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1856, -1, -1, -1, -1, -1, -1,
+ 1854, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1855, 1856, -1, -1, -1,
+ -1, -1, -1, 1857, -1, 1858, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 1857, -1, -1, -1, -1,
- -1, -1, -1, 1858, -1, 1859, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1859, -1,
-1, 1860, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 1861, -1, 1862, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 1863, -1, -1, -1,
- 1864, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1865, -1, 1866, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 1867, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 1868, -1, -1, 1869, -1, 1870, -1, 1871,
- -1, 1872, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1873, -1,
- -1, 1874, -1, -1, -1, -1, -1, 1875,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 1876, 1877, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 1878, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 1879, -1, 1880,
+ -1, -1, -1, -1, 1861, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1862, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1863, 1864, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1865, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1866, -1, -1, 1867, -1, -1, -1, 1868,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1869, -1, -1, 1870, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1871, -1, -1, -1, 1872, -1, -1,
+ -1, -1, 1873, -1, 1874, -1, -1, -1,
+ -1, -1, -1, 1875, 1876, -1, -1, -1,
+ -1, -1, -1, 1877, -1, -1, 1878, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1879,
+ -1, 1880, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, 1881, -1, -1, -1,
- -1, -1, 1882, -1, 1883, 1884, -1, -1,
+ -1, -1, -1, -1, -1, 1882, 1883, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1884, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1885, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1886, -1, -1,
+ -1, -1, -1, -1, 1887, -1, -1, -1,
+ -1, 1888, -1, -1, -1, -1, -1, -1,
+ -1, 1889, -1, -1, -1, -1, -1, -1,
+ -1, 1890, -1, 1891, 1892, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1893, -1,
+ -1, -1, -1, -1, 1894, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 1885, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1895, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1896, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1886, -1, -1, -1, -1, -1,
+ 1897, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 1887, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 1888, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1889,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 1890, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 1891, -1, 1892, -1,
- -1, -1, -1, -1, -1, 1893, -1, -1,
- -1, -1, -1, -1, 1894, -1, 1895, -1,
- -1, 1896, -1, -1, -1, -1, 1897, -1,
-1, -1, -1, -1, -1, -1, -1, 1898,
+ -1, 1899, -1, -1, -1, -1, -1, -1,
+ 1900, -1, -1, -1, -1, -1, -1, 1901,
+ -1, -1, -1, -1, -1, -1, -1, 1902,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1899, -1, 1900, 1901, -1, -1, -1,
+ 1903, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1904,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1902, -1, -1, -1, -1, -1, -1,
- -1, -1, 1903, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1904, 1905,
+ -1, -1, 1905, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1906, -1, -1, -1,
+ 1907, -1, -1, 1908, 1909, -1, -1, -1,
+ -1, 1910, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1911, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1912, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1913, -1, -1,
+ -1, -1, 1914, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1915, -1, -1,
+ 1916, 1917, -1, -1, -1, -1, 1918, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1919,
+ -1, -1, -1, -1, 1920, 1921, -1, -1,
+ -1, 1922, -1, -1, -1, 1923, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1924, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1906, -1,
- -1, -1, -1, -1, 1907, -1, 1908, -1,
- -1, -1, -1, 1909, -1, -1, -1, -1,
- 1910, -1, 1911, -1, -1, -1, 1912, -1,
+ -1, -1, 1925, -1, -1, -1, -1, 1926,
+ -1, -1, -1, 1927, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1913,
- -1, -1, -1, 1914, -1, -1, -1, 1915,
+ 1928, -1, -1, -1, -1, 1929, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1916, -1, -1, -1, -1, -1,
- -1, 1917, -1, 1918, -1, -1, -1, 1919,
+ -1, -1, -1, -1, -1, -1, 1930, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1920, -1,
+ -1, -1, -1, 1931, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1932, -1,
+ -1, 1933, -1, -1, -1, -1, -1, -1,
+ 1934, -1, -1, -1, -1, -1, 1935, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1936, 1937, -1, -1, -1, -1, -1,
+ -1, -1, 1938, -1, -1, -1, -1, 1939,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1940, 1941, -1, -1,
+ -1, 1942, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1943, -1, 1944, -1,
+ -1, 1945, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 1921, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 1922, -1, 1923, -1, 1924, 1925, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1926, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 1927, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1928, -1, 1929, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1930, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 1931, -1, 1932, -1,
- -1, -1, -1, -1, -1, -1, 1933, -1,
- -1, -1, -1, -1, -1, -1, -1, 1934,
- -1, -1, 1935, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1936, -1, -1, 1937, -1, -1, -1,
- -1, 1938, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 1939, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1940, -1, -1, -1, -1, 1941, -1,
- -1, -1, -1, -1, 1942, -1, 1943, -1,
- -1, -1, 1944, -1, -1, -1, -1, -1,
- -1, -1, -1, 1945, -1, -1, -1, 1946,
- -1, -1, -1, -1, -1, -1, -1, 1947,
- -1, -1, 1948, -1, -1, 1949, -1, -1,
- -1, -1, -1, -1, 1950, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 1951,
- -1, -1, -1, -1, -1, -1, -1, 1952,
- 1953, -1, -1, -1, -1, -1, 1954, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1946, -1, -1, -1,
+ 1947, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1948, -1,
+ -1, 1949, -1, 1950, -1, -1, -1, 1951,
+ -1, -1, -1, 1952, -1, -1, -1, -1,
+ 1953, -1, 1954, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, 1955, 1956, -1,
- -1, -1, -1, 1957, -1, 1958, -1, 1959,
- -1, -1, 1960, -1, -1, 1961, 1962, -1,
- 1963, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 1964, -1, -1, 1965, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 1966, 1967, -1, -1,
- -1, -1, -1, -1, 1968, 1969, 1970, -1,
- 1971, 1972, 1973, -1, -1, -1, -1, -1,
- -1, -1, -1, 1974, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 1975, -1,
- -1, 1976, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 1977, 1978, -1, -1, -1,
- 1979, -1, -1, 1980, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 1981, -1, -1, -1, -1, 1982, 1983, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 1984, -1, 1985, -1, -1, -1, 1986, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 1987, -1, -1, -1, -1, -1,
- -1, -1, -1, 1988, 1989, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1990, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 1991, -1, 1992, -1, -1, -1, -1, -1,
- -1, -1, -1, 1993, 1994, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 1995, -1, -1, 1996, -1, -1, 1997,
- -1, 1998, -1, -1, -1, 1999, 2000, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2001, -1, -1, -1, -1, -1,
- -1, 2002, -1, -1, 2003, -1, -1, 2004,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2005, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2006, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 2007,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2008, -1, -1, 2009, -1, -1, -1,
- -1, 2010, -1, -1, -1, -1, -1, 2011,
- -1, -1, -1, 2012, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2013, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 2014,
- -1, 2015, -1, -1, 2016, -1, -1, -1,
- -1, 2017, -1, -1, -1, 2018, -1, -1,
- -1, -1, -1, 2019, 2020, -1, -1, 2021,
- -1, -1, -1, -1, -1, -1, 2022, 2023,
- -1, -1, -1, 2024, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 2025,
- -1, 2026, 2027, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2028, -1, 2029, 2030,
- -1, -1, -1, -1, 2031, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2032, -1, -1, -1, -1, -1,
- -1, -1, -1, 2033, -1, -1, -1, -1,
- -1, -1, -1, -1, 2034, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 2035,
- -1, 2036, -1, 2037, -1, -1, -1, -1,
- -1, -1, -1, 2038, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2039, 2040,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 2041, 2042, 2043, 2044, -1, -1, -1, -1,
- -1, -1, -1, 2045, -1, -1, -1, 2046,
- 2047, -1, -1, -1, -1, -1, -1, 2048,
- -1, 2049, -1, -1, -1, -1, -1, -1,
- -1, -1, 2050, -1, -1, -1, 2051, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 2052, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2053, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2054, -1, -1, 2055, -1, -1, 2056,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2057, -1, -1, -1, -1,
- -1, -1, -1, -1, 2058, -1, -1, -1,
- -1, 2059, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2060, -1, -1, -1,
- -1, -1, 2061, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2062, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2063, -1, -1, -1,
- -1, -1, -1, -1, 2064, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1957,
+ 1958, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 2065, -1, -1,
- -1, -1, -1, -1, 2066, -1, -1, -1,
- -1, -1, 2067, -1, -1, -1, -1, -1,
- 2068, -1, 2069, 2070, -1, -1, -1, -1,
- -1, -1, -1, 2071, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2072, -1, -1, -1, 2073, -1, 2074,
- -1, -1, -1, -1, 2075, -1, -1, -1,
- -1, -1, -1, -1, -1, 2076, -1, -1,
- -1, 2077, 2078, 2079, -1, -1, -1, -1,
- -1, 2080, -1, -1, -1, -1, 2081, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 2082, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2083, -1, -1, -1, -1,
- -1, 2084, -1, -1, -1, -1, -1, 2085,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2086, -1, -1, -1,
- -1, -1, 2087, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1959, -1, -1, -1, -1, -1, -1,
+ 1960, 1961, 1962, 1963, -1, -1, 1964, -1,
+ -1, -1, -1, -1, 1965, 1966, -1, -1,
+ -1, -1, 1967, -1, -1, -1, -1, -1,
+ -1, -1, 1968, -1, 1969, -1, -1, -1,
+ -1, -1, -1, 1970, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1971, -1, -1, -1, -1, 1972, -1, -1,
+ -1, 1973, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1974, 1975, -1, -1, 1976, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1977, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1978, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1979,
+ -1, -1, -1, -1, 1980, -1, -1, 1981,
+ -1, -1, 1982, -1, -1, -1, -1, -1,
+ 1983, -1, -1, -1, 1984, -1, -1, -1,
+ -1, -1, -1, -1, 1985, -1, -1, -1,
+ 1986, -1, -1, -1, 1987, -1, -1, -1,
+ -1, -1, -1, -1, 1988, -1, -1, 1989,
+ -1, -1, -1, -1, 1990, -1, 1991, -1,
+ 1992, -1, 1993, -1, -1, -1, -1, 1994,
+ 1995, 1996, -1, -1, -1, 1997, -1, -1,
+ 1998, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1999, -1, -1, -1,
+ -1, -1, -1, -1, 2000, -1, -1, 2001,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2002, -1, -1, -1, -1, -1, -1,
+ 2003, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2004,
+ -1, -1, 2005, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2006, -1, -1,
+ -1, 2007, -1, 2008, 2009, -1, -1, 2010,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2011, -1, 2012, -1, -1, 2013,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2014, -1, -1, -1, -1,
+ -1, -1, 2015, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2016, -1, -1, -1, 2017, -1, -1,
+ -1, -1, -1, -1, 2018, -1, -1, 2019,
+ -1, -1, -1, -1, 2020, -1, -1, -1,
+ -1, -1, -1, -1, 2021, -1, -1, -1,
+ -1, -1, -1, 2022, 2023, -1, -1, -1,
+ -1, 2024, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2025, -1, 2026, -1, -1, -1,
+ -1, -1, 2027, -1, -1, -1, 2028, -1,
+ -1, -1, 2029, -1, -1, 2030, 2031, 2032,
+ -1, -1, -1, -1, -1, -1, -1, 2033,
+ -1, -1, -1, -1, -1, 2034, -1, -1,
+ -1, -1, 2035, -1, -1, -1, -1, -1,
+ -1, 2036, -1, -1, -1, -1, 2037, -1,
+ -1, -1, -1, -1, -1, -1, 2038, -1,
+ -1, -1, -1, -1, -1, -1, 2039, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2040, -1, 2041, -1, -1, -1, -1,
+ 2042, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2043, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2044,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2045, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2046, 2047, -1, -1, -1, 2048, -1,
+ -1, -1, -1, -1, -1, 2049, -1, 2050,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2051, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2052, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2053, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2054, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2055,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2056, -1,
+ -1, -1, 2057, 2058, -1, 2059, -1, -1,
+ -1, 2060, 2061, -1, -1, 2062, -1, -1,
+ -1, -1, 2063, -1, -1, 2064, -1, -1,
+ -1, -1, -1, -1, 2065, -1, -1, -1,
+ 2066, 2067, -1, -1, -1, -1, -1, 2068,
+ -1, -1, -1, 2069, 2070, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2071, -1, -1,
+ 2072, -1, -1, -1, 2073, -1, -1, -1,
+ -1, 2074, 2075, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2076, -1, -1, 2077, -1, 2078, -1,
+ -1, 2079, -1, -1, 2080, -1, -1, -1,
+ -1, 2081, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2082,
+ 2083, -1, -1, -1, -1, -1, -1, -1,
+ 2084, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2085, -1,
+ -1, -1, -1, -1, -1, 2086, -1, 2087,
-1, -1, -1, -1, 2088, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2089,
-1, -1, -1, -1, -1, -1, -1, -1,
- 2089, -1, -1, -1, -1, -1, -1, 2090,
- -1, -1, -1, -1, -1, -1, -1, 2091,
- 2092, -1, -1, 2093, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2094, -1,
+ 2090, -1, -1, 2091, -1, 2092, -1, 2093,
+ -1, -1, -1, -1, -1, -1, -1, 2094,
+ -1, -1, -1, -1, 2095, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2096, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 2095, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2096, -1, -1, 2097, -1,
- 2098, -1, 2099, -1, 2100, -1, -1, 2101,
- 2102, -1, -1, -1, -1, -1, -1, -1,
- -1, 2103, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2104, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 2105, -1, -1,
+ -1, -1, -1, 2097, 2098, -1, -1, -1,
+ -1, -1, 2099, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2100,
+ -1, 2101, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2102, -1, 2103, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2104,
+ 2105, -1, -1, 2106, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2106, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2107, -1, -1, -1,
- -1, -1, 2108, -1, -1, -1, 2109, -1,
- -1, 2110, -1, 2111, -1, -1, 2112, 2113,
- -1, -1, -1, 2114, -1, -1, 2115, -1,
- -1, -1, 2116, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2117, -1, -1, -1,
- -1, -1, 2118, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2119, 2120, -1, 2121,
- -1, -1, -1, 2122, -1, -1, -1, -1,
- -1, -1, -1, 2123, -1, -1, -1, -1,
- -1, -1, -1, 2124, -1, 2125, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 2126,
- -1, -1, -1, -1, -1, 2127, -1, -1,
+ -1, -1, -1, -1, 2107, -1, 2108, -1,
+ 2109, -1, -1, -1, -1, 2110, -1, -1,
+ -1, -1, -1, -1, -1, 2111, -1, -1,
+ -1, -1, 2112, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2113, -1,
+ 2114, -1, 2115, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2116, -1, -1, 2117, 2118,
+ -1, -1, 2119, -1, -1, -1, -1, -1,
+ 2120, -1, -1, 2121, -1, -1, -1, -1,
+ 2122, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2123, 2124, -1, -1, -1, -1, -1,
+ -1, -1, 2125, -1, 2126, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2128, -1, 2129, -1, -1, -1,
- -1, -1, -1, 2130, -1, -1, -1, -1,
- -1, -1, -1, -1, 2131, -1, -1, -1,
- -1, -1, -1, -1, -1, 2132, -1, -1,
- -1, 2133, -1, 2134, 2135, -1, -1, -1,
- -1, 2136, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2127, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2128, -1, -1, -1, 2129, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2130, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2131,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2132, -1, -1, -1, -1, -1, -1, -1,
+ 2133, -1, -1, 2134, -1, -1, -1, 2135,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2136, -1, -1, -1, -1,
-1, -1, -1, 2137, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 2138,
- -1, -1, -1, -1, -1, -1, 2139, -1,
- -1, 2140, -1, -1, -1, -1, -1, -1,
- -1, -1, 2141, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2142, -1, -1, -1, -1,
- -1, 2143, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2138, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2139,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2140, -1, -1, 2141, -1, -1, 2142, -1,
+ 2143, -1, 2144, -1, -1, -1, 2145, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2146, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2147, -1, -1,
+ -1, -1, -1, -1, 2148, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2149, -1, 2150, -1, -1, -1, -1,
+ -1, -1, 2151, -1, -1, -1, -1, 2152,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2153, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2154, -1,
+ -1, 2155, -1, -1, -1, -1, -1, -1,
+ -1, 2156, -1, -1, -1, -1, 2157, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2158, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2144, 2145, 2146, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2147, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2159,
+ -1, 2160, 2161, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2148, -1, -1, -1, -1,
- 2149, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2162, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2163, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2164, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2165, -1, -1, -1, 2166, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2167, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2168, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2169, 2170, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2150, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2171, -1,
+ -1, -1, -1, -1, 2172, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2173, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2151, 2152, -1, 2153, -1,
- -1, -1, -1, 2154, -1, -1, -1, -1,
- -1, -1, -1, 2155, -1, -1, -1, -1,
- -1, -1, 2156, 2157, -1, 2158, -1, 2159,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2160, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2161, -1,
- -1, -1, -1, -1, -1, 2162, 2163, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2164, 2165, -1, -1, -1,
- -1, 2166, -1, 2167, -1, -1, 2168, -1,
- -1, 2169, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2170, -1, 2171, 2172, -1, -1, 2173,
- -1, -1, -1, -1, -1, 2174, 2175, -1,
+ 2174, -1, -1, -1, 2175, -1, -1, -1,
+ 2176, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2177, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2178, -1, -1, -1, 2179,
+ 2180, -1, -1, -1, 2181, -1, -1, -1,
+ -1, 2182, -1, -1, -1, -1, -1, -1,
+ 2183, -1, -1, -1, -1, 2184, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2185, -1,
+ -1, -1, 2186, -1, 2187, -1, 2188, -1,
+ -1, -1, -1, 2189, -1, -1, -1, -1,
+ 2190, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2176, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 2177,
- 2178, -1, -1, 2179, -1, 2180, -1, 2181,
- -1, -1, 2182, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2183, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2184, -1, -1, -1, 2185, -1, -1,
- 2186, -1, -1, -1, -1, -1, -1, -1,
- -1, 2187, 2188, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2191, -1, -1, -1,
+ -1, -1, -1, -1, 2192, -1, -1, 2193,
+ 2194, -1, -1, -1, 2195, -1, -1, -1,
+ -1, -1, -1, -1, 2196, -1, 2197, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2189, -1, -1, 2190, 2191, -1, -1,
+ -1, -1, 2198, -1, -1, -1, -1, -1,
+ 2199, -1, -1, 2200, -1, 2201, -1, -1,
+ 2202, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2203, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2204, -1,
+ -1, -1, -1, -1, -1, -1, 2205, -1,
+ -1, 2206, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2207, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2208, -1, 2209, -1, -1, 2210, 2211, -1,
+ -1, 2212, 2213, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2192, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2193, -1,
+ -1, -1, 2214, -1, -1, -1, -1, 2215,
+ -1, -1, -1, 2216, -1, -1, -1, -1,
+ 2217, -1, -1, -1, -1, -1, 2218, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2219,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2220, -1, 2221, 2222, -1, 2223,
+ -1, 2224, 2225, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2194, -1, -1, -1,
- 2195, -1, -1, -1, -1, -1, -1, -1,
- 2196, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2197, -1, -1, -1, -1, -1,
- -1, -1, -1, 2198, -1, -1, 2199, -1,
- -1, -1, 2200, -1, -1, -1, -1, -1,
+ 2226, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2227,
+ -1, 2228, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2229, -1, 2230, 2231, -1, -1,
+ -1, 2232, -1, -1, 2233, -1, -1, -1,
+ -1, -1, -1, -1, 2234, -1, -1, -1,
+ -1, 2235, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2236, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2201, 2202,
- -1, -1, 2203, 2204, -1, -1, 2205, -1,
+ -1, -1, -1, -1, 2237, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 2206, -1, -1, -1, -1, -1, 2207, -1,
- -1, -1, -1, -1, 2208, -1, -1, -1,
- 2209, -1, -1, -1, 2210, 2211, -1, 2212,
- -1, -1, -1, 2213, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2214, -1,
- -1, -1, -1, -1, -1, 2215, 2216, 2217,
- -1, -1, -1, -1, 2218, -1, -1, -1,
- 2219, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2220, -1,
- -1, -1, 2221, -1, -1, -1, 2222, -1,
+ -1, -1, -1, -1, -1, 2238, -1, 2239,
+ -1, -1, -1, -1, 2240, -1, -1, 2241,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2223, -1, -1, 2224, -1, 2225,
- 2226, 2227, -1, -1, -1, -1, -1, -1,
- -1, 2228, -1, -1, -1, -1, -1, 2229,
+ -1, -1, 2242, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 2230,
- -1, 2231, -1, -1, -1, -1, -1, 2232,
- -1, -1, 2233, -1, -1, -1, -1, -1,
- -1, -1, 2234, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2235, -1, 2236, -1, -1, 2237, -1,
- -1, 2238, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 2239, -1, -1,
- -1, -1, -1, -1, 2240, -1, 2241, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 2242, 2243, -1, -1, -1, -1, -1, -1,
- -1, -1, 2244, -1, 2245, -1, -1, -1,
+ -1, -1, -1, 2243, -1, -1, -1, -1,
+ 2244, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2245, -1,
-1, 2246, -1, -1, -1, -1, -1, -1,
- -1, -1, 2247, -1, -1, -1, -1, -1,
- -1, 2248, -1, -1, -1, -1, -1, -1,
- -1, 2249, -1, 2250, 2251, -1, 2252, -1,
- 2253, 2254, -1, -1, -1, -1, 2255, -1,
- -1, -1, 2256, -1, 2257, -1, -1, -1,
- -1, -1, 2258, -1, 2259, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2260, -1, 2261, -1,
- -1, -1, -1, -1, -1, -1, -1, 2262,
- -1, -1, -1, -1, -1, -1, 2263, -1,
- -1, 2264, -1, -1, -1, 2265, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 2266,
+ -1, -1, -1, -1, 2247, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2248, -1, -1,
+ 2249, -1, -1, -1, -1, -1, -1, 2250,
-1, -1, -1, -1, -1, -1, -1, -1,
- 2267, 2268, 2269, -1, -1, -1, -1, 2270,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 2271, 2272, -1,
- -1, -1, -1, -1, -1, 2273, -1, -1,
- -1, 2274, -1, -1, -1, -1, -1, -1,
- -1, 2275, -1, -1, -1, -1, -1, -1,
+ -1, 2251, 2252, -1, -1, 2253, -1, -1,
+ 2254, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2255,
+ -1, -1, -1, 2256, -1, -1, 2257, 2258,
+ -1, 2259, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2260,
+ -1, -1, -1, 2261, -1, 2262, 2263, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2264,
+ 2265, -1, 2266, -1, 2267, -1, -1, 2268,
+ -1, -1, -1, -1, 2269, -1, -1, -1,
+ 2270, -1, 2271, -1, -1, -1, -1, -1,
+ -1, -1, 2272, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2273,
+ 2274, -1, 2275, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, 2276, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2277,
+ 2278, -1, -1, -1, 2279, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2277, -1, -1, 2278, -1,
- 2279, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2280, 2281, -1, -1, -1,
+ -1, -1, -1, 2280, -1, 2281, 2282, -1,
+ 2283, -1, 2284, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2285, -1, 2286, 2287, -1,
+ -1, -1, 2288, 2289, -1, -1, -1, -1,
+ 2290, 2291, 2292, 2293, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2294, 2295, -1,
+ 2296, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2297, 2298, -1, -1, -1,
+ -1, -1, -1, 2299, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2282, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2283, -1, -1, -1,
- -1, -1, 2284, -1, -1, -1, -1, -1,
- 2285, -1, -1, -1, -1, -1, 2286, -1,
+ -1, -1, -1, -1, -1, -1, 2300, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2287, -1,
- 2288, -1, -1, -1, -1, -1, 2289, -1,
+ -1, -1, -1, -1, 2301, -1, -1, -1,
+ 2302, 2303, -1, -1, -1, -1, 2304, 2305,
+ -1, -1, -1, -1, 2306, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2307,
+ -1, -1, 2308, -1, -1, -1, 2309, 2310,
+ 2311, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2312, -1, -1, -1,
+ -1, -1, 2313, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2314,
+ -1, -1, -1, -1, -1, -1, -1, 2315,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2290, -1, -1, -1, -1, -1, 2291,
- 2292, -1, 2293, -1, -1, -1, -1, -1,
- -1, -1, 2294, 2295, -1, -1, 2296, -1,
- -1, 2297, -1, -1, 2298, -1, -1, 2299,
+ -1, 2316, -1, -1, -1, -1, -1, -1,
+ -1, 2317, -1, 2318, 2319, -1, 2320, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 2300, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2301, -1,
- -1, -1, -1, -1, -1, 2302, -1, -1,
- -1, -1, 2303, 2304, -1, -1, 2305, -1,
- -1, -1, 2306, -1, -1, -1, 2307, 2308,
- 2309, -1, -1, -1, -1, 2310, 2311, 2312,
- -1, -1, 2313, -1, -1, 2314, -1, 2315,
+ -1, -1, -1, -1, -1, -1, 2321, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2322, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2323, -1, -1, -1, 2324, -1, -1, -1,
+ -1, 2325, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2326, 2327, -1, -1,
+ -1, -1, -1, -1, 2328, -1, -1, -1,
+ -1, 2329, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2330, -1, 2331, -1, -1, -1,
+ 2332, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2333, -1, 2334, 2335, -1, 2336,
+ -1, -1, -1, -1, -1, -1, 2337, -1,
+ -1, -1, 2338, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2339, -1, -1, -1, -1, -1, -1,
+ -1, 2340, 2341, 2342, -1, -1, -1, -1,
+ 2343, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2344, -1, -1, -1, -1, 2345, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2346, 2347, -1, 2348, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2349, -1, -1,
+ -1, 2350, 2351, -1, 2352, -1, -1, -1,
+ -1, -1, -1, -1, 2353, -1, 2354, -1,
+ 2355, 2356, 2357, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2358, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2359, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2360, 2361, -1, -1, 2362, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2363,
+ -1, -1, -1, -1, -1, -1, -1, 2364,
+ -1, -1, -1, -1, 2365, -1, -1, 2366,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 2316,
+ 2367, -1, -1, -1, -1, 2368, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2369,
+ -1, -1, 2370, -1, -1, -1, -1, -1,
+ 2371, -1, -1, -1, -1, 2372, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 2317,
- -1, -1, -1, -1, -1, -1, -1, 2318,
+ -1, -1, 2373, -1, -1, -1, -1, 2374,
+ -1, -1, 2375, -1, -1, 2376, -1, 2377,
+ 2378, -1, 2379, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2380, -1, -1,
+ -1, -1, -1, 2381, -1, -1, -1, 2382,
+ -1, -1, -1, 2383, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2384, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2385,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2386, 2387, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 2319, -1, -1,
- -1, -1, 2320, 2321, -1, -1, -1, 2322,
- -1, 2323, 2324, 2325, 2326, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2388,
+ -1, -1, -1, -1, 2389, -1, 2390, -1,
+ -1, -1, -1, -1, 2391, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 2327, -1, -1, -1, 2328, 2329, -1, 2330,
- 2331, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 2332, 2333, -1, -1, 2334, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2335, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2336, -1, 2337, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2338, -1, -1, -1, -1, 2339, 2340,
- -1, -1, -1, 2341, 2342, -1, -1, -1,
- -1, -1, -1, -1, 2343, -1, -1, 2344,
- 2345, 2346, -1, -1, -1, -1, -1, -1,
- 2347, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2348, -1, -1, -1, -1, 2349,
- -1, -1, -1, -1, 2350, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2351, -1, -1, -1, -1, -1, -1,
- -1, -1, 2352, -1, -1, -1, -1, -1,
- 2353, -1, -1, 2354, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2355, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2356, -1, -1, -1, 2357, -1, -1,
- -1, -1, -1, -1, 2358, 2359, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2360, -1,
- -1, -1, -1, -1, -1, 2361, 2362, -1,
- -1, -1, -1, -1, 2363, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 2364, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2365, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2366, 2367, -1, -1,
- -1, -1, -1, -1, 2368, 2369, -1, -1,
- -1, -1, -1, 2370, -1, -1, -1, -1,
- -1, -1, -1, -1, 2371, -1, -1, -1,
- -1, -1, -1, -1, 2372, -1, 2373, -1,
- -1, 2374, -1, -1, -1, -1, -1, -1,
- -1, 2375, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 2376, -1, -1,
- -1, -1, -1, -1, 2377, 2378, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 2379, -1, -1,
- -1, -1, -1, -1, -1, -1, 2380, -1,
- -1, 2381, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 2382, -1, -1, -1, 2383, -1, -1, 2384,
- -1, -1, -1, -1, 2385, -1, -1, -1,
- -1, -1, -1, 2386, -1, 2387, -1, 2388,
- -1, -1, 2389, 2390, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2391, 2392,
- -1, -1, -1, -1, -1, -1, -1, 2393,
- -1, -1, 2394, -1, -1, -1, 2395, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2396, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2397, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2398, -1, 2399, 2400, -1, -1, -1,
+ 2392, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2393, -1, -1, -1, -1, 2394,
+ 2395, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2396, -1, -1, 2397, -1, -1,
+ -1, -1, 2398, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2399, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2400, -1,
-1, 2401, -1, -1, -1, -1, 2402, -1,
- 2403, -1, -1, 2404, -1, 2405, -1, 2406,
- -1, -1, -1, -1, 2407, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2408, -1, -1, 2409, -1, 2410,
- 2411, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2403, -1, 2404, 2405,
+ -1, 2406, -1, -1, -1, -1, -1, 2407,
+ 2408, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2409, -1, -1, -1, -1, -1, -1, 2410,
+ -1, -1, -1, -1, -1, -1, -1, 2411,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2412, 2413, -1, 2414,
+ -1, 2415, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2416, -1, 2417, -1, -1, -1, -1, -1,
+ -1, 2418, -1, 2419, 2420, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2421,
+ -1, -1, -1, 2422, -1, -1, -1, -1,
+ -1, 2423, -1, -1, -1, 2424, 2425, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2426,
+ -1, -1, -1, 2427, -1, -1, 2428, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2429, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2430, -1,
+ -1, -1, -1, -1, 2431, -1, -1, -1,
+ -1, 2432, -1, -1, -1, 2433, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2434, -1, 2435, -1, -1, -1, -1, -1,
+ 2436, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2437, -1, 2438, -1, 2439, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2440, -1,
+ -1, 2441, -1, 2442, 2443, -1, -1, 2444,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2445, -1, -1, -1, -1,
+ 2446, -1, -1, -1, -1, -1, -1, 2447,
+ -1, 2448, -1, -1, -1, 2449, -1, 2450,
+ 2451, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2452, -1, -1, 2453, -1, 2454, -1,
+ -1, -1, -1, -1, 2455, -1, -1, 2456,
+ -1, -1, -1, -1, -1, -1, 2457, -1,
+ -1, -1, 2458, -1, 2459, -1, -1, 2460,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2461, 2462, 2463, -1, -1, -1,
+ 2464, -1, -1, 2465, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2466, -1, -1,
+ -1, -1, -1, -1, 2467, -1, 2468, -1,
+ 2469, -1, 2470, -1, -1, 2471, -1, -1,
+ -1, 2472, -1, -1, -1, -1, -1, 2473,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2474, -1, -1, -1, -1, -1, 2475,
+ -1, 2476, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2477,
+ 2478, -1, -1, -1, 2479, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2480, -1, -1,
+ 2481, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2412, -1, 2413, 2414, -1, -1, -1,
- -1, -1, -1, 2415, -1, -1, 2416, -1,
- -1, 2417, -1, -1, 2418, -1, -1, -1,
- -1, -1, -1, -1, -1, 2419, -1, -1,
- -1, -1, -1, 2420, -1, 2421, -1, -1,
- -1, -1, -1, -1, 2422, -1, -1, -1,
+ -1, -1, 2482, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 2423, -1, -1, -1, -1, -1, 2424, -1,
+ -1, -1, -1, -1, 2483, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 2425, 2426, 2427,
- -1, -1, -1, -1, 2428, -1, -1, 2429,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 2430, -1, -1, 2431, -1, 2432, -1, -1,
- -1, -1, -1, 2433, 2434, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2435, 2436, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2437, -1, -1, -1, -1,
- 2438, -1, -1, -1, -1, -1, -1, -1,
- 2439, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 2440, 2441, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 2442,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2443, -1, -1, -1, -1, -1, -1,
- -1, -1, 2444, -1, -1, -1, -1, -1,
- -1, -1, 2445, -1, -1, -1, 2446, -1,
- -1, -1, -1, -1, -1, -1, -1, 2447,
- -1, -1, 2448, -1, -1, -1, -1, 2449,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2450, 2451, -1, -1, -1, -1,
- 2452, -1, -1, -1, -1, -1, 2453, 2454,
- 2455, -1, -1, 2456, -1, -1, -1, 2457,
- -1, -1, -1, 2458, -1, -1, -1, 2459,
- -1, -1, -1, -1, -1, -1, 2460, -1,
- -1, 2461, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2462, -1, -1, 2463, 2464, -1,
- -1, -1, -1, 2465, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 2466, -1, -1, -1, -1, 2467, 2468, -1,
- -1, -1, -1, 2469, -1, -1, 2470, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2471, -1, -1, 2472, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 2473, 2474, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2475, -1, 2476, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2477, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2478, 2479, -1, -1,
- 2480, -1, 2481, -1, -1, -1, -1, -1,
- -1, -1, -1, 2482, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 2483, -1, -1, 2484, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2485, 2486,
- -1, -1, 2487, -1, -1, -1, -1, 2488,
+ -1, -1, -1, -1, 2484, -1, -1, 2485,
+ -1, -1, -1, 2486, -1, -1, -1, -1,
+ -1, -1, 2487, -1, -1, 2488, -1, -1,
-1, -1, -1, -1, -1, -1, -1, 2489,
- -1, 2490, -1, -1, 2491, -1, -1, 2492,
- -1, -1, -1, -1, 2493, -1, -1, -1,
+ -1, 2490, -1, -1, -1, 2491, -1, -1,
+ -1, -1, -1, 2492, 2493, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2494, -1, -1, -1, -1, -1, -1,
+ -1, 2494, -1, 2495, 2496, -1, -1, -1,
+ -1, 2497, -1, -1, -1, -1, 2498, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2499,
+ -1, -1, -1, -1, -1, -1, -1, 2500,
+ -1, -1, -1, -1, -1, -1, -1, 2501,
+ -1, -1, -1, -1, 2502, -1, -1, -1,
+ 2503, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2504,
+ 2505, -1, -1, -1, -1, -1, 2506, -1,
+ -1, 2507, -1, -1, -1, -1, 2508, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2509,
+ -1, -1, -1, -1, 2510, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2511,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 2495, 2496, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2497, 2498, -1, -1, -1, 2499,
- -1, -1, -1, -1, -1, 2500, 2501, 2502,
- -1, -1, -1, -1, -1, -1, -1, 2503,
+ 2512, 2513, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2504, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2505, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 2506, -1, 2507,
- -1, -1, -1, -1, -1, -1, 2508, -1,
- -1, -1, -1, -1, 2509, -1, -1, 2510,
- -1, -1, -1, -1, 2511, -1, -1, -1,
- -1, -1, -1, 2512, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2514, -1,
+ -1, -1, -1, -1, 2515, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2513, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2514, -1, -1, -1,
- 2515, -1, -1, -1, -1, -1, -1, 2516,
- -1, 2517, -1, -1, -1, -1, 2518, -1,
+ -1, -1, -1, -1, -1, 2516, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2517, -1,
+ -1, -1, 2518, -1, 2519, -1, -1, -1,
+ 2520, -1, -1, -1, -1, -1, 2521, -1,
+ -1, -1, -1, -1, -1, 2522, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 2519, -1, -1,
+ -1, -1, -1, 2523, -1, -1, -1, 2524,
+ -1, -1, -1, -1, -1, -1, 2525, -1,
+ -1, -1, -1, 2526, -1, -1, -1, -1,
+ -1, -1, 2527, -1, -1, -1, -1, 2528,
+ 2529, -1, -1, 2530, -1, -1, -1, 2531,
+ 2532, -1, -1, -1, -1, -1, -1, -1,
+ 2533, -1, 2534, 2535, -1, -1, -1, 2536,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2520, -1,
+ -1, -1, 2537, 2538, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2539,
+ 2540, -1, -1, 2541, 2542, -1, -1, -1,
+ -1, 2543, -1, 2544, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2545, -1, -1, -1,
+ -1, -1, 2546, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2521, -1, -1, -1, -1, -1, -1,
+ 2547, -1, -1, -1, -1, -1, -1, 2548,
+ -1, -1, -1, -1, -1, -1, -1, 2549,
+ 2550, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2551, 2552,
+ -1, -1, -1, -1, -1, -1, -1, 2553,
+ -1, 2554, -1, -1, -1, -1, -1, 2555,
+ -1, -1, 2556, -1, -1, -1, -1, -1,
+ 2557, 2558, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2522, -1, -1, -1, -1, -1, 2523,
- -1, -1, -1, 2524, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 2525, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2559, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 2526, 2527, -1, -1, -1, -1, 2528, -1,
- -1, -1, -1, 2529, -1, 2530, -1, -1,
- -1, -1, 2531, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ 2560, -1, -1, -1, -1, -1, 2561, -1,
+ 2562, -1, -1, -1, -1, -1, -1, -1,
+ 2563, -1, -1, 2564, -1, -1, -1, 2565,
+ -1, -1, -1, -1, -1, -1, -1, 2566,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ 2567, -1, -1, -1, -1, -1, -1, -1,
+ 2568, -1, -1, -1, -1, -1, 2569, -1,
+ -1, 2570, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2571, 2572, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2573, 2574, -1,
+ -1, -1, -1, -1, 2575, -1, 2576, -1,
+ -1, -1, -1, 2577, -1, -1, -1, -1,
+ 2578, 2579, -1, -1, -1, -1, -1, 2580,
-1, -1, -1, -1, -1, -1, -1, -1,
- 2532, -1, -1, -1, -1, 2533, -1, -1,
- -1, -1, -1, -1, 2534, -1, 2535, -1,
- -1, -1, -1, -1, -1, -1, 2536, -1,
+ 2581, 2582, -1, -1, -1, -1, -1, 2583,
+ -1, -1, -1, 2584, 2585, -1, 2586, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2537, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2538, -1,
+ -1, -1, -1, 2587, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 2539, -1, 2540,
- -1, -1, -1, -1, -1, -1, -1, 2541,
- -1, -1, -1, -1, -1, -1, 2542, -1,
+ -1, -1, -1, -1, 2588, -1, 2589, -1,
+ -1, -1, 2590, -1, -1, -1, -1, -1,
+ -1, -1, 2591, -1, -1, -1, -1, -1,
+ -1, 2592, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2593, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2543, -1, -1, -1, -1, -1, -1,
+ 2594, -1, -1, -1, -1, -1, 2595, -1,
+ 2596, -1, -1, -1, -1, -1, 2597, -1,
+ -1, 2598, -1, 2599, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2544, -1,
- 2545, -1, -1, -1, -1, -1, -1, 2546,
+ -1, -1, -1, -1, 2600, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2601,
+ -1, -1, -1, -1, 2602, -1, -1, 2603,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2547, 2548, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2549, -1, -1, -1, -1, -1,
- 2550, -1, -1, -1, 2551, -1, -1, -1,
- -1, -1, -1, 2552, -1, -1, -1, -1,
- 2553, 2554, 2555, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2556, -1, 2557, -1,
- -1, 2558, -1, -1, -1, -1, -1, 2559,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2560, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 2561, -1, -1,
- -1, -1, -1, -1, 2562, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2563, 2564, -1, -1, -1,
- -1, 2565, -1, -1, -1, -1, -1, 2566,
- -1, -1, -1, -1, -1, -1, 2567, -1,
- 2568, -1, 2569, -1, -1, -1, -1, -1,
- -1, -1, 2570, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 2571, -1, -1, -1, -1, -1, -1, -1,
- 2572, 2573, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 2574,
- -1, 2575, -1, -1, -1, -1, -1, -1,
- -1, -1, 2576, -1, -1, -1, -1, -1,
- -1, -1, 2577, -1, -1, -1, -1, -1,
- 2578, -1, -1, 2579, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 2580, -1, -1, -1, 2581, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 2582,
- -1, 2583, 2584, 2585, -1, 2586, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2587, -1, 2588, -1, 2589,
- -1, -1, -1, 2590, -1, -1, -1, 2591,
- -1, -1, -1, -1, 2592, -1, -1, -1,
- -1, -1, -1, -1, -1, 2593, -1, 2594,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 2595, -1, -1,
- -1, -1, -1, 2596, 2597, -1, 2598, -1,
- -1, -1, -1, -1, -1, 2599, 2600, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 2601, 2602, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2603, -1, 2604, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2605, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2606, -1, -1, -1, -1,
- 2607, 2608, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2609, -1, -1, -1,
- 2610, -1, -1, -1, -1, 2611, -1, -1,
- -1, -1, 2612, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2613, -1,
- -1, -1, -1, -1, -1, 2614, -1, -1,
- -1, -1, 2615, 2616, -1, -1, -1, 2617,
- -1, -1, -1, 2618, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 2619, -1, -1,
- -1, -1, -1, -1, 2620, -1, -1, -1,
- -1, -1, 2621, -1, -1, -1, -1, -1,
- 2622, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 2623, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 2624, -1, 2625,
- -1, -1, -1, 2626, -1, 2627, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2604, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2605, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2606,
+ -1, -1, -1, -1, -1, 2607, -1, -1,
+ -1, -1, -1, -1, 2608, -1, 2609, -1,
+ -1, -1, 2610, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2611, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2612, -1,
+ 2613, -1, -1, -1, -1, -1, -1, -1,
+ 2614, 2615, -1, -1, -1, 2616, -1, -1,
+ -1, 2617, 2618, -1, -1, -1, -1, -1,
+ -1, -1, 2619, -1, -1, 2620, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2621, -1, 2622, 2623, -1, -1,
+ -1, -1, -1, -1, 2624, 2625, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2628, -1, 2629, -1, -1, 2630,
+ -1, -1, -1, -1, -1, -1, -1, 2626,
+ -1, -1, -1, 2627, -1, -1, -1, -1,
+ -1, -1, 2628, -1, -1, -1, -1, -1,
+ -1, -1, 2629, -1, -1, -1, 2630, 2631,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2631, 2632, -1, -1, -1,
- -1, -1, -1, -1, -1, 2633, -1, -1,
- 2634, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2635, -1, -1, -1,
- -1, -1, -1, 2636, -1, -1, -1, -1,
+ -1, -1, 2632, -1, -1, -1, -1, 2633,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2637, 2638, 2639, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2634,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2640, -1, -1, 2641, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2642, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2643, -1,
+ -1, -1, -1, 2635, -1, 2636, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 2644, -1, -1,
- -1, 2645, -1, -1, -1, -1, -1, -1,
- -1, 2646, -1, -1, 2647, -1, -1, -1,
+ 2637, -1, -1, -1, 2638, -1, -1, -1,
+ -1, -1, -1, -1, 2639, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2640,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2648, -1, 2649, -1, -1,
- -1, -1, -1, 2650, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2641, -1,
+ -1, -1, -1, -1, -1, -1, 2642, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2651, -1, -1, -1, -1, 2652, -1,
- -1, -1, -1, -1, -1, 2653, -1, -1,
+ -1, 2643, -1, -1, -1, 2644, -1, -1,
+ -1, -1, -1, 2645, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2646, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2647, -1,
+ 2648, -1, -1, 2649, -1, -1, -1, -1,
+ 2650, 2651, 2652, -1, -1, -1, -1, 2653,
2654, -1, -1, -1, -1, 2655, -1, -1,
- -1, 2656, -1, -1, -1, -1, -1, -1,
- 2657, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2656, -1, -1, -1,
+ -1, -1, -1, -1, 2657, -1, -1, -1,
+ 2658, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2658, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2659, 2660, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 2659, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2661, -1,
+ -1, -1, -1, -1, 2662, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2663, -1,
+ -1, -1, -1, -1, -1, 2664, -1, 2665,
+ -1, -1, -1, 2666, -1, 2667, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 2660, -1, -1, -1, 2661, 2662, -1, -1,
+ -1, 2668, -1, 2669, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2663, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2664, -1, 2665, -1,
- -1, -1, -1, -1, -1, 2666, -1, -1,
+ 2670, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2667, -1, -1, 2668, -1,
- -1, 2669, -1, 2670, -1, -1, -1, -1,
-1, 2671, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, 2672, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2673, -1, -1, -1,
- -1, -1, -1, 2674, 2675, -1, -1, -1,
+ -1, -1, 2673, 2674, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2675, 2676, -1,
+ -1, -1, -1, -1, 2677, -1, -1, -1,
+ -1, 2678, -1, -1, -1, -1, -1, -1,
+ 2679, -1, 2680, -1, -1, -1, -1, -1,
+ -1, -1, 2681, 2682, -1, -1, -1, -1,
+ -1, -1, -1, 2683, -1, -1, -1, -1,
+ -1, -1, 2684, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2685, -1, 2686,
+ -1, -1, -1, -1, 2687, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2676, -1, -1, 2677, 2678, -1, -1,
- -1, 2679, -1, -1, -1, -1, -1, 2680,
- -1, 2681, 2682, -1, -1, 2683, -1, -1,
- 2684, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2685, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 2686,
- -1, 2687, -1, -1, -1, -1, -1, -1,
+ -1, 2688, -1, -1, -1, -1, -1, -1,
+ 2689, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 2688,
- 2689, -1, 2690, -1, -1, 2691, -1, 2692,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2693, -1, -1, -1,
+ -1, 2690, -1, 2691, -1, -1, 2692, -1,
+ -1, -1, -1, -1, -1, -1, 2693, -1,
+ -1, -1, 2694, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2694, -1, -1, -1,
+ -1, 2695, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2695, -1, -1, 2696, -1, -1, -1,
+ -1, -1, 2696, -1, -1, -1, -1, 2697,
+ 2698, 2699, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2700,
+ -1, 2701, -1, -1, -1, -1, -1, -1,
+ 2702, 2703, -1, -1, -1, 2704, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2697, 2698, -1, -1, -1,
- 2699, -1, 2700, 2701, -1, 2702, -1, -1,
- -1, -1, -1, 2703, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2705, 2706,
+ -1, -1, -1, -1, -1, -1, -1, 2707,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2704, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2705, 2706, -1, -1, -1, -1, -1,
+ -1, -1, 2708, -1, -1, -1, 2709, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2707, -1, -1, 2708, 2709, -1,
- -1, 2710, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2711, -1,
+ -1, -1, 2710, -1, -1, -1, -1, -1,
+ -1, -1, 2711, -1, -1, -1, -1, -1,
-1, 2712, -1, -1, -1, -1, -1, -1,
- -1, 2713, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2713, 2714,
+ 2715, -1, -1, -1, -1, 2716, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2717, -1, -1, 2718, -1,
+ -1, 2719, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2720,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2721, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2722, 2723, -1,
+ -1, 2724, -1, -1, 2725, 2726, -1, -1,
+ -1, 2727, -1, 2728, -1, -1, -1, -1,
+ 2729, -1, -1, -1, 2730, 2731, -1, -1,
+ -1, -1, -1, 2732, 2733, 2734, -1, -1,
+ -1, 2735, -1, -1, 2736, -1, -1, 2737,
+ -1, -1, -1, -1, -1, -1, 2738, -1,
+ -1, 2739, -1, -1, -1, 2740, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2741, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2742,
+ -1, -1, -1, 2743, 2744, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2745, -1, -1, -1, 2746, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ 2747, -1, -1, -1, 2748, -1, -1, 2749,
+ -1, 2750, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2751, -1,
+ -1, 2752, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2753, 2754, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2714, -1, -1, -1, 2715, 2716, -1,
- -1, -1, -1, -1, 2717, 2718, -1, -1,
- -1, -1, -1, -1, -1, 2719, -1, -1,
- -1, -1, -1, 2720, -1, 2721, -1, -1,
- -1, -1, -1, -1, 2722, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 2723, -1, -1, -1, 2724, 2725, -1, -1,
- -1, -1, -1, -1, 2726, 2727, -1, -1,
- -1, -1, -1, 2728, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2729, -1, 2730, -1, -1, -1,
- 2731, -1, -1, -1, -1, 2732, -1, -1,
- 2733, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2734, 2735, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2736, -1, -1, -1, -1, -1,
- 2737, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 2738, -1, -1, -1, -1, -1, -1, 2739,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2740, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 2741,
- -1, -1, -1, -1, -1, -1, 2742, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2743, -1, -1, -1, 2744, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2745, -1, -1, -1,
- -1, -1, 2746, -1, -1, -1, -1, 2747,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2748, -1, -1, -1,
- -1, -1, -1, -1, 2749, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2750, -1, -1, -1, -1,
- 2751, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 2752,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2753, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2754, -1, -1, -1,
2755, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2756, 2757, -1, 2758, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2759, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2760, -1,
- -1, -1, -1, -1, -1, 2761, -1, -1,
- 2762, -1, 2763, -1, -1, -1, 2764, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2765, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2766, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2756,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2767, -1,
- 2768, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2769, -1, -1, -1, -1, 2770,
- -1, -1, 2771, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2772, -1, -1, -1,
+ -1, 2757, 2758, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2759, -1, -1, 2760, 2761, -1, -1, 2762,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2763, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2764,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2765, -1, -1, -1, -1, -1,
+ -1, 2766, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2767,
+ -1, -1, -1, -1, -1, 2768, -1, -1,
+ -1, -1, 2769, 2770, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2771, 2772, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2773, -1, -1, -1, -1, 2774, -1, -1,
+ -1, -1, -1, -1, 2775, -1, -1, 2776,
+ -1, 2777, -1, -1, 2778, -1, 2779, -1,
+ -1, -1, -1, -1, -1, 2780, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2773, -1, -1, -1, -1,
- -1, 2774, -1, 2775, -1, 2776, -1, -1,
- -1, -1, -1, 2777, -1, -1, -1, -1,
- 2778, -1, -1, -1, -1, -1, -1, 2779,
- 2780, -1, 2781, -1, -1, 2782, -1, -1,
+ -1, -1, -1, -1, 2781, -1, -1, -1,
+ -1, 2782, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, 2783, -1, -1, -1,
- -1, 2784, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2785, -1, -1, -1, -1,
- -1, -1, -1, -1, 2786, -1, -1, 2787,
- -1, -1, -1, 2788, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2789, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2790, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2784, -1, -1,
+ -1, 2785, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2786, -1,
+ -1, -1, -1, -1, 2787, -1, -1, -1,
+ 2788, -1, -1, -1, 2789, -1, -1, 2790,
+ 2791, -1, -1, -1, -1, -1, 2792, -1,
+ -1, -1, -1, -1, -1, 2793, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2791, -1, 2792, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 2793,
- -1, -1, 2794, -1, -1, 2795, -1, -1,
- -1, -1, 2796, 2797, -1, -1, -1, -1,
+ -1, 2794, -1, 2795, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2796, -1, 2797, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 2798, -1, -1, -1, -1, 2799, -1, -1,
- -1, -1, -1, -1, -1, 2800, -1, -1,
+ 2798, 2799, -1, -1, -1, -1, -1, -1,
+ 2800, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2801, -1, -1, -1, -1, -1,
+ -1, -1, 2802, 2803, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2804,
+ -1, 2805, -1, -1, -1, -1, -1, -1,
+ -1, 2806, 2807, -1, -1, -1, -1, -1,
+ -1, -1, 2808, 2809, -1, -1, -1, -1,
+ 2810, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2801, -1,
- -1, -1, -1, -1, -1, 2802, -1, 2803,
+ -1, -1, -1, 2811, 2812, 2813, 2814, -1,
+ -1, -1, -1, 2815, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2804, 2805, -1, -1,
- -1, 2806, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 2807, -1, -1,
- -1, -1, -1, -1, -1, 2808, -1, -1,
+ -1, 2816, -1, -1, 2817, -1, -1, -1,
+ -1, -1, -1, 2818, -1, -1, -1, -1,
+ -1, 2819, -1, -1, -1, 2820, -1, -1,
+ -1, 2821, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2809, -1, -1, -1, -1,
+ -1, 2822, -1, -1, -1, -1, -1, -1,
+ 2823, 2824, -1, -1, 2825, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 2810, 2811, -1, -1, -1, 2812, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2813, -1, -1, -1, -1, 2814,
- 2815, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2816, -1,
- -1, -1, -1, -1, -1, -1, -1, 2817,
- 2818, 2819, -1, -1, 2820, -1, -1, -1,
- 2821, -1, -1, 2822, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 2823, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 2824,
- -1, 2825, -1, -1, -1, -1, -1, 2826,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2827, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2826, -1, 2827, -1,
-1, -1, -1, -1, 2828, -1, -1, -1,
- -1, -1, -1, 2829, -1, -1, 2830, -1,
+ -1, -1, 2829, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2831, -1, -1, -1, 2832,
- -1, -1, 2833, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2830, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2831, -1, -1, 2832, -1, 2833, -1,
-1, -1, -1, 2834, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2835, -1, -1, -1,
- 2836, -1, -1, -1, -1, 2837, -1, -1,
- -1, -1, -1, -1, -1, -1, 2838, -1,
- 2839, -1, 2840, 2841, 2842, -1, -1, -1,
- -1, -1, -1, 2843, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2844, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2845, 2846, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2835, -1, -1, -1, -1, -1,
+ -1, 2836, -1, -1, -1, -1, -1, -1,
+ 2837, -1, 2838, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2839, -1,
+ 2840, 2841, -1, -1, -1, -1, -1, -1,
+ 2842, -1, -1, 2843, -1, -1, -1, 2844,
+ -1, 2845, 2846, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, 2847,
+ -1, -1, -1, 2848, -1, -1, -1, 2849,
+ -1, -1, 2850, -1, -1, -1, -1, -1,
+ -1, 2851, -1, -1, 2852, -1, -1, 2853,
+ 2854, -1, -1, -1, 2855, -1, -1, -1,
+ -1, -1, -1, 2856, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 2848,
- -1, -1, -1, -1, 2849, -1, 2850, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2857,
+ -1, -1, -1, -1, -1, -1, -1, 2858,
+ 2859, -1, 2860, -1, -1, -1, -1, -1,
+ -1, -1, 2861, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2862, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2863, -1, 2864, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2865, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2866, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2867, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2868, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2869,
+ -1, -1, 2870, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2851, 2852, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2871,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2853, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2854, -1, -1, -1, 2855, -1, -1,
- -1, 2856, -1, -1, -1, -1, -1, -1,
- -1, -1, 2857, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2858, -1, -1, -1,
- -1, -1, -1, -1, 2859, -1, -1, -1,
- -1, -1, -1, -1, -1, 2860, -1, -1,
- -1, 2861, -1, 2862, -1, 2863, -1, -1,
- 2864, -1, -1, 2865, -1, -1, -1, -1,
- -1, -1, -1, 2866, -1, 2867, -1, -1,
- -1, -1, 2868, -1, -1, -1, -1, 2869,
- -1, 2870, -1, -1, -1, -1, -1, -1,
- -1, 2871, -1, -1, -1, -1, 2872, -1,
+ -1, -1, -1, -1, -1, 2872, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2873, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2873, -1, -1,
-1, -1, 2874, -1, -1, -1, -1, -1,
- -1, -1, -1, 2875, -1, 2876, -1, 2877,
- -1, -1, -1, 2878, 2879, -1, -1, -1,
+ -1, -1, -1, -1, 2875, -1, 2876, -1,
+ -1, -1, -1, 2877, -1, -1, -1, -1,
+ -1, -1, 2878, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2879, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2880,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2881, -1, -1, -1, 2882, -1, -1, -1,
+ 2883, 2884, -1, 2885, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2886, -1, 2887, -1, -1, -1, -1,
+ -1, -1, 2888, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2889, -1, -1, -1, -1, -1,
+ -1, -1, 2890, 2891, -1, -1, -1, -1,
+ -1, 2892, -1, -1, -1, -1, 2893, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2894,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2880, -1, 2881, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 2882,
- 2883, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2895, -1, 2896, -1,
+ -1, -1, -1, -1, -1, -1, 2897, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2884, -1, -1, 2885, -1, -1, -1,
- -1, -1, -1, -1, 2886, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2887, -1, 2888, -1, 2889, -1, -1,
- -1, 2890, 2891, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2892, -1,
- 2893, -1, -1, -1, -1, -1, 2894, -1,
- -1, -1, 2895, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2896, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 2897, 2898, -1,
+ 2898, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, 2899, -1,
- -1, -1, -1, -1, -1, -1, 2900, -1,
+ -1, 2900, -1, 2901, -1, 2902, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2901, -1, -1, -1, -1, -1, -1,
- -1, 2902, -1, -1, -1, -1, -1, -1,
- 2903, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2904, -1, -1, -1,
- 2905, -1, -1, -1, -1, -1, -1, 2906,
+ 2903, 2904, -1, -1, -1, -1, -1, 2905,
+ -1, -1, -1, -1, -1, -1, 2906, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ 2907, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2908,
+ -1, 2909, -1, 2910, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 2907, -1, 2908,
+ -1, -1, 2911, -1, -1, -1, -1, -1,
+ 2912, -1, -1, -1, -1, 2913, -1, -1,
+ -1, 2914, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2915, 2916, -1, -1,
+ 2917, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2918, -1, -1, -1,
+ 2919, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 2909, -1, -1, 2910, 2911, -1, -1, -1,
- -1, -1, -1, -1, -1, 2912, -1, 2913,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 2914,
- -1, 2915, -1, -1, -1, -1, 2916, -1,
- -1, -1, 2917, -1, -1, -1, -1, -1,
- -1, -1, 2918, -1, -1, -1, -1, 2919,
-1, -1, -1, -1, -1, 2920, -1, -1,
- -1, -1, -1, -1, 2921, 2922, -1, -1,
+ 2921, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2922,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2923, -1, 2924, -1, -1,
- -1, -1, -1, -1, -1, -1, 2925, -1,
- -1, -1, -1, -1, -1, -1, -1, 2926,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 2927, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2928, -1,
- 2929, -1, -1, -1, -1, 2930, -1, 2931,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 2932, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 2933,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2934, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2923, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2935, -1, -1, -1, -1, -1,
- -1, 2936, -1, -1, 2937, 2938, -1, -1,
- -1, -1, -1, -1, 2939, -1, -1, 2940,
- -1, 2941, -1, -1, 2942, -1, -1, -1,
- -1, -1, -1, -1, -1, 2943, 2944, -1,
- -1, -1, -1, -1, -1, -1, 2945, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2924, 2925, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2926, -1, -1, -1,
+ -1, -1, 2927, -1, -1, -1, 2928, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2929, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2930, 2931,
+ -1, -1, -1, 2932, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2933, 2934, -1, -1, -1,
+ -1, -1, -1, 2935, -1, -1, -1, 2936,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2937, -1, -1, 2938, -1, -1,
+ -1, 2939, -1, 2940, -1, -1, 2941, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2942,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2943, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2944, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2945, -1, -1,
-1, -1, -1, 2946, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2947, -1, -1, -1, -1, 2948,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2949, -1, 2950, -1, -1, 2951, -1,
- -1, -1, -1, 2952, 2953, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2947, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2948,
+ -1, -1, -1, 2949, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2950, -1,
+ 2951, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2952, -1, -1, -1, 2953,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
2954, -1, -1, -1, -1, -1, -1, -1,
+ 2955, -1, 2956, -1, -1, -1, -1, -1,
+ 2957, -1, -1, -1, -1, -1, 2958, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 2955, -1, 2956,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2957, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 2958, -1, -1, -1, -1, 2959, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 2960, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 2961,
- -1, -1, -1, -1, -1, -1, 2962, -1,
- -1, -1, -1, -1, 2963, -1, 2964, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2965, -1, 2966, -1, -1, -1, -1,
+ -1, -1, 2959, -1, -1, -1, -1, -1,
+ -1, -1, 2960, -1, -1, 2961, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2962,
+ -1, 2963, 2964, -1, -1, -1, -1, 2965,
+ -1, -1, -1, -1, 2966, -1, -1, -1,
-1, -1, -1, -1, -1, 2967, -1, -1,
+ -1, -1, -1, -1, -1, 2968, -1, -1,
+ -1, -1, -1, -1, 2969, -1, -1, -1,
+ -1, -1, -1, -1, 2970, -1, -1, 2971,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2968, -1, 2969, -1, -1, -1, -1,
- -1, -1, 2970, -1, -1, -1, -1, -1,
- -1, -1, -1, 2971, -1, -1, -1, -1,
-1, 2972, -1, -1, -1, -1, -1, -1,
+ -1, 2973, -1, -1, 2974, -1, -1, -1,
+ -1, 2975, 2976, -1, -1, 2977, -1, 2978,
+ -1, -1, -1, -1, -1, -1, -1, 2979,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2980, -1, -1, 2981, -1, 2982, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2983, -1, -1, 2984, -1,
+ -1, -1, -1, -1, -1, -1, 2985, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2986, -1, -1, -1, 2987, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2988, -1, 2989, 2990,
+ 2991, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2992, 2993, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2994, -1,
+ -1, -1, 2995, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2996,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2997,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2973, -1, 2974, -1, -1,
+ -1, -1, -1, 2998, 2999, -1, -1, -1,
+ 3000, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2975, 2976, -1, -1, 2977,
+ -1, -1, -1, 3001, -1, 3002, -1, -1,
+ -1, 3003, -1, -1, -1, -1, -1, -1,
+ -1, 3004, -1, 3005, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2978, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2979, -1,
+ -1, -1, -1, -1, 3006, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3007,
+ -1, -1, -1, 3008, 3009, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3010, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 2980, -1, -1, 2981, -1, -1, -1, 2982,
- -1, -1, -1, -1, -1, -1, -1, 2983,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2984, -1, -1, -1, -1, -1, -1,
- -1, -1, 2985, -1, 2986, -1, 2987, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 2988, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 2989, -1, -1, -1, -1,
+ 3011, -1, -1, -1, -1, 3012, -1, -1,
+ -1, -1, -1, 3013, -1, -1, -1, -1,
+ 3014, 3015, -1, 3016, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3017, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ 3018, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3019, 3020, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2990, -1,
- -1, 2991, -1, -1, -1, -1, -1, -1,
+ -1, 3021, 3022, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3023, -1,
+ -1, -1, -1, -1, 3024, -1, -1, -1,
+ 3025, -1, -1, 3026, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3027, -1, 3028, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 2992, -1, -1, -1,
+ -1, -1, 3029, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3030, -1, -1,
+ -1, -1, -1, 3031, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2993, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 2994, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 2995, 2996, 2997,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 2998, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 2999, -1,
- -1, -1, -1, -1, 3000, 3001, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3002, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 3003, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3004, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3005, -1,
- -1, -1, -1, 3006, 3007, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3008, -1, -1,
- -1, -1, -1, -1, -1, -1, 3009, -1,
- -1, -1, -1, -1, -1, -1, -1, 3010,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3011,
- -1, -1, -1, -1, -1, 3012, -1, -1,
- -1, -1, -1, -1, -1, 3013, -1, -1,
- -1, -1, -1, -1, 3014, -1, -1, -1,
- 3015, -1, -1, -1, -1, -1, -1, -1,
- -1, 3016, -1, -1, -1, -1, -1, -1,
- -1, -1, 3017, -1, -1, 3018, -1, -1,
+ -1, -1, -1, 3032, -1, -1, -1, -1,
+ -1, 3033, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3034, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3019, -1,
- -1, -1, -1, 3020, -1, -1, -1, -1,
- -1, -1, -1, 3021, -1, -1, -1, -1,
- -1, -1, -1, 3022, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3023, -1, -1, -1, -1, -1, -1,
- 3024, 3025, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3026,
- -1, -1, -1, -1, -1, -1, 3027, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3028,
- 3029, -1, -1, -1, -1, -1, 3030, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3031, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3032, -1, 3033,
- -1, -1, -1, -1, -1, -1, 3034, -1,
- -1, -1, -1, -1, -1, -1, 3035, -1,
- -1, -1, 3036, -1, -1, -1, -1, -1,
- 3037, 3038, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3039, 3040, -1, 3041, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3042,
- -1, -1, -1, -1, -1, -1, 3043, -1,
- 3044, -1, -1, -1, -1, -1, -1, -1,
+ 3035, -1, -1, -1, -1, 3036, 3037, -1,
+ 3038, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3039, 3040, 3041, -1,
+ -1, -1, -1, -1, 3042, -1, 3043, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3045, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3044, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3045,
-1, -1, -1, 3046, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, 3047, -1, -1,
- -1, -1, -1, -1, 3048, 3049, -1, -1,
- -1, 3050, 3051, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 3052, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3053, -1,
+ -1, -1, -1, -1, 3048, -1, -1, -1,
+ -1, -1, -1, 3049, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3050, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3051,
+ -1, -1, -1, -1, -1, 3052, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3053, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, 3054, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, 3055, -1, -1, -1, -1,
+ -1, -1, -1, 3056, 3057, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3056, -1, -1,
- -1, -1, -1, -1, -1, 3057, -1, -1,
- -1, 3058, 3059, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3058,
-1, -1, -1, -1, -1, -1, -1, -1,
- 3060, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3059, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3061, -1,
- -1, -1, -1, -1, -1, -1, -1, 3062,
+ -1, -1, -1, -1, 3060, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3063, -1, -1,
+ -1, -1, 3061, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3064,
- -1, -1, 3065, -1, -1, -1, 3066, -1,
+ -1, -1, -1, 3062, 3063, 3064, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 3067, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3068, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3065, -1,
+ -1, -1, -1, -1, -1, 3066, -1, -1,
+ -1, -1, -1, 3067, -1, -1, 3068, -1,
-1, -1, -1, -1, 3069, -1, -1, -1,
- -1, -1, -1, -1, 3070, 3071, -1, -1,
- 3072, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 3073, -1, -1, -1, -1, -1, -1, -1,
- -1, 3074, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3070, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3071, -1,
+ -1, -1, -1, 3072, -1, -1, 3073, -1,
+ 3074, -1, -1, -1, 3075, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3076, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 3075, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3076, -1, -1, 3077, -1,
- -1, -1, -1, -1, 3078, -1, -1, -1,
- -1, -1, 3079, -1, 3080, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3077, -1,
+ -1, -1, -1, 3078, -1, -1, -1, 3079,
+ -1, -1, -1, -1, -1, -1, -1, 3080,
+ 3081, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 3081, 3082, -1, 3083, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3084, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3085, -1, -1, -1, -1, -1,
- -1, -1, -1, 3086, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3087,
- 3088, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3089, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3090,
- -1, -1, -1, -1, -1, 3091, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3092, -1, -1,
- -1, -1, 3093, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3094,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3095, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3096, -1, -1, -1,
- 3097, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3098, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3099,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3100, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3101,
- -1, -1, 3102, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 3103, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3104,
- -1, 3105, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3106, -1, -1,
- -1, -1, -1, 3107, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3082, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3108,
- -1, 3109, -1, -1, -1, -1, -1, -1,
- -1, 3110, -1, 3111, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3112, -1, 3113, -1, -1,
+ 3083, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3084, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3114,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3085, -1, -1, -1, -1, -1, -1,
+ -1, 3086, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3115, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3116, 3117, -1,
- 3118, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3119,
+ -1, -1, -1, -1, 3087, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3088, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3089, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3090, -1, -1, -1, 3091, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3120, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3092, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3093,
+ -1, 3094, -1, -1, -1, -1, 3095, -1,
+ -1, -1, -1, 3096, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3097, -1, -1, -1,
+ -1, 3098, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3121, -1, -1, -1, -1,
- -1, -1, -1, -1, 3122, 3123, -1, -1,
- -1, 3124, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3099, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3100, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3101, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3102, -1, -1, -1,
+ -1, 3103, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3104, -1, -1, 3105, 3106,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3107, -1, -1, -1, -1, -1,
+ -1, -1, 3108, 3109, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3110, -1, -1,
+ 3111, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3125, -1,
+ -1, -1, 3112, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3113, -1, -1, -1, -1, -1, -1,
+ -1, 3114, -1, -1, -1, 3115, -1, 3116,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3117, -1, 3118, -1, -1,
+ -1, 3119, -1, -1, -1, -1, -1, -1,
+ 3120, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3121, -1, -1, 3122, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3123, -1, 3124,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3125, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, 3126,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3127, -1, -1, -1,
- -1, 3128, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3129, -1,
- -1, -1, -1, 3130, -1, -1, -1, -1,
+ -1, 3127, 3128, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3129, -1, -1,
+ 3130, -1, -1, -1, -1, -1, -1, -1,
-1, -1, 3131, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3132, -1,
+ -1, 3132, -1, 3133, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3133, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3134, -1, -1,
+ -1, -1, 3134, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3135, -1, -1, -1, -1, -1, -1, -1,
+ 3136, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3135, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3136, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 3137, -1, -1, -1, -1, 3138, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3137, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3138,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, 3139, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3140, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3140,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3141,
+ -1, -1, -1, 3141, -1, -1, -1, -1,
+ -1, -1, 3142, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3143, -1, -1,
+ -1, 3144, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 3142, -1, 3143, -1, -1, -1, -1, -1,
- -1, -1, -1, 3144, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, 3145, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3146, -1, -1, -1,
+ -1, 3147, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3146, 3147, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3148,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3149, -1, -1,
- -1, 3150, -1, -1, -1, -1, 3151, -1,
- -1, -1, -1, -1, -1, 3152, -1, -1,
- -1, -1, -1, -1, 3153, -1, -1, -1,
- -1, 3154, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3155, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3156, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3157, -1, -1,
+ -1, -1, -1, 3148, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3158, 3159, 3160, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3149, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3150, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3151, -1, 3152, -1, 3153, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3154, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3155, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -10159,310 +10321,312 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3161, -1, -1,
- -1, -1, -1, 3162, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 3163, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3164, -1, -1, -1, -1,
+ 3156, -1, -1, -1, -1, -1, -1, -1,
+ 3157, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3158, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3165, -1, -1, -1, -1, -1,
- 3166, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3159, -1, -1, 3160, -1,
+ 3161, -1, -1, -1, -1, -1, 3162, -1,
+ -1, -1, 3163, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3164, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3165, -1, -1, -1,
+ -1, 3166, 3167, -1, -1, 3168, 3169, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3170, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3171, -1, -1, -1, -1, 3172,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3167, -1, -1,
- -1, -1, -1, -1, -1, 3168, -1, -1,
- -1, -1, -1, 3169, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3170, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3171, -1,
- 3172, -1, -1, -1, -1, -1, 3173, -1,
- -1, -1, -1, -1, -1, -1, -1, 3174,
- -1, -1, -1, 3175, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3173, -1, -1, -1,
+ -1, 3174, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3176, -1, 3177, -1,
+ 3175, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3176, -1, -1,
+ 3177, -1, -1, -1, -1, -1, -1, 3178,
+ -1, -1, 3179, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3178, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 3179, -1, -1, 3180, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3181,
+ -1, -1, -1, 3180, -1, -1, -1, -1,
+ 3181, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, 3182, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3183, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3184, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3183,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3185, -1, -1, -1, 3186, -1, -1,
- 3187, -1, -1, -1, 3188, -1, -1, -1,
- 3189, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3184, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3190, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 3191, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3185,
+ -1, 3186, -1, -1, -1, -1, -1, -1,
+ 3187, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3188,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3189, 3190, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3191, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3192, -1, -1, -1, 3193, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3194, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3195, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3196, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3197, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3198, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3199, -1,
+ -1, -1, -1, 3200, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3201, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ 3202, 3203, -1, -1, -1, -1, -1, -1,
+ -1, 3204, -1, 3205, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3206,
+ -1, -1, -1, -1, -1, -1, -1, 3207,
-1, -1, -1, -1, -1, -1, -1, -1,
- 3192, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3208,
+ -1, -1, -1, 3209, 3210, -1, -1, -1,
+ -1, -1, -1, 3211, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3193, -1, -1, -1, -1, -1,
- -1, -1, -1, 3194, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3195, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3196,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 3197, -1, -1, -1, -1, 3198, -1, -1,
- -1, -1, -1, -1, 3199, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3200, -1, 3201, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3202, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3203, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3204, 3205,
- -1, -1, -1, 3206, -1, 3207, -1, 3208,
- -1, 3209, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3210, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3211, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3212, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3212, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, 3213, -1, -1, -1, -1, -1,
- -1, -1, 3214, -1, -1, 3215, -1, 3216,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3214,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
+ 3215, -1, -1, -1, -1, -1, 3216, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, 3217, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, 3218, -1, 3219, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3220, -1, -1, -1, 3221,
- -1, -1, -1, -1, 3222, -1, -1, -1,
- -1, -1, -1, -1, -1, 3223, -1, -1,
+ -1, 3220, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3221, 3222, -1, -1, -1, -1, 3223,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3224, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3225,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 3226, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3224, -1, -1, -1, -1, -1, -1,
+ -1, 3225, -1, -1, -1, -1, 3226, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3227, -1, -1, -1,
+ -1, -1, -1, 3227, -1, -1, -1, -1,
+ 3228, -1, -1, -1, -1, -1, 3229, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3230,
+ -1, 3231, -1, -1, -1, 3232, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3233, -1, -1, -1, 3234, -1,
+ -1, -1, -1, 3235, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3236, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3237, -1, -1, -1, 3238, -1,
+ -1, -1, 3239, 3240, 3241, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3228, -1, -1, -1, -1, -1,
- -1, 3229, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3242, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3243,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3230, -1, -1, 3231, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3232,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3233, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3234,
- -1, -1, -1, -1, -1, 3235, -1, -1,
- -1, -1, -1, -1, -1, -1, 3236, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3237,
- -1, -1, -1, -1, 3238, -1, -1, -1,
- -1, -1, -1, 3239, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 3240, -1, -1, -1, -1, -1, 3241, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3242, -1, -1, 3243, -1, -1,
- -1, -1, -1, 3244, -1, -1, -1, -1,
- -1, 3245, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3244, 3245, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, 3246, -1, -1,
- -1, -1, -1, -1, 3247, -1, -1, 3248,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3247, -1,
+ -1, -1, -1, -1, -1, -1, 3248, -1,
-1, -1, -1, -1, 3249, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3250, -1, 3251, -1, -1, -1,
+ -1, -1, -1, 3250, -1, 3251, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3252, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3253, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3254, -1, -1, -1, -1,
+ -1, -1, -1, 3255, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3256, -1, -1, -1, -1, -1, -1,
+ 3257, -1, -1, -1, -1, -1, 3258, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 3252, -1, -1, -1, -1, -1, -1, 3253,
- -1, -1, -1, -1, 3254, -1, -1, -1,
- -1, -1, -1, -1, 3255, -1, -1, -1,
+ -1, -1, -1, 3259, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3256, -1, -1, -1,
- -1, -1, 3257, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3258, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3259, -1, -1, -1,
+ 3260, -1, -1, 3261, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3260, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3261, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, 3262, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3263,
+ -1, -1, 3263, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3264, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3265, -1, -1,
- -1, -1, -1, 3266, -1, 3267, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3264, -1, 3265, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3266, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3267, -1, -1, -1,
-1, -1, -1, -1, -1, 3268, -1, -1,
- -1, -1, -1, -1, 3269, -1, -1, -1,
- -1, 3270, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3269, 3270, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, 3271,
- -1, -1, 3272, 3273, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3274, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3275, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3276, -1, 3277,
- -1, 3278, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3279, -1, -1,
- -1, -1, -1, -1, -1, 3280, -1, -1,
+ -1, -1, -1, -1, -1, 3272, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3281, 3282, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3283, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3284,
- -1, -1, 3285, 3286, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3287, -1,
- -1, -1, -1, -1, 3288, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3273, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3274, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3275, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3276, -1, -1, 3277, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3278, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3279, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3280, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3281, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3282, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3283, 3284, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3285, -1,
+ -1, -1, -1, 3286, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3287, 3288,
+ -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, 3289, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3290, -1, -1,
- 3291, -1, -1, -1, -1, -1, 3292, -1,
- -1, -1, 3293, 3294, -1, 3295, -1, -1,
- 3296, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3297, -1, -1, -1,
- 3298, -1, -1, 3299, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3300,
- -1, -1, -1, 3301, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3302,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3303, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3304, -1, -1,
- -1, -1, -1, 3305, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3306, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3290, -1,
+ -1, -1, -1, 3291, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3292, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3307, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3308, -1,
- -1, 3309, 3310, -1, -1, 3311, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3312, -1, -1,
- -1, -1, 3313, -1, -1, -1, -1, -1,
+ -1, 3293, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3314, -1, -1, -1, -1, 3315, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3316, -1, -1, -1, -1, 3317, 3318,
+ -1, -1, -1, -1, -1, 3294, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3319, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3295,
+ -1, -1, 3296, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3297, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3298,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3299, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3300, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3301, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3302, 3303, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3304, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3305, -1, -1, 3306, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3320, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3307, -1, -1, -1, -1, -1,
+ 3308, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3309, -1, -1, -1, -1, -1, -1,
+ 3310, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 3321, -1, -1, -1, -1, -1, 3322, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3311,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -10471,147 +10635,200 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ 3312, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3313, -1, 3314, -1, 3315, -1,
+ 3316, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3317, -1, -1, -1,
+ -1, -1, 3318, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3319, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3320, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3321, -1, -1,
+ -1, -1, 3322, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, 3323, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3324, -1, -1, -1, 3325,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3326, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3327, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3324,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3325,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3326, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3327, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3328, -1, -1,
+ -1, 3329, -1, -1, -1, -1, -1, 3330,
+ -1, 3331, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3332, -1, -1, -1, 3333,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3328, -1,
+ -1, -1, 3334, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3335, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3336, -1, -1, -1, -1,
+ 3337, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 3329, -1, -1, -1, 3330, -1, -1, 3331,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 3332, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3333, -1,
+ -1, -1, -1, -1, 3338, -1, 3339, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 3334, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3340, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3335, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3341,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3342, -1, -1, -1, -1, -1,
+ 3343, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 3336, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3337, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3338, -1,
- -1, -1, -1, -1, 3339, 3340, -1, -1,
- -1, -1, 3341, -1, -1, -1, -1, -1,
- -1, 3342, -1, -1, -1, -1, -1, -1,
- -1, -1, 3343, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3344, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3344,
+ -1, -1, -1, -1, -1, 3345, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 3345, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3346, -1, -1,
+ -1, -1, 3347, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3348, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3349, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3346, -1, 3347, -1, 3348,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3350, 3351, -1, -1, -1,
+ -1, -1, 3352, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3353, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3354, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3349, -1, -1, -1,
+ -1, -1, -1, 3355, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3356, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3350,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3351, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3352,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3353,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 3354, -1, -1, -1, -1, -1, -1, -1,
- 3355, -1, -1, -1, 3356, -1, -1, -1,
+ -1, -1, -1, 3357, -1, -1, -1, 3358,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3357, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 3358, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3359, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3359,
-1, -1, 3360, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3361, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3361, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3362, -1, -1,
+ 3362, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3363, -1, -1, -1, -1, -1,
- -1, 3364, -1, -1, -1, 3365, 3366, -1,
+ -1, -1, -1, 3363, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3367, 3368, -1, -1, -1, -1, -1,
- -1, 3369, -1, 3370, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3371, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3364, -1, -1, -1, -1, -1, -1, -1,
+ 3365, -1, -1, -1, 3366, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3367, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3368, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3369, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3370, 3371, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, 3372, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3373, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 3374, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3375, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3373,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ 3374, -1, -1, -1, -1, -1, -1, 3375,
-1, -1, -1, -1, -1, -1, -1, -1,
3376, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3377, -1, 3378, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3379, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3380, -1, -1, 3381, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3382, -1,
+ -1, -1, 3377, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3378, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3379, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3380, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -10622,43 +10839,34 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3381, -1, -1, -1, -1, 3382,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3383, 3384, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3383, -1,
+ -1, -1, -1, 3385, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3386, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3387, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3388, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 3384, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3385,
+ -1, -1, -1, 3389, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3390,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 3386, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 3387, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 3388, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3389, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 3390, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3391, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3391, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -10666,81 +10874,51 @@
-1, -1, -1, -1, -1, -1, -1, 3392,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3393, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3393, -1,
- -1, -1, -1, -1, -1, -1, -1, 3394,
+ 3394, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 3395, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3396, -1, 3397, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3398, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3399, -1, -1, -1, -1,
+ -1, 3395, -1, -1, -1, 3396, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3400, -1, -1, -1, -1,
+ 3397, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3401, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3398,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3399, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3400, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3401, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3402,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3403, -1, 3404, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3405,
+ -1, -1, -1, -1, 3406, -1, -1, -1,
+ 3407, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3408, -1, -1, -1,
+ -1, 3409, -1, -1, -1, -1, 3410, 3411,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3412, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3402, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 3403, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3404,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3405, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3406, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3407, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3408, -1,
- -1, -1, 3409, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 3410, -1, -1, 3411, -1, -1, 3412, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3413, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3413, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -10748,54 +10926,51 @@
-1, -1, -1, -1, -1, -1, -1, 3414,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3415, -1, -1, -1, -1,
- -1, -1, -1, -1, 3416, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3417, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3418, -1,
+ -1, -1, -1, -1, 3415, -1, -1, 3416,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3417, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3418, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3419, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3419,
+ 3420, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 3420, -1, -1, -1, -1, -1, -1, 3421,
+ -1, -1, -1, -1, -1, 3421, -1, -1,
-1, -1, -1, -1, -1, -1, 3422, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3423, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3423, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, 3424, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3425, -1, -1, -1,
+ -1, -1, -1, -1, 3426, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3427, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3428, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 3425, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3426, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3427,
- -1, -1, -1, 3428, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3429, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3430, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -10804,17 +10979,19 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3431, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3432, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ 3433, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3429,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -10823,143 +11000,130 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3434, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3430, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 3431, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3432, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3433, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3434, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3435, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3435, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
3436, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3437,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3437, -1, -1, -1, -1,
+ -1, -1, 3438, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 3438, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3439,
- -1, -1, -1, 3440, -1, -1, -1, -1,
+ 3439, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3440, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3441, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3441,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3442, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3443, 3444, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3445, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3446, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3442, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3447, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3448, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3449, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3443, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ 3450, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3451, -1, -1, -1,
+ -1, 3452, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3453,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3444, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3454, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3445, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3446, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3455, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3456, -1, -1, -1,
+ 3457, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3458, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3459,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3460, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3447, -1, -1, -1, -1,
- 3448, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3461, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3462, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3463,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ 3464, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3449,
+ -1, -1, 3465, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3466, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3467, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3450, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3468, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3451, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3469, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3470, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3471, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3452,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -10968,163 +11132,11 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ 3472, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 3453, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3454, -1, -1, -1, -1,
- -1, -1, 3455, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3456, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3457, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3458, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3459, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3460,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3461,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3462, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 3463, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3464, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3465,
- -1, -1, -1, -1, -1, -1, -1, 3466,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3467, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3468, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3469, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 3470, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3471, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3472, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3473, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3474, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3473, -1,
+ -1, -1, -1, 3474, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -11132,27 +11144,25 @@
-1, -1, 3475, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3476, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3476, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, 3477, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3478, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3479, -1,
+ 3478, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3480, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3479,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3480, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -11161,58 +11171,25 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3482, -1, -1, -1, 3483, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3482, -1,
+ -1, -1, -1, -1, -1, 3484, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3485,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3486, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3483, -1, -1, -1, -1,
+ -1, 3487, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 3484, 3485, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3486, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3487, -1, -1, -1, -1, -1,
- 3488, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3489,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
+ 3488, -1, -1, -1, -1, -1, 3489, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, 3490, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -11226,8 +11203,20 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3491, -1, 3492, -1, -1, -1,
- -1, 3493, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3491, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3492, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3493,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3494, -1, -1, -1, 3495, 3496, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3497, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -11236,194 +11225,97 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3494, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3495,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3496, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3497,
- -1, -1, -1, -1, -1, -1, -1, -1,
-1, 3498, -1, -1, -1, -1, -1, -1,
- -1, -1, 3499, -1, -1, -1, -1, -1,
+ -1, 3499, 3500, -1, -1, -1, -1, 3501,
+ -1, -1, 3502, -1, -1, -1, 3503, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3504, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3500, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3501, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3502, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3503, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3504, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3505, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3505,
-1, -1, -1, 3506, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3507, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3507, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, 3508, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3509, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3509,
-1, -1, -1, -1, -1, -1, -1, 3510,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, 3511, -1, -1, -1, -1, -1,
+ -1, 3512, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3513, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3514, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3515, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3512, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3516, -1, -1,
+ 3517, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3518, -1,
+ -1, -1, -1, -1, -1, 3519, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3513, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3520, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3521,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3522, -1, -1, -1, -1, 3523, -1,
+ -1, -1, -1, -1, -1, -1, 3524, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3525, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3514,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3515, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3526, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ 3527, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3528, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3516, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3517,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3529, -1, -1, 3530, -1, -1, -1,
+ -1, 3531, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3532,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3533, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3518, -1, -1, -1,
+ 3534, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3535, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ 3536, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -11435,259 +11327,52 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3537, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3538, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3539, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3540, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3541, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3542,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3543, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3544, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3545, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3519,
- -1, 3520, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3521, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3522, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3523, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3524, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3525,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3526,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3527, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3528, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3529, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3530, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3531, -1, -1, 3532, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3533,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3534, -1, -1, -1,
- -1, 3535, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3536, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 3537, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3538, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3539, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3540,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3541, -1, -1, -1, -1,
- -1, -1, -1, 3542, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3543, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3544, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3545, -1, -1, -1, -1, -1, -1,
- -1, -1, 3546, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3546, -1, -1, -1,
-1, 3547, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3548, -1, -1, -1,
+ -1, -1, 3549, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3550, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3551, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -11695,6 +11380,7 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3552, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -11702,75 +11388,17 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3553, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 3548, -1, 3549, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3550, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3551, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3552, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3553, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3554,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3554, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, 3555, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3556, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3557, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3558, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -11782,18 +11410,22 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3559, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ 3560, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3561,
+ -1, -1, -1, 3562, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3556, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3563, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -11801,11 +11433,13 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3564, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3565, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -11814,7 +11448,6 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3557, -1, -1, -1, 3558, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -11826,45 +11459,48 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3566, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3559, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3560, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3567, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3568, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3569, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3570, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3561, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3571, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -11882,23 +11518,22 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- 3562, -1, -1, -1, -1, -1, -1, -1,
- 3563, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3564, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3572, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3573, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -11911,114 +11546,22 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3574, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3575, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3565,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3566, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3567, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3568, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3569, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3570, -1, -1, -1, -1, 3571,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3572, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3573, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3574, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3575, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3576, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3576, -1, -1,
-1, -1, -1, 3577, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -12028,25 +11571,20 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3578, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3578, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, 3579,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3580, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3581, -1, -1, -1, 3582,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3583, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -12067,15 +11605,17 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3580, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3581,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3582, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3584, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -12083,11 +11623,11 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3585, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3583, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -12099,13 +11639,16 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3584, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3585, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3586, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -12126,6 +11669,7 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3587, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -12141,210 +11685,41 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3588, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3589, -1, -1, 3590,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3586, -1,
+ -1, 3591, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3592, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3593, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3594, -1, 3595, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ 3596, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3597,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3587,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3588, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ 3598, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3589,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 3590, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 3591, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3592, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3593, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3594, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3595, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3596, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- 3597, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3598, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3599, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3599, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -12357,17 +11732,21 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3601, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ 3602, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3603, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3604, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3601, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3605, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -12375,6 +11754,7 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ 3606, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -12389,262 +11769,15 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3607, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3608, -1, 3609, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3602, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3603, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3604, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3605, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3606, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3607, -1, -1, -1,
- -1, 3608, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 3609, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3610,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3610, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -12654,6 +11787,7 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, 3611, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3612,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -12662,11 +11796,12 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3613, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3614, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3612, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -12680,6 +11815,7 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3615, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -12687,96 +11823,22 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3616,
+ -1, -1, -1, -1, -1, -1, 3617, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3613, -1,
- -1, -1, -1, -1, -1, 3614, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3615, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3616, -1, -1, -1, -1,
- -1, -1, -1, -1, 3617, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
+ 3618, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3618,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, 3619,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3620, -1, -1, -1, -1, -1,
+ -1, -1, 3621, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -12784,12 +11846,12 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3622, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3620, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -12815,6 +11877,7 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ 3623, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -12834,6 +11897,7 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3624, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -12843,6 +11907,7 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
+ 3625, -1, -1, -1, -1, 3626, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -12855,344 +11920,7 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3621,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3622, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3623, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3624, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 3625, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, 3626, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3627, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3627, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -13204,7 +11932,2453 @@
-1, -1, -1, -1, -1, -1, -1, 3628,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3629, 3630, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3629, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3630, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3631, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3632, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3633, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3634,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3635, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3636, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3637, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3638, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3639, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3640, -1,
+ -1, -1, -1, -1, 3641, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3642, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3643, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3644,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3645, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3646, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3647, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3648, -1,
+ -1, 3649, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3650, -1, -1, -1,
+ -1, -1, -1, -1, 3651, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3652,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3653, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3654, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3655,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3656, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3657, -1, -1, -1,
+ -1, -1, -1, -1, 3658, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3659,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3660, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3661, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3662, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3663, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3664, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3665, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3666, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3667, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3668, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3669, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3670, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3671, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3672, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3673, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3674, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3675, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3676, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3677, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3678, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3679, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3680, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3681, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3682, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3683, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3684, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3685, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3686, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3687,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3688, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3689, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3690, -1,
+ -1, -1, -1, -1, 3691, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3692, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3693, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3694, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3695, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3696, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -13300,8 +14474,6 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, 3631, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3632, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -13355,7 +14527,6 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, 3633, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -13503,7 +14674,6 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3634, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -13578,7 +14748,6 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, 3635,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -14367,7 +15536,7 @@
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 3636
+ -1, -1, -1, 3697
};
if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH)
@@ -14389,5 +15558,5 @@
}
return 0;
}
-#line 3651 "effective_tld_names.gperf"
+#line 3712 "effective_tld_names.gperf"
diff --git a/net/base/effective_tld_names.dat b/net/base/effective_tld_names.dat
index b6de8d1..0298da0 100644
--- a/net/base/effective_tld_names.dat
+++ b/net/base/effective_tld_names.dat
@@ -1,30 +1,30 @@
// ***** BEGIN LICENSE BLOCK *****
// Version: MPL 1.1/GPL 2.0/LGPL 2.1
-//
-// The contents of this file are subject to the Mozilla Public License Version
-// 1.1 (the "License"); you may not use this file except in compliance with
-// the License. You may obtain a copy of the License at
+//
+// The contents of this file are subject to the Mozilla Public License Version
+// 1.1 (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.mozilla.org/MPL/
-//
+//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
// for the specific language governing rights and limitations under the
// License.
-//
+//
// The Original Code is the Public Suffix List.
-//
+//
// The Initial Developer of the Original Code is
// Jo Hermans <jo.hermans@gmail.com>.
// Portions created by the Initial Developer are Copyright (C) 2007
// the Initial Developer. All Rights Reserved.
-//
+//
// Contributor(s):
// Ruben Arakelyan <ruben@wackomenace.co.uk>
// Gervase Markham <gerv@gerv.net>
// Pamela Greene <pamg.bugs@gmail.com>
// David Triendl <david@triendl.name>
// The kind representatives of many TLD registries
-//
+//
// Alternatively, the contents of this file may be used under the terms of
// either the GNU General Public License Version 2 or later (the "GPL"), or
// the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
@@ -36,11 +36,11 @@
// and other provisions required by the GPL or the LGPL. If you do not delete
// the provisions above, a recipient may use your version of this file under
// the terms of any one of the MPL, the GPL or the LGPL.
-//
+//
// ***** END LICENSE BLOCK *****
-// Chromium note: this based on Mozilla's file:
-// http://mxr.mozilla.org/mozilla-central/source/netwerk/dns/src/effective_tld_names.dat?raw=1
+// Chromium note: this is based on Mozilla's file:
+// http://mxr.mozilla.org/mozilla-central/source/netwerk/dns/effective_tld_names.dat?raw=1
// ac : http://en.wikipedia.org/wiki/.ac
ac
@@ -181,13 +181,14 @@
net.ai
org.ai
-// al : http://www.inima.al/Domains.html
+// al : http://www.ert.gov.al/ert_alb/faq_det.html?Id=31
al
-gov.al
-edu.al
-org.al
com.al
+edu.al
+gov.al
+mil.al
net.al
+org.al
// am : http://en.wikipedia.org/wiki/.am
am
@@ -229,6 +230,7 @@
e164.arpa
in-addr.arpa
ip6.arpa
+iris.arpa
uri.arpa
urn.arpa
@@ -242,11 +244,15 @@
// at : http://en.wikipedia.org/wiki/.at
// Confirmed by registry <it@nic.at> 2008-06-17
at
-gv.at
ac.at
co.at
+gv.at
or.at
+// http://www.info.at/
+biz.at
+info.at
+
// priv.at : http://www.nic.priv.at/
// Submitted by registry <lendl@nic.at> 2008-06-09
priv.at
@@ -263,13 +269,23 @@
vic.edu.au
wa.edu.au
act.gov.au
-nsw.gov.au
+// Removed at request of Shae.Donelan@services.nsw.gov.au, 2010-03-04
+// nsw.gov.au
nt.gov.au
qld.gov.au
sa.gov.au
tas.gov.au
vic.gov.au
wa.gov.au
+// CGDNs - http://www.aucd.org.au/
+act.au
+nsw.au
+nt.au
+qld.au
+sa.au
+tas.au
+vic.au
+wa.au
// aw : http://en.wikipedia.org/wiki/.aw
aw
@@ -308,11 +324,14 @@
// bb : http://en.wikipedia.org/wiki/.bb
bb
+biz.bb
com.bb
edu.bb
gov.bb
+info.bb
net.bb
org.bb
+store.bb
// bd : http://en.wikipedia.org/wiki/.bd
*.bd
@@ -364,12 +383,15 @@
6.bg
7.bg
8.bg
-9.bg
+9.bg
// bh : http://en.wikipedia.org/wiki/.bh
-// list of other 2nd level tlds ?
bh
com.bh
+edu.bh
+net.bh
+org.bh
+gov.bh
// bi : http://en.wikipedia.org/wiki/.bi
// http://whois.nic.bi/
@@ -384,8 +406,10 @@
biz
// bj : http://en.wikipedia.org/wiki/.bj
-// list of 2nd level tlds ?
bj
+asso.bj
+barreau.bj
+gouv.bj
// bm : http://www.bermudanic.bm/dnr-text.txt
bm
@@ -510,6 +534,9 @@
// www.yahoo.com.by, for example), so we list it here for safety's sake.
com.by
+// http://hoster.by/
+of.by
+
// bz : http://en.wikipedia.org/wiki/.bz
// http://www.belizenic.bz/
bz
@@ -681,6 +708,9 @@
uy.com
za.com
+// Requested by Yngve Pettersen <yngve@opera.com> 2009-11-26
+operaunite.com
+
// coop : http://en.wikipedia.org/wiki/.coop
coop
@@ -810,6 +840,8 @@
// completely removed.
// TODO: Check for updates (expected to be phased out around Q1/2009)
aland.fi
+// iki.fi : Submitted by Hannu Aronsson <haa@iki.fi> 2009-11-05
+iki.fi
// fj : http://en.wikipedia.org/wiki/.fj
*.fj
@@ -982,7 +1014,7 @@
网絡.hk
组织.hk
組織.hk
-組织.hk
+組织.hk
// hm : http://en.wikipedia.org/wiki/.hm
hm
@@ -1119,7 +1151,9 @@
org.iq
net.iq
-// ir : http://www.nic.ir/ascii/Appendix1.htm
+// ir : http://www.nic.ir/Terms_and_Conditions_ir,_Appendix_1_Domain_Rules
+// Also see http://www.nic.ir/Internationalized_Domain_Names
+// Two <iran>.ir entries added at request of <tech-team@nic.ir>, 2010-04-16
ir
ac.ir
co.ir
@@ -1128,6 +1162,10 @@
net.ir
org.ir
sch.ir
+// xn--mgba3a4f16a.ir (<iran>.ir, Persian YEH)
+ایران.ir
+// xn--mgba3a4fra.ir (<iran>.ir, Arabic YEH)
+ايران.ir
// is : http://www.isnic.is/domain/rules.php
// Confirmed by registry <marius@isgate.is> 2008-12-06
@@ -1409,7 +1447,7 @@
// Submitted by registry <yone@jprs.co.jp> 2008-06-11
// Updated by registry <yone@jprs.co.jp> 2008-12-04
jp
-// jp organizational type names
+// jp organizational type names
ac.jp
ad.jp
co.jp
@@ -1809,7 +1847,15 @@
name.mk
// ml : http://www.gobin.info/domainname/ml-template.doc
-*.ml
+// see also: http://en.wikipedia.org/wiki/.ml
+ml
+com.ml
+edu.ml
+gouv.ml
+gov.ml
+net.ml
+org.ml
+presse.ml
// mm : http://en.wikipedia.org/wiki/.mm
*.mm
@@ -2411,7 +2457,22 @@
иком.museum
// mv : http://en.wikipedia.org/wiki/.mv
-*.mv
+// "mv" included because, contra Wikipedia, google.mv exists.
+mv
+aero.mv
+biz.mv
+com.mv
+coop.mv
+edu.mv
+gov.mv
+info.mv
+int.mv
+mil.mv
+museum.mv
+name.mv
+net.mv
+org.mv
+pro.mv
// mw : http://www.registrar.mw/
mw
@@ -2423,6 +2484,7 @@
edu.mw
gov.mw
int.mw
+museum.mw
net.mw
org.mw
@@ -2474,6 +2536,7 @@
// nc : http://www.cctld.nc/
nc
+asso.nc
// ne : http://en.wikipedia.org/wiki/.ne
ne
@@ -2524,7 +2587,7 @@
// no : http://www.norid.no/regelverk/index.en.html
// The Norwegian registry has declined to notify us of updates. The web pages
// referenced below are the official source of the data. There is also an
-// announce mailing list:
+// announce mailing list:
// https://postlister.uninett.no/sympa/info/norid-diskusjon
no
// Norid generic domains : http://www.norid.no/regelverk/vedlegg-c.en.html
@@ -3438,7 +3501,6 @@
po.gov.pl
pa.gov.pl
// other functional domains
-med.pl
ngo.pl
irc.pl
usenet.pl
@@ -3528,6 +3590,7 @@
rzeszow.pl
sanok.pl
sejny.pl
+siedlce.pl
slask.pl
slupsk.pl
sosnowiec.pl
@@ -3566,6 +3629,7 @@
gda.pl
gdansk.pl
gdynia.pl
+med.pl
sopot.pl
// other geographical domains
gliwice.pl
@@ -3829,6 +3893,7 @@
gouv.rw
// sa : http://www.nic.net.sa/
+sa
com.sa
net.sa
org.sa
@@ -3947,23 +4012,31 @@
// sn : http://en.wikipedia.org/wiki/.sn
sn
+art.sn
+com.sn
+edu.sn
+gouv.sn
+org.sn
+perso.sn
+univ.sn
// sr : http://en.wikipedia.org/wiki/.sr
sr
// st : http://www.nic.st/html/policyrules/
st
-gov.st
-saotome.st
-principe.st
-consulado.st
-org.st
-edu.st
-net.st
-com.st
-store.st
-mil.st
co.st
+com.st
+consulado.st
+edu.st
+embaixada.st
+gov.st
+mil.st
+net.st
+org.st
+principe.st
+saotome.st
+store.st
// su : http://en.wikipedia.org/wiki/.su
su
@@ -4021,17 +4094,19 @@
tj
ac.tj
biz.tj
-com.tj
co.tj
+com.tj
edu.tj
+go.tj
+gov.tj
int.tj
+mil.tj
name.tj
net.tj
+nic.tj
org.tj
+test.tj
web.tj
-gov.tj
-go.tj
-mil.tj
// tk : http://en.wikipedia.org/wiki/.tk
tk
@@ -4080,6 +4155,9 @@
// tr : http://en.wikipedia.org/wiki/.tr
*.tr
+// Used by government in the TRNC
+// http://en.wikipedia.org/wiki/.nc.tr
+gov.nc.tr
// travel : http://en.wikipedia.org/wiki/.travel
travel
@@ -4105,12 +4183,9 @@
edu.tt
// tv : http://en.wikipedia.org/wiki/.tv
-// list of other 2nd level tlds ?
+// Not listing any 2LDs as reserved since none seem to exist in practice,
+// Wikipedia notwithstanding.
tv
-com.tv
-net.tv
-org.tv
-gov.tv
// tw : http://en.wikipedia.org/wiki/.tw
tw
@@ -4349,6 +4424,18 @@
gov.ws
edu.ws
+// xn--mgbaam7a8h (UAE) : http://nic.ae/english/arabicdomain/rules.jsp
+امارات
+
+// xn--mgberp4a5d4ar (Saudi Arabia) : http://www.nic.net.sa/
+السعودية
+
+// xn--p1ai (Russia) : http://www.cctld.ru/en/docs/rulesrf.php
+рф
+
+// xn--wgbh1c (Egypt) : http://www.dotmasr.eg/
+مصر
+
// ye : http://www.y.net.ye/services/domain_name.htm
*.ye
diff --git a/net/base/effective_tld_names.gperf b/net/base/effective_tld_names.gperf
index 8f511cf..48ac9d3 100644
--- a/net/base/effective_tld_names.gperf
+++ b/net/base/effective_tld_names.gperf
@@ -63,6 +63,7 @@
academy.museum, 0
accident-investigation.aero, 0
accident-prevention.aero, 0
+act.au, 0
act.edu.au, 0
act.gov.au, 0
ad, 0
@@ -75,6 +76,7 @@
ae.org, 0
aejrie.no, 0
aero, 0
+aero.mv, 0
aero.tt, 0
aerobatic.aero, 0
aeroclub.aero, 0
@@ -184,6 +186,7 @@
art.ht, 0
art.museum, 0
art.pl, 0
+art.sn, 0
artanddesign.museum, 0
artcenter.museum, 0
artdeco.museum, 0
@@ -212,6 +215,7 @@
assedic.fr, 0
assisi.museum, 0
assn.lk, 0
+asso.bj, 0
asso.ci, 0
asso.dz, 0
asso.fr, 0
@@ -219,6 +223,7 @@
asso.ht, 0
asso.km, 0
asso.mc, 0
+asso.nc, 0
asso.re, 0
association.aero, 0
association.museum, 0
@@ -284,6 +289,7 @@
bari.it, 0
barletta-andria-trani.it, 0
barlettaandriatrani.it, 0
+barreau.bj, 0
barum.no, 0
baseball.museum, 0
basel.museum, 0
@@ -338,8 +344,11 @@
birkenes.no, 0
birthplace.museum, 0
biz, 0
+biz.at, 0
biz.az, 0
+biz.bb, 0
biz.ki, 0
+biz.mv, 0
biz.mw, 0
biz.nr, 0
biz.pk, 0
@@ -643,8 +652,10 @@
com.ly, 0
com.mg, 0
com.mk, 0
+com.ml, 0
com.mo, 0
com.mu, 0
+com.mv, 0
com.mw, 0
com.mx, 0
com.my, 0
@@ -671,13 +682,13 @@
com.sd, 0
com.sg, 0
com.sl, 0
+com.sn, 0
com.st, 0
com.sy, 0
com.tj, 0
com.tn, 0
com.to, 0
com.tt, 0
-com.tv, 0
com.tw, 0
com.ua, 0
com.uz, 0
@@ -705,6 +716,7 @@
coop.br, 0
coop.ht, 0
coop.km, 0
+coop.mv, 0
coop.mw, 0
coop.tt, 0
copenhagen.museum, 0
@@ -820,6 +832,7 @@
edu.az, 0
edu.ba, 0
edu.bb, 0
+edu.bh, 0
edu.bi, 0
edu.bm, 0
edu.bo, 0
@@ -865,8 +878,10 @@
edu.me, 0
edu.mg, 0
edu.mk, 0
+edu.ml, 0
edu.mn, 0
edu.mo, 0
+edu.mv, 0
edu.mw, 0
edu.mx, 0
edu.my, 0
@@ -891,6 +906,7 @@
edu.sd, 0
edu.sg, 0
edu.sl, 0
+edu.sn, 0
edu.st, 0
edu.sy, 0
edu.tj, 0
@@ -923,6 +939,7 @@
elk.pl, 0
elvendrell.museum, 0
elverum.no, 0
+embaixada.st, 0
embroidery.museum, 0
emergency.aero, 0
en.it, 0
@@ -1172,11 +1189,14 @@
gorizia.it, 0
gorlice.pl, 0
gos.pk, 0
+gouv.bj, 0
gouv.ci, 0
gouv.fr, 0
gouv.ht, 0
gouv.km, 0
+gouv.ml, 0
gouv.rw, 0
+gouv.sn, 0
gov, 0
gov.ac, 0
gov.ae, 0
@@ -1187,6 +1207,7 @@
gov.ba, 0
gov.bb, 0
gov.bf, 0
+gov.bh, 0
gov.bm, 0
gov.bo, 0
gov.br, 0
@@ -1238,12 +1259,15 @@
gov.me, 0
gov.mg, 0
gov.mk, 0
+gov.ml, 0
gov.mn, 0
gov.mo, 0
gov.mr, 0
gov.mu, 0
+gov.mv, 0
gov.mw, 0
gov.my, 0
+gov.nc.tr, 0
gov.ng, 0
gov.nr, 0
gov.ph, 0
@@ -1269,7 +1293,6 @@
gov.tn, 0
gov.to, 0
gov.tt, 0
-gov.tv, 0
gov.tw, 0
gov.ua, 0
gov.vc, 0
@@ -1448,6 +1471,7 @@
idv.tw, 0
ie, 0
if.ua, 0
+iki.fi, 0
il, 2
il.us, 0
ilawa.pl, 0
@@ -1477,13 +1501,16 @@
inf.cu, 0
inf.mk, 0
info, 0
+info.at, 0
info.az, 0
+info.bb, 0
info.co, 0
info.ec, 0
info.ht, 0
info.hu, 0
info.ki, 0
info.la, 0
+info.mv, 0
info.na, 0
info.nf, 0
info.nr, 0
@@ -1506,6 +1533,7 @@
int.is, 0
int.la, 0
int.lk, 0
+int.mv, 0
int.mw, 0
int.pt, 0
int.ru, 0
@@ -1522,6 +1550,7 @@
ir, 0
iraq.museum, 0
irc.pl, 0
+iris.arpa, 0
irkutsk.ru, 0
iron.museum, 0
is, 0
@@ -1913,6 +1942,7 @@
mil, 0
mil.ac, 0
mil.ae, 0
+mil.al, 0
mil.az, 0
mil.ba, 0
mil.bo, 0
@@ -1933,6 +1963,7 @@
mil.kz, 0
mil.lv, 0
mil.mg, 0
+mil.mv, 0
mil.my, 0
mil.no, 0
mil.pe, 0
@@ -1961,7 +1992,7 @@
mjondalen.no, 0
mk, 0
mk.ua, 0
-ml, 2
+ml, 0
mm, 2
mn, 0
mn.it, 0
@@ -2020,12 +2051,14 @@
mus.br, 0
museet.museum, 0
museum, 0
+museum.mv, 0
+museum.mw, 0
museum.no, 0
museum.tt, 0
museumcenter.museum, 0
museumvereniging.museum, 0
music.museum, 0
-mv, 2
+mv, 0
mw, 0
mx, 0
mx.na, 0
@@ -2050,6 +2083,7 @@
name.hr, 0
name.jo, 0
name.mk, 0
+name.mv, 0
name.my, 0
name.na, 0
name.pr, 0
@@ -2115,6 +2149,7 @@
net.az, 0
net.ba, 0
net.bb, 0
+net.bh, 0
net.bm, 0
net.bo, 0
net.br, 0
@@ -2158,8 +2193,10 @@
net.ma, 0
net.me, 0
net.mk, 0
+net.ml, 0
net.mo, 0
net.mu, 0
+net.mv, 0
net.mw, 0
net.mx, 0
net.my, 0
@@ -2190,7 +2227,6 @@
net.tn, 0
net.to, 0
net.tt, 0
-net.tv, 0
net.tw, 0
net.ua, 0
net.vc, 0
@@ -2217,6 +2253,7 @@
nic.ar, 1
nic.im, 0
nic.in, 0
+nic.tj, 0
niepce.museum, 0
nieruchomosci.pl, 0
niigata.jp, 2
@@ -2275,8 +2312,9 @@
ns.ca, 0
nsk.ru, 0
nsn.us, 0
+nsw.au, 0
nsw.edu.au, 0
-nsw.gov.au, 0
+nt.au, 0
nt.ca, 0
nt.edu.au, 0
nt.gov.au, 0
@@ -2304,6 +2342,7 @@
odda.no, 0
odessa.ua, 0
odo.br, 0
+of.by, 0
of.no, 0
off.ai, 0
og.ao, 0
@@ -2326,6 +2365,7 @@
online.museum, 0
ontario.museum, 0
openair.museum, 0
+operaunite.com, 0
opoczno.pl, 0
opole.pl, 0
oppdal.no, 0
@@ -2358,6 +2398,7 @@
org.az, 0
org.ba, 0
org.bb, 0
+org.bh, 0
org.bi, 0
org.bm, 0
org.bo, 0
@@ -2410,9 +2451,11 @@
org.me, 0
org.mg, 0
org.mk, 0
+org.ml, 0
org.mn, 0
org.mo, 0
org.mu, 0
+org.mv, 0
org.mw, 0
org.mx, 0
org.my, 0
@@ -2439,6 +2482,7 @@
org.se, 0
org.sg, 0
org.sl, 0
+org.sn, 0
org.st, 0
org.sy, 0
org.sz, 0
@@ -2446,7 +2490,6 @@
org.tn, 0
org.to, 0
org.tt, 0
-org.tv, 0
org.tw, 0
org.ua, 0
org.vc, 0
@@ -2520,6 +2563,7 @@
per.sg, 0
perm.ru, 0
perso.ht, 0
+perso.sn, 0
perso.tn, 0
perugia.it, 0
pesaro-urbino.it, 0
@@ -2646,6 +2690,7 @@
presse.ci, 0
presse.fr, 0
presse.km, 0
+presse.ml, 0
pri.ee, 0
principe.st, 0
priv.at, 0
@@ -2658,6 +2703,7 @@
pro.br, 0
pro.ec, 0
pro.ht, 0
+pro.mv, 0
pro.na, 0
pro.pr, 0
pro.tt, 0
@@ -2693,6 +2739,7 @@
qc.ca, 0
qc.com, 0
qh.cn, 0
+qld.au, 0
qld.edu.au, 0
qld.gov.au, 0
qsl.br, 0
@@ -2798,6 +2845,7 @@
s.bg, 0
s.se, 0
sa, 0
+sa.au, 0
sa.com, 0
sa.cr, 0
sa.edu.au, 0
@@ -2910,6 +2958,7 @@
si, 0
si.it, 0
sibenik.museum, 0
+siedlce.pl, 0
siellak.no, 0
siena.it, 0
sigdal.no, 0
@@ -3026,6 +3075,7 @@
stor-elvdal.no, 0
stord.no, 0
stordal.no, 0
+store.bb, 0
store.nf, 0
store.ro, 0
store.st, 0
@@ -3081,6 +3131,7 @@
taranto.it, 0
targi.pl, 0
tarnobrzeg.pl, 0
+tas.au, 0
tas.edu.au, 0
tas.gov.au, 0
tatarstan.ru, 0
@@ -3098,6 +3149,7 @@
terni.it, 0
ternopil.ua, 0
test.ru, 0
+test.tj, 0
texas.museum, 0
textile.museum, 0
tf, 0
@@ -3234,6 +3286,7 @@
unbi.ba, 0
undersea.museum, 0
union.aero, 0
+univ.sn, 0
university.museum, 0
unjarga.no, 0
unsa.ba, 0
@@ -3322,6 +3375,7 @@
vi.us, 0
vibo-valentia.it, 0
vibovalentia.it, 0
+vic.au, 0
vic.edu.au, 0
vic.gov.au, 0
vicenza.it, 0
@@ -3360,6 +3414,7 @@
vyatka.ru, 0
w.bg, 0
w.se, 0
+wa.au, 0
wa.edu.au, 0
wa.gov.au, 0
wa.us, 0
@@ -3506,6 +3561,10 @@
xn--lury-ira.no, 0
xn--mely-ira.no, 0
xn--merker-kua.no, 0
+xn--mgba3a4f16a.ir, 0
+xn--mgba3a4fra.ir, 0
+xn--mgbaam7a8h, 0
+xn--mgberp4a5d4ar, 0
xn--mjndalen-64a.no, 0
xn--mk0axi.hk, 0
xn--mlatvuopmi-s4a.no, 0
@@ -3528,6 +3587,7 @@
xn--oppegrd-ixa.no, 0
xn--ostery-fya.no, 0
xn--osyro-wua.no, 0
+xn--p1ai, 0
xn--porsgu-sta26f.no, 0
xn--rady-ira.no, 0
xn--rdal-poa.no, 0
@@ -3600,6 +3660,7 @@
xn--vrggt-xqad.no, 0
xn--vry-yla5g.no, 0
xn--wcvs22d.hk, 0
+xn--wgbh1c, 0
xn--yer-zna.no, 0
xn--ygarden-p1a.no, 0
xn--ystre-slidre-ujb.no, 0
diff --git a/net/base/escape.cc b/net/base/escape.cc
index bf23bcb..bc01e11 100644
--- a/net/base/escape.cc
+++ b/net/base/escape.cc
@@ -99,7 +99,7 @@
// 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0,
// @ A B C D E F G H I J K L M N O
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
// P Q R S T U V W X Y Z [ \ ] ^ _
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
// ` a b c d e f g h i j k l m n o
diff --git a/net/base/escape_unittest.cc b/net/base/escape_unittest.cc
index 0049528..28972cb 100644
--- a/net/base/escape_unittest.cc
+++ b/net/base/escape_unittest.cc
@@ -9,6 +9,7 @@
#include "base/basictypes.h"
#include "base/i18n/icu_string_conversions.h"
#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
@@ -237,6 +238,10 @@
{L"Hello%20%13%10world %23# %3F? %3D= %26& %25% %2B+",
UnescapeRule::URL_SPECIAL_CHARS,
L"Hello%20%13%10world ## ?? == && %% ++"},
+ // We can neither escape nor unescape '@' since some websites expect it to
+ // be preserved as either '@' or "%40".
+ // See http://b/996720 and http://crbug.com/23933 .
+ {L"me@my%40example", UnescapeRule::NORMAL, L"me@my%40example"},
// Control characters.
{L"%01%02%03%04%05%06%07%08%09 %25", UnescapeRule::URL_SPECIAL_CHARS,
L"%01%02%03%04%05%06%07%08%09 %"},
diff --git a/net/base/ev_root_ca_metadata.cc b/net/base/ev_root_ca_metadata.cc
index 621a174..023b1fb 100644
--- a/net/base/ev_root_ca_metadata.cc
+++ b/net/base/ev_root_ca_metadata.cc
@@ -20,7 +20,7 @@
struct EVMetadata {
// The SHA-1 fingerprint of the root CA certificate, used as a unique
// identifier for a root CA certificate.
- X509Certificate::Fingerprint fingerprint;
+ SHA1Fingerprint fingerprint;
// The EV policy OID of the root CA.
// Note: a root CA may have multiple EV policies. When that actually
@@ -35,6 +35,12 @@
0x57, 0x69, 0x4d, 0xf5, 0xe4, 0x5b, 0x68, 0x85, 0x18, 0x68 } },
"1.3.6.1.4.1.6449.1.2.1.5.1"
},
+ // CertPlus Class 2 Primary CA (KEYNECTIS)
+ // https://www.keynectis.com/
+ { { { 0x74, 0x20, 0x74, 0x41, 0x72, 0x9c, 0xdd, 0x92, 0xec, 0x79,
+ 0x31, 0xd8, 0x23, 0x10, 0x8d, 0xc2, 0x81, 0x92, 0xe2, 0xbb } },
+ "1.3.6.1.4.1.22234.2.5.2.3.1"
+ },
// COMODO Certification Authority
// https://secure.comodo.com/
{ { { 0x66, 0x31, 0xbf, 0x9e, 0xf7, 0x4f, 0x9e, 0xb6, 0xc9, 0xd5,
@@ -149,6 +155,12 @@
0x3d, 0xd8, 0x90, 0x8f, 0xfd, 0x28, 0x86, 0x65, 0x64, 0x7d } },
"1.2.392.200091.100.721.1"
},
+ // StartCom Certification Authority
+ // https://www.startssl.com/
+ { { { 0x3e, 0x2b, 0xf7, 0xf2, 0x03, 0x1b, 0x96, 0xf3, 0x8c, 0xe6,
+ 0xc4, 0xd8, 0xa8, 0x5d, 0x3e, 0x2d, 0x58, 0x47, 0x6a, 0x0f } },
+ "1.3.6.1.4.1.23223.1.1.1"
+ },
// Starfield Class 2 Certification Authority
// https://www.starfieldtech.com/
{ { { 0xad, 0x7e, 0x1c, 0x28, 0xb0, 0x64, 0xef, 0x8f, 0x60, 0x03,
@@ -221,7 +233,7 @@
}
bool EVRootCAMetadata::GetPolicyOID(
- const X509Certificate::Fingerprint& fingerprint,
+ const SHA1Fingerprint& fingerprint,
PolicyOID* policy_oid) const {
PolicyOidMap::const_iterator iter = ev_policy_.find(fingerprint);
if (iter == ev_policy_.end())
diff --git a/net/base/ev_root_ca_metadata.h b/net/base/ev_root_ca_metadata.h
index f006878..b1b2781 100644
--- a/net/base/ev_root_ca_metadata.h
+++ b/net/base/ev_root_ca_metadata.h
@@ -35,7 +35,7 @@
// If the root CA cert has an EV policy OID, returns true and stores the
// policy OID in *policy_oid. Otherwise, returns false.
- bool GetPolicyOID(const X509Certificate::Fingerprint& fingerprint,
+ bool GetPolicyOID(const SHA1Fingerprint& fingerprint,
PolicyOID* policy_oid) const;
const PolicyOID* GetPolicyOIDs() const { return &policy_oids_[0]; }
@@ -47,8 +47,8 @@
friend struct DefaultSingletonTraits<EVRootCAMetadata>;
- typedef std::map<X509Certificate::Fingerprint, PolicyOID,
- X509Certificate::FingerprintLessThan> PolicyOidMap;
+ typedef std::map<SHA1Fingerprint, PolicyOID,
+ SHA1FingerprintLessThan> PolicyOidMap;
// Maps an EV root CA cert's SHA-1 fingerprint to its EV policy OID.
PolicyOidMap ev_policy_;
diff --git a/net/base/file_stream.h b/net/base/file_stream.h
index 6b7f3dc..a6d5739 100644
--- a/net/base/file_stream.h
+++ b/net/base/file_stream.h
@@ -34,6 +34,8 @@
// |file| is valid file handle.
// |flags| is a bitfield of base::PlatformFileFlags when the file handle was
// opened.
+ // The already opened file will not be automatically closed when FileStream
+ // is destructed.
FileStream(base::PlatformFile file, int flags);
~FileStream();
@@ -118,6 +120,15 @@
// platform with this call.
int64 Truncate(int64 bytes);
+ // Forces out a filesystem sync on this file to make sure that the file was
+ // written out to disk and is not currently sitting in the buffer. This does
+ // not have to be called, it just forces one to happen at the time of
+ // calling.
+ //
+ /// Returns an error code if the operation could not be performed.
+ //
+ // This method should not be called if the stream was opened READ_ONLY.
+ int Flush();
private:
class AsyncContext;
friend class AsyncContext;
@@ -128,6 +139,7 @@
base::PlatformFile file_;
int open_flags_;
+ bool auto_closed_;
DISALLOW_COPY_AND_ASSIGN(FileStream);
};
diff --git a/net/base/file_stream_posix.cc b/net/base/file_stream_posix.cc
index eda0927..ba91db2 100644
--- a/net/base/file_stream_posix.cc
+++ b/net/base/file_stream_posix.cc
@@ -1,6 +1,6 @@
-// Copyright (c) 2008 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.
+// 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.
// For 64-bit file access (off_t = off64_t, lseek64, etc).
#define _FILE_OFFSET_BITS 64
@@ -14,8 +14,10 @@
#include <errno.h>
#include "base/basictypes.h"
+#include "base/callback.h"
#include "base/eintr_wrapper.h"
#include "base/file_path.h"
+#include "base/histogram.h"
#include "base/logging.h"
#include "base/message_loop.h"
#include "base/string_util.h"
@@ -69,6 +71,16 @@
return res;
}
+// FlushFile() is a simple wrapper around fsync() that handles EINTR signals and
+// calls MapErrorCode() to map errno to net error codes. It tries to flush to
+// completion.
+int FlushFile(base::PlatformFile file) {
+ ssize_t res = HANDLE_EINTR(fsync(file));
+ if (res == -1)
+ return MapErrorCode(errno);
+ return res;
+}
+
// BackgroundReadTask is a simple task that reads a file and then runs
// |callback|. AsyncContext will post this task to the WorkerPool.
class BackgroundReadTask : public Task {
@@ -222,11 +234,12 @@
// still running the IO task, or the completion callback is queued up on the
// MessageLoopForIO, but AsyncContext() got deleted before then.
const bool need_to_wait = !background_io_completed_.IsSignaled();
- base::Time start = base::Time::Now();
+ base::TimeTicks start = base::TimeTicks::Now();
RunAsynchronousCallback();
if (need_to_wait) {
// We want to see if we block the message loop for too long.
- UMA_HISTOGRAM_TIMES("AsyncIO.FileStreamClose", base::Time::Now() - start);
+ UMA_HISTOGRAM_TIMES("AsyncIO.FileStreamClose",
+ base::TimeTicks::Now() - start);
}
}
}
@@ -292,13 +305,15 @@
FileStream::FileStream()
: file_(base::kInvalidPlatformFileValue),
- open_flags_(0) {
+ open_flags_(0),
+ auto_closed_(true) {
DCHECK(!IsOpen());
}
FileStream::FileStream(base::PlatformFile file, int flags)
: file_(file),
- open_flags_(flags) {
+ open_flags_(flags),
+ auto_closed_(false) {
// If the file handle is opened with base::PLATFORM_FILE_ASYNC, we need to
// make sure we will perform asynchronous File IO to it.
if (flags & base::PLATFORM_FILE_ASYNC) {
@@ -307,7 +322,8 @@
}
FileStream::~FileStream() {
- Close();
+ if (auto_closed_)
+ Close();
}
void FileStream::Close() {
@@ -440,6 +456,13 @@
}
}
+int FileStream::Flush() {
+ if (!IsOpen())
+ return ERR_UNEXPECTED;
+
+ return FlushFile(file_);
+}
+
int64 FileStream::Truncate(int64 bytes) {
if (!IsOpen())
return ERR_UNEXPECTED;
diff --git a/net/base/file_stream_unittest.cc b/net/base/file_stream_unittest.cc
index a7a650f..cf80699 100644
--- a/net/base/file_stream_unittest.cc
+++ b/net/base/file_stream_unittest.cc
@@ -1,7 +1,8 @@
-// Copyright (c) 2008 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.
+// 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/callback.h"
#include "base/file_util.h"
#include "base/path_service.h"
#include "base/platform_file.h"
diff --git a/net/base/file_stream_win.cc b/net/base/file_stream_win.cc
index cec6a9d..0460b3d 100644
--- a/net/base/file_stream_win.cc
+++ b/net/base/file_stream_win.cc
@@ -1,12 +1,13 @@
-// Copyright (c) 2008 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.
+// 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 "net/base/file_stream.h"
#include <windows.h>
#include "base/file_path.h"
+#include "base/histogram.h"
#include "base/logging.h"
#include "base/message_loop.h"
#include "net/base/net_errors.h"
@@ -74,14 +75,15 @@
FileStream::AsyncContext::~AsyncContext() {
is_closing_ = true;
bool waited = false;
- base::Time start = base::Time::Now();
+ base::TimeTicks start = base::TimeTicks::Now();
while (callback_) {
waited = true;
MessageLoopForIO::current()->WaitForIOCompletion(INFINITE, this);
}
if (waited) {
// We want to see if we block the message loop for too long.
- UMA_HISTOGRAM_TIMES("AsyncIO.FileStreamClose", base::Time::Now() - start);
+ UMA_HISTOGRAM_TIMES("AsyncIO.FileStreamClose",
+ base::TimeTicks::Now() - start);
}
}
@@ -117,12 +119,14 @@
FileStream::FileStream()
: file_(INVALID_HANDLE_VALUE),
- open_flags_(0) {
+ open_flags_(0),
+ auto_closed_(true) {
}
FileStream::FileStream(base::PlatformFile file, int flags)
: file_(file),
- open_flags_(flags) {
+ open_flags_(flags),
+ auto_closed_(false) {
// If the file handle is opened with base::PLATFORM_FILE_ASYNC, we need to
// make sure we will perform asynchronous File IO to it.
if (flags & base::PLATFORM_FILE_ASYNC) {
@@ -133,7 +137,8 @@
}
FileStream::~FileStream() {
- Close();
+ if (auto_closed_)
+ Close();
}
void FileStream::Close() {
@@ -298,6 +303,21 @@
return rv;
}
+int FileStream::Flush() {
+ if (!IsOpen())
+ return ERR_UNEXPECTED;
+
+ DCHECK(open_flags_ & base::PLATFORM_FILE_WRITE);
+ if (FlushFileBuffers(file_)) {
+ return OK;
+ }
+
+ int rv;
+ DWORD error = GetLastError();
+ rv = MapErrorCode(error);
+ return rv;
+}
+
int64 FileStream::Truncate(int64 bytes) {
if (!IsOpen())
return ERR_UNEXPECTED;
diff --git a/net/base/filter.cc b/net/base/filter.cc
index 4a7b8a6..c72e00e 100644
--- a/net/base/filter.cc
+++ b/net/base/filter.cc
@@ -36,6 +36,9 @@
} // namespace
+FilterContext::~FilterContext() {
+}
+
Filter* Filter::Factory(const std::vector<FilterType>& filter_types,
const FilterContext& filter_context) {
DCHECK_GT(filter_context.GetInputStreamBufferSize(), 0);
diff --git a/net/base/filter.h b/net/base/filter.h
index cf32d2f..acc0e7e 100644
--- a/net/base/filter.h
+++ b/net/base/filter.h
@@ -62,7 +62,7 @@
SDCH_EXPERIMENT_HOLDBACK,
};
- virtual ~FilterContext() {}
+ virtual ~FilterContext();
// What mime type was specified in the header for this data?
// Only makes senses for some types of contexts, and returns false
diff --git a/net/base/forwarding_net_log.cc b/net/base/forwarding_net_log.cc
new file mode 100644
index 0000000..6cea529
--- /dev/null
+++ b/net/base/forwarding_net_log.cc
@@ -0,0 +1,101 @@
+// 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 "net/base/forwarding_net_log.h"
+
+#include "base/lock.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+
+namespace net {
+
+// Reference-counted wrapper, so we can use PostThread and it can safely
+// outlive the parent ForwardingNetLog.
+class ForwardingNetLog::Core
+ : public base::RefCountedThreadSafe<ForwardingNetLog::Core> {
+ public:
+ Core(NetLog* impl, MessageLoop* loop) : impl_(impl), loop_(loop) {
+ DCHECK(impl);
+ DCHECK(loop);
+ }
+
+ // Called once the parent ForwardingNetLog is being destroyed. It
+ // is invalid to access |loop_| and |impl_| afterwards.
+ void Orphan() {
+ AutoLock l(lock_);
+ loop_ = NULL;
+ impl_ = NULL;
+ }
+
+ void AddEntry(EventType type,
+ const base::TimeTicks& time,
+ const Source& source,
+ EventPhase phase,
+ EventParameters* params) {
+ AutoLock l(lock_);
+ if (!loop_)
+ return; // Was orphaned.
+
+ loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(
+ this, &Core::AddEntryOnLoop, type, time, source, phase,
+ scoped_refptr<EventParameters>(params)));
+ }
+
+ private:
+ void AddEntryOnLoop(EventType type,
+ const base::TimeTicks& time,
+ const Source& source,
+ EventPhase phase,
+ scoped_refptr<EventParameters> params) {
+ AutoLock l(lock_);
+ if (!loop_)
+ return; // Was orphaned.
+
+ DCHECK_EQ(MessageLoop::current(), loop_);
+
+ // TODO(eroman): This shouldn't be necessary. See crbug.com/48806.
+ NetLog::Source effective_source = source;
+ if (effective_source.id == NetLog::Source::kInvalidId)
+ effective_source.id = impl_->NextID();
+
+ impl_->AddEntry(type, time, effective_source, phase, params);
+ }
+
+ Lock lock_;
+ NetLog* impl_;
+ MessageLoop* loop_;
+};
+
+ForwardingNetLog::ForwardingNetLog(NetLog* impl, MessageLoop* loop)
+ : core_(new Core(impl, loop)) {
+}
+
+ForwardingNetLog::~ForwardingNetLog() {
+ core_->Orphan();
+}
+
+void ForwardingNetLog::AddEntry(EventType type,
+ const base::TimeTicks& time,
+ const Source& source,
+ EventPhase phase,
+ EventParameters* params) {
+ core_->AddEntry(type, time, source, phase, params);
+}
+
+uint32 ForwardingNetLog::NextID() {
+ // Can't forward a synchronous API.
+ CHECK(false) << "Not supported";
+ return 0;
+}
+
+bool ForwardingNetLog::HasListener() const {
+ // Can't forward a synchronous API.
+ CHECK(false) << "Not supported";
+ return false;
+}
+
+} // namespace net
+
diff --git a/net/base/forwarding_net_log.h b/net/base/forwarding_net_log.h
new file mode 100644
index 0000000..a97d4ac
--- /dev/null
+++ b/net/base/forwarding_net_log.h
@@ -0,0 +1,53 @@
+// 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.
+
+#ifndef NET_BASE_FORWARDING_NET_LOG_H_
+#define NET_BASE_FORWARDING_NET_LOG_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_log.h"
+
+class MessageLoop;
+
+namespace net {
+
+// ForwardingNetLog is a wrapper that can be called on any thread, and will
+// forward any calls to NetLog::AddEntry() over to |impl| on the specified
+// message loop.
+//
+// This allows using a non-threadsafe NetLog implementation from another
+// thread.
+//
+// TODO(eroman): Explore making NetLog threadsafe and obviating the need
+// for this class.
+class ForwardingNetLog : public NetLog {
+ public:
+ // Both |impl| and |loop| must outlive the lifetime of this instance.
+ // |impl| will be operated only from |loop|.
+ ForwardingNetLog(NetLog* impl, MessageLoop* loop);
+
+ // On destruction any outstanding call to AddEntry() which didn't make
+ // it to |loop| yet will be cancelled.
+ ~ForwardingNetLog();
+
+ // NetLog methods:
+ virtual void AddEntry(EventType type,
+ const base::TimeTicks& time,
+ const Source& source,
+ EventPhase phase,
+ EventParameters* params);
+ virtual uint32 NextID();
+ virtual bool HasListener() const;
+
+ private:
+ class Core;
+ scoped_refptr<Core> core_;
+
+ DISALLOW_COPY_AND_ASSIGN(ForwardingNetLog);
+};
+
+} // namespace net
+
+#endif // NET_BASE_FORWARDING_NET_LOG_H_
+
diff --git a/net/base/forwarding_net_log_unittest.cc b/net/base/forwarding_net_log_unittest.cc
new file mode 100644
index 0000000..3f25129
--- /dev/null
+++ b/net/base/forwarding_net_log_unittest.cc
@@ -0,0 +1,84 @@
+// 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 "net/base/forwarding_net_log.h"
+
+#include "base/message_loop.h"
+#include "net/base/capturing_net_log.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+// Test forwarding a call to AddEntry() to another implementation, operating
+// on this same message loop.
+TEST(ForwardingNetLogTest, Basic) {
+ // Create a forwarding NetLog that sends messages to this same thread.
+ CapturingNetLog log(CapturingNetLog::kUnbounded);
+ ForwardingNetLog forwarding(&log, MessageLoop::current());
+
+ EXPECT_EQ(0u, log.entries().size());
+
+ NetLogStringParameter* params = new NetLogStringParameter("xxx", "yyy");
+
+ forwarding.AddEntry(
+ NetLog::TYPE_PAC_JAVASCRIPT_ALERT,
+ base::TimeTicks(),
+ NetLog::Source(),
+ NetLog::PHASE_NONE,
+ params);
+
+ // Should still be empty, since we posted an async task.
+ EXPECT_EQ(0u, log.entries().size());
+
+ MessageLoop::current()->RunAllPending();
+
+ // After draining the message loop, we should now have executed the task
+ // and hence emitted the log entry.
+ ASSERT_EQ(1u, log.entries().size());
+
+ // Check that the forwarded call contained received all the right inputs.
+ EXPECT_EQ(NetLog::TYPE_PAC_JAVASCRIPT_ALERT, log.entries()[0].type);
+ EXPECT_EQ(NetLog::SOURCE_NONE, log.entries()[0].source.type);
+ EXPECT_EQ(NetLog::PHASE_NONE, log.entries()[0].phase);
+ EXPECT_EQ(params, log.entries()[0].extra_parameters.get());
+
+ // Check that the parameters is still referenced. (if the reference was
+ // lost then this will be a memory error and probaby crash).
+ EXPECT_EQ("yyy", params->value());
+}
+
+// Test forwarding a call to AddEntry() to another implementation that runs
+// on the same message loop. However destroy the forwarder before the posted
+// task has a chance to run.
+TEST(ForwardingNetLogTest, Orphan) {
+ // Create a forwarding NetLog that sends messages to this same thread.
+ CapturingNetLog log(CapturingNetLog::kUnbounded);
+ {
+ ForwardingNetLog forwarding(&log, MessageLoop::current());
+ EXPECT_EQ(0u, log.entries().size());
+
+ forwarding.AddEntry(
+ NetLog::TYPE_PAC_JAVASCRIPT_ALERT,
+ base::TimeTicks(),
+ NetLog::Source(),
+ NetLog::PHASE_NONE,
+ NULL);
+
+ // Should still be empty, since we posted an async task.
+ EXPECT_EQ(0u, log.entries().size());
+ }
+
+ // At this point the ForwardingNetLog is deleted. However it had already
+ // posted a task to the message loop. Once we drain the message loop, we
+ // verify that the task didn't actually try to emit to the NetLog.
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(0u, log.entries().size());
+}
+
+} // namespace
+
+} // namespace net
+
diff --git a/net/base/gzip_filter.h b/net/base/gzip_filter.h
index d22b9e4..eb88b7f 100644
--- a/net/base/gzip_filter.h
+++ b/net/base/gzip_filter.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -136,7 +136,7 @@
// we don't get a valid gzip header.
bool possible_sdch_pass_through_;
- DISALLOW_EVIL_CONSTRUCTORS(GZipFilter);
+ DISALLOW_COPY_AND_ASSIGN(GZipFilter);
};
#endif // NET_BASE_GZIP_FILTER_H__
diff --git a/net/base/host_cache.cc b/net/base/host_cache.cc
index 791ecca..28173d2 100644
--- a/net/base/host_cache.cc
+++ b/net/base/host_cache.cc
@@ -35,6 +35,7 @@
const HostCache::Entry* HostCache::Lookup(const Key& key,
base::TimeTicks now) const {
+ DCHECK(CalledOnValidThread());
if (caching_is_disabled())
return NULL;
@@ -51,8 +52,9 @@
HostCache::Entry* HostCache::Set(const Key& key,
int error,
- const AddressList addrlist,
+ const AddressList& addrlist,
base::TimeTicks now) {
+ DCHECK(CalledOnValidThread());
if (caching_is_disabled())
return NULL;
@@ -79,6 +81,37 @@
}
}
+void HostCache::clear() {
+ DCHECK(CalledOnValidThread());
+ entries_.clear();
+}
+
+size_t HostCache::size() const {
+ DCHECK(CalledOnValidThread());
+ return entries_.size();
+}
+
+size_t HostCache::max_entries() const {
+ DCHECK(CalledOnValidThread());
+ return max_entries_;
+}
+
+base::TimeDelta HostCache::success_entry_ttl() const {
+ DCHECK(CalledOnValidThread());
+ return success_entry_ttl_;
+}
+
+base::TimeDelta HostCache::failure_entry_ttl() const {
+ DCHECK(CalledOnValidThread());
+ return failure_entry_ttl_;
+}
+
+// Note that this map may contain expired entries.
+const HostCache::EntryMap& HostCache::entries() const {
+ DCHECK(CalledOnValidThread());
+ return entries_;
+}
+
// static
bool HostCache::CanUseEntry(const Entry* entry, const base::TimeTicks now) {
return entry->expiration > now;
diff --git a/net/base/host_cache.h b/net/base/host_cache.h
index c13c6a0..1b40318 100644
--- a/net/base/host_cache.h
+++ b/net/base/host_cache.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -8,6 +8,7 @@
#include <map>
#include <string>
+#include "base/non_thread_safe.h"
#include "base/ref_counted.h"
#include "base/time.h"
#include "net/base/address_family.h"
@@ -17,7 +18,7 @@
namespace net {
// Cache used by HostResolver to map hostnames to their resolved result.
-class HostCache {
+class HostCache : public NonThreadSafe {
public:
// Stores the latest address list that was looked up for a hostname.
struct Entry : public base::RefCounted<Entry> {
@@ -37,22 +38,35 @@
};
struct Key {
- Key(const std::string& hostname, AddressFamily address_family)
- : hostname(hostname), address_family(address_family) {}
+ Key(const std::string& hostname, AddressFamily address_family,
+ HostResolverFlags host_resolver_flags)
+ : hostname(hostname),
+ address_family(address_family),
+ host_resolver_flags(host_resolver_flags) {}
bool operator==(const Key& other) const {
- return other.hostname == hostname &&
- other.address_family == address_family;
+ // |address_family| and |host_resolver_flags| are compared before
+ // |hostname| under assumption that integer comparisons are faster than
+ // string comparisons.
+ return (other.address_family == address_family &&
+ other.host_resolver_flags == host_resolver_flags &&
+ other.hostname == hostname);
}
bool operator<(const Key& other) const {
- if (address_family == other.address_family)
- return hostname < other.hostname;
- return address_family < other.address_family;
+ // |address_family| and |host_resolver_flags| are compared before
+ // |hostname| under assumption that integer comparisons are faster than
+ // string comparisons.
+ if (address_family != other.address_family)
+ return address_family < other.address_family;
+ if (host_resolver_flags != other.host_resolver_flags)
+ return host_resolver_flags < other.host_resolver_flags;
+ return hostname < other.hostname;
}
std::string hostname;
AddressFamily address_family;
+ HostResolverFlags host_resolver_flags;
};
typedef std::map<Key, scoped_refptr<Entry> > EntryMap;
@@ -76,40 +90,24 @@
// timestamp.
Entry* Set(const Key& key,
int error,
- const AddressList addrlist,
+ const AddressList& addrlist,
base::TimeTicks now);
- // Empties the cache.
- void clear() {
- entries_.clear();
- }
-
- // Returns true if this HostCache can contain no entries.
- bool caching_is_disabled() const {
- return max_entries_ == 0;
- }
+ // Empties the cache
+ void clear();
// Returns the number of entries in the cache.
- size_t size() const {
- return entries_.size();
- }
+ size_t size() const;
- size_t max_entries() const {
- return max_entries_;
- }
+ // Following are used by net_internals UI.
+ size_t max_entries() const;
- base::TimeDelta success_entry_ttl() const {
- return success_entry_ttl_;
- }
+ base::TimeDelta success_entry_ttl() const;
- base::TimeDelta failure_entry_ttl() const {
- return failure_entry_ttl_;
- }
+ base::TimeDelta failure_entry_ttl() const;
// Note that this map may contain expired entries.
- const EntryMap& entries() const {
- return entries_;
- }
+ const EntryMap& entries() const;
private:
FRIEND_TEST(HostCacheTest, Compact);
@@ -122,6 +120,11 @@
// matching |pinned_entry| will NOT be pruned.
void Compact(base::TimeTicks now, const Entry* pinned_entry);
+ // Returns true if this HostCache can contain no entries.
+ bool caching_is_disabled() const {
+ return max_entries_ == 0;
+ }
+
// Bound on total size of the cache.
size_t max_entries_;
diff --git a/net/base/host_cache_unittest.cc b/net/base/host_cache_unittest.cc
index 141cfde..a851c0b 100644
--- a/net/base/host_cache_unittest.cc
+++ b/net/base/host_cache_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -20,7 +20,7 @@
// Builds a key for |hostname|, defaulting the address family to unspecified.
HostCache::Key Key(const std::string& hostname) {
- return HostCache::Key(hostname, ADDRESS_FAMILY_UNSPECIFIED);
+ return HostCache::Key(hostname, ADDRESS_FAMILY_UNSPECIFIED, 0);
}
} // namespace
@@ -276,8 +276,8 @@
// t=0.
base::TimeTicks now;
- HostCache::Key key1("foobar.com", ADDRESS_FAMILY_UNSPECIFIED);
- HostCache::Key key2("foobar.com", ADDRESS_FAMILY_IPV4);
+ HostCache::Key key1("foobar.com", ADDRESS_FAMILY_UNSPECIFIED, 0);
+ HostCache::Key key2("foobar.com", ADDRESS_FAMILY_IPV4, 0);
const HostCache::Entry* entry1 = NULL; // Entry for key1
const HostCache::Entry* entry2 = NULL; // Entry for key2
@@ -303,6 +303,42 @@
EXPECT_NE(entry1, entry2);
}
+// Tests that the same hostname can be duplicated in the cache, so long as
+// the HostResolverFlags differ.
+TEST(HostCacheTest, HostResolverFlagsArePartOfKey) {
+ HostCache cache(kMaxCacheEntries, kSuccessEntryTTL, kFailureEntryTTL);
+
+ // t=0.
+ base::TimeTicks now;
+
+ HostCache::Key key1("foobar.com", ADDRESS_FAMILY_IPV4, 0);
+ HostCache::Key key2("foobar.com", ADDRESS_FAMILY_IPV4,
+ HOST_RESOLVER_CANONNAME);
+
+ const HostCache::Entry* entry1 = NULL; // Entry for key1
+ const HostCache::Entry* entry2 = NULL; // Entry for key2
+
+ EXPECT_EQ(0U, cache.size());
+
+ // Add an entry for ("foobar.com", IPV4, NONE) at t=0.
+ EXPECT_TRUE(cache.Lookup(key1, base::TimeTicks()) == NULL);
+ cache.Set(key1, OK, AddressList(), now);
+ entry1 = cache.Lookup(key1, base::TimeTicks());
+ EXPECT_FALSE(entry1 == NULL);
+ EXPECT_EQ(1U, cache.size());
+
+ // Add an entry for ("foobar.com", IPV4, CANONNAME) at t=0.
+ EXPECT_TRUE(cache.Lookup(key2, base::TimeTicks()) == NULL);
+ cache.Set(key2, OK, AddressList(), now);
+ entry2 = cache.Lookup(key2, base::TimeTicks());
+ EXPECT_FALSE(entry2 == NULL);
+ EXPECT_EQ(2U, cache.size());
+
+ // Even though the hostnames were the same, we should have two unique
+ // entries (because the HostResolverFlags differ).
+ EXPECT_NE(entry1, entry2);
+}
+
TEST(HostCacheTest, NoCache) {
// Disable caching.
HostCache cache(0, kSuccessEntryTTL, kFailureEntryTTL);
@@ -353,33 +389,52 @@
int expected_comparison;
} tests[] = {
{
- HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED),
- HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED),
+ HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED, 0),
+ HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED, 0),
0
},
{
- HostCache::Key("host1", ADDRESS_FAMILY_IPV4),
- HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED),
+ HostCache::Key("host1", ADDRESS_FAMILY_IPV4, 0),
+ HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED, 0),
1
},
{
- HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED),
- HostCache::Key("host1", ADDRESS_FAMILY_IPV4),
+ HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED, 0),
+ HostCache::Key("host1", ADDRESS_FAMILY_IPV4, 0),
-1
},
{
- HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED),
- HostCache::Key("host2", ADDRESS_FAMILY_UNSPECIFIED),
+ HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED, 0),
+ HostCache::Key("host2", ADDRESS_FAMILY_UNSPECIFIED, 0),
-1
},
{
- HostCache::Key("host1", ADDRESS_FAMILY_IPV4),
- HostCache::Key("host2", ADDRESS_FAMILY_UNSPECIFIED),
+ HostCache::Key("host1", ADDRESS_FAMILY_IPV4, 0),
+ HostCache::Key("host2", ADDRESS_FAMILY_UNSPECIFIED, 0),
1
},
{
- HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED),
- HostCache::Key("host2", ADDRESS_FAMILY_IPV4),
+ HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED, 0),
+ HostCache::Key("host2", ADDRESS_FAMILY_IPV4, 0),
+ -1
+ },
+ {
+ HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED, 0),
+ HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED,
+ HOST_RESOLVER_CANONNAME),
+ -1
+ },
+ {
+ HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED,
+ HOST_RESOLVER_CANONNAME),
+ HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED, 0),
+ 1
+ },
+ {
+ HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED,
+ HOST_RESOLVER_CANONNAME),
+ HostCache::Key("host2", ADDRESS_FAMILY_UNSPECIFIED,
+ HOST_RESOLVER_CANONNAME),
-1
},
};
diff --git a/net/base/host_mapping_rules.cc b/net/base/host_mapping_rules.cc
new file mode 100644
index 0000000..3296b0e
--- /dev/null
+++ b/net/base/host_mapping_rules.cc
@@ -0,0 +1,85 @@
+// 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 "net/base/host_mapping_rules.h"
+
+#include "base/logging.h"
+#include "base/string_tokenizer.h"
+#include "base/string_util.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_util.h"
+
+namespace net {
+
+HostMappingRules::HostMappingRules() {}
+
+bool HostMappingRules::RewriteHost(HostPortPair* host_port) const {
+ // Check if the hostname was excluded.
+ for (ExclusionRuleList::const_iterator it = exclusion_rules_.begin();
+ it != exclusion_rules_.end(); ++it) {
+ const ExclusionRule& rule = *it;
+ if (MatchPatternASCII(host_port->host, rule.hostname_pattern))
+ return false;
+ }
+
+ // Check if the hostname was remapped.
+ for (MapRuleList::const_iterator it = map_rules_.begin();
+ it != map_rules_.end(); ++it) {
+ const MapRule& rule = *it;
+
+ if (!MatchPatternASCII(host_port->host, rule.hostname_pattern))
+ continue; // This rule doesn't apply.
+
+ host_port->host = rule.replacement_hostname;
+ if (rule.replacement_port != -1)
+ host_port->port = rule.replacement_port;
+ return true;
+ }
+
+ return false;
+}
+
+bool HostMappingRules::AddRuleFromString(const std::string& rule_string) {
+ std::string trimmed;
+ TrimWhitespaceASCII(rule_string, TRIM_ALL, &trimmed);
+ std::vector<std::string> parts;
+ SplitString(trimmed, ' ', &parts);
+
+ // Test for EXCLUSION rule.
+ if (parts.size() == 2 && LowerCaseEqualsASCII(parts[0], "exclude")) {
+ ExclusionRule rule;
+ rule.hostname_pattern = StringToLowerASCII(parts[1]);
+ exclusion_rules_.push_back(rule);
+ return true;
+ }
+
+ // Test for MAP rule.
+ if (parts.size() == 3 && LowerCaseEqualsASCII(parts[0], "map")) {
+ MapRule rule;
+ rule.hostname_pattern = StringToLowerASCII(parts[1]);
+
+ if (!ParseHostAndPort(parts[2], &rule.replacement_hostname,
+ &rule.replacement_port)) {
+ return false; // Failed parsing the hostname/port.
+ }
+
+ map_rules_.push_back(rule);
+ return true;
+ }
+
+ return false;
+}
+
+void HostMappingRules::SetRulesFromString(const std::string& rules_string) {
+ exclusion_rules_.clear();
+ map_rules_.clear();
+
+ StringTokenizer rules(rules_string, ",");
+ while (rules.GetNext()) {
+ bool ok = AddRuleFromString(rules.token());
+ LOG_IF(ERROR, !ok) << "Failed parsing rule: " << rules.token();
+ }
+}
+
+} // namespace net
diff --git a/net/base/host_mapping_rules.h b/net/base/host_mapping_rules.h
new file mode 100644
index 0000000..a754a47
--- /dev/null
+++ b/net/base/host_mapping_rules.h
@@ -0,0 +1,61 @@
+// 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.
+
+#ifndef NET_BASE_HOST_MAPPING_RULES_H_
+#define NET_BASE_HOST_MAPPING_RULES_H_
+
+#include <string>
+#include <vector>
+#include "base/basictypes.h"
+
+namespace net {
+
+struct HostPortPair;
+
+class HostMappingRules {
+ public:
+ HostMappingRules();
+
+ // Modifies |*host_port| based on the current rules. Returns true if the
+ // RequestInfo was modified, false otherwise.
+ bool RewriteHost(HostPortPair* host_port) const;
+
+ // Adds a rule to this mapper. The format of the rule can be one of:
+ //
+ // "MAP" <hostname_pattern> <replacement_host> [":" <replacement_port>]
+ // "EXCLUDE" <hostname_pattern>
+ //
+ // The <replacement_host> can be either a hostname, or an IP address literal.
+ //
+ // Returns true if the rule was successfully parsed and added.
+ bool AddRuleFromString(const std::string& rule_string);
+
+ // Sets the rules from a comma separated list of rules.
+ void SetRulesFromString(const std::string& rules_string);
+
+ private:
+ struct MapRule {
+ MapRule() : replacement_port(-1) {}
+
+ std::string hostname_pattern;
+ std::string replacement_hostname;
+ int replacement_port;
+ };
+
+ struct ExclusionRule {
+ std::string hostname_pattern;
+ };
+
+ typedef std::vector<MapRule> MapRuleList;
+ typedef std::vector<ExclusionRule> ExclusionRuleList;
+
+ MapRuleList map_rules_;
+ ExclusionRuleList exclusion_rules_;
+
+ DISALLOW_COPY_AND_ASSIGN(HostMappingRules);
+};
+
+} // namespace net
+
+#endif // NET_BASE_HOST_MAPPING_RULES_H_
diff --git a/net/base/host_mapping_rules_unittest.cc b/net/base/host_mapping_rules_unittest.cc
new file mode 100644
index 0000000..0cea821
--- /dev/null
+++ b/net/base/host_mapping_rules_unittest.cc
@@ -0,0 +1,56 @@
+// 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 "net/base/host_mapping_rules.h"
+
+#include "net/base/host_port_pair.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+TEST(HostMappingRulesTest, SetRulesFromString) {
+ HostMappingRules rules;
+ rules.SetRulesFromString(
+ "map *.com baz , map *.net bar:60, EXCLUDE *.foo.com");
+
+ HostPortPair host_port("test", 1234);
+ EXPECT_FALSE(rules.RewriteHost(&host_port));
+ EXPECT_EQ("test", host_port.host);
+ EXPECT_EQ(1234u, host_port.port);
+
+ host_port = HostPortPair("chrome.net", 80);
+ EXPECT_TRUE(rules.RewriteHost(&host_port));
+ EXPECT_EQ("bar", host_port.host);
+ EXPECT_EQ(60u, host_port.port);
+
+ host_port = HostPortPair("crack.com", 80);
+ EXPECT_TRUE(rules.RewriteHost(&host_port));
+ EXPECT_EQ("baz", host_port.host);
+ EXPECT_EQ(80u, host_port.port);
+
+ host_port = HostPortPair("wtf.foo.com", 666);
+ EXPECT_FALSE(rules.RewriteHost(&host_port));
+ EXPECT_EQ("wtf.foo.com", host_port.host);
+ EXPECT_EQ(666u, host_port.port);
+}
+
+// Parsing bad rules should silently discard the rule (and never crash).
+TEST(HostMappingRulesTest, ParseInvalidRules) {
+ HostMappingRules rules;
+
+ EXPECT_FALSE(rules.AddRuleFromString("xyz"));
+ EXPECT_FALSE(rules.AddRuleFromString(""));
+ EXPECT_FALSE(rules.AddRuleFromString(" "));
+ EXPECT_FALSE(rules.AddRuleFromString("EXCLUDE"));
+ EXPECT_FALSE(rules.AddRuleFromString("EXCLUDE foo bar"));
+ EXPECT_FALSE(rules.AddRuleFromString("INCLUDE"));
+ EXPECT_FALSE(rules.AddRuleFromString("INCLUDE x"));
+ EXPECT_FALSE(rules.AddRuleFromString("INCLUDE x :10"));
+}
+
+} // namespace
+
+} // namespace net
diff --git a/net/base/host_port_pair.cc b/net/base/host_port_pair.cc
new file mode 100644
index 0000000..d4e7d4e
--- /dev/null
+++ b/net/base/host_port_pair.cc
@@ -0,0 +1,21 @@
+// 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 "net/base/host_port_pair.h"
+#include "base/string_util.h"
+
+namespace net {
+
+HostPortPair::HostPortPair() : port(0) {}
+HostPortPair::HostPortPair(const std::string& in_host, uint16 in_port)
+ : host(in_host), port(in_port) {}
+
+std::string HostPortPair::ToString() const {
+ // Check to see if the host is an IPv6 address. If so, added brackets.
+ if (host.find(':') != std::string::npos)
+ return StringPrintf("[%s]:%u", host.c_str(), port);
+ return StringPrintf("%s:%u", host.c_str(), port);
+}
+
+} // namespace net
diff --git a/net/base/host_port_pair.h b/net/base/host_port_pair.h
new file mode 100644
index 0000000..78f74ea
--- /dev/null
+++ b/net/base/host_port_pair.h
@@ -0,0 +1,44 @@
+// 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.
+
+#ifndef NET_BASE_HOST_PORT_PAIR_H_
+#define NET_BASE_HOST_PORT_PAIR_H_
+
+#include <string>
+#include "base/basictypes.h"
+
+namespace net {
+
+struct HostPortPair {
+ HostPortPair();
+ // If |in_host| represents an IPv6 address, it should not bracket the address.
+ HostPortPair(const std::string& in_host, uint16 in_port);
+
+ // TODO(willchan): Define a functor instead.
+ // Comparator function so this can be placed in a std::map.
+ // TODO(jar): Violation of style guide, and should be removed.
+ bool operator<(const HostPortPair& other) const {
+ if (port != other.port)
+ return port < other.port;
+ return host < other.host;
+ }
+
+ // Equality test of contents. (Probably another violation of style guide).
+ bool Equals(const HostPortPair& other) const {
+ return host == other.host && port == other.port;
+ }
+
+ // ToString() will convert the HostPortPair to "host:port". If |host| is an
+ // IPv6 literal, it will add brackets around |host|.
+ std::string ToString() const;
+
+ // If |host| represents an IPv6 address, this string will not contain brackets
+ // around the address.
+ std::string host;
+ uint16 port;
+};
+
+} // namespace net
+
+#endif // NET_BASE_HOST_PORT_PAIR_H_
diff --git a/net/base/host_resolver.cc b/net/base/host_resolver.cc
index ebad57e..320406c 100644
--- a/net/base/host_resolver.cc
+++ b/net/base/host_resolver.cc
@@ -26,7 +26,7 @@
int SingleRequestHostResolver::Resolve(const HostResolver::RequestInfo& info,
AddressList* addresses,
CompletionCallback* callback,
- LoadLog* load_log) {
+ const BoundNetLog& net_log) {
DCHECK(!cur_request_ && !cur_request_callback_) << "resolver already in use";
HostResolver::RequestHandle request = NULL;
@@ -36,7 +36,7 @@
CompletionCallback* transient_callback = callback ? &callback_ : NULL;
int rv = resolver_->Resolve(
- info, addresses, transient_callback, &request, load_log);
+ info, addresses, transient_callback, &request, net_log);
if (rv == ERR_IO_PENDING) {
// Cleared in OnResolveCompletion().
diff --git a/net/base/host_resolver.h b/net/base/host_resolver.h
index 32706a0..895ffc2 100644
--- a/net/base/host_resolver.h
+++ b/net/base/host_resolver.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -18,9 +18,9 @@
namespace net {
class AddressList;
+class BoundNetLog;
class HostCache;
-class LoadLog;
-class NetworkChangeNotifier;
+class HostResolverImpl;
// This class represents the task of resolving hostnames (or IP address
// literal) to an AddressList object.
@@ -31,7 +31,7 @@
// request at a time is to create a SingleRequestHostResolver wrapper around
// HostResolver (which will automatically cancel the single request when it
// goes out of scope).
-class HostResolver : public base::RefCountedThreadSafe<HostResolver> {
+class HostResolver : public base::RefCounted<HostResolver> {
public:
// The parameters for doing a Resolve(). |hostname| and |port| are required,
// the rest are optional (and have reasonable defaults).
@@ -40,19 +40,34 @@
RequestInfo(const std::string& hostname, int port)
: hostname_(hostname),
address_family_(ADDRESS_FAMILY_UNSPECIFIED),
+ host_resolver_flags_(0),
port_(port),
allow_cached_response_(true),
is_speculative_(false),
priority_(MEDIUM) {}
- const int port() const { return port_; }
+ int port() const { return port_; }
+ void set_port(int port) {
+ port_ = port;
+ }
+
const std::string& hostname() const { return hostname_; }
+ void set_hostname(const std::string& hostname) {
+ hostname_ = hostname;
+ }
AddressFamily address_family() const { return address_family_; }
void set_address_family(AddressFamily address_family) {
address_family_ = address_family;
}
+ HostResolverFlags host_resolver_flags() const {
+ return host_resolver_flags_;
+ }
+ void set_host_resolver_flags(HostResolverFlags host_resolver_flags) {
+ host_resolver_flags_ = host_resolver_flags;
+ }
+
bool allow_cached_response() const { return allow_cached_response_; }
void set_allow_cached_response(bool b) { allow_cached_response_ = b; }
@@ -72,6 +87,9 @@
// The address family to restrict results to.
AddressFamily address_family_;
+ // Flags to use when resolving this request.
+ HostResolverFlags host_resolver_flags_;
+
// The port number to set in the result's sockaddrs.
int port_;
@@ -113,6 +131,11 @@
// Opaque type used to cancel a request.
typedef void* RequestHandle;
+ // This value can be passed into CreateSystemHostResolver as the
+ // |max_concurrent_resolves| parameter. It will select a default level of
+ // concurrency.
+ static const size_t kDefaultParallelism = 0;
+
// Resolves the given hostname (or IP address literal), filling out the
// |addresses| object upon success. The |info.port| parameter will be set as
// the sin(6)_port field of the sockaddr_in{6} struct. Returns OK if
@@ -129,12 +152,12 @@
// the async request. This handle is not valid after the request has
// completed.
//
- // Profiling information for the request is saved to |load_log| if non-NULL.
+ // Profiling information for the request is saved to |net_log| if non-NULL.
virtual int Resolve(const RequestInfo& info,
AddressList* addresses,
CompletionCallback* callback,
RequestHandle* out_req,
- LoadLog* load_log) = 0;
+ const BoundNetLog& net_log) = 0;
// Cancels the specified request. |req| is the handle returned by Resolve().
// After a request is cancelled, its completion callback will not be called.
@@ -148,22 +171,22 @@
// Unregisters an observer previously added by AddObserver().
virtual void RemoveObserver(Observer* observer) = 0;
- // TODO(eroman): temp hack for http://crbug.com/18373
- virtual void Shutdown() = 0;
-
// Sets the default AddressFamily to use when requests have left it
// unspecified. For example, this could be used to restrict resolution
// results to AF_INET by passing in ADDRESS_FAMILY_IPV4, or to
// AF_INET6 by passing in ADDRESS_FAMILY_IPV6.
virtual void SetDefaultAddressFamily(AddressFamily address_family) {}
- // Returns true if this HostResolver is an instance of HostResolverImpl.
- // Used primarily to expose additional functionality on the
- // about:net-internals page.
- virtual bool IsHostResolverImpl() { return false; }
+ // Returns |this| cast to a HostResolverImpl*, or NULL if the subclass
+ // is not compatible with HostResolverImpl. Used primarily to expose
+ // additional functionality on the about:net-internals page.
+ virtual HostResolverImpl* GetAsHostResolverImpl() { return NULL; }
+
+ // Does additional cleanup prior to destruction.
+ virtual void Shutdown() {}
protected:
- friend class base::RefCountedThreadSafe<HostResolver>;
+ friend class base::RefCounted<HostResolver>;
HostResolver() { }
@@ -193,7 +216,7 @@
int Resolve(const HostResolver::RequestInfo& info,
AddressList* addresses,
CompletionCallback* callback,
- LoadLog* load_log);
+ const BoundNetLog& net_log);
// Cancels the in-progress request, if any. This prevents the callback
// from being invoked. Resolve() can be called again after cancelling.
@@ -220,10 +243,10 @@
// Creates a HostResolver implementation that queries the underlying system.
// (Except if a unit-test has changed the global HostResolverProc using
// ScopedHostResolverProc to intercept requests to the system).
-// |network_change_notifier| must outlive HostResolver. It can optionally be
-// NULL, in which case HostResolver will not respond to network changes.
-HostResolver* CreateSystemHostResolver(
- NetworkChangeNotifier* network_change_notifier);
+// |max_concurrent_resolves| is how many resolve requests will be allowed to
+// run in parallel. Pass HostResolver::kDefaultParallelism to choose a
+// default value.
+HostResolver* CreateSystemHostResolver(size_t max_concurrent_resolves);
} // namespace net
diff --git a/net/base/host_resolver_impl.cc b/net/base/host_resolver_impl.cc
index 94c3857..ffba57f 100644
--- a/net/base/host_resolver_impl.cc
+++ b/net/base/host_resolver_impl.cc
@@ -1,26 +1,35 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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 "net/base/host_resolver_impl.h"
+#if defined(OS_WIN)
+#include <Winsock2.h>
+#elif defined(OS_POSIX)
+#include <netdb.h>
+#endif
+
#include <cmath>
#include <deque>
+#include <vector>
#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "base/debug_util.h"
+#include "base/histogram.h"
#include "base/lock.h"
#include "base/message_loop.h"
#include "base/stl_util-inl.h"
#include "base/string_util.h"
#include "base/time.h"
+#include "base/values.h"
#include "base/worker_pool.h"
#include "net/base/address_list.h"
#include "net/base/host_resolver_proc.h"
-#include "net/base/load_log.h"
+#include "net/base/net_log.h"
#include "net/base/net_errors.h"
-#include "net/base/network_change_notifier.h"
+#include "net/base/net_util.h"
#if defined(OS_WIN)
#include "net/base/winsock_init.h"
@@ -36,22 +45,24 @@
HostCache* cache = new HostCache(
kMaxHostCacheEntries,
base::TimeDelta::FromMinutes(1),
- base::TimeDelta::FromSeconds(1));
+ base::TimeDelta::FromSeconds(0)); // Disable caching of failed DNS.
return cache;
}
} // anonymous namespace
-HostResolver* CreateSystemHostResolver(
- NetworkChangeNotifier* network_change_notifier) {
+HostResolver* CreateSystemHostResolver(size_t max_concurrent_resolves) {
// Maximum of 50 concurrent threads.
// TODO(eroman): Adjust this, do some A/B experiments.
- static const size_t kMaxJobs = 50u;
+ static const size_t kDefaultMaxJobs = 50u;
- // TODO(willchan): Pass in the NetworkChangeNotifier.
- HostResolverImpl* resolver = new HostResolverImpl(
- NULL, CreateDefaultCache(), network_change_notifier, kMaxJobs);
+ if (max_concurrent_resolves == HostResolver::kDefaultParallelism)
+ max_concurrent_resolves = kDefaultMaxJobs;
+
+ HostResolverImpl* resolver =
+ new HostResolverImpl(NULL, CreateDefaultCache(),
+ max_concurrent_resolves);
return resolver;
}
@@ -59,26 +70,117 @@
static int ResolveAddrInfo(HostResolverProc* resolver_proc,
const std::string& host,
AddressFamily address_family,
- AddressList* out) {
+ HostResolverFlags host_resolver_flags,
+ AddressList* out,
+ int* os_error) {
if (resolver_proc) {
// Use the custom procedure.
- return resolver_proc->Resolve(host, address_family, out);
+ return resolver_proc->Resolve(host, address_family,
+ host_resolver_flags, out, os_error);
} else {
// Use the system procedure (getaddrinfo).
- return SystemHostResolverProc(host, address_family, out);
+ return SystemHostResolverProc(host, address_family,
+ host_resolver_flags, out, os_error);
}
}
+// Extra parameters to attach to the NetLog when the resolve failed.
+class HostResolveFailedParams : public NetLog::EventParameters {
+ public:
+ HostResolveFailedParams(int net_error, int os_error, bool was_from_cache)
+ : net_error_(net_error),
+ os_error_(os_error),
+ was_from_cache_(was_from_cache) {
+ }
+
+ virtual Value* ToValue() const {
+ DictionaryValue* dict = new DictionaryValue();
+ dict->SetInteger(L"net_error", net_error_);
+ dict->SetBoolean(L"was_from_cache", was_from_cache_);
+
+ if (os_error_) {
+ dict->SetInteger(L"os_error", os_error_);
+#if defined(OS_POSIX)
+ dict->SetString(L"os_error_string", gai_strerror(os_error_));
+#elif defined(OS_WIN)
+ // Map the error code to a human-readable string.
+ LPWSTR error_string = NULL;
+ int size = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM,
+ 0, // Use the internal message table.
+ os_error_,
+ 0, // Use default language.
+ (LPWSTR)&error_string,
+ 0, // Buffer size.
+ 0); // Arguments (unused).
+ dict->SetString(L"os_error_string", error_string);
+ LocalFree(error_string);
+#endif
+ }
+
+ return dict;
+ }
+
+ private:
+ const int net_error_;
+ const int os_error_;
+ const bool was_from_cache_;
+};
+
+// Gets a list of the likely error codes that getaddrinfo() can return
+// (non-exhaustive). These are the error codes that we will track via
+// a histogram.
+std::vector<int> GetAllGetAddrinfoOSErrors() {
+ int os_errors[] = {
+#if defined(OS_POSIX)
+ EAI_ADDRFAMILY,
+ EAI_AGAIN,
+ EAI_BADFLAGS,
+ EAI_FAIL,
+ EAI_FAMILY,
+ EAI_MEMORY,
+ EAI_NODATA,
+ EAI_NONAME,
+ EAI_SERVICE,
+ EAI_SOCKTYPE,
+ EAI_SYSTEM,
+#elif defined(OS_WIN)
+ // See: http://msdn.microsoft.com/en-us/library/ms738520(VS.85).aspx
+ WSA_NOT_ENOUGH_MEMORY,
+ WSAEAFNOSUPPORT,
+ WSAEINVAL,
+ WSAESOCKTNOSUPPORT,
+ WSAHOST_NOT_FOUND,
+ WSANO_DATA,
+ WSANO_RECOVERY,
+ WSANOTINITIALISED,
+ WSATRY_AGAIN,
+ WSATYPE_NOT_FOUND,
+#endif
+ };
+
+ // Histogram enumerations require positive numbers.
+ std::vector<int> errors;
+ for (size_t i = 0; i < arraysize(os_errors); ++i) {
+ errors.push_back(std::abs(os_errors[i]));
+ // Also add N+1 for each error, so the bucket that contains our expected
+ // error is of size 1. That way if we get unexpected error codes, they
+ // won't fall into the same buckets as the expected ones.
+ errors.push_back(std::abs(os_errors[i]) + 1);
+ }
+ return errors;
+}
+
//-----------------------------------------------------------------------------
class HostResolverImpl::Request {
public:
- Request(LoadLog* load_log,
+ Request(const BoundNetLog& net_log,
int id,
const RequestInfo& info,
CompletionCallback* callback,
AddressList* addresses)
- : load_log_(load_log),
+ : net_log_(net_log),
id_(id),
info_(info),
job_(NULL),
@@ -117,8 +219,8 @@
return job_;
}
- LoadLog* load_log() const {
- return load_log_;
+ const BoundNetLog& net_log() {
+ return net_log_;
}
int id() const {
@@ -130,7 +232,7 @@
}
private:
- scoped_refptr<LoadLog> load_log_;
+ BoundNetLog net_log_;
// Unique ID for this request. Used by observers to identify requests.
int id_;
@@ -152,71 +254,35 @@
//-----------------------------------------------------------------------------
-// Threadsafe log.
-class HostResolverImpl::RequestsTrace
- : public base::RefCountedThreadSafe<HostResolverImpl::RequestsTrace> {
- public:
- RequestsTrace() : log_(new LoadLog(LoadLog::kUnbounded)) {}
-
- void Add(const std::string& msg) {
- AutoLock l(lock_);
- LoadLog::AddString(log_, msg);
- }
-
- void Get(LoadLog* out) {
- AutoLock l(lock_);
- out->Append(log_);
- }
-
- void Clear() {
- AutoLock l(lock_);
- log_ = new LoadLog(LoadLog::kUnbounded);
- }
-
- private:
- Lock lock_;
- scoped_refptr<LoadLog> log_;
-};
-
-//-----------------------------------------------------------------------------
-
// This class represents a request to the worker pool for a "getaddrinfo()"
// call.
class HostResolverImpl::Job
: public base::RefCountedThreadSafe<HostResolverImpl::Job> {
public:
- Job(int id, HostResolverImpl* resolver, const Key& key,
- RequestsTrace* requests_trace)
- : id_(id), key_(key),
+ Job(int id, HostResolverImpl* resolver, const Key& key)
+ : id_(id),
+ key_(key),
resolver_(resolver),
origin_loop_(MessageLoop::current()),
resolver_proc_(resolver->effective_resolver_proc()),
- requests_trace_(requests_trace),
- error_(OK) {
- if (requests_trace_) {
- requests_trace_->Add(StringPrintf(
- "Created job j%d for {hostname='%s', address_family=%d}",
- id_, key.hostname.c_str(),
- static_cast<int>(key.address_family)));
- }
+ error_(OK),
+ os_error_(0),
+ had_non_speculative_request_(false) {
}
// Attaches a request to this job. The job takes ownership of |req| and will
// take care to delete it.
void AddRequest(Request* req) {
- if (requests_trace_) {
- requests_trace_->Add(StringPrintf(
- "Attached request r%d to job j%d", req->id(), id_));
- }
-
req->set_job(this);
requests_.push_back(req);
+
+ if (!req->info().is_speculative())
+ had_non_speculative_request_ = true;
}
// Called from origin loop.
void Start() {
- if (requests_trace_)
- requests_trace_->Add(StringPrintf("Starting job j%d", id_));
+ start_time_ = base::TimeTicks::Now();
// Dispatch the job to a worker thread.
if (!WorkerPool::PostTask(FROM_HERE,
@@ -237,9 +303,6 @@
HostResolver* resolver = resolver_;
resolver_ = NULL;
- if (requests_trace_)
- requests_trace_->Add(StringPrintf("Cancelled job j%d", id_));
-
// Mark the job as cancelled, so when worker thread completes it will
// not try to post completion to origin loop.
{
@@ -267,6 +330,14 @@
return key_;
}
+ int id() const {
+ return id_;
+ }
+
+ base::TimeTicks start_time() const {
+ return start_time_;
+ }
+
// Called from origin thread.
const RequestsList& requests() const {
return requests_;
@@ -279,6 +350,11 @@
return requests_[0];
}
+ // Returns true if |req_info| can be fulfilled by this job.
+ bool CanServiceRequest(const RequestInfo& req_info) const {
+ return key_ == resolver_->GetEffectiveKeyForRequest(req_info);
+ }
+
private:
friend class base::RefCountedThreadSafe<HostResolverImpl::Job>;
@@ -287,37 +363,28 @@
STLDeleteElements(&requests_);
}
+ // WARNING: This code runs inside a worker pool. The shutdown code cannot
+ // wait for it to finish, so we must be very careful here about using other
+ // objects (like MessageLoops, Singletons, etc). During shutdown these objects
+ // may no longer exist.
void DoLookup() {
- if (requests_trace_) {
- requests_trace_->Add(StringPrintf(
- "[resolver thread] Running job j%d", id_));
- }
-
// Running on the worker thread
error_ = ResolveAddrInfo(resolver_proc_,
key_.hostname,
key_.address_family,
- &results_);
-
- if (requests_trace_) {
- requests_trace_->Add(StringPrintf(
- "[resolver thread] Completed job j%d", id_));
- }
-
- Task* reply = NewRunnableMethod(this, &Job::OnLookupComplete);
+ key_.host_resolver_flags,
+ &results_,
+ &os_error_);
// The origin loop could go away while we are trying to post to it, so we
// need to call its PostTask method inside a lock. See ~HostResolver.
{
AutoLock locked(origin_loop_lock_);
if (origin_loop_) {
- origin_loop_->PostTask(FROM_HERE, reply);
- reply = NULL;
+ origin_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &Job::OnLookupComplete));
}
}
-
- // Does nothing if it got posted.
- delete reply;
}
// Callback for when DoLookup() completes (runs on origin thread).
@@ -328,8 +395,18 @@
//DCHECK_EQ(origin_loop_, MessageLoop::current());
DCHECK(error_ || results_.head());
- if (requests_trace_)
- requests_trace_->Add(StringPrintf("Completing job j%d", id_));
+ base::TimeDelta job_duration = base::TimeTicks::Now() - start_time_;
+
+ if (had_non_speculative_request_) {
+ // TODO(eroman): Add histogram for job times of non-speculative
+ // requests.
+ }
+
+ if (error_ != OK) {
+ UMA_HISTOGRAM_CUSTOM_ENUMERATION("Net.OSErrorsForGetAddrinfo",
+ std::abs(os_error_),
+ GetAllGetAddrinfoOSErrors());
+ }
if (was_cancelled())
return;
@@ -340,7 +417,7 @@
if (error_ == OK)
results_.SetPort(requests_[0]->port());
- resolver_->OnJobComplete(this, error_, results_);
+ resolver_->OnJobComplete(this, error_, os_error_, results_);
}
// Immutable. Can be read from either thread,
@@ -363,18 +440,122 @@
// reference ensures that it remains valid until we are done.
scoped_refptr<HostResolverProc> resolver_proc_;
- // Thread safe log to write details into, or NULL.
- scoped_refptr<RequestsTrace> requests_trace_;
-
// Assigned on the worker thread, read on the origin thread.
int error_;
+ int os_error_;
+
+ // True if a non-speculative request was ever attached to this job
+ // (regardless of whether or not it was later cancelled.
+ // This boolean is used for histogramming the duration of jobs used to
+ // service non-speculative requests.
+ bool had_non_speculative_request_;
+
AddressList results_;
+ // The time when the job was started.
+ base::TimeTicks start_time_;
+
DISALLOW_COPY_AND_ASSIGN(Job);
};
//-----------------------------------------------------------------------------
+// This class represents a request to the worker pool for a "probe for IPv6
+// support" call.
+class HostResolverImpl::IPv6ProbeJob
+ : public base::RefCountedThreadSafe<HostResolverImpl::IPv6ProbeJob> {
+ public:
+ explicit IPv6ProbeJob(HostResolverImpl* resolver)
+ : resolver_(resolver),
+ origin_loop_(MessageLoop::current()) {
+ DCHECK(!was_cancelled());
+ }
+
+ void Start() {
+ if (was_cancelled())
+ return;
+ DCHECK(IsOnOriginThread());
+ const bool kIsSlow = true;
+ WorkerPool::PostTask(
+ FROM_HERE, NewRunnableMethod(this, &IPv6ProbeJob::DoProbe), kIsSlow);
+ }
+
+ // Cancels the current job.
+ void Cancel() {
+ if (was_cancelled())
+ return;
+ DCHECK(IsOnOriginThread());
+ resolver_ = NULL; // Read/write ONLY on origin thread.
+ {
+ AutoLock locked(origin_loop_lock_);
+ // Origin loop may be destroyed before we can use it!
+ origin_loop_ = NULL; // Write ONLY on origin thread.
+ }
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<HostResolverImpl::IPv6ProbeJob>;
+
+ ~IPv6ProbeJob() {
+ }
+
+ // Should be run on |orgin_thread_|, but that may not be well defined now.
+ bool was_cancelled() const {
+ if (!resolver_ || !origin_loop_) {
+ DCHECK(!resolver_);
+ DCHECK(!origin_loop_);
+ return true;
+ }
+ return false;
+ }
+
+ // Run on worker thread.
+ void DoProbe() {
+ // Do actual testing on this thread, as it takes 40-100ms.
+ AddressFamily family = IPv6Supported() ? ADDRESS_FAMILY_UNSPECIFIED
+ : ADDRESS_FAMILY_IPV4;
+
+ Task* reply = NewRunnableMethod(this, &IPv6ProbeJob::OnProbeComplete,
+ family);
+
+ // The origin loop could go away while we are trying to post to it, so we
+ // need to call its PostTask method inside a lock. See ~HostResolver.
+ {
+ AutoLock locked(origin_loop_lock_);
+ if (origin_loop_) {
+ origin_loop_->PostTask(FROM_HERE, reply);
+ return;
+ }
+ }
+
+ // We didn't post, so delete the reply.
+ delete reply;
+ }
+
+ // Callback for when DoProbe() completes (runs on origin thread).
+ void OnProbeComplete(AddressFamily address_family) {
+ if (was_cancelled())
+ return;
+ DCHECK(IsOnOriginThread());
+ resolver_->IPv6ProbeSetDefaultAddressFamily(address_family);
+ }
+
+ bool IsOnOriginThread() const {
+ return !MessageLoop::current() || origin_loop_ == MessageLoop::current();
+ }
+
+ // Used/set only on origin thread.
+ HostResolverImpl* resolver_;
+
+ // Used to post ourselves onto the origin thread.
+ Lock origin_loop_lock_;
+ MessageLoop* origin_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(IPv6ProbeJob);
+};
+
+//-----------------------------------------------------------------------------
+
// We rely on the priority enum values being sequential having starting at 0,
// and increasing for lower priorities.
COMPILE_ASSERT(HIGHEST == 0u &&
@@ -402,7 +583,7 @@
// specific meaning of these parameters.
void SetConstraints(size_t max_outstanding_jobs,
size_t max_pending_requests) {
- CHECK(max_outstanding_jobs != 0u);
+ CHECK_NE(max_outstanding_jobs, 0u);
max_outstanding_jobs_ = max_outstanding_jobs;
max_pending_requests_ = max_pending_requests;
}
@@ -483,15 +664,15 @@
}
// Removes any pending requests from the queue which are for the
- // same hostname/address-family as |job|, and attaches them to |job|.
+ // same (hostname / effective address-family) as |job|, and attaches them to
+ // |job|.
void MoveRequestsToJob(Job* job) {
for (size_t i = 0u; i < arraysize(pending_requests_); ++i) {
PendingRequestsQueue& q = pending_requests_[i];
PendingRequestsQueue::iterator req_it = q.begin();
while (req_it != q.end()) {
Request* req = *req_it;
- Key req_key(req->info().hostname(), req->info().address_family());
- if (req_key == job->key()) {
+ if (job->CanServiceRequest(req->info())) {
// Job takes ownership of |req|.
job->AddRequest(req);
req_it = q.erase(req_it);
@@ -526,7 +707,6 @@
HostResolverImpl::HostResolverImpl(
HostResolverProc* resolver_proc,
HostCache* cache,
- NetworkChangeNotifier* network_change_notifier,
size_t max_jobs)
: cache_(cache),
max_jobs_(max_jobs),
@@ -535,7 +715,7 @@
resolver_proc_(resolver_proc),
default_address_family_(ADDRESS_FAMILY_UNSPECIFIED),
shutdown_(false),
- network_change_notifier_(network_change_notifier) {
+ ipv6_probe_monitoring_(false) {
DCHECK_GT(max_jobs, 0u);
// It is cumbersome to expose all of the constraints in the constructor,
@@ -545,13 +725,14 @@
#if defined(OS_WIN)
EnsureWinsockInit();
#endif
- if (network_change_notifier_)
- network_change_notifier_->AddObserver(this);
+ NetworkChangeNotifier::AddObserver(this);
}
HostResolverImpl::~HostResolverImpl() {
// Cancel the outstanding jobs. Those jobs may contain several attached
// requests, which will also be cancelled.
+ DiscardIPv6ProbeJob();
+
for (JobMap::iterator it = jobs_.begin(); it != jobs_.end(); ++it)
it->second->Cancel();
@@ -559,8 +740,7 @@
if (cur_completing_job_)
cur_completing_job_->Cancel();
- if (network_change_notifier_)
- network_change_notifier_->RemoveObserver(this);
+ NetworkChangeNotifier::RemoveObserver(this);
// Delete the job pools.
for (size_t i = 0u; i < arraysize(job_pools_); ++i)
@@ -573,43 +753,47 @@
AddressList* addresses,
CompletionCallback* callback,
RequestHandle* out_req,
- LoadLog* load_log) {
+ const BoundNetLog& net_log) {
+ DCHECK(CalledOnValidThread());
+
if (shutdown_)
return ERR_UNEXPECTED;
// Choose a unique ID number for observers to see.
int request_id = next_request_id_++;
- // Update the load log and notify registered observers.
- OnStartRequest(load_log, request_id, info);
+ // Update the net log and notify registered observers.
+ OnStartRequest(net_log, request_id, info);
// Build a key that identifies the request in the cache and in the
// outstanding jobs map.
- Key key(info.hostname(), info.address_family());
- if (key.address_family == ADDRESS_FAMILY_UNSPECIFIED)
- key.address_family = default_address_family_;
+ Key key = GetEffectiveKeyForRequest(info);
// If we have an unexpired cache entry, use it.
if (info.allow_cached_response() && cache_.get()) {
const HostCache::Entry* cache_entry = cache_->Lookup(
key, base::TimeTicks::Now());
if (cache_entry) {
- int error = cache_entry->error;
- if (error == OK)
+ int net_error = cache_entry->error;
+ if (net_error == OK)
addresses->SetFrom(cache_entry->addrlist, info.port());
- // Update the load log and notify registered observers.
- OnFinishRequest(load_log, request_id, info, error);
+ // Update the net log and notify registered observers.
+ OnFinishRequest(net_log, request_id, info, net_error,
+ 0, /* os_error (unknown since from cache) */
+ true /* was_from_cache */);
- return error;
+ return net_error;
}
}
// If no callback was specified, do a synchronous resolution.
if (!callback) {
AddressList addrlist;
+ int os_error = 0;
int error = ResolveAddrInfo(
- effective_resolver_proc(), key.hostname, key.address_family, &addrlist);
+ effective_resolver_proc(), key.hostname, key.address_family,
+ key.host_resolver_flags, &addrlist, &os_error);
if (error == OK) {
addrlist.SetPort(info.port());
*addresses = addrlist;
@@ -619,15 +803,16 @@
if (cache_.get())
cache_->Set(key, error, addrlist, base::TimeTicks::Now());
- // Update the load log and notify registered observers.
- OnFinishRequest(load_log, request_id, info, error);
+ // Update the net log and notify registered observers.
+ OnFinishRequest(net_log, request_id, info, error, os_error,
+ false /* was_from_cache */);
return error;
}
// Create a handle for this request, and pass it back to the user if they
// asked for it (out_req != NULL).
- Request* req = new Request(load_log, request_id, info, callback, addresses);
+ Request* req = new Request(net_log, request_id, info, callback, addresses);
if (out_req)
*out_req = reinterpret_cast<RequestHandle>(req);
@@ -656,6 +841,7 @@
// See OnJobComplete(Job*) for why it is important not to clean out
// cancelled requests from Job::requests_.
void HostResolverImpl::CancelRequest(RequestHandle req_handle) {
+ DCHECK(CalledOnValidThread());
if (shutdown_) {
// TODO(eroman): temp hack for: http://crbug.com/18373
// Because we destroy outstanding requests during Shutdown(),
@@ -681,14 +867,16 @@
// NULL out the fields of req, to mark it as cancelled.
req->MarkAsCancelled();
- OnCancelRequest(req->load_log(), req->id(), req->info());
+ OnCancelRequest(req->net_log(), req->id(), req->info());
}
void HostResolverImpl::AddObserver(HostResolver::Observer* observer) {
+ DCHECK(CalledOnValidThread());
observers_.push_back(observer);
}
void HostResolverImpl::RemoveObserver(HostResolver::Observer* observer) {
+ DCHECK(CalledOnValidThread());
ObserversList::iterator it =
std::find(observers_.begin(), observers_.end(), observer);
@@ -698,56 +886,37 @@
observers_.erase(it);
}
+void HostResolverImpl::SetDefaultAddressFamily(AddressFamily address_family) {
+ DCHECK(CalledOnValidThread());
+ ipv6_probe_monitoring_ = false;
+ DiscardIPv6ProbeJob();
+ default_address_family_ = address_family;
+}
+
+void HostResolverImpl::ProbeIPv6Support() {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!ipv6_probe_monitoring_);
+ ipv6_probe_monitoring_ = true;
+ OnIPAddressChanged(); // Give initial setup call.
+}
+
void HostResolverImpl::Shutdown() {
+ DCHECK(CalledOnValidThread());
shutdown_ = true;
// Cancel the outstanding jobs.
for (JobMap::iterator it = jobs_.begin(); it != jobs_.end(); ++it)
it->second->Cancel();
jobs_.clear();
-}
-
-void HostResolverImpl::ClearRequestsTrace() {
- if (requests_trace_)
- requests_trace_->Clear();
-}
-
-void HostResolverImpl::EnableRequestsTracing(bool enable) {
- requests_trace_ = enable ? new RequestsTrace : NULL;
- if (enable) {
- // Print the state of the world when logging was started.
- requests_trace_->Add("Enabled tracing");
- requests_trace_->Add(StringPrintf(
- "Current num outstanding jobs: %d",
- static_cast<int>(jobs_.size())));
-
- size_t total = 0u;
- for (size_t i = 0; i < arraysize(job_pools_); ++i)
- total += job_pools_[i]->GetNumPendingRequests();
-
- requests_trace_->Add(StringPrintf(
- "Number of queued requests: %d", static_cast<int>(total)));
- }
-}
-
-bool HostResolverImpl::IsRequestsTracingEnabled() const {
- return !!requests_trace_; // Cast to bool.
-}
-
-scoped_refptr<LoadLog> HostResolverImpl::GetRequestsTrace() {
- if (!requests_trace_)
- return NULL;
-
- scoped_refptr<LoadLog> copy_of_log = new LoadLog(LoadLog::kUnbounded);
- requests_trace_->Get(copy_of_log);
- return copy_of_log;
+ DiscardIPv6ProbeJob();
}
void HostResolverImpl::SetPoolConstraints(JobPoolIndex pool_index,
size_t max_outstanding_jobs,
size_t max_pending_requests) {
- CHECK(pool_index >= 0);
- CHECK(pool_index < POOL_COUNT);
+ DCHECK(CalledOnValidThread());
+ CHECK_GE(pool_index, 0);
+ CHECK_LT(pool_index, POOL_COUNT);
CHECK(jobs_.empty()) << "Can only set constraints during setup";
JobPool* pool = job_pools_[pool_index];
pool->SetConstraints(max_outstanding_jobs, max_pending_requests);
@@ -780,13 +949,14 @@
}
void HostResolverImpl::OnJobComplete(Job* job,
- int error,
+ int net_error,
+ int os_error,
const AddressList& addrlist) {
RemoveOutstandingJob(job);
// Write result to the cache.
if (cache_.get())
- cache_->Set(job->key(), error, addrlist, base::TimeTicks::Now());
+ cache_->Set(job->key(), net_error, addrlist, base::TimeTicks::Now());
// Make a note that we are executing within OnJobComplete() in case the
// HostResolver is deleted by a callback invocation.
@@ -803,10 +973,11 @@
if (!req->was_cancelled()) {
DCHECK_EQ(job, req->job());
- // Update the load log and notify registered observers.
- OnFinishRequest(req->load_log(), req->id(), req->info(), error);
+ // Update the net log and notify registered observers.
+ OnFinishRequest(req->net_log(), req->id(), req->info(), net_error,
+ os_error, false /* was_from_cache */);
- req->OnComplete(error, addrlist);
+ req->OnComplete(net_error, addrlist);
// Check if the job was cancelled as a result of running the callback.
// (Meaning that |this| was deleted).
@@ -818,95 +989,93 @@
cur_completing_job_ = NULL;
}
-void HostResolverImpl::OnStartRequest(LoadLog* load_log,
+void HostResolverImpl::OnStartRequest(const BoundNetLog& net_log,
int request_id,
const RequestInfo& info) {
- LoadLog::BeginEvent(load_log, LoadLog::TYPE_HOST_RESOLVER_IMPL);
-
- if (requests_trace_) {
- requests_trace_->Add(StringPrintf(
- "Received request r%d for {hostname='%s', port=%d, priority=%d, "
- "speculative=%d, address_family=%d, allow_cached=%d, referrer='%s'}",
- request_id,
- info.hostname().c_str(),
- info.port(),
- static_cast<int>(info.priority()),
- static_cast<int>(info.is_speculative()),
- static_cast<int>(info.address_family()),
- static_cast<int>(info.allow_cached_response()),
- info.referrer().spec().c_str()));
- }
+ net_log.BeginEvent(NetLog::TYPE_HOST_RESOLVER_IMPL, NULL);
// Notify the observers of the start.
if (!observers_.empty()) {
- LoadLog::BeginEvent(
- load_log, LoadLog::TYPE_HOST_RESOLVER_IMPL_OBSERVER_ONSTART);
-
for (ObserversList::iterator it = observers_.begin();
it != observers_.end(); ++it) {
(*it)->OnStartResolution(request_id, info);
}
-
- LoadLog::EndEvent(
- load_log, LoadLog::TYPE_HOST_RESOLVER_IMPL_OBSERVER_ONSTART);
}
}
-void HostResolverImpl::OnFinishRequest(LoadLog* load_log,
+void HostResolverImpl::OnFinishRequest(const BoundNetLog& net_log,
int request_id,
const RequestInfo& info,
- int error) {
- if (requests_trace_) {
- requests_trace_->Add(StringPrintf(
- "Finished request r%d with error=%d", request_id, error));
- }
+ int net_error,
+ int os_error,
+ bool was_from_cache) {
+ bool was_resolved = net_error == OK;
// Notify the observers of the completion.
if (!observers_.empty()) {
- LoadLog::BeginEvent(
- load_log, LoadLog::TYPE_HOST_RESOLVER_IMPL_OBSERVER_ONFINISH);
-
- bool was_resolved = error == OK;
for (ObserversList::iterator it = observers_.begin();
it != observers_.end(); ++it) {
(*it)->OnFinishResolutionWithStatus(request_id, was_resolved, info);
}
-
- LoadLog::EndEvent(
- load_log, LoadLog::TYPE_HOST_RESOLVER_IMPL_OBSERVER_ONFINISH);
}
- LoadLog::EndEvent(load_log, LoadLog::TYPE_HOST_RESOLVER_IMPL);
+ // Log some extra parameters on failure.
+ scoped_refptr<NetLog::EventParameters> params;
+ if (!was_resolved)
+ params = new HostResolveFailedParams(net_error, os_error, was_from_cache);
+
+ net_log.EndEvent(NetLog::TYPE_HOST_RESOLVER_IMPL, params);
}
-void HostResolverImpl::OnCancelRequest(LoadLog* load_log,
+void HostResolverImpl::OnCancelRequest(const BoundNetLog& net_log,
int request_id,
const RequestInfo& info) {
- LoadLog::AddEvent(load_log, LoadLog::TYPE_CANCELLED);
-
- if (requests_trace_)
- requests_trace_->Add(StringPrintf("Cancelled request r%d", request_id));
+ net_log.AddEvent(NetLog::TYPE_CANCELLED, NULL);
// Notify the observers of the cancellation.
if (!observers_.empty()) {
- LoadLog::BeginEvent(
- load_log, LoadLog::TYPE_HOST_RESOLVER_IMPL_OBSERVER_ONCANCEL);
-
for (ObserversList::iterator it = observers_.begin();
it != observers_.end(); ++it) {
(*it)->OnCancelResolution(request_id, info);
}
-
- LoadLog::EndEvent(
- load_log, LoadLog::TYPE_HOST_RESOLVER_IMPL_OBSERVER_ONCANCEL);
}
- LoadLog::EndEvent(load_log, LoadLog::TYPE_HOST_RESOLVER_IMPL);
+ net_log.EndEvent(NetLog::TYPE_HOST_RESOLVER_IMPL, NULL);
}
void HostResolverImpl::OnIPAddressChanged() {
if (cache_.get())
cache_->clear();
+ if (ipv6_probe_monitoring_) {
+ DCHECK(!shutdown_);
+ if (shutdown_)
+ return;
+ DiscardIPv6ProbeJob();
+ ipv6_probe_job_ = new IPv6ProbeJob(this);
+ ipv6_probe_job_->Start();
+ }
+}
+
+void HostResolverImpl::DiscardIPv6ProbeJob() {
+ if (ipv6_probe_job_.get()) {
+ ipv6_probe_job_->Cancel();
+ ipv6_probe_job_ = NULL;
+ }
+}
+
+void HostResolverImpl::IPv6ProbeSetDefaultAddressFamily(
+ AddressFamily address_family) {
+ DCHECK(address_family == ADDRESS_FAMILY_UNSPECIFIED ||
+ address_family == ADDRESS_FAMILY_IPV4);
+ if (default_address_family_ != address_family) {
+ LOG(INFO) << "IPv6Probe forced AddressFamily setting to "
+ << ((address_family == ADDRESS_FAMILY_UNSPECIFIED)
+ ? "ADDRESS_FAMILY_UNSPECIFIED"
+ : "ADDRESS_FAMILY_IPV4");
+ }
+ default_address_family_ = address_family;
+ // Drop reference since the job has called us back.
+ DiscardIPv6ProbeJob();
}
// static
@@ -949,10 +1118,19 @@
}
}
+HostResolverImpl::Key HostResolverImpl::GetEffectiveKeyForRequest(
+ const RequestInfo& info) const {
+ AddressFamily effective_address_family = info.address_family();
+ if (effective_address_family == ADDRESS_FAMILY_UNSPECIFIED)
+ effective_address_family = default_address_family_;
+ return Key(info.hostname(), effective_address_family,
+ info.host_resolver_flags());
+}
+
HostResolverImpl::Job* HostResolverImpl::CreateAndStartJob(Request* req) {
DCHECK(CanCreateJobForPool(*GetPoolForRequest(req)));
- Key key(req->info().hostname(), req->info().address_family());
- scoped_refptr<Job> job = new Job(next_job_id_++, this, key, requests_trace_);
+ Key key = GetEffectiveKeyForRequest(req->info());
+ scoped_refptr<Job> job = new Job(next_job_id_++, this, key);
job->AddRequest(req);
AddOutstandingJob(job);
job->Start();
@@ -960,9 +1138,6 @@
}
int HostResolverImpl::EnqueueRequest(JobPool* pool, Request* req) {
- if (requests_trace_)
- requests_trace_->Add(StringPrintf("Queued request r%d", req->id()));
-
scoped_ptr<Request> req_evicted_from_queue(
pool->InsertPendingRequest(req));
@@ -971,10 +1146,9 @@
Request* r = req_evicted_from_queue.get();
int error = ERR_HOST_RESOLVER_QUEUE_TOO_LARGE;
- if (requests_trace_)
- requests_trace_->Add(StringPrintf("Evicted request r%d", r->id()));
-
- OnFinishRequest(r->load_log(), r->id(), r->info(), error);
+ OnFinishRequest(r->net_log(), r->id(), r->info(), error,
+ 0, /* os_error (not applicable) */
+ false /* was_from_cache */);
if (r == req)
return error;
diff --git a/net/base/host_resolver_impl.h b/net/base/host_resolver_impl.h
index feedc05..28526d1 100644
--- a/net/base/host_resolver_impl.h
+++ b/net/base/host_resolver_impl.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -8,10 +8,13 @@
#include <string>
#include <vector>
+#include "base/non_thread_safe.h"
#include "base/scoped_ptr.h"
+#include "net/base/capturing_net_log.h"
#include "net/base/host_cache.h"
#include "net/base/host_resolver.h"
#include "net/base/host_resolver_proc.h"
+#include "net/base/net_log.h"
#include "net/base/network_change_notifier.h"
namespace net {
@@ -47,6 +50,7 @@
// Requests are ordered in the queue based on their priority.
class HostResolverImpl : public HostResolver,
+ public NonThreadSafe,
public NetworkChangeNotifier::Observer {
public:
// The index into |job_pools_| for the various job pools. Pools with a higher
@@ -69,13 +73,10 @@
// thread-safe since it is run from multiple worker threads. If
// |resolver_proc| is NULL then the default host resolver procedure is
// used (which is SystemHostResolverProc except if overridden).
- // |notifier| must outlive HostResolverImpl. It can optionally be NULL, in
- // which case HostResolverImpl will not respond to network changes.
// |max_jobs| specifies the maximum number of threads that the host resolver
// will use. Use SetPoolConstraints() to specify finer-grain settings.
HostResolverImpl(HostResolverProc* resolver_proc,
HostCache* cache,
- NetworkChangeNotifier* notifier,
size_t max_jobs);
// HostResolver methods:
@@ -83,34 +84,26 @@
AddressList* addresses,
CompletionCallback* callback,
RequestHandle* out_req,
- LoadLog* load_log);
+ const BoundNetLog& net_log);
virtual void CancelRequest(RequestHandle req);
virtual void AddObserver(HostResolver::Observer* observer);
virtual void RemoveObserver(HostResolver::Observer* observer);
- // TODO(eroman): temp hack for http://crbug.com/15513
+ // Set address family, and disable IPv6 probe support.
+ virtual void SetDefaultAddressFamily(AddressFamily address_family);
+
+ // Continuously observe whether IPv6 is supported, and set the allowable
+ // address family to IPv4 iff IPv6 is not supported.
+ void ProbeIPv6Support();
+
+ virtual HostResolverImpl* GetAsHostResolverImpl() { return this; }
+
+ // TODO(eroman): hack for http://crbug.com/15513
virtual void Shutdown();
- virtual void SetDefaultAddressFamily(AddressFamily address_family) {
- default_address_family_ = address_family;
- }
-
- virtual bool IsHostResolverImpl() { return true; }
-
// Returns the cache this resolver uses, or NULL if caching is disabled.
HostCache* cache() { return cache_.get(); }
- // Clears the request trace log.
- void ClearRequestsTrace();
-
- // Starts/ends capturing requests to a trace log.
- void EnableRequestsTracing(bool enable);
-
- bool IsRequestsTracingEnabled() const;
-
- // Returns a copy of the requests trace log, or NULL if there is none.
- scoped_refptr<LoadLog> GetRequestsTrace();
-
// Applies a set of constraints for requests that belong to the specified
// pool. NOTE: Don't call this after requests have been already been started.
//
@@ -129,8 +122,8 @@
private:
class Job;
class JobPool;
+ class IPv6ProbeJob;
class Request;
- class RequestsTrace;
typedef std::vector<Request*> RequestsList;
typedef HostCache::Key Key;
typedef std::map<Key, scoped_refptr<Job> > JobMap;
@@ -156,28 +149,37 @@
// Removes |job| from the outstanding jobs list.
void RemoveOutstandingJob(Job* job);
- // Callback for when |job| has completed with |error| and |addrlist|.
- void OnJobComplete(Job* job, int error, const AddressList& addrlist);
+ // Callback for when |job| has completed with |net_error| and |addrlist|.
+ void OnJobComplete(Job* job, int net_error, int os_error,
+ const AddressList& addrlist);
// Called when a request has just been started.
- void OnStartRequest(LoadLog* load_log,
+ void OnStartRequest(const BoundNetLog& net_log,
int request_id,
const RequestInfo& info);
// Called when a request has just completed (before its callback is run).
- void OnFinishRequest(LoadLog* load_log,
+ void OnFinishRequest(const BoundNetLog& net_log,
int request_id,
const RequestInfo& info,
- int error);
+ int net_error,
+ int os_error,
+ bool was_from_cache);
// Called when a request has been cancelled.
- void OnCancelRequest(LoadLog* load_log,
+ void OnCancelRequest(const BoundNetLog& net_log,
int request_id,
const RequestInfo& info);
// NetworkChangeNotifier::Observer methods:
virtual void OnIPAddressChanged();
+ // Notify IPv6ProbeJob not to call back, and discard reference to the job.
+ void DiscardIPv6ProbeJob();
+
+ // Callback from IPv6 probe activity.
+ void IPv6ProbeSetDefaultAddressFamily(AddressFamily address_family);
+
// Returns true if the constraints for |pool| are met, and a new job can be
// created for this pool.
bool CanCreateJobForPool(const JobPool& pool) const;
@@ -193,6 +195,11 @@
// may have multiple requests attached to it.
void ProcessQueuedRequests();
+ // Returns the (hostname, address_family) key to use for |info|, choosing an
+ // "effective" address family by inheriting the resolver's default address
+ // family when the request leaves it unspecified.
+ Key GetEffectiveKeyForRequest(const RequestInfo& info) const;
+
// Attaches |req| to a new job, and starts it. Returns that job.
Job* CreateAndStartJob(Request* req);
@@ -234,12 +241,16 @@
// Address family to use when the request doesn't specify one.
AddressFamily default_address_family_;
- // TODO(eroman): temp hack for http://crbug.com/15513
+ // TODO(eroman): hack for http://crbug.com/15513
bool shutdown_;
- NetworkChangeNotifier* const network_change_notifier_;
+ // Indicate if probing is done after each network change event to set address
+ // family.
+ // When false, explicit setting of address family is used.
+ bool ipv6_probe_monitoring_;
- scoped_refptr<RequestsTrace> requests_trace_;
+ // The last un-cancelled IPv6ProbeJob (if any).
+ scoped_refptr<IPv6ProbeJob> ipv6_probe_job_;
DISALLOW_COPY_AND_ASSIGN(HostResolverImpl);
};
diff --git a/net/base/host_resolver_impl_unittest.cc b/net/base/host_resolver_impl_unittest.cc
index 8ca58f0..6b33a56 100644
--- a/net/base/host_resolver_impl_unittest.cc
+++ b/net/base/host_resolver_impl_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -9,12 +9,13 @@
#include "base/compiler_specific.h"
#include "base/message_loop.h"
#include "base/ref_counted.h"
+#include "base/string_util.h"
#include "net/base/address_list.h"
#include "net/base/completion_callback.h"
-#include "net/base/load_log_unittest.h"
#include "net/base/mock_host_resolver.h"
-#include "net/base/mock_network_change_notifier.h"
#include "net/base/net_errors.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/net_util.h"
#include "net/base/sys_addrinfo.h"
#include "net/base/test_completion_callback.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -37,11 +38,7 @@
static const size_t kMaxJobs = 10u;
HostResolverImpl* CreateHostResolverImpl(HostResolverProc* resolver_proc) {
- return new HostResolverImpl(
- resolver_proc,
- CreateDefaultCache(),
- NULL, // network_change_notifier
- kMaxJobs);
+ return new HostResolverImpl(resolver_proc, CreateDefaultCache(), kMaxJobs);
}
// Helper to create a HostResolver::RequestInfo.
@@ -53,11 +50,31 @@
return info;
}
+// Helper to create a HostResolver::RequestInfo.
+HostResolver::RequestInfo CreateResolverRequestForAddressFamily(
+ const std::string& hostname,
+ RequestPriority priority,
+ AddressFamily address_family) {
+ HostResolver::RequestInfo info(hostname, 80);
+ info.set_priority(priority);
+ info.set_address_family(address_family);
+ return info;
+}
+
// A variant of WaitingHostResolverProc that pushes each host mapped into a
// list.
// (and uses a manual-reset event rather than auto-reset).
class CapturingHostResolverProc : public HostResolverProc {
public:
+ struct CaptureEntry {
+ CaptureEntry(const std::string& hostname, AddressFamily address_family)
+ : hostname(hostname), address_family(address_family) {}
+ std::string hostname;
+ AddressFamily address_family;
+ };
+
+ typedef std::vector<CaptureEntry> CaptureList;
+
explicit CapturingHostResolverProc(HostResolverProc* previous)
: HostResolverProc(previous), event_(true, false) {
}
@@ -66,19 +83,22 @@
event_.Signal();
}
- virtual int Resolve(const std::string& host,
+ virtual int Resolve(const std::string& hostname,
AddressFamily address_family,
- AddressList* addrlist) {
+ HostResolverFlags host_resolver_flags,
+ AddressList* addrlist,
+ int* os_error) {
event_.Wait();
{
AutoLock l(lock_);
- capture_list_.push_back(host);
+ capture_list_.push_back(CaptureEntry(hostname, address_family));
}
- return ResolveUsingPrevious(host, address_family, addrlist);
+ return ResolveUsingPrevious(hostname, address_family,
+ host_resolver_flags, addrlist, os_error);
}
- std::vector<std::string> GetCaptureList() const {
- std::vector<std::string> copy;
+ CaptureList GetCaptureList() const {
+ CaptureList copy;
{
AutoLock l(lock_);
copy = capture_list_;
@@ -89,11 +109,45 @@
private:
~CapturingHostResolverProc() {}
- std::vector<std::string> capture_list_;
+ CaptureList capture_list_;
mutable Lock lock_;
base::WaitableEvent event_;
};
+// This resolver function creates an IPv4 address, whose numeral value
+// describes a hash of the requested hostname, and the value of the requested
+// address_family.
+//
+// The resolved address for (hostname, address_family) will take the form:
+// 192.x.y.z
+//
+// Where:
+// x = length of hostname
+// y = ASCII value of hostname[0]
+// z = value of address_family
+//
+class EchoingHostResolverProc : public HostResolverProc {
+ public:
+ EchoingHostResolverProc() : HostResolverProc(NULL) {}
+
+ virtual int Resolve(const std::string& hostname,
+ AddressFamily address_family,
+ HostResolverFlags host_resolver_flags,
+ AddressList* addrlist,
+ int* os_error) {
+ // Encode the request's hostname and address_family in the output address.
+ std::string ip_literal = StringPrintf("192.%d.%d.%d",
+ static_cast<int>(hostname.size()),
+ static_cast<int>(hostname[0]),
+ static_cast<int>(address_family));
+
+ return SystemHostResolverProc(ip_literal,
+ ADDRESS_FAMILY_UNSPECIFIED,
+ host_resolver_flags,
+ addrlist, os_error);
+ }
+};
+
// Helper that represents a single Resolve() result, used to inspect all the
// resolve results by forwarding them to Delegate.
class ResolveRequest {
@@ -113,7 +167,8 @@
ALLOW_THIS_IN_INITIALIZER_LIST(
callback_(this, &ResolveRequest::OnLookupFinished)) {
// Start the request.
- int err = resolver->Resolve(info_, &addrlist_, &callback_, &req_, NULL);
+ int err = resolver->Resolve(info_, &addrlist_, &callback_, &req_,
+ BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, err);
}
@@ -124,7 +179,8 @@
ALLOW_THIS_IN_INITIALIZER_LIST(
callback_(this, &ResolveRequest::OnLookupFinished)) {
// Start the request.
- int err = resolver->Resolve(info, &addrlist_, &callback_, &req_, NULL);
+ int err = resolver->Resolve(info, &addrlist_, &callback_, &req_,
+ BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, err);
}
@@ -209,13 +265,15 @@
CreateHostResolverImpl(resolver_proc));
HostResolver::RequestInfo info("just.testing", kPortnum);
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
- int err = host_resolver->Resolve(info, &adrlist, NULL, NULL, log);
+ CapturingBoundNetLog log(CapturingNetLog::kUnbounded);
+ int err = host_resolver->Resolve(info, &adrlist, NULL, NULL, log.bound());
EXPECT_EQ(OK, err);
- EXPECT_EQ(2u, log->entries().size());
- EXPECT_TRUE(LogContainsBeginEvent(*log, 0, LoadLog::TYPE_HOST_RESOLVER_IMPL));
- EXPECT_TRUE(LogContainsEndEvent(*log, 1, LoadLog::TYPE_HOST_RESOLVER_IMPL));
+ EXPECT_EQ(2u, log.entries().size());
+ EXPECT_TRUE(LogContainsBeginEvent(
+ log.entries(), 0, NetLog::TYPE_HOST_RESOLVER_IMPL));
+ EXPECT_TRUE(LogContainsEndEvent(
+ log.entries(), 1, NetLog::TYPE_HOST_RESOLVER_IMPL));
const struct addrinfo* ainfo = adrlist.head();
EXPECT_EQ(static_cast<addrinfo*>(NULL), ainfo->ai_next);
@@ -239,20 +297,23 @@
CreateHostResolverImpl(resolver_proc));
HostResolver::RequestInfo info("just.testing", kPortnum);
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
- int err = host_resolver->Resolve(info, &adrlist, &callback_, NULL, log);
+ CapturingBoundNetLog log(CapturingNetLog::kUnbounded);
+ int err = host_resolver->Resolve(info, &adrlist, &callback_, NULL,
+ log.bound());
EXPECT_EQ(ERR_IO_PENDING, err);
- EXPECT_EQ(1u, log->entries().size());
- EXPECT_TRUE(LogContainsBeginEvent(*log, 0, LoadLog::TYPE_HOST_RESOLVER_IMPL));
+ EXPECT_EQ(1u, log.entries().size());
+ EXPECT_TRUE(LogContainsBeginEvent(
+ log.entries(), 0, NetLog::TYPE_HOST_RESOLVER_IMPL));
MessageLoop::current()->Run();
ASSERT_TRUE(callback_called_);
ASSERT_EQ(OK, callback_result_);
- EXPECT_EQ(2u, log->entries().size());
- EXPECT_TRUE(LogContainsEndEvent(*log, 1, LoadLog::TYPE_HOST_RESOLVER_IMPL));
+ EXPECT_EQ(2u, log.entries().size());
+ EXPECT_TRUE(LogContainsEndEvent(
+ log.entries(), 1, NetLog::TYPE_HOST_RESOLVER_IMPL));
const struct addrinfo* ainfo = adrlist.head();
EXPECT_EQ(static_cast<addrinfo*>(NULL), ainfo->ai_next);
@@ -268,7 +329,7 @@
scoped_refptr<WaitingHostResolverProc> resolver_proc =
new WaitingHostResolverProc(NULL);
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
+ CapturingBoundNetLog log(CapturingNetLog::kUnbounded);
{
scoped_refptr<HostResolver> host_resolver(
CreateHostResolverImpl(resolver_proc));
@@ -276,7 +337,8 @@
const int kPortnum = 80;
HostResolver::RequestInfo info("just.testing", kPortnum);
- int err = host_resolver->Resolve(info, &adrlist, &callback_, NULL, log);
+ int err = host_resolver->Resolve(info, &adrlist, &callback_, NULL,
+ log.bound());
EXPECT_EQ(ERR_IO_PENDING, err);
// Make sure we will exit the queue even when callback is not called.
@@ -288,11 +350,13 @@
resolver_proc->Signal();
- EXPECT_EQ(3u, log->entries().size());
- EXPECT_TRUE(LogContainsBeginEvent(*log, 0, LoadLog::TYPE_HOST_RESOLVER_IMPL));
+ EXPECT_EQ(3u, log.entries().size());
+ EXPECT_TRUE(LogContainsBeginEvent(
+ log.entries(), 0, NetLog::TYPE_HOST_RESOLVER_IMPL));
EXPECT_TRUE(LogContainsEvent(
- *log, 1, LoadLog::TYPE_CANCELLED, LoadLog::PHASE_NONE));
- EXPECT_TRUE(LogContainsEndEvent(*log, 2, LoadLog::TYPE_HOST_RESOLVER_IMPL));
+ log.entries(), 1, NetLog::TYPE_CANCELLED, NetLog::PHASE_NONE));
+ EXPECT_TRUE(LogContainsEndEvent(
+ log.entries(), 2, NetLog::TYPE_HOST_RESOLVER_IMPL));
EXPECT_FALSE(callback_called_);
}
@@ -309,7 +373,7 @@
AddressList adrlist;
const int kPortnum = 5555;
HostResolver::RequestInfo info("127.1.2.3", kPortnum);
- int err = host_resolver->Resolve(info, &adrlist, NULL, NULL, NULL);
+ int err = host_resolver->Resolve(info, &adrlist, NULL, NULL, BoundNetLog());
EXPECT_EQ(OK, err);
const struct addrinfo* ainfo = adrlist.head();
@@ -334,7 +398,7 @@
AddressList adrlist;
const int kPortnum = 5555;
HostResolver::RequestInfo info("2001:db8::1", kPortnum);
- int err = host_resolver->Resolve(info, &adrlist, NULL, NULL, NULL);
+ int err = host_resolver->Resolve(info, &adrlist, NULL, NULL, BoundNetLog());
// On computers without IPv6 support, getaddrinfo cannot convert IPv6
// address literals to addresses (getaddrinfo returns EAI_NONAME). So this
// test has to allow host_resolver->Resolve to fail.
@@ -369,7 +433,7 @@
AddressList adrlist;
const int kPortnum = 5555;
HostResolver::RequestInfo info("", kPortnum);
- int err = host_resolver->Resolve(info, &adrlist, NULL, NULL, NULL);
+ int err = host_resolver->Resolve(info, &adrlist, NULL, NULL, BoundNetLog());
EXPECT_EQ(ERR_NAME_NOT_RESOLVED, err);
}
@@ -403,7 +467,8 @@
// The resolver_proc should have been called only twice -- once with "a",
// once with "b".
- std::vector<std::string> capture_list = resolver_proc_->GetCaptureList();
+ CapturingHostResolverProc::CaptureList capture_list =
+ resolver_proc_->GetCaptureList();
EXPECT_EQ(2U, capture_list.size());
// End this test, we are done.
@@ -678,7 +743,7 @@
// Turn off caching for this host resolver.
scoped_refptr<HostResolver> host_resolver(
- new HostResolverImpl(resolver_proc, NULL, NULL, kMaxJobs));
+ new HostResolverImpl(resolver_proc, NULL, kMaxJobs));
// The class will receive callbacks for when each resolve completes. It
// checks that the right things happened.
@@ -720,7 +785,8 @@
AddressList addrlist;
HostResolver::RequestInfo info("a", 70);
- int error = resolver->Resolve(info, &addrlist, junk_callback, NULL, NULL);
+ int error = resolver->Resolve(info, &addrlist, junk_callback, NULL,
+ BoundNetLog());
EXPECT_EQ(OK, error);
// Ok good. Now make sure that if we ask to bypass the cache, it can no
@@ -838,22 +904,15 @@
// Resolve "host1".
HostResolver::RequestInfo info1("host1", 70);
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
- int rv = host_resolver->Resolve(info1, &addrlist, NULL, NULL, log);
+ CapturingBoundNetLog log(CapturingNetLog::kUnbounded);
+ int rv = host_resolver->Resolve(info1, &addrlist, NULL, NULL, log.bound());
EXPECT_EQ(OK, rv);
- EXPECT_EQ(6u, log->entries().size());
- EXPECT_TRUE(LogContainsBeginEvent(*log, 0, LoadLog::TYPE_HOST_RESOLVER_IMPL));
+ EXPECT_EQ(2u, log.entries().size());
EXPECT_TRUE(LogContainsBeginEvent(
- *log, 1, LoadLog::TYPE_HOST_RESOLVER_IMPL_OBSERVER_ONSTART));
+ log.entries(), 0, NetLog::TYPE_HOST_RESOLVER_IMPL));
EXPECT_TRUE(LogContainsEndEvent(
- *log, 2, LoadLog::TYPE_HOST_RESOLVER_IMPL_OBSERVER_ONSTART));
- EXPECT_TRUE(LogContainsBeginEvent(
- *log, 3, LoadLog::TYPE_HOST_RESOLVER_IMPL_OBSERVER_ONFINISH));
- EXPECT_TRUE(LogContainsEndEvent(
- *log, 4, LoadLog::TYPE_HOST_RESOLVER_IMPL_OBSERVER_ONFINISH));
- EXPECT_TRUE(LogContainsEndEvent(
- *log, 5, LoadLog::TYPE_HOST_RESOLVER_IMPL));
+ log.entries(), 1, NetLog::TYPE_HOST_RESOLVER_IMPL));
EXPECT_EQ(1U, observer.start_log.size());
EXPECT_EQ(1U, observer.finish_log.size());
@@ -866,7 +925,7 @@
// Resolve "host1" again -- this time it will be served from cache, but it
// should still notify of completion.
TestCompletionCallback callback;
- rv = host_resolver->Resolve(info1, &addrlist, &callback, NULL, NULL);
+ rv = host_resolver->Resolve(info1, &addrlist, &callback, NULL, BoundNetLog());
ASSERT_EQ(OK, rv); // Should complete synchronously.
EXPECT_EQ(2U, observer.start_log.size());
@@ -880,7 +939,7 @@
// Resolve "host2", setting referrer to "http://foobar.com"
HostResolver::RequestInfo info2("host2", 70);
info2.set_referrer(GURL("http://foobar.com"));
- rv = host_resolver->Resolve(info2, &addrlist, NULL, NULL, NULL);
+ rv = host_resolver->Resolve(info2, &addrlist, NULL, NULL, BoundNetLog());
EXPECT_EQ(OK, rv);
EXPECT_EQ(3U, observer.start_log.size());
@@ -896,7 +955,7 @@
// Resolve "host3"
HostResolver::RequestInfo info3("host3", 70);
- host_resolver->Resolve(info3, &addrlist, NULL, NULL, NULL);
+ host_resolver->Resolve(info3, &addrlist, NULL, NULL, BoundNetLog());
// No effect this time, since observer was removed.
EXPECT_EQ(3U, observer.start_log.size());
@@ -926,7 +985,8 @@
HostResolver::RequestInfo info1("host1", 70);
HostResolver::RequestHandle req = NULL;
AddressList addrlist;
- int rv = host_resolver->Resolve(info1, &addrlist, &callback, &req, NULL);
+ int rv = host_resolver->Resolve(info1, &addrlist, &callback, &req,
+ BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_TRUE(NULL != req);
@@ -949,7 +1009,8 @@
// Start an async request for (host2:60)
HostResolver::RequestInfo info2("host2", 60);
- rv = host_resolver->Resolve(info2, &addrlist, &callback, NULL, NULL);
+ rv = host_resolver->Resolve(info2, &addrlist, &callback, NULL,
+ BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_TRUE(NULL != req);
@@ -978,32 +1039,31 @@
// Test that IP address changes flush the cache.
TEST_F(HostResolverImplTest, FlushCacheOnIPAddressChange) {
- MockNetworkChangeNotifier mock_network_change_notifier;
scoped_refptr<HostResolver> host_resolver(
- new HostResolverImpl(NULL, CreateDefaultCache(),
- &mock_network_change_notifier,
- kMaxJobs));
+ new HostResolverImpl(NULL, CreateDefaultCache(), kMaxJobs));
AddressList addrlist;
// Resolve "host1".
HostResolver::RequestInfo info1("host1", 70);
TestCompletionCallback callback;
- int rv = host_resolver->Resolve(info1, &addrlist, &callback, NULL, NULL);
+ int rv = host_resolver->Resolve(info1, &addrlist, &callback, NULL,
+ BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(OK, callback.WaitForResult());
// Resolve "host1" again -- this time it will be served from cache, but it
// should still notify of completion.
- rv = host_resolver->Resolve(info1, &addrlist, &callback, NULL, NULL);
+ rv = host_resolver->Resolve(info1, &addrlist, &callback, NULL, BoundNetLog());
ASSERT_EQ(OK, rv); // Should complete synchronously.
// Flush cache by triggering an IP address change.
- mock_network_change_notifier.NotifyIPAddressChange();
+ NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
+ MessageLoop::current()->RunAllPending(); // Notification happens async.
// Resolve "host1" again -- this time it won't be served from cache, so it
// will complete asynchronously.
- rv = host_resolver->Resolve(info1, &addrlist, &callback, NULL, NULL);
+ rv = host_resolver->Resolve(info1, &addrlist, &callback, NULL, BoundNetLog());
ASSERT_EQ(ERR_IO_PENDING, rv); // Should complete asynchronously.
EXPECT_EQ(OK, callback.WaitForResult());
}
@@ -1017,8 +1077,7 @@
// This HostResolverImpl will only allow 1 outstanding resolve at a time.
size_t kMaxJobs = 1u;
scoped_refptr<HostResolver> host_resolver(
- new HostResolverImpl(resolver_proc, CreateDefaultCache(),
- NULL, kMaxJobs));
+ new HostResolverImpl(resolver_proc, CreateDefaultCache(), kMaxJobs));
CapturingObserver observer;
host_resolver->AddObserver(&observer);
@@ -1043,7 +1102,7 @@
// Start all of the requests.
for (size_t i = 0; i < arraysize(req); ++i) {
int rv = host_resolver->Resolve(req[i], &addrlist[i],
- &callback[i], NULL, NULL);
+ &callback[i], NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
}
@@ -1061,16 +1120,17 @@
// the requests should complete in order of priority (with the exception
// of the first request, which gets started right away, since there is
// nothing outstanding).
- std::vector<std::string> capture_list = resolver_proc->GetCaptureList();
+ CapturingHostResolverProc::CaptureList capture_list =
+ resolver_proc->GetCaptureList();
ASSERT_EQ(7u, capture_list.size());
- EXPECT_EQ("req0", capture_list[0]);
- EXPECT_EQ("req4", capture_list[1]);
- EXPECT_EQ("req5", capture_list[2]);
- EXPECT_EQ("req1", capture_list[3]);
- EXPECT_EQ("req2", capture_list[4]);
- EXPECT_EQ("req3", capture_list[5]);
- EXPECT_EQ("req6", capture_list[6]);
+ EXPECT_EQ("req0", capture_list[0].hostname);
+ EXPECT_EQ("req4", capture_list[1].hostname);
+ EXPECT_EQ("req5", capture_list[2].hostname);
+ EXPECT_EQ("req1", capture_list[3].hostname);
+ EXPECT_EQ("req2", capture_list[4].hostname);
+ EXPECT_EQ("req3", capture_list[5].hostname);
+ EXPECT_EQ("req6", capture_list[6].hostname);
// Also check using the observer's trace.
EXPECT_EQ(8U, observer.start_log.size());
@@ -1101,8 +1161,7 @@
// This HostResolverImpl will only allow 1 outstanding resolve at a time.
const size_t kMaxJobs = 1u;
scoped_refptr<HostResolver> host_resolver(
- new HostResolverImpl(resolver_proc, CreateDefaultCache(),
- NULL, kMaxJobs));
+ new HostResolverImpl(resolver_proc, CreateDefaultCache(), kMaxJobs));
// Note that at this point the CapturingHostResolverProc is blocked, so any
// requests we make will not complete.
@@ -1124,7 +1183,7 @@
// Start all of the requests.
for (size_t i = 0; i < arraysize(req); ++i) {
int rv = host_resolver->Resolve(req[i], &addrlist[i],
- &callback[i], &handle[i], NULL);
+ &callback[i], &handle[i], BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
}
@@ -1146,13 +1205,14 @@
// Verify that they called out the the resolver proc (which runs on the
// resolver thread) in the expected order.
- std::vector<std::string> capture_list = resolver_proc->GetCaptureList();
+ CapturingHostResolverProc::CaptureList capture_list =
+ resolver_proc->GetCaptureList();
ASSERT_EQ(4u, capture_list.size());
- EXPECT_EQ("req0", capture_list[0]);
- EXPECT_EQ("req2", capture_list[1]);
- EXPECT_EQ("req6", capture_list[2]);
- EXPECT_EQ("req3", capture_list[3]);
+ EXPECT_EQ("req0", capture_list[0].hostname);
+ EXPECT_EQ("req2", capture_list[1].hostname);
+ EXPECT_EQ("req6", capture_list[2].hostname);
+ EXPECT_EQ("req3", capture_list[3].hostname);
}
// Test that when too many requests are enqueued, old ones start to be aborted.
@@ -1162,9 +1222,8 @@
// This HostResolverImpl will only allow 1 outstanding resolve at a time.
const size_t kMaxOutstandingJobs = 1u;
- scoped_refptr<HostResolverImpl> host_resolver(
- new HostResolverImpl(resolver_proc, CreateDefaultCache(),
- NULL, kMaxOutstandingJobs));
+ scoped_refptr<HostResolverImpl> host_resolver(new HostResolverImpl(
+ resolver_proc, CreateDefaultCache(), kMaxOutstandingJobs));
// Only allow up to 3 requests to be enqueued at a time.
const size_t kMaxPendingRequests = 3u;
@@ -1198,7 +1257,7 @@
// Start all of the requests.
for (size_t i = 0; i < arraysize(req); ++i) {
int rv = host_resolver->Resolve(req[i], &addrlist[i],
- &callback[i], &handle[i], NULL);
+ &callback[i], &handle[i], BoundNetLog());
if (i == 4u)
EXPECT_EQ(ERR_HOST_RESOLVER_QUEUE_TOO_LARGE, rv);
else
@@ -1223,15 +1282,208 @@
// Verify that they called out the the resolver proc (which runs on the
// resolver thread) in the expected order.
- std::vector<std::string> capture_list = resolver_proc->GetCaptureList();
+ CapturingHostResolverProc::CaptureList capture_list =
+ resolver_proc->GetCaptureList();
ASSERT_EQ(4u, capture_list.size());
- EXPECT_EQ("req0", capture_list[0]);
- EXPECT_EQ("req1", capture_list[1]);
- EXPECT_EQ("req6", capture_list[2]);
- EXPECT_EQ("req7", capture_list[3]);
+ EXPECT_EQ("req0", capture_list[0].hostname);
+ EXPECT_EQ("req1", capture_list[1].hostname);
+ EXPECT_EQ("req6", capture_list[2].hostname);
+ EXPECT_EQ("req7", capture_list[3].hostname);
}
+// Tests that after changing the default AddressFamily to IPV4, requests
+// with UNSPECIFIED address family map to IPV4.
+TEST_F(HostResolverImplTest, SetDefaultAddressFamily_IPv4) {
+ scoped_refptr<CapturingHostResolverProc> resolver_proc =
+ new CapturingHostResolverProc(new EchoingHostResolverProc);
+
+ // This HostResolverImpl will only allow 1 outstanding resolve at a time.
+ const size_t kMaxOutstandingJobs = 1u;
+ scoped_refptr<HostResolverImpl> host_resolver(new HostResolverImpl(
+ resolver_proc, CreateDefaultCache(), kMaxOutstandingJobs));
+
+ host_resolver->SetDefaultAddressFamily(ADDRESS_FAMILY_IPV4);
+
+ // Note that at this point the CapturingHostResolverProc is blocked, so any
+ // requests we make will not complete.
+
+ HostResolver::RequestInfo req[] = {
+ CreateResolverRequestForAddressFamily("h1", MEDIUM,
+ ADDRESS_FAMILY_UNSPECIFIED),
+ CreateResolverRequestForAddressFamily("h1", MEDIUM, ADDRESS_FAMILY_IPV4),
+ CreateResolverRequestForAddressFamily("h1", MEDIUM, ADDRESS_FAMILY_IPV6),
+ };
+
+ TestCompletionCallback callback[arraysize(req)];
+ AddressList addrlist[arraysize(req)];
+ HostResolver::RequestHandle handle[arraysize(req)];
+
+ // Start all of the requests.
+ for (size_t i = 0; i < arraysize(req); ++i) {
+ int rv = host_resolver->Resolve(req[i], &addrlist[i],
+ &callback[i], &handle[i], BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv) << i;
+ }
+
+ // Unblock the resolver thread so the requests can run.
+ resolver_proc->Signal();
+
+ // Wait for all the requests to complete.
+ for (size_t i = 0u; i < arraysize(req); ++i) {
+ EXPECT_EQ(OK, callback[i].WaitForResult());
+ }
+
+ // Since the requests all had the same priority and we limited the thread
+ // count to 1, they should have completed in the same order as they were
+ // requested. Moreover, request0 and request1 will have been serviced by
+ // the same job.
+
+ CapturingHostResolverProc::CaptureList capture_list =
+ resolver_proc->GetCaptureList();
+ ASSERT_EQ(2u, capture_list.size());
+
+ EXPECT_EQ("h1", capture_list[0].hostname);
+ EXPECT_EQ(ADDRESS_FAMILY_IPV4, capture_list[0].address_family);
+
+ EXPECT_EQ("h1", capture_list[1].hostname);
+ EXPECT_EQ(ADDRESS_FAMILY_IPV6, capture_list[1].address_family);
+
+ // Now check that the correct resolved IP addresses were returned.
+ // Addresses take the form: 192.x.y.z
+ // x = length of hostname
+ // y = ASCII value of hostname[0]
+ // z = value of address family
+ EXPECT_EQ("192.2.104.1", NetAddressToString(addrlist[0].head()));
+ EXPECT_EQ("192.2.104.1", NetAddressToString(addrlist[1].head()));
+ EXPECT_EQ("192.2.104.2", NetAddressToString(addrlist[2].head()));
+}
+
+// This is the exact same test as SetDefaultAddressFamily_IPv4, except the order
+// of requests 0 and 1 is flipped, and the default is set to IPv6 in place of
+// IPv4.
+TEST_F(HostResolverImplTest, SetDefaultAddressFamily_IPv6) {
+ scoped_refptr<CapturingHostResolverProc> resolver_proc =
+ new CapturingHostResolverProc(new EchoingHostResolverProc);
+
+ // This HostResolverImpl will only allow 1 outstanding resolve at a time.
+ const size_t kMaxOutstandingJobs = 1u;
+ scoped_refptr<HostResolverImpl> host_resolver(new HostResolverImpl(
+ resolver_proc, CreateDefaultCache(), kMaxOutstandingJobs));
+
+ host_resolver->SetDefaultAddressFamily(ADDRESS_FAMILY_IPV6);
+
+ // Note that at this point the CapturingHostResolverProc is blocked, so any
+ // requests we make will not complete.
+
+ HostResolver::RequestInfo req[] = {
+ CreateResolverRequestForAddressFamily("h1", MEDIUM, ADDRESS_FAMILY_IPV6),
+ CreateResolverRequestForAddressFamily("h1", MEDIUM,
+ ADDRESS_FAMILY_UNSPECIFIED),
+ CreateResolverRequestForAddressFamily("h1", MEDIUM, ADDRESS_FAMILY_IPV4),
+ };
+
+ TestCompletionCallback callback[arraysize(req)];
+ AddressList addrlist[arraysize(req)];
+ HostResolver::RequestHandle handle[arraysize(req)];
+
+ // Start all of the requests.
+ for (size_t i = 0; i < arraysize(req); ++i) {
+ int rv = host_resolver->Resolve(req[i], &addrlist[i],
+ &callback[i], &handle[i], BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv) << i;
+ }
+
+ // Unblock the resolver thread so the requests can run.
+ resolver_proc->Signal();
+
+ // Wait for all the requests to complete.
+ for (size_t i = 0u; i < arraysize(req); ++i) {
+ EXPECT_EQ(OK, callback[i].WaitForResult());
+ }
+
+ // Since the requests all had the same priority and we limited the thread
+ // count to 1, they should have completed in the same order as they were
+ // requested. Moreover, request0 and request1 will have been serviced by
+ // the same job.
+
+ CapturingHostResolverProc::CaptureList capture_list =
+ resolver_proc->GetCaptureList();
+ ASSERT_EQ(2u, capture_list.size());
+
+ EXPECT_EQ("h1", capture_list[0].hostname);
+ EXPECT_EQ(ADDRESS_FAMILY_IPV6, capture_list[0].address_family);
+
+ EXPECT_EQ("h1", capture_list[1].hostname);
+ EXPECT_EQ(ADDRESS_FAMILY_IPV4, capture_list[1].address_family);
+
+ // Now check that the correct resolved IP addresses were returned.
+ // Addresses take the form: 192.x.y.z
+ // x = length of hostname
+ // y = ASCII value of hostname[0]
+ // z = value of address family
+ EXPECT_EQ("192.2.104.2", NetAddressToString(addrlist[0].head()));
+ EXPECT_EQ("192.2.104.2", NetAddressToString(addrlist[1].head()));
+ EXPECT_EQ("192.2.104.1", NetAddressToString(addrlist[2].head()));
+}
+
+// This tests that the default address family is respected for synchronous
+// resolutions.
+TEST_F(HostResolverImplTest, SetDefaultAddressFamily_Synchronous) {
+ scoped_refptr<CapturingHostResolverProc> resolver_proc =
+ new CapturingHostResolverProc(new EchoingHostResolverProc);
+
+ const size_t kMaxOutstandingJobs = 10u;
+ scoped_refptr<HostResolverImpl> host_resolver(new HostResolverImpl(
+ resolver_proc, CreateDefaultCache(), kMaxOutstandingJobs));
+
+ host_resolver->SetDefaultAddressFamily(ADDRESS_FAMILY_IPV4);
+
+ // Unblock the resolver thread so the requests can run.
+ resolver_proc->Signal();
+
+ HostResolver::RequestInfo req[] = {
+ CreateResolverRequestForAddressFamily("b", MEDIUM,
+ ADDRESS_FAMILY_UNSPECIFIED),
+ CreateResolverRequestForAddressFamily("b", MEDIUM, ADDRESS_FAMILY_IPV6),
+ CreateResolverRequestForAddressFamily("b", MEDIUM,
+ ADDRESS_FAMILY_UNSPECIFIED),
+ CreateResolverRequestForAddressFamily("b", MEDIUM, ADDRESS_FAMILY_IPV4),
+ };
+ AddressList addrlist[arraysize(req)];
+
+ // Start and run all of the requests synchronously.
+ for (size_t i = 0; i < arraysize(req); ++i) {
+ int rv = host_resolver->Resolve(req[i], &addrlist[i],
+ NULL, NULL, BoundNetLog());
+ EXPECT_EQ(OK, rv) << i;
+ }
+
+ // We should have sent 2 requests to the resolver --
+ // one for (b, IPv4), and one for (b, IPv6).
+ CapturingHostResolverProc::CaptureList capture_list =
+ resolver_proc->GetCaptureList();
+ ASSERT_EQ(2u, capture_list.size());
+
+ EXPECT_EQ("b", capture_list[0].hostname);
+ EXPECT_EQ(ADDRESS_FAMILY_IPV4, capture_list[0].address_family);
+
+ EXPECT_EQ("b", capture_list[1].hostname);
+ EXPECT_EQ(ADDRESS_FAMILY_IPV6, capture_list[1].address_family);
+
+ // Now check that the correct resolved IP addresses were returned.
+ // Addresses take the form: 192.x.y.z
+ // x = length of hostname
+ // y = ASCII value of hostname[0]
+ // z = value of address family
+ EXPECT_EQ("192.1.98.1", NetAddressToString(addrlist[0].head()));
+ EXPECT_EQ("192.1.98.2", NetAddressToString(addrlist[1].head()));
+ EXPECT_EQ("192.1.98.1", NetAddressToString(addrlist[2].head()));
+ EXPECT_EQ("192.1.98.1", NetAddressToString(addrlist[3].head()));
+}
+
+// TODO(cbentzel): Test a mix of requests with different HostResolverFlags.
+
} // namespace
} // namespace net
diff --git a/net/base/host_resolver_proc.cc b/net/base/host_resolver_proc.cc
index 5ccf01a..804135c 100644
--- a/net/base/host_resolver_proc.cc
+++ b/net/base/host_resolver_proc.cc
@@ -67,14 +67,20 @@
return default_proc_;
}
-int HostResolverProc::ResolveUsingPrevious(const std::string& host,
- AddressFamily address_family,
- AddressList* addrlist) {
- if (previous_proc_)
- return previous_proc_->Resolve(host, address_family, addrlist);
+int HostResolverProc::ResolveUsingPrevious(
+ const std::string& host,
+ AddressFamily address_family,
+ HostResolverFlags host_resolver_flags,
+ AddressList* addrlist,
+ int* os_error) {
+ if (previous_proc_) {
+ return previous_proc_->Resolve(host, address_family, host_resolver_flags,
+ addrlist, os_error);
+ }
// Final fallback is the system resolver.
- return SystemHostResolverProc(host, address_family, addrlist);
+ return SystemHostResolverProc(host, address_family, host_resolver_flags,
+ addrlist, os_error);
}
#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_OPENBSD)
@@ -152,7 +158,12 @@
int SystemHostResolverProc(const std::string& host,
AddressFamily address_family,
- AddressList* addrlist) {
+ HostResolverFlags host_resolver_flags,
+ AddressList* addrlist,
+ int* os_error) {
+ if (os_error)
+ *os_error = 0;
+
// The result of |getaddrinfo| for empty hosts is inconsistent across systems.
// On Windows it gives the default interface's address, whereas on Linux it
// gives an error. We will make it fail on all platforms for consistency.
@@ -205,6 +216,9 @@
hints.ai_flags = AI_ADDRCONFIG;
#endif
+ if (host_resolver_flags & HOST_RESOLVER_CANONNAME)
+ hints.ai_flags |= AI_CANONNAME;
+
// Restrict result set to only this socket type to avoid duplicates.
hints.ai_socktype = SOCK_STREAM;
@@ -220,8 +234,16 @@
}
#endif
- if (err)
+ if (err) {
+ if (os_error) {
+#if defined(OS_WIN)
+ *os_error = WSAGetLastError();
+#else
+ *os_error = err;
+#endif
+ }
return ERR_NAME_NOT_RESOLVED;
+ }
addrlist->Adopt(ai);
return OK;
diff --git a/net/base/host_resolver_proc.h b/net/base/host_resolver_proc.h
index ca0c55d..3a8adc7 100644
--- a/net/base/host_resolver_proc.h
+++ b/net/base/host_resolver_proc.h
@@ -27,10 +27,13 @@
// Resolves |host| to an address list, restricting the results to addresses
// in |address_family|. If successful returns OK and fills |addrlist| with
- // a list of socket addresses. Otherwise returns a network error code.
+ // a list of socket addresses. Otherwise returns a network error code, and
+ // fills |os_error| with a more specific error if it was non-NULL.
virtual int Resolve(const std::string& host,
AddressFamily address_family,
- AddressList* addrlist) = 0;
+ HostResolverFlags host_resolver_flags,
+ AddressList* addrlist,
+ int* os_error) = 0;
protected:
friend class base::RefCountedThreadSafe<HostResolverProc>;
@@ -40,7 +43,9 @@
// Asks the fallback procedure (if set) to do the resolve.
int ResolveUsingPrevious(const std::string& host,
AddressFamily address_family,
- AddressList* addrlist);
+ HostResolverFlags host_resolver_flags,
+ AddressList* addrlist,
+ int* os_error);
private:
friend class HostResolverImpl;
@@ -75,10 +80,13 @@
// Resolves |host| to an address list, using the system's default host resolver.
// (i.e. this calls out to getaddrinfo()). If successful returns OK and fills
// |addrlist| with a list of socket addresses. Otherwise returns a
-// network error code.
+// network error code, and fills |os_error| with a more specific errir if it
+// was non-NULL.
int SystemHostResolverProc(const std::string& host,
AddressFamily address_family,
- AddressList* addrlist);
+ HostResolverFlags host_resolver_flags,
+ AddressList* addrlist,
+ int* os_error);
} // namespace net
diff --git a/net/base/io_buffer.cc b/net/base/io_buffer.cc
index 9ad0570..b922733 100644
--- a/net/base/io_buffer.cc
+++ b/net/base/io_buffer.cc
@@ -8,17 +8,81 @@
namespace net {
+IOBuffer::IOBuffer()
+ : data_(NULL) {
+}
+
IOBuffer::IOBuffer(int buffer_size) {
DCHECK(buffer_size > 0);
data_ = new char[buffer_size];
}
+IOBuffer::IOBuffer(char* data)
+ : data_(data) {
+}
+
+IOBuffer::~IOBuffer() {
+ delete[] data_;
+}
+
+IOBufferWithSize::IOBufferWithSize(int size)
+ : IOBuffer(size),
+ size_(size) {
+}
+
+StringIOBuffer::StringIOBuffer(const std::string& s)
+ : IOBuffer(static_cast<char*>(NULL)),
+ string_data_(s) {
+ data_ = const_cast<char*>(string_data_.data());
+}
+
+StringIOBuffer::~StringIOBuffer() {
+ // We haven't allocated the buffer, so remove it before the base class
+ // destructor tries to delete[] it.
+ data_ = NULL;
+}
+
+DrainableIOBuffer::DrainableIOBuffer(IOBuffer* base, int size)
+ : IOBuffer(base->data()),
+ base_(base),
+ size_(size),
+ used_(0) {
+}
+
+DrainableIOBuffer::~DrainableIOBuffer() {
+ // The buffer is owned by the |base_| instance.
+ data_ = NULL;
+}
+
+void DrainableIOBuffer::DidConsume(int bytes) {
+ SetOffset(used_ + bytes);
+}
+
+int DrainableIOBuffer::BytesRemaining() const {
+ return size_ - used_;
+}
+
+// Returns the number of consumed bytes.
+int DrainableIOBuffer::BytesConsumed() const {
+ return used_;
+}
+
void DrainableIOBuffer::SetOffset(int bytes) {
DCHECK(bytes >= 0 && bytes <= size_);
used_ = bytes;
data_ = base_->data() + used_;
}
+GrowableIOBuffer::GrowableIOBuffer()
+ : IOBuffer(),
+ capacity_(0),
+ offset_(0) {
+}
+
+GrowableIOBuffer::~GrowableIOBuffer() {
+ data_ = NULL;
+}
+
void GrowableIOBuffer::SetCapacity(int capacity) {
DCHECK(capacity >= 0);
// realloc will crash if it fails.
@@ -36,4 +100,29 @@
data_ = real_data_.get() + offset;
}
+int GrowableIOBuffer::RemainingCapacity() {
+ return capacity_ - offset_;
+}
+
+char* GrowableIOBuffer::StartOfBuffer() {
+ return real_data_.get();
+}
+
+PickledIOBuffer::PickledIOBuffer() : IOBuffer() {}
+
+PickledIOBuffer::~PickledIOBuffer() { data_ = NULL; }
+
+void PickledIOBuffer::Done() {
+ data_ = const_cast<char*>(static_cast<const char*>(pickle_.data()));
+}
+
+
+WrappedIOBuffer::WrappedIOBuffer(const char* data)
+ : IOBuffer(const_cast<char*>(data)) {
+}
+
+WrappedIOBuffer::~WrappedIOBuffer() {
+ data_ = NULL;
+}
+
} // namespace net
diff --git a/net/base/io_buffer.h b/net/base/io_buffer.h
index d908eec..cc6092a 100644
--- a/net/base/io_buffer.h
+++ b/net/base/io_buffer.h
@@ -17,7 +17,7 @@
// easier asynchronous IO handling.
class IOBuffer : public base::RefCountedThreadSafe<IOBuffer> {
public:
- IOBuffer() : data_(NULL) {}
+ IOBuffer();
explicit IOBuffer(int buffer_size);
char* data() { return data_; }
@@ -27,11 +27,9 @@
// Only allow derived classes to specify data_.
// In all other cases, we own data_, and must delete it at destruction time.
- explicit IOBuffer(char* data) : data_(data) {}
+ explicit IOBuffer(char* data);
- virtual ~IOBuffer() {
- delete[] data_;
- }
+ virtual ~IOBuffer();
char* data_;
};
@@ -42,7 +40,7 @@
// argument to IO functions. Please keep using IOBuffer* for API declarations.
class IOBufferWithSize : public IOBuffer {
public:
- explicit IOBufferWithSize(int size) : IOBuffer(size), size_(size) {}
+ explicit IOBufferWithSize(int size);
int size() const { return size_; }
@@ -56,20 +54,12 @@
// the IOBuffer interface does not provide a proper way to modify it.
class StringIOBuffer : public IOBuffer {
public:
- explicit StringIOBuffer(const std::string& s)
- : IOBuffer(static_cast<char*>(NULL)),
- string_data_(s) {
- data_ = const_cast<char*>(string_data_.data());
- }
+ explicit StringIOBuffer(const std::string& s);
int size() const { return string_data_.size(); }
private:
- ~StringIOBuffer() {
- // We haven't allocated the buffer, so remove it before the base class
- // destructor tries to delete[] it.
- data_ = NULL;
- }
+ ~StringIOBuffer();
std::string string_data_;
};
@@ -78,18 +68,17 @@
// to progressively read all the data.
class DrainableIOBuffer : public IOBuffer {
public:
- DrainableIOBuffer(IOBuffer* base, int size)
- : IOBuffer(base->data()), base_(base), size_(size), used_(0) {}
+ DrainableIOBuffer(IOBuffer* base, int size);
// DidConsume() changes the |data_| pointer so that |data_| always points
// to the first unconsumed byte.
- void DidConsume(int bytes) { SetOffset(used_ + bytes); }
+ void DidConsume(int bytes);
// Returns the number of unconsumed bytes.
- int BytesRemaining() const { return size_ - used_; }
+ int BytesRemaining() const;
// Returns the number of consumed bytes.
- int BytesConsumed() const { return used_; }
+ int BytesConsumed() const;
// Seeks to an arbitrary point in the buffer. The notion of bytes consumed
// and remaining are updated appropriately.
@@ -98,10 +87,7 @@
int size() const { return size_; }
private:
- ~DrainableIOBuffer() {
- // The buffer is owned by the |base_| instance.
- data_ = NULL;
- }
+ ~DrainableIOBuffer();
scoped_refptr<IOBuffer> base_;
int size_;
@@ -111,7 +97,7 @@
// This version provides a resizable buffer and a changeable offset.
class GrowableIOBuffer : public IOBuffer {
public:
- GrowableIOBuffer() : IOBuffer(), capacity_(0), offset_(0) {}
+ GrowableIOBuffer();
// realloc memory to the specified capacity.
void SetCapacity(int capacity);
@@ -121,11 +107,11 @@
void set_offset(int offset);
int offset() { return offset_; }
- int RemainingCapacity() { return capacity_ - offset_; }
- char* StartOfBuffer() { return real_data_.get(); }
+ int RemainingCapacity();
+ char* StartOfBuffer();
private:
- ~GrowableIOBuffer() { data_ = NULL; }
+ ~GrowableIOBuffer();
scoped_ptr_malloc<char> real_data_;
int capacity_;
@@ -136,18 +122,16 @@
// operation, avoiding an extra data copy.
class PickledIOBuffer : public IOBuffer {
public:
- PickledIOBuffer() : IOBuffer() {}
+ PickledIOBuffer();
Pickle* pickle() { return &pickle_; }
// Signals that we are done writing to the picke and we can use it for a
// write-style IO operation.
- void Done() {
- data_ = const_cast<char*>(static_cast<const char*>(pickle_.data()));
- }
+ void Done();
private:
- ~PickledIOBuffer() { data_ = NULL; }
+ ~PickledIOBuffer();
Pickle pickle_;
};
@@ -159,13 +143,10 @@
// of the buffer can be completely managed by its intended owner.
class WrappedIOBuffer : public IOBuffer {
public:
- explicit WrappedIOBuffer(const char* data)
- : IOBuffer(const_cast<char*>(data)) {}
+ explicit WrappedIOBuffer(const char* data);
protected:
- ~WrappedIOBuffer() {
- data_ = NULL;
- }
+ ~WrappedIOBuffer();
};
} // namespace net
diff --git a/net/base/keygen_handler.h b/net/base/keygen_handler.h
index 346b577..a582816 100644
--- a/net/base/keygen_handler.h
+++ b/net/base/keygen_handler.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -10,18 +10,36 @@
namespace net {
// This class handles keypair generation for generating client
-// certificates via the Netscape <keygen> tag.
+// certificates via the <keygen> tag.
+// <http://dev.w3.org/html5/spec/Overview.html#the-keygen-element>
+// <https://developer.mozilla.org/En/HTML/HTML_Extensions/KEYGEN_Tag>
class KeygenHandler {
public:
- KeygenHandler(int key_size_index, const std::string& challenge);
+ // Creates a handler that will generate a key with the given key size
+ // and incorporate the |challenge| into the Netscape SPKAC structure.
+ inline KeygenHandler(int key_size_in_bits, const std::string& challenge);
+
+ // Actually generates the key-pair and the cert request (SPKAC), and returns
+ // a base64-encoded string suitable for use as the form value of <keygen>.
std::string GenKeyAndSignChallenge();
+ // Exposed only for unit tests.
+ void set_stores_key(bool store) { stores_key_ = store;}
+
private:
- int key_size_index_;
- std::string challenge_;
+ int key_size_in_bits_; // key size in bits (usually 2048)
+ std::string challenge_; // challenge string sent by server
+ bool stores_key_; // should the generated key-pair be stored persistently?
};
+KeygenHandler::KeygenHandler(int key_size_in_bits,
+ const std::string& challenge)
+ : key_size_in_bits_(key_size_in_bits),
+ challenge_(challenge),
+ stores_key_(true) {
+}
+
} // namespace net
#endif // NET_BASE_KEYGEN_HANDLER_H_
diff --git a/net/base/keygen_handler_mac.cc b/net/base/keygen_handler_mac.cc
index f6b4551..c2c63d7 100644
--- a/net/base/keygen_handler_mac.cc
+++ b/net/base/keygen_handler_mac.cc
@@ -1,23 +1,297 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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 "net/base/keygen_handler.h"
+#include <Security/SecAsn1Coder.h>
+#include <Security/SecAsn1Templates.h>
+#include <Security/Security.h>
+
+#include "base/base64.h"
+#include "base/crypto/cssm_init.h"
+#include "base/lock.h"
#include "base/logging.h"
+#include "base/scoped_cftyperef.h"
+
+// These are in Security.framework but not declared in a public header.
+extern const SecAsn1Template kSecAsn1AlgorithmIDTemplate[];
+extern const SecAsn1Template kSecAsn1SubjectPublicKeyInfoTemplate[];
namespace net {
-KeygenHandler::KeygenHandler(int key_size_index,
- const std::string& challenge)
- : key_size_index_(key_size_index),
- challenge_(challenge) {
- NOTIMPLEMENTED();
-}
+// Declarations of Netscape keygen cert structures for ASN.1 encoding:
+
+struct PublicKeyAndChallenge {
+ CSSM_X509_SUBJECT_PUBLIC_KEY_INFO spki;
+ CSSM_DATA challenge_string;
+};
+
+// This is a copy of the built-in kSecAsn1IA5StringTemplate, but without the
+// 'streamable' flag, which was causing bogus data to be written.
+const SecAsn1Template kIA5StringTemplate[] = {
+ { SEC_ASN1_IA5_STRING, 0, NULL, sizeof(CSSM_DATA) }
+};
+
+static const SecAsn1Template kPublicKeyAndChallengeTemplate[] = {
+ {
+ SEC_ASN1_SEQUENCE,
+ 0,
+ NULL,
+ sizeof(PublicKeyAndChallenge)
+ },
+ {
+ SEC_ASN1_INLINE,
+ offsetof(PublicKeyAndChallenge, spki),
+ kSecAsn1SubjectPublicKeyInfoTemplate
+ },
+ {
+ SEC_ASN1_INLINE,
+ offsetof(PublicKeyAndChallenge, challenge_string),
+ kIA5StringTemplate
+ },
+ {
+ 0
+ }
+};
+
+struct SignedPublicKeyAndChallenge {
+ PublicKeyAndChallenge pkac;
+ CSSM_X509_ALGORITHM_IDENTIFIER signature_algorithm;
+ CSSM_DATA signature;
+};
+
+static const SecAsn1Template kSignedPublicKeyAndChallengeTemplate[] = {
+ {
+ SEC_ASN1_SEQUENCE,
+ 0,
+ NULL,
+ sizeof(SignedPublicKeyAndChallenge)
+ },
+ {
+ SEC_ASN1_INLINE,
+ offsetof(SignedPublicKeyAndChallenge, pkac),
+ kPublicKeyAndChallengeTemplate
+ },
+ {
+ SEC_ASN1_INLINE,
+ offsetof(SignedPublicKeyAndChallenge, signature_algorithm),
+ kSecAsn1AlgorithmIDTemplate
+ },
+ {
+ SEC_ASN1_BIT_STRING,
+ offsetof(SignedPublicKeyAndChallenge, signature)
+ },
+ {
+ 0
+ }
+};
+
+
+static OSStatus CreateRSAKeyPair(int size_in_bits,
+ SecKeyRef* out_pub_key,
+ SecKeyRef* out_priv_key);
+static OSStatus SignData(CSSM_DATA data,
+ SecKeyRef private_key,
+ CSSM_DATA* signature);
std::string KeygenHandler::GenKeyAndSignChallenge() {
- NOTIMPLEMENTED();
- return std::string();
+ std::string result;
+ OSStatus err;
+ SecKeyRef public_key = NULL;
+ SecKeyRef private_key = NULL;
+ SecAsn1CoderRef coder = NULL;
+ CSSM_DATA signature = {0, NULL};
+
+ {
+ // Create the key-pair.
+ err = CreateRSAKeyPair(key_size_in_bits_, &public_key, &private_key);
+ if (err)
+ goto failure;
+
+ // Get the public key data (DER sequence of modulus, exponent).
+ CFDataRef key_data = NULL;
+ err = SecKeychainItemExport(public_key, kSecFormatBSAFE, 0, NULL,
+ &key_data);
+ if (err) {
+ base::LogCSSMError("SecKeychainItemExpor", err);
+ goto failure;
+ }
+ scoped_cftyperef<CFDataRef> scoped_key_data(key_data);
+
+ // Create an ASN.1 encoder.
+ err = SecAsn1CoderCreate(&coder);
+ if (err) {
+ base::LogCSSMError("SecAsn1CoderCreate", err);
+ goto failure;
+ }
+
+ // Fill in and DER-encode the PublicKeyAndChallenge:
+ SignedPublicKeyAndChallenge spkac;
+ memset(&spkac, 0, sizeof(spkac));
+ spkac.pkac.spki.algorithm.algorithm = CSSMOID_RSA;
+ spkac.pkac.spki.subjectPublicKey.Length =
+ CFDataGetLength(key_data) * 8; // interpreted as a _bit_ count
+ spkac.pkac.spki.subjectPublicKey.Data =
+ const_cast<uint8_t*>(CFDataGetBytePtr(key_data));
+ spkac.pkac.challenge_string.Length = challenge_.length();
+ spkac.pkac.challenge_string.Data =
+ reinterpret_cast<uint8_t*>(const_cast<char*>(challenge_.data()));
+
+ CSSM_DATA encoded;
+ err = SecAsn1EncodeItem(coder, &spkac.pkac,
+ kPublicKeyAndChallengeTemplate, &encoded);
+ if (err) {
+ base::LogCSSMError("SecAsn1EncodeItem", err);
+ goto failure;
+ }
+
+ // Compute a signature of the result:
+ err = SignData(encoded, private_key, &signature);
+ if (err)
+ goto failure;
+ spkac.signature.Data = signature.Data;
+ spkac.signature.Length = signature.Length * 8; // a _bit_ count
+ spkac.signature_algorithm.algorithm = CSSMOID_MD5WithRSA;
+ // TODO(snej): MD5 is weak. Can we use SHA1 instead?
+ // See <https://bugzilla.mozilla.org/show_bug.cgi?id=549460>
+
+ // DER-encode the entire SignedPublicKeyAndChallenge:
+ err = SecAsn1EncodeItem(coder, &spkac,
+ kSignedPublicKeyAndChallengeTemplate, &encoded);
+ if (err) {
+ base::LogCSSMError("SecAsn1EncodeItem", err);
+ goto failure;
+ }
+
+ // Base64 encode the result.
+ std::string input(reinterpret_cast<char*>(encoded.Data), encoded.Length);
+ base::Base64Encode(input, &result);
+ }
+
+ failure:
+ if (err) {
+ LOG(ERROR) << "SSL Keygen failed! OSStatus = " << err;
+ } else {
+ LOG(INFO) << "SSL Keygen succeeded! Output is: " << result;
+ }
+
+ // Remove keys from keychain if asked to during unit testing:
+ if (!stores_key_) {
+ if (public_key)
+ SecKeychainItemDelete(reinterpret_cast<SecKeychainItemRef>(public_key));
+ if (private_key)
+ SecKeychainItemDelete(reinterpret_cast<SecKeychainItemRef>(private_key));
+ }
+
+ // Clean up:
+ free(signature.Data);
+ if (coder)
+ SecAsn1CoderRelease(coder);
+ if (public_key)
+ CFRelease(public_key);
+ if (private_key)
+ CFRelease(private_key);
+ return result;
+}
+
+
+static OSStatus CreateRSAKeyPair(int size_in_bits,
+ SecKeyRef* out_pub_key,
+ SecKeyRef* out_priv_key) {
+ OSStatus err;
+ SecKeychainRef keychain;
+ err = SecKeychainCopyDefault(&keychain);
+ if (err) {
+ base::LogCSSMError("SecKeychainCopyDefault", err);
+ return err;
+ }
+ scoped_cftyperef<SecKeychainRef> scoped_keychain(keychain);
+ {
+ AutoLock locked(base::GetMacSecurityServicesLock());
+ err = SecKeyCreatePair(
+ keychain,
+ CSSM_ALGID_RSA,
+ size_in_bits,
+ 0LL,
+ // public key usage and attributes:
+ CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_VERIFY | CSSM_KEYUSE_WRAP,
+ CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT,
+ // private key usage and attributes:
+ CSSM_KEYUSE_DECRYPT | CSSM_KEYUSE_SIGN | CSSM_KEYUSE_UNWRAP,
+ CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT |
+ CSSM_KEYATTR_SENSITIVE,
+ NULL,
+ out_pub_key, out_priv_key);
+ }
+ if (err)
+ base::LogCSSMError("SecKeyCreatePair", err);
+ return err;
+}
+
+static OSStatus CreateSignatureContext(SecKeyRef key,
+ CSSM_ALGORITHMS algorithm,
+ CSSM_CC_HANDLE* out_cc_handle) {
+ OSStatus err;
+ const CSSM_ACCESS_CREDENTIALS* credentials = NULL;
+ {
+ AutoLock locked(base::GetMacSecurityServicesLock());
+ err = SecKeyGetCredentials(key,
+ CSSM_ACL_AUTHORIZATION_SIGN,
+ kSecCredentialTypeDefault,
+ &credentials);
+ }
+ if (err) {
+ base::LogCSSMError("SecKeyGetCredentials", err);
+ return err;
+ }
+
+ CSSM_CSP_HANDLE csp_handle = 0;
+ {
+ AutoLock locked(base::GetMacSecurityServicesLock());
+ err = SecKeyGetCSPHandle(key, &csp_handle);
+ }
+ if (err) {
+ base::LogCSSMError("SecKeyGetCSPHandle", err);
+ return err;
+ }
+
+ const CSSM_KEY* cssm_key = NULL;
+ {
+ AutoLock locked(base::GetMacSecurityServicesLock());
+ err = SecKeyGetCSSMKey(key, &cssm_key);
+ }
+ if (err) {
+ base::LogCSSMError("SecKeyGetCSSMKey", err);
+ return err;
+ }
+
+ err = CSSM_CSP_CreateSignatureContext(csp_handle,
+ algorithm,
+ credentials,
+ cssm_key,
+ out_cc_handle);
+ if (err)
+ base::LogCSSMError("CSSM_CSP_CreateSignatureContext", err);
+ return err;
+}
+
+static OSStatus SignData(CSSM_DATA data,
+ SecKeyRef private_key,
+ CSSM_DATA* signature) {
+ CSSM_CC_HANDLE cc_handle;
+ OSStatus err = CreateSignatureContext(private_key,
+ CSSM_ALGID_MD5WithRSA,
+ &cc_handle);
+ if (err) {
+ base::LogCSSMError("CreateSignatureContext", err);
+ return err;
+ }
+ err = CSSM_SignData(cc_handle, &data, 1, CSSM_ALGID_NONE, signature);
+ if (err)
+ base::LogCSSMError("CSSM_SignData", err);
+ CSSM_DeleteContext(cc_handle);
+ return err;
}
} // namespace net
diff --git a/net/base/keygen_handler_nss.cc b/net/base/keygen_handler_nss.cc
index 6c17298..638fbd5 100644
--- a/net/base/keygen_handler_nss.cc
+++ b/net/base/keygen_handler_nss.cc
@@ -1,255 +1,19 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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 "net/base/keygen_handler.h"
-#include <pk11pub.h>
-#include <secmod.h>
-#include <ssl.h>
-#include <nssb64.h> // NSSBase64_EncodeItem()
-#include <secder.h> // DER_Encode()
-#include <cryptohi.h> // SEC_DerSignData()
-#include <keyhi.h> // SECKEY_CreateSubjectPublicKeyInfo()
+#include "net/third_party/mozilla_security_manager/nsKeygenHandler.h"
-#include "base/nss_util.h"
-#include "base/logging.h"
+// PSM = Mozilla's Personal Security Manager.
+namespace psm = mozilla_security_manager;
namespace net {
-const int64 DEFAULT_RSA_PUBLIC_EXPONENT = 0x10001;
-
-// Template for creating the signed public key structure to be sent to the CA.
-DERTemplate SECAlgorithmIDTemplate[] = {
- { DER_SEQUENCE,
- 0, NULL, sizeof(SECAlgorithmID) },
- { DER_OBJECT_ID,
- offsetof(SECAlgorithmID, algorithm), },
- { DER_OPTIONAL | DER_ANY,
- offsetof(SECAlgorithmID, parameters), },
- { 0, }
-};
-
-DERTemplate CERTSubjectPublicKeyInfoTemplate[] = {
- { DER_SEQUENCE,
- 0, NULL, sizeof(CERTSubjectPublicKeyInfo) },
- { DER_INLINE,
- offsetof(CERTSubjectPublicKeyInfo, algorithm),
- SECAlgorithmIDTemplate, },
- { DER_BIT_STRING,
- offsetof(CERTSubjectPublicKeyInfo, subjectPublicKey), },
- { 0, }
-};
-
-DERTemplate CERTPublicKeyAndChallengeTemplate[] = {
- { DER_SEQUENCE,
- 0, NULL, sizeof(CERTPublicKeyAndChallenge) },
- { DER_ANY,
- offsetof(CERTPublicKeyAndChallenge, spki), },
- { DER_IA5_STRING,
- offsetof(CERTPublicKeyAndChallenge, challenge), },
- { 0, }
-};
-
-// This maps displayed strings indicating level of keysecurity in the <keygen>
-// menu to the key size in bits.
-// TODO(gauravsh): Should this mapping be moved else where?
-int RSAkeySizeMap[] = {2048, 1024};
-
-KeygenHandler::KeygenHandler(int key_size_index,
- const std::string& challenge)
- : key_size_index_(key_size_index),
- challenge_(challenge) {
- if (key_size_index_ < 0 ||
- key_size_index_ >=
- static_cast<int>(sizeof(RSAkeySizeMap) / sizeof(RSAkeySizeMap[0])))
- key_size_index_ = 0;
-}
-
-// This function is largely copied from the Firefox's
-// <keygen> implementation in security/manager/ssl/src/nsKeygenHandler.cpp
-// FIXME(gauravsh): Do we need a copy of the Mozilla license here?
-
std::string KeygenHandler::GenKeyAndSignChallenge() {
- // Key pair generation mechanism - only RSA is supported at present.
- PRUint32 keyGenMechanism = CKM_RSA_PKCS_KEY_PAIR_GEN; // from nss/pkcs11t.h
- char *keystring = NULL; // Temporary store for result/
-
- // Temporary structures used for generating the result
- // in the right format.
- PK11SlotInfo *slot = NULL;
- PK11RSAGenParams rsaKeyGenParams; // Keygen parameters.
- SECOidTag algTag; // used by SEC_DerSignData().
- SECKEYPrivateKey *privateKey = NULL;
- SECKEYPublicKey *publicKey = NULL;
- CERTSubjectPublicKeyInfo *spkInfo = NULL;
- PRArenaPool *arena = NULL;
- SECStatus sec_rv =SECFailure;
- SECItem spkiItem;
- SECItem pkacItem;
- SECItem signedItem;
- CERTPublicKeyAndChallenge pkac;
- void *keyGenParams;
- pkac.challenge.data = NULL;
- bool isSuccess = true; // Set to false as soon as a step fails.
-
- std::string result_blob; // the result.
-
- // Ensure NSS is initialized.
- base::EnsureNSSInit();
-
- slot = PK11_GetInternalKeySlot();
- if (!slot) {
- LOG(ERROR) << "Couldn't get Internal key slot!";
- isSuccess = false;
- goto failure;
- }
-
- switch (keyGenMechanism) {
- case CKM_RSA_PKCS_KEY_PAIR_GEN:
- rsaKeyGenParams.keySizeInBits = RSAkeySizeMap[key_size_index_];
- rsaKeyGenParams.pe = DEFAULT_RSA_PUBLIC_EXPONENT;
- keyGenParams = &rsaKeyGenParams;
-
- algTag = SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION; // from <nss/secoidt.h>.
- break;
- default:
- // TODO(gauravsh): If we ever support other mechanisms,
- // this can be changed.
- LOG(ERROR) << "Only RSA keygen mechanism is supported";
- isSuccess = false;
- goto failure;
- break;
- }
-
- // Need to make sure that the token was initialized.
- // Assume a null password.
- sec_rv = PK11_Authenticate(slot, PR_TRUE, NULL);
- if (SECSuccess != sec_rv) {
- LOG(ERROR) << "Couldn't initialze PK11 token!";
- isSuccess = false;
- goto failure;
- }
-
- LOG(INFO) << "Creating key pair...";
- privateKey = PK11_GenerateKeyPair(slot,
- keyGenMechanism,
- keyGenParams,
- &publicKey,
- PR_TRUE, // isPermanent?
- PR_TRUE, // isSensitive?
- NULL);
- LOG(INFO) << "done.";
-
- if (!privateKey) {
- LOG(INFO) << "Generation of Keypair failed!";
- isSuccess = false;
- goto failure;
- }
-
- // The CA expects the signed public key in a specific format
- // Let's create that now.
-
- // Create a subject public key info from the public key.
- spkInfo = SECKEY_CreateSubjectPublicKeyInfo(publicKey);
- if (!spkInfo) {
- LOG(ERROR) << "Couldn't create SubjectPublicKeyInfo from public key";
- isSuccess = false;
- goto failure;
- }
-
- // Temporary work store used by NSS.
- arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
- if (!arena) {
- LOG(ERROR) << "PORT_NewArena: Couldn't allocate memory";
- isSuccess = false;
- goto failure;
- }
-
- // DER encode the whole subjectPublicKeyInfo.
- sec_rv = DER_Encode(arena, &spkiItem, CERTSubjectPublicKeyInfoTemplate,
- spkInfo);
- if (SECSuccess != sec_rv) {
- LOG(ERROR) << "Couldn't DER Encode subjectPublicKeyInfo";
- isSuccess = false;
- goto failure;
- }
-
- // Set up the PublicKeyAndChallenge data structure, then DER encode it.
- pkac.spki = spkiItem;
- pkac.challenge.len = challenge_.length();
- pkac.challenge.data = (unsigned char *)strdup(challenge_.c_str());
- if (!pkac.challenge.data) {
- LOG(ERROR) << "Out of memory while making a copy of challenge data";
- isSuccess = false;
- goto failure;
- }
- sec_rv = DER_Encode(arena, &pkacItem, CERTPublicKeyAndChallengeTemplate,
- &pkac);
- if (SECSuccess != sec_rv) {
- LOG(ERROR) << "Couldn't DER Encode PublicKeyAndChallenge";
- isSuccess = false;
- goto failure;
- }
-
- // Sign the DER encoded PublicKeyAndChallenge.
- sec_rv = SEC_DerSignData(arena, &signedItem, pkacItem.data, pkacItem.len,
- privateKey, algTag);
- if (SECSuccess != sec_rv) {
- LOG(ERROR) << "Couldn't sign the DER encoded PublicKeyandChallenge";
- isSuccess = false;
- goto failure;
- }
-
- // Convert the signed public key and challenge into base64/ascii.
- keystring = NSSBase64_EncodeItem(arena,
- NULL, // NSS will allocate a buffer for us.
- 0,
- &signedItem);
- if (!keystring) {
- LOG(ERROR) << "Couldn't convert signed public key into base64";
- isSuccess = false;
- goto failure;
- }
-
- result_blob = keystring;
-
- failure:
- if (!isSuccess) {
- LOG(ERROR) << "SSL Keygen failed!";
- } else {
- LOG(INFO) << "SSl Keygen succeeded!";
- }
-
- // Do cleanups
- if (privateKey) {
- // TODO(gauravsh): We still need to maintain the private key because it's
- // used for certificate enrollment checks.
-
- // PK11_DestroyTokenObject(privateKey->pkcs11Slot,privateKey->pkcs11ID);
- // SECKEY_DestroyPrivateKey(privateKey);
- }
-
- if (publicKey) {
- PK11_DestroyTokenObject(publicKey->pkcs11Slot, publicKey->pkcs11ID);
- }
- if (spkInfo) {
- SECKEY_DestroySubjectPublicKeyInfo(spkInfo);
- }
- if (publicKey) {
- SECKEY_DestroyPublicKey(publicKey);
- }
- if (arena) {
- PORT_FreeArena(arena, PR_TRUE);
- }
- if (slot != NULL) {
- PK11_FreeSlot(slot);
- }
- if (pkac.challenge.data) {
- free(pkac.challenge.data);
- }
-
- return (isSuccess ? result_blob : std::string());
+ return psm::GenKeyAndSignChallenge(key_size_in_bits_, challenge_,
+ stores_key_);
}
} // namespace net
diff --git a/net/base/keygen_handler_unittest.cc b/net/base/keygen_handler_unittest.cc
new file mode 100644
index 0000000..15ec0ce
--- /dev/null
+++ b/net/base/keygen_handler_unittest.cc
@@ -0,0 +1,143 @@
+// 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 "net/base/keygen_handler.h"
+
+#include "build/build_config.h" // Needs to be imported early for USE_NSS
+
+#if defined(USE_NSS)
+#include <private/pprthred.h> // PR_DetachThread
+#endif
+
+#include <string>
+
+#include "base/base64.h"
+#include "base/logging.h"
+#include "base/nss_util.h"
+#include "base/task.h"
+#include "base/waitable_event.h"
+#include "base/worker_pool.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+class KeygenHandlerTest : public ::testing::Test {
+ public:
+ KeygenHandlerTest() {}
+ virtual ~KeygenHandlerTest() {}
+
+ virtual void SetUp() {
+#if defined(OS_CHROMEOS)
+ base::OpenPersistentNSSDB();
+#endif
+ }
+};
+
+// Assert that |result| is a valid output for KeygenHandler given challenge
+// string of |challenge|.
+void AssertValidSignedPublicKeyAndChallenge(const std::string& result,
+ const std::string& challenge) {
+ ASSERT_GT(result.length(), 0U);
+
+ // Verify it's valid base64:
+ std::string spkac;
+ ASSERT_TRUE(base::Base64Decode(result, &spkac));
+ // In lieu of actually parsing and validating the DER data,
+ // just check that it exists and has a reasonable length.
+ // (It's almost always 590 bytes, but the DER encoding of the random key
+ // and signature could sometimes be a few bytes different.)
+ ASSERT_GE(spkac.length(), 580U);
+ ASSERT_LE(spkac.length(), 600U);
+
+ // NOTE:
+ // The value of |result| can be validated by prefixing 'SPKAC=' to it
+ // and piping it through
+ // openssl spkac -verify
+ // whose output should look like:
+ // Netscape SPKI:
+ // Public Key Algorithm: rsaEncryption
+ // RSA Public Key: (2048 bit)
+ // Modulus (2048 bit):
+ // 00:b6:cc:14:c9:43:b5:2d:51:65:7e:11:8b:80:9e: .....
+ // Exponent: 65537 (0x10001)
+ // Challenge String: some challenge
+ // Signature Algorithm: md5WithRSAEncryption
+ // 92:f3:cc:ff:0b:d3:d0:4a:3a:4c:ba:ff:d6:38:7f:a5:4b:b5: .....
+ // Signature OK
+ //
+ // The value of |spkac| can be ASN.1-parsed with:
+ // openssl asn1parse -inform DER
+}
+
+TEST_F(KeygenHandlerTest, FLAKY_SmokeTest) {
+ KeygenHandler handler(2048, "some challenge");
+ handler.set_stores_key(false); // Don't leave the key-pair behind
+ std::string result = handler.GenKeyAndSignChallenge();
+ LOG(INFO) << "KeygenHandler produced: " << result;
+ AssertValidSignedPublicKeyAndChallenge(result, "some challenge");
+}
+
+class ConcurrencyTestTask : public Task {
+ public:
+ ConcurrencyTestTask(base::WaitableEvent* event,
+ const std::string& challenge, std::string* result)
+ : event_(event),
+ challenge_(challenge),
+ result_(result) {
+ }
+
+ virtual void Run() {
+ KeygenHandler handler(2048, "some challenge");
+ handler.set_stores_key(false); // Don't leave the key-pair behind.
+ *result_ = handler.GenKeyAndSignChallenge();
+ event_->Signal();
+#if defined(USE_NSS)
+ // Detach the thread from NSPR.
+ // Calling NSS functions attaches the thread to NSPR, which stores
+ // the NSPR thread ID in thread-specific data.
+ // The threads in our thread pool terminate after we have called
+ // PR_Cleanup. Unless we detach them from NSPR, net_unittests gets
+ // segfaults on shutdown when the threads' thread-specific data
+ // destructors run.
+ PR_DetachThread();
+#endif
+ }
+
+ private:
+ base::WaitableEvent* event_;
+ std::string challenge_;
+ std::string* result_;
+};
+
+// We asynchronously generate the keys so as not to hang up the IO thread. This
+// test tries to catch concurrency problems in the keygen implementation.
+TEST_F(KeygenHandlerTest, ConcurrencyTest) {
+ const int NUM_HANDLERS = 5;
+ base::WaitableEvent* events[NUM_HANDLERS] = { NULL };
+ std::string results[NUM_HANDLERS];
+ for (int i = 0; i < NUM_HANDLERS; i++) {
+ events[i] = new base::WaitableEvent(false, false);
+ WorkerPool::PostTask(FROM_HERE,
+ new ConcurrencyTestTask(events[i], "some challenge",
+ &results[i]),
+ true);
+ }
+
+ for (int i = 0; i < NUM_HANDLERS; i++) {
+ // Make sure the job completed
+ bool signaled = events[i]->Wait();
+ EXPECT_TRUE(signaled);
+ delete events[i];
+ events[i] = NULL;
+
+ LOG(INFO) << "KeygenHandler " << i << " produced: " << results[i];
+ AssertValidSignedPublicKeyAndChallenge(results[i], "some challenge");
+ }
+}
+
+} // namespace
+
+} // namespace net
diff --git a/net/base/keygen_handler_win.cc b/net/base/keygen_handler_win.cc
index f6b4551..8fc32e5 100644
--- a/net/base/keygen_handler_win.cc
+++ b/net/base/keygen_handler_win.cc
@@ -1,23 +1,220 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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 "net/base/keygen_handler.h"
+#include <windows.h>
+#include <wincrypt.h>
+#pragma comment(lib, "crypt32.lib")
+#include <rpc.h>
+#pragma comment(lib, "rpcrt4.lib")
+
+#include <list>
+#include <string>
+#include <vector>
+
+#include "base/base64.h"
+#include "base/basictypes.h"
+#include "base/crypto/capi_util.h"
#include "base/logging.h"
+#include "base/string_piece.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
namespace net {
-KeygenHandler::KeygenHandler(int key_size_index,
- const std::string& challenge)
- : key_size_index_(key_size_index),
- challenge_(challenge) {
- NOTIMPLEMENTED();
+// Assigns the contents of a CERT_PUBLIC_KEY_INFO structure for the signing
+// key in |prov| to |output|. Returns true if encoding was successful.
+bool GetSubjectPublicKeyInfo(HCRYPTPROV prov, std::vector<BYTE>* output) {
+ BOOL ok;
+ DWORD size = 0;
+
+ // From the private key stored in HCRYPTPROV, obtain the public key, stored
+ // as a CERT_PUBLIC_KEY_INFO structure. Currently, only RSA public keys are
+ // supported.
+ ok = CryptExportPublicKeyInfoEx(prov, AT_KEYEXCHANGE, X509_ASN_ENCODING,
+ szOID_RSA_RSA, 0, NULL, NULL, &size);
+ DCHECK(ok);
+ if (!ok)
+ return false;
+
+ output->resize(size);
+
+ PCERT_PUBLIC_KEY_INFO public_key_casted =
+ reinterpret_cast<PCERT_PUBLIC_KEY_INFO>(&(*output)[0]);
+ ok = CryptExportPublicKeyInfoEx(prov, AT_KEYEXCHANGE, X509_ASN_ENCODING,
+ szOID_RSA_RSA, 0, NULL, public_key_casted,
+ &size);
+ DCHECK(ok);
+ if (!ok)
+ return false;
+
+ output->resize(size);
+
+ return true;
+}
+
+// Generates a DER encoded SignedPublicKeyAndChallenge structure from the
+// signing key of |prov| and the specified ASCII |challenge| string and
+// appends it to |output|.
+// True if the encoding was successfully generated.
+bool GetSignedPublicKeyAndChallenge(HCRYPTPROV prov,
+ const std::string& challenge,
+ std::string* output) {
+ std::wstring wide_challenge = ASCIIToWide(challenge);
+ std::vector<BYTE> spki;
+
+ if (!GetSubjectPublicKeyInfo(prov, &spki))
+ return false;
+
+ // PublicKeyAndChallenge ::= SEQUENCE {
+ // spki SubjectPublicKeyInfo,
+ // challenge IA5STRING
+ // }
+ CERT_KEYGEN_REQUEST_INFO pkac;
+ pkac.dwVersion = CERT_KEYGEN_REQUEST_V1;
+ pkac.SubjectPublicKeyInfo =
+ *reinterpret_cast<PCERT_PUBLIC_KEY_INFO>(&spki[0]);
+ pkac.pwszChallengeString = const_cast<wchar_t*>(wide_challenge.c_str());
+
+ CRYPT_ALGORITHM_IDENTIFIER sig_alg;
+ memset(&sig_alg, 0, sizeof(sig_alg));
+ sig_alg.pszObjId = szOID_RSA_MD5RSA;
+
+ BOOL ok;
+ DWORD size = 0;
+ std::vector<BYTE> signed_pkac;
+ ok = CryptSignAndEncodeCertificate(prov, AT_KEYEXCHANGE, X509_ASN_ENCODING,
+ X509_KEYGEN_REQUEST_TO_BE_SIGNED,
+ &pkac, &sig_alg, NULL,
+ NULL, &size);
+ DCHECK(ok);
+ if (!ok)
+ return false;
+
+ signed_pkac.resize(size);
+ ok = CryptSignAndEncodeCertificate(prov, AT_KEYEXCHANGE, X509_ASN_ENCODING,
+ X509_KEYGEN_REQUEST_TO_BE_SIGNED,
+ &pkac, &sig_alg, NULL,
+ &signed_pkac[0], &size);
+ DCHECK(ok);
+ if (!ok)
+ return false;
+
+ output->assign(reinterpret_cast<char*>(&signed_pkac[0]), size);
+ return true;
+}
+
+// Generates a unique name for the container which will store the key that is
+// generated. The traditional Windows approach is to use a GUID here.
+std::wstring GetNewKeyContainerId() {
+ RPC_STATUS status = RPC_S_OK;
+ std::wstring result;
+
+ UUID id = { 0 };
+ status = UuidCreateSequential(&id);
+ if (status != RPC_S_OK && status != RPC_S_UUID_LOCAL_ONLY)
+ return result;
+
+ RPC_WSTR rpc_string = NULL;
+ status = UuidToString(&id, &rpc_string);
+ if (status != RPC_S_OK)
+ return result;
+
+ // RPC_WSTR is unsigned short*. wchar_t is a built-in type of Visual C++,
+ // so the type cast is necessary.
+ result.assign(reinterpret_cast<wchar_t*>(rpc_string));
+ RpcStringFree(&rpc_string);
+
+ return result;
}
std::string KeygenHandler::GenKeyAndSignChallenge() {
- NOTIMPLEMENTED();
- return std::string();
+ std::string result;
+
+ bool is_success = true;
+ HCRYPTPROV prov = NULL;
+ HCRYPTKEY key = NULL;
+ DWORD flags = (key_size_in_bits_ << 16) | CRYPT_EXPORTABLE;
+ std::string spkac;
+
+ std::wstring new_key_id;
+
+ // TODO(rsleevi): Have the user choose which provider they should use, which
+ // needs to be filtered by those providers which can provide the key type
+ // requested or the key size requested. This is especially important for
+ // generating certificates that will be stored on smart cards.
+ const int kMaxAttempts = 5;
+ BOOL ok = FALSE;
+ for (int attempt = 0; attempt < kMaxAttempts; ++attempt) {
+ // Per MSDN documentation for CryptAcquireContext, if applications will be
+ // creating their own keys, they should ensure unique naming schemes to
+ // prevent overlap with any other applications or consumers of CSPs, and
+ // *should not* store new keys within the default, NULL key container.
+ new_key_id = GetNewKeyContainerId();
+ if (new_key_id.empty())
+ return result;
+
+ // Only create new key containers, so that existing key containers are not
+ // overwritten.
+ ok = base::CryptAcquireContextLocked(&prov, new_key_id.c_str(), NULL,
+ PROV_RSA_FULL,
+ CRYPT_SILENT | CRYPT_NEWKEYSET);
+
+ if (ok || GetLastError() != NTE_BAD_KEYSET)
+ break;
+ }
+ if (!ok) {
+ LOG(ERROR) << "Couldn't acquire a CryptoAPI provider context: "
+ << GetLastError();
+ is_success = false;
+ goto failure;
+ }
+
+ if (!CryptGenKey(prov, CALG_RSA_KEYX, flags, &key)) {
+ LOG(ERROR) << "Couldn't generate an RSA key";
+ is_success = false;
+ goto failure;
+ }
+
+ if (!GetSignedPublicKeyAndChallenge(prov, challenge_, &spkac)) {
+ LOG(ERROR) << "Couldn't generate the signed public key and challenge";
+ is_success = false;
+ goto failure;
+ }
+
+ if (!base::Base64Encode(spkac, &result)) {
+ LOG(ERROR) << "Couldn't convert signed key into base64";
+ is_success = false;
+ goto failure;
+ }
+
+ failure:
+ if (!is_success) {
+ LOG(ERROR) << "SSL Keygen failed";
+ } else {
+ LOG(INFO) << "SSL Key succeeded";
+ }
+ if (key) {
+ // Securely destroys the handle, but leaves the underlying key alone. The
+ // key can be obtained again by resolving the key location. If
+ // |stores_key_| is false, the underlying key will be destroyed below.
+ CryptDestroyKey(key);
+ }
+
+ if (prov) {
+ CryptReleaseContext(prov, 0);
+ prov = NULL;
+ if (!stores_key_) {
+ // Fully destroys any of the keys that were created and releases prov.
+ base::CryptAcquireContextLocked(&prov, new_key_id.c_str(), NULL,
+ PROV_RSA_FULL,
+ CRYPT_SILENT | CRYPT_DELETEKEYSET);
+ }
+ }
+
+ return result;
}
} // namespace net
diff --git a/net/base/listen_socket.cc b/net/base/listen_socket.cc
index 88e9592..0cb529d 100644
--- a/net/base/listen_socket.cc
+++ b/net/base/listen_socket.cc
@@ -10,6 +10,7 @@
#include <winsock2.h>
#elif defined(OS_POSIX)
#include <errno.h>
+#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include "net/base/net_errors.h"
@@ -30,7 +31,7 @@
namespace {
-const int kReadBufSize = 200;
+const int kReadBufSize = 4096;
} // namespace
@@ -159,7 +160,7 @@
// TODO(ibrar): maybe change DidRead to take a length instead
DCHECK(len > 0 && len <= kReadBufSize);
buf[len] = 0; // already create a buffer with +1 length
- socket_delegate_->DidRead(this, buf);
+ socket_delegate_->DidRead(this, buf, len);
}
} while (len == kReadBufSize);
}
@@ -205,19 +206,32 @@
}
void ListenSocket::SendInternal(const char* bytes, int len) {
- int sent = HANDLE_EINTR(send(socket_, bytes, len, 0));
- if (sent == kSocketError) {
-#if defined(OS_WIN)
- int err = WSAGetLastError();
- if (err == WSAEWOULDBLOCK) {
-#elif defined(OS_POSIX)
- if (errno == EWOULDBLOCK || errno == EAGAIN) {
-#endif
- // TODO(ibrar): there should be logic here to handle this because
- // it is not an error
+ char* send_buf = const_cast<char *>(bytes);
+ int len_left = len;
+ while (true) {
+ int sent = HANDLE_EINTR(send(socket_, send_buf, len_left, 0));
+ if (sent == len_left) { // A shortcut to avoid extraneous checks.
+ break;
}
- } else if (sent != len) {
- LOG(ERROR) << "send failed: ";
+ if (sent == kSocketError) {
+#if defined(OS_WIN)
+ if (WSAGetLastError() != WSAEWOULDBLOCK) {
+ LOG(ERROR) << "send failed: WSAGetLastError()==" << WSAGetLastError();
+#elif defined(OS_POSIX)
+ if (errno != EWOULDBLOCK && errno != EAGAIN) {
+ LOG(ERROR) << "send failed: errno==" << errno;
+#endif
+ break;
+ }
+ // Otherwise we would block, and now we have to wait for a retry.
+ // Fall through to PlatformThread::YieldCurrentThread()
+ } else {
+ // sent != len_left according to the shortcut above.
+ // Shift the buffer start and send the remainder after a short while.
+ send_buf += sent;
+ len_left -= sent;
+ }
+ PlatformThread::YieldCurrentThread();
}
}
diff --git a/net/base/listen_socket.h b/net/base/listen_socket.h
index 585a094..923d361 100644
--- a/net/base/listen_socket.h
+++ b/net/base/listen_socket.h
@@ -51,7 +51,9 @@
// Socket that was created. Ownership of connection is transferred
// to the delegate with this call.
virtual void DidAccept(ListenSocket *server, ListenSocket *connection) = 0;
- virtual void DidRead(ListenSocket *connection, const std::string& data) = 0;
+ virtual void DidRead(ListenSocket *connection,
+ const char* data,
+ int len) = 0;
virtual void DidClose(ListenSocket *sock) = 0;
};
diff --git a/net/base/listen_socket_unittest.cc b/net/base/listen_socket_unittest.cc
index eb224c3..b38019d 100644
--- a/net/base/listen_socket_unittest.cc
+++ b/net/base/listen_socket_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -54,7 +54,7 @@
// verify the connect/accept and setup test_socket_
test_socket_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
- ASSERT_NE(-1, test_socket_);
+ ASSERT_NE(INVALID_SOCKET, test_socket_);
struct sockaddr_in client;
client.sin_family = AF_INET;
client.sin_addr.s_addr = inet_addr(kLoopback);
@@ -212,8 +212,10 @@
}
void ListenSocketTester::DidRead(ListenSocket *connection,
- const std::string& data) {
- ReportAction(ListenSocketTestAction(ACTION_READ, data));
+ const char* data,
+ int len) {
+ std::string str(data, len);
+ ReportAction(ListenSocketTestAction(ACTION_READ, str));
}
void ListenSocketTester::DidClose(ListenSocket *sock) {
@@ -317,6 +319,8 @@
tester_->TestClientSendLong();
}
-TEST_F(ListenSocketTest, ServerSend) {
+// This test is flaky; see comment in ::TestServerSend.
+// http://code.google.com/p/chromium/issues/detail?id=48562
+TEST_F(ListenSocketTest, FLAKY_ServerSend) {
tester_->TestServerSend();
}
diff --git a/net/base/listen_socket_unittest.h b/net/base/listen_socket_unittest.h
index 81cbb95..d43364a 100644
--- a/net/base/listen_socket_unittest.h
+++ b/net/base/listen_socket_unittest.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -16,6 +16,7 @@
#include <arpa/inet.h>
#endif
+#include "base/scoped_ptr.h"
#include "base/thread.h"
#include "base/basictypes.h"
#include "base/message_loop.h"
@@ -52,7 +53,7 @@
data_(data) {}
const std::string data() const { return data_; }
- const ActionType type() const { return action_; }
+ ActionType type() const { return action_; }
private:
ActionType action_;
@@ -94,7 +95,7 @@
void Listen();
void SendFromTester();
virtual void DidAccept(ListenSocket *server, ListenSocket *connection);
- virtual void DidRead(ListenSocket *connection, const std::string& data);
+ virtual void DidRead(ListenSocket *connection, const char* data, int len);
virtual void DidClose(ListenSocket *sock);
virtual bool Send(SOCKET sock, const std::string& str);
// verify the send/read from client to server
diff --git a/net/base/load_flags.h b/net/base/load_flags.h
index 49c6daf..53b2d58 100644
--- a/net/base/load_flags.h
+++ b/net/base/load_flags.h
@@ -1,84 +1,22 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
-#ifndef NET_BASE_LOAD_FLAGS_H__
-#define NET_BASE_LOAD_FLAGS_H__
+#ifndef NET_BASE_LOAD_FLAGS_H_
+#define NET_BASE_LOAD_FLAGS_H_
namespace net {
// These flags provide metadata about the type of the load request. They are
// intended to be OR'd together.
enum {
- LOAD_NORMAL = 0,
- // This is "normal reload", meaning an if-none-match/if-modified-since query
- LOAD_VALIDATE_CACHE = 1 << 0,
+#define LOAD_FLAG(label, value) LOAD_ ## label = value,
+#include "net/base/load_flags_list.h"
+#undef LOAD_FLAG
- // This is "shift-reload", meaning a "pragma: no-cache" end-to-end fetch
- LOAD_BYPASS_CACHE = 1 << 1,
-
- // This is a back/forward style navigation where the cached content should
- // be preferred over any protocol specific cache validation.
- LOAD_PREFERRING_CACHE = 1 << 2,
-
- // This is a navigation that will fail if it cannot serve the requested
- // resource from the cache (or some equivalent local store).
- LOAD_ONLY_FROM_CACHE = 1 << 3,
-
- // This is a navigation that will not use the cache at all. It does not
- // impact the HTTP request headers.
- LOAD_DISABLE_CACHE = 1 << 4,
-
- // This is a navigation that will not be intercepted by any registered
- // URLRequest::Interceptors.
- LOAD_DISABLE_INTERCEPT = 1 << 5,
-
- // If present, upload progress messages should be provided to initiator.
- LOAD_ENABLE_UPLOAD_PROGRESS = 1 << 6,
-
- // If present, ignores certificate mismatches with the domain name.
- // (The default behavior is to trigger an OnSSLCertificateError callback.)
- LOAD_IGNORE_CERT_COMMON_NAME_INVALID = 1 << 8,
-
- // If present, ignores certificate expiration dates
- // (The default behavior is to trigger an OnSSLCertificateError callback).
- LOAD_IGNORE_CERT_DATE_INVALID = 1 << 9,
-
- // If present, trusts all certificate authorities
- // (The default behavior is to trigger an OnSSLCertificateError callback).
- LOAD_IGNORE_CERT_AUTHORITY_INVALID = 1 << 10,
-
- // If present, ignores certificate revocation
- // (The default behavior is to trigger an OnSSLCertificateError callback).
- LOAD_IGNORE_CERT_REVOCATION = 1 << 11,
-
- // If present, ignores wrong key usage of the certificate
- // (The default behavior is to trigger an OnSSLCertificateError callback).
- LOAD_IGNORE_CERT_WRONG_USAGE = 1 << 12,
-
- // This load will not make any changes to cookies, including storing new
- // cookies or updating existing ones.
- LOAD_DO_NOT_SAVE_COOKIES = 1 << 13,
-
- // Do not resolve proxies. This override is used when downloading PAC files
- // to avoid having a circular dependency.
- LOAD_BYPASS_PROXY = 1 << 14,
-
- // Indicate this request is for a download, as opposed to viewing.
- LOAD_IS_DOWNLOAD = 1 << 15,
-
- // Requires EV certificate verification.
- LOAD_VERIFY_EV_CERT = 1 << 16,
-
- // This load will not send any cookies.
- LOAD_DO_NOT_SEND_COOKIES = 1 << 17,
-
- // This load will not send authentication data (user name/password)
- // to the server (as opposed to the proxy).
- LOAD_DO_NOT_SEND_AUTH_DATA = 1 << 18,
};
} // namespace net
-#endif // NET_BASE_LOAD_FLAGS_H__
+#endif // NET_BASE_LOAD_FLAGS_H_
diff --git a/net/base/load_flags_list.h b/net/base/load_flags_list.h
new file mode 100644
index 0000000..ff5e609
--- /dev/null
+++ b/net/base/load_flags_list.h
@@ -0,0 +1,84 @@
+// 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.
+
+// This is the list of load flags and their values. For the enum values,
+// include the file "net/base/load_flags.h".
+//
+// Here we define the values using a macro LOAD_FLAG, so it can be
+// expanded differently in some places (for example, to automatically
+// map a load flag value to its symbolic name).
+
+LOAD_FLAG(NORMAL, 0)
+
+// This is "normal reload", meaning an if-none-match/if-modified-since query
+LOAD_FLAG(VALIDATE_CACHE, 1 << 0)
+
+// This is "shift-reload", meaning a "pragma: no-cache" end-to-end fetch
+LOAD_FLAG(BYPASS_CACHE, 1 << 1)
+
+// This is a back/forward style navigation where the cached content should
+// be preferred over any protocol specific cache validation.
+LOAD_FLAG(PREFERRING_CACHE, 1 << 2)
+
+// This is a navigation that will fail if it cannot serve the requested
+// resource from the cache (or some equivalent local store).
+LOAD_FLAG(ONLY_FROM_CACHE, 1 << 3)
+
+// This is a navigation that will not use the cache at all. It does not
+// impact the HTTP request headers.
+LOAD_FLAG(DISABLE_CACHE, 1 << 4)
+
+// This is a navigation that will not be intercepted by any registered
+// URLRequest::Interceptors.
+LOAD_FLAG(DISABLE_INTERCEPT, 1 << 5)
+
+// If present, upload progress messages should be provided to initiator.
+LOAD_FLAG(ENABLE_UPLOAD_PROGRESS, 1 << 6)
+
+// If present, collect load timing for the request.
+LOAD_FLAG(ENABLE_LOAD_TIMING, 1 << 7)
+
+// If present, ignores certificate mismatches with the domain name.
+// (The default behavior is to trigger an OnSSLCertificateError callback.)
+LOAD_FLAG(IGNORE_CERT_COMMON_NAME_INVALID, 1 << 8)
+
+// If present, ignores certificate expiration dates
+// (The default behavior is to trigger an OnSSLCertificateError callback).
+LOAD_FLAG(IGNORE_CERT_DATE_INVALID, 1 << 9)
+
+// If present, trusts all certificate authorities
+// (The default behavior is to trigger an OnSSLCertificateError callback).
+LOAD_FLAG(IGNORE_CERT_AUTHORITY_INVALID, 1 << 10)
+
+// If present, ignores certificate revocation
+// (The default behavior is to trigger an OnSSLCertificateError callback).
+LOAD_FLAG(IGNORE_CERT_REVOCATION, 1 << 11)
+
+// If present, ignores wrong key usage of the certificate
+// (The default behavior is to trigger an OnSSLCertificateError callback).
+LOAD_FLAG(IGNORE_CERT_WRONG_USAGE, 1 << 12)
+
+// This load will not make any changes to cookies, including storing new
+// cookies or updating existing ones.
+LOAD_FLAG(DO_NOT_SAVE_COOKIES, 1 << 13)
+
+// Do not resolve proxies. This override is used when downloading PAC files
+// to avoid having a circular dependency.
+LOAD_FLAG(BYPASS_PROXY, 1 << 14)
+
+// Indicate this request is for a download, as opposed to viewing.
+LOAD_FLAG(IS_DOWNLOAD, 1 << 15)
+
+// Requires EV certificate verification.
+LOAD_FLAG(VERIFY_EV_CERT, 1 << 16)
+
+// This load will not send any cookies.
+LOAD_FLAG(DO_NOT_SEND_COOKIES, 1 << 17)
+
+// This load will not send authentication data (user name/password)
+// to the server (as opposed to the proxy).
+LOAD_FLAG(DO_NOT_SEND_AUTH_DATA, 1 << 18)
+
+// This should only be used for testing (set by HttpNetworkTransaction).
+LOAD_FLAG(IGNORE_ALL_CERT_ERRORS, 1 << 19)
diff --git a/net/base/load_states.h b/net/base/load_states.h
index 9555be9..321dee8 100644
--- a/net/base/load_states.h
+++ b/net/base/load_states.h
@@ -29,6 +29,10 @@
// host before deciding what proxy to use.
LOAD_STATE_RESOLVING_PROXY_FOR_URL,
+ // This state indicates that we're in the process of establishing a tunnel
+ // through the proxy server.
+ LOAD_STATE_ESTABLISHING_PROXY_TUNNEL,
+
// This state corresponds to a resource load that is blocked waiting for a
// host name to be resolved. This could either indicate resolution of the
// origin server corresponding to the resource or to the host name of a proxy
@@ -40,6 +44,10 @@
// requests that reuse a keep-alive connection skip this state.
LOAD_STATE_CONNECTING,
+ // This state corresponds to a resource load that is blocked waiting for the
+ // SSL handshake to complete.
+ LOAD_STATE_SSL_HANDSHAKE,
+
// This state corresponds to a resource load that is blocked waiting to
// completely upload a request to a server. In the case of a HTTP POST
// request, this state includes the period of time during which the message
diff --git a/net/base/mapped_host_resolver.cc b/net/base/mapped_host_resolver.cc
new file mode 100644
index 0000000..b401917
--- /dev/null
+++ b/net/base/mapped_host_resolver.cc
@@ -0,0 +1,52 @@
+// 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 "net/base/mapped_host_resolver.h"
+
+#include "base/string_tokenizer.h"
+#include "base/string_util.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_util.h"
+
+namespace net {
+
+MappedHostResolver::MappedHostResolver(HostResolver* impl)
+ : impl_(impl) {
+}
+
+int MappedHostResolver::Resolve(const RequestInfo& info,
+ AddressList* addresses,
+ CompletionCallback* callback,
+ RequestHandle* out_req,
+ const BoundNetLog& net_log) {
+ // Modify the request before forwarding it to |impl_|.
+ RequestInfo modified_info = info;
+ HostPortPair host_port(info.hostname(), info.port());
+ if (rules_.RewriteHost(&host_port)) {
+ modified_info.set_hostname(host_port.host);
+ modified_info.set_port(host_port.port);
+ }
+ return impl_->Resolve(modified_info, addresses, callback, out_req, net_log);
+}
+
+void MappedHostResolver::CancelRequest(RequestHandle req) {
+ impl_->CancelRequest(req);
+}
+
+void MappedHostResolver::AddObserver(Observer* observer) {
+ impl_->AddObserver(observer);
+}
+
+void MappedHostResolver::RemoveObserver(Observer* observer) {
+ impl_->RemoveObserver(observer);
+}
+
+HostResolverImpl* MappedHostResolver::GetAsHostResolverImpl() {
+ return impl_->GetAsHostResolverImpl();
+}
+
+MappedHostResolver::~MappedHostResolver() {
+}
+
+} // namespace net
diff --git a/net/base/mapped_host_resolver.h b/net/base/mapped_host_resolver.h
new file mode 100644
index 0000000..3229209
--- /dev/null
+++ b/net/base/mapped_host_resolver.h
@@ -0,0 +1,65 @@
+// 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.
+
+#ifndef NET_BASE_MAPPED_HOST_RESOLVER_H_
+#define NET_BASE_MAPPED_HOST_RESOLVER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/ref_counted.h"
+#include "net/base/host_mapping_rules.h"
+#include "net/base/host_resolver.h"
+
+namespace net {
+
+// This class wraps an existing HostResolver instance, but modifies the
+// request before passing it off to |impl|. This is different from
+// MockHostResolver which does the remapping at the HostResolverProc
+// layer, so it is able to preserve the effectiveness of the cache.
+class MappedHostResolver : public HostResolver {
+ public:
+ // Creates a MappedHostResolver that forwards all of its requests through
+ // |impl|.
+ explicit MappedHostResolver(HostResolver* impl);
+
+ // HostResolver methods:
+ virtual int Resolve(const RequestInfo& info,
+ AddressList* addresses,
+ CompletionCallback* callback,
+ RequestHandle* out_req,
+ const BoundNetLog& net_log);
+ virtual void CancelRequest(RequestHandle req);
+ virtual void AddObserver(Observer* observer);
+ virtual void RemoveObserver(Observer* observer);
+ virtual HostResolverImpl* GetAsHostResolverImpl();
+
+ // Adds a rule to this mapper. The format of the rule can be one of:
+ //
+ // "MAP" <hostname_pattern> <replacement_host> [":" <replacement_port>]
+ // "EXCLUDE" <hostname_pattern>
+ //
+ // The <replacement_host> can be either a hostname, or an IP address literal.
+ //
+ // Returns true if the rule was successfully parsed and added.
+ bool AddRuleFromString(const std::string& rule_string) {
+ return rules_.AddRuleFromString(rule_string);
+ }
+
+ // Takes a comma separated list of rules, and assigns them to this resolver.
+ void SetRulesFromString(const std::string& rules_string) {
+ rules_.SetRulesFromString(rules_string);
+ }
+
+ private:
+ virtual ~MappedHostResolver();
+
+ scoped_refptr<HostResolver> impl_;
+
+ HostMappingRules rules_;
+};
+
+} // namespace net
+
+#endif // NET_BASE_MAPPED_HOST_RESOLVER_H_
diff --git a/net/base/mapped_host_resolver_unittest.cc b/net/base/mapped_host_resolver_unittest.cc
new file mode 100644
index 0000000..4b7281c
--- /dev/null
+++ b/net/base/mapped_host_resolver_unittest.cc
@@ -0,0 +1,149 @@
+// 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 "net/base/mapped_host_resolver.h"
+
+#include "net/base/mock_host_resolver.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+TEST(MappedHostResolverTest, Inclusion) {
+ // Create a mock host resolver, with specific hostname to IP mappings.
+ scoped_refptr<MockHostResolver> resolver_impl = new MockHostResolver();
+ resolver_impl->rules()->AddSimulatedFailure("*google.com");
+ resolver_impl->rules()->AddRule("baz.com", "192.168.1.5");
+ resolver_impl->rules()->AddRule("foo.com", "192.168.1.8");
+ resolver_impl->rules()->AddRule("proxy", "192.168.1.11");
+
+ // Create a remapped resolver that uses |resolver_impl|.
+ scoped_refptr<MappedHostResolver> resolver =
+ new MappedHostResolver(resolver_impl);
+
+ int rv;
+ AddressList address_list;
+
+ // Try resolving "www.google.com:80". There are no mappings yet, so this
+ // hits |resolver_impl| and fails.
+ rv = resolver->Resolve(HostResolver::RequestInfo("www.google.com", 80),
+ &address_list, NULL, NULL, BoundNetLog());
+ EXPECT_EQ(ERR_NAME_NOT_RESOLVED, rv);
+
+ // Remap *.google.com to baz.com.
+ EXPECT_TRUE(resolver->AddRuleFromString("map *.google.com baz.com"));
+
+ // Try resolving "www.google.com:80". Should be remapped to "baz.com:80".
+ rv = resolver->Resolve(HostResolver::RequestInfo("www.google.com", 80),
+ &address_list, NULL, NULL, BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("192.168.1.5", NetAddressToString(address_list.head()));
+ EXPECT_EQ(80, address_list.GetPort());
+
+ // Try resolving "foo.com:77". This will NOT be remapped, so result
+ // is "foo.com:77".
+ rv = resolver->Resolve(HostResolver::RequestInfo("foo.com", 77),
+ &address_list, NULL, NULL, BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("192.168.1.8", NetAddressToString(address_list.head()));
+ EXPECT_EQ(77, address_list.GetPort());
+
+ // Remap "*.org" to "proxy:99".
+ EXPECT_TRUE(resolver->AddRuleFromString("Map *.org proxy:99"));
+
+ // Try resolving "chromium.org:61". Should be remapped to "proxy:99".
+ rv = resolver->Resolve(HostResolver::RequestInfo("chromium.org", 61),
+ &address_list, NULL, NULL, BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("192.168.1.11", NetAddressToString(address_list.head()));
+ EXPECT_EQ(99, address_list.GetPort());
+}
+
+// Tests that exclusions are respected.
+TEST(MappedHostResolverTest, Exclusion) {
+ // Create a mock host resolver, with specific hostname to IP mappings.
+ scoped_refptr<MockHostResolver> resolver_impl = new MockHostResolver();
+ resolver_impl->rules()->AddRule("baz", "192.168.1.5");
+ resolver_impl->rules()->AddRule("www.google.com", "192.168.1.3");
+
+ // Create a remapped resolver that uses |resolver_impl|.
+ scoped_refptr<MappedHostResolver> resolver =
+ new MappedHostResolver(resolver_impl);
+
+ int rv;
+ AddressList address_list;
+
+ // Remap "*.com" to "baz".
+ EXPECT_TRUE(resolver->AddRuleFromString("map *.com baz"));
+
+ // Add an exclusion for "*.google.com".
+ EXPECT_TRUE(resolver->AddRuleFromString("EXCLUDE *.google.com"));
+
+ // Try resolving "www.google.com". Should not be remapped due to exclusion).
+ rv = resolver->Resolve(HostResolver::RequestInfo("www.google.com", 80),
+ &address_list, NULL, NULL, BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("192.168.1.3", NetAddressToString(address_list.head()));
+ EXPECT_EQ(80, address_list.GetPort());
+
+ // Try resolving "chrome.com:80". Should be remapped to "baz:80".
+ rv = resolver->Resolve(HostResolver::RequestInfo("chrome.com", 80),
+ &address_list, NULL, NULL, BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("192.168.1.5", NetAddressToString(address_list.head()));
+ EXPECT_EQ(80, address_list.GetPort());
+}
+
+TEST(MappedHostResolverTest, SetRulesFromString) {
+ // Create a mock host resolver, with specific hostname to IP mappings.
+ scoped_refptr<MockHostResolver> resolver_impl = new MockHostResolver();
+ resolver_impl->rules()->AddRule("baz", "192.168.1.7");
+ resolver_impl->rules()->AddRule("bar", "192.168.1.9");
+
+ // Create a remapped resolver that uses |resolver_impl|.
+ scoped_refptr<MappedHostResolver> resolver =
+ new MappedHostResolver(resolver_impl);
+
+ int rv;
+ AddressList address_list;
+
+ // Remap "*.com" to "baz", and *.net to "bar:60".
+ resolver->SetRulesFromString("map *.com baz , map *.net bar:60");
+
+ // Try resolving "www.google.com". Should be remapped to "baz".
+ rv = resolver->Resolve(HostResolver::RequestInfo("www.google.com", 80),
+ &address_list, NULL, NULL, BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("192.168.1.7", NetAddressToString(address_list.head()));
+ EXPECT_EQ(80, address_list.GetPort());
+
+ // Try resolving "chrome.net:80". Should be remapped to "bar:60".
+ rv = resolver->Resolve(HostResolver::RequestInfo("chrome.net", 80),
+ &address_list, NULL, NULL, BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("192.168.1.9", NetAddressToString(address_list.head()));
+ EXPECT_EQ(60, address_list.GetPort());
+}
+
+// Parsing bad rules should silently discard the rule (and never crash).
+TEST(MappedHostResolverTest, ParseInvalidRules) {
+ scoped_refptr<MappedHostResolver> resolver = new MappedHostResolver(NULL);
+
+ EXPECT_FALSE(resolver->AddRuleFromString("xyz"));
+ EXPECT_FALSE(resolver->AddRuleFromString(""));
+ EXPECT_FALSE(resolver->AddRuleFromString(" "));
+ EXPECT_FALSE(resolver->AddRuleFromString("EXCLUDE"));
+ EXPECT_FALSE(resolver->AddRuleFromString("EXCLUDE foo bar"));
+ EXPECT_FALSE(resolver->AddRuleFromString("INCLUDE"));
+ EXPECT_FALSE(resolver->AddRuleFromString("INCLUDE x"));
+ EXPECT_FALSE(resolver->AddRuleFromString("INCLUDE x :10"));
+}
+
+} // namespace
+
+} // namespace net
diff --git a/net/base/mime_sniffer.cc b/net/base/mime_sniffer.cc
index 8e063cb..1961107 100644
--- a/net/base/mime_sniffer.cc
+++ b/net/base/mime_sniffer.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -105,9 +105,6 @@
namespace net {
-// We aren't interested in looking at more than 512 bytes of content
-static const size_t kMaxBytesToSniff = 512;
-
// The number of content bytes we need to use all our magic numbers. Feel free
// to increase this number if you add a longer magic number.
static const size_t kBytesRequiredForMagic = 42;
@@ -224,7 +221,7 @@
const size_t len = magic_entry->magic_len;
// Keep kBytesRequiredForMagic honest.
- DCHECK(len <= kBytesRequiredForMagic);
+ DCHECK_LE(len, kBytesRequiredForMagic);
// To compare with magic strings, we need to compute strlen(content), but
// content might not actually have a null terminator. In that case, we
@@ -263,8 +260,29 @@
return false;
}
-static bool SniffForHTML(const char* content, size_t size,
+// Truncates |size| to |max_size| and returns true if |size| is at least
+// |max_size|.
+static bool TruncateSize(const size_t max_size, size_t* size) {
+ // Keep kMaxBytesToSniff honest.
+ DCHECK_LE(static_cast<int>(max_size), kMaxBytesToSniff);
+
+ if (*size >= max_size) {
+ *size = max_size;
+ return true;
+ }
+ return false;
+}
+
+// Returns true and sets result if the content appears to be HTML.
+// Clears have_enough_content if more data could possibly change the result.
+static bool SniffForHTML(const char* content,
+ size_t size,
+ bool* have_enough_content,
std::string* result) {
+ // For HTML, we are willing to consider up to 512 bytes. This may be overly
+ // conservative as IE only considers 256.
+ *have_enough_content &= TruncateSize(512, &size);
+
// We adopt a strategy similar to that used by Mozilla to sniff HTML tags,
// but with some modifications to better match the HTML5 spec.
const char* const end = content + size;
@@ -282,8 +300,14 @@
counter.get(), result);
}
-static bool SniffForMagicNumbers(const char* content, size_t size,
+// Returns true and sets result if the content matches any of kMagicNumbers.
+// Clears have_enough_content if more data could possibly change the result.
+static bool SniffForMagicNumbers(const char* content,
+ size_t size,
+ bool* have_enough_content,
std::string* result) {
+ *have_enough_content &= TruncateSize(kBytesRequiredForMagic, &size);
+
// Check our big table of Magic Numbers
static scoped_refptr<Histogram> counter =
UMASnifferHistogramGet("mime_sniffer.kMagicNumbers2",
@@ -305,18 +329,22 @@
MAGIC_STRING("application/rss+xml", "<rss") // UTF-8
};
-// Sniff an XML document to judge whether it contains XHTML or a feed.
-// Returns true if it has seen enough content to make a definitive decision.
+// Returns true and sets result if the content appears to contain XHTML or a
+// feed.
+// Clears have_enough_content if more data could possibly change the result.
+//
// TODO(evanm): this is similar but more conservative than what Safari does,
// while HTML5 has a different recommendation -- what should we do?
// TODO(evanm): this is incorrect for documents whose encoding isn't a superset
// of ASCII -- do we care?
-static bool SniffXML(const char* content, size_t size, std::string* result) {
- // We allow at most kFirstTagBytes bytes of content before we expect the
- // opening tag.
- const size_t kFeedAllowedHeaderBytes = 300;
- const char* const end = content + std::min(size, kFeedAllowedHeaderBytes);
+static bool SniffXML(const char* content,
+ size_t size,
+ bool* have_enough_content,
+ std::string* result) {
+ // We allow at most 300 bytes of content before we expect the opening tag.
+ *have_enough_content &= TruncateSize(300, &size);
const char* pos = content;
+ const char* const end = content + size;
// This loop iterates through tag-looking offsets in the file.
// We want to skip XML processing instructions (of the form "<?xml ...")
@@ -389,7 +417,22 @@
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xF0 - 0xFF
};
-static bool LooksBinary(const char* content, size_t size) {
+// Returns true and sets result to "application/octet-stream" if the content
+// appears to be binary data. Otherwise, returns false and sets "text/plain".
+// Clears have_enough_content if more data could possibly change the result.
+static bool SniffBinary(const char* content,
+ size_t size,
+ bool* have_enough_content,
+ std::string* result) {
+ // There is no concensus about exactly how to sniff for binary content.
+ // * IE 7: Don't sniff for binary looking bytes, but trust the file extension.
+ // * Firefox 3.5: Sniff first 4096 bytes for a binary looking byte.
+ // Here, we side with FF, but with a smaller buffer. This size was chosen
+ // because it is small enough to comfortably fit into a single packet (after
+ // allowing for headers) and yet large enough to account for binary formats
+ // that have a significant amount of ASCII at the beginning (crbug.com/15314).
+ const bool is_truncated = TruncateSize(kMaxBytesToSniff, &size);
+
// First, we look for a BOM.
static scoped_refptr<Histogram> counter =
UMASnifferHistogramGet("mime_sniffer.kByteOrderMark2",
@@ -399,17 +442,24 @@
kByteOrderMark, arraysize(kByteOrderMark),
counter.get(), &unused)) {
// If there is BOM, we think the buffer is not binary.
+ result->assign("text/plain");
return false;
}
// Next we look to see if any of the bytes "look binary."
for (size_t i = 0; i < size; ++i) {
// If we a see a binary-looking byte, we think the content is binary.
- if (kByteLooksBinary[static_cast<unsigned char>(content[i])])
+ if (kByteLooksBinary[static_cast<unsigned char>(content[i])]) {
+ result->assign("application/octet-stream");
return true;
+ }
}
- // No evidence either way, default to non-binary.
+ // No evidence either way. Default to non-binary and, if truncated, clear
+ // have_enough_content because there could be a binary looking byte in the
+ // truncated data.
+ *have_enough_content &= is_truncated;
+ result->assign("text/plain");
return false;
}
@@ -443,9 +493,15 @@
return false;
}
-// Sniff a crx (chrome extension) file.
-static bool SniffCRX(const char* content, size_t content_size, const GURL& url,
- const std::string& type_hint, std::string* result) {
+// Returns true and sets result if the content appears to be a crx (chrome
+// extension) file.
+// Clears have_enough_content if more data could possibly change the result.
+static bool SniffCRX(const char* content,
+ size_t size,
+ const GURL& url,
+ const std::string& type_hint,
+ bool* have_enough_content,
+ std::string* result) {
static scoped_refptr<Histogram> counter =
UMASnifferHistogramGet("mime_sniffer.kSniffCRX", 3);
@@ -456,13 +512,14 @@
//
// TODO(aa): If we ever have another magic number, we'll want to pass a
// histogram into CheckForMagicNumbers(), below, to see which one matched.
- const struct MagicNumber kCRXMagicNumbers[] = {
+ static const struct MagicNumber kCRXMagicNumbers[] = {
MAGIC_NUMBER("application/x-chrome-extension", "Cr24\x02\x00\x00\x00")
};
// Only consider files that have the extension ".crx".
- const char kCRXExtension[] = ".crx";
- const int kExtensionLength = arraysize(kCRXExtension) - 1; // ignore null
+ static const char kCRXExtension[] = ".crx";
+ // Ignore null by subtracting 1.
+ static const int kExtensionLength = arraysize(kCRXExtension) - 1;
if (url.path().rfind(kCRXExtension, std::string::npos, kExtensionLength) ==
url.path().size() - kExtensionLength) {
counter->Add(1);
@@ -470,7 +527,8 @@
return false;
}
- if (CheckForMagicNumbers(content, content_size,
+ *have_enough_content &= TruncateSize(kBytesRequiredForMagic, &size);
+ if (CheckForMagicNumbers(content, size,
kCRXMagicNumbers, arraysize(kCRXMagicNumbers),
NULL, result)) {
counter->Add(2);
@@ -488,7 +546,8 @@
bool sniffable_scheme = url.is_empty() ||
url.SchemeIs("http") ||
url.SchemeIs("https") ||
- url.SchemeIs("ftp");
+ url.SchemeIs("ftp") ||
+ url.SchemeIsFile();
if (!sniffable_scheme) {
should_sniff_counter->Add(1);
return false;
@@ -534,17 +593,14 @@
DCHECK(content);
DCHECK(result);
+ // By default, we assume we have enough content.
+ // Each sniff routine may unset this if it wasn't provided enough content.
+ bool have_enough_content = true;
+
// By default, we'll return the type hint.
+ // Each sniff routine may modify this if it has a better guess..
result->assign(type_hint);
- // Flag for tracking whether our decision was limited by content_size. We
- // probably have enough content if we can use all our magic numbers.
- const bool have_enough_content = content_size >= kBytesRequiredForMagic;
-
- // We have an upper limit on the number of bytes we will consider.
- if (content_size > kMaxBytesToSniff)
- content_size = kMaxBytesToSniff;
-
// Cache information about the type_hint
const bool hint_is_unknown_mime_type = IsUnknownMimeType(type_hint);
@@ -553,34 +609,41 @@
// We're only willing to sniff HTML if the server has not supplied a mime
// type, or if the type it did supply indicates that it doesn't know what
// the type should be.
- if (SniffForHTML(content, content_size, result))
+ if (SniffForHTML(content, content_size, &have_enough_content, result))
return true; // We succeeded in sniffing HTML. No more content needed.
}
- // We'll reuse this information later
+ // We're only willing to sniff for binary in 3 cases:
+ // 1. The server has not supplied a mime type.
+ // 2. The type it did supply indicates that it doesn't know what the type
+ // should be.
+ // 3. The type is "text/plain" which is the default on some web servers and
+ // could be indicative of a mis-configuration that we shield the user from.
const bool hint_is_text_plain = (type_hint == "text/plain");
- const bool looks_binary = LooksBinary(content, content_size);
-
- if (hint_is_text_plain && !looks_binary) {
- // The server said the content was text/plain and we don't really have any
- // evidence otherwise.
- result->assign("text/plain");
- return have_enough_content;
+ if (hint_is_unknown_mime_type || hint_is_text_plain) {
+ if (!SniffBinary(content, content_size, &have_enough_content, result)) {
+ // If the server said the content was text/plain and it doesn't appear
+ // to be binary, then we trust it.
+ if (hint_is_text_plain) {
+ return have_enough_content;
+ }
+ }
}
// If we have plain XML, sniff XML subtypes.
if (type_hint == "text/xml" || type_hint == "application/xml") {
// We're not interested in sniffing these types for images and the like.
- // Instead, we're looking explicitly for a feed. If we don't find one we're
- // done and return early.
- if (SniffXML(content, content_size, result))
+ // Instead, we're looking explicitly for a feed. If we don't find one
+ // we're done and return early.
+ if (SniffXML(content, content_size, &have_enough_content, result))
return true;
- return content_size >= kMaxBytesToSniff;
+ return have_enough_content;
}
// CRX files (chrome extensions) have a special sniffing algorithm. It is
// tighter than the others because we don't have to match legacy behavior.
- if (SniffCRX(content, content_size, url, type_hint, result))
+ if (SniffCRX(content, content_size, url, type_hint,
+ &have_enough_content, result))
return true;
// We're not interested in sniffing for magic numbers when the type_hint
@@ -590,21 +653,10 @@
// Now we look in our large table of magic numbers to see if we can find
// anything that matches the content.
- if (SniffForMagicNumbers(content, content_size, result))
+ if (SniffForMagicNumbers(content, content_size,
+ &have_enough_content, result))
return true; // We've matched a magic number. No more content needed.
- // Having failed thus far, we're willing to override unknown mime types and
- // text/plain.
- if (hint_is_unknown_mime_type || hint_is_text_plain) {
- if (looks_binary)
- result->assign("application/octet-stream");
- else
- result->assign("text/plain");
- // We could change our mind if a binary-looking byte appears later in
- // the content, so we only have enough content if we have the max.
- return content_size >= kMaxBytesToSniff;
- }
-
return have_enough_content;
}
diff --git a/net/base/mime_sniffer.h b/net/base/mime_sniffer.h
index 6fd7014..d0c4e78 100644
--- a/net/base/mime_sniffer.h
+++ b/net/base/mime_sniffer.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -11,6 +11,12 @@
namespace net {
+// The maximum number of bytes used by any internal mime sniffing routine. May
+// be useful for callers to determine an efficient buffer size to pass to
+// |SniffMimeType|.
+// This must be updated if any internal sniffing routine needs more bytes.
+const int kMaxBytesToSniff = 1024;
+
// Examine the URL and the mime_type and decide whether we should sniff a
// replacement mime type from the content.
//
diff --git a/net/base/mime_sniffer_unittest.cc b/net/base/mime_sniffer_unittest.cc
index 56dfd51..d70cb23 100644
--- a/net/base/mime_sniffer_unittest.cc
+++ b/net/base/mime_sniffer_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -149,6 +149,9 @@
{ "Cr24\x02\x00\x00\x01", sizeof("Cr24\x02\x00\x00\x01")-1,
"http://www.example.com/foo.crx?monkey",
"", "application/octet-stream" },
+ { "PADDING_Cr24\x02\x00\x00\x00", sizeof("PADDING_Cr24\x02\x00\x00\x00")-1,
+ "http://www.example.com/foo.crx?monkey",
+ "", "application/octet-stream" },
};
TestArray(tests, arraysize(tests));
@@ -359,16 +362,33 @@
}
-// Test content which is >= 512 bytes, and includes no open angle bracket.
+// Test content which is >= 1024 bytes, and includes no open angle bracket.
// http://code.google.com/p/chromium/issues/detail?id=3521
TEST(MimeSnifferTest, XMLTestLargeNoAngledBracket) {
- // Make a large input, with 600 bytes of "x".
+ // Make a large input, with 1024 bytes of "x".
std::string content;
- content.resize(600);
+ content.resize(1024);
std::fill(content.begin(), content.end(), 'x');
- // content.size() >= kMaxBytesToSniff (512) so the sniff is unambiguous.
+ // content.size() >= 1024 so the sniff is unambiguous.
std::string mime_type;
EXPECT_TRUE(net::SniffMimeType(content.data(), content.size(), GURL(),
"text/xml", &mime_type));
+ EXPECT_EQ("text/xml", mime_type);
+}
+
+// Test content which is >= 1024 bytes, and includes a binary looking byte.
+// http://code.google.com/p/chromium/issues/detail?id=15314
+TEST(MimeSnifferTest, LooksBinary) {
+ // Make a large input, with 1024 bytes of "x" and 1 byte of 0x01.
+ std::string content;
+ content.resize(1024);
+ std::fill(content.begin(), content.end(), 'x');
+ content[1000] = 0x01;
+
+ // content.size() >= 1024 so the sniff is unambiguous.
+ std::string mime_type;
+ EXPECT_TRUE(net::SniffMimeType(content.data(), content.size(), GURL(),
+ "text/plain", &mime_type));
+ EXPECT_EQ("application/octet-stream", mime_type);
}
diff --git a/net/base/mime_util.cc b/net/base/mime_util.cc
index 0bccb0a..5863b68 100644
--- a/net/base/mime_util.cc
+++ b/net/base/mime_util.cc
@@ -1,7 +1,8 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// 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 <map>
#include <string>
#include "net/base/mime_util.h"
@@ -11,6 +12,7 @@
#include "base/logging.h"
#include "base/singleton.h"
#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
using std::string;
@@ -40,7 +42,12 @@
bool AreSupportedMediaCodecs(const std::vector<std::string>& codecs) const;
void ParseCodecString(const std::string& codecs,
- std::vector<std::string>* codecs_out);
+ std::vector<std::string>* codecs_out,
+ bool strip);
+
+ bool IsStrictMediaMimeType(const std::string& mime_type) const;
+ bool IsSupportedStrictMediaMimeType(const std::string& mime_type,
+ const std::vector<std::string>& codecs) const;
private:
friend struct DefaultSingletonTraits<MimeUtil>;
@@ -58,6 +65,9 @@
MimeMappings javascript_map_;
MimeMappings view_source_map_;
MimeMappings codecs_map_;
+
+ typedef std::map<std::string, base::hash_set<std::string> > StrictMappings;
+ StrictMappings strict_format_map_;
}; // class MimeUtil
struct MimeInfo {
@@ -77,6 +87,8 @@
{ "audio/mp3", "mp3" },
{ "video/ogg", "ogv,ogm" },
{ "audio/ogg", "ogg,oga" },
+ { "video/webm", "webm" },
+ { "audio/webm", "webm" },
{ "application/xhtml+xml", "xhtml,xht" },
{ "application/x-chrome-extension", "crx" }
};
@@ -126,6 +138,11 @@
bool MimeUtil::GetMimeTypeFromExtension(const FilePath::StringType& ext,
string* result) const {
+ // Avoids crash when unable to handle a long file path. See crbug.com/48733.
+ const unsigned kMaxFilePathSize = 65536;
+ if (ext.length() > kMaxFilePathSize)
+ return false;
+
// We implement the same algorithm as Mozilla for mapping a file extension to
// a mime type. That is, we first check a hard-coded list (that cannot be
// overridden), and then if not found there, we defer to the system registry.
@@ -187,8 +204,10 @@
"video/ogg",
"audio/ogg",
"application/ogg",
+ "video/webm",
+ "audio/webm",
-#if defined(GOOGLE_CHROME_BUILD)
+#if defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS)
// MPEG-4.
"video/mp4",
"video/x-m4v",
@@ -207,16 +226,18 @@
// Refer to http://wiki.whatwg.org/wiki/Video_type_parameters#Browser_Support
// for more information.
static const char* const supported_media_codecs[] = {
-#if defined(GOOGLE_CHROME_BUILD)
+#if defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS)
"avc1",
"mp4a",
#endif
"theora",
"vorbis",
+ "vp8"
};
// Note: does not include javascript types list (see supported_javascript_types)
static const char* const supported_non_image_types[] = {
+ "text/cache-manifest",
"text/html",
"text/xml",
"text/xsl",
@@ -237,7 +258,11 @@
"application/json",
"application/x-x509-user-cert",
"multipart/x-mixed-replace"
+ // Note: ADDING a new type here will probably render it AS HTML. This can
+ // result in cross site scripting.
};
+COMPILE_ASSERT(arraysize(supported_non_image_types) == 16,
+ supported_non_images_types_must_equal_16);
// Mozilla 1.8 and WinIE 7 both accept text/javascript and text/ecmascript.
// Mozilla 1.8 accepts application/javascript, application/ecmascript, and
@@ -271,6 +296,16 @@
"image/svg+xml"
};
+struct MediaFormatStrict {
+ const char* mime_type;
+ const char* codecs_list;
+};
+
+static const MediaFormatStrict format_codec_mappings[] = {
+ { "video/webm", "vorbis,vp8,vp8.0" },
+ { "audio/webm", "vorbis" }
+};
+
void MimeUtil::InitializeMimeTypeMaps() {
for (size_t i = 0; i < arraysize(supported_image_types); ++i)
image_map_.insert(supported_image_types[i]);
@@ -295,6 +330,19 @@
for (size_t i = 0; i < arraysize(supported_media_codecs); ++i)
codecs_map_.insert(supported_media_codecs[i]);
+
+ // Initialize the strict supported media types.
+ for (size_t i = 0; i < arraysize(format_codec_mappings); ++i) {
+ std::vector<std::string> mime_type_codecs;
+ ParseCodecString(format_codec_mappings[i].codecs_list,
+ &mime_type_codecs,
+ false);
+
+ MimeMappings codecs;
+ for (size_t j = 0; j < mime_type_codecs.size(); ++j)
+ codecs.insert(mime_type_codecs[j]);
+ strict_format_map_[format_codec_mappings[i].mime_type] = codecs;
+ }
}
bool MimeUtil::IsSupportedImageMimeType(const char* mime_type) const {
@@ -372,12 +420,16 @@
}
void MimeUtil::ParseCodecString(const std::string& codecs,
- std::vector<std::string>* codecs_out) {
+ std::vector<std::string>* codecs_out,
+ bool strip) {
std::string no_quote_codecs;
TrimString(codecs, "\"", &no_quote_codecs);
SplitString(no_quote_codecs, ',', codecs_out);
- // Truncate each string at the '.'
+ if (!strip)
+ return;
+
+ // Strip everything past the first '.'
for (std::vector<std::string>::iterator it = codecs_out->begin();
it != codecs_out->end();
++it) {
@@ -387,6 +439,28 @@
}
}
+bool MimeUtil::IsStrictMediaMimeType(const std::string& mime_type) const {
+ if (strict_format_map_.find(mime_type) == strict_format_map_.end())
+ return false;
+ return true;
+}
+
+bool MimeUtil::IsSupportedStrictMediaMimeType(const std::string& mime_type,
+ const std::vector<std::string>& codecs) const {
+ StrictMappings::const_iterator it = strict_format_map_.find(mime_type);
+
+ if (it == strict_format_map_.end())
+ return false;
+
+ const MimeMappings strict_codecs_map = it->second;
+ for (size_t i = 0; i < codecs.size(); ++i) {
+ if (strict_codecs_map.find(codecs[i]) == strict_codecs_map.end()) {
+ return false;
+ }
+ }
+ return true;
+}
+
//----------------------------------------------------------------------------
// Wrappers for the singleton
//----------------------------------------------------------------------------
@@ -442,9 +516,19 @@
return GetMimeUtil()->AreSupportedMediaCodecs(codecs);
}
+bool IsStrictMediaMimeType(const std::string& mime_type) {
+ return GetMimeUtil()->IsStrictMediaMimeType(mime_type);
+}
+
+bool IsSupportedStrictMediaMimeType(const std::string& mime_type,
+ const std::vector<std::string>& codecs) {
+ return GetMimeUtil()->IsSupportedStrictMediaMimeType(mime_type, codecs);
+}
+
void ParseCodecString(const std::string& codecs,
- std::vector<std::string>* codecs_out) {
- GetMimeUtil()->ParseCodecString(codecs, codecs_out);
+ std::vector<std::string>* codecs_out,
+ const bool strip) {
+ GetMimeUtil()->ParseCodecString(codecs, codecs_out, strip);
}
} // namespace net
diff --git a/net/base/mime_util.h b/net/base/mime_util.h
index 1846087..eebc2e8 100644
--- a/net/base/mime_util.h
+++ b/net/base/mime_util.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -50,11 +50,25 @@
bool AreSupportedMediaCodecs(const std::vector<std::string>& codecs);
// Parses a codec string, populating |codecs_out| with the prefix of each codec
-// in the string |codecs_in|. For example, passed "aaa.b.c,dd.eee", |codecs_out|
-// will contain {"aaa", "dd"}.
+// in the string |codecs_in|. For example, passed "aaa.b.c,dd.eee", if
+// |strip| == true |codecs_out| will contain {"aaa", "dd"}, if |strip| == false
+// |codecs_out| will contain {"aaa.b.c", "dd.eee"}.
// See http://www.ietf.org/rfc/rfc4281.txt.
void ParseCodecString(const std::string& codecs,
- std::vector<std::string>* codecs_out);
+ std::vector<std::string>* codecs_out,
+ bool strip);
+
+// Check to see if a particular MIME type is in our list which only supports a
+// certain subset of codecs.
+bool IsStrictMediaMimeType(const std::string& mime_type);
+
+// Check to see if a particular MIME type is in our list which only supports a
+// certain subset of codecs. Returns true if and only if all codecs are
+// supported for that specific MIME type, false otherwise. If this returns
+// false you will still need to check if the media MIME tpyes and codecs are
+// supported.
+bool IsSupportedStrictMediaMimeType(const std::string& mime_type,
+ const std::vector<std::string>& codecs);
} // namespace net
diff --git a/net/base/mime_util_unittest.cc b/net/base/mime_util_unittest.cc
index 0300da8..51b7f3a 100644
--- a/net/base/mime_util_unittest.cc
+++ b/net/base/mime_util_unittest.cc
@@ -118,10 +118,17 @@
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
std::vector<std::string> codecs_out;
- net::ParseCodecString(tests[i].original, &codecs_out);
+ net::ParseCodecString(tests[i].original, &codecs_out, true);
EXPECT_EQ(tests[i].expected_size, codecs_out.size());
for (size_t j = 0; j < tests[i].expected_size; ++j) {
EXPECT_EQ(tests[i].results[j], codecs_out[j]);
}
}
+
+ // Test without stripping the codec type.
+ std::vector<std::string> codecs_out;
+ net::ParseCodecString("avc1.42E01E, mp4a.40.2", &codecs_out, false);
+ EXPECT_EQ(2u, codecs_out.size());
+ EXPECT_STREQ("avc1.42E01E", codecs_out[0].c_str());
+ EXPECT_STREQ("mp4a.40.2", codecs_out[1].c_str());
}
diff --git a/net/base/mock_host_resolver.cc b/net/base/mock_host_resolver.cc
index 2185182..8d1fd89 100644
--- a/net/base/mock_host_resolver.cc
+++ b/net/base/mock_host_resolver.cc
@@ -7,31 +7,35 @@
#include "base/string_util.h"
#include "base/platform_thread.h"
#include "base/ref_counted.h"
-#include "googleurl/src/url_canon_ip.h"
#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
namespace net {
namespace {
-// Fills |addrlist| with a socket address for |host| which should be an
-// IPv6 literal. Returns OK on success.
-int ResolveIPV6LiteralUsingGURL(const std::string& host,
- AddressList* addrlist) {
- // GURL expects the hostname to be surrounded with brackets.
- std::string host_brackets = "[" + host + "]";
- url_parse::Component host_comp(0, host_brackets.size());
- // Try parsing the hostname as an IPv6 literal.
- unsigned char ipv6_addr[16]; // 128 bits.
- bool ok = url_canon::IPv6AddressToNumber(host_brackets.data(),
- host_comp,
- ipv6_addr);
- if (!ok) {
- LOG(WARNING) << "Not an IPv6 literal: " << host;
+// Fills |*addrlist| with a socket address for |host| which should be an
+// IPv4 or IPv6 literal without enclosing brackets. If |canonical_name| is
+// non-empty it is used as the DNS canonical name for the host. Returns OK on
+// success, ERR_UNEXPECTED otherwise.
+int CreateIPAddress(const std::string& host,
+ const std::string& canonical_name,
+ AddressList* addrlist) {
+ IPAddressNumber ip_number;
+ if (!ParseIPLiteralToNumber(host, &ip_number)) {
+ LOG(WARNING) << "Not a supported IP literal: " << host;
return ERR_UNEXPECTED;
}
- *addrlist = AddressList::CreateIPv6Address(ipv6_addr);
+ if (ip_number.size() == 4) {
+ *addrlist = AddressList::CreateIPv4Address(&ip_number[0], canonical_name);
+ } else if (ip_number.size() == 16) {
+ *addrlist = AddressList::CreateIPv6Address(&ip_number[0], canonical_name);
+ } else {
+ NOTREACHED();
+ return ERR_UNEXPECTED;
+ }
+
return OK;
}
@@ -46,12 +50,12 @@
AddressList* addresses,
CompletionCallback* callback,
RequestHandle* out_req,
- LoadLog* load_log) {
+ const BoundNetLog& net_log) {
if (synchronous_mode_) {
callback = NULL;
out_req = NULL;
}
- return impl_->Resolve(info, addresses, callback, out_req, load_log);
+ return impl_->Resolve(info, addresses, callback, out_req, net_log);
}
void MockHostResolverBase::CancelRequest(RequestHandle req) {
@@ -66,10 +70,6 @@
impl_->RemoveObserver(observer);
}
-void MockHostResolverBase::Shutdown() {
- impl_->Shutdown();
-}
-
void MockHostResolverBase::Reset(HostResolverProc* interceptor) {
synchronous_mode_ = false;
@@ -98,7 +98,7 @@
base::TimeDelta::FromSeconds(0));
}
- impl_ = new HostResolverImpl(proc, cache, NULL, 50u);
+ impl_ = new HostResolverImpl(proc, cache, 50u);
}
//-----------------------------------------------------------------------------
@@ -107,24 +107,30 @@
enum ResolverType {
kResolverTypeFail,
kResolverTypeSystem,
- kResolverTypeIPV6Literal,
+ kResolverTypeIPLiteral,
};
ResolverType resolver_type;
std::string host_pattern;
AddressFamily address_family;
+ HostResolverFlags host_resolver_flags;
std::string replacement;
+ std::string canonical_name;
int latency_ms; // In milliseconds.
Rule(ResolverType resolver_type,
const std::string& host_pattern,
AddressFamily address_family,
+ HostResolverFlags host_resolver_flags,
const std::string& replacement,
+ const std::string& canonical_name,
int latency_ms)
: resolver_type(resolver_type),
host_pattern(host_pattern),
address_family(address_family),
+ host_resolver_flags(host_resolver_flags),
replacement(replacement),
+ canonical_name(canonical_name),
latency_ms(latency_ms) {}
};
@@ -147,14 +153,21 @@
const std::string& replacement) {
DCHECK(!replacement.empty());
Rule rule(Rule::kResolverTypeSystem, host_pattern,
- address_family, replacement, 0);
+ address_family, 0, replacement, "", 0);
rules_.push_back(rule);
}
-void RuleBasedHostResolverProc::AddIPv6Rule(const std::string& host_pattern,
- const std::string& ipv6_literal) {
- Rule rule(Rule::kResolverTypeIPV6Literal, host_pattern,
- ADDRESS_FAMILY_UNSPECIFIED, ipv6_literal, 0);
+void RuleBasedHostResolverProc::AddIPLiteralRule(
+ const std::string& host_pattern,
+ const std::string& ip_literal,
+ const std::string& canonical_name) {
+ Rule rule(Rule::kResolverTypeIPLiteral,
+ host_pattern,
+ ADDRESS_FAMILY_UNSPECIFIED,
+ canonical_name.empty() ? 0 : HOST_RESOLVER_CANONNAME,
+ ip_literal,
+ canonical_name,
+ 0);
rules_.push_back(rule);
}
@@ -164,34 +177,42 @@
int latency_ms) {
DCHECK(!replacement.empty());
Rule rule(Rule::kResolverTypeSystem, host_pattern,
- ADDRESS_FAMILY_UNSPECIFIED, replacement, latency_ms);
+ ADDRESS_FAMILY_UNSPECIFIED, 0, replacement, "", latency_ms);
rules_.push_back(rule);
}
void RuleBasedHostResolverProc::AllowDirectLookup(
const std::string& host_pattern) {
Rule rule(Rule::kResolverTypeSystem, host_pattern,
- ADDRESS_FAMILY_UNSPECIFIED, "", 0);
+ ADDRESS_FAMILY_UNSPECIFIED, 0, "", "", 0);
rules_.push_back(rule);
}
void RuleBasedHostResolverProc::AddSimulatedFailure(
const std::string& host_pattern) {
Rule rule(Rule::kResolverTypeFail, host_pattern,
- ADDRESS_FAMILY_UNSPECIFIED, "", 0);
+ ADDRESS_FAMILY_UNSPECIFIED, 0, "", "", 0);
rules_.push_back(rule);
}
int RuleBasedHostResolverProc::Resolve(const std::string& host,
AddressFamily address_family,
- AddressList* addrlist) {
+ HostResolverFlags host_resolver_flags,
+ AddressList* addrlist,
+ int* os_error) {
RuleList::iterator r;
for (r = rules_.begin(); r != rules_.end(); ++r) {
bool matches_address_family =
r->address_family == ADDRESS_FAMILY_UNSPECIFIED ||
r->address_family == address_family;
-
- if (matches_address_family && MatchPatternASCII(host, r->host_pattern)) {
+ // Flags match if all of the bitflags in host_resolver_flags are enabled
+ // in the rule's host_resolver_flags. However, the rule may have additional
+ // flags specified, in which case the flags should still be considered a
+ // match.
+ bool matches_flags = (r->host_resolver_flags & host_resolver_flags) ==
+ host_resolver_flags;
+ if (matches_flags && matches_address_family &&
+ MatchPatternASCII(host, r->host_pattern)) {
if (r->latency_ms != 0)
PlatformThread::Sleep(r->latency_ms);
@@ -206,16 +227,18 @@
case Rule::kResolverTypeSystem:
return SystemHostResolverProc(effective_host,
address_family,
- addrlist);
- case Rule::kResolverTypeIPV6Literal:
- return ResolveIPV6LiteralUsingGURL(effective_host, addrlist);
+ host_resolver_flags,
+ addrlist, os_error);
+ case Rule::kResolverTypeIPLiteral:
+ return CreateIPAddress(effective_host, r->canonical_name, addrlist);
default:
NOTREACHED();
return ERR_UNEXPECTED;
}
}
}
- return ResolveUsingPrevious(host, address_family, addrlist);
+ return ResolveUsingPrevious(host, address_family,
+ host_resolver_flags, addrlist, os_error);
}
//-----------------------------------------------------------------------------
@@ -228,7 +251,7 @@
ScopedDefaultHostResolverProc::~ScopedDefaultHostResolverProc() {
HostResolverProc* old_proc = HostResolverProc::SetDefault(previous_proc_);
// The lifetimes of multiple instances must be nested.
- CHECK(old_proc == current_proc_);
+ CHECK_EQ(old_proc, current_proc_);
}
void ScopedDefaultHostResolverProc::Init(HostResolverProc* proc) {
diff --git a/net/base/mock_host_resolver.h b/net/base/mock_host_resolver.h
index 8c843b6..ee1e55a 100644
--- a/net/base/mock_host_resolver.h
+++ b/net/base/mock_host_resolver.h
@@ -43,12 +43,10 @@
AddressList* addresses,
CompletionCallback* callback,
RequestHandle* out_req,
- LoadLog* load_log);
+ const BoundNetLog& net_log);
virtual void CancelRequest(RequestHandle req);
virtual void AddObserver(Observer* observer);
virtual void RemoveObserver(Observer* observer);
- // TODO(eroman): temp hack for http://crbug.com/18373
- virtual void Shutdown();
RuleBasedHostResolverProc* rules() { return rules_; }
@@ -109,12 +107,14 @@
AddressFamily address_family,
const std::string& replacement);
- // Same as AddRule(), but the replacement is expected to be an IPV6 literal.
- // You should use this in place of AddRule(), since the system's host resolver
- // may not support IPv6 literals on all systems. Whereas this variant
- // constructs the socket address directly so it will always work.
- void AddIPv6Rule(const std::string& host_pattern,
- const std::string& ipv6_literal);
+ // Same as AddRule(), but the replacement is expected to be an IPv4 or IPv6
+ // literal. This can be used in place of AddRule() to bypass the system's
+ // host resolver (the address list will be constructed manually).
+ // If |canonical-name| is non-empty, it is copied to the resulting AddressList
+ // but does not impact DNS resolution.
+ void AddIPLiteralRule(const std::string& host_pattern,
+ const std::string& ip_literal,
+ const std::string& canonical_name);
void AddRuleWithLatency(const std::string& host_pattern,
const std::string& replacement,
@@ -130,7 +130,9 @@
// HostResolverProc methods:
virtual int Resolve(const std::string& host,
AddressFamily address_family,
- AddressList* addrlist);
+ HostResolverFlags host_resolver_flags,
+ AddressList* addrlist,
+ int* os_error);
private:
~RuleBasedHostResolverProc();
@@ -154,9 +156,12 @@
// HostResolverProc methods:
virtual int Resolve(const std::string& host,
AddressFamily address_family,
- AddressList* addrlist) {
+ HostResolverFlags host_resolver_flags,
+ AddressList* addrlist,
+ int* os_error) {
event_.Wait();
- return ResolveUsingPrevious(host, address_family, addrlist);
+ return ResolveUsingPrevious(host, address_family, host_resolver_flags,
+ addrlist, os_error);
}
private:
diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h
index bff55d5..e302a9f 100644
--- a/net/base/net_error_list.h
+++ b/net/base/net_error_list.h
@@ -1,7 +1,10 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// 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.
+// This file intentionally does not have header guards, it's included
+// inside a macro to generate enum.
+
// This file contains the list of network errors.
//
@@ -11,6 +14,8 @@
// 200-299 Certificate errors
// 300-399 HTTP errors
// 400-499 Cache errors
+// 500-599 ?
+// 600-699 FTP errors
//
// An asynchronous IO operation is not yet complete. This usually does not
@@ -56,6 +61,10 @@
// Memory allocation failed.
NET_ERROR(OUT_OF_MEMORY, -13)
+// The file upload failed because the file's modification time was different
+// from the expectation.
+NET_ERROR(UPLOAD_FILE_CHANGED, -14)
+
// A connection was closed (corresponding to a TCP FIN).
NET_ERROR(CONNECTION_CLOSED, -100)
@@ -105,8 +114,9 @@
// The server requested a renegotiation (rehandshake).
NET_ERROR(SSL_RENEGOTIATION_REQUESTED, -114)
-// The proxy requested authentication (for tunnel establishment).
-NET_ERROR(PROXY_AUTH_REQUESTED, -115)
+// The proxy requested authentication (for tunnel establishment) with an
+// unsupported method.
+NET_ERROR(PROXY_AUTH_UNSUPPORTED, -115)
// During SSL renegotiation (rehandshake), the server sent a certificate with
// an error.
@@ -125,6 +135,43 @@
// aborted.
NET_ERROR(HOST_RESOLVER_QUEUE_TOO_LARGE, -119)
+// Failed establishing a connection to the SOCKS proxy server for a target host.
+NET_ERROR(SOCKS_CONNECTION_FAILED, -120)
+
+// The SOCKS proxy server failed establishing connection to the target host
+// because that host is unreachable.
+NET_ERROR(SOCKS_CONNECTION_HOST_UNREACHABLE, -121)
+
+// The request to negotiate an alternate protocol failed.
+NET_ERROR(NPN_NEGOTIATION_FAILED, -122)
+
+// The peer sent an SSL no_renegotiation alert message.
+NET_ERROR(SSL_NO_RENEGOTIATION, -123)
+
+// Winsock sometimes reports more data written than passed. This is probably
+// due to a broken LSP.
+NET_ERROR(WINSOCK_UNEXPECTED_WRITTEN_BYTES, -124)
+
+// An SSL peer sent us a fatal decompression_failure alert. This typically
+// occurs when a peer selects DEFLATE compression in the mismaken belief that
+// it supports it.
+NET_ERROR(SSL_DECOMPRESSION_FAILURE_ALERT, -125)
+
+// An SSL peer sent us a fatal bad_record_mac alert. This has been observed
+// from servers with buggy DEFLATE support.
+NET_ERROR(SSL_BAD_RECORD_MAC_ALERT, -126)
+
+// The proxy requested authentication (for tunnel establishment).
+NET_ERROR(PROXY_AUTH_REQUESTED, -127)
+
+// A known TLS strict server didn't offer the renegotiation extension.
+NET_ERROR(SSL_UNSAFE_NEGOTIATION, -128)
+
+// The socket is reporting that we tried to provide new credentials after a
+// a failed attempt on a connection without keep alive. We need to
+// reestablish the transport socket in order to retry the authentication.
+NET_ERROR(RETRY_CONNECTION, -129)
+
// Certificate error codes
//
// The values of certificate error codes must be consecutive.
@@ -176,6 +223,8 @@
//
// MSDN describes this error as follows:
// "The SSL certificate contains errors."
+// NOTE: It's unclear how this differs from ERR_CERT_INVALID. For consistency,
+// use that code instead of this one from now on.
//
NET_ERROR(CERT_CONTAINS_ERRORS, -203)
@@ -289,14 +338,27 @@
// The server sent an FTP directory listing in a format we do not understand.
NET_ERROR(UNRECOGNIZED_FTP_DIRECTORY_LISTING_FORMAT, -334)
-// Attempted use of an unknown FLIP stream id.
-NET_ERROR(INVALID_FLIP_STREAM, -335)
+// Attempted use of an unknown SPDY stream id.
+NET_ERROR(INVALID_SPDY_STREAM, -335)
// There are no supported proxies in the provided list.
NET_ERROR(NO_SUPPORTED_PROXIES, -336)
-// There is a FLIP protocol framing error.
-NET_ERROR(FLIP_PROTOCOL_ERROR, -337)
+// There is a SPDY protocol framing error.
+NET_ERROR(SPDY_PROTOCOL_ERROR, -337)
+
+// Credentials could not be estalished during HTTP Authentication.
+NET_ERROR(INVALID_AUTH_CREDENTIALS, -338)
+
+// An HTTP Authentication scheme was tried which is not supported on this
+// machine.
+NET_ERROR(UNSUPPORTED_AUTH_SCHEME, -339)
+
+// Detecting the encoding of the response failed.
+NET_ERROR(ENCODING_DETECTION_FAILED, -340)
+
+// (GSSAPI) No Kerberos credentials were available during HTTP Authentication.
+NET_ERROR(MISSING_AUTH_CREDENTIALS, -341)
// The cache does not have the requested entry.
NET_ERROR(CACHE_MISS, -400)
@@ -323,3 +385,43 @@
// The server's response was insecure (e.g. there was a cert error).
NET_ERROR(INSECURE_RESPONSE, -501)
+
+// The server responded to a <keygen> with a generated client cert that we
+// don't have the matching private key for.
+NET_ERROR(NO_PRIVATE_KEY_FOR_CERT, -502)
+
+// An error adding to the OS certificate database (e.g. OS X Keychain).
+NET_ERROR(ADD_USER_CERT_FAILED, -503)
+
+// *** Code -600 is reserved (was FTP_PASV_COMMAND_FAILED). ***
+
+// A generic error for failed FTP control connection command.
+// If possible, please use or add a more specific error code.
+NET_ERROR(FTP_FAILED, -601)
+
+// The server cannot fulfill the request at this point. This is a temporary
+// error.
+// FTP response code 421.
+NET_ERROR(FTP_SERVICE_UNAVAILABLE, -602)
+
+// The server has aborted the transfer.
+// FTP response code 426.
+NET_ERROR(FTP_TRANSFER_ABORTED, -603)
+
+// The file is busy, or some other temporary error condition on opening
+// the file.
+// FTP response code 450.
+NET_ERROR(FTP_FILE_BUSY, -604)
+
+// Server rejected our command because of syntax errors.
+// FTP response codes 500, 501.
+NET_ERROR(FTP_SYNTAX_ERROR, -605)
+
+// Server does not support the command we issued.
+// FTP response codes 502, 504.
+NET_ERROR(FTP_COMMAND_NOT_SUPPORTED, -606)
+
+// Server rejected our command because we didn't issue the commands in right
+// order.
+// FTP response code 503.
+NET_ERROR(FTP_BAD_COMMAND_SEQUENCE, -607)
diff --git a/net/base/net_log.cc b/net/base/net_log.cc
new file mode 100644
index 0000000..bd62f91
--- /dev/null
+++ b/net/base/net_log.cc
@@ -0,0 +1,112 @@
+// 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 "net/base/net_log.h"
+#include "base/string_util.h"
+#include "base/time.h"
+#include "base/values.h"
+
+namespace net {
+
+// static
+const char* NetLog::EventTypeToString(EventType event) {
+ switch (event) {
+#define EVENT_TYPE(label) case TYPE_ ## label: return #label;
+#include "net/base/net_log_event_type_list.h"
+#undef EVENT_TYPE
+ }
+ return NULL;
+}
+
+// static
+std::vector<NetLog::EventType> NetLog::GetAllEventTypes() {
+ std::vector<NetLog::EventType> types;
+#define EVENT_TYPE(label) types.push_back(TYPE_ ## label);
+#include "net/base/net_log_event_type_list.h"
+#undef EVENT_TYPE
+ return types;
+}
+
+void BoundNetLog::AddEntry(
+ NetLog::EventType type,
+ NetLog::EventPhase phase,
+ const scoped_refptr<NetLog::EventParameters>& params) const {
+ if (net_log_) {
+ net_log_->AddEntry(type, base::TimeTicks::Now(), source_, phase, params);
+ }
+}
+
+void BoundNetLog::AddEntryWithTime(
+ NetLog::EventType type,
+ const base::TimeTicks& time,
+ NetLog::EventPhase phase,
+ const scoped_refptr<NetLog::EventParameters>& params) const {
+ if (net_log_) {
+ net_log_->AddEntry(type, time, source_, phase, params);
+ }
+}
+
+bool BoundNetLog::HasListener() const {
+ if (net_log_)
+ return net_log_->HasListener();
+ return false;
+}
+
+void BoundNetLog::AddEvent(
+ NetLog::EventType event_type,
+ const scoped_refptr<NetLog::EventParameters>& params) const {
+ AddEntry(event_type, NetLog::PHASE_NONE, params);
+}
+
+void BoundNetLog::BeginEvent(
+ NetLog::EventType event_type,
+ const scoped_refptr<NetLog::EventParameters>& params) const {
+ AddEntry(event_type, NetLog::PHASE_BEGIN, params);
+}
+
+void BoundNetLog::EndEvent(
+ NetLog::EventType event_type,
+ const scoped_refptr<NetLog::EventParameters>& params) const {
+ AddEntry(event_type, NetLog::PHASE_END, params);
+}
+
+// static
+BoundNetLog BoundNetLog::Make(NetLog* net_log,
+ NetLog::SourceType source_type) {
+ if (!net_log)
+ return BoundNetLog();
+
+ NetLog::Source source(source_type, net_log->NextID());
+ return BoundNetLog(source, net_log);
+}
+
+NetLogStringParameter::NetLogStringParameter(const char* name,
+ const std::string& value)
+ : name_(name), value_(value) {
+}
+
+Value* NetLogIntegerParameter::ToValue() const {
+ DictionaryValue* dict = new DictionaryValue();
+ dict->SetInteger(ASCIIToWide(name_), value_);
+ return dict;
+}
+
+Value* NetLogStringParameter::ToValue() const {
+ DictionaryValue* dict = new DictionaryValue();
+ dict->SetString(ASCIIToWide(name_), value_);
+ return dict;
+}
+
+Value* NetLogSourceParameter::ToValue() const {
+ DictionaryValue* dict = new DictionaryValue();
+
+ DictionaryValue* source_dict = new DictionaryValue();
+ source_dict->SetInteger(L"type", static_cast<int>(value_.type));
+ source_dict->SetInteger(L"id", static_cast<int>(value_.id));
+
+ dict->Set(ASCIIToWide(name_), source_dict);
+ return dict;
+}
+
+} // namespace net
diff --git a/net/base/net_log.h b/net/base/net_log.h
new file mode 100644
index 0000000..266b93a
--- /dev/null
+++ b/net/base/net_log.h
@@ -0,0 +1,233 @@
+// 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.
+
+#ifndef NET_BASE_NET_LOG_H_
+#define NET_BASE_NET_LOG_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/ref_counted.h"
+
+class Value;
+
+namespace base {
+class TimeTicks;
+}
+
+namespace net {
+
+// NetLog is the destination for log messages generated by the network stack.
+// Each log message has a "source" field which identifies the specific entity
+// that generated the message (for example, which URLRequest or which
+// SocketStream).
+//
+// To avoid needing to pass in the "source id" to the logging functions, NetLog
+// is usually accessed through a BoundNetLog, which will always pass in a
+// specific source ID.
+//
+// Note that NetLog is NOT THREADSAFE.
+//
+// ******** The NetLog (and associated logging) is a work in progress ********
+//
+// TODO(eroman): Remove the 'const' qualitifer from the BoundNetLog methods.
+// TODO(eroman): Make the DNS jobs emit into the NetLog.
+// TODO(eroman): Start a new Source each time URLRequest redirects
+// (simpler to reason about each as a separate entity).
+
+class NetLog {
+ public:
+ enum EventType {
+#define EVENT_TYPE(label) TYPE_ ## label,
+#include "net/base/net_log_event_type_list.h"
+#undef EVENT_TYPE
+ };
+
+ // The 'phase' of an event trace (whether it marks the beginning or end
+ // of an event.).
+ enum EventPhase {
+ PHASE_NONE,
+ PHASE_BEGIN,
+ PHASE_END,
+ };
+
+ // The "source" identifies the entity that generated the log message.
+ enum SourceType {
+#define SOURCE_TYPE(label, value) SOURCE_ ## label = value,
+#include "net/base/net_log_source_type_list.h"
+#undef SOURCE_TYPE
+ };
+
+ // Identifies the entity that generated this log. The |id| field should
+ // uniquely identify the source, and is used by log observers to infer
+ // message groupings. Can use NetLog::NextID() to create unique IDs.
+ struct Source {
+ static const uint32 kInvalidId = 0;
+
+ Source() : type(SOURCE_NONE), id(kInvalidId) {}
+ Source(SourceType type, uint32 id) : type(type), id(id) {}
+ bool is_valid() { return id != kInvalidId; }
+
+ SourceType type;
+ uint32 id;
+ };
+
+ // Base class for associating additional parameters with an event. Log
+ // observers need to know what specific derivations of EventParameters a
+ // particular EventType uses, in order to get at the individual components.
+ class EventParameters : public base::RefCounted<EventParameters> {
+ public:
+ EventParameters() {}
+ virtual ~EventParameters() {}
+
+ // Serializes the parameters to a Value tree. This is intended to be a
+ // lossless conversion, which is used to serialize the parameters to JSON.
+ // The caller takes ownership of the returned Value*.
+ virtual Value* ToValue() const = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(EventParameters);
+ };
+
+ NetLog() {}
+ virtual ~NetLog() {}
+
+ // Emits an event to the log stream.
+ // |type| - The type of the event.
+ // |time| - The time when the event occurred.
+ // |source| - The source that generated the event.
+ // |phase| - An optional parameter indicating whether this is the start/end
+ // of an action.
+ // |params| - Optional (may be NULL) parameters for this event.
+ // The specific subclass of EventParameters is defined
+ // by the contract for events of this |type|.
+ // TODO(eroman): Take a scoped_refptr<> instead.
+ virtual void AddEntry(EventType type,
+ const base::TimeTicks& time,
+ const Source& source,
+ EventPhase phase,
+ EventParameters* params) = 0;
+
+ // Returns a unique ID which can be used as a source ID.
+ virtual uint32 NextID() = 0;
+
+ // Returns true if more complicated messages should be sent to the log.
+ virtual bool HasListener() const = 0;
+
+ // Returns a C-String symbolic name for |event_type|.
+ static const char* EventTypeToString(EventType event_type);
+
+ // Returns a list of all the available EventTypes.
+ static std::vector<EventType> GetAllEventTypes();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NetLog);
+};
+
+// Helper that binds a Source to a NetLog, and exposes convenience methods to
+// output log messages without needing to pass in the source.
+class BoundNetLog {
+ public:
+ BoundNetLog() : net_log_(NULL) {}
+
+ BoundNetLog(const NetLog::Source& source, NetLog* net_log)
+ : source_(source), net_log_(net_log) {
+ }
+
+ // Convenience methods that call through to the NetLog, passing in the
+ // currently bound source.
+ void AddEntry(NetLog::EventType type,
+ NetLog::EventPhase phase,
+ const scoped_refptr<NetLog::EventParameters>& params) const;
+
+ void AddEntryWithTime(
+ NetLog::EventType type,
+ const base::TimeTicks& time,
+ NetLog::EventPhase phase,
+ const scoped_refptr<NetLog::EventParameters>& params) const;
+
+ // Convenience methods that call through to the NetLog, passing in the
+ // currently bound source, current time, and a fixed "capture phase"
+ // (begin, end, or none).
+ void AddEvent(NetLog::EventType event_type,
+ const scoped_refptr<NetLog::EventParameters>& params) const;
+ void BeginEvent(NetLog::EventType event_type,
+ const scoped_refptr<NetLog::EventParameters>& params) const;
+ void EndEvent(NetLog::EventType event_type,
+ const scoped_refptr<NetLog::EventParameters>& params) const;
+
+ bool HasListener() const;
+
+ // Helper to create a BoundNetLog given a NetLog and a SourceType. Takes care
+ // of creating a unique source ID, and handles the case of NULL net_log.
+ static BoundNetLog Make(NetLog* net_log, NetLog::SourceType source_type);
+
+ const NetLog::Source& source() const { return source_; }
+ NetLog* net_log() const { return net_log_; }
+
+ private:
+ NetLog::Source source_;
+ NetLog* net_log_;
+};
+
+// NetLogStringParameter is a subclass of EventParameters that encapsulates a
+// single std::string parameter.
+class NetLogStringParameter : public NetLog::EventParameters {
+ public:
+ // |name| must be a string literal.
+ NetLogStringParameter(const char* name, const std::string& value);
+
+ const std::string& value() const {
+ return value_;
+ }
+
+ virtual Value* ToValue() const;
+
+ private:
+ const char* const name_;
+ const std::string value_;
+};
+
+// NetLogIntegerParameter is a subclass of EventParameters that encapsulates a
+// single integer parameter.
+class NetLogIntegerParameter : public NetLog::EventParameters {
+ public:
+ // |name| must be a string literal.
+ NetLogIntegerParameter(const char* name, int value)
+ : name_(name), value_(value) {}
+
+ int value() const {
+ return value_;
+ }
+
+ virtual Value* ToValue() const;
+
+ private:
+ const char* name_;
+ const int value_;
+};
+
+// NetLogSourceParameter is a subclass of EventParameters that encapsulates a
+// single NetLog::Source parameter.
+class NetLogSourceParameter : public NetLog::EventParameters {
+ public:
+ // |name| must be a string literal.
+ NetLogSourceParameter(const char* name, const NetLog::Source& value)
+ : name_(name), value_(value) {}
+
+ const NetLog::Source& value() const {
+ return value_;
+ }
+
+ virtual Value* ToValue() const;
+
+ private:
+ const char* name_;
+ const NetLog::Source value_;
+};
+
+} // namespace net
+
+#endif // NET_BASE_NET_LOG_H_
diff --git a/net/base/net_log_event_type_list.h b/net/base/net_log_event_type_list.h
new file mode 100644
index 0000000..2fcd6fa
--- /dev/null
+++ b/net/base/net_log_event_type_list.h
@@ -0,0 +1,609 @@
+// 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.
+
+// NOTE: No header guards are used, since this file is intended to be expanded
+// directly into net_log.h. DO NOT include this file anywhere else.
+
+// --------------------------------------------------------------------------
+// General pseudo-events
+// --------------------------------------------------------------------------
+
+// Something got cancelled (we determine what is cancelled based on the
+// log context around it.)
+EVENT_TYPE(CANCELLED)
+
+// Marks the creation/destruction of a request (URLRequest or SocketStream).
+EVENT_TYPE(REQUEST_ALIVE)
+
+// ------------------------------------------------------------------------
+// HostResolverImpl
+// ------------------------------------------------------------------------
+
+// The start/end of a host resolve (DNS) request.
+// If an error occurred, the end phase will contain these parameters:
+// {
+// "net_error": <The net error code integer for the failure>,
+// "os_error": <The exact error code integer that getaddrinfo() returned>,
+// "was_from_cache": <True if the response was gotten from the cache>
+// }
+EVENT_TYPE(HOST_RESOLVER_IMPL)
+
+// ------------------------------------------------------------------------
+// InitProxyResolver
+// ------------------------------------------------------------------------
+
+// The start/end of auto-detect + custom PAC URL configuration.
+EVENT_TYPE(INIT_PROXY_RESOLVER)
+
+// The start/end of download of a PAC script. This could be the well-known
+// WPAD URL (if testing auto-detect), or a custom PAC URL.
+//
+// The START event has the parameters:
+// {
+// "url": <URL string of script being fetched>
+// }
+//
+// If the fetch failed, then the END phase has these parameters:
+// {
+// "error_code": <Net error code integer>
+// }
+EVENT_TYPE(INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT)
+
+// The start/end of the testing of a PAC script (trying to parse the fetched
+// file as javascript).
+//
+// If the parsing of the script failed, the END phase will have parameters:
+// {
+// "error_code": <Net error code integer>
+// }
+EVENT_TYPE(INIT_PROXY_RESOLVER_SET_PAC_SCRIPT)
+
+// This event means that initialization failed because there was no
+// configured script fetcher. (This indicates a configuration error).
+EVENT_TYPE(INIT_PROXY_RESOLVER_HAS_NO_FETCHER)
+
+// This event is emitted after deciding to fall-back to the next PAC
+// script in the list.
+EVENT_TYPE(INIT_PROXY_RESOLVER_FALLING_BACK_TO_NEXT_PAC_URL)
+
+// ------------------------------------------------------------------------
+// ProxyService
+// ------------------------------------------------------------------------
+
+// The start/end of a proxy resolve request.
+EVENT_TYPE(PROXY_SERVICE)
+
+// The time while a request is waiting on InitProxyResolver to configure
+// against either WPAD or custom PAC URL. The specifics on this time
+// are found from ProxyService::init_proxy_resolver_log().
+EVENT_TYPE(PROXY_SERVICE_WAITING_FOR_INIT_PAC)
+
+// The time taken to fetch the system proxy configuration.
+EVENT_TYPE(PROXY_SERVICE_POLL_CONFIG_SERVICE_FOR_CHANGES)
+
+// This event is emitted to show what the PAC script returned. It can contain
+// extra parameters that are either:
+// {
+// "pac_string": <List of valid proxy servers, in PAC format>
+// }
+//
+// Or if the the resolver failed:
+// {
+// "net_error": <Net error code that resolver failed with>
+// }
+EVENT_TYPE(PROXY_SERVICE_RESOLVED_PROXY_LIST)
+
+// ------------------------------------------------------------------------
+// Proxy Resolver
+// ------------------------------------------------------------------------
+
+// Measures the time taken to execute the "myIpAddress()" javascript binding.
+EVENT_TYPE(PAC_JAVASCRIPT_MY_IP_ADDRESS)
+
+// Measures the time taken to execute the "myIpAddressEx()" javascript binding.
+EVENT_TYPE(PAC_JAVASCRIPT_MY_IP_ADDRESS_EX)
+
+// Measures the time taken to execute the "dnsResolve()" javascript binding.
+EVENT_TYPE(PAC_JAVASCRIPT_DNS_RESOLVE)
+
+// Measures the time taken to execute the "dnsResolveEx()" javascript binding.
+EVENT_TYPE(PAC_JAVASCRIPT_DNS_RESOLVE_EX)
+
+// This event is emitted when a javascript error has been triggered by a
+// PAC script. It contains the following event parameters:
+// {
+// "line_number": <The line number in the PAC script
+// (or -1 if not applicable)>,
+// "message": <The error message>
+// }
+EVENT_TYPE(PAC_JAVASCRIPT_ERROR)
+
+// This event is emitted when a PAC script called alert(). It contains the
+// following event parameters:
+// {
+// "message": <The string of the alert>
+// }
+EVENT_TYPE(PAC_JAVASCRIPT_ALERT)
+
+// Measures the time that a proxy resolve request was stalled waiting for a
+// proxy resolver thread to free-up.
+EVENT_TYPE(WAITING_FOR_PROXY_RESOLVER_THREAD)
+
+// This event is emitted just before a PAC request is bound to a thread. It
+// contains these parameters:
+//
+// {
+// "thread_number": <Identifier for the PAC thread that is going to
+// run this request>
+// }
+EVENT_TYPE(SUBMITTED_TO_RESOLVER_THREAD)
+
+// ------------------------------------------------------------------------
+// ClientSocket
+// ------------------------------------------------------------------------
+
+// The start/end of a TCP connect(). This corresponds with a call to
+// TCPClientSocket::Connect().
+//
+// The START event contains these parameters:
+//
+// {
+// "address_list": <List of network address strings>
+// }
+//
+// And the END event will contain the following parameters on failure:
+//
+// {
+// "net_error": <Net integer error code>
+// }
+EVENT_TYPE(TCP_CONNECT)
+
+// Nested within TCP_CONNECT, there may be multiple attempts to connect
+// to the individual addresses. The START event will describe the
+// address the attempt is for:
+//
+// {
+// "address": <String of the network address>
+// }
+//
+// And the END event will contain the system error code if it failed:
+//
+// {
+// "os_error": <Integer error code the operating system returned>
+// }
+EVENT_TYPE(TCP_CONNECT_ATTEMPT)
+
+// Marks the begin/end of a socket (TCP/SOCKS/SSL).
+EVENT_TYPE(SOCKET_ALIVE)
+
+// This event is logged to the socket stream whenever the socket is
+// acquired/released via a ClientSocketHandle.
+//
+// The BEGIN phase contains the following parameters:
+//
+// {
+// "source_dependency": <Source identifier for the controlling entity>
+// }
+EVENT_TYPE(SOCKET_IN_USE)
+
+// The start/end of a SOCKS connect().
+EVENT_TYPE(SOCKS_CONNECT)
+
+// The start/end of a SOCKS5 connect().
+EVENT_TYPE(SOCKS5_CONNECT)
+
+// This event is emitted when the SOCKS connect fails because the provided
+// was longer than 255 characters.
+EVENT_TYPE(SOCKS_HOSTNAME_TOO_BIG)
+
+// These events are emitted when insufficient data was read while
+// trying to establish a connection to the SOCKS proxy server
+// (during the greeting phase or handshake phase, respectively).
+EVENT_TYPE(SOCKS_UNEXPECTEDLY_CLOSED_DURING_GREETING)
+EVENT_TYPE(SOCKS_UNEXPECTEDLY_CLOSED_DURING_HANDSHAKE)
+
+// This event indicates that a bad version number was received in the
+// proxy server's response. The extra parameters show its value:
+// {
+// "version": <Integer version number in the response>
+// }
+EVENT_TYPE(SOCKS_UNEXPECTED_VERSION)
+
+// This event indicates that the SOCKS proxy server returned an error while
+// trying to create a connection. The following parameters will be attached
+// to the event:
+// {
+// "error_code": <Integer error code returned by the server>
+// }
+EVENT_TYPE(SOCKS_SERVER_ERROR)
+
+// This event indicates that the SOCKS proxy server asked for an authentication
+// method that we don't support. The following parameters are attached to the
+// event:
+// {
+// "method": <Integer method code>
+// }
+EVENT_TYPE(SOCKS_UNEXPECTED_AUTH)
+
+// This event indicates that the SOCKS proxy server's response indicated an
+// address type which we are not prepared to handle.
+// The following parameters are attached to the event:
+// {
+// "address_type": <Integer code for the address type>
+// }
+EVENT_TYPE(SOCKS_UNKNOWN_ADDRESS_TYPE)
+
+// The start/end of a SSL connect().
+EVENT_TYPE(SSL_CONNECT)
+
+// The specified number of bytes were sent on the socket.
+// The following parameters are attached:
+// {
+// "num_bytes": <Number of bytes that were just sent>
+// }
+EVENT_TYPE(SOCKET_BYTES_SENT)
+
+// The specified number of bytes were received on the socket.
+// The following parameters are attached:
+// {
+// "num_bytes": <Number of bytes that were just sent>
+// }
+EVENT_TYPE(SOCKET_BYTES_RECEIVED)
+
+// ------------------------------------------------------------------------
+// ClientSocketPoolBase::ConnectJob
+// ------------------------------------------------------------------------
+
+// The start/end of a ConnectJob.
+EVENT_TYPE(SOCKET_POOL_CONNECT_JOB)
+
+// The start/end of the ConnectJob::Connect().
+//
+// The BEGIN phase has these parameters:
+//
+// {
+// "group_name": <The group name for the socket request.>
+// }
+EVENT_TYPE(SOCKET_POOL_CONNECT_JOB_CONNECT)
+
+// This event is logged whenever the ConnectJob gets a new socket
+// association. The event parameters point to that socket:
+//
+// {
+// "source_dependency": <The source identifier for the new socket.>
+// }
+EVENT_TYPE(CONNECT_JOB_SET_SOCKET)
+
+// Whether the connect job timed out.
+EVENT_TYPE(SOCKET_POOL_CONNECT_JOB_TIMED_OUT)
+
+// ------------------------------------------------------------------------
+// ClientSocketPoolBaseHelper
+// ------------------------------------------------------------------------
+
+// The start/end of a client socket pool request for a socket.
+EVENT_TYPE(SOCKET_POOL)
+
+// The request stalled because there are too many sockets in the pool.
+EVENT_TYPE(SOCKET_POOL_STALLED_MAX_SOCKETS)
+
+// The request stalled because there are too many sockets in the group.
+EVENT_TYPE(SOCKET_POOL_STALLED_MAX_SOCKETS_PER_GROUP)
+
+// Indicates that we reused an existing socket. Attached to the event are
+// the parameters:
+// {
+// "idle_ms": <The number of milliseconds the socket was sitting idle for>
+// }
+EVENT_TYPE(SOCKET_POOL_REUSED_AN_EXISTING_SOCKET)
+
+// This event simply describes the host:port that were requested from the
+// socket pool. Its parameters are:
+// {
+// "host_and_port": <String encoding the host and port>
+// }
+EVENT_TYPE(TCP_CLIENT_SOCKET_POOL_REQUESTED_SOCKET)
+
+
+// A backup socket is created due to slow connect
+EVENT_TYPE(SOCKET_BACKUP_CREATED)
+
+// This event is sent when a connect job is eventually bound to a request
+// (because of late binding and socket backup jobs, we don't assign the job to
+// a request until it has completed).
+//
+// The event parameters are:
+// {
+// "source_dependency": <Source identifer for the connect job we are
+// bound to>
+// }
+EVENT_TYPE(SOCKET_POOL_BOUND_TO_CONNECT_JOB)
+
+// Identifies the NetLog::Source() for the Socket assigned to the pending
+// request. The event parameters are:
+// {
+// "source_dependency": <Source identifier for the socket we acquired>
+// }
+EVENT_TYPE(SOCKET_POOL_BOUND_TO_SOCKET)
+
+// ------------------------------------------------------------------------
+// URLRequest
+// ------------------------------------------------------------------------
+
+// Measures the time it took a URLRequestJob to start. For the most part this
+// corresponds with the time between URLRequest::Start() and
+// URLRequest::ResponseStarted(), however it is also repeated for every
+// redirect, and every intercepted job that handles the request.
+//
+// For the BEGIN phase, the following parameters are attached:
+// {
+// "url": <String of URL being loaded>,
+// "method": <The method ("POST" or "GET" or "HEAD" etc..)>,
+// "load_flags": <Numeric value of the combined load flags>
+// }
+//
+// For the END phase, if there was an error, the following parameters are
+// attached:
+// {
+// "net_error": <Net error code of the failure>
+// }
+EVENT_TYPE(URL_REQUEST_START_JOB)
+
+// This event is sent once a URLRequest receives a redirect. The parameters
+// attached to the event are:
+// {
+// "location": <The URL that was redirected to>
+// }
+EVENT_TYPE(URL_REQUEST_REDIRECTED)
+
+// ------------------------------------------------------------------------
+// HttpCache
+// ------------------------------------------------------------------------
+
+// Measures the time while opening a disk cache entry.
+EVENT_TYPE(HTTP_CACHE_OPEN_ENTRY)
+
+// Measures the time while creating a disk cache entry.
+EVENT_TYPE(HTTP_CACHE_CREATE_ENTRY)
+
+// Measures the time while deleting a disk cache entry.
+EVENT_TYPE(HTTP_CACHE_DOOM_ENTRY)
+
+// Measures the time while reading the response info from a disk cache entry.
+EVENT_TYPE(HTTP_CACHE_READ_INFO)
+
+// Measures the time that an HttpCache::Transaction is stalled waiting for
+// the cache entry to become available (for example if we are waiting for
+// exclusive access to an existing entry).
+EVENT_TYPE(HTTP_CACHE_WAITING)
+
+// ------------------------------------------------------------------------
+// HttpNetworkTransaction
+// ------------------------------------------------------------------------
+
+// Measures the time taken to send the tunnel request to the server.
+EVENT_TYPE(HTTP_TRANSACTION_TUNNEL_SEND_REQUEST)
+
+// This event is sent for a tunnel request.
+// The following parameters are attached:
+// {
+// "line": <The HTTP request line, CRLF terminated>,
+// "headers": <The list of header:value pairs>
+// }
+EVENT_TYPE(HTTP_TRANSACTION_SEND_TUNNEL_HEADERS)
+
+// Measures the time to read the tunnel response headers from the server.
+EVENT_TYPE(HTTP_TRANSACTION_TUNNEL_READ_HEADERS)
+
+// This event is sent on receipt of the HTTP response headers to a tunnel
+// request.
+// The following parameters are attached:
+// {
+// "headers": <The list of header:value pairs>
+// }
+EVENT_TYPE(HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS)
+
+// Measures the time taken to send the request to the server.
+EVENT_TYPE(HTTP_TRANSACTION_SEND_REQUEST)
+
+// This event is sent for a HTTP request.
+// The following parameters are attached:
+// {
+// "line": <The HTTP request line, CRLF terminated>,
+// "headers": <The list of header:value pairs>
+// }
+EVENT_TYPE(HTTP_TRANSACTION_SEND_REQUEST_HEADERS)
+
+// Measures the time to read HTTP response headers from the server.
+EVENT_TYPE(HTTP_TRANSACTION_READ_HEADERS)
+
+// This event is sent on receipt of the HTTP response headers.
+// The following parameters are attached:
+// {
+// "headers": <The list of header:value pairs>
+// }
+EVENT_TYPE(HTTP_TRANSACTION_READ_RESPONSE_HEADERS)
+
+// Measures the time to read the entity body from the server.
+EVENT_TYPE(HTTP_TRANSACTION_READ_BODY)
+
+// Measures the time taken to read the response out of the socket before
+// restarting for authentication, on keep alive connections.
+EVENT_TYPE(HTTP_TRANSACTION_DRAIN_BODY_FOR_AUTH_RESTART)
+
+// ------------------------------------------------------------------------
+// SpdyNetworkTransaction
+// ------------------------------------------------------------------------
+
+// Measures the time taken to get a spdy stream.
+EVENT_TYPE(SPDY_TRANSACTION_INIT_CONNECTION)
+
+// Measures the time taken to send the request to the server.
+EVENT_TYPE(SPDY_TRANSACTION_SEND_REQUEST)
+
+// Measures the time to read HTTP response headers from the server.
+EVENT_TYPE(SPDY_TRANSACTION_READ_HEADERS)
+
+// Measures the time to read the entity body from the server.
+EVENT_TYPE(SPDY_TRANSACTION_READ_BODY)
+
+// ------------------------------------------------------------------------
+// SpdySession
+// ------------------------------------------------------------------------
+
+// The start/end of a SpdySession.
+EVENT_TYPE(SPDY_SESSION)
+
+// On sending a SPDY SETTINGS frame.
+// The following parameters are attached:
+// {
+// "settings": <The list of setting id:value pairs>
+// }
+EVENT_TYPE(SPDY_SESSION_SEND_SETTINGS)
+
+// Receipt of a SPDY SETTINGS frame.
+// The following parameters are attached:
+// {
+// "settings": <The list of setting id:value pairs>
+// }
+EVENT_TYPE(SPDY_SESSION_RECV_SETTINGS)
+
+// Receipt of a SPDY GOAWAY frame.
+// The following parameters are attached:
+// {
+// "last_accepted_stream_id": <Last stream id accepted by the server, duh>
+// }
+EVENT_TYPE(SPDY_SESSION_GOAWAY)
+
+// This event is sent for a SPDY SYN_STREAM pushed by the server, but no
+// URLRequest has requested it yet.
+// The following parameters are attached:
+// {
+// "flags": <The control frame flags>
+// "headers": <The list of header:value pairs>
+// "id": <The stream id>
+// }
+EVENT_TYPE(SPDY_SESSION_PUSHED_SYN_STREAM)
+
+// ------------------------------------------------------------------------
+// SpdyStream
+// ------------------------------------------------------------------------
+
+// This event is sent for a SPDY SYN_STREAM.
+// The following parameters are attached:
+// {
+// "flags": <The control frame flags>,
+// "headers": <The list of header:value pairs>,
+// "id": <The stream id>
+// }
+EVENT_TYPE(SPDY_STREAM_SYN_STREAM)
+
+// This event is sent for a SPDY SYN_STREAM pushed by the server, where a
+// URLRequest is already waiting for the stream.
+// The following parameters are attached:
+// {
+// "flags": <The control frame flags>
+// "headers": <The list of header:value pairs>
+// "id": <The stream id>
+// }
+EVENT_TYPE(SPDY_STREAM_PUSHED_SYN_STREAM)
+
+// Measures the time taken to send headers on a stream.
+EVENT_TYPE(SPDY_STREAM_SEND_HEADERS)
+
+// Measures the time taken to send the body (e.g. a POST) on a stream.
+EVENT_TYPE(SPDY_STREAM_SEND_BODY)
+
+// This event is sent for a SPDY SYN_REPLY.
+// The following parameters are attached:
+// {
+// "flags": <The control frame flags>,
+// "headers": <The list of header:value pairs>,
+// "id": <The stream id>
+// }
+EVENT_TYPE(SPDY_STREAM_SYN_REPLY)
+
+// Measures the time taken to read headers on a stream.
+EVENT_TYPE(SPDY_STREAM_READ_HEADERS)
+
+// Measures the time taken to read the body on a stream.
+EVENT_TYPE(SPDY_STREAM_READ_BODY)
+
+// Logs that a stream attached to a pushed stream.
+EVENT_TYPE(SPDY_STREAM_ADOPTED_PUSH_STREAM)
+
+// The receipt of a RST_STREAM
+// The following parameters are attached:
+// {
+// "status": <The reason for the RST_STREAM>
+// }
+EVENT_TYPE(SPDY_STREAM_RST_STREAM)
+
+// ------------------------------------------------------------------------
+// HttpStreamParser
+// ------------------------------------------------------------------------
+
+// Measures the time to read HTTP response headers from the server.
+EVENT_TYPE(HTTP_STREAM_PARSER_READ_HEADERS)
+
+// ------------------------------------------------------------------------
+// SocketStream
+// ------------------------------------------------------------------------
+
+// Measures the time between SocketStream::Connect() and
+// SocketStream::DidEstablishConnection()
+//
+// For the BEGIN phase, the following parameters are attached:
+// {
+// "url": <String of URL being loaded>
+// }
+//
+// For the END phase, if there was an error, the following parameters are
+// attached:
+// {
+// "net_error": <Net error code of the failure>
+// }
+EVENT_TYPE(SOCKET_STREAM_CONNECT)
+
+// A message sent on the SocketStream.
+EVENT_TYPE(SOCKET_STREAM_SENT)
+
+// A message received on the SocketStream.
+EVENT_TYPE(SOCKET_STREAM_RECEIVED)
+
+// ------------------------------------------------------------------------
+// SOCKS5ClientSocket
+// ------------------------------------------------------------------------
+
+// The time spent sending the "greeting" to the SOCKS server.
+EVENT_TYPE(SOCKS5_GREET_WRITE)
+
+// The time spent waiting for the "greeting" response from the SOCKS server.
+EVENT_TYPE(SOCKS5_GREET_READ)
+
+// The time spent sending the CONNECT request to the SOCKS server.
+EVENT_TYPE(SOCKS5_HANDSHAKE_WRITE)
+
+// The time spent waiting for the response to the CONNECT request.
+EVENT_TYPE(SOCKS5_HANDSHAKE_READ)
+
+// ------------------------------------------------------------------------
+// HTTP Authentication
+// ------------------------------------------------------------------------
+
+// The time spent authenticating to the proxy.
+EVENT_TYPE(AUTH_PROXY)
+
+// The time spent authentication to the server.
+EVENT_TYPE(AUTH_SERVER)
+
+// ------------------------------------------------------------------------
+// Global events
+// ------------------------------------------------------------------------
+// These are events which are not grouped by source id, as they have no
+// context.
+
+// This event is emitted whenever NetworkChangeNotifier determines that the
+// underlying network has changed.
+EVENT_TYPE(NETWORK_IP_ADDRESSSES_CHANGED)
diff --git a/net/base/net_log_source_type_list.h b/net/base/net_log_source_type_list.h
new file mode 100644
index 0000000..d416f3f
--- /dev/null
+++ b/net/base/net_log_source_type_list.h
@@ -0,0 +1,17 @@
+// 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.
+
+// NOTE: No header guards are used, since this file is intended to be expanded
+// directly within a block where the SOURCE_TYPE macro is defined.
+
+// Used for global events which don't correspond to a particular entity.
+SOURCE_TYPE(NONE, 0)
+SOURCE_TYPE(URL_REQUEST, 1)
+SOURCE_TYPE(SOCKET_STREAM, 2)
+SOURCE_TYPE(INIT_PROXY_RESOLVER, 3)
+SOURCE_TYPE(CONNECT_JOB, 4)
+SOURCE_TYPE(SOCKET, 5)
+SOURCE_TYPE(SPDY_SESSION, 6)
+
+SOURCE_TYPE(COUNT, 7) // Always keep this as the last entry.
diff --git a/net/base/net_log_unittest.h b/net/base/net_log_unittest.h
new file mode 100644
index 0000000..376ceac
--- /dev/null
+++ b/net/base/net_log_unittest.h
@@ -0,0 +1,129 @@
+// Copyright (c) 2009 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.
+
+#ifndef NET_BASE_NET_LOG_UNITTEST_H_
+#define NET_BASE_NET_LOG_UNITTEST_H_
+
+#include <cstddef>
+#include <vector>
+#include "net/base/capturing_net_log.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+// Create a timestamp with internal value of |t| milliseconds from the epoch.
+inline base::TimeTicks MakeTime(int t) {
+ base::TimeTicks ticks; // initialized to 0.
+ ticks += base::TimeDelta::FromMilliseconds(t);
+ return ticks;
+}
+
+inline ::testing::AssertionResult LogContainsEventHelper(
+ const CapturingNetLog::EntryList& entries,
+ int i, // Negative indices are reverse indices.
+ const base::TimeTicks& expected_time,
+ bool check_time,
+ NetLog::EventType expected_event,
+ NetLog::EventPhase expected_phase) {
+ // Negative indices are reverse indices.
+ size_t j = (i < 0) ? entries.size() + i : i;
+ if (j >= entries.size())
+ return ::testing::AssertionFailure() << j << " is out of bounds.";
+ const CapturingNetLog::Entry& entry = entries[j];
+ if (expected_event != entry.type) {
+ return ::testing::AssertionFailure()
+ << "Actual event: " << NetLog::EventTypeToString(entry.type)
+ << ". Expected event: " << NetLog::EventTypeToString(expected_event)
+ << ".";
+ }
+ if (expected_phase != entry.phase) {
+ return ::testing::AssertionFailure()
+ << "Actual phase: " << entry.phase
+ << ". Expected phase: " << expected_phase << ".";
+ }
+ if (check_time) {
+ if (expected_time != entry.time) {
+ return ::testing::AssertionFailure()
+ << "Actual time: " << entry.time.ToInternalValue()
+ << ". Expected time: " << expected_time.ToInternalValue()
+ << ".";
+ }
+ }
+ return ::testing::AssertionSuccess();
+}
+
+inline ::testing::AssertionResult LogContainsEventAtTime(
+ const CapturingNetLog::EntryList& log,
+ int i, // Negative indices are reverse indices.
+ const base::TimeTicks& expected_time,
+ NetLog::EventType expected_event,
+ NetLog::EventPhase expected_phase) {
+ return LogContainsEventHelper(log, i, expected_time, true,
+ expected_event, expected_phase);
+}
+
+// Version without timestamp.
+inline ::testing::AssertionResult LogContainsEvent(
+ const CapturingNetLog::EntryList& log,
+ int i, // Negative indices are reverse indices.
+ NetLog::EventType expected_event,
+ NetLog::EventPhase expected_phase) {
+ return LogContainsEventHelper(log, i, base::TimeTicks(), false,
+ expected_event, expected_phase);
+}
+
+// Version for PHASE_BEGIN (and no timestamp).
+inline ::testing::AssertionResult LogContainsBeginEvent(
+ const CapturingNetLog::EntryList& log,
+ int i, // Negative indices are reverse indices.
+ NetLog::EventType expected_event) {
+ return LogContainsEvent(log, i, expected_event, NetLog::PHASE_BEGIN);
+}
+
+// Version for PHASE_END (and no timestamp).
+inline ::testing::AssertionResult LogContainsEndEvent(
+ const CapturingNetLog::EntryList& log,
+ int i, // Negative indices are reverse indices.
+ NetLog::EventType expected_event) {
+ return LogContainsEvent(log, i, expected_event, NetLog::PHASE_END);
+}
+
+inline ::testing::AssertionResult LogContainsEntryWithType(
+ const CapturingNetLog::EntryList& entries,
+ int i, // Negative indices are reverse indices.
+ NetLog::EventType type) {
+ // Negative indices are reverse indices.
+ size_t j = (i < 0) ? entries.size() + i : i;
+ if (j >= entries.size())
+ return ::testing::AssertionFailure() << j << " is out of bounds.";
+ const CapturingNetLog::Entry& entry = entries[j];
+ if (entry.type != type)
+ return ::testing::AssertionFailure() << "Type does not match.";
+ return ::testing::AssertionSuccess();
+}
+
+
+// Expect that the log contains an event, but don't care about where
+// as long as the index where it is found is greater than min_index.
+// Returns the position where the event was found.
+inline size_t ExpectLogContainsSomewhere(
+ const CapturingNetLog::EntryList& entries,
+ size_t min_index,
+ NetLog::EventType expected_event,
+ NetLog::EventPhase expected_phase) {
+ size_t i = 0;
+ for (; i < entries.size(); ++i) {
+ const CapturingNetLog::Entry& entry = entries[i];
+ if (entry.type == expected_event &&
+ entry.phase == expected_phase)
+ break;
+ }
+ EXPECT_LT(i, entries.size());
+ EXPECT_GE(i, min_index);
+ return i;
+}
+
+} // namespace net
+
+#endif // NET_BASE_NET_LOG_UNITTEST_H_
diff --git a/net/base/net_resources.grd b/net/base/net_resources.grd
index f64dd60..327778a 100644
--- a/net/base/net_resources.grd
+++ b/net/base/net_resources.grd
@@ -4,8 +4,8 @@
<output filename="grit/net_resources.h" type="rc_header">
<emit emit_type='prepend'></emit>
</output>
- <output filename="net_resources.rc" type="rc_all" />
<output filename="net_resources.pak" type="data_package" />
+ <output filename="net_resources.rc" type="rc_all" />
</outputs>
<translations />
<release seq="1">
diff --git a/net/base/net_test_suite.h b/net/base/net_test_suite.h
index af58ca7..cc4ed2a 100644
--- a/net/base/net_test_suite.h
+++ b/net/base/net_test_suite.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -25,6 +25,8 @@
// TestSuite::Initialize(). TestSuite::Initialize() performs some global
// initialization that can only be done once.
void InitializeTestThread() {
+ network_change_notifier_.reset(net::NetworkChangeNotifier::CreateMock());
+
host_resolver_proc_ = new net::RuleBasedHostResolverProc(NULL);
scoped_host_resolver_proc_.Init(host_resolver_proc_.get());
// In case any attempts are made to resolve host names, force them all to
@@ -44,6 +46,7 @@
}
private:
+ scoped_ptr<net::NetworkChangeNotifier> network_change_notifier_;
scoped_ptr<MessageLoop> message_loop_;
scoped_refptr<net::RuleBasedHostResolverProc> host_resolver_proc_;
net::ScopedDefaultHostResolverProc scoped_host_resolver_proc_;
diff --git a/net/base/net_util.cc b/net/base/net_util.cc
index 43fa906..c7b6a6f 100644
--- a/net/base/net_util.cc
+++ b/net/base/net_util.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -21,15 +21,19 @@
#include <ws2tcpip.h>
#include <wspiapi.h> // Needed for Win2k compat.
#elif defined(OS_POSIX)
-#include <netdb.h>
-#include <sys/socket.h>
#include <fcntl.h>
+#include <ifaddrs.h>
+#include <netdb.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
#endif
#include "base/base64.h"
#include "base/basictypes.h"
#include "base/file_path.h"
#include "base/file_util.h"
+#include "base/histogram.h"
#include "base/i18n/file_util_icu.h"
#include "base/i18n/icu_string_conversions.h"
#include "base/i18n/time_formatting.h"
@@ -46,10 +50,13 @@
#include "base/sys_string_conversions.h"
#include "base/time.h"
#include "base/utf_offset_string_conversions.h"
+#include "base/utf_string_conversions.h"
#include "grit/net_resources.h"
#include "googleurl/src/gurl.h"
#include "googleurl/src/url_canon.h"
+#include "googleurl/src/url_canon_ip.h"
#include "googleurl/src/url_parse.h"
+#include "net/base/dns_util.h"
#include "net/base/escape.h"
#include "net/base/net_module.h"
#if defined(OS_WIN)
@@ -128,6 +135,13 @@
3659, // apple-sasl / PasswordServer
4045, // lockd
6000, // X11
+ 6665, // Alternate IRC [Apple addition]
+ 6666, // Alternate IRC [Apple addition]
+ 6667, // Standard IRC [Apple addition]
+ 6668, // Alternate IRC [Apple addition]
+ 6669, // Alternate IRC [Apple addition]
+ 0xFFFF, // Used to block all invalid port numbers (see
+ // third_party/WebKit/WebCore/platform/KURLGoogle.cpp, port())
};
// FTP overrides the following restricted ports.
@@ -257,8 +271,13 @@
bool DecodeWord(const std::string& encoded_word,
const std::string& referrer_charset,
- bool *is_rfc2047,
+ bool* is_rfc2047,
std::string* output) {
+ *is_rfc2047 = false;
+ output->clear();
+ if (encoded_word.empty())
+ return true;
+
if (!IsStringASCII(encoded_word)) {
// Try UTF-8, referrer_charset and the native OS default charset in turn.
if (IsStringUTF8(encoded_word)) {
@@ -274,7 +293,7 @@
*output = WideToUTF8(base::SysNativeMBToWide(encoded_word));
}
}
- *is_rfc2047 = false;
+
return true;
}
@@ -697,10 +716,30 @@
return false;
}
+// If |component| is valid, its begin is incremented by |delta|.
+void AdjustComponent(int delta, url_parse::Component* component) {
+ if (!component->is_valid())
+ return;
+
+ DCHECK(delta >= 0 || component->begin >= -delta);
+ component->begin += delta;
+}
+
+// Adjusts all the components of |parsed| by |delta|, except for the scheme.
+void AdjustComponents(int delta, url_parse::Parsed* parsed) {
+ AdjustComponent(delta, &(parsed->username));
+ AdjustComponent(delta, &(parsed->password));
+ AdjustComponent(delta, &(parsed->host));
+ AdjustComponent(delta, &(parsed->port));
+ AdjustComponent(delta, &(parsed->path));
+ AdjustComponent(delta, &(parsed->query));
+ AdjustComponent(delta, &(parsed->ref));
+}
+
// Helper for FormatUrl().
std::wstring FormatViewSourceUrl(const GURL& url,
const std::wstring& languages,
- bool omit_username_password,
+ net::FormatUrlTypes format_types,
UnescapeRule::Type unescape_rules,
url_parse::Parsed* new_parsed,
size_t* prefix_end,
@@ -715,8 +754,7 @@
size_t* temp_offset_ptr = (*offset_for_adjustment < kViewSourceLengthPlus1) ?
NULL : &temp_offset;
std::wstring result = net::FormatUrl(real_url, languages,
- omit_username_password, unescape_rules, new_parsed, prefix_end,
- temp_offset_ptr);
+ format_types, unescape_rules, new_parsed, prefix_end, temp_offset_ptr);
result.insert(0, kWideViewSource);
// Adjust position values.
@@ -727,20 +765,7 @@
new_parsed->scheme.begin = 0;
new_parsed->scheme.len = kViewSourceLengthPlus1 - 1;
}
- if (new_parsed->username.is_nonempty())
- new_parsed->username.begin += kViewSourceLengthPlus1;
- if (new_parsed->password.is_nonempty())
- new_parsed->password.begin += kViewSourceLengthPlus1;
- if (new_parsed->host.is_nonempty())
- new_parsed->host.begin += kViewSourceLengthPlus1;
- if (new_parsed->port.is_nonempty())
- new_parsed->port.begin += kViewSourceLengthPlus1;
- if (new_parsed->path.is_nonempty())
- new_parsed->path.begin += kViewSourceLengthPlus1;
- if (new_parsed->query.is_nonempty())
- new_parsed->query.begin += kViewSourceLengthPlus1;
- if (new_parsed->ref.is_nonempty())
- new_parsed->ref.begin += kViewSourceLengthPlus1;
+ AdjustComponents(kViewSourceLengthPlus1, new_parsed);
if (prefix_end)
*prefix_end += kViewSourceLengthPlus1;
if (temp_offset_ptr) {
@@ -754,6 +779,13 @@
namespace net {
+const FormatUrlType kFormatUrlOmitNothing = 0;
+const FormatUrlType kFormatUrlOmitUsernamePassword = 1 << 0;
+const FormatUrlType kFormatUrlOmitHTTP = 1 << 1;
+const FormatUrlType kFormatUrlOmitTrailingSlashOnBareHostname = 1 << 2;
+const FormatUrlType kFormatUrlOmitAll = kFormatUrlOmitUsernamePassword |
+ kFormatUrlOmitHTTP | kFormatUrlOmitTrailingSlashOnBareHostname;
+
std::set<int> explicitly_allowed_ports;
// Appends the substring |in_component| inside of the URL |spec| to |output|,
@@ -956,7 +988,8 @@
return (c >= '0') && (c <= '9');
}
-bool IsCanonicalizedHostCompliant(const std::string& host) {
+bool IsCanonicalizedHostCompliant(const std::string& host,
+ const std::string& desired_tld) {
if (host.empty())
return false;
@@ -986,7 +1019,8 @@
}
}
- return most_recent_component_started_alpha;
+ return most_recent_component_started_alpha ||
+ (!desired_tld.empty() && IsHostCharAlpha(desired_tld[0]));
}
std::string GetDirectoryListingEntry(const string16& name,
@@ -1051,7 +1085,7 @@
}
const std::string filename_from_cd = GetFileNameFromCD(content_disposition,
- referrer_charset);
+ referrer_charset);
#if defined(OS_WIN)
FilePath::StringType filename = UTF8ToWide(filename_from_cd);
#elif defined(OS_POSIX)
@@ -1072,14 +1106,35 @@
const std::string unescaped_url_filename = UnescapeURLComponent(
url.ExtractFileName(),
UnescapeRule::SPACES | UnescapeRule::URL_SPECIAL_CHARS);
+
+ // The URL's path should be escaped UTF-8, but may not be.
+ std::string decoded_filename = unescaped_url_filename;
+ if (!IsStringASCII(decoded_filename)) {
+ bool ignore;
+ // TODO(jshin): this is probably not robust enough. To be sure, we
+ // need encoding detection.
+ DecodeWord(unescaped_url_filename, referrer_charset, &ignore,
+ &decoded_filename);
+ }
+
#if defined(OS_WIN)
- filename = UTF8ToWide(unescaped_url_filename);
+ filename = UTF8ToWide(decoded_filename);
#elif defined(OS_POSIX)
- filename = unescaped_url_filename;
+ filename = decoded_filename;
#endif
}
}
+#if defined(OS_WIN)
+ { // Handle CreateFile() stripping trailing dots and spaces on filenames
+ // http://support.microsoft.com/kb/115827
+ std::string::size_type pos = filename.find_last_not_of(L" .");
+ if (pos == std::string::npos)
+ filename.resize(0);
+ else
+ filename.resize(++pos);
+ }
+#endif
// Trim '.' once more.
TrimString(filename, FILE_PATH_LITERAL("."), &filename);
@@ -1147,7 +1202,7 @@
#elif defined(OS_POSIX)
int flags = fcntl(fd, F_GETFL, 0);
if (-1 == flags)
- flags = 0;
+ return flags;
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
#endif
}
@@ -1238,6 +1293,21 @@
return std::string(buffer);
}
+std::string NetAddressToStringWithPort(const struct addrinfo* net_address) {
+ std::string ip_address_string = NetAddressToString(net_address);
+ if (ip_address_string.empty())
+ return std::string(); // Failed.
+
+ int port = GetPortFromAddrinfo(net_address);
+
+ if (ip_address_string.find(':') != std::string::npos) {
+ // Surround with square brackets to avoid ambiguity.
+ return StringPrintf("[%s]:%d", ip_address_string.c_str(), port);
+ }
+
+ return StringPrintf("%s:%d", ip_address_string.c_str(), port);
+}
+
std::string GetHostName() {
#if defined(OS_WIN)
EnsureWinsockInit();
@@ -1256,13 +1326,18 @@
void GetIdentityFromURL(const GURL& url,
std::wstring* username,
std::wstring* password) {
- UnescapeRule::Type flags = UnescapeRule::SPACES;
+ UnescapeRule::Type flags =
+ UnescapeRule::SPACES | UnescapeRule::URL_SPECIAL_CHARS;
*username = UTF16ToWideHack(UnescapeAndDecodeUTF8URLComponent(url.username(),
flags, NULL));
*password = UTF16ToWideHack(UnescapeAndDecodeUTF8URLComponent(url.password(),
flags, NULL));
}
+std::string GetHostOrSpecFromURL(const GURL& url) {
+ return url.has_host() ? net::TrimEndingDot(url.host()) : url.spec();
+}
+
void AppendFormattedHost(const GURL& url,
const std::wstring& languages,
std::wstring* output,
@@ -1349,7 +1424,7 @@
std::wstring FormatUrl(const GURL& url,
const std::wstring& languages,
- bool omit_username_password,
+ FormatUrlTypes format_types,
UnescapeRule::Type unescape_rules,
url_parse::Parsed* new_parsed,
size_t* prefix_end,
@@ -1357,6 +1432,8 @@
url_parse::Parsed parsed_temp;
if (!new_parsed)
new_parsed = &parsed_temp;
+ else
+ *new_parsed = url_parse::Parsed();
size_t offset_temp = std::wstring::npos;
if (!offset_for_adjustment)
offset_for_adjustment = &offset_temp;
@@ -1374,11 +1451,11 @@
// Special handling for view-source:. Don't use chrome::kViewSourceScheme
// because this library shouldn't depend on chrome.
const char* const kViewSource = "view-source";
+ // Reject "view-source:view-source:..." to avoid deep recursion.
const char* const kViewSourceTwice = "view-source:view-source:";
- // Rejects view-source:view-source:... to avoid deep recursive call.
if (url.SchemeIs(kViewSource) &&
!StartsWithASCII(url.possibly_invalid_spec(), kViewSourceTwice, false)) {
- return FormatViewSourceUrl(url, languages, omit_username_password,
+ return FormatViewSourceUrl(url, languages, format_types,
unescape_rules, new_parsed, prefix_end, offset_for_adjustment);
}
@@ -1395,9 +1472,22 @@
spec.begin() + parsed.CountCharactersBefore(url_parse::Parsed::USERNAME,
true),
std::back_inserter(url_string));
+
+ const wchar_t kHTTP[] = L"http://";
+ const char kFTP[] = "ftp.";
+ // URLFixerUpper::FixupURL() treats "ftp.foo.com" as ftp://ftp.foo.com. This
+ // means that if we trim "http://" off a URL whose host starts with "ftp." and
+ // the user inputs this into any field subject to fixup (which is basically
+ // all input fields), the meaning would be changed. (In fact, often the
+ // formatted URL is directly pre-filled into an input field.) For this reason
+ // we avoid stripping "http://" in this case.
+ bool omit_http =
+ (format_types & kFormatUrlOmitHTTP) && (url_string == kHTTP) &&
+ (url.host().compare(0, arraysize(kFTP) - 1, kFTP) != 0);
+
new_parsed->scheme = parsed.scheme;
- if (omit_username_password) {
+ if ((format_types & kFormatUrlOmitUsernamePassword) != 0) {
// Remove the username and password fields. We don't want to display those
// to the user since they can be used for attacks,
// e.g. "http://google.com:search@evil.ru/"
@@ -1433,14 +1523,12 @@
} else {
AppendFormattedComponent(spec, parsed.username, unescape_rules, &url_string,
&new_parsed->username, offset_for_adjustment);
- if (parsed.password.is_valid()) {
+ if (parsed.password.is_valid())
url_string.push_back(':');
- }
AppendFormattedComponent(spec, parsed.password, unescape_rules, &url_string,
&new_parsed->password, offset_for_adjustment);
- if (parsed.username.is_valid() || parsed.password.is_valid()) {
+ if (parsed.username.is_valid() || parsed.password.is_valid())
url_string.push_back('@');
- }
}
if (prefix_end)
*prefix_end = static_cast<size_t>(url_string.length());
@@ -1460,8 +1548,11 @@
}
// Path and query both get the same general unescape & convert treatment.
- AppendFormattedComponent(spec, parsed.path, unescape_rules, &url_string,
- &new_parsed->path, offset_for_adjustment);
+ if (!(format_types & kFormatUrlOmitTrailingSlashOnBareHostname) ||
+ !CanStripTrailingSlash(url)) {
+ AppendFormattedComponent(spec, parsed.path, unescape_rules, &url_string,
+ &new_parsed->path, offset_for_adjustment);
+ }
if (parsed.query.is_valid())
url_string.push_back('?');
AppendFormattedComponent(spec, parsed.query, unescape_rules, &url_string,
@@ -1497,9 +1588,37 @@
}
}
+ // If we need to strip out http do it after the fact. This way we don't need
+ // to worry about how offset_for_adjustment is interpreted.
+ const size_t kHTTPSize = arraysize(kHTTP) - 1;
+ if (omit_http && !url_string.compare(0, kHTTPSize, kHTTP)) {
+ url_string = url_string.substr(kHTTPSize);
+ if (*offset_for_adjustment != std::wstring::npos) {
+ if (*offset_for_adjustment < kHTTPSize)
+ *offset_for_adjustment = std::wstring::npos;
+ else
+ *offset_for_adjustment -= kHTTPSize;
+ }
+ if (prefix_end)
+ *prefix_end -= kHTTPSize;
+
+ // Adjust new_parsed.
+ DCHECK(new_parsed->scheme.is_valid());
+ int delta = -(new_parsed->scheme.len + 3); // +3 for ://.
+ new_parsed->scheme.reset();
+ AdjustComponents(delta, new_parsed);
+ }
+
return url_string;
}
+bool CanStripTrailingSlash(const GURL& url) {
+ // Omit the path only for standard, non-file URLs with nothing but "/" after
+ // the hostname.
+ return url.IsStandard() && !url.SchemeIsFile() && !url.has_query() &&
+ !url.has_ref() && url.path() == "/";
+}
+
GURL SimplifyUrlForRequest(const GURL& url) {
DCHECK(url.is_valid());
GURL::Replacements replacements;
@@ -1538,4 +1657,269 @@
explicitly_allowed_ports = ports;
}
+enum IPv6SupportStatus {
+ IPV6_CANNOT_CREATE_SOCKETS,
+ IPV6_CAN_CREATE_SOCKETS,
+ IPV6_GETIFADDRS_FAILED,
+ IPV6_GLOBAL_ADDRESS_MISSING,
+ IPV6_GLOBAL_ADDRESS_PRESENT,
+ IPV6_INTERFACE_ARRAY_TOO_SHORT,
+ IPV6_SUPPORT_MAX // Bounding values for enumeration.
+};
+
+static void IPv6SupportResults(IPv6SupportStatus result) {
+ static bool run_once = false;
+ if (!run_once) {
+ run_once = true;
+ UMA_HISTOGRAM_ENUMERATION("Net.IPv6Status", result, IPV6_SUPPORT_MAX);
+ } else {
+ UMA_HISTOGRAM_ENUMERATION("Net.IPv6Status_retest", result,
+ IPV6_SUPPORT_MAX);
+ }
+}
+
+// TODO(jar): The following is a simple estimate of IPv6 support. We may need
+// to do a test resolution, and a test connection, to REALLY verify support.
+// static
+bool IPv6Supported() {
+#if defined(OS_POSIX)
+ int test_socket = socket(AF_INET6, SOCK_STREAM, 0);
+ if (test_socket == -1) {
+ IPv6SupportResults(IPV6_CANNOT_CREATE_SOCKETS);
+ return false;
+ }
+ close(test_socket);
+
+ // Check to see if any interface has a IPv6 address.
+ struct ifaddrs* interface_addr = NULL;
+ int rv = getifaddrs(&interface_addr);
+ if (rv != 0) {
+ IPv6SupportResults(IPV6_GETIFADDRS_FAILED);
+ return true; // Don't yet block IPv6.
+ }
+
+ bool found_ipv6 = false;
+ for (struct ifaddrs* interface = interface_addr;
+ interface != NULL;
+ interface = interface->ifa_next) {
+ if (!(IFF_UP & interface->ifa_flags))
+ continue;
+ if (IFF_LOOPBACK & interface->ifa_flags)
+ continue;
+ struct sockaddr* addr = interface->ifa_addr;
+ if (!addr)
+ continue;
+ if (addr->sa_family != AF_INET6)
+ continue;
+ // Safe cast since this is AF_INET6.
+ struct sockaddr_in6* addr_in6 =
+ reinterpret_cast<struct sockaddr_in6*>(addr);
+ struct in6_addr* sin6_addr = &addr_in6->sin6_addr;
+ if (IN6_IS_ADDR_LOOPBACK(sin6_addr) || IN6_IS_ADDR_LINKLOCAL(sin6_addr))
+ continue;
+ found_ipv6 = true;
+ break;
+ }
+ freeifaddrs(interface_addr);
+ if (!found_ipv6) {
+ IPv6SupportResults(IPV6_GLOBAL_ADDRESS_MISSING);
+ return false;
+ }
+
+ IPv6SupportResults(IPV6_GLOBAL_ADDRESS_PRESENT);
+ return true;
+#elif defined(OS_WIN)
+ EnsureWinsockInit();
+ SOCKET test_socket = socket(AF_INET6, SOCK_STREAM, 0);
+ if (test_socket == INVALID_SOCKET) {
+ IPv6SupportResults(IPV6_CANNOT_CREATE_SOCKETS);
+ return false;
+ }
+ closesocket(test_socket);
+
+ // TODO(jar): Bug 40851: The remainder of probe is not working.
+ IPv6SupportResults(IPV6_CAN_CREATE_SOCKETS); // Record status.
+ return true; // Don't disable IPv6 yet.
+
+ // Check to see if any interface has a IPv6 address.
+ // Note: The original IPv6 socket can't be used here, as WSAIoctl() will fail.
+ test_socket = socket(AF_INET, SOCK_STREAM, 0);
+ DCHECK(test_socket != INVALID_SOCKET);
+ INTERFACE_INFO interfaces[128];
+ DWORD bytes_written = 0;
+ int rv = WSAIoctl(test_socket, SIO_GET_INTERFACE_LIST, NULL, 0, interfaces,
+ sizeof(interfaces), &bytes_written, NULL, NULL);
+ closesocket(test_socket);
+
+ if (0 != rv) {
+ if (WSAGetLastError() == WSAEFAULT)
+ IPv6SupportResults(IPV6_INTERFACE_ARRAY_TOO_SHORT);
+ else
+ IPv6SupportResults(IPV6_GETIFADDRS_FAILED);
+ return true; // Don't yet block IPv6.
+ }
+ size_t interface_count = bytes_written / sizeof(interfaces[0]);
+ for (size_t i = 0; i < interface_count; ++i) {
+ INTERFACE_INFO* interface = &interfaces[i];
+ if (!(IFF_UP & interface->iiFlags))
+ continue;
+ if (IFF_LOOPBACK & interface->iiFlags)
+ continue;
+ sockaddr* addr = &interface->iiAddress.Address;
+ if (addr->sa_family != AF_INET6)
+ continue;
+ struct in6_addr* sin6_addr = &interface->iiAddress.AddressIn6.sin6_addr;
+ if (IN6_IS_ADDR_LOOPBACK(sin6_addr) || IN6_IS_ADDR_LINKLOCAL(sin6_addr))
+ continue;
+ IPv6SupportResults(IPV6_GLOBAL_ADDRESS_PRESENT);
+ return true;
+ }
+
+ IPv6SupportResults(IPV6_GLOBAL_ADDRESS_MISSING);
+ return false;
+#else
+ NOTIMPLEMENTED();
+ return true;
+#endif // defined(various platforms)
+}
+
+bool ParseIPLiteralToNumber(const std::string& ip_literal,
+ IPAddressNumber* ip_number) {
+ // |ip_literal| could be either a IPv4 or an IPv6 literal. If it contains
+ // a colon however, it must be an IPv6 address.
+ if (ip_literal.find(':') != std::string::npos) {
+ // GURL expects IPv6 hostnames to be surrounded with brackets.
+ std::string host_brackets = "[" + ip_literal + "]";
+ url_parse::Component host_comp(0, host_brackets.size());
+
+ // Try parsing the hostname as an IPv6 literal.
+ ip_number->resize(16); // 128 bits.
+ return url_canon::IPv6AddressToNumber(host_brackets.data(),
+ host_comp,
+ &(*ip_number)[0]);
+ }
+
+ // Otherwise the string is an IPv4 address.
+ ip_number->resize(4); // 32 bits.
+ url_parse::Component host_comp(0, ip_literal.size());
+ int num_components;
+ url_canon::CanonHostInfo::Family family = url_canon::IPv4AddressToNumber(
+ ip_literal.data(), host_comp, &(*ip_number)[0], &num_components);
+ return family == url_canon::CanonHostInfo::IPV4;
+}
+
+IPAddressNumber ConvertIPv4NumberToIPv6Number(
+ const IPAddressNumber& ipv4_number) {
+ DCHECK(ipv4_number.size() == 4);
+
+ // IPv4-mapped addresses are formed by:
+ // <80 bits of zeros> + <16 bits of ones> + <32-bit IPv4 address>.
+ IPAddressNumber ipv6_number;
+ ipv6_number.reserve(16);
+ ipv6_number.insert(ipv6_number.end(), 10, 0);
+ ipv6_number.push_back(0xFF);
+ ipv6_number.push_back(0xFF);
+ ipv6_number.insert(ipv6_number.end(), ipv4_number.begin(), ipv4_number.end());
+ return ipv6_number;
+}
+
+bool ParseCIDRBlock(const std::string& cidr_literal,
+ IPAddressNumber* ip_number,
+ size_t* prefix_length_in_bits) {
+ // We expect CIDR notation to match one of these two templates:
+ // <IPv4-literal> "/" <number of bits>
+ // <IPv6-literal> "/" <number of bits>
+
+ std::vector<std::string> parts;
+ SplitString(cidr_literal, '/', &parts);
+ if (parts.size() != 2)
+ return false;
+
+ // Parse the IP address.
+ if (!ParseIPLiteralToNumber(parts[0], ip_number))
+ return false;
+
+ // Parse the prefix length.
+ int number_of_bits = -1;
+ if (!StringToInt(parts[1], &number_of_bits))
+ return false;
+
+ // Make sure the prefix length is in a valid range.
+ if (number_of_bits < 0 ||
+ number_of_bits > static_cast<int>(ip_number->size() * 8))
+ return false;
+
+ *prefix_length_in_bits = static_cast<size_t>(number_of_bits);
+ return true;
+}
+
+bool IPNumberMatchesPrefix(const IPAddressNumber& ip_number,
+ const IPAddressNumber& ip_prefix,
+ size_t prefix_length_in_bits) {
+ // Both the input IP address and the prefix IP address should be
+ // either IPv4 or IPv6.
+ DCHECK(ip_number.size() == 4 || ip_number.size() == 16);
+ DCHECK(ip_prefix.size() == 4 || ip_prefix.size() == 16);
+
+ DCHECK_LE(prefix_length_in_bits, ip_prefix.size() * 8);
+
+ // In case we have an IPv6 / IPv4 mismatch, convert the IPv4 addresses to
+ // IPv6 addresses in order to do the comparison.
+ if (ip_number.size() != ip_prefix.size()) {
+ if (ip_number.size() == 4) {
+ return IPNumberMatchesPrefix(ConvertIPv4NumberToIPv6Number(ip_number),
+ ip_prefix, prefix_length_in_bits);
+ }
+ return IPNumberMatchesPrefix(ip_number,
+ ConvertIPv4NumberToIPv6Number(ip_prefix),
+ 96 + prefix_length_in_bits);
+ }
+
+ // Otherwise we are comparing two IPv4 addresses, or two IPv6 addresses.
+ // Compare all the bytes that fall entirely within the prefix.
+ int num_entire_bytes_in_prefix = prefix_length_in_bits / 8;
+ for (int i = 0; i < num_entire_bytes_in_prefix; ++i) {
+ if (ip_number[i] != ip_prefix[i])
+ return false;
+ }
+
+ // In case the prefix was not a multiple of 8, there will be 1 byte
+ // which is only partially masked.
+ int remaining_bits = prefix_length_in_bits % 8;
+ if (remaining_bits != 0) {
+ unsigned char mask = 0xFF << (8 - remaining_bits);
+ int i = num_entire_bytes_in_prefix;
+ if ((ip_number[i] & mask) != (ip_prefix[i] & mask))
+ return false;
+ }
+
+ return true;
+}
+
+// Returns the port field of the sockaddr in |info|.
+uint16* GetPortFieldFromAddrinfo(const struct addrinfo* info) {
+ DCHECK(info);
+ if (info->ai_family == AF_INET) {
+ DCHECK_EQ(sizeof(sockaddr_in), info->ai_addrlen);
+ struct sockaddr_in* sockaddr =
+ reinterpret_cast<struct sockaddr_in*>(info->ai_addr);
+ return &sockaddr->sin_port;
+ } else if (info->ai_family == AF_INET6) {
+ DCHECK_EQ(sizeof(sockaddr_in6), info->ai_addrlen);
+ struct sockaddr_in6* sockaddr =
+ reinterpret_cast<struct sockaddr_in6*>(info->ai_addr);
+ return &sockaddr->sin6_port;
+ } else {
+ NOTREACHED();
+ return NULL;
+ }
+}
+
+int GetPortFromAddrinfo(const struct addrinfo* info) {
+ uint16* port_field = GetPortFieldFromAddrinfo(info);
+ if (!port_field)
+ return -1;
+ return ntohs(*port_field);
+}
+
} // namespace net
diff --git a/net/base/net_util.h b/net/base/net_util.h
index d9affe6..5633c11 100644
--- a/net/base/net_util.h
+++ b/net/base/net_util.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -13,6 +13,7 @@
#include <string>
#include <set>
+#include <vector>
#include "base/basictypes.h"
#include "base/string16.h"
@@ -36,6 +37,26 @@
namespace net {
+// Used by FormatUrl to specify handling of certain parts of the url.
+typedef uint32 FormatUrlType;
+typedef uint32 FormatUrlTypes;
+
+// Nothing is ommitted.
+extern const FormatUrlType kFormatUrlOmitNothing;
+
+// If set, any username and password are removed.
+extern const FormatUrlType kFormatUrlOmitUsernamePassword;
+
+// If the scheme is 'http://', it's removed.
+extern const FormatUrlType kFormatUrlOmitHTTP;
+
+// Omits the path if it is just a slash and there is no query or ref. This is
+// meaningful for non-file "standard" URLs.
+extern const FormatUrlType kFormatUrlOmitTrailingSlashOnBareHostname;
+
+// Convenience for omitting all unecessary types.
+extern const FormatUrlType kFormatUrlOmitAll;
+
// Holds a list of ports that should be accepted despite bans.
extern std::set<int> explicitly_allowed_ports;
@@ -74,6 +95,10 @@
// Returns empty string on failure.
std::string NetAddressToString(const struct addrinfo* net_address);
+// Same as NetAddressToString, but additionally includes the port number. For
+// example: "192.168.0.1:99" or "[::1]:80".
+std::string NetAddressToStringWithPort(const struct addrinfo* net_address);
+
// Returns the hostname of the current system. Returns empty string on failure.
std::string GetHostName();
@@ -83,6 +108,9 @@
std::wstring* username,
std::wstring* password);
+// Returns either the host from |url|, or, if the host is empty, the full spec.
+std::string GetHostOrSpecFromURL(const GURL& url);
+
// Return the value of the HTTP response header with name 'name'. 'headers'
// should be in the format that URLRequest::GetResponseHeaders() returns.
// Returns the empty string if the header is not found.
@@ -171,10 +199,15 @@
// * Each component contains only alphanumeric characters and '-' or '_'
// * The last component does not begin with a digit
// * Optional trailing dot after last component (means "treat as FQDN")
+// If |desired_tld| is non-NULL, the host will only be considered invalid if
+// appending it as a trailing component still results in an invalid host. This
+// helps us avoid marking as "invalid" user attempts to open "www.401k.com" by
+// typing 4-0-1-k-<ctrl>+<enter>.
//
// NOTE: You should only pass in hosts that have been returned from
// CanonicalizeHost(), or you may not get accurate results.
-bool IsCanonicalizedHostCompliant(const std::string& host);
+bool IsCanonicalizedHostCompliant(const std::string& host,
+ const std::string& desired_tld);
// Call these functions to get the html snippet for a directory listing.
// The return values of both functions are in UTF-8.
@@ -186,10 +219,12 @@
// Currently, it's a script tag containing a call to a Javascript function
// |addRow|.
//
-// Its 1st parameter is derived from |name| and is the Javascript-string
-// escaped form of |name| (i.e \uXXXX). The 2nd parameter is the url-escaped
-// |raw_bytes| if it's not empty. If empty, the 2nd parameter is the
-// url-escaped |name| in UTF-8.
+// |name| is the file name to be displayed. |raw_bytes| will be used
+// as the actual target of the link (so for example, ftp links should use
+// server's encoding). If |raw_bytes| is an empty string, UTF-8 encoded |name|
+// will be used.
+//
+// Both |name| and |raw_bytes| are escaped internally.
std::string GetDirectoryListingEntry(const string16& name,
const std::string& raw_bytes,
bool is_dir, int64 size,
@@ -240,13 +275,12 @@
size_t* offset_for_adjustment);
// Creates a string representation of |url|. The IDN host name may be in Unicode
-// if |languages| accepts the Unicode representation. If
-// |omit_username_password| is true, any username and password are removed.
-// |unescape_rules| defines how to clean the URL for human readability.
-// You will generally want |UnescapeRule::SPACES| for display to the user if you
-// can handle spaces, or |UnescapeRule::NORMAL| if not. If the path part and the
-// query part seem to be encoded in %-encoded UTF-8, decodes %-encoding and
-// UTF-8.
+// if |languages| accepts the Unicode representation. |format_type| is a bitmask
+// of FormatUrlTypes, see it for details. |unescape_rules| defines how to clean
+// the URL for human readability. You will generally want |UnescapeRule::SPACES|
+// for display to the user if you can handle spaces, or |UnescapeRule::NORMAL|
+// if not. If the path part and the query part seem to be encoded in %-encoded
+// UTF-8, decodes %-encoding and UTF-8.
//
// The last three parameters may be NULL.
// |new_parsed| will be set to the parsing parameters of the resultant URL.
@@ -262,20 +296,25 @@
// std::wstring::npos.
std::wstring FormatUrl(const GURL& url,
const std::wstring& languages,
- bool omit_username_password,
+ FormatUrlTypes format_types,
UnescapeRule::Type unescape_rules,
url_parse::Parsed* new_parsed,
size_t* prefix_end,
size_t* offset_for_adjustment);
-// Creates a string representation of |url| for display to the user.
-// This is a shorthand of the above function with omit_username_password=true,
-// unescape=SPACES, new_parsed=NULL, and prefix_end=NULL.
+// This is a convenience function for FormatUrl() with
+// format_types = kFormatUrlOmitAll and unescape = SPACES. This is the typical
+// set of flags for "URLs to display to the user". You should be cautious about
+// using this for URLs which will be parsed or sent to other applications.
inline std::wstring FormatUrl(const GURL& url, const std::wstring& languages) {
- return FormatUrl(url, languages, true, UnescapeRule::SPACES, NULL, NULL,
- NULL);
+ return FormatUrl(url, languages, kFormatUrlOmitAll, UnescapeRule::SPACES,
+ NULL, NULL, NULL);
}
+// Returns whether FormatUrl() would strip a trailing slash from |url|, given a
+// format flag including kFormatUrlOmitTrailingSlashOnBareHostname.
+bool CanStripTrailingSlash(const GURL& url);
+
// Strip the portions of |url| that aren't core to the network request.
// - user name / password
// - reference section
@@ -283,6 +322,62 @@
void SetExplicitlyAllowedPorts(const std::wstring& allowed_ports);
+// Perform a simplistic test to see if IPv6 is supported by trying to create an
+// IPv6 socket.
+// TODO(jar): Make test more in-depth as needed.
+bool IPv6Supported();
+
+// IPAddressNumber is used to represent an IP address's numeric value as an
+// array of bytes, from most significant to least significant. This is the
+// network byte ordering.
+//
+// IPv4 addresses will have length 4, whereas IPv6 address will have length 16.
+typedef std::vector<unsigned char> IPAddressNumber;
+
+// Parses an IP address literal (either IPv4 or IPv6) to its numeric value.
+// Returns true on success and fills |ip_number| with the numeric value.
+bool ParseIPLiteralToNumber(const std::string& ip_literal,
+ IPAddressNumber* ip_number);
+
+// Converts an IPv4 address to an IPv4-mapped IPv6 address.
+// For example 192.168.0.1 would be converted to ::ffff:192.168.0.1.
+IPAddressNumber ConvertIPv4NumberToIPv6Number(
+ const IPAddressNumber& ipv4_number);
+
+// Parses an IP block specifier from CIDR notation to an
+// (IP address, prefix length) pair. Returns true on success and fills
+// |*ip_number| with the numeric value of the IP address and sets
+// |*prefix_length_in_bits| with the length of the prefix.
+//
+// CIDR notation literals can use either IPv4 or IPv6 literals. Some examples:
+//
+// 10.10.3.1/20
+// a:b:c::/46
+// ::1/128
+bool ParseCIDRBlock(const std::string& cidr_literal,
+ IPAddressNumber* ip_number,
+ size_t* prefix_length_in_bits);
+
+// Compares an IP address to see if it falls within the specified IP block.
+// Returns true if it does, false otherwise.
+//
+// The IP block is given by (|ip_prefix|, |prefix_length_in_bits|) -- any
+// IP address whose |prefix_length_in_bits| most significant bits match
+// |ip_prefix| will be matched.
+//
+// In cases when an IPv4 address is being compared to an IPv6 address prefix
+// and vice versa, the IPv4 addresses will be converted to IPv4-mapped
+// (IPv6) addresses.
+bool IPNumberMatchesPrefix(const IPAddressNumber& ip_number,
+ const IPAddressNumber& ip_prefix,
+ size_t prefix_length_in_bits);
+
+// Returns the port field of the sockaddr in |info|.
+uint16* GetPortFieldFromAddrinfo(const struct addrinfo* info);
+
+// Returns the value of |info's| port (in host byte ordering).
+int GetPortFromAddrinfo(const struct addrinfo* info);
+
} // namespace net
#endif // NET_BASE_NET_UTIL_H_
diff --git a/net/base/net_util_unittest.cc b/net/base/net_util_unittest.cc
index 9265f0a..461b6d3 100644
--- a/net/base/net_util_unittest.cc
+++ b/net/base/net_util_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -8,6 +8,7 @@
#include "base/format_macros.h"
#include "base/string_util.h"
#include "base/sys_string_conversions.h"
+#include "base/utf_string_conversions.h"
#include "base/time.h"
#include "googleurl/src/gurl.h"
#include "net/base/sys_addrinfo.h"
@@ -347,6 +348,7 @@
struct CompliantHostCase {
const char* host;
+ const char* desired_tld;
bool expected_output;
};
@@ -362,7 +364,7 @@
const char* description;
const char* input;
const std::wstring languages;
- bool omit;
+ net::FormatUrlTypes format_types;
UnescapeRule::Type escape_rules;
const std::wstring output;
size_t prefix_len;
@@ -371,7 +373,7 @@
// Returns an addrinfo for the given 32-bit address (IPv4.)
// The result lives in static storage, so don't delete it.
// |bytes| should be an array of length 4.
-const struct addrinfo* GetIPv4Address(const uint8* bytes) {
+const struct addrinfo* GetIPv4Address(const uint8* bytes, int port) {
static struct addrinfo static_ai;
static struct sockaddr_in static_addr4;
@@ -384,7 +386,7 @@
struct sockaddr_in* addr4 = &static_addr4;
memset(addr4, 0, sizeof(static_addr4));
- addr4->sin_port = htons(80);
+ addr4->sin_port = htons(port);
addr4->sin_family = ai->ai_family;
memcpy(&addr4->sin_addr, bytes, 4);
@@ -395,7 +397,7 @@
// Returns a addrinfo for the given 128-bit address (IPv6.)
// The result lives in static storage, so don't delete it.
// |bytes| should be an array of length 16.
-const struct addrinfo* GetIPv6Address(const uint8* bytes) {
+const struct addrinfo* GetIPv6Address(const uint8* bytes, int port) {
static struct addrinfo static_ai;
static struct sockaddr_in6 static_addr6;
@@ -408,7 +410,7 @@
struct sockaddr_in6* addr6 = &static_addr6;
memset(addr6, 0, sizeof(static_addr6));
- addr6->sin6_port = htons(80);
+ addr6->sin6_port = htons(port);
addr6->sin6_family = ai->ai_family;
memcpy(&addr6->sin6_addr, bytes, 16);
@@ -416,7 +418,6 @@
return ai;
}
-
// A helper for IDN*{Fast,Slow}.
// Append "::<language list>" to |expected| and |actual| to make it
// easy to tell which sub-case fails without debugging.
@@ -429,6 +430,17 @@
actual->append(languages);
}
+// Helper to strignize an IP number (used to define expectations).
+std::string DumpIPNumber(const net::IPAddressNumber& v) {
+ std::string out;
+ for (size_t i = 0; i < v.size(); ++i) {
+ if (i != 0)
+ out.append(",");
+ out.append(IntToString(static_cast<int>(v[i])));
+ }
+ return out;
+}
+
} // anonymous namespace
TEST(NetUtilTest, FileURLConversion) {
@@ -552,6 +564,11 @@
L"username",
L"p@ssword",
},
+ { // Special URL characters should be unescaped.
+ "http://username:p%3fa%26s%2fs%23@google.com",
+ L"username",
+ L"p?a&s/s#",
+ },
{ // Username contains %20.
"http://use rname:password@google.com",
L"use rname",
@@ -837,30 +854,35 @@
TEST(NetUtilTest, CompliantHost) {
const CompliantHostCase compliant_host_cases[] = {
- {"", false},
- {"a", true},
- {"-", false},
- {".", false},
- {"a.", true},
- {"a.a", true},
- {"9.a", true},
- {"a.9", false},
- {"_9a", false},
- {"a.a9", true},
- {"a.9a", false},
- {"a+9a", false},
- {"1-.a-b", false},
- {"1-2.a_b", true},
- {"a.b.c.d.e", true},
- {"1.2.3.4.e", true},
- {"a.b.c.d.5", false},
- {"1.2.3.4.e.", true},
- {"a.b.c.d.5.", false},
+ {"", "", false},
+ {"a", "", true},
+ {"-", "", false},
+ {".", "", false},
+ {"9", "", false},
+ {"9", "a", true},
+ {"9a", "", false},
+ {"9a", "a", true},
+ {"a.", "", true},
+ {"a.a", "", true},
+ {"9.a", "", true},
+ {"a.9", "", false},
+ {"_9a", "", false},
+ {"a.a9", "", true},
+ {"a.9a", "", false},
+ {"a+9a", "", false},
+ {"1-.a-b", "", false},
+ {"1-2.a_b", "", true},
+ {"a.b.c.d.e", "", true},
+ {"1.2.3.4.e", "", true},
+ {"a.b.c.d.5", "", false},
+ {"1.2.3.4.e.", "", true},
+ {"a.b.c.d.5.", "", false},
};
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(compliant_host_cases); ++i) {
EXPECT_EQ(compliant_host_cases[i].expected_output,
- net::IsCanonicalizedHostCompliant(compliant_host_cases[i].host));
+ net::IsCanonicalizedHostCompliant(compliant_host_cases[i].host,
+ compliant_host_cases[i].desired_tld));
}
}
@@ -1037,6 +1059,23 @@
"",
L"",
L"test"},
+ // The filename encoding is specified by the referrer charset.
+ {"http://example.com/V%FDvojov%E1%20psychologie.doc",
+ "",
+ "iso-8859-1",
+ L"",
+ L"V\u00fdvojov\u00e1 psychologie.doc"},
+ // The filename encoding doesn't match the referrer charset, the
+ // system charset, or UTF-8.
+ // TODO(jshin): we need to handle this case.
+#if 0
+ {"http://example.com/V%FDvojov%E1%20psychologie.doc",
+ "",
+ "utf-8",
+ L"",
+ L"V\u00fdvojov\u00e1 psychologie.doc",
+ },
+#endif
};
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
#if defined(OS_WIN)
@@ -1220,7 +1259,7 @@
};
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
- const addrinfo* ai = GetIPv4Address(tests[i].addr);
+ const addrinfo* ai = GetIPv4Address(tests[i].addr, 80);
std::string result = net::NetAddressToString(ai);
EXPECT_EQ(std::string(tests[i].result), result);
}
@@ -1237,7 +1276,7 @@
};
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
- const addrinfo* ai = GetIPv6Address(tests[i].addr);
+ const addrinfo* ai = GetIPv6Address(tests[i].addr, 80);
std::string result = net::NetAddressToString(ai);
// Allow NetAddressToString() to fail, in case the system doesn't
// support IPv6.
@@ -1246,6 +1285,26 @@
}
}
+TEST(NetUtilTest, NetAddressToStringWithPort_IPv4) {
+ uint8 addr[] = {127, 0, 0, 1};
+ const addrinfo* ai = GetIPv4Address(addr, 166);
+ std::string result = net::NetAddressToStringWithPort(ai);
+ EXPECT_EQ("127.0.0.1:166", result);
+}
+
+TEST(NetUtilTest, NetAddressToStringWithPort_IPv6) {
+ uint8 addr[] = {
+ 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10, 0xFE, 0xDC, 0xBA,
+ 0x98, 0x76, 0x54, 0x32, 0x10
+ };
+ const addrinfo* ai = GetIPv6Address(addr, 361);
+ std::string result = net::NetAddressToStringWithPort(ai);
+
+ // May fail on systems that don't support IPv6.
+ if (!result.empty())
+ EXPECT_EQ("[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:361", result);
+}
+
TEST(NetUtilTest, GetHostName) {
// We can't check the result of GetHostName() directly, since the result
// will differ across machines. Our goal here is to simply exercise the
@@ -1255,74 +1314,78 @@
}
TEST(NetUtilTest, FormatUrl) {
+ net::FormatUrlTypes default_format_type = net::kFormatUrlOmitUsernamePassword;
const UrlTestData tests[] = {
- {"Empty URL", "", L"", true, UnescapeRule::NORMAL, L"", 0},
+ {"Empty URL", "", L"", default_format_type, UnescapeRule::NORMAL, L"", 0},
{"Simple URL",
- "http://www.google.com/", L"", true, UnescapeRule::NORMAL,
+ "http://www.google.com/", L"", default_format_type, UnescapeRule::NORMAL,
L"http://www.google.com/", 7},
{"With a port number and a reference",
- "http://www.google.com:8080/#\xE3\x82\xB0", L"", true,
+ "http://www.google.com:8080/#\xE3\x82\xB0", L"", default_format_type,
UnescapeRule::NORMAL,
L"http://www.google.com:8080/#\x30B0", 7},
// -------- IDN tests --------
{"Japanese IDN with ja",
- "http://xn--l8jvb1ey91xtjb.jp", L"ja", true, UnescapeRule::NORMAL,
- L"http://\x671d\x65e5\x3042\x3055\x3072.jp/", 7},
+ "http://xn--l8jvb1ey91xtjb.jp", L"ja", default_format_type,
+ UnescapeRule::NORMAL, L"http://\x671d\x65e5\x3042\x3055\x3072.jp/", 7},
{"Japanese IDN with en",
- "http://xn--l8jvb1ey91xtjb.jp", L"en", true, UnescapeRule::NORMAL,
- L"http://xn--l8jvb1ey91xtjb.jp/", 7},
+ "http://xn--l8jvb1ey91xtjb.jp", L"en", default_format_type,
+ UnescapeRule::NORMAL, L"http://xn--l8jvb1ey91xtjb.jp/", 7},
{"Japanese IDN without any languages",
- "http://xn--l8jvb1ey91xtjb.jp", L"", true, UnescapeRule::NORMAL,
+ "http://xn--l8jvb1ey91xtjb.jp", L"", default_format_type,
+ UnescapeRule::NORMAL,
// Single script is safe for empty languages.
L"http://\x671d\x65e5\x3042\x3055\x3072.jp/", 7},
{"mailto: with Japanese IDN",
- "mailto:foo@xn--l8jvb1ey91xtjb.jp", L"ja", true, UnescapeRule::NORMAL,
+ "mailto:foo@xn--l8jvb1ey91xtjb.jp", L"ja", default_format_type,
+ UnescapeRule::NORMAL,
// GURL doesn't assume an email address's domain part as a host name.
L"mailto:foo@xn--l8jvb1ey91xtjb.jp", 7},
{"file: with Japanese IDN",
- "file://xn--l8jvb1ey91xtjb.jp/config.sys", L"ja", true,
+ "file://xn--l8jvb1ey91xtjb.jp/config.sys", L"ja", default_format_type,
UnescapeRule::NORMAL,
L"file://\x671d\x65e5\x3042\x3055\x3072.jp/config.sys", 7},
{"ftp: with Japanese IDN",
- "ftp://xn--l8jvb1ey91xtjb.jp/config.sys", L"ja", true,
+ "ftp://xn--l8jvb1ey91xtjb.jp/config.sys", L"ja", default_format_type,
UnescapeRule::NORMAL,
L"ftp://\x671d\x65e5\x3042\x3055\x3072.jp/config.sys", 6},
// -------- omit_username_password flag tests --------
{"With username and password, omit_username_password=false",
- "http://user:passwd@example.com/foo", L"", false, UnescapeRule::NORMAL,
+ "http://user:passwd@example.com/foo", L"",
+ net::kFormatUrlOmitNothing, UnescapeRule::NORMAL,
L"http://user:passwd@example.com/foo", 19},
{"With username and password, omit_username_password=true",
- "http://user:passwd@example.com/foo", L"", true, UnescapeRule::NORMAL,
- L"http://example.com/foo", 7},
+ "http://user:passwd@example.com/foo", L"", default_format_type,
+ UnescapeRule::NORMAL, L"http://example.com/foo", 7},
{"With username and no password",
- "http://user@example.com/foo", L"", true, UnescapeRule::NORMAL,
- L"http://example.com/foo", 7},
+ "http://user@example.com/foo", L"", default_format_type,
+ UnescapeRule::NORMAL, L"http://example.com/foo", 7},
{"Just '@' without username and password",
- "http://@example.com/foo", L"", true, UnescapeRule::NORMAL,
+ "http://@example.com/foo", L"", default_format_type, UnescapeRule::NORMAL,
L"http://example.com/foo", 7},
// GURL doesn't think local-part of an email address is username for URL.
{"mailto:, omit_username_password=true",
- "mailto:foo@example.com", L"", true, UnescapeRule::NORMAL,
+ "mailto:foo@example.com", L"", default_format_type, UnescapeRule::NORMAL,
L"mailto:foo@example.com", 7},
// -------- unescape flag tests --------
{"Do not unescape",
"http://%E3%82%B0%E3%83%BC%E3%82%B0%E3%83%AB.jp/"
"%E3%82%B0%E3%83%BC%E3%82%B0%E3%83%AB"
- "?q=%E3%82%B0%E3%83%BC%E3%82%B0%E3%83%AB", L"en", true,
+ "?q=%E3%82%B0%E3%83%BC%E3%82%B0%E3%83%AB", L"en", default_format_type,
UnescapeRule::NONE,
// GURL parses %-encoded hostnames into Punycode.
L"http://xn--qcka1pmc.jp/%E3%82%B0%E3%83%BC%E3%82%B0%E3%83%AB"
@@ -1331,38 +1394,98 @@
{"Unescape normally",
"http://%E3%82%B0%E3%83%BC%E3%82%B0%E3%83%AB.jp/"
"%E3%82%B0%E3%83%BC%E3%82%B0%E3%83%AB"
- "?q=%E3%82%B0%E3%83%BC%E3%82%B0%E3%83%AB", L"en", true,
+ "?q=%E3%82%B0%E3%83%BC%E3%82%B0%E3%83%AB", L"en", default_format_type,
UnescapeRule::NORMAL,
L"http://xn--qcka1pmc.jp/\x30B0\x30FC\x30B0\x30EB"
L"?q=\x30B0\x30FC\x30B0\x30EB", 7},
{"Unescape normally including unescape spaces",
- "http://www.google.com/search?q=Hello%20World", L"en", true,
- UnescapeRule::SPACES,
- L"http://www.google.com/search?q=Hello World", 7},
+ "http://www.google.com/search?q=Hello%20World", L"en", default_format_type,
+ UnescapeRule::SPACES, L"http://www.google.com/search?q=Hello World", 7},
/*
{"unescape=true with some special characters",
- "http://user%3A:%40passwd@example.com/foo%3Fbar?q=b%26z", L"", false, true,
+ "http://user%3A:%40passwd@example.com/foo%3Fbar?q=b%26z", L"",
+ net::kFormatUrlOmitNothing, UnescapeRule::NORMAL,
L"http://user%3A:%40passwd@example.com/foo%3Fbar?q=b%26z", 25},
*/
// Disabled: the resultant URL becomes "...user%253A:%2540passwd...".
+ // -------- omit http: --------
+ {"omit http with user name",
+ "http://user@example.com/foo", L"", net::kFormatUrlOmitAll,
+ UnescapeRule::NORMAL, L"example.com/foo", 0},
+
+ {"omit http",
+ "http://www.google.com/", L"en", net::kFormatUrlOmitHTTP,
+ UnescapeRule::NORMAL, L"www.google.com/",
+ 0},
+
+ {"omit http with https",
+ "https://www.google.com/", L"en", net::kFormatUrlOmitHTTP,
+ UnescapeRule::NORMAL, L"https://www.google.com/",
+ 8},
+
+ {"omit http starts with ftp.",
+ "http://ftp.google.com/", L"en", net::kFormatUrlOmitHTTP,
+ UnescapeRule::NORMAL, L"http://ftp.google.com/",
+ 7},
+
+ // -------- omit trailing slash on bare hostname --------
+ {"omit slash when it's the entire path",
+ "http://www.google.com/", L"en",
+ net::kFormatUrlOmitTrailingSlashOnBareHostname, UnescapeRule::NORMAL,
+ L"http://www.google.com", 7},
+ {"omit slash when there's a ref",
+ "http://www.google.com/#ref", L"en",
+ net::kFormatUrlOmitTrailingSlashOnBareHostname, UnescapeRule::NORMAL,
+ L"http://www.google.com/#ref", 7},
+ {"omit slash when there's a query",
+ "http://www.google.com/?", L"en",
+ net::kFormatUrlOmitTrailingSlashOnBareHostname, UnescapeRule::NORMAL,
+ L"http://www.google.com/?", 7},
+ {"omit slash when it's not the entire path",
+ "http://www.google.com/foo", L"en",
+ net::kFormatUrlOmitTrailingSlashOnBareHostname, UnescapeRule::NORMAL,
+ L"http://www.google.com/foo", 7},
+ {"omit slash for nonstandard URLs",
+ "data:/", L"en", net::kFormatUrlOmitTrailingSlashOnBareHostname,
+ UnescapeRule::NORMAL, L"data:/", 5},
+ {"omit slash for file URLs",
+ "file:///", L"en", net::kFormatUrlOmitTrailingSlashOnBareHostname,
+ UnescapeRule::NORMAL, L"file:///", 7},
+
// -------- view-source: --------
{"view-source",
- "view-source:http://xn--qcka1pmc.jp/", L"ja", true, UnescapeRule::NORMAL,
- L"view-source:http://\x30B0\x30FC\x30B0\x30EB.jp/", 12 + 7},
+ "view-source:http://xn--qcka1pmc.jp/", L"ja", default_format_type,
+ UnescapeRule::NORMAL, L"view-source:http://\x30B0\x30FC\x30B0\x30EB.jp/",
+ 19},
{"view-source of view-source",
- "view-source:view-source:http://xn--qcka1pmc.jp/", L"ja", true,
- UnescapeRule::NORMAL,
+ "view-source:view-source:http://xn--qcka1pmc.jp/", L"ja",
+ default_format_type, UnescapeRule::NORMAL,
L"view-source:view-source:http://xn--qcka1pmc.jp/", 12},
+
+ // view-source should omit http and trailing slash where non-view-source
+ // would.
+ {"view-source omit http",
+ "view-source:http://a.b/c", L"en", net::kFormatUrlOmitAll,
+ UnescapeRule::NORMAL, L"view-source:a.b/c",
+ 12},
+ {"view-source omit http starts with ftp.",
+ "view-source:http://ftp.b/c", L"en", net::kFormatUrlOmitAll,
+ UnescapeRule::NORMAL, L"view-source:http://ftp.b/c",
+ 19},
+ {"view-source omit slash when it's the entire path",
+ "view-source:http://a.b/", L"en", net::kFormatUrlOmitAll,
+ UnescapeRule::NORMAL, L"view-source:a.b",
+ 12},
};
for (size_t i = 0; i < arraysize(tests); ++i) {
size_t prefix_len;
std::wstring formatted = net::FormatUrl(
- GURL(tests[i].input), tests[i].languages, tests[i].omit,
+ GURL(tests[i].input), tests[i].languages, tests[i].format_types,
tests[i].escape_rules, NULL, &prefix_len, NULL);
EXPECT_EQ(tests[i].output, formatted) << tests[i].description;
EXPECT_EQ(tests[i].prefix_len, prefix_len) << tests[i].description;
@@ -1375,7 +1498,8 @@
std::wstring formatted = net::FormatUrl(
GURL("http://\xE3\x82\xB0:\xE3\x83\xBC@xn--qcka1pmc.jp:8080/"
"%E3%82%B0/?q=%E3%82%B0#\xE3\x82\xB0"),
- L"ja", false, UnescapeRule::NONE, &parsed, NULL, NULL);
+ L"ja", net::kFormatUrlOmitNothing, UnescapeRule::NONE, &parsed, NULL,
+ NULL);
EXPECT_EQ(L"http://%E3%82%B0:%E3%83%BC@\x30B0\x30FC\x30B0\x30EB.jp:8080"
L"/%E3%82%B0/?q=%E3%82%B0#\x30B0", formatted);
EXPECT_EQ(L"%E3%82%B0",
@@ -1395,7 +1519,8 @@
formatted = net::FormatUrl(
GURL("http://\xE3\x82\xB0:\xE3\x83\xBC@xn--qcka1pmc.jp:8080/"
"%E3%82%B0/?q=%E3%82%B0#\xE3\x82\xB0"),
- L"ja", false, UnescapeRule::NORMAL, &parsed, NULL, NULL);
+ L"ja", net::kFormatUrlOmitNothing, UnescapeRule::NORMAL, &parsed, NULL,
+ NULL);
EXPECT_EQ(L"http://\x30B0:\x30FC@\x30B0\x30FC\x30B0\x30EB.jp:8080"
L"/\x30B0/?q=\x30B0#\x30B0", formatted);
EXPECT_EQ(L"\x30B0",
@@ -1414,7 +1539,8 @@
formatted = net::FormatUrl(
GURL("http://\xE3\x82\xB0:\xE3\x83\xBC@xn--qcka1pmc.jp:8080/"
"%E3%82%B0/?q=%E3%82%B0#\xE3\x82\xB0"),
- L"ja", true, UnescapeRule::NORMAL, &parsed, NULL, NULL);
+ L"ja", net::kFormatUrlOmitUsernamePassword, UnescapeRule::NORMAL,
+ &parsed, NULL, NULL);
EXPECT_EQ(L"http://\x30B0\x30FC\x30B0\x30EB.jp:8080"
L"/\x30B0/?q=\x30B0#\x30B0", formatted);
EXPECT_FALSE(parsed.username.is_valid());
@@ -1430,7 +1556,8 @@
// View-source case.
formatted = net::FormatUrl(
GURL("view-source:http://user:passwd@host:81/path?query#ref"),
- L"", true, UnescapeRule::NORMAL, &parsed, NULL, NULL);
+ L"", net::kFormatUrlOmitUsernamePassword, UnescapeRule::NORMAL, &parsed,
+ NULL, NULL);
EXPECT_EQ(L"view-source:http://host:81/path?query#ref", formatted);
EXPECT_EQ(L"view-source:http",
formatted.substr(parsed.scheme.begin, parsed.scheme.len));
@@ -1441,6 +1568,50 @@
EXPECT_EQ(L"/path", formatted.substr(parsed.path.begin, parsed.path.len));
EXPECT_EQ(L"query", formatted.substr(parsed.query.begin, parsed.query.len));
EXPECT_EQ(L"ref", formatted.substr(parsed.ref.begin, parsed.ref.len));
+
+ // omit http case.
+ formatted = net::FormatUrl(
+ GURL("http://host:8000/a?b=c#d"),
+ L"", net::kFormatUrlOmitHTTP, UnescapeRule::NORMAL, &parsed, NULL, NULL);
+ EXPECT_EQ(L"host:8000/a?b=c#d", formatted);
+ EXPECT_FALSE(parsed.scheme.is_valid());
+ EXPECT_FALSE(parsed.username.is_valid());
+ EXPECT_FALSE(parsed.password.is_valid());
+ EXPECT_EQ(L"host", formatted.substr(parsed.host.begin, parsed.host.len));
+ EXPECT_EQ(L"8000", formatted.substr(parsed.port.begin, parsed.port.len));
+ EXPECT_EQ(L"/a", formatted.substr(parsed.path.begin, parsed.path.len));
+ EXPECT_EQ(L"b=c", formatted.substr(parsed.query.begin, parsed.query.len));
+ EXPECT_EQ(L"d", formatted.substr(parsed.ref.begin, parsed.ref.len));
+
+ // omit http starts with ftp case.
+ formatted = net::FormatUrl(
+ GURL("http://ftp.host:8000/a?b=c#d"),
+ L"", net::kFormatUrlOmitHTTP, UnescapeRule::NORMAL, &parsed, NULL, NULL);
+ EXPECT_EQ(L"http://ftp.host:8000/a?b=c#d", formatted);
+ EXPECT_TRUE(parsed.scheme.is_valid());
+ EXPECT_FALSE(parsed.username.is_valid());
+ EXPECT_FALSE(parsed.password.is_valid());
+ EXPECT_EQ(L"http", formatted.substr(parsed.scheme.begin, parsed.scheme.len));
+ EXPECT_EQ(L"ftp.host", formatted.substr(parsed.host.begin, parsed.host.len));
+ EXPECT_EQ(L"8000", formatted.substr(parsed.port.begin, parsed.port.len));
+ EXPECT_EQ(L"/a", formatted.substr(parsed.path.begin, parsed.path.len));
+ EXPECT_EQ(L"b=c", formatted.substr(parsed.query.begin, parsed.query.len));
+ EXPECT_EQ(L"d", formatted.substr(parsed.ref.begin, parsed.ref.len));
+
+ // omit http starts with 'f' case.
+ formatted = net::FormatUrl(
+ GURL("http://f/"),
+ L"", net::kFormatUrlOmitHTTP, UnescapeRule::NORMAL, &parsed, NULL, NULL);
+ EXPECT_EQ(L"f/", formatted);
+ EXPECT_FALSE(parsed.scheme.is_valid());
+ EXPECT_FALSE(parsed.username.is_valid());
+ EXPECT_FALSE(parsed.password.is_valid());
+ EXPECT_FALSE(parsed.port.is_valid());
+ EXPECT_TRUE(parsed.path.is_valid());
+ EXPECT_FALSE(parsed.query.is_valid());
+ EXPECT_FALSE(parsed.ref.is_valid());
+ EXPECT_EQ(L"f", formatted.substr(parsed.host.begin, parsed.host.len));
+ EXPECT_EQ(L"/", formatted.substr(parsed.path.begin, parsed.path.len));
}
TEST(NetUtilTest, FormatUrlAdjustOffset) {
@@ -1460,8 +1631,9 @@
};
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(basic_cases); ++i) {
size_t offset = basic_cases[i].input_offset;
- net::FormatUrl(GURL("http://www.google.com/foo/"), L"en", true,
- UnescapeRule::NORMAL, NULL, NULL, &offset);
+ net::FormatUrl(GURL("http://www.google.com/foo/"), L"en",
+ net::kFormatUrlOmitUsernamePassword, UnescapeRule::NORMAL,
+ NULL, NULL, &offset);
EXPECT_EQ(basic_cases[i].output_offset, offset);
}
@@ -1483,8 +1655,9 @@
};
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(omit_auth_cases); ++i) {
size_t offset = omit_auth_cases[i].input_offset;
- net::FormatUrl(GURL(omit_auth_cases[i].input_url), L"en", true,
- UnescapeRule::NORMAL, NULL, NULL, &offset);
+ net::FormatUrl(GURL(omit_auth_cases[i].input_url), L"en",
+ net::kFormatUrlOmitUsernamePassword, UnescapeRule::NORMAL,
+ NULL, NULL, &offset);
EXPECT_EQ(omit_auth_cases[i].output_offset, offset);
}
@@ -1502,8 +1675,9 @@
};
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(view_source_cases); ++i) {
size_t offset = view_source_cases[i].input_offset;
- net::FormatUrl(GURL("view-source:http://foo@www.google.com/"), L"en", true,
- UnescapeRule::NORMAL, NULL, NULL, &offset);
+ net::FormatUrl(GURL("view-source:http://foo@www.google.com/"), L"en",
+ net::kFormatUrlOmitUsernamePassword, UnescapeRule::NORMAL,
+ NULL, NULL, &offset);
EXPECT_EQ(view_source_cases[i].output_offset, offset);
}
@@ -1517,8 +1691,9 @@
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(idn_hostname_cases); ++i) {
size_t offset = idn_hostname_cases[i].input_offset;
// "http://\x671d\x65e5\x3042\x3055\x3072.jp/foo/"
- net::FormatUrl(GURL("http://xn--l8jvb1ey91xtjb.jp/foo/"), L"ja", true,
- UnescapeRule::NORMAL, NULL, NULL, &offset);
+ net::FormatUrl(GURL("http://xn--l8jvb1ey91xtjb.jp/foo/"), L"ja",
+ net::kFormatUrlOmitUsernamePassword, UnescapeRule::NORMAL,
+ NULL, NULL, &offset);
EXPECT_EQ(idn_hostname_cases[i].output_offset, offset);
}
@@ -1539,7 +1714,8 @@
// "http://www.google.com/foo bar/\x30B0\x30FC\x30B0\x30EB"
net::FormatUrl(GURL(
"http://www.google.com/foo%20bar/%E3%82%B0%E3%83%BC%E3%82%B0%E3%83%AB"),
- L"en", true, UnescapeRule::SPACES, NULL, NULL, &offset);
+ L"en", net::kFormatUrlOmitUsernamePassword, UnescapeRule::SPACES, NULL,
+ NULL, &offset);
EXPECT_EQ(unescape_cases[i].output_offset, offset);
}
@@ -1556,9 +1732,48 @@
// "http://www.google.com/foo.html#\x30B0\x30B0z"
net::FormatUrl(GURL(
"http://www.google.com/foo.html#\xE3\x82\xB0\xE3\x82\xB0z"), L"en",
- true, UnescapeRule::NORMAL, NULL, NULL, &offset);
+ net::kFormatUrlOmitUsernamePassword, UnescapeRule::NORMAL, NULL, NULL,
+ &offset);
EXPECT_EQ(ref_cases[i].output_offset, offset);
}
+
+ const AdjustOffsetCase omit_http_cases[] = {
+ {0, std::wstring::npos},
+ {3, std::wstring::npos},
+ {7, 0},
+ {8, 1},
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(omit_http_cases); ++i) {
+ size_t offset = omit_http_cases[i].input_offset;
+ net::FormatUrl(GURL("http://www.google.com"), L"en",
+ net::kFormatUrlOmitHTTP, UnescapeRule::NORMAL, NULL, NULL, &offset);
+ EXPECT_EQ(omit_http_cases[i].output_offset, offset);
+ }
+
+ const AdjustOffsetCase omit_http_start_with_ftp[] = {
+ {0, 0},
+ {3, 3},
+ {8, 8},
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(omit_http_start_with_ftp); ++i) {
+ size_t offset = omit_http_start_with_ftp[i].input_offset;
+ net::FormatUrl(GURL("http://ftp.google.com"), L"en",
+ net::kFormatUrlOmitHTTP, UnescapeRule::NORMAL, NULL, NULL, &offset);
+ EXPECT_EQ(omit_http_start_with_ftp[i].output_offset, offset);
+ }
+
+ const AdjustOffsetCase omit_all_cases[] = {
+ {12, 0},
+ {13, 1},
+ {0, std::wstring::npos},
+ {3, std::wstring::npos},
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(omit_all_cases); ++i) {
+ size_t offset = omit_all_cases[i].input_offset;
+ net::FormatUrl(GURL("http://user@foo.com/"), L"en", net::kFormatUrlOmitAll,
+ UnescapeRule::NORMAL, NULL, NULL, &offset);
+ EXPECT_EQ(omit_all_cases[i].output_offset, offset);
+ }
}
TEST(NetUtilTest, SimplifyUrlForRequest) {
@@ -1592,9 +1807,9 @@
"ftp://user:pass@google.com:80/sup?yo#X#X",
"ftp://google.com:80/sup?yo",
},
- { // Try an standard URL with unknow scheme.
+ { // Try an nonstandard URL
"foobar://user:pass@google.com:80/sup?yo#X#X",
- "foobar://google.com:80/sup?yo",
+ "foobar://user:pass@google.com:80/sup?yo#X#X",
},
};
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
@@ -1619,3 +1834,173 @@
EXPECT_EQ(i, net::explicitly_allowed_ports.size());
}
}
+
+TEST(NetUtilTest, GetHostOrSpecFromURL) {
+ EXPECT_EQ("example.com",
+ net::GetHostOrSpecFromURL(GURL("http://example.com/test")));
+ EXPECT_EQ("example.com",
+ net::GetHostOrSpecFromURL(GURL("http://example.com./test")));
+ EXPECT_EQ("file:///tmp/test.html",
+ net::GetHostOrSpecFromURL(GURL("file:///tmp/test.html")));
+}
+
+// Test that invalid IP literals fail to parse.
+TEST(NetUtilTest, ParseIPLiteralToNumber_FailParse) {
+ net::IPAddressNumber number;
+
+ EXPECT_FALSE(net::ParseIPLiteralToNumber("bad value", &number));
+ EXPECT_FALSE(net::ParseIPLiteralToNumber("bad:value", &number));
+ EXPECT_FALSE(net::ParseIPLiteralToNumber("", &number));
+ EXPECT_FALSE(net::ParseIPLiteralToNumber("192.168.0.1:30", &number));
+ EXPECT_FALSE(net::ParseIPLiteralToNumber(" 192.168.0.1 ", &number));
+ EXPECT_FALSE(net::ParseIPLiteralToNumber("[::1]", &number));
+}
+
+// Test parsing an IPv4 literal.
+TEST(NetUtilTest, ParseIPLiteralToNumber_IPv4) {
+ net::IPAddressNumber number;
+ EXPECT_TRUE(net::ParseIPLiteralToNumber("192.168.0.1", &number));
+ EXPECT_EQ("192,168,0,1", DumpIPNumber(number));
+}
+
+// Test parsing an IPv6 literal.
+TEST(NetUtilTest, ParseIPLiteralToNumber_IPv6) {
+ net::IPAddressNumber number;
+ EXPECT_TRUE(net::ParseIPLiteralToNumber("1:abcd::3:4:ff", &number));
+ EXPECT_EQ("0,1,171,205,0,0,0,0,0,0,0,3,0,4,0,255", DumpIPNumber(number));
+}
+
+// Test mapping an IPv4 address to an IPv6 address.
+TEST(NetUtilTest, ConvertIPv4NumberToIPv6Number) {
+ net::IPAddressNumber ipv4_number;
+ EXPECT_TRUE(net::ParseIPLiteralToNumber("192.168.0.1", &ipv4_number));
+
+ net::IPAddressNumber ipv6_number =
+ net::ConvertIPv4NumberToIPv6Number(ipv4_number);
+
+ // ::ffff:192.168.1.1
+ EXPECT_EQ("0,0,0,0,0,0,0,0,0,0,255,255,192,168,0,1",
+ DumpIPNumber(ipv6_number));
+}
+
+// Test parsing invalid CIDR notation literals.
+TEST(NetUtilTest, ParseCIDRBlock_Invalid) {
+ const char* bad_literals[] = {
+ "foobar",
+ "",
+ "192.168.0.1",
+ "::1",
+ "/",
+ "/1",
+ "1",
+ "192.168.1.1/-1",
+ "192.168.1.1/33",
+ "::1/-3",
+ "a::3/129",
+ "::1/x",
+ "192.168.0.1//11"
+ };
+
+ for (size_t i = 0; i < arraysize(bad_literals); ++i) {
+ net::IPAddressNumber ip_number;
+ size_t prefix_length_in_bits;
+
+ EXPECT_FALSE(net::ParseCIDRBlock(bad_literals[i],
+ &ip_number,
+ &prefix_length_in_bits));
+ }
+}
+
+// Test parsing a valid CIDR notation literal.
+TEST(NetUtilTest, ParseCIDRBlock_Valid) {
+ net::IPAddressNumber ip_number;
+ size_t prefix_length_in_bits;
+
+ EXPECT_TRUE(net::ParseCIDRBlock("192.168.0.1/11",
+ &ip_number,
+ &prefix_length_in_bits));
+
+ EXPECT_EQ("192,168,0,1", DumpIPNumber(ip_number));
+ EXPECT_EQ(11u, prefix_length_in_bits);
+}
+
+TEST(NetUtilTest, IPNumberMatchesPrefix) {
+ struct {
+ const char* cidr_literal;
+ const char* ip_literal;
+ bool expected_to_match;
+ } tests[] = {
+ // IPv4 prefix with IPv4 inputs.
+ {
+ "10.10.1.32/27",
+ "10.10.1.44",
+ true
+ },
+ {
+ "10.10.1.32/27",
+ "10.10.1.90",
+ false
+ },
+ {
+ "10.10.1.32/27",
+ "10.10.1.90",
+ false
+ },
+
+ // IPv6 prefix with IPv6 inputs.
+ {
+ "2001:db8::/32",
+ "2001:DB8:3:4::5",
+ true
+ },
+ {
+ "2001:db8::/32",
+ "2001:c8::",
+ false
+ },
+
+ // IPv6 prefix with IPv4 inputs.
+ {
+ "2001:db8::/33",
+ "192.168.0.1",
+ false
+ },
+ {
+ "::ffff:192.168.0.1/112",
+ "192.168.33.77",
+ true
+ },
+
+ // IPv4 prefix with IPv6 inputs.
+ {
+ "10.11.33.44/16",
+ "::ffff:0a0b:89",
+ true
+ },
+ {
+ "10.11.33.44/16",
+ "::ffff:10.12.33.44",
+ false
+ },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ SCOPED_TRACE(StringPrintf("Test[%" PRIuS "]: %s, %s", i,
+ tests[i].cidr_literal,
+ tests[i].ip_literal));
+
+ net::IPAddressNumber ip_number;
+ EXPECT_TRUE(net::ParseIPLiteralToNumber(tests[i].ip_literal, &ip_number));
+
+ net::IPAddressNumber ip_prefix;
+ size_t prefix_length_in_bits;
+
+ EXPECT_TRUE(net::ParseCIDRBlock(tests[i].cidr_literal,
+ &ip_prefix,
+ &prefix_length_in_bits));
+
+ EXPECT_EQ(tests[i].expected_to_match,
+ net::IPNumberMatchesPrefix(ip_number,
+ ip_prefix,
+ prefix_length_in_bits));
+ }
+}
diff --git a/net/base/net_util_win.cc b/net/base/net_util_win.cc
index 244f4ad..a6d37c1 100644
--- a/net/base/net_util_win.cc
+++ b/net/base/net_util_win.cc
@@ -8,6 +8,7 @@
#include "base/string_piece.h"
#include "base/string_util.h"
#include "base/sys_string_conversions.h"
+#include "base/utf_string_conversions.h"
#include "googleurl/src/gurl.h"
#include "net/base/escape.h"
diff --git a/net/base/network_change_notifier.cc b/net/base/network_change_notifier.cc
index aeca2ab..a0bc6b5 100644
--- a/net/base/network_change_notifier.cc
+++ b/net/base/network_change_notifier.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -14,9 +14,22 @@
namespace net {
-// static
-NetworkChangeNotifier*
-NetworkChangeNotifier::CreateDefaultNetworkChangeNotifier() {
+namespace {
+
+// The actual singleton notifier. The class contract forbids usage of the API
+// in ways that would require us to place locks around access to this object.
+// (The prohibition on global non-POD objects makes it tricky to do such a thing
+// anyway.)
+NetworkChangeNotifier* g_network_change_notifier = NULL;
+
+} // namespace
+
+NetworkChangeNotifier::~NetworkChangeNotifier() {
+ DCHECK_EQ(this, g_network_change_notifier);
+ g_network_change_notifier = NULL;
+}
+
+NetworkChangeNotifier* NetworkChangeNotifier::Create() {
#if defined(OS_WIN)
return new NetworkChangeNotifierWin();
#elif defined(OS_LINUX)
@@ -29,4 +42,27 @@
#endif
}
+void NetworkChangeNotifier::AddObserver(Observer* observer) {
+ if (g_network_change_notifier)
+ g_network_change_notifier->observer_list_->AddObserver(observer);
+}
+
+void NetworkChangeNotifier::RemoveObserver(Observer* observer) {
+ if (g_network_change_notifier)
+ g_network_change_notifier->observer_list_->RemoveObserver(observer);
+}
+
+NetworkChangeNotifier::NetworkChangeNotifier()
+ : observer_list_(new ObserverListThreadSafe<Observer>()) {
+ DCHECK(!g_network_change_notifier);
+ g_network_change_notifier = this;
+}
+
+void NetworkChangeNotifier::NotifyObserversOfIPAddressChange() {
+ if (g_network_change_notifier) {
+ g_network_change_notifier->observer_list_->Notify(
+ &Observer::OnIPAddressChanged);
+ }
+}
+
} // namespace net
diff --git a/net/base/network_change_notifier.h b/net/base/network_change_notifier.h
index 67d57ff..e0a8800 100644
--- a/net/base/network_change_notifier.h
+++ b/net/base/network_change_notifier.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -6,13 +6,14 @@
#define NET_BASE_NETWORK_CHANGE_NOTIFIER_H_
#include "base/basictypes.h"
-#include "base/non_thread_safe.h"
+#include "base/observer_list_threadsafe.h"
namespace net {
// NetworkChangeNotifier monitors the system for network changes, and notifies
-// observers on those events.
-class NetworkChangeNotifier : public NonThreadSafe {
+// registered observers of those events. Observers may register on any thread,
+// and will be called back on the thread from which they registered.
+class NetworkChangeNotifier {
public:
class Observer {
public:
@@ -29,21 +30,58 @@
DISALLOW_COPY_AND_ASSIGN(Observer);
};
- NetworkChangeNotifier() {}
- virtual ~NetworkChangeNotifier() {}
+ virtual ~NetworkChangeNotifier();
- // These functions add and remove observers to/from the NetworkChangeNotifier.
- // Each call to AddObserver() must be matched with a corresponding call to
- // RemoveObserver() with the same parameter. Observers must remove themselves
- // before they get deleted, otherwise the NetworkChangeNotifier may try to
- // notify the wrong object.
- virtual void AddObserver(Observer* observer) = 0;
- virtual void RemoveObserver(Observer* observer) = 0;
+ // Creates the process-wide, platform-specific NetworkChangeNotifier. The
+ // caller owns the returned pointer. You may call this on any thread. You
+ // may also avoid creating this entirely (in which case nothing will be
+ // monitored), but if you do create it, you must do so before any other
+ // threads try to access the API below, and it must outlive all other threads
+ // which might try to use it.
+ static NetworkChangeNotifier* Create();
- // This will create the platform specific default NetworkChangeNotifier.
- static NetworkChangeNotifier* CreateDefaultNetworkChangeNotifier();
+#ifdef UNIT_TEST
+ // Like Create(), but for use in tests. The mock object doesn't monitor any
+ // events, it merely rebroadcasts notifications when requested.
+ static NetworkChangeNotifier* CreateMock() {
+ return new NetworkChangeNotifier();
+ }
+#endif
+
+ // Registers |observer| to receive notifications of network changes. The
+ // thread on which this is called is the thread on which |observer| will be
+ // called back with notifications. This is safe to call if Create() has not
+ // been called (as long as it doesn't race the Create() call on another
+ // thread), in which case it will simply do nothing.
+ static void AddObserver(Observer* observer);
+
+ // Unregisters |observer| from receiving notifications. This must be called
+ // on the same thread on which AddObserver() was called. Like AddObserver(),
+ // this is safe to call if Create() has not been called (as long as it doesn't
+ // race the Create() call on another thread), in which case it will simply do
+ // nothing. Technically, it's also safe to call after the notifier object has
+ // been destroyed, if the call doesn't race the notifier's destruction, but
+ // there's no reason to use the API in this risky way, so don't do it.
+ static void RemoveObserver(Observer* observer);
+
+#ifdef UNIT_TEST
+ // Allow unit tests to trigger notifications.
+ static void NotifyObserversOfIPAddressChangeForTests() {
+ NotifyObserversOfIPAddressChange();
+ }
+#endif
+
+ protected:
+ NetworkChangeNotifier();
+
+ // Broadcasts a notification to all registered observers. Note that this
+ // happens asynchronously, even for observers on the current thread, even in
+ // tests.
+ static void NotifyObserversOfIPAddressChange();
private:
+ const scoped_refptr<ObserverListThreadSafe<Observer> > observer_list_;
+
DISALLOW_COPY_AND_ASSIGN(NetworkChangeNotifier);
};
diff --git a/net/base/network_change_notifier_linux.cc b/net/base/network_change_notifier_linux.cc
index e6eba24..22be563 100644
--- a/net/base/network_change_notifier_linux.cc
+++ b/net/base/network_change_notifier_linux.cc
@@ -1,12 +1,139 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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 "net/base/network_change_notifier_linux.h"
+#include <errno.h>
+#include <sys/socket.h>
+
+#include "base/eintr_wrapper.h"
+#include "base/task.h"
+#include "base/thread.h"
+#include "net/base/net_errors.h"
+#include "net/base/network_change_notifier_netlink_linux.h"
+
+// We only post tasks to a child thread we own, so we don't need refcounting.
+DISABLE_RUNNABLE_METHOD_REFCOUNT(net::NetworkChangeNotifierLinux);
+
namespace net {
-NetworkChangeNotifierLinux::NetworkChangeNotifierLinux() {}
-NetworkChangeNotifierLinux::~NetworkChangeNotifierLinux() {}
+namespace {
+
+const int kInvalidSocket = -1;
+
+} // namespace
+
+NetworkChangeNotifierLinux::NetworkChangeNotifierLinux()
+ : notifier_thread_(new base::Thread("NetworkChangeNotifier")),
+ netlink_fd_(kInvalidSocket) {
+ // We create this notifier thread because the notification implementation
+ // needs a MessageLoopForIO, and there's no guarantee that
+ // MessageLoop::current() meets that criterion.
+ base::Thread::Options thread_options(MessageLoop::TYPE_IO, 0);
+ notifier_thread_->StartWithOptions(thread_options);
+ notifier_thread_->message_loop()->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &NetworkChangeNotifierLinux::Init));
+}
+
+NetworkChangeNotifierLinux::~NetworkChangeNotifierLinux() {
+ // We don't need to explicitly Stop(), but doing so allows us to sanity-
+ // check that the notifier thread shut down properly.
+ notifier_thread_->Stop();
+ DCHECK_EQ(kInvalidSocket, netlink_fd_);
+}
+
+void NetworkChangeNotifierLinux::WillDestroyCurrentMessageLoop() {
+ DCHECK(notifier_thread_ != NULL);
+ // We can't check the notifier_thread_'s message_loop(), as it's now 0.
+ // DCHECK_EQ(notifier_thread_->message_loop(), MessageLoop::current());
+
+ if (netlink_fd_ != kInvalidSocket) {
+ if (HANDLE_EINTR(close(netlink_fd_)) != 0)
+ PLOG(ERROR) << "Failed to close socket";
+ netlink_fd_ = kInvalidSocket;
+ netlink_watcher_.StopWatchingFileDescriptor();
+ }
+}
+
+void NetworkChangeNotifierLinux::OnFileCanReadWithoutBlocking(int fd) {
+ DCHECK(notifier_thread_ != NULL);
+ DCHECK_EQ(notifier_thread_->message_loop(), MessageLoop::current());
+
+ DCHECK_EQ(fd, netlink_fd_);
+ ListenForNotifications();
+}
+
+void NetworkChangeNotifierLinux::OnFileCanWriteWithoutBlocking(int /* fd */) {
+ DCHECK(notifier_thread_ != NULL);
+ DCHECK_EQ(notifier_thread_->message_loop(), MessageLoop::current());
+
+ NOTREACHED();
+}
+
+void NetworkChangeNotifierLinux::Init() {
+ DCHECK(notifier_thread_ != NULL);
+ DCHECK_EQ(notifier_thread_->message_loop(), MessageLoop::current());
+
+ netlink_fd_ = InitializeNetlinkSocket();
+ if (netlink_fd_ < 0) {
+ netlink_fd_ = kInvalidSocket;
+ return;
+ }
+ MessageLoop::current()->AddDestructionObserver(this);
+ ListenForNotifications();
+}
+
+void NetworkChangeNotifierLinux::ListenForNotifications() {
+ DCHECK(notifier_thread_ != NULL);
+ DCHECK_EQ(notifier_thread_->message_loop(), MessageLoop::current());
+
+ char buf[4096];
+ int rv = ReadNotificationMessage(buf, arraysize(buf));
+ while (rv > 0) {
+ if (HandleNetlinkMessage(buf, rv)) {
+ LOG(INFO) << "Detected IP address changes.";
+#if defined(OS_CHROMEOS)
+ // TODO(zelidrag): chromium-os:3996 - introduced artificial delay to
+ // work around the issue of proxy initialization before name resolving
+ // is functional in ChromeOS. This should be removed once this bug
+ // is properly fixed.
+ const int kObserverNotificationDelayMS = 500;
+ MessageLoop::current()->PostDelayedTask(FROM_HERE, NewRunnableFunction(
+ &NetworkChangeNotifier::NotifyObserversOfIPAddressChange),
+ kObserverNotificationDelayMS);
+#else
+ NotifyObserversOfIPAddressChange();
+#endif
+ }
+ rv = ReadNotificationMessage(buf, arraysize(buf));
+ }
+
+ if (rv == ERR_IO_PENDING) {
+ rv = MessageLoopForIO::current()->WatchFileDescriptor(netlink_fd_, false,
+ MessageLoopForIO::WATCH_READ, &netlink_watcher_, this);
+ LOG_IF(ERROR, !rv) << "Failed to watch netlink socket: " << netlink_fd_;
+ }
+}
+
+int NetworkChangeNotifierLinux::ReadNotificationMessage(char* buf, size_t len) {
+ DCHECK(notifier_thread_ != NULL);
+ DCHECK_EQ(notifier_thread_->message_loop(), MessageLoop::current());
+
+ DCHECK_NE(len, 0u);
+ DCHECK(buf);
+ memset(buf, 0, sizeof(buf));
+ int rv = recv(netlink_fd_, buf, len, 0);
+ if (rv > 0)
+ return rv;
+
+ DCHECK_NE(rv, 0);
+ if (errno != EAGAIN && errno != EWOULDBLOCK) {
+ PLOG(DFATAL) << "recv";
+ return ERR_FAILED;
+ }
+
+ return ERR_IO_PENDING;
+}
} // namespace net
diff --git a/net/base/network_change_notifier_linux.h b/net/base/network_change_notifier_linux.h
index 78111c9..aca9377 100644
--- a/net/base/network_change_notifier_linux.h
+++ b/net/base/network_change_notifier_linux.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -6,28 +6,54 @@
#define NET_BASE_NETWORK_CHANGE_NOTIFIER_LINUX_H_
#include "base/basictypes.h"
-#include "net/base/network_change_notifier_helper.h"
+#include "base/message_loop.h"
+#include "base/scoped_ptr.h"
+#include "net/base/network_change_notifier.h"
+
+namespace base {
+class Thread;
+}
namespace net {
-class NetworkChangeNotifierLinux : public NetworkChangeNotifier {
+class NetworkChangeNotifierLinux : public MessageLoop::DestructionObserver,
+ public MessageLoopForIO::Watcher,
+ public NetworkChangeNotifier {
public:
NetworkChangeNotifierLinux();
- virtual void AddObserver(Observer* observer) {
- helper_.AddObserver(observer);
- }
-
- virtual void RemoveObserver(Observer* observer) {
- helper_.RemoveObserver(observer);
- }
-
private:
virtual ~NetworkChangeNotifierLinux();
- void OnIPAddressChanged() { helper_.OnIPAddressChanged(); }
+ // MessageLoop::DestructionObserver:
+ virtual void WillDestroyCurrentMessageLoop();
- internal::NetworkChangeNotifierHelper helper_;
+ // MessageLoopForIO::Watcher:
+ virtual void OnFileCanReadWithoutBlocking(int fd);
+ virtual void OnFileCanWriteWithoutBlocking(int /* fd */);
+
+ // Called on the notifier thread to initialize the notification
+ // implementation.
+ void Init();
+
+ // Starts listening for netlink messages. Also handles the messages if there
+ // are any available on the netlink socket.
+ void ListenForNotifications();
+
+ // Attempts to read from the netlink socket into |buf| of length |len|.
+ // Returns the bytes read on synchronous success and ERR_IO_PENDING if the
+ // recv() would block. Otherwise, it returns a net error code.
+ int ReadNotificationMessage(char* buf, size_t len);
+
+ // The thread used to listen for notifications. This relays the notification
+ // to the registered observers without posting back to the thread the object
+ // was created on.
+ scoped_ptr<base::Thread> notifier_thread_;
+
+ // The netlink socket descriptor.
+ int netlink_fd_;
+
+ MessageLoopForIO::FileDescriptorWatcher netlink_watcher_;
DISALLOW_COPY_AND_ASSIGN(NetworkChangeNotifierLinux);
};
diff --git a/net/base/network_change_notifier_mac.cc b/net/base/network_change_notifier_mac.cc
index 5606210..797de6d 100644
--- a/net/base/network_change_notifier_mac.cc
+++ b/net/base/network_change_notifier_mac.cc
@@ -1,168 +1,117 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
-// There are three classes involved here. There's NetworkChangeNotifierMac,
-// which is the Mac specific implementation of NetworkChangeNotifier. It is the
-// class with which clients can register themselves as network change
-// observers. There's NetworkChangeNotifierThread, which is a base::Thread
-// subclass of MessageLoop::TYPE_UI (since it needs a CFRunLoop) that contains
-// the NetworkChangeNotifierImpl. NetworkChangeNotifierImpl is the object
-// that receives the actual OS X notifications and posts them to the
-// NetworkChangeNotifierMac's message loop, so that NetworkChangeNotifierMac
-// can notify all its observers.
-//
-// When NetworkChangeNotifierMac is being deleted, it will delete the
-// NetworkChangeNotifierThread, which will Stop() it and also delete the
-// NetworkChangeNotifierImpl. Therefore, NetworkChangeNotifierImpl and
-// NetworkChangeNotifierThread's lifetimes generally begin after and end before
-// NetworkChangeNotifierMac. There is an edge case where a notification task
-// gets posted to the IO thread, thereby maintaining a reference to
-// NetworkChangeNotifierImpl beyond the lifetime of NetworkChangeNotifierThread.
-// In this case, the notification is cancelled, and NetworkChangeNotifierImpl
-// will be deleted once all notification tasks that reference it have been run.
-
#include "net/base/network_change_notifier_mac.h"
-#include <SystemConfiguration/SCDynamicStore.h>
+
#include <SystemConfiguration/SCDynamicStoreKey.h>
#include <SystemConfiguration/SCSchemaDefinitions.h>
#include <algorithm>
-#include "base/logging.h"
-#include "base/scoped_cftyperef.h"
+
#include "base/thread.h"
+// We only post tasks to a child thread we own, so we don't need refcounting.
+DISABLE_RUNNABLE_METHOD_REFCOUNT(net::NetworkChangeNotifierMac);
+
namespace net {
-namespace {
-
-// NetworkChangeNotifierImpl should be created on a thread with a CFRunLoop,
-// since it requires one to pump notifications. However, it also runs some
-// methods on |notifier_loop_|, because it cannot post calls to |notifier_|
-// since NetworkChangeNotifier is not ref counted in a thread safe manner.
-class NetworkChangeNotifierImpl
- : public base::RefCountedThreadSafe<NetworkChangeNotifierImpl> {
- public:
- NetworkChangeNotifierImpl(MessageLoop* notifier_loop,
- NetworkChangeNotifierMac* notifier);
-
- void Shutdown();
-
- private:
- friend class base::RefCountedThreadSafe<NetworkChangeNotifierImpl>;
- ~NetworkChangeNotifierImpl();
-
- static void DynamicStoreCallback(SCDynamicStoreRef /* store */,
- CFArrayRef changed_keys,
- void* config);
-
- void OnNetworkConfigChange(CFArrayRef changed_keys);
-
- // Runs on |notifier_loop_|.
- void OnIPAddressChanged();
-
- // Raw pointers. Note that |notifier_| _must_ outlive the
- // NetworkChangeNotifierImpl. For lifecycle management details, read the
- // comment at the top of the file.
- MessageLoop* const notifier_loop_;
- NetworkChangeNotifierMac* notifier_;
-
- scoped_cftyperef<CFRunLoopSourceRef> source_;
-
- DISALLOW_COPY_AND_ASSIGN(NetworkChangeNotifierImpl);
-};
-
-NetworkChangeNotifierImpl::NetworkChangeNotifierImpl(
- MessageLoop* notifier_loop, NetworkChangeNotifierMac* notifier)
- : notifier_loop_(notifier_loop),
- notifier_(notifier) {
- DCHECK_EQ(MessageLoop::TYPE_UI, MessageLoop::current()->type());
- SCDynamicStoreContext context = {
- 0, // Version 0.
- this, // User data.
- NULL, // This is not reference counted. No retain function.
- NULL, // This is not reference counted. No release function.
- NULL, // No description for this.
- };
-
- // Get a reference to the dynamic store.
- scoped_cftyperef<SCDynamicStoreRef> store(
- SCDynamicStoreCreate(NULL /* use default allocator */,
- CFSTR("org.chromium"),
- DynamicStoreCallback, &context));
-
- // Create a run loop source for the dynamic store.
- source_.reset(SCDynamicStoreCreateRunLoopSource(
- NULL /* use default allocator */,
- store.get(),
- 0 /* 0 sounds like a fine source order to me! */));
-
- // Add the run loop source to the current run loop.
- CFRunLoopAddSource(CFRunLoopGetCurrent(),
- source_.get(),
- kCFRunLoopCommonModes);
-
- // Set up the notification keys.
- scoped_cftyperef<CFMutableArrayRef> notification_keys(
- CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks));
-
- // Monitor interface changes.
- scoped_cftyperef<CFStringRef> key(
- SCDynamicStoreKeyCreateNetworkGlobalEntity(
- NULL /* default allocator */, kSCDynamicStoreDomainState,
- kSCEntNetInterface));
- CFArrayAppendValue(notification_keys.get(), key.get());
-
- // Monitor IP address changes.
-
- key.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity(
- NULL /* default allocator */, kSCDynamicStoreDomainState,
- kSCEntNetIPv4));
- CFArrayAppendValue(notification_keys.get(), key.get());
-
- key.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity(
- NULL /* default allocator */, kSCDynamicStoreDomainState,
- kSCEntNetIPv6));
- CFArrayAppendValue(notification_keys.get(), key.get());
-
- // Ok, let's ask for notifications!
- // TODO(willchan): Figure out a proper way to handle this rather than crash.
- CHECK(SCDynamicStoreSetNotificationKeys(
- store.get(), notification_keys.get(), NULL));
+NetworkChangeNotifierMac::NetworkChangeNotifierMac()
+ : notifier_thread_(new base::Thread("NetworkChangeNotifier")) {
+ // We create this notifier thread because the notification implementation
+ // needs a thread with a CFRunLoop, and there's no guarantee that
+ // MessageLoop::current() meets that criterion.
+ base::Thread::Options thread_options(MessageLoop::TYPE_UI, 0);
+ notifier_thread_->StartWithOptions(thread_options);
+ // TODO(willchan): Look to see if there's a better signal for when it's ok to
+ // initialize this, rather than just delaying it by a fixed time.
+ const int kNotifierThreadInitializationDelayMS = 1000;
+ notifier_thread_->message_loop()->PostDelayedTask(FROM_HERE,
+ NewRunnableMethod(this, &NetworkChangeNotifierMac::Init),
+ kNotifierThreadInitializationDelayMS);
}
-NetworkChangeNotifierImpl::~NetworkChangeNotifierImpl() {
- CFRunLoopRemoveSource(CFRunLoopGetCurrent(),
- source_.get(),
- kCFRunLoopCommonModes);
-}
-
-void NetworkChangeNotifierImpl::Shutdown() {
- CHECK(notifier_);
- notifier_ = NULL;
+NetworkChangeNotifierMac::~NetworkChangeNotifierMac() {
+ // We don't need to explicitly Stop(), but doing so allows us to sanity-
+ // check that the notifier thread shut down properly.
+ notifier_thread_->Stop();
+ DCHECK(run_loop_source_ == NULL);
}
// static
-void NetworkChangeNotifierImpl::DynamicStoreCallback(
+void NetworkChangeNotifierMac::DynamicStoreCallback(
SCDynamicStoreRef /* store */,
CFArrayRef changed_keys,
void* config) {
- NetworkChangeNotifierImpl* net_config =
- static_cast<NetworkChangeNotifierImpl*>(config);
+ NetworkChangeNotifierMac* net_config =
+ static_cast<NetworkChangeNotifierMac*>(config);
net_config->OnNetworkConfigChange(changed_keys);
}
-void NetworkChangeNotifierImpl::OnNetworkConfigChange(CFArrayRef changed_keys) {
+void NetworkChangeNotifierMac::WillDestroyCurrentMessageLoop() {
+ DCHECK(notifier_thread_ != NULL);
+ // We can't check the notifier_thread_'s message_loop(), as it's now 0.
+ // DCHECK_EQ(notifier_thread_->message_loop(), MessageLoop::current());
+
+ DCHECK(run_loop_source_ != NULL);
+ CFRunLoopRemoveSource(CFRunLoopGetCurrent(), run_loop_source_.get(),
+ kCFRunLoopCommonModes);
+ run_loop_source_.reset();
+}
+
+void NetworkChangeNotifierMac::Init() {
+ DCHECK(notifier_thread_ != NULL);
+ DCHECK_EQ(notifier_thread_->message_loop(), MessageLoop::current());
+
+ // Add a run loop source for a dynamic store to the current run loop.
+ SCDynamicStoreContext context = {
+ 0, // Version 0.
+ this, // User data.
+ NULL, // This is not reference counted. No retain function.
+ NULL, // This is not reference counted. No release function.
+ NULL, // No description for this.
+ };
+ scoped_cftyperef<SCDynamicStoreRef> store(SCDynamicStoreCreate(
+ NULL, CFSTR("org.chromium"), DynamicStoreCallback, &context));
+ run_loop_source_.reset(SCDynamicStoreCreateRunLoopSource(
+ NULL, store.get(), 0));
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop_source_.get(),
+ kCFRunLoopCommonModes);
+
+ // Set up notifications for interface and IP address changes.
+ scoped_cftyperef<CFMutableArrayRef> notification_keys(
+ CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks));
+ scoped_cftyperef<CFStringRef> key(SCDynamicStoreKeyCreateNetworkGlobalEntity(
+ NULL, kSCDynamicStoreDomainState, kSCEntNetInterface));
+ CFArrayAppendValue(notification_keys.get(), key.get());
+ key.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity(
+ NULL, kSCDynamicStoreDomainState, kSCEntNetIPv4));
+ CFArrayAppendValue(notification_keys.get(), key.get());
+ key.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity(
+ NULL, kSCDynamicStoreDomainState, kSCEntNetIPv6));
+ CFArrayAppendValue(notification_keys.get(), key.get());
+
+ // Set the notification keys. This starts us receiving notifications.
+ bool ret = SCDynamicStoreSetNotificationKeys(
+ store.get(), notification_keys.get(), NULL);
+ // TODO(willchan): Figure out a proper way to handle this rather than crash.
+ CHECK(ret);
+
+ MessageLoop::current()->AddDestructionObserver(this);
+}
+
+void NetworkChangeNotifierMac::OnNetworkConfigChange(CFArrayRef changed_keys) {
+ DCHECK(notifier_thread_ != NULL);
+ DCHECK_EQ(notifier_thread_->message_loop(), MessageLoop::current());
+
for (CFIndex i = 0; i < CFArrayGetCount(changed_keys); ++i) {
CFStringRef key = static_cast<CFStringRef>(
CFArrayGetValueAtIndex(changed_keys, i));
if (CFStringHasSuffix(key, kSCEntNetIPv4) ||
CFStringHasSuffix(key, kSCEntNetIPv6)) {
- notifier_loop_->PostTask(
- FROM_HERE,
- NewRunnableMethod(
- this,
- &NetworkChangeNotifierImpl::OnIPAddressChanged));
- } else if (CFStringHasSuffix(key, kSCEntNetInterface)) {
+ NotifyObserversOfIPAddressChange();
+ return;
+ }
+ if (CFStringHasSuffix(key, kSCEntNetInterface)) {
// TODO(willchan): Does not appear to be working. Look into this.
// Perhaps this isn't needed anyway.
} else {
@@ -171,60 +120,4 @@
}
}
-void NetworkChangeNotifierImpl::OnIPAddressChanged() {
- // If |notifier_| doesn't exist, then that means we're shutting down, so
- // notifications are all cancelled.
- if (notifier_)
- notifier_->OnIPAddressChanged();
-}
-
-class NetworkChangeNotifierThread : public base::Thread {
- public:
- NetworkChangeNotifierThread(MessageLoop* notifier_loop,
- NetworkChangeNotifierMac* notifier);
- ~NetworkChangeNotifierThread();
-
- protected:
- virtual void Init();
-
- private:
- MessageLoop* const notifier_loop_;
- NetworkChangeNotifierMac* const notifier_;
- scoped_refptr<NetworkChangeNotifierImpl> notifier_impl_;
-
- DISALLOW_COPY_AND_ASSIGN(NetworkChangeNotifierThread);
-};
-
-NetworkChangeNotifierThread::NetworkChangeNotifierThread(
- MessageLoop* notifier_loop, NetworkChangeNotifierMac* notifier)
- : base::Thread("NetworkChangeNotifier"),
- notifier_loop_(notifier_loop),
- notifier_(notifier) {}
-
-NetworkChangeNotifierThread::~NetworkChangeNotifierThread() {
- notifier_impl_->Shutdown();
- Stop();
-}
-
-// Note that |notifier_impl_| is initialized on the network change
-// notifier thread, not whatever thread constructs the
-// NetworkChangeNotifierThread object. This is important, because this thread
-// is the one that has a CFRunLoop.
-void NetworkChangeNotifierThread::Init() {
- notifier_impl_ =
- new NetworkChangeNotifierImpl(notifier_loop_, notifier_);
-}
-
-} // namespace
-
-NetworkChangeNotifierMac::NetworkChangeNotifierMac()
- : notifier_thread_(
- new NetworkChangeNotifierThread(MessageLoop::current(), this)) {
- base::Thread::Options thread_options;
- thread_options.message_loop_type = MessageLoop::TYPE_UI;
- notifier_thread_->StartWithOptions(thread_options);
-}
-
-NetworkChangeNotifierMac::~NetworkChangeNotifierMac() {}
-
} // namespace net
diff --git a/net/base/network_change_notifier_mac.h b/net/base/network_change_notifier_mac.h
index cbf0128..c85f244 100644
--- a/net/base/network_change_notifier_mac.h
+++ b/net/base/network_change_notifier_mac.h
@@ -1,47 +1,56 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
#ifndef NET_BASE_NETWORK_CHANGE_NOTIFIER_MAC_H_
#define NET_BASE_NETWORK_CHANGE_NOTIFIER_MAC_H_
+#include <SystemConfiguration/SCDynamicStore.h>
+
#include "base/basictypes.h"
-#include "base/ref_counted.h"
+#include "base/message_loop.h"
+#include "base/scoped_cftyperef.h"
#include "base/scoped_ptr.h"
#include "net/base/network_change_notifier.h"
-#include "net/base/network_change_notifier_helper.h"
namespace base {
class Thread;
-} // namespace base
+}
namespace net {
-class NetworkChangeNotifierMac : public NetworkChangeNotifier {
+class NetworkChangeNotifierMac : public MessageLoop::DestructionObserver,
+ public NetworkChangeNotifier {
public:
NetworkChangeNotifierMac();
- void OnIPAddressChanged() { helper_.OnIPAddressChanged(); }
-
- // NetworkChangeNotifier methods:
-
- virtual void AddObserver(Observer* observer) {
- helper_.AddObserver(observer);
- }
-
- virtual void RemoveObserver(Observer* observer) {
- helper_.RemoveObserver(observer);
- }
-
private:
- friend class base::RefCounted<NetworkChangeNotifierMac>;
-
virtual ~NetworkChangeNotifierMac();
- // Receives the OS X network change notifications on this thread.
- const scoped_ptr<base::Thread> notifier_thread_;
+ // Called back by OS. Calls OnNetworkConfigChange().
+ static void DynamicStoreCallback(SCDynamicStoreRef /* store */,
+ CFArrayRef changed_keys,
+ void* config);
- internal::NetworkChangeNotifierHelper helper_;
+ // MessageLoop::DestructionObserver:
+ virtual void WillDestroyCurrentMessageLoop();
+
+ // Called on the notifier thread to initialize the notification
+ // implementation. The SystemConfiguration calls in this function can lead to
+ // contention early on, so we invoke this function later on in startup to keep
+ // it fast.
+ void Init();
+
+ // Called by DynamicStoreCallback() when something about the network config
+ // changes.
+ void OnNetworkConfigChange(CFArrayRef changed_keys);
+
+ // The thread used to listen for notifications. This relays the notification
+ // to the registered observers without posting back to the thread the object
+ // was created on.
+ scoped_ptr<base::Thread> notifier_thread_;
+
+ scoped_cftyperef<CFRunLoopSourceRef> run_loop_source_;
DISALLOW_COPY_AND_ASSIGN(NetworkChangeNotifierMac);
};
diff --git a/net/base/network_change_notifier_netlink_linux.cc b/net/base/network_change_notifier_netlink_linux.cc
new file mode 100644
index 0000000..5f71f45
--- /dev/null
+++ b/net/base/network_change_notifier_netlink_linux.cc
@@ -0,0 +1,95 @@
+// 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 "net/base/network_change_notifier_netlink_linux.h"
+
+#include <fcntl.h>
+// socket.h is needed to define types for the linux kernel header netlink.h
+// so it needs to come before netlink.h.
+#include <sys/socket.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "base/logging.h"
+
+namespace {
+
+// Return true on success, false on failure.
+// Too small a function to bother putting in a library?
+bool SetNonBlocking(int fd) {
+ int flags = fcntl(fd, F_GETFL, 0);
+ if (-1 == flags)
+ return false;
+ return fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0 ? true : false;
+}
+
+} // namespace
+
+int InitializeNetlinkSocket() {
+ int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+ if (sock < 0) {
+ PLOG(ERROR) << "Error creating netlink socket";
+ return -1;
+ }
+
+ if (!SetNonBlocking(sock)) {
+ PLOG(ERROR) << "Failed to set netlink socket to non-blocking mode.";
+ if (close(sock) != 0)
+ PLOG(ERROR) << "Failed to close socket";
+ return -1;
+ }
+
+ struct sockaddr_nl local_addr;
+ memset(&local_addr, 0, sizeof(local_addr));
+ local_addr.nl_family = AF_NETLINK;
+ local_addr.nl_pid = getpid();
+ local_addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_NOTIFY;
+ int ret = bind(sock, reinterpret_cast<struct sockaddr*>(&local_addr),
+ sizeof(local_addr));
+ if (ret < 0) {
+ PLOG(ERROR) << "Error binding netlink socket";
+ if (close(sock) != 0)
+ PLOG(ERROR) << "Failed to close socket";
+ return -1;
+ }
+
+ return sock;
+}
+
+bool HandleNetlinkMessage(char* buf, size_t len) {
+ const struct nlmsghdr* netlink_message_header =
+ reinterpret_cast<struct nlmsghdr*>(buf);
+ DCHECK(netlink_message_header);
+ for (; NLMSG_OK(netlink_message_header, len);
+ netlink_message_header = NLMSG_NEXT(netlink_message_header, len)) {
+ int netlink_message_type = netlink_message_header->nlmsg_type;
+ switch (netlink_message_type) {
+ case NLMSG_DONE:
+ NOTREACHED()
+ << "This is a monitoring netlink socket. It should never be done.";
+ return false;
+ case NLMSG_ERROR:
+ LOG(ERROR) << "Unexpected netlink error.";
+ return false;
+ // During IP address changes, we will see all these messages. Only fire
+ // the notification when we get a new address or remove an address. We
+ // may still end up notifying observers more than strictly necessary, but
+ // if the primary interface goes down and back up, then this is necessary.
+ case RTM_NEWADDR:
+ case RTM_DELADDR:
+ return true;
+ case RTM_NEWLINK:
+ case RTM_DELLINK:
+ return false;
+ default:
+ LOG(DFATAL) << "Received unexpected netlink message type: "
+ << netlink_message_type;
+ return false;
+ }
+ }
+
+ return false;
+}
diff --git a/net/base/network_change_notifier_netlink_linux.h b/net/base/network_change_notifier_netlink_linux.h
new file mode 100644
index 0000000..c06fdc7
--- /dev/null
+++ b/net/base/network_change_notifier_netlink_linux.h
@@ -0,0 +1,20 @@
+// 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.
+
+// This file is to hide the netlink implementation details since the netlink.h
+// header contains a struct net; which conflicts with the net namespace. So we
+// separate out all the netlink stuff into these files.
+
+#ifndef NET_BASE_NETWORK_CHANGE_NOTIFIER_NETLINK_LINUX_H_
+#define NET_BASE_NETWORK_CHANGE_NOTIFIER_NETLINK_LINUX_H_
+
+#include <cstddef>
+
+// Returns the file descriptor if successful. Otherwise, returns -1.
+int InitializeNetlinkSocket();
+
+// Returns true if a network change has been detected, otherwise returns false.
+bool HandleNetlinkMessage(char* buf, size_t len);
+
+#endif // NET_BASE_NETWORK_CHANGE_NOTIFIER_NETLINK_LINUX_H_
diff --git a/net/base/network_change_notifier_win.cc b/net/base/network_change_notifier_win.cc
index 711bd66..7bc5ddf 100644
--- a/net/base/network_change_notifier_win.cc
+++ b/net/base/network_change_notifier_win.cc
@@ -1,12 +1,40 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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 "net/base/network_change_notifier_win.h"
+#include <iphlpapi.h>
+#include <winsock2.h>
+
+#pragma comment(lib, "iphlpapi.lib")
+
namespace net {
-NetworkChangeNotifierWin::NetworkChangeNotifierWin() {}
-NetworkChangeNotifierWin::~NetworkChangeNotifierWin() {}
+NetworkChangeNotifierWin::NetworkChangeNotifierWin() {
+ memset(&addr_overlapped_, 0, sizeof addr_overlapped_);
+ addr_overlapped_.hEvent = WSACreateEvent();
+ WatchForAddressChange();
+}
+
+NetworkChangeNotifierWin::~NetworkChangeNotifierWin() {
+ CancelIPChangeNotify(&addr_overlapped_);
+ addr_watcher_.StopWatching();
+ WSACloseEvent(addr_overlapped_.hEvent);
+}
+
+void NetworkChangeNotifierWin::OnObjectSignaled(HANDLE object) {
+ NotifyObserversOfIPAddressChange();
+
+ // Start watching for the next address change.
+ WatchForAddressChange();
+}
+
+void NetworkChangeNotifierWin::WatchForAddressChange() {
+ HANDLE handle = NULL;
+ DWORD ret = NotifyAddrChange(&handle, &addr_overlapped_);
+ CHECK(ret == ERROR_IO_PENDING);
+ addr_watcher_.StartWatching(addr_overlapped_.hEvent, this);
+}
} // namespace net
diff --git a/net/base/network_change_notifier_win.h b/net/base/network_change_notifier_win.h
index b208ad7..2077ca8 100644
--- a/net/base/network_change_notifier_win.h
+++ b/net/base/network_change_notifier_win.h
@@ -1,33 +1,34 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
#ifndef NET_BASE_NETWORK_CHANGE_NOTIFIER_WIN_H_
#define NET_BASE_NETWORK_CHANGE_NOTIFIER_WIN_H_
+#include <windows.h>
+
#include "base/basictypes.h"
-#include "net/base/network_change_notifier_helper.h"
+#include "base/object_watcher.h"
+#include "net/base/network_change_notifier.h"
namespace net {
-class NetworkChangeNotifierWin : public NetworkChangeNotifier {
+class NetworkChangeNotifierWin : public NetworkChangeNotifier,
+ public base::ObjectWatcher::Delegate {
public:
NetworkChangeNotifierWin();
- virtual void AddObserver(Observer* observer) {
- helper_.AddObserver(observer);
- }
-
- virtual void RemoveObserver(Observer* observer) {
- helper_.RemoveObserver(observer);
- }
-
private:
virtual ~NetworkChangeNotifierWin();
- void OnIPAddressChanged() { helper_.OnIPAddressChanged(); }
+ // ObjectWatcher::Delegate methods:
+ virtual void OnObjectSignaled(HANDLE object);
- internal::NetworkChangeNotifierHelper helper_;
+ // Begins listening for a single subsequent address change.
+ void WatchForAddressChange();
+
+ base::ObjectWatcher addr_watcher_;
+ OVERLAPPED addr_overlapped_;
DISALLOW_COPY_AND_ASSIGN(NetworkChangeNotifierWin);
};
diff --git a/net/base/nss_memio.c b/net/base/nss_memio.c
index 341cfee..5f7fd00 100644
--- a/net/base/nss_memio.c
+++ b/net/base/nss_memio.c
@@ -362,7 +362,7 @@
void memio_SetPeerName(PRFileDesc *fd, const PRNetAddr *peername)
{
PRFileDesc *memiofd = PR_GetIdentitiesLayer(fd, memio_identity);
- struct PRFilePrivate *secret = memiofd->secret;
+ struct PRFilePrivate *secret = memiofd->secret;
secret->peername = *peername;
}
diff --git a/net/base/nss_memio.h b/net/base/nss_memio.h
index 0bee53e..a2b642a 100644
--- a/net/base/nss_memio.h
+++ b/net/base/nss_memio.h
@@ -6,6 +6,8 @@
#ifndef __MEMIO_H
#define __MEMIO_H
+#include <stddef.h>
+
#ifdef __cplusplus
extern "C" {
#endif
diff --git a/net/base/platform_mime_util_linux.cc b/net/base/platform_mime_util_linux.cc
index 7debe35..b39fe27 100644
--- a/net/base/platform_mime_util_linux.cc
+++ b/net/base/platform_mime_util_linux.cc
@@ -38,8 +38,37 @@
return true;
}
+struct MimeToExt {
+ const char* mime_type;
+ const char* ext;
+};
+
+const struct MimeToExt mime_type_ext_map[] = {
+ {"image/jpeg", "jpg"},
+ {"image/png", "png"},
+ {"image/gif", "gif"},
+ {"text/html", "html"},
+ {"video/mp4", "mp4"},
+ {"video/mpeg", "mpg"},
+ {"audio/mpeg", "mp3"},
+ {"text/plain", "txt"},
+ {"application/pdf", "pdf"},
+ {"application/x-tar", "tar"}
+};
+
bool PlatformMimeUtil::GetPreferredExtensionForMimeType(
const std::string& mime_type, FilePath::StringType* ext) const {
+
+ for (size_t x = 0;
+ x < (sizeof(mime_type_ext_map) / sizeof(MimeToExt));
+ x++) {
+ if (mime_type_ext_map[x].mime_type == mime_type) {
+ *ext = mime_type_ext_map[x].ext;
+ return true;
+ }
+ }
+
+ // TODO(dhg): Fix this the right way by implementing whats said below.
// Unlike GetPlatformMimeTypeFromExtension, this method doesn't have a
// default list that it uses, but for now we are also returning false since
// this doesn't really matter as much under Linux.
diff --git a/net/base/platform_mime_util_win.cc b/net/base/platform_mime_util_win.cc
index b07a02d..26d924b 100644
--- a/net/base/platform_mime_util_win.cc
+++ b/net/base/platform_mime_util_win.cc
@@ -7,7 +7,7 @@
#include "net/base/platform_mime_util.h"
#include "base/registry.h"
-#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
namespace net {
diff --git a/net/base/run_all_unittests.cc b/net/base/run_all_unittests.cc
index 9786f6e..85bda7d 100644
--- a/net/base/run_all_unittests.cc
+++ b/net/base/run_all_unittests.cc
@@ -1,39 +1,28 @@
-// Copyright 2008, Google Inc.
-// All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-// * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-// * Redistributions in binary form must reproduce the above
-// copyright notice, this list of conditions and the following disclaimer
-// in the documentation and/or other materials provided with the
-// distribution.
-// * Neither the name of Google Inc. nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// 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 "build/build_config.h"
#include "base/histogram.h"
#include "net/base/net_test_suite.h"
+#if defined(OS_WIN)
+#include "net/socket/ssl_client_socket_nss_factory.h"
+#endif
int main(int argc, char** argv) {
// Record histograms, so we can get histograms data in tests.
StatisticsRecorder recorder;
NetTestSuite test_suite(argc, argv);
+
+#if defined(OS_WIN)
+ // Use NSS for SSL on Windows. TODO(wtc): this should eventually be hidden
+ // inside DefaultClientSocketFactory::CreateSSLClientSocket.
+ net::ClientSocketFactory::SetSSLClientSocketFactory(
+ net::SSLClientSocketNSSFactory);
+ // We want to be sure to init NSPR on the main thread.
+ base::EnsureNSPRInit();
+#endif
+
// TODO(phajdan.jr): Enforce test isolation, http://crbug.com/12710.
return test_suite.Run();
}
diff --git a/net/base/sdch_filter_unittest.cc b/net/base/sdch_filter_unittest.cc
index 99a3ffd..675a135 100644
--- a/net/base/sdch_filter_unittest.cc
+++ b/net/base/sdch_filter_unittest.cc
@@ -111,7 +111,7 @@
size_t input_block_length,
const size_t output_buffer_length,
Filter* filter, std::string* output) {
- CHECK(input_block_length > 0);
+ CHECK_GT(input_block_length, 0u);
Filter::FilterStatus status(Filter::FILTER_NEED_MORE_DATA);
size_t source_index = 0;
scoped_array<char> output_buffer(new char[output_buffer_length]);
@@ -375,8 +375,8 @@
// list of dictionaries, so the filter should error out immediately.
std::string dictionary_hash_postfix("4abcd\0", 6);
- CHECK(dictionary_hash_postfix.size() <
- static_cast<size_t>(input_buffer_size));
+ CHECK_LT(dictionary_hash_postfix.size(),
+ static_cast<size_t>(input_buffer_size));
memcpy(input_buffer, dictionary_hash_postfix.data(),
dictionary_hash_postfix.size());
filter->FlushStreamBuffer(dictionary_hash_postfix.size());
@@ -792,7 +792,7 @@
8, // DEF_MEM_LEVEL
Z_DEFAULT_STRATEGY);
- CHECK(code == Z_OK);
+ CHECK_EQ(Z_OK, code);
// Fill in zlib control block
zlib_stream.next_in = bit_cast<Bytef*>(input.data());
@@ -819,7 +819,7 @@
// Header value we generate:
const char kGZipHeader[] = { '\037', '\213', '\010', '\000', '\000',
'\000', '\000', '\000', '\002', '\377' };
- CHECK(zlib_stream.avail_out > sizeof(kGZipHeader));
+ CHECK_GT(zlib_stream.avail_out, sizeof(kGZipHeader));
memcpy(zlib_stream.next_out, kGZipHeader, sizeof(kGZipHeader));
zlib_stream.next_out += sizeof(kGZipHeader);
zlib_stream.avail_out -= sizeof(kGZipHeader);
@@ -860,9 +860,9 @@
// First try with a large buffer (larger than test input, or compressed data).
const size_t kLargeInputBufferSize(1000); // Used internally in filters.
- CHECK(kLargeInputBufferSize > gzip_compressed_sdch.size());
- CHECK(kLargeInputBufferSize > sdch_compressed.size());
- CHECK(kLargeInputBufferSize > expanded_.size());
+ CHECK_GT(kLargeInputBufferSize, gzip_compressed_sdch.size());
+ CHECK_GT(kLargeInputBufferSize, sdch_compressed.size());
+ CHECK_GT(kLargeInputBufferSize, expanded_.size());
MockFilterContext filter_context(kLargeInputBufferSize);
filter_context.SetURL(url);
scoped_ptr<Filter> filter(Filter::Factory(filter_types, filter_context));
@@ -884,11 +884,11 @@
// Next try with a mid-sized internal buffer size.
const size_t kMidSizedInputBufferSize(100);
// Buffer should be big enough to swallow whole gzip content.
- CHECK(kMidSizedInputBufferSize > gzip_compressed_sdch.size());
+ CHECK_GT(kMidSizedInputBufferSize, gzip_compressed_sdch.size());
// Buffer should be small enough that entire SDCH content can't fit.
// We'll go even further, and force the chain to flush the buffer between the
// two filters more than once (that is why we multiply by 2).
- CHECK(kMidSizedInputBufferSize * 2 < sdch_compressed.size());
+ CHECK_LT(kMidSizedInputBufferSize * 2, sdch_compressed.size());
filter_context.SetBufferSize(kMidSizedInputBufferSize);
filter_context.SetURL(url);
filter.reset(Filter::Factory(filter_types, filter_context));
diff --git a/net/base/sdch_manager.cc b/net/base/sdch_manager.cc
index b279749..6a8709b 100644
--- a/net/base/sdch_manager.cc
+++ b/net/base/sdch_manager.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -122,7 +122,7 @@
global_->sdch_enabled_ = true;
}
-const bool SdchManager::IsInSupportedDomain(const GURL& url) {
+bool SdchManager::IsInSupportedDomain(const GURL& url) {
if (!sdch_enabled_ )
return false;
if (!supported_domain_.empty() &&
diff --git a/net/base/sdch_manager.h b/net/base/sdch_manager.h
index 67ca5e5..23d100d 100644
--- a/net/base/sdch_manager.h
+++ b/net/base/sdch_manager.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -274,7 +274,7 @@
// supported domain (i.e., not blacklisted, and either the specific supported
// domain, or all domains were assumed supported). If it is blacklist, reduce
// by 1 the number of times it will be reported as blacklisted.
- const bool IsInSupportedDomain(const GURL& url);
+ bool IsInSupportedDomain(const GURL& url);
// Schedule the URL fetching to load a dictionary. This will always return
// before the dictionary is actually loaded and added.
diff --git a/net/base/ssl_cipher_suite_names.cc b/net/base/ssl_cipher_suite_names.cc
new file mode 100644
index 0000000..dfff63f
--- /dev/null
+++ b/net/base/ssl_cipher_suite_names.cc
@@ -0,0 +1,350 @@
+// 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 "net/base/ssl_cipher_suite_names.h"
+
+#include <stdlib.h>
+
+#include "base/logging.h"
+
+// Rather than storing the names of all the ciphersuites we eliminate the
+// redundancy and break each cipher suite into a key exchange method, cipher
+// and mac. For all the ciphersuites in the IANA registry, we extract each of
+// those components from the name, number them and pack the result into a
+// 16-bit number thus:
+// (MSB to LSB)
+// <4 bits> unused
+// <5 bits> key exchange
+// <4 bits> cipher
+// <3 bits> mac
+
+// The following tables were generated by ssl_cipher_suite_names_generate.go,
+// found in the same directory as this file.
+
+struct CipherSuite {
+ uint16 cipher_suite, encoded;
+};
+
+static const struct CipherSuite kCipherSuites[] = {
+ {0x0, 0x0}, // TLS_NULL_WITH_NULL_NULL
+ {0x1, 0x81}, // TLS_RSA_WITH_NULL_MD5
+ {0x2, 0x82}, // TLS_RSA_WITH_NULL_SHA
+ {0x3, 0x109}, // TLS_RSA_EXPORT_WITH_RC4_40_MD5
+ {0x4, 0x91}, // TLS_RSA_WITH_RC4_128_MD5
+ {0x5, 0x92}, // TLS_RSA_WITH_RC4_128_SHA
+ {0x6, 0x119}, // TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5
+ {0x7, 0xa2}, // TLS_RSA_WITH_IDEA_CBC_SHA
+ {0x8, 0x12a}, // TLS_RSA_EXPORT_WITH_DES40_CBC_SHA
+ {0x9, 0xb2}, // TLS_RSA_WITH_DES_CBC_SHA
+ {0xa, 0xba}, // TLS_RSA_WITH_3DES_EDE_CBC_SHA
+ {0xb, 0x1aa}, // TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA
+ {0xc, 0x232}, // TLS_DH_DSS_WITH_DES_CBC_SHA
+ {0xd, 0x23a}, // TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA
+ {0xe, 0x2aa}, // TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA
+ {0xf, 0x332}, // TLS_DH_RSA_WITH_DES_CBC_SHA
+ {0x10, 0x33a}, // TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA
+ {0x11, 0x3aa}, // TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA
+ {0x12, 0x432}, // TLS_DHE_DSS_WITH_DES_CBC_SHA
+ {0x13, 0x43a}, // TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA
+ {0x14, 0x4aa}, // TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA
+ {0x15, 0x532}, // TLS_DHE_RSA_WITH_DES_CBC_SHA
+ {0x16, 0x53a}, // TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA
+ {0x17, 0x589}, // TLS_DH_anon_EXPORT_WITH_RC4_40_MD5
+ {0x18, 0x611}, // TLS_DH_anon_WITH_RC4_128_MD5
+ {0x19, 0x5aa}, // TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA
+ {0x1a, 0x632}, // TLS_DH_anon_WITH_DES_CBC_SHA
+ {0x1b, 0x63a}, // TLS_DH_anon_WITH_3DES_EDE_CBC_SHA
+ {0x1e, 0x6b2}, // TLS_KRB5_WITH_DES_CBC_SHA
+ {0x1f, 0x6ba}, // TLS_KRB5_WITH_3DES_EDE_CBC_SHA
+ {0x20, 0x692}, // TLS_KRB5_WITH_RC4_128_SHA
+ {0x21, 0x6a2}, // TLS_KRB5_WITH_IDEA_CBC_SHA
+ {0x22, 0x6b1}, // TLS_KRB5_WITH_DES_CBC_MD5
+ {0x23, 0x6b9}, // TLS_KRB5_WITH_3DES_EDE_CBC_MD5
+ {0x24, 0x691}, // TLS_KRB5_WITH_RC4_128_MD5
+ {0x25, 0x6a1}, // TLS_KRB5_WITH_IDEA_CBC_MD5
+ {0x26, 0x742}, // TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA
+ {0x27, 0x71a}, // TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA
+ {0x28, 0x70a}, // TLS_KRB5_EXPORT_WITH_RC4_40_SHA
+ {0x29, 0x741}, // TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5
+ {0x2a, 0x719}, // TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5
+ {0x2b, 0x709}, // TLS_KRB5_EXPORT_WITH_RC4_40_MD5
+ {0x2c, 0x782}, // TLS_PSK_WITH_NULL_SHA
+ {0x2d, 0x802}, // TLS_DHE_PSK_WITH_NULL_SHA
+ {0x2e, 0x882}, // TLS_RSA_PSK_WITH_NULL_SHA
+ {0x2f, 0xca}, // TLS_RSA_WITH_AES_128_CBC_SHA
+ {0x30, 0x24a}, // TLS_DH_DSS_WITH_AES_128_CBC_SHA
+ {0x31, 0x34a}, // TLS_DH_RSA_WITH_AES_128_CBC_SHA
+ {0x32, 0x44a}, // TLS_DHE_DSS_WITH_AES_128_CBC_SHA
+ {0x33, 0x54a}, // TLS_DHE_RSA_WITH_AES_128_CBC_SHA
+ {0x34, 0x64a}, // TLS_DH_anon_WITH_AES_128_CBC_SHA
+ {0x35, 0xd2}, // TLS_RSA_WITH_AES_256_CBC_SHA
+ {0x36, 0x252}, // TLS_DH_DSS_WITH_AES_256_CBC_SHA
+ {0x37, 0x352}, // TLS_DH_RSA_WITH_AES_256_CBC_SHA
+ {0x38, 0x452}, // TLS_DHE_DSS_WITH_AES_256_CBC_SHA
+ {0x39, 0x552}, // TLS_DHE_RSA_WITH_AES_256_CBC_SHA
+ {0x3a, 0x652}, // TLS_DH_anon_WITH_AES_256_CBC_SHA
+ {0x3b, 0x83}, // TLS_RSA_WITH_NULL_SHA256
+ {0x3c, 0xcb}, // TLS_RSA_WITH_AES_128_CBC_SHA256
+ {0x3d, 0xd3}, // TLS_RSA_WITH_AES_256_CBC_SHA256
+ {0x3e, 0x24b}, // TLS_DH_DSS_WITH_AES_128_CBC_SHA256
+ {0x3f, 0x34b}, // TLS_DH_RSA_WITH_AES_128_CBC_SHA256
+ {0x40, 0x44b}, // TLS_DHE_DSS_WITH_AES_128_CBC_SHA256
+ {0x41, 0xda}, // TLS_RSA_WITH_CAMELLIA_128_CBC_SHA
+ {0x42, 0x25a}, // TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA
+ {0x43, 0x35a}, // TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA
+ {0x44, 0x45a}, // TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA
+ {0x45, 0x55a}, // TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA
+ {0x46, 0x65a}, // TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA
+ {0x67, 0x54b}, // TLS_DHE_RSA_WITH_AES_128_CBC_SHA256
+ {0x68, 0x253}, // TLS_DH_DSS_WITH_AES_256_CBC_SHA256
+ {0x69, 0x353}, // TLS_DH_RSA_WITH_AES_256_CBC_SHA256
+ {0x6a, 0x453}, // TLS_DHE_DSS_WITH_AES_256_CBC_SHA256
+ {0x6b, 0x553}, // TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
+ {0x6c, 0x64b}, // TLS_DH_anon_WITH_AES_128_CBC_SHA256
+ {0x6d, 0x653}, // TLS_DH_anon_WITH_AES_256_CBC_SHA256
+ {0x84, 0xe2}, // TLS_RSA_WITH_CAMELLIA_256_CBC_SHA
+ {0x85, 0x262}, // TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA
+ {0x86, 0x362}, // TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA
+ {0x87, 0x462}, // TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA
+ {0x88, 0x562}, // TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA
+ {0x89, 0x662}, // TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA
+ {0x8a, 0x792}, // TLS_PSK_WITH_RC4_128_SHA
+ {0x8b, 0x7ba}, // TLS_PSK_WITH_3DES_EDE_CBC_SHA
+ {0x8c, 0x7ca}, // TLS_PSK_WITH_AES_128_CBC_SHA
+ {0x8d, 0x7d2}, // TLS_PSK_WITH_AES_256_CBC_SHA
+ {0x8e, 0x812}, // TLS_DHE_PSK_WITH_RC4_128_SHA
+ {0x8f, 0x83a}, // TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA
+ {0x90, 0x84a}, // TLS_DHE_PSK_WITH_AES_128_CBC_SHA
+ {0x91, 0x852}, // TLS_DHE_PSK_WITH_AES_256_CBC_SHA
+ {0x92, 0x892}, // TLS_RSA_PSK_WITH_RC4_128_SHA
+ {0x93, 0x8ba}, // TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA
+ {0x94, 0x8ca}, // TLS_RSA_PSK_WITH_AES_128_CBC_SHA
+ {0x95, 0x8d2}, // TLS_RSA_PSK_WITH_AES_256_CBC_SHA
+ {0x96, 0xea}, // TLS_RSA_WITH_SEED_CBC_SHA
+ {0x97, 0x26a}, // TLS_DH_DSS_WITH_SEED_CBC_SHA
+ {0x98, 0x36a}, // TLS_DH_RSA_WITH_SEED_CBC_SHA
+ {0x99, 0x46a}, // TLS_DHE_DSS_WITH_SEED_CBC_SHA
+ {0x9a, 0x56a}, // TLS_DHE_RSA_WITH_SEED_CBC_SHA
+ {0x9b, 0x66a}, // TLS_DH_anon_WITH_SEED_CBC_SHA
+ {0x9c, 0xf3}, // TLS_RSA_WITH_AES_128_GCM_SHA256
+ {0x9d, 0xfc}, // TLS_RSA_WITH_AES_256_GCM_SHA384
+ {0x9e, 0x573}, // TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
+ {0x9f, 0x57c}, // TLS_DHE_RSA_WITH_AES_256_GCM_SHA384
+ {0xa0, 0x373}, // TLS_DH_RSA_WITH_AES_128_GCM_SHA256
+ {0xa1, 0x37c}, // TLS_DH_RSA_WITH_AES_256_GCM_SHA384
+ {0xa2, 0x473}, // TLS_DHE_DSS_WITH_AES_128_GCM_SHA256
+ {0xa3, 0x47c}, // TLS_DHE_DSS_WITH_AES_256_GCM_SHA384
+ {0xa4, 0x273}, // TLS_DH_DSS_WITH_AES_128_GCM_SHA256
+ {0xa5, 0x27c}, // TLS_DH_DSS_WITH_AES_256_GCM_SHA384
+ {0xa6, 0x673}, // TLS_DH_anon_WITH_AES_128_GCM_SHA256
+ {0xa7, 0x67c}, // TLS_DH_anon_WITH_AES_256_GCM_SHA384
+ {0xa8, 0x7f3}, // TLS_PSK_WITH_AES_128_GCM_SHA256
+ {0xa9, 0x7fc}, // TLS_PSK_WITH_AES_256_GCM_SHA384
+ {0xaa, 0x873}, // TLS_DHE_PSK_WITH_AES_128_GCM_SHA256
+ {0xab, 0x87c}, // TLS_DHE_PSK_WITH_AES_256_GCM_SHA384
+ {0xac, 0x8f3}, // TLS_RSA_PSK_WITH_AES_128_GCM_SHA256
+ {0xad, 0x8fc}, // TLS_RSA_PSK_WITH_AES_256_GCM_SHA384
+ {0xae, 0x7cb}, // TLS_PSK_WITH_AES_128_CBC_SHA256
+ {0xaf, 0x7d4}, // TLS_PSK_WITH_AES_256_CBC_SHA384
+ {0xb0, 0x783}, // TLS_PSK_WITH_NULL_SHA256
+ {0xb1, 0x784}, // TLS_PSK_WITH_NULL_SHA384
+ {0xb2, 0x84b}, // TLS_DHE_PSK_WITH_AES_128_CBC_SHA256
+ {0xb3, 0x854}, // TLS_DHE_PSK_WITH_AES_256_CBC_SHA384
+ {0xb4, 0x803}, // TLS_DHE_PSK_WITH_NULL_SHA256
+ {0xb5, 0x804}, // TLS_DHE_PSK_WITH_NULL_SHA384
+ {0xb6, 0x8cb}, // TLS_RSA_PSK_WITH_AES_128_CBC_SHA256
+ {0xb7, 0x8d4}, // TLS_RSA_PSK_WITH_AES_256_CBC_SHA384
+ {0xb8, 0x883}, // TLS_RSA_PSK_WITH_NULL_SHA256
+ {0xb9, 0x884}, // TLS_RSA_PSK_WITH_NULL_SHA384
+ {0xba, 0xdb}, // TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256
+ {0xbb, 0x25b}, // TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256
+ {0xbc, 0x35b}, // TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256
+ {0xbd, 0x45b}, // TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256
+ {0xbe, 0x55b}, // TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256
+ {0xbf, 0x65b}, // TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256
+ {0xc0, 0xe3}, // TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256
+ {0xc1, 0x263}, // TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256
+ {0xc2, 0x363}, // TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256
+ {0xc3, 0x463}, // TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256
+ {0xc4, 0x563}, // TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256
+ {0xc5, 0x663}, // TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256
+ {0xc001, 0x902}, // TLS_ECDH_ECDSA_WITH_NULL_SHA
+ {0xc002, 0x912}, // TLS_ECDH_ECDSA_WITH_RC4_128_SHA
+ {0xc003, 0x93a}, // TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA
+ {0xc004, 0x94a}, // TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA
+ {0xc005, 0x952}, // TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA
+ {0xc006, 0x982}, // TLS_ECDHE_ECDSA_WITH_NULL_SHA
+ {0xc007, 0x992}, // TLS_ECDHE_ECDSA_WITH_RC4_128_SHA
+ {0xc008, 0x9ba}, // TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA
+ {0xc009, 0x9ca}, // TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
+ {0xc00a, 0x9d2}, // TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
+ {0xc00b, 0xa02}, // TLS_ECDH_RSA_WITH_NULL_SHA
+ {0xc00c, 0xa12}, // TLS_ECDH_RSA_WITH_RC4_128_SHA
+ {0xc00d, 0xa3a}, // TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA
+ {0xc00e, 0xa4a}, // TLS_ECDH_RSA_WITH_AES_128_CBC_SHA
+ {0xc00f, 0xa52}, // TLS_ECDH_RSA_WITH_AES_256_CBC_SHA
+ {0xc010, 0xa82}, // TLS_ECDHE_RSA_WITH_NULL_SHA
+ {0xc011, 0xa92}, // TLS_ECDHE_RSA_WITH_RC4_128_SHA
+ {0xc012, 0xaba}, // TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
+ {0xc013, 0xaca}, // TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
+ {0xc014, 0xad2}, // TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
+ {0xc015, 0xb02}, // TLS_ECDH_anon_WITH_NULL_SHA
+ {0xc016, 0xb12}, // TLS_ECDH_anon_WITH_RC4_128_SHA
+ {0xc017, 0xb3a}, // TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA
+ {0xc018, 0xb4a}, // TLS_ECDH_anon_WITH_AES_128_CBC_SHA
+ {0xc019, 0xb52}, // TLS_ECDH_anon_WITH_AES_256_CBC_SHA
+ {0xc01a, 0xbba}, // TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA
+ {0xc01b, 0xc3a}, // TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA
+ {0xc01c, 0xcba}, // TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA
+ {0xc01d, 0xbca}, // TLS_SRP_SHA_WITH_AES_128_CBC_SHA
+ {0xc01e, 0xc4a}, // TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA
+ {0xc01f, 0xcca}, // TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA
+ {0xc020, 0xbd2}, // TLS_SRP_SHA_WITH_AES_256_CBC_SHA
+ {0xc021, 0xc52}, // TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA
+ {0xc022, 0xcd2}, // TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA
+ {0xc023, 0x9cb}, // TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
+ {0xc024, 0x9d4}, // TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
+ {0xc025, 0x94b}, // TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256
+ {0xc026, 0x954}, // TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384
+ {0xc027, 0xacb}, // TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
+ {0xc028, 0xad4}, // TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
+ {0xc029, 0xa4b}, // TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256
+ {0xc02a, 0xa54}, // TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384
+ {0xc02b, 0x9f3}, // TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
+ {0xc02c, 0x9fc}, // TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
+ {0xc02d, 0x973}, // TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256
+ {0xc02e, 0x97c}, // TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384
+ {0xc02f, 0xaf3}, // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
+ {0xc030, 0xafc}, // TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
+ {0xc031, 0xa73}, // TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256
+ {0xc032, 0xa7c}, // TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384
+ {0xc033, 0xd12}, // TLS_ECDHE_PSK_WITH_RC4_128_SHA
+ {0xc034, 0xd3a}, // TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA
+ {0xc035, 0xd4a}, // TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA
+ {0xc036, 0xd52}, // TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA
+ {0xc037, 0xd4b}, // TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256
+ {0xc038, 0xd54}, // TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384
+ {0xc039, 0xd02}, // TLS_ECDHE_PSK_WITH_NULL_SHA
+ {0xc03a, 0xd03}, // TLS_ECDHE_PSK_WITH_NULL_SHA256
+ {0xc03b, 0xd04}, // TLS_ECDHE_PSK_WITH_NULL_SHA384
+};
+
+static const struct {
+ char name[15];
+} kKeyExchangeNames[27] = {
+ {"NULL"}, // 0
+ {"RSA"}, // 1
+ {"RSA_EXPORT"}, // 2
+ {"DH_DSS_EXPORT"}, // 3
+ {"DH_DSS"}, // 4
+ {"DH_RSA_EXPORT"}, // 5
+ {"DH_RSA"}, // 6
+ {"DHE_DSS_EXPORT"}, // 7
+ {"DHE_DSS"}, // 8
+ {"DHE_RSA_EXPORT"}, // 9
+ {"DHE_RSA"}, // 10
+ {"DH_anon_EXPORT"}, // 11
+ {"DH_anon"}, // 12
+ {"KRB5"}, // 13
+ {"KRB5_EXPORT"}, // 14
+ {"PSK"}, // 15
+ {"DHE_PSK"}, // 16
+ {"RSA_PSK"}, // 17
+ {"ECDH_ECDSA"}, // 18
+ {"ECDHE_ECDSA"}, // 19
+ {"ECDH_RSA"}, // 20
+ {"ECDHE_RSA"}, // 21
+ {"ECDH_anon"}, // 22
+ {"SRP_SHA"}, // 23
+ {"SRP_SHA_RSA"}, // 24
+ {"SRP_SHA_DSS"}, // 25
+ {"ECDHE_PSK"}, // 26
+};
+
+static const struct {
+ char name[17];
+} kCipherNames[16] = {
+ {"NULL"}, // 0
+ {"RC4_40"}, // 1
+ {"RC4_128"}, // 2
+ {"RC2_CBC_40"}, // 3
+ {"IDEA_CBC"}, // 4
+ {"DES40_CBC"}, // 5
+ {"DES_CBC"}, // 6
+ {"3DES_EDE_CBC"}, // 7
+ {"DES_CBC_40"}, // 8
+ {"AES_128_CBC"}, // 9
+ {"AES_256_CBC"}, // 10
+ {"CAMELLIA_128_CBC"}, // 11
+ {"CAMELLIA_256_CBC"}, // 12
+ {"SEED_CBC"}, // 13
+ {"AES_128_GCM"}, // 14
+ {"AES_256_GCM"}, // 15
+};
+
+static const struct {
+ char name[7];
+} kMacNames[5] = {
+ {"NULL"}, // 0
+ {"MD5"}, // 1
+ {"SHA1"}, // 2
+ {"SHA256"}, // 3
+ {"SHA384"}, // 4
+};
+
+
+namespace net {
+
+static int CipherSuiteCmp(const void* ia, const void* ib) {
+ const CipherSuite* a = static_cast<const CipherSuite*>(ia);
+ const CipherSuite* b = static_cast<const CipherSuite*>(ib);
+
+ if (a->cipher_suite < b->cipher_suite) {
+ return -1;
+ } else if (a->cipher_suite == b->cipher_suite) {
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+void SSLCipherSuiteToStrings(const char** key_exchange_str,
+ const char** cipher_str,
+ const char** mac_str, uint16 cipher_suite) {
+ *key_exchange_str = *cipher_str = *mac_str = "???";
+
+ struct CipherSuite desired;
+ desired.cipher_suite = cipher_suite;
+
+ void* r = bsearch(&desired, kCipherSuites,
+ arraysize(kCipherSuites), sizeof(kCipherSuites[0]),
+ CipherSuiteCmp);
+
+ if (!r)
+ return;
+
+ const CipherSuite* cs = static_cast<CipherSuite*>(r);
+
+ const int key_exchange = cs->encoded >> 7;
+ const int cipher = (cs->encoded >> 3) & 0xf;
+ const int mac = cs->encoded & 0x7;
+
+ *key_exchange_str = kKeyExchangeNames[key_exchange].name;
+ *cipher_str = kCipherNames[cipher].name;
+ *mac_str = kMacNames[mac].name;
+}
+
+void SSLCompressionToString(const char** name, uint8 compresssion) {
+ if (compresssion == 0) {
+ *name = "NONE";
+ } else if (compresssion == 1) {
+ *name = "DEFLATE";
+ } else if (compresssion == 64) {
+ *name = "LZS";
+ } else {
+ *name = "???";
+ }
+}
+
+} // namespace net
diff --git a/net/base/ssl_cipher_suite_names.h b/net/base/ssl_cipher_suite_names.h
new file mode 100644
index 0000000..09429ae
--- /dev/null
+++ b/net/base/ssl_cipher_suite_names.h
@@ -0,0 +1,29 @@
+// 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.
+
+#ifndef NET_BASE_SSL_CIPHER_SUITE_NAMES_H_
+#define NET_BASE_SSL_CIPHER_SUITE_NAMES_H_
+
+#include "base/basictypes.h"
+
+namespace net {
+
+// SSLCipherSuiteToStrings returns three strings for a given cipher suite
+// number, the name of the key exchange algorithm, the name of the cipher and
+// the name of the MAC. The cipher suite number is the number as sent on the
+// wire and recorded at
+// http://www.iana.org/assignments/tls-parameters/tls-parameters.xml
+// If the cipher suite is unknown, the strings are set to "???".
+void SSLCipherSuiteToStrings(const char** key_exchange_str,
+ const char** cipher_str, const char** mac_str,
+ uint16 cipher_suite);
+
+// SSLCompressionToString returns the name of the compression algorithm
+// specified by |compression_method|, which is the TLS compression id.
+// If the algorithm is unknown, |name| is set to "???".
+void SSLCompressionToString(const char** name, uint8 compression_method);
+
+} // namespace net
+
+#endif // NET_BASE_SSL_CIPHER_SUITE_NAMES_H_
diff --git a/net/base/ssl_cipher_suite_names_generate.go b/net/base/ssl_cipher_suite_names_generate.go
new file mode 100644
index 0000000..f286ce3
--- /dev/null
+++ b/net/base/ssl_cipher_suite_names_generate.go
@@ -0,0 +1,189 @@
+// This program reads in the contents of [1] from /tmp/tls-parameters.xml and
+// writes out a compact form the ciphersuite information found there in.
+// It's used to generate the tables in net/base/ssl_cipher_suite_names.cc
+//
+// [1] http://www.iana.org/assignments/tls-parameters/tls-parameters.xml
+package main
+
+import (
+ "fmt"
+ "os"
+ "sort"
+ "strings"
+ "xml"
+)
+
+// Structures for parsing the XML
+
+type TLSRegistry struct {
+ Registry []Registry
+}
+
+type Registry struct {
+ Id string "attr"
+ Title string
+ Record []Record
+}
+
+type Record struct {
+ Value string
+ Description string
+}
+
+type CipherSuite struct {
+ value uint16
+ kx string
+ cipher string
+ mac string
+}
+
+func fromHex(c byte) int {
+ if c >= '0' && c <= '9' {
+ return int(c - '0')
+ }
+ if c >= 'a' && c <= 'f' {
+ return int(c - 'a' + 10)
+ }
+ if c >= 'A' && c <= 'F' {
+ return int(c - 'A' + 10)
+ }
+ panic("Bad char passed to fromHex")
+}
+
+type TLSValue struct {
+ v int
+ name string
+}
+
+type TLSMapping []TLSValue
+
+func (m TLSMapping) Len() int {
+ return len(m)
+}
+
+func (m TLSMapping) Less(i, j int) bool {
+ return m[i].v < m[j].v
+}
+
+func (m TLSMapping) Swap(i, j int) {
+ m[i], m[j] = m[j], m[i]
+}
+
+func printDict(d map[string]int, name string) {
+ a := make([]TLSValue, len(d))
+
+ maxLen := 0
+ i := 0
+ for k, v := range d {
+ if len(k) > maxLen {
+ maxLen = len(k)
+ }
+ a[i].v = v
+ a[i].name = k
+ i++
+ }
+
+ sort.Sort(TLSMapping(a))
+
+ fmt.Printf("static const struct {\n char name[%d];\n} %s[%d] = {\n", maxLen+1, name, len(d))
+ for _, m := range a {
+ fmt.Printf(" {\"%s\"}, // %d\n", m.name, m.v)
+ }
+
+ fmt.Printf("};\n\n")
+}
+
+func parseCipherSuiteString(s string) (kx, cipher, mac string) {
+ s = s[4:]
+ i := strings.Index(s, "_WITH_")
+ kx = s[0:i]
+ s = s[i+6:]
+ i = strings.LastIndex(s, "_")
+ cipher = s[0:i]
+ mac = s[i+1:]
+ return
+}
+
+func main() {
+ infile, err := os.Open("/tmp/tls-parameters.xml", os.O_RDONLY, 0)
+ if err != nil {
+ fmt.Printf("Cannot open input: %s\n", err)
+ return
+ }
+
+ var input TLSRegistry
+ err = xml.Unmarshal(infile, &input)
+ if err != nil {
+ fmt.Printf("Error parsing XML: %s\n", err)
+ return
+ }
+
+ var cipherSuitesRegistry *Registry
+ for _, r := range input.Registry {
+ if r.Id == "tls-parameters-4" {
+ cipherSuitesRegistry = &r
+ break
+ }
+ }
+
+ if cipherSuitesRegistry == nil {
+ fmt.Printf("Didn't find tls-parameters-4 registry\n")
+ }
+
+ kxs := make(map[string]int)
+ next_kx := 0
+ ciphers := make(map[string]int)
+ next_cipher := 0
+ macs := make(map[string]int)
+ next_mac := 0
+ lastValue := uint16(0)
+
+ fmt.Printf("struct CipherSuite {\n uint16 cipher_suite, encoded;\n};\n\n")
+ fmt.Printf("static const struct CipherSuite kCipherSuites[] = {\n")
+
+ for _, r := range cipherSuitesRegistry.Record {
+ if strings.Index(r.Description, "_WITH_") == -1 {
+ continue
+ }
+
+ value := uint16(fromHex(r.Value[2])<<12 | fromHex(r.Value[3])<<8 | fromHex(r.Value[7])<<4 | fromHex(r.Value[8]))
+ kx, cipher, mac := parseCipherSuiteString(r.Description)
+
+ if value < lastValue {
+ panic("Input isn't sorted")
+ }
+ lastValue = value
+
+ var kx_n, cipher_n, mac_n int
+ var ok bool
+
+ if kx_n, ok = kxs[kx]; !ok {
+ kxs[kx] = next_kx
+ kx_n = next_kx
+ next_kx++
+ }
+ if cipher_n, ok = ciphers[cipher]; !ok {
+ ciphers[cipher] = next_cipher
+ cipher_n = next_cipher
+ next_cipher++
+ }
+ if mac_n, ok = macs[mac]; !ok {
+ macs[mac] = next_mac
+ mac_n = next_mac
+ next_mac++
+ }
+
+ if kx_n > 32 || cipher_n > 15 || mac_n > 7 {
+ panic("Need to shift bit boundaries")
+ }
+
+ encoded := (kx_n << 7) | (cipher_n << 3) | mac_n
+ fmt.Printf(" {0x%x, 0x%x}, // %s\n", value, encoded, r.Description)
+ }
+
+ fmt.Printf("};\n\n")
+
+ printDict(kxs, "kKeyExchangeNames")
+ printDict(ciphers, "kCipherNames")
+ printDict(macs, "kMacNames")
+}
diff --git a/net/base/ssl_cipher_suite_names_unittest.cc b/net/base/ssl_cipher_suite_names_unittest.cc
new file mode 100644
index 0000000..3a9c2ee
--- /dev/null
+++ b/net/base/ssl_cipher_suite_names_unittest.cc
@@ -0,0 +1,27 @@
+// 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 "net/base/ssl_cipher_suite_names.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+TEST(CipherSuiteNamesTest, Basic) {
+ const char *key_exchange, *cipher, *mac;
+ SSLCipherSuiteToStrings(&key_exchange, &cipher, &mac, 0xc001);
+ EXPECT_STREQ(key_exchange, "ECDH_ECDSA");
+ EXPECT_STREQ(cipher, "NULL");
+ EXPECT_STREQ(mac, "SHA1");
+
+ SSLCipherSuiteToStrings(&key_exchange, &cipher, &mac, 0xff31);
+ EXPECT_STREQ(key_exchange, "???");
+ EXPECT_STREQ(cipher, "???");
+ EXPECT_STREQ(mac, "???");
+}
+
+} // anonymous namespace
+
+} // namespace net
diff --git a/net/base/ssl_config_service.cc b/net/base/ssl_config_service.cc
index 67d1349..06dc610 100644
--- a/net/base/ssl_config_service.cc
+++ b/net/base/ssl_config_service.cc
@@ -25,4 +25,31 @@
#endif
}
+// static
+bool SSLConfigService::IsKnownStrictTLSServer(const std::string& hostname) {
+ // If you wish to add an entry to this list, please contact agl AT chromium
+ // DOT org.
+ //
+ // If this list starts growing, it'll need to be something more efficient
+ // than a linear list.
+ static const char kStrictServers[][20] = {
+ "www.google.com",
+ "mail.google.com",
+ "www.gmail.com",
+ "docs.google.com",
+ "clients1.google.com",
+
+ // Removed until we update the XMPP servers with the renegotiation
+ // extension.
+ // "gmail.com",
+ };
+
+ for (size_t i = 0; i < arraysize(kStrictServers); i++) {
+ if (strcmp(hostname.c_str(), kStrictServers[i]) == 0)
+ return true;
+ }
+
+ return false;
+}
+
} // namespace net
diff --git a/net/base/ssl_config_service.h b/net/base/ssl_config_service.h
index 45c1fc6..3f0f479 100644
--- a/net/base/ssl_config_service.h
+++ b/net/base/ssl_config_service.h
@@ -18,8 +18,8 @@
// Default to SSL 2.0 off, SSL 3.0 on, and TLS 1.0 on.
SSLConfig()
: rev_checking_enabled(true), ssl2_enabled(false), ssl3_enabled(true),
- tls1_enabled(true), send_client_cert(false), verify_ev_cert(false),
- next_protos("\007http1.1") {
+ tls1_enabled(true), ssl3_fallback(false), send_client_cert(false),
+ verify_ev_cert(false) {
}
bool rev_checking_enabled; // True if server certificate revocation
@@ -27,6 +27,8 @@
bool ssl2_enabled; // True if SSL 2.0 is enabled.
bool ssl3_enabled; // True if SSL 3.0 is enabled.
bool tls1_enabled; // True if TLS 1.0 is enabled.
+ bool ssl3_fallback; // True if we are falling back to SSL 3.0 (one still
+ // needs to clear tls1_enabled).
// TODO(wtc): move the following members to a new SSLParams structure. They
// are not SSL configuration settings.
@@ -85,6 +87,14 @@
// May not be thread-safe, should only be called on the IO thread.
virtual void GetSSLConfig(SSLConfig* config) = 0;
+ // Returns true if the given hostname is known to be 'strict'. This means
+ // that we will require the renegotiation extension and will always use TLS
+ // (no SSLv3 fallback).
+ //
+ // If you wish to add an element to this list, file a bug at
+ // http://crbug.com and email the link to agl AT chromium DOT org.
+ static bool IsKnownStrictTLSServer(const std::string& hostname);
+
protected:
friend class base::RefCountedThreadSafe<SSLConfigService>;
diff --git a/net/base/ssl_config_service_mac.h b/net/base/ssl_config_service_mac.h
index 5a60803..6d639ca 100644
--- a/net/base/ssl_config_service_mac.h
+++ b/net/base/ssl_config_service_mac.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -48,7 +48,7 @@
base::TimeTicks config_time_;
bool ever_updated_;
- DISALLOW_EVIL_CONSTRUCTORS(SSLConfigServiceMac);
+ DISALLOW_COPY_AND_ASSIGN(SSLConfigServiceMac);
};
} // namespace net
diff --git a/net/base/ssl_config_service_win.h b/net/base/ssl_config_service_win.h
index e7b77c7..928ca1a 100644
--- a/net/base/ssl_config_service_win.h
+++ b/net/base/ssl_config_service_win.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -53,7 +53,7 @@
base::TimeTicks config_time_;
bool ever_updated_;
- DISALLOW_EVIL_CONSTRUCTORS(SSLConfigServiceWin);
+ DISALLOW_COPY_AND_ASSIGN(SSLConfigServiceWin);
};
} // namespace net
diff --git a/net/base/ssl_connection_status_flags.h b/net/base/ssl_connection_status_flags.h
new file mode 100644
index 0000000..9ec22fa
--- /dev/null
+++ b/net/base/ssl_connection_status_flags.h
@@ -0,0 +1,45 @@
+// 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.
+
+#ifndef NET_BASE_SSL_CONNECTION_STATUS_FLAGS_H_
+#define NET_BASE_SSL_CONNECTION_STATUS_FLAGS_H_
+
+namespace net {
+
+// Status flags for SSLInfo::connection_status.
+enum {
+ // The lower 16 bits are reserved for the TLS ciphersuite id.
+ SSL_CONNECTION_CIPHERSUITE_SHIFT = 0,
+ SSL_CONNECTION_CIPHERSUITE_MASK = 0xffff,
+
+ // The next two bits are reserved for the compression used.
+ SSL_CONNECTION_COMPRESSION_SHIFT = 16,
+ SSL_CONNECTION_COMPRESSION_MASK = 3,
+
+ // We fell back to SSLv3 for this connection.
+ SSL_CONNECTION_SSL3_FALLBACK = 1 << 18,
+
+ // The server doesn't support the renegotiation_info extension. If this bit
+ // is not set then either the extension isn't supported, or we don't have any
+ // knowledge either way. (The latter case will occur when we use an SSL
+ // library that doesn't report it, like SChannel.)
+ SSL_CONNECTION_NO_RENEGOTIATION_EXTENSION = 1 << 19,
+
+ // 1 << 31 (the sign bit) is reserved so that the SSL connection status will
+ // never be negative.
+};
+
+inline int SSLConnectionStatusToCipherSuite(int connection_status) {
+ return (connection_status >> SSL_CONNECTION_CIPHERSUITE_SHIFT) &
+ SSL_CONNECTION_CIPHERSUITE_MASK;
+}
+
+inline int SSLConnectionStatusToCompression(int connection_status) {
+ return (connection_status >> SSL_CONNECTION_COMPRESSION_SHIFT) &
+ SSL_CONNECTION_COMPRESSION_MASK;
+}
+
+} // namespace net
+
+#endif // NET_BASE_SSL_CONNECTION_STATUS_FLAGS_H_
diff --git a/net/base/ssl_info.h b/net/base/ssl_info.h
index 3fe0ce4..7c14163 100644
--- a/net/base/ssl_info.h
+++ b/net/base/ssl_info.h
@@ -16,12 +16,13 @@
// This is really a struct. All members are public.
class SSLInfo {
public:
- SSLInfo() : cert_status(0), security_bits(-1) { }
+ SSLInfo() : cert_status(0), security_bits(-1), connection_status(0) { }
void Reset() {
cert = NULL;
- security_bits = -1;
cert_status = 0;
+ security_bits = -1;
+ connection_status = 0;
}
bool is_valid() const { return cert != NULL; }
@@ -43,6 +44,12 @@
// 0 means the connection is not encrypted.
// -1 means the security strength is unknown.
int security_bits;
+
+ // Information about the SSL connection itself. See
+ // ssl_connection_status_flags.h for values. The ciphersuite and compression
+ // in use are encoded within.
+ // TODO(agl): also encode the protocol version used.
+ int connection_status;
};
} // namespace net
diff --git a/net/base/static_cookie_policy.h b/net/base/static_cookie_policy.h
index 4734d33..d903149 100644
--- a/net/base/static_cookie_policy.h
+++ b/net/base/static_cookie_policy.h
@@ -5,6 +5,8 @@
#ifndef NET_BASE_STATIC_COOKIE_POLICY_H_
#define NET_BASE_STATIC_COOKIE_POLICY_H_
+#include <string>
+
#include "base/basictypes.h"
#include "net/base/cookie_policy.h"
diff --git a/net/base/sys_addrinfo.h b/net/base/sys_addrinfo.h
index cfdd424..62a6317 100644
--- a/net/base/sys_addrinfo.h
+++ b/net/base/sys_addrinfo.h
@@ -20,5 +20,6 @@
#include <ws2tcpip.h>
#elif defined(OS_POSIX)
#include <netdb.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
#endif
-
diff --git a/net/base/telnet_server.cc b/net/base/telnet_server.cc
index 33eb5b1..5b027d7 100644
--- a/net/base/telnet_server.cc
+++ b/net/base/telnet_server.cc
@@ -197,7 +197,9 @@
case EXPECTING_NEW_LINE:
if (c == TelnetProtocol::LF) {
Send("\n", 1);
- socket_delegate_->DidRead(this, command_line_);
+ socket_delegate_->DidRead(this,
+ command_line_.c_str(),
+ command_line_.length());
command_line_ = "";
}
input_state_ = NOT_IN_IAC_OR_ESC_SEQUENCE;
diff --git a/net/base/telnet_server.h b/net/base/telnet_server.h
index d8d21e8..8160141 100644
--- a/net/base/telnet_server.h
+++ b/net/base/telnet_server.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -55,7 +55,7 @@
int iac_option_; // Last option read.
std::string command_line_;
- DISALLOW_EVIL_CONSTRUCTORS(TelnetServer);
+ DISALLOW_COPY_AND_ASSIGN(TelnetServer);
};
#endif // NET_BASE_TELNET_SERVER_H_
diff --git a/net/base/telnet_server_unittest.cc b/net/base/telnet_server_unittest.cc
index 483309c..1112bd2 100644
--- a/net/base/telnet_server_unittest.cc
+++ b/net/base/telnet_server_unittest.cc
@@ -59,7 +59,8 @@
scoped_refptr<TelnetServerTester> tester_;
};
-TEST_F(TelnetServerTest, ServerClientSend) {
+// Flaky, http://crbug.com/38093.
+TEST_F(TelnetServerTest, FLAKY_ServerClientSend) {
tester_->TestClientSend();
}
diff --git a/net/base/test_certificate_data.h b/net/base/test_certificate_data.h
index e475906..1ca6db8 100644
--- a/net/base/test_certificate_data.h
+++ b/net/base/test_certificate_data.h
@@ -443,127 +443,4 @@
0x23, 0x82, 0x6f, 0xdb, 0xb8, 0x22, 0x1c, 0x43, 0x96, 0x07, 0xa8, 0xbb
};
-// A certificate for https://www.unosoft.hu/, whose AIA extension contains
-// an LDAP URL without a host name. Expires on 2011-09-08.
-unsigned char unosoft_hu_der[] = {
- 0x30, 0x82, 0x05, 0x7f, 0x30, 0x82, 0x04, 0x67, 0xa0, 0x03, 0x02, 0x01,
- 0x02, 0x02, 0x0a, 0x17, 0x73, 0x3e, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x3f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
- 0x01, 0x05, 0x05, 0x00, 0x30, 0x4a, 0x31, 0x15, 0x30, 0x13, 0x06, 0x0a,
- 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19, 0x16, 0x05,
- 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x31, 0x17, 0x30, 0x15, 0x06, 0x0a, 0x09,
- 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19, 0x16, 0x07, 0x75,
- 0x6e, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03,
- 0x55, 0x04, 0x03, 0x13, 0x0f, 0x55, 0x4e, 0x4f, 0x2d, 0x53, 0x4f, 0x46,
- 0x54, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d,
- 0x30, 0x39, 0x31, 0x32, 0x30, 0x37, 0x31, 0x30, 0x33, 0x35, 0x35, 0x34,
- 0x5a, 0x17, 0x0d, 0x31, 0x31, 0x30, 0x39, 0x30, 0x38, 0x31, 0x38, 0x30,
- 0x36, 0x30, 0x37, 0x5a, 0x30, 0x62, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
- 0x55, 0x04, 0x06, 0x13, 0x02, 0x48, 0x55, 0x31, 0x11, 0x30, 0x0f, 0x06,
- 0x03, 0x55, 0x04, 0x07, 0x13, 0x08, 0x42, 0x75, 0x64, 0x61, 0x70, 0x65,
- 0x73, 0x74, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
- 0x1e, 0x55, 0x4e, 0x4f, 0x2d, 0x53, 0x4f, 0x46, 0x54, 0x20, 0x53, 0x7a,
- 0x61, 0x6d, 0x69, 0x74, 0x61, 0x73, 0x74, 0x65, 0x63, 0x68, 0x6e, 0x69,
- 0x6b, 0x61, 0x69, 0x20, 0x4b, 0x46, 0x54, 0x31, 0x17, 0x30, 0x15, 0x06,
- 0x03, 0x55, 0x04, 0x03, 0x13, 0x0e, 0x77, 0x77, 0x77, 0x2e, 0x75, 0x6e,
- 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x68, 0x75, 0x30, 0x81, 0x9f, 0x30,
- 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
- 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30, 0x81, 0x89, 0x02, 0x81, 0x81,
- 0x00, 0xa4, 0x07, 0x76, 0xf1, 0xe0, 0xfa, 0x16, 0x58, 0xcc, 0x45, 0xee,
- 0xbb, 0x8f, 0xa1, 0x1b, 0x4b, 0x95, 0xe5, 0x6e, 0xc1, 0x93, 0xa3, 0xc1,
- 0x0a, 0x79, 0x33, 0x92, 0xfa, 0x6a, 0xb8, 0xf1, 0x14, 0x35, 0x45, 0x8f,
- 0xc5, 0x82, 0x0b, 0x71, 0xca, 0x2c, 0x48, 0x7e, 0x15, 0xa6, 0xa6, 0x66,
- 0x4f, 0x0b, 0x41, 0xe8, 0xdf, 0xeb, 0x96, 0x06, 0xb0, 0xed, 0x08, 0xbb,
- 0xe9, 0x14, 0x97, 0xf8, 0xdd, 0x66, 0x3f, 0xb7, 0x32, 0x7e, 0x8b, 0xab,
- 0x80, 0xd1, 0x2b, 0xd7, 0x8b, 0x0e, 0xfb, 0x88, 0x68, 0x73, 0x6b, 0xd7,
- 0x94, 0x22, 0x42, 0x94, 0x07, 0xd5, 0xe3, 0xc4, 0x98, 0x25, 0x01, 0x66,
- 0xe7, 0xc2, 0x5a, 0x1a, 0x3d, 0x0b, 0x01, 0x14, 0x49, 0x32, 0x47, 0x6c,
- 0xff, 0x19, 0x07, 0x6c, 0x1a, 0x47, 0x2f, 0x87, 0x4c, 0x3a, 0xc4, 0x61,
- 0xae, 0x77, 0x33, 0x4f, 0x27, 0xc1, 0xa5, 0xdd, 0x63, 0x02, 0x03, 0x01,
- 0x00, 0x01, 0xa3, 0x82, 0x02, 0xd1, 0x30, 0x82, 0x02, 0xcd, 0x30, 0x1d,
- 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xac, 0xee, 0x30,
- 0xf0, 0x0e, 0x73, 0x42, 0x73, 0x2f, 0x8f, 0xb5, 0x62, 0xcb, 0xb6, 0x3e,
- 0xdb, 0x62, 0x6c, 0xb2, 0x0a, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23,
- 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x29, 0x53, 0x4a, 0x9d, 0x9c, 0xd9,
- 0xf9, 0xda, 0x04, 0xfe, 0x46, 0x3a, 0x76, 0x49, 0x5c, 0xdd, 0x3b, 0x0e,
- 0x98, 0x76, 0x30, 0x82, 0x01, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04,
- 0x82, 0x01, 0x05, 0x30, 0x82, 0x01, 0x01, 0x30, 0x81, 0xfe, 0xa0, 0x81,
- 0xfb, 0xa0, 0x81, 0xf8, 0x86, 0x81, 0xb8, 0x6c, 0x64, 0x61, 0x70, 0x3a,
- 0x2f, 0x2f, 0x2f, 0x43, 0x4e, 0x3d, 0x55, 0x4e, 0x4f, 0x2d, 0x53, 0x4f,
- 0x46, 0x54, 0x25, 0x32, 0x30, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x2c,
- 0x43, 0x4e, 0x3d, 0x55, 0x4e, 0x4f, 0x44, 0x43, 0x2c, 0x43, 0x4e, 0x3d,
- 0x43, 0x44, 0x50, 0x2c, 0x43, 0x4e, 0x3d, 0x50, 0x75, 0x62, 0x6c, 0x69,
- 0x63, 0x25, 0x32, 0x30, 0x4b, 0x65, 0x79, 0x25, 0x32, 0x30, 0x53, 0x65,
- 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2c, 0x43, 0x4e, 0x3d, 0x53, 0x65,
- 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2c, 0x43, 0x4e, 0x3d, 0x43, 0x6f,
- 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2c,
- 0x44, 0x43, 0x3d, 0x75, 0x6e, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2c, 0x44,
- 0x43, 0x3d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x3f, 0x63, 0x65, 0x72, 0x74,
- 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x76, 0x6f, 0x63,
- 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x3f, 0x62, 0x61,
- 0x73, 0x65, 0x3f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x6c, 0x61,
- 0x73, 0x73, 0x3d, 0x63, 0x52, 0x4c, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69,
- 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x86,
- 0x3b, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x75, 0x6e, 0x6f, 0x64,
- 0x63, 0x2e, 0x75, 0x6e, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x6c, 0x6f,
- 0x63, 0x61, 0x6c, 0x2f, 0x43, 0x65, 0x72, 0x74, 0x45, 0x6e, 0x72, 0x6f,
- 0x6c, 0x6c, 0x2f, 0x55, 0x4e, 0x4f, 0x2d, 0x53, 0x4f, 0x46, 0x54, 0x25,
- 0x32, 0x30, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c,
- 0x30, 0x82, 0x01, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
- 0x01, 0x01, 0x04, 0x82, 0x01, 0x16, 0x30, 0x82, 0x01, 0x12, 0x30, 0x81,
- 0xb2, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86,
- 0x81, 0xa5, 0x6c, 0x64, 0x61, 0x70, 0x3a, 0x2f, 0x2f, 0x2f, 0x43, 0x4e,
- 0x3d, 0x55, 0x4e, 0x4f, 0x2d, 0x53, 0x4f, 0x46, 0x54, 0x25, 0x32, 0x30,
- 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x2c, 0x43, 0x4e, 0x3d, 0x41, 0x49,
- 0x41, 0x2c, 0x43, 0x4e, 0x3d, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x25,
- 0x32, 0x30, 0x4b, 0x65, 0x79, 0x25, 0x32, 0x30, 0x53, 0x65, 0x72, 0x76,
- 0x69, 0x63, 0x65, 0x73, 0x2c, 0x43, 0x4e, 0x3d, 0x53, 0x65, 0x72, 0x76,
- 0x69, 0x63, 0x65, 0x73, 0x2c, 0x43, 0x4e, 0x3d, 0x43, 0x6f, 0x6e, 0x66,
- 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x44, 0x43,
- 0x3d, 0x75, 0x6e, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2c, 0x44, 0x43, 0x3d,
- 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x3f, 0x63, 0x41, 0x43, 0x65, 0x72, 0x74,
- 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x3f, 0x62, 0x61, 0x73, 0x65,
- 0x3f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x6c, 0x61, 0x73, 0x73,
- 0x3d, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
- 0x6f, 0x6e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30,
- 0x5b, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86,
- 0x4f, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x75, 0x6e, 0x6f, 0x64,
- 0x63, 0x2e, 0x75, 0x6e, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x6c, 0x6f,
- 0x63, 0x61, 0x6c, 0x2f, 0x43, 0x65, 0x72, 0x74, 0x45, 0x6e, 0x72, 0x6f,
- 0x6c, 0x6c, 0x2f, 0x55, 0x4e, 0x4f, 0x44, 0x43, 0x2e, 0x75, 0x6e, 0x6f,
- 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x55,
- 0x4e, 0x4f, 0x2d, 0x53, 0x4f, 0x46, 0x54, 0x25, 0x32, 0x30, 0x52, 0x6f,
- 0x6f, 0x74, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x21, 0x06, 0x09,
- 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x14, 0x02, 0x04, 0x14, 0x1e,
- 0x12, 0x00, 0x57, 0x00, 0x65, 0x00, 0x62, 0x00, 0x53, 0x00, 0x65, 0x00,
- 0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x30, 0x0c, 0x06, 0x03, 0x55,
- 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x02, 0x30, 0x00, 0x30, 0x0b, 0x06,
- 0x03, 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, 0x02, 0x05, 0xa0, 0x30, 0x13,
- 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x0c, 0x30, 0x0a, 0x06, 0x08, 0x2b,
- 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x30, 0x0d, 0x06, 0x09, 0x2a,
- 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82,
- 0x01, 0x01, 0x00, 0xaa, 0xc8, 0x95, 0xc4, 0x61, 0xcf, 0x3a, 0x6d, 0xf1,
- 0x66, 0xba, 0x2d, 0x24, 0x06, 0xe3, 0x1e, 0x33, 0x9a, 0x21, 0xda, 0x48,
- 0x99, 0xce, 0xfa, 0x36, 0x98, 0x78, 0x20, 0xc4, 0x81, 0xda, 0x5a, 0x9e,
- 0x45, 0xfd, 0xfa, 0x1e, 0xe6, 0x11, 0x72, 0xf2, 0xfd, 0x58, 0x79, 0x29,
- 0x52, 0x6a, 0xe6, 0xf3, 0x1b, 0xab, 0x28, 0xe6, 0x92, 0xd3, 0x65, 0x30,
- 0xf5, 0xd6, 0x51, 0x40, 0x01, 0xc0, 0x92, 0xbb, 0x4f, 0x8f, 0x74, 0xae,
- 0x78, 0x5d, 0x2c, 0x78, 0xce, 0x9c, 0xae, 0x90, 0x50, 0x1e, 0x67, 0x79,
- 0xc1, 0x84, 0xba, 0x0c, 0xff, 0x02, 0xe2, 0x31, 0xbb, 0x72, 0x75, 0x31,
- 0x16, 0x08, 0x10, 0x0e, 0xb2, 0x40, 0x6d, 0xa1, 0x52, 0xb8, 0x4b, 0x52,
- 0x47, 0xbe, 0xaa, 0x6f, 0x31, 0xd0, 0xb9, 0x52, 0xef, 0xb7, 0x3e, 0xf3,
- 0xae, 0x49, 0xf5, 0x1e, 0x33, 0x76, 0x43, 0xf3, 0x74, 0xeb, 0x7b, 0x22,
- 0xdf, 0x46, 0x15, 0x2b, 0xb5, 0xe5, 0x10, 0x24, 0x5d, 0x69, 0x30, 0x5a,
- 0xb0, 0xfe, 0xa2, 0x4d, 0xf5, 0xe6, 0x67, 0x87, 0x18, 0x81, 0x2d, 0x3d,
- 0xa2, 0xfb, 0xc3, 0x47, 0xc2, 0x87, 0x03, 0x5a, 0x2b, 0x3d, 0xdf, 0x7c,
- 0x52, 0xed, 0x24, 0xf3, 0x9e, 0x55, 0x4c, 0x76, 0x59, 0x0d, 0x29, 0x04,
- 0x70, 0xaa, 0x9c, 0x83, 0x8e, 0x6c, 0x3e, 0x46, 0xe3, 0x1b, 0x2e, 0x88,
- 0xd7, 0x68, 0x06, 0xbe, 0x92, 0x2b, 0x4a, 0x1d, 0x4a, 0x6a, 0xa6, 0x13,
- 0x75, 0xf1, 0x52, 0x5f, 0xfa, 0xf7, 0x14, 0x83, 0x62, 0x54, 0xb5, 0x69,
- 0x83, 0xd8, 0x9f, 0xce, 0xf0, 0xb9, 0xec, 0x05, 0x33, 0x85, 0xfc, 0xd1,
- 0xe0, 0x23, 0xd4, 0xab, 0xa2, 0xcd, 0xac, 0x90, 0xc5, 0xe4, 0x23, 0x1a,
- 0xc4, 0xd7, 0x38, 0xcf, 0x34, 0x43, 0xba, 0x58, 0x63, 0x92, 0xed, 0x11,
- 0x8e, 0x05, 0x08, 0xbe, 0xc9, 0x9a, 0x4b
-};
-
} // namespace
diff --git a/net/base/test_completion_callback.h b/net/base/test_completion_callback.h
index 5fa14ae..1d24532 100644
--- a/net/base/test_completion_callback.h
+++ b/net/base/test_completion_callback.h
@@ -5,6 +5,7 @@
#ifndef NET_BASE_TEST_COMPLETION_CALLBACK_H_
#define NET_BASE_TEST_COMPLETION_CALLBACK_H_
+#include "base/callback.h"
#include "base/message_loop.h"
#include "net/base/completion_callback.h"
#include "net/base/net_errors.h"
diff --git a/net/base/transport_security_state.cc b/net/base/transport_security_state.cc
index 35b930c..5ddcf39 100644
--- a/net/base/transport_security_state.cc
+++ b/net/base/transport_security_state.cc
@@ -27,12 +27,21 @@
const std::string canonicalised_host = CanonicaliseHost(host);
if (canonicalised_host.empty())
return;
+
+ bool temp;
+ if (isPreloadedSTS(canonicalised_host, &temp))
+ return;
+
char hashed[base::SHA256_LENGTH];
base::SHA256HashString(canonicalised_host, hashed, sizeof(hashed));
- AutoLock lock(lock_);
+ // Use the original creation date if we already have this host.
+ DomainState state_copy(state);
+ DomainState existing_state;
+ if (IsEnabledForHost(&existing_state, host))
+ state_copy.created = existing_state.created;
- enabled_hosts_[std::string(hashed, sizeof(hashed))] = state;
+ enabled_hosts_[std::string(hashed, sizeof(hashed))] = state_copy;
DirtyNotify();
}
@@ -42,8 +51,15 @@
if (canonicalised_host.empty())
return false;
+ bool include_subdomains;
+ if (isPreloadedSTS(canonicalised_host, &include_subdomains)) {
+ result->created = result->expiry = base::Time::FromTimeT(0);
+ result->mode = DomainState::MODE_STRICT;
+ result->include_subdomains = include_subdomains;
+ return true;
+ }
+
base::Time current_time(base::Time::Now());
- AutoLock lock(lock_);
for (size_t i = 0; canonicalised_host[i]; i += canonicalised_host[i] + 1) {
char hashed_domain[base::SHA256_LENGTH];
@@ -175,8 +191,6 @@
void TransportSecurityState::SetDelegate(
TransportSecurityState::Delegate* delegate) {
- AutoLock lock(lock_);
-
delegate_ = delegate;
}
@@ -202,13 +216,12 @@
}
bool TransportSecurityState::Serialise(std::string* output) {
- AutoLock lock(lock_);
-
DictionaryValue toplevel;
for (std::map<std::string, DomainState>::const_iterator
i = enabled_hosts_.begin(); i != enabled_hosts_.end(); ++i) {
DictionaryValue* state = new DictionaryValue;
state->SetBoolean(L"include_subdomains", i->second.include_subdomains);
+ state->SetReal(L"created", i->second.created.ToDoubleT());
state->SetReal(L"expiry", i->second.expiry.ToDoubleT());
switch (i->second.mode) {
@@ -234,9 +247,8 @@
return true;
}
-bool TransportSecurityState::Deserialise(const std::string& input) {
- AutoLock lock(lock_);
-
+bool TransportSecurityState::Deserialise(const std::string& input,
+ bool* dirty) {
enabled_hosts_.clear();
scoped_ptr<Value> value(
@@ -246,6 +258,7 @@
DictionaryValue* dict_value = reinterpret_cast<DictionaryValue*>(value.get());
const base::Time current_time(base::Time::Now());
+ bool dirtied = false;
for (DictionaryValue::key_iterator i = dict_value->begin_keys();
i != dict_value->end_keys(); ++i) {
@@ -255,6 +268,7 @@
bool include_subdomains;
std::string mode_string;
+ double created;
double expiry;
if (!state->GetBoolean(L"include_subdomains", &include_subdomains) ||
@@ -277,8 +291,21 @@
}
base::Time expiry_time = base::Time::FromDoubleT(expiry);
- if (expiry_time <= current_time)
+ base::Time created_time;
+ if (state->GetReal(L"created", &created)) {
+ created_time = base::Time::FromDoubleT(created);
+ } else {
+ // We're migrating an old entry with no creation date. Make sure we
+ // write the new date back in a reasonable time frame.
+ dirtied = true;
+ created_time = base::Time::Now();
+ }
+
+ if (expiry_time <= current_time) {
+ // Make sure we dirty the state if we drop an entry.
+ dirtied = true;
continue;
+ }
std::string hashed = ExternalStringToHashedDomain(*i);
if (hashed.empty())
@@ -286,14 +313,33 @@
DomainState new_state;
new_state.mode = mode;
+ new_state.created = created_time;
new_state.expiry = expiry_time;
new_state.include_subdomains = include_subdomains;
enabled_hosts_[hashed] = new_state;
}
+ *dirty = dirtied;
return true;
}
+void TransportSecurityState::DeleteSince(const base::Time& time) {
+ bool dirtied = false;
+
+ std::map<std::string, DomainState>::iterator i = enabled_hosts_.begin();
+ while (i != enabled_hosts_.end()) {
+ if (i->second.created >= time) {
+ dirtied = true;
+ enabled_hosts_.erase(i++);
+ } else {
+ i++;
+ }
+ }
+
+ if (dirtied)
+ DirtyNotify();
+}
+
void TransportSecurityState::DirtyNotify() {
if (delegate_)
delegate_->StateIsDirty(this);
@@ -334,4 +380,37 @@
return new_host;
}
+// isPreloadedSTS returns true if the canonicalised hostname should always be
+// considered to have STS enabled.
+// static
+bool TransportSecurityState::isPreloadedSTS(
+ const std::string& canonicalised_host, bool *include_subdomains) {
+ // In the medium term this list is likely to just be hardcoded here. This,
+ // slightly odd, form removes the need for additional relocations records.
+ static const struct {
+ uint8 length;
+ bool include_subdomains;
+ char dns_name[30];
+ } kPreloadedSTS[] = {
+ {16, false, "\003www\006paypal\003com"},
+ {16, false, "\003www\006elanex\003biz"},
+ {12, true, "\006jottit\003com"},
+ };
+ static const size_t kNumPreloadedSTS = ARRAYSIZE_UNSAFE(kPreloadedSTS);
+
+ for (size_t i = 0; canonicalised_host[i]; i += canonicalised_host[i] + 1) {
+ for (size_t j = 0; j < kNumPreloadedSTS; j++) {
+ if (kPreloadedSTS[j].length == canonicalised_host.size() + 1 - i &&
+ (kPreloadedSTS[j].include_subdomains || i == 0) &&
+ memcmp(kPreloadedSTS[j].dns_name, &canonicalised_host[i],
+ kPreloadedSTS[j].length) == 0) {
+ *include_subdomains = kPreloadedSTS[j].include_subdomains;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
} // namespace
diff --git a/net/base/transport_security_state.h b/net/base/transport_security_state.h
index 360eb0b..6d03776 100644
--- a/net/base/transport_security_state.h
+++ b/net/base/transport_security_state.h
@@ -12,6 +12,7 @@
#include "base/lock.h"
#include "base/ref_counted.h"
#include "base/time.h"
+#include "testing/gtest/include/gtest/gtest_prod.h"
class GURL;
@@ -48,8 +49,10 @@
DomainState()
: mode(MODE_STRICT),
+ created(base::Time::Now()),
include_subdomains(false) { }
+ base::Time created; // when this host entry was first created
base::Time expiry; // the absolute time (UTC) when this record expires
bool include_subdomains; // subdomains included?
};
@@ -61,6 +64,9 @@
// *result is filled out.
bool IsEnabledForHost(DomainState* result, const std::string& host);
+ // Deletes all records created since a given time.
+ void DeleteSince(const base::Time& time);
+
// Returns |true| if |value| parses as a valid *-Transport-Security
// header value. The values of max-age and and includeSubDomains are
// returned in |max_age| and |include_subdomains|, respectively. The out
@@ -74,15 +80,19 @@
// This function may not block and may be called with internal locks held.
// Thus it must not reenter the TransportSecurityState object.
virtual void StateIsDirty(TransportSecurityState* state) = 0;
+
+ protected:
+ virtual ~Delegate() {}
};
void SetDelegate(Delegate*);
bool Serialise(std::string* output);
- bool Deserialise(const std::string& state);
+ bool Deserialise(const std::string& state, bool* dirty);
private:
friend class base::RefCountedThreadSafe<TransportSecurityState>;
+ FRIEND_TEST(TransportSecurityStateTest, IsPreloaded);
~TransportSecurityState() {}
@@ -95,13 +105,12 @@
// ('www.google.com') to the form used in DNS: "\x03www\x06google\x03com"
std::map<std::string, DomainState> enabled_hosts_;
- // Protect access to our data members with this lock.
- Lock lock_;
-
// Our delegate who gets notified when we are dirtied, or NULL.
Delegate* delegate_;
static std::string CanonicaliseHost(const std::string& host);
+ static bool isPreloadedSTS(const std::string& canonicalised_host,
+ bool* out_include_subdomains);
DISALLOW_COPY_AND_ASSIGN(TransportSecurityState);
};
diff --git a/net/base/transport_security_state_unittest.cc b/net/base/transport_security_state_unittest.cc
index f52912c..00eeae4 100644
--- a/net/base/transport_security_state_unittest.cc
+++ b/net/base/transport_security_state_unittest.cc
@@ -5,6 +5,8 @@
#include "net/base/transport_security_state.h"
#include "testing/gtest/include/gtest/gtest.h"
+namespace net {
+
class TransportSecurityStateTest : public testing::Test {
};
@@ -12,71 +14,71 @@
int max_age = 42;
bool include_subdomains = false;
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
"", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
" ", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
"abc", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
" abc", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
" abc ", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
"max-age", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
" max-age", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
" max-age ", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
"max-age=", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
" max-age=", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
" max-age =", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
" max-age= ", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
" max-age = ", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
" max-age = xy", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
" max-age = 3488a923", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
"max-age=3488a923 ", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
"max-ag=3488923", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
"max-aged=3488923", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
"max-age==3488923", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
"amax-age=3488923", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
"max-age=-3488923", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
"max-age=3488923;", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
"max-age=3488923 e", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
"max-age=3488923 includesubdomain", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
"max-age=3488923includesubdomains", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
"max-age=3488923=includesubdomains", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
"max-age=3488923 includesubdomainx", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
"max-age=3488923 includesubdomain=", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
"max-age=3488923 includesubdomain=true", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
"max-age=3488923 includesubdomainsx", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
"max-age=3488923 includesubdomains x", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
"max-age=34889.23 includesubdomains", &max_age, &include_subdomains));
- EXPECT_FALSE(net::TransportSecurityState::ParseHeader(
+ EXPECT_FALSE(TransportSecurityState::ParseHeader(
"max-age=34889 includesubdomains", &max_age, &include_subdomains));
EXPECT_EQ(max_age, 42);
@@ -87,51 +89,51 @@
int max_age = 42;
bool include_subdomains = true;
- EXPECT_TRUE(net::TransportSecurityState::ParseHeader(
+ EXPECT_TRUE(TransportSecurityState::ParseHeader(
"max-age=243", &max_age, &include_subdomains));
EXPECT_EQ(max_age, 243);
EXPECT_FALSE(include_subdomains);
- EXPECT_TRUE(net::TransportSecurityState::ParseHeader(
+ EXPECT_TRUE(TransportSecurityState::ParseHeader(
" Max-agE = 567", &max_age, &include_subdomains));
EXPECT_EQ(max_age, 567);
EXPECT_FALSE(include_subdomains);
- EXPECT_TRUE(net::TransportSecurityState::ParseHeader(
+ EXPECT_TRUE(TransportSecurityState::ParseHeader(
" mAx-aGe = 890 ", &max_age, &include_subdomains));
EXPECT_EQ(max_age, 890);
EXPECT_FALSE(include_subdomains);
- EXPECT_TRUE(net::TransportSecurityState::ParseHeader(
+ EXPECT_TRUE(TransportSecurityState::ParseHeader(
"max-age=123;incLudesUbdOmains", &max_age, &include_subdomains));
EXPECT_EQ(max_age, 123);
EXPECT_TRUE(include_subdomains);
- EXPECT_TRUE(net::TransportSecurityState::ParseHeader(
+ EXPECT_TRUE(TransportSecurityState::ParseHeader(
"max-age=394082; incLudesUbdOmains", &max_age, &include_subdomains));
EXPECT_EQ(max_age, 394082);
EXPECT_TRUE(include_subdomains);
- EXPECT_TRUE(net::TransportSecurityState::ParseHeader(
+ EXPECT_TRUE(TransportSecurityState::ParseHeader(
"max-age=39408299 ;incLudesUbdOmains", &max_age, &include_subdomains));
EXPECT_EQ(max_age, 39408299);
EXPECT_TRUE(include_subdomains);
- EXPECT_TRUE(net::TransportSecurityState::ParseHeader(
+ EXPECT_TRUE(TransportSecurityState::ParseHeader(
"max-age=394082038 ; incLudesUbdOmains", &max_age, &include_subdomains));
EXPECT_EQ(max_age, 394082038);
EXPECT_TRUE(include_subdomains);
- EXPECT_TRUE(net::TransportSecurityState::ParseHeader(
+ EXPECT_TRUE(TransportSecurityState::ParseHeader(
" max-age=0 ; incLudesUbdOmains ", &max_age, &include_subdomains));
EXPECT_EQ(max_age, 0);
EXPECT_TRUE(include_subdomains);
}
TEST_F(TransportSecurityStateTest, SimpleMatches) {
- scoped_refptr<net::TransportSecurityState> state(
- new net::TransportSecurityState);
- net::TransportSecurityState::DomainState domain_state;
+ scoped_refptr<TransportSecurityState> state(
+ new TransportSecurityState);
+ TransportSecurityState::DomainState domain_state;
const base::Time current_time(base::Time::Now());
const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
@@ -142,9 +144,9 @@
}
TEST_F(TransportSecurityStateTest, MatchesCase1) {
- scoped_refptr<net::TransportSecurityState> state(
- new net::TransportSecurityState);
- net::TransportSecurityState::DomainState domain_state;
+ scoped_refptr<TransportSecurityState> state(
+ new TransportSecurityState);
+ TransportSecurityState::DomainState domain_state;
const base::Time current_time(base::Time::Now());
const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
@@ -155,9 +157,9 @@
}
TEST_F(TransportSecurityStateTest, MatchesCase2) {
- scoped_refptr<net::TransportSecurityState> state(
- new net::TransportSecurityState);
- net::TransportSecurityState::DomainState domain_state;
+ scoped_refptr<TransportSecurityState> state(
+ new TransportSecurityState);
+ TransportSecurityState::DomainState domain_state;
const base::Time current_time(base::Time::Now());
const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
@@ -168,9 +170,9 @@
}
TEST_F(TransportSecurityStateTest, SubdomainMatches) {
- scoped_refptr<net::TransportSecurityState> state(
- new net::TransportSecurityState);
- net::TransportSecurityState::DomainState domain_state;
+ scoped_refptr<TransportSecurityState> state(
+ new TransportSecurityState);
+ TransportSecurityState::DomainState domain_state;
const base::Time current_time(base::Time::Now());
const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
@@ -187,61 +189,149 @@
}
TEST_F(TransportSecurityStateTest, Serialise1) {
- scoped_refptr<net::TransportSecurityState> state(
- new net::TransportSecurityState);
+ scoped_refptr<TransportSecurityState> state(
+ new TransportSecurityState);
std::string output;
+ bool dirty;
state->Serialise(&output);
- EXPECT_TRUE(state->Deserialise(output));
+ EXPECT_TRUE(state->Deserialise(output, &dirty));
+ EXPECT_FALSE(dirty);
}
TEST_F(TransportSecurityStateTest, Serialise2) {
- scoped_refptr<net::TransportSecurityState> state(
- new net::TransportSecurityState);
+ scoped_refptr<TransportSecurityState> state(
+ new TransportSecurityState);
- net::TransportSecurityState::DomainState domain_state;
+ TransportSecurityState::DomainState domain_state;
const base::Time current_time(base::Time::Now());
const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
EXPECT_FALSE(state->IsEnabledForHost(&domain_state, "google.com"));
- domain_state.mode = net::TransportSecurityState::DomainState::MODE_STRICT;
+ domain_state.mode = TransportSecurityState::DomainState::MODE_STRICT;
domain_state.expiry = expiry;
domain_state.include_subdomains = true;
state->EnableHost("google.com", domain_state);
std::string output;
+ bool dirty;
state->Serialise(&output);
- EXPECT_TRUE(state->Deserialise(output));
+ EXPECT_TRUE(state->Deserialise(output, &dirty));
EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "google.com"));
- EXPECT_EQ(domain_state.mode, net::TransportSecurityState::DomainState::MODE_STRICT);
+ EXPECT_EQ(domain_state.mode, TransportSecurityState::DomainState::MODE_STRICT);
EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "foo.google.com"));
- EXPECT_EQ(domain_state.mode, net::TransportSecurityState::DomainState::MODE_STRICT);
+ EXPECT_EQ(domain_state.mode, TransportSecurityState::DomainState::MODE_STRICT);
EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "foo.bar.google.com"));
- EXPECT_EQ(domain_state.mode, net::TransportSecurityState::DomainState::MODE_STRICT);
+ EXPECT_EQ(domain_state.mode, TransportSecurityState::DomainState::MODE_STRICT);
EXPECT_TRUE(state->IsEnabledForHost(&domain_state,
"foo.bar.baz.google.com"));
- EXPECT_EQ(domain_state.mode, net::TransportSecurityState::DomainState::MODE_STRICT);
+ EXPECT_EQ(domain_state.mode, TransportSecurityState::DomainState::MODE_STRICT);
EXPECT_FALSE(state->IsEnabledForHost(&domain_state, "com"));
}
TEST_F(TransportSecurityStateTest, Serialise3) {
- scoped_refptr<net::TransportSecurityState> state(
- new net::TransportSecurityState);
+ scoped_refptr<TransportSecurityState> state(
+ new TransportSecurityState);
- net::TransportSecurityState::DomainState domain_state;
+ TransportSecurityState::DomainState domain_state;
const base::Time current_time(base::Time::Now());
const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
EXPECT_FALSE(state->IsEnabledForHost(&domain_state, "google.com"));
- domain_state.mode = net::TransportSecurityState::DomainState::MODE_OPPORTUNISTIC;
+ domain_state.mode = TransportSecurityState::DomainState::MODE_OPPORTUNISTIC;
domain_state.expiry = expiry;
state->EnableHost("google.com", domain_state);
std::string output;
+ bool dirty;
state->Serialise(&output);
- EXPECT_TRUE(state->Deserialise(output));
+ EXPECT_TRUE(state->Deserialise(output, &dirty));
EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "google.com"));
EXPECT_EQ(domain_state.mode,
- net::TransportSecurityState::DomainState::MODE_OPPORTUNISTIC);
+ TransportSecurityState::DomainState::MODE_OPPORTUNISTIC);
}
+
+TEST_F(TransportSecurityStateTest, DeleteSince) {
+ scoped_refptr<TransportSecurityState> state(
+ new TransportSecurityState);
+
+ TransportSecurityState::DomainState domain_state;
+ const base::Time current_time(base::Time::Now());
+ const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
+ const base::Time older = current_time - base::TimeDelta::FromSeconds(1000);
+
+ EXPECT_FALSE(state->IsEnabledForHost(&domain_state, "google.com"));
+ domain_state.mode = TransportSecurityState::DomainState::MODE_STRICT;
+ domain_state.expiry = expiry;
+ state->EnableHost("google.com", domain_state);
+
+ state->DeleteSince(expiry);
+ EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "google.com"));
+ state->DeleteSince(older);
+ EXPECT_FALSE(state->IsEnabledForHost(&domain_state, "google.com"));
+}
+
+TEST_F(TransportSecurityStateTest, SerialiseOld) {
+ scoped_refptr<TransportSecurityState> state(
+ new TransportSecurityState);
+ // This is an old-style piece of transport state JSON, which has no creation
+ // date.
+ std::string output =
+ "{ "
+ "\"NiyD+3J1r6z1wjl2n1ALBu94Zj9OsEAMo0kCN8js0Uk=\": {"
+ "\"expiry\": 1266815027.983453, "
+ "\"include_subdomains\": false, "
+ "\"mode\": \"strict\" "
+ "}"
+ "}";
+ bool dirty;
+ EXPECT_TRUE(state->Deserialise(output, &dirty));
+ EXPECT_TRUE(dirty);
+}
+
+TEST_F(TransportSecurityStateTest, IsPreloaded) {
+ const std::string paypal =
+ TransportSecurityState::CanonicaliseHost("paypal.com");
+ const std::string www_paypal =
+ TransportSecurityState::CanonicaliseHost("www.paypal.com");
+ const std::string a_www_paypal =
+ TransportSecurityState::CanonicaliseHost("a.www.paypal.com");
+ const std::string abc_paypal =
+ TransportSecurityState::CanonicaliseHost("a.b.c.paypal.com");
+ const std::string example =
+ TransportSecurityState::CanonicaliseHost("example.com");
+ const std::string aypal =
+ TransportSecurityState::CanonicaliseHost("aypal.com");
+
+ bool b;
+ EXPECT_FALSE(TransportSecurityState::isPreloadedSTS(paypal, &b));
+ EXPECT_TRUE(TransportSecurityState::isPreloadedSTS(www_paypal, &b));
+ EXPECT_FALSE(b);
+ EXPECT_FALSE(TransportSecurityState::isPreloadedSTS(a_www_paypal, &b));
+ EXPECT_FALSE(TransportSecurityState::isPreloadedSTS(abc_paypal, &b));
+ EXPECT_FALSE(TransportSecurityState::isPreloadedSTS(example, &b));
+ EXPECT_FALSE(TransportSecurityState::isPreloadedSTS(aypal, &b));
+}
+
+TEST_F(TransportSecurityStateTest, Preloaded) {
+ scoped_refptr<TransportSecurityState> state(
+ new TransportSecurityState);
+ TransportSecurityState::DomainState domain_state;
+ EXPECT_FALSE(state->IsEnabledForHost(&domain_state, "paypal.com"));
+ EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "www.paypal.com"));
+ EXPECT_EQ(domain_state.mode,
+ TransportSecurityState::DomainState::MODE_STRICT);
+ EXPECT_FALSE(domain_state.include_subdomains);
+ EXPECT_FALSE(state->IsEnabledForHost(&domain_state, "www2.paypal.com"));
+ EXPECT_FALSE(state->IsEnabledForHost(&domain_state, "a.www.paypal.com"));
+
+ EXPECT_FALSE(state->IsEnabledForHost(&domain_state, "elanex.biz"));
+ EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "www.elanex.biz"));
+ EXPECT_EQ(domain_state.mode,
+ TransportSecurityState::DomainState::MODE_STRICT);
+ EXPECT_FALSE(state->IsEnabledForHost(&domain_state, "foo.elanex.biz"));
+ EXPECT_FALSE(state->IsEnabledForHost(&domain_state, "a.foo.elanex.biz"));
+}
+
+} // namespace net
diff --git a/net/base/upload_data.cc b/net/base/upload_data.cc
index 0045409..198b051 100644
--- a/net/base/upload_data.cc
+++ b/net/base/upload_data.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -6,27 +6,42 @@
#include "base/file_util.h"
#include "base/logging.h"
+#include "net/base/net_errors.h"
namespace net {
-uint64 UploadData::GetContentLength() const {
+uint64 UploadData::GetContentLength() {
uint64 len = 0;
- std::vector<Element>::const_iterator it = elements_.begin();
+ std::vector<Element>::iterator it = elements_.begin();
for (; it != elements_.end(); ++it)
len += (*it).GetContentLength();
return len;
}
-uint64 UploadData::Element::GetContentLength() const {
+uint64 UploadData::Element::GetContentLength() {
+ if (override_content_length_ || content_length_computed_)
+ return content_length_;
+
if (type_ == TYPE_BYTES)
return static_cast<uint64>(bytes_.size());
- DCHECK(type_ == TYPE_FILE);
+ DCHECK_EQ(TYPE_FILE, type_);
+ DCHECK(!file_stream_);
// TODO(darin): This size calculation could be out of sync with the state of
// the file when we get around to reading it. We should probably find a way
// to lock the file or somehow protect against this error condition.
+ content_length_computed_ = true;
+ content_length_ = 0;
+
+ // We need to open the file here to decide if we should report the file's
+ // size or zero. We cache the open file, so that we can still read it when
+ // it comes time to.
+ file_stream_ = NewFileStreamForReading();
+ if (!file_stream_)
+ return 0;
+
int64 length = 0;
if (!file_util::GetFileSize(file_path_, &length))
return 0;
@@ -35,7 +50,40 @@
return 0; // range is beyond eof
// compensate for the offset and clip file_range_length_ to eof
- return std::min(length - file_range_offset_, file_range_length_);
+ content_length_ = std::min(length - file_range_offset_, file_range_length_);
+ return content_length_;
+}
+
+FileStream* UploadData::Element::NewFileStreamForReading() {
+ // In common usage GetContentLength() will call this first and store the
+ // result into |file_| and a subsequent call (from UploadDataStream) will
+ // get the cached open FileStream.
+ if (file_stream_) {
+ FileStream* file = file_stream_;
+ file_stream_ = NULL;
+ return file;
+ }
+
+ scoped_ptr<FileStream> file(new FileStream());
+ int64 rv = file->Open(file_path_,
+ base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ);
+ if (rv != OK) {
+ // If the file can't be opened, we'll just upload an empty file.
+ DLOG(WARNING) << "Failed to open \"" << file_path_.value()
+ << "\" for reading: " << rv;
+ return NULL;
+ }
+ if (file_range_offset_) {
+ rv = file->Seek(FROM_BEGIN, file_range_offset_);
+ if (rv < 0) {
+ DLOG(WARNING) << "Failed to seek \"" << file_path_.value()
+ << "\" to offset: " << file_range_offset_ << " (" << rv
+ << ")";
+ return NULL;
+ }
+ }
+
+ return file.release();
}
} // namespace net
diff --git a/net/base/upload_data.h b/net/base/upload_data.h
index 02880d3..fe395f0 100644
--- a/net/base/upload_data.h
+++ b/net/base/upload_data.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -7,8 +7,13 @@
#include <vector>
+#include "base/basictypes.h"
#include "base/file_path.h"
+#include "base/logging.h"
#include "base/ref_counted.h"
+#include "net/base/file_stream.h"
+#include "base/time.h"
+#include "testing/gtest/include/gtest/gtest_prod.h"
namespace net {
@@ -24,7 +29,15 @@
class Element {
public:
Element() : type_(TYPE_BYTES), file_range_offset_(0),
- file_range_length_(kuint64max) {
+ file_range_length_(kuint64max),
+ override_content_length_(false),
+ content_length_computed_(false),
+ file_stream_(NULL) {
+ }
+
+ ~Element() {
+ // In the common case |file__stream_| will be null.
+ delete file_stream_;
}
Type type() const { return type_; }
@@ -32,6 +45,10 @@
const FilePath& file_path() const { return file_path_; }
uint64 file_range_offset() const { return file_range_offset_; }
uint64 file_range_length() const { return file_range_length_; }
+ // If NULL time is returned, we do not do the check.
+ const base::Time& expected_file_modification_time() const {
+ return expected_file_modification_time_;
+ }
void SetToBytes(const char* bytes, int bytes_len) {
type_ = TYPE_BYTES;
@@ -39,27 +56,53 @@
}
void SetToFilePath(const FilePath& path) {
- SetToFilePathRange(path, 0, kuint64max);
+ SetToFilePathRange(path, 0, kuint64max, base::Time());
}
+ // If expected_modification_time is NULL, we do not check for the file
+ // change. Also note that the granularity for comparison is time_t, not
+ // the full precision.
void SetToFilePathRange(const FilePath& path,
- uint64 offset, uint64 length) {
+ uint64 offset, uint64 length,
+ const base::Time& expected_modification_time) {
type_ = TYPE_FILE;
file_path_ = path;
file_range_offset_ = offset;
file_range_length_ = length;
+ expected_file_modification_time_ = expected_modification_time;
}
// Returns the byte-length of the element. For files that do not exist, 0
// is returned. This is done for consistency with Mozilla.
- uint64 GetContentLength() const;
+ // Once called, this function will always return the same value.
+ uint64 GetContentLength();
+
+ // Returns a FileStream opened for reading for this element, positioned at
+ // |file_range_offset_|. The caller gets ownership and is responsible
+ // for cleaning up the FileStream. Returns NULL if this element is not of
+ // type TYPE_FILE or if the file is not openable.
+ FileStream* NewFileStreamForReading();
private:
+ // Allows tests to override the result of GetContentLength.
+ void SetContentLength(uint64 content_length) {
+ override_content_length_ = true;
+ content_length_ = content_length;
+ }
+
Type type_;
std::vector<char> bytes_;
FilePath file_path_;
uint64 file_range_offset_;
uint64 file_range_length_;
+ base::Time expected_file_modification_time_;
+ bool override_content_length_;
+ bool content_length_computed_;
+ uint64 content_length_;
+ FileStream* file_stream_;
+
+ FRIEND_TEST(UploadDataStreamTest, FileSmallerThanLength);
+ FRIEND_TEST(HttpNetworkTransactionTest, UploadFileSmallerThanLength);
};
void AppendBytes(const char* bytes, int bytes_len) {
@@ -75,16 +118,18 @@
}
void AppendFileRange(const FilePath& file_path,
- uint64 offset, uint64 length) {
+ uint64 offset, uint64 length,
+ const base::Time& expected_modification_time) {
elements_.push_back(Element());
- elements_.back().SetToFilePathRange(file_path, offset, length);
+ elements_.back().SetToFilePathRange(file_path, offset, length,
+ expected_modification_time);
}
// Returns the total size in bytes of the data to upload.
- uint64 GetContentLength() const;
+ uint64 GetContentLength();
- const std::vector<Element>& elements() const {
- return elements_;
+ std::vector<Element>* elements() {
+ return &elements_;
}
void set_elements(const std::vector<Element>& elements) {
diff --git a/net/base/upload_data_stream.cc b/net/base/upload_data_stream.cc
index d22929a..140aa8a 100644
--- a/net/base/upload_data_stream.cc
+++ b/net/base/upload_data_stream.cc
@@ -1,33 +1,45 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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 "net/base/upload_data_stream.h"
+#include "base/file_util.h"
#include "base/logging.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
namespace net {
-UploadDataStream::UploadDataStream(const UploadData* data)
+UploadDataStream* UploadDataStream::Create(UploadData* data, int* error_code) {
+ scoped_ptr<UploadDataStream> stream(new UploadDataStream(data));
+ int rv = stream->FillBuf();
+ if (error_code)
+ *error_code = rv;
+ if (rv != OK)
+ return NULL;
+
+ return stream.release();
+}
+
+UploadDataStream::UploadDataStream(UploadData* data)
: data_(data),
buf_(new IOBuffer(kBufSize)),
buf_len_(0),
- next_element_(data->elements().begin()),
+ next_element_(data->elements()->begin()),
next_element_offset_(0),
next_element_remaining_(0),
total_size_(data->GetContentLength()),
- current_position_(0) {
- FillBuf();
+ current_position_(0),
+ eof_(false) {
}
UploadDataStream::~UploadDataStream() {
}
void UploadDataStream::DidConsume(size_t num_bytes) {
- // TODO(vandebo): Change back to a DCHECK when issue 27870 is resolved.
- CHECK(num_bytes <= buf_len_);
+ DCHECK_LE(num_bytes, buf_len_);
+ DCHECK(!eof_);
buf_len_ -= num_bytes;
if (buf_len_)
@@ -38,14 +50,14 @@
current_position_ += num_bytes;
}
-void UploadDataStream::FillBuf() {
- std::vector<UploadData::Element>::const_iterator end =
- data_->elements().end();
+int UploadDataStream::FillBuf() {
+ std::vector<UploadData::Element>::iterator end =
+ data_->elements()->end();
while (buf_len_ < kBufSize && next_element_ != end) {
bool advance_to_next_element = false;
- const UploadData::Element& element = *next_element_;
+ UploadData::Element& element = *next_element_;
size_t size_remaining = kBufSize - buf_len_;
if (element.type() == UploadData::TYPE_BYTES) {
@@ -65,47 +77,58 @@
} else {
DCHECK(element.type() == UploadData::TYPE_FILE);
- if (!next_element_stream_.IsOpen()) {
- int flags = base::PLATFORM_FILE_OPEN |
- base::PLATFORM_FILE_READ;
- int rv = next_element_stream_.Open(element.file_path(), flags);
- // If the file does not exist, that's technically okay.. we'll just
- // upload an empty file. This is for consistency with Mozilla.
- DLOG_IF(WARNING, rv != OK) << "Failed to open \""
- << element.file_path().value()
- << "\" for reading: " << rv;
-
- next_element_remaining_ = 0; // Default to reading nothing.
- if (rv == OK) {
- uint64 offset = element.file_range_offset();
- if (offset && next_element_stream_.Seek(FROM_BEGIN, offset) < 0) {
- DLOG(WARNING) << "Failed to seek \"" << element.file_path().value()
- << "\" to offset: " << offset;
- } else {
- next_element_remaining_ = element.file_range_length();
+ if (!next_element_remaining_) {
+ // If the underlying file has been changed, treat it as error.
+ // Note that the expected modification time from WebKit is based on
+ // time_t precision. So we have to convert both to time_t to compare.
+ if (!element.expected_file_modification_time().is_null()) {
+ file_util::FileInfo info;
+ if (file_util::GetFileInfo(element.file_path(), &info) &&
+ element.expected_file_modification_time().ToTimeT() !=
+ info.last_modified.ToTimeT()) {
+ return ERR_UPLOAD_FILE_CHANGED;
}
}
+ next_element_remaining_ = element.GetContentLength();
+ next_element_stream_.reset(element.NewFileStreamForReading());
}
int rv = 0;
- int count = static_cast<int>(std::min(
- static_cast<uint64>(size_remaining), next_element_remaining_));
- if (count > 0 &&
- (rv = next_element_stream_.Read(buf_->data() + buf_len_,
- count, NULL)) > 0) {
+ int count =
+ static_cast<int>(std::min(next_element_remaining_,
+ static_cast<uint64>(size_remaining)));
+ if (count > 0) {
+ if (next_element_stream_.get())
+ rv = next_element_stream_->Read(buf_->data() + buf_len_, count, NULL);
+ if (rv <= 0) {
+ // If there's less data to read than we initially observed, then
+ // pad with zero. Otherwise the server will hang waiting for the
+ // rest of the data.
+ memset(buf_->data() + buf_len_, 0, count);
+ rv = count;
+ }
buf_len_ += rv;
- next_element_remaining_ -= rv;
- } else {
+ }
+
+ if (static_cast<int>(next_element_remaining_) == rv) {
advance_to_next_element = true;
+ } else {
+ next_element_remaining_ -= rv;
}
}
if (advance_to_next_element) {
++next_element_;
next_element_offset_ = 0;
- next_element_stream_.Close();
+ next_element_remaining_ = 0;
+ next_element_stream_.reset();
}
}
+
+ if (next_element_ == end && !buf_len_)
+ eof_ = true;
+
+ return OK;
}
} // namespace net
diff --git a/net/base/upload_data_stream.h b/net/base/upload_data_stream.h
index d703c3d..5e28721 100644
--- a/net/base/upload_data_stream.h
+++ b/net/base/upload_data_stream.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -14,7 +14,11 @@
class UploadDataStream {
public:
- explicit UploadDataStream(const UploadData* data);
+ // Returns a new instance of UploadDataStream if it can be created and
+ // initialized successfully. If not, NULL will be returned and the error
+ // code will be set if the output parameter error_code is not empty.
+ static UploadDataStream* Create(UploadData* data, int* error_code);
+
~UploadDataStream();
// Returns the stream's buffer and buffer length.
@@ -27,13 +31,27 @@
void DidConsume(size_t num_bytes);
// Returns the total size of the data stream and the current position.
+ // size() is not to be used to determine whether the stream has ended
+ // because it is possible for the stream to end before its size is reached,
+ // for example, if the file is truncated.
uint64 size() const { return total_size_; }
uint64 position() const { return current_position_; }
- private:
- void FillBuf();
+ // Returns whether there is no more data to read, regardless of whether
+ // position < size.
+ bool eof() const { return eof_; }
- const UploadData* data_;
+ private:
+ // Protects from public access since now we have a static creator function
+ // which will do both creation and initialization and might return an error.
+ explicit UploadDataStream(UploadData* data);
+
+ // Fills the buffer with any remaining data and sets eof_ if there was nothing
+ // left to fill the buffer with.
+ // Returns OK if the operation succeeds. Otherwise error code is returned.
+ int FillBuf();
+
+ UploadData* data_;
// This buffer is filled with data to be uploaded. The data to be sent is
// always at the front of the buffer. If we cannot send all of the buffer at
@@ -44,7 +62,7 @@
size_t buf_len_;
// Iterator to the upload element to be written to the send buffer next.
- std::vector<UploadData::Element>::const_iterator next_element_;
+ std::vector<UploadData::Element>::iterator next_element_;
// The byte offset into next_element_'s data buffer if the next element is
// a TYPE_BYTES element.
@@ -52,7 +70,7 @@
// A stream to the currently open file, for next_element_ if the next element
// is a TYPE_FILE element.
- FileStream next_element_stream_;
+ scoped_ptr<FileStream> next_element_stream_;
// The number of bytes remaining to be read from the currently open file
// if the next element is of TYPE_FILE.
@@ -62,7 +80,10 @@
uint64 total_size_;
uint64 current_position_;
- DISALLOW_EVIL_CONSTRUCTORS(UploadDataStream);
+ // Whether there is no data left to read.
+ bool eof_;
+
+ DISALLOW_COPY_AND_ASSIGN(UploadDataStream);
};
} // namespace net
diff --git a/net/base/upload_data_stream_unittest.cc b/net/base/upload_data_stream_unittest.cc
new file mode 100644
index 0000000..92c065e
--- /dev/null
+++ b/net/base/upload_data_stream_unittest.cc
@@ -0,0 +1,127 @@
+// 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 "net/base/upload_data_stream.h"
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/scoped_ptr.h"
+#include "base/time.h"
+#include "net/base/net_errors.h"
+#include "net/base/upload_data.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+namespace {
+
+const char kTestData[] = "0123456789";
+const int kTestDataSize = arraysize(kTestData) - 1;
+
+} // namespace
+
+class UploadDataStreamTest : public PlatformTest {
+ public:
+ UploadDataStreamTest() : upload_data_(new UploadData) { }
+
+ void FileChangedHelper(const FilePath& file_path,
+ const base::Time& time,
+ bool error_expected);
+
+ scoped_refptr<UploadData> upload_data_;
+};
+
+TEST_F(UploadDataStreamTest, EmptyUploadData) {
+ upload_data_->AppendBytes("", 0);
+ scoped_ptr<UploadDataStream> stream(
+ UploadDataStream::Create(upload_data_, NULL));
+ ASSERT_TRUE(stream.get());
+ EXPECT_TRUE(stream->eof());
+}
+
+TEST_F(UploadDataStreamTest, ConsumeAll) {
+ upload_data_->AppendBytes(kTestData, kTestDataSize);
+ scoped_ptr<UploadDataStream> stream(
+ UploadDataStream::Create(upload_data_, NULL));
+ ASSERT_TRUE(stream.get());
+ while (!stream->eof()) {
+ stream->DidConsume(stream->buf_len());
+ }
+}
+
+TEST_F(UploadDataStreamTest, FileSmallerThanLength) {
+ FilePath temp_file_path;
+ ASSERT_TRUE(file_util::CreateTemporaryFile(&temp_file_path));
+ ASSERT_EQ(kTestDataSize, file_util::WriteFile(temp_file_path,
+ kTestData, kTestDataSize));
+ const uint64 kFakeSize = kTestDataSize*2;
+
+ std::vector<UploadData::Element> elements;
+ UploadData::Element element;
+ element.SetToFilePath(temp_file_path);
+ element.SetContentLength(kFakeSize);
+ elements.push_back(element);
+ upload_data_->set_elements(elements);
+ EXPECT_EQ(kFakeSize, upload_data_->GetContentLength());
+
+ scoped_ptr<UploadDataStream> stream(
+ UploadDataStream::Create(upload_data_, NULL));
+ ASSERT_TRUE(stream.get());
+ EXPECT_FALSE(stream->eof());
+ uint64 read_counter = 0;
+ while (!stream->eof()) {
+ read_counter += stream->buf_len();
+ stream->DidConsume(stream->buf_len());
+ }
+ // UpdateDataStream will pad out the file with 0 bytes so that the HTTP
+ // transaction doesn't hang. Therefore we expected the full size.
+ EXPECT_EQ(read_counter, stream->size());
+
+ file_util::Delete(temp_file_path, false);
+}
+
+void UploadDataStreamTest::FileChangedHelper(const FilePath& file_path,
+ const base::Time& time,
+ bool error_expected) {
+ std::vector<UploadData::Element> elements;
+ UploadData::Element element;
+ element.SetToFilePathRange(file_path, 1, 2, time);
+ elements.push_back(element);
+ upload_data_->set_elements(elements);
+
+ int error_code;
+ scoped_ptr<UploadDataStream> stream(
+ UploadDataStream::Create(upload_data_, &error_code));
+ if (error_expected)
+ ASSERT_TRUE(!stream.get() && error_code == net::ERR_UPLOAD_FILE_CHANGED);
+ else
+ ASSERT_TRUE(stream.get() && error_code == net::OK);
+}
+
+TEST_F(UploadDataStreamTest, FileChanged) {
+ FilePath temp_file_path;
+ ASSERT_TRUE(file_util::CreateTemporaryFile(&temp_file_path));
+ ASSERT_EQ(kTestDataSize, file_util::WriteFile(temp_file_path,
+ kTestData, kTestDataSize));
+
+ file_util::FileInfo file_info;
+ ASSERT_TRUE(file_util::GetFileInfo(temp_file_path, &file_info));
+
+ // Test file not changed.
+ FileChangedHelper(temp_file_path, file_info.last_modified, false);
+
+ // Test file changed.
+ FileChangedHelper(temp_file_path,
+ file_info.last_modified - base::TimeDelta::FromSeconds(1),
+ true);
+
+ file_util::Delete(temp_file_path, false);
+}
+
+} // namespace net
+
diff --git a/net/base/winsock_init.cc b/net/base/winsock_init.cc
index 5c3f005..ccaf01c 100644
--- a/net/base/winsock_init.cc
+++ b/net/base/winsock_init.cc
@@ -13,11 +13,11 @@
class WinsockInitSingleton {
public:
- WinsockInitSingleton() : did_init_(false) {
+ WinsockInitSingleton() {
WORD winsock_ver = MAKEWORD(2, 2);
WSAData wsa_data;
- did_init_ = (WSAStartup(winsock_ver, &wsa_data) == 0);
- if (did_init_) {
+ bool did_init = (WSAStartup(winsock_ver, &wsa_data) == 0);
+ if (did_init) {
DCHECK(wsa_data.wVersion == winsock_ver);
// The first time WSAGetLastError is called, the delay load helper will
@@ -32,12 +32,9 @@
}
~WinsockInitSingleton() {
- if (did_init_)
- WSACleanup();
+ // Don't call WSACleanup() since the worker pool threads can continue to
+ // call getaddrinfo() after Winsock has shutdown, which can lead to crashes.
}
-
- private:
- bool did_init_;
};
} // namespace
diff --git a/net/base/winsock_init.h b/net/base/winsock_init.h
index ab34fb0..9608e95 100644
--- a/net/base/winsock_init.h
+++ b/net/base/winsock_init.h
@@ -3,9 +3,7 @@
// found in the LICENSE file.
// Winsock initialization must happen before any Winsock calls are made. The
-// EnsureWinsockInit method will make sure that WSAStartup has been called. If
-// the call to WSAStartup caused Winsock to initialize, WSACleanup will be
-// called automatically on program shutdown.
+// EnsureWinsockInit method will make sure that WSAStartup has been called.
#ifndef NET_BASE_WINSOCK_INIT_H_
#define NET_BASE_WINSOCK_INIT_H_
diff --git a/net/base/x509_cert_types.cc b/net/base/x509_cert_types.cc
new file mode 100644
index 0000000..8f7a2ae
--- /dev/null
+++ b/net/base/x509_cert_types.cc
@@ -0,0 +1,114 @@
+// Copyright (c) 2006-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 "net/base/x509_cert_types.h"
+
+#include <ostream>
+
+#include "net/base/x509_certificate.h"
+#include "base/logging.h"
+
+namespace net {
+
+bool match(const std::string &str, const std::string &against) {
+ // TODO(snej): Use the full matching rules specified in RFC 5280 sec. 7.1
+ // including trimming and case-folding: <http://www.ietf.org/rfc/rfc5280.txt>.
+ return against == str;
+}
+
+bool match(const std::vector<std::string> &rdn1,
+ const std::vector<std::string> &rdn2) {
+ // "Two relative distinguished names RDN1 and RDN2 match if they have the
+ // same number of naming attributes and for each naming attribute in RDN1
+ // there is a matching naming attribute in RDN2." --RFC 5280 sec. 7.1.
+ if (rdn1.size() != rdn2.size())
+ return false;
+ for (unsigned i1 = 0; i1 < rdn1.size(); ++i1) {
+ unsigned i2;
+ for (i2 = 0; i2 < rdn2.size(); ++i2) {
+ if (match(rdn1[i1], rdn2[i2]))
+ break;
+ }
+ if (i2 == rdn2.size())
+ return false;
+ }
+ return true;
+}
+
+
+bool CertPrincipal::Matches(const CertPrincipal& against) const {
+ return match(common_name, against.common_name) &&
+ match(common_name, against.common_name) &&
+ match(locality_name, against.locality_name) &&
+ match(state_or_province_name, against.state_or_province_name) &&
+ match(country_name, against.country_name) &&
+ match(street_addresses, against.street_addresses) &&
+ match(organization_names, against.organization_names) &&
+ match(organization_unit_names, against.organization_unit_names) &&
+ match(domain_components, against.domain_components);
+}
+
+std::ostream& operator<<(std::ostream& s, const CertPrincipal& p) {
+ s << "CertPrincipal[";
+ if (!p.common_name.empty())
+ s << "cn=\"" << p.common_name << "\" ";
+ for (unsigned i = 0; i < p.street_addresses.size(); ++i)
+ s << "street=\"" << p.street_addresses[i] << "\" ";
+ if (!p.locality_name.empty())
+ s << "l=\"" << p.locality_name << "\" ";
+ for (unsigned i = 0; i < p.organization_names.size(); ++i)
+ s << "o=\"" << p.organization_names[i] << "\" ";
+ for (unsigned i = 0; i < p.organization_unit_names.size(); ++i)
+ s << "ou=\"" << p.organization_unit_names[i] << "\" ";
+ if (!p.state_or_province_name.empty())
+ s << "st=\"" << p.state_or_province_name << "\" ";
+ if (!p.country_name.empty())
+ s << "c=\"" << p.country_name << "\" ";
+ for (unsigned i = 0; i < p.domain_components.size(); ++i)
+ s << "dc=\"" << p.domain_components[i] << "\" ";
+ return s << "]";
+}
+
+CertPolicy::Judgment CertPolicy::Check(
+ X509Certificate* cert) const {
+ // It shouldn't matter which set we check first, but we check denied first
+ // in case something strange has happened.
+
+ if (denied_.find(cert->fingerprint()) != denied_.end()) {
+ // DCHECK that the order didn't matter.
+ DCHECK(allowed_.find(cert->fingerprint()) == allowed_.end());
+ return DENIED;
+ }
+
+ if (allowed_.find(cert->fingerprint()) != allowed_.end()) {
+ // DCHECK that the order didn't matter.
+ DCHECK(denied_.find(cert->fingerprint()) == denied_.end());
+ return ALLOWED;
+ }
+
+ // We don't have a policy for this cert.
+ return UNKNOWN;
+}
+
+void CertPolicy::Allow(X509Certificate* cert) {
+ // Put the cert in the allowed set and (maybe) remove it from the denied set.
+ denied_.erase(cert->fingerprint());
+ allowed_.insert(cert->fingerprint());
+}
+
+void CertPolicy::Deny(X509Certificate* cert) {
+ // Put the cert in the denied set and (maybe) remove it from the allowed set.
+ allowed_.erase(cert->fingerprint());
+ denied_.insert(cert->fingerprint());
+}
+
+bool CertPolicy::HasAllowedCert() const {
+ return !allowed_.empty();
+}
+
+bool CertPolicy::HasDeniedCert() const {
+ return !denied_.empty();
+}
+
+} // namespace net
diff --git a/net/base/x509_cert_types.h b/net/base/x509_cert_types.h
new file mode 100644
index 0000000..ad3ea2d
--- /dev/null
+++ b/net/base/x509_cert_types.h
@@ -0,0 +1,137 @@
+// Copyright (c) 2007-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.
+
+#ifndef NET_BASE_X509_CERT_TYPES_H_
+#define NET_BASE_X509_CERT_TYPES_H_
+
+#include <string.h>
+
+#include <functional>
+#include <iosfwd>
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/ref_counted.h"
+#include "base/singleton.h"
+#include "base/time.h"
+#include "testing/gtest/include/gtest/gtest_prod.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#include <wincrypt.h>
+#elif defined(OS_MACOSX)
+#include <Security/x509defs.h>
+#elif defined(USE_NSS)
+// Forward declaration; real one in <cert.h>
+struct CERTCertificateStr;
+#endif
+
+namespace net {
+
+class X509Certificate;
+
+// SHA-1 fingerprint (160 bits) of a certificate.
+struct SHA1Fingerprint {
+ bool Equals(const SHA1Fingerprint& other) const {
+ return memcmp(data, other.data, sizeof(data)) == 0;
+ }
+
+ unsigned char data[20];
+};
+
+class SHA1FingerprintLessThan
+ : public std::binary_function<SHA1Fingerprint, SHA1Fingerprint, bool> {
+ public:
+ bool operator() (const SHA1Fingerprint& lhs,
+ const SHA1Fingerprint& rhs) const {
+ return memcmp(lhs.data, rhs.data, sizeof(lhs.data)) < 0;
+ }
+};
+
+// CertPrincipal represents the issuer or subject field of an X.509 certificate.
+struct CertPrincipal {
+ CertPrincipal() { }
+ explicit CertPrincipal(const std::string& name) : common_name(name) { }
+
+ // Parses a BER-format DistinguishedName.
+ bool ParseDistinguishedName(const void* ber_name_data, size_t length);
+
+#if defined(OS_MACOSX)
+ // Parses a CSSM_X509_NAME struct.
+ void Parse(const CSSM_X509_NAME* name);
+#endif
+
+ // Returns true if all attributes of the two objects match,
+ // where "match" is defined in RFC 5280 sec. 7.1.
+ bool Matches(const CertPrincipal& against) const;
+
+ // The different attributes for a principal. They may be "".
+ // Note that some of them can have several values.
+
+ std::string common_name;
+ std::string locality_name;
+ std::string state_or_province_name;
+ std::string country_name;
+
+ std::vector<std::string> street_addresses;
+ std::vector<std::string> organization_names;
+ std::vector<std::string> organization_unit_names;
+ std::vector<std::string> domain_components;
+};
+
+// Writes a human-readable description of a CertPrincipal, for debugging.
+std::ostream& operator<<(std::ostream& s, const CertPrincipal& p);
+
+// This class is useful for maintaining policies about which certificates are
+// permitted or forbidden for a particular purpose.
+class CertPolicy {
+ public:
+ // The judgments this policy can reach.
+ enum Judgment {
+ // We don't have policy information for this certificate.
+ UNKNOWN,
+
+ // This certificate is allowed.
+ ALLOWED,
+
+ // This certificate is denied.
+ DENIED,
+ };
+
+ // Returns the judgment this policy makes about this certificate.
+ Judgment Check(X509Certificate* cert) const;
+
+ // Causes the policy to allow this certificate.
+ void Allow(X509Certificate* cert);
+
+ // Causes the policy to deny this certificate.
+ void Deny(X509Certificate* cert);
+
+ // Returns true if this policy has allowed at least one certificate.
+ bool HasAllowedCert() const;
+
+ // Returns true if this policy has denied at least one certificate.
+ bool HasDeniedCert() const;
+
+ private:
+ // The set of fingerprints of allowed certificates.
+ std::set<SHA1Fingerprint, SHA1FingerprintLessThan> allowed_;
+
+ // The set of fingerprints of denied certificates.
+ std::set<SHA1Fingerprint, SHA1FingerprintLessThan> denied_;
+};
+
+#if defined(OS_MACOSX)
+// Compares two OIDs by value.
+inline bool CSSMOIDEqual(const CSSM_OID* oid1, const CSSM_OID* oid2) {
+ return oid1->Length == oid2->Length &&
+ (memcmp(oid1->Data, oid2->Data, oid1->Length) == 0);
+}
+#endif
+
+} // namespace net
+
+#endif // NET_BASE_X509_CERT_TYPES_H_
diff --git a/net/base/x509_cert_types_mac.cc b/net/base/x509_cert_types_mac.cc
new file mode 100644
index 0000000..504dde5
--- /dev/null
+++ b/net/base/x509_cert_types_mac.cc
@@ -0,0 +1,293 @@
+// Copyright (c) 2006-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 "net/base/x509_cert_types.h"
+
+#include <CoreServices/CoreServices.h>
+#include <Security/Security.h>
+#include <Security/SecAsn1Coder.h>
+
+#include "base/logging.h"
+#include "base/i18n/icu_string_conversions.h"
+#include "base/utf_string_conversions.h"
+
+namespace net {
+
+static const CSSM_OID* kOIDs[] = {
+ &CSSMOID_CommonName,
+ &CSSMOID_LocalityName,
+ &CSSMOID_StateProvinceName,
+ &CSSMOID_CountryName,
+ &CSSMOID_StreetAddress,
+ &CSSMOID_OrganizationName,
+ &CSSMOID_OrganizationalUnitName,
+ &CSSMOID_DNQualifier // This should be "DC" but is undoubtedly wrong.
+}; // TODO(avi): Find the right OID.
+
+// Converts raw CSSM_DATA to a std::string. (Char encoding is unaltered.)
+static std::string DataToString(CSSM_DATA data);
+
+// Converts raw CSSM_DATA in ISO-8859-1 to a std::string in UTF-8.
+static std::string Latin1DataToUTF8String(CSSM_DATA data);
+
+// Converts big-endian UTF-16 to UTF-8 in a std::string.
+// Note: The byte-order flipping is done in place on the input buffer!
+static bool UTF16BigEndianToUTF8(char16* chars, size_t length,
+ std::string* out_string);
+
+// Converts big-endian UTF-32 to UTF-8 in a std::string.
+// Note: The byte-order flipping is done in place on the input buffer!
+static bool UTF32BigEndianToUTF8(char32* chars, size_t length,
+ std::string* out_string);
+
+// Adds a type+value pair to the appropriate vector from a C array.
+// The array is keyed by the matching OIDs from kOIDS[].
+ static void AddTypeValuePair(const CSSM_OID type,
+ const std::string& value,
+ std::vector<std::string>* values[]);
+
+// Stores the first string of the vector, if any, to *single_value.
+static void SetSingle(const std::vector<std::string> &values,
+ std::string* single_value);
+
+
+void CertPrincipal::Parse(const CSSM_X509_NAME* name) {
+ std::vector<std::string> common_names, locality_names, state_names,
+ country_names;
+
+ std::vector<std::string>* values[] = {
+ &common_names, &locality_names,
+ &state_names, &country_names,
+ &(this->street_addresses),
+ &(this->organization_names),
+ &(this->organization_unit_names),
+ &(this->domain_components)
+ };
+ DCHECK(arraysize(kOIDs) == arraysize(values));
+
+ for (size_t rdn = 0; rdn < name->numberOfRDNs; ++rdn) {
+ CSSM_X509_RDN rdn_struct = name->RelativeDistinguishedName[rdn];
+ for (size_t pair = 0; pair < rdn_struct.numberOfPairs; ++pair) {
+ CSSM_X509_TYPE_VALUE_PAIR pair_struct =
+ rdn_struct.AttributeTypeAndValue[pair];
+ AddTypeValuePair(pair_struct.type,
+ DataToString(pair_struct.value),
+ values);
+ }
+ }
+
+ SetSingle(common_names, &this->common_name);
+ SetSingle(locality_names, &this->locality_name);
+ SetSingle(state_names, &this->state_or_province_name);
+ SetSingle(country_names, &this->country_name);
+}
+
+
+// The following structs and templates work with Apple's very arcane and under-
+// documented SecAsn1Parser API, which is apparently the same as NSS's ASN.1
+// decoder:
+// http://www.mozilla.org/projects/security/pki/nss/tech-notes/tn1.html
+
+// These are used to parse the contents of a raw
+// BER DistinguishedName structure.
+
+struct KeyValuePair {
+ CSSM_OID key;
+ int value_type;
+ CSSM_DATA value;
+
+ enum {
+ kTypeOther = 0,
+ kTypePrintableString,
+ kTypeIA5String,
+ kTypeT61String,
+ kTypeUTF8String,
+ kTypeBMPString,
+ kTypeUniversalString,
+ };
+};
+
+static const SecAsn1Template kStringValueTemplate[] = {
+ { SEC_ASN1_CHOICE, offsetof(KeyValuePair, value_type), },
+ { SEC_ASN1_PRINTABLE_STRING,
+ offsetof(KeyValuePair, value), 0, KeyValuePair::kTypePrintableString },
+ { SEC_ASN1_IA5_STRING,
+ offsetof(KeyValuePair, value), 0, KeyValuePair::kTypeIA5String },
+ { SEC_ASN1_T61_STRING,
+ offsetof(KeyValuePair, value), 0, KeyValuePair::kTypeT61String },
+ { SEC_ASN1_UTF8_STRING,
+ offsetof(KeyValuePair, value), 0, KeyValuePair::kTypeUTF8String },
+ { SEC_ASN1_BMP_STRING,
+ offsetof(KeyValuePair, value), 0, KeyValuePair::kTypeBMPString },
+ { SEC_ASN1_UNIVERSAL_STRING,
+ offsetof(KeyValuePair, value), 0, KeyValuePair::kTypeUniversalString },
+ { 0, }
+};
+
+static const SecAsn1Template kKeyValuePairTemplate[] = {
+ { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(KeyValuePair) },
+ { SEC_ASN1_OBJECT_ID, offsetof(KeyValuePair, key), },
+ { SEC_ASN1_INLINE, 0, &kStringValueTemplate, },
+ { 0, }
+};
+
+struct KeyValuePairs {
+ KeyValuePair* pairs;
+};
+
+static const SecAsn1Template kKeyValuePairSetTemplate[] = {
+ { SEC_ASN1_SET_OF, offsetof(KeyValuePairs,pairs),
+ kKeyValuePairTemplate, sizeof(KeyValuePairs) }
+};
+
+struct X509Name {
+ KeyValuePairs** pairs_list;
+};
+
+static const SecAsn1Template kNameTemplate[] = {
+ { SEC_ASN1_SEQUENCE_OF, offsetof(X509Name,pairs_list),
+ kKeyValuePairSetTemplate, sizeof(X509Name) }
+};
+
+bool CertPrincipal::ParseDistinguishedName(const void* ber_name_data,
+ size_t length) {
+ DCHECK(ber_name_data);
+
+ // First parse the BER |name_data| into the above structs.
+ SecAsn1CoderRef coder = NULL;
+ SecAsn1CoderCreate(&coder);
+ DCHECK(coder);
+ X509Name* name = NULL;
+ OSStatus err = SecAsn1Decode(coder, ber_name_data, length, kNameTemplate,
+ &name);
+ if (err) {
+ LOG(ERROR) << "SecAsn1Decode returned " << err << "; name=" << name;
+ SecAsn1CoderRelease(coder);
+ return false;
+ }
+
+ // Now scan the structs and add the values to my string vectors.
+ // I don't store multiple common/locality/state/country names, so use
+ // temporary vectors for those.
+ std::vector<std::string> common_names, locality_names, state_names,
+ country_names;
+ std::vector<std::string>* values[] = {
+ &common_names, &locality_names,
+ &state_names, &country_names,
+ &this->street_addresses,
+ &this->organization_names,
+ &this->organization_unit_names,
+ &this->domain_components
+ };
+ DCHECK(arraysize(kOIDs) == arraysize(values));
+
+ for (int rdn=0; name[rdn].pairs_list; ++rdn) {
+ KeyValuePair *pair;
+ for (int pair_index = 0;
+ NULL != (pair = name[rdn].pairs_list[0][pair_index].pairs);
+ ++pair_index) {
+ switch (pair->value_type) {
+ case KeyValuePair::kTypeIA5String: // ASCII (that means 7-bit!)
+ case KeyValuePair::kTypePrintableString: // a subset of ASCII
+ case KeyValuePair::kTypeUTF8String: // UTF-8
+ AddTypeValuePair(pair->key, DataToString(pair->value), values);
+ break;
+ case KeyValuePair::kTypeT61String: // T61, pretend it's Latin-1
+ AddTypeValuePair(pair->key,
+ Latin1DataToUTF8String(pair->value),
+ values);
+ break;
+ case KeyValuePair::kTypeBMPString: { // UTF-16, big-endian
+ std::string value;
+ UTF16BigEndianToUTF8(reinterpret_cast<char16*>(pair->value.Data),
+ pair->value.Length / sizeof(char16),
+ &value);
+ AddTypeValuePair(pair->key, value, values);
+ break;
+ }
+ case KeyValuePair::kTypeUniversalString: { // UTF-32, big-endian
+ std::string value;
+ UTF32BigEndianToUTF8(reinterpret_cast<char32*>(pair->value.Data),
+ pair->value.Length / sizeof(char32),
+ &value);
+ AddTypeValuePair(pair->key, value, values);
+ break;
+ }
+ default:
+ DCHECK_EQ(pair->value_type, KeyValuePair::kTypeOther);
+ // We don't know what data type this is, but we'll store it as a blob.
+ // Displaying the string may not work, but at least it can be compared
+ // byte-for-byte by a Matches() call.
+ AddTypeValuePair(pair->key, DataToString(pair->value), values);
+ break;
+ }
+ }
+ }
+
+ SetSingle(common_names, &this->common_name);
+ SetSingle(locality_names, &this->locality_name);
+ SetSingle(state_names, &this->state_or_province_name);
+ SetSingle(country_names, &this->country_name);
+
+ // Releasing |coder| frees all the memory pointed to via |name|.
+ SecAsn1CoderRelease(coder);
+ return true;
+}
+
+
+// SUBROUTINES:
+
+static std::string DataToString(CSSM_DATA data) {
+ return std::string(
+ reinterpret_cast<std::string::value_type*>(data.Data),
+ data.Length);
+}
+
+static std::string Latin1DataToUTF8String(CSSM_DATA data) {
+ string16 utf16;
+ if (!CodepageToUTF16(DataToString(data), base::kCodepageLatin1,
+ base::OnStringConversionError::FAIL, &utf16))
+ return "";
+ return UTF16ToUTF8(utf16);
+}
+
+bool UTF16BigEndianToUTF8(char16* chars, size_t length,
+ std::string* out_string) {
+ for (size_t i = 0; i < length; i++)
+ chars[i] = EndianU16_BtoN(chars[i]);
+ return UTF16ToUTF8(chars, length, out_string);
+}
+
+bool UTF32BigEndianToUTF8(char32* chars, size_t length,
+ std::string* out_string) {
+ for (size_t i = 0; i < length; i++)
+ chars[i] = EndianS32_BtoN(chars[i]);
+#if defined(WCHAR_T_IS_UTF32)
+ return WideToUTF8(reinterpret_cast<const wchar_t*>(chars),
+ length, out_string);
+#else
+#error This code doesn't handle 16-bit wchar_t.
+#endif
+}
+
+ static void AddTypeValuePair(const CSSM_OID type,
+ const std::string& value,
+ std::vector<std::string>* values[]) {
+ for (size_t oid = 0; oid < arraysize(kOIDs); ++oid) {
+ if (CSSMOIDEqual(&type, kOIDs[oid])) {
+ values[oid]->push_back(value);
+ break;
+ }
+ }
+}
+
+static void SetSingle(const std::vector<std::string> &values,
+ std::string* single_value) {
+ // We don't expect to have more than one CN, L, S, and C.
+ LOG_IF(WARNING, values.size() > 1) << "Didn't expect multiple values";
+ if (values.size() > 0)
+ *single_value = values[0];
+}
+
+} // namespace net
diff --git a/net/base/x509_cert_types_unittest.cc b/net/base/x509_cert_types_unittest.cc
new file mode 100644
index 0000000..50012b1
--- /dev/null
+++ b/net/base/x509_cert_types_unittest.cc
@@ -0,0 +1,344 @@
+// 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 "net/base/x509_cert_types.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// 0:d=0 hl=2 l= 95 cons: SEQUENCE
+// 2:d=1 hl=2 l= 11 cons: SET
+// 4:d=2 hl=2 l= 9 cons: SEQUENCE
+// 6:d=3 hl=2 l= 3 prim: OBJECT :countryName
+// 11:d=3 hl=2 l= 2 prim: PRINTABLESTRING :US
+// 15:d=1 hl=2 l= 23 cons: SET
+// 17:d=2 hl=2 l= 21 cons: SEQUENCE
+// 19:d=3 hl=2 l= 3 prim: OBJECT :organizationName
+// 24:d=3 hl=2 l= 14 prim: PRINTABLESTRING :VeriSign, Inc.
+// 40:d=1 hl=2 l= 55 cons: SET
+// 42:d=2 hl=2 l= 53 cons: SEQUENCE
+// 44:d=3 hl=2 l= 3 prim: OBJECT :organizationalUnitName
+// 49:d=3 hl=2 l= 46 prim: PRINTABLESTRING :Class 1 Public Primary Certification Authority
+static const uint8 VerisignDN[] = {
+ 0x30, 0x5f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e,
+ 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63,
+ 0x2e, 0x31, 0x37, 0x30, 0x35, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2e, 0x43,
+ 0x6c, 0x61, 0x73, 0x73, 0x20, 0x31, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63,
+ 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74,
+ 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74,
+ 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79
+};
+
+// 0:d=0 hl=2 l= 125 cons: SEQUENCE
+// 2:d=1 hl=2 l= 11 cons: SET
+// 4:d=2 hl=2 l= 9 cons: SEQUENCE
+// 6:d=3 hl=2 l= 3 prim: OBJECT :countryName
+// 11:d=3 hl=2 l= 2 prim: PRINTABLESTRING :IL
+// 15:d=1 hl=2 l= 22 cons: SET
+// 17:d=2 hl=2 l= 20 cons: SEQUENCE
+// 19:d=3 hl=2 l= 3 prim: OBJECT :organizationName
+// 24:d=3 hl=2 l= 13 prim: PRINTABLESTRING :StartCom Ltd.
+// 39:d=1 hl=2 l= 43 cons: SET
+// 41:d=2 hl=2 l= 41 cons: SEQUENCE
+// 43:d=3 hl=2 l= 3 prim: OBJECT :organizationalUnitName
+// 48:d=3 hl=2 l= 34 prim: PRINTABLESTRING :Secure Digital Certificate Signing
+// 84:d=1 hl=2 l= 41 cons: SET
+// 86:d=2 hl=2 l= 39 cons: SEQUENCE
+// 88:d=3 hl=2 l= 3 prim: OBJECT :commonName
+// 93:d=3 hl=2 l= 32 prim: PRINTABLESTRING :StartCom Certification Authority
+static const uint8 StartComDN[] = {
+ 0x30, 0x7d, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x49, 0x4c, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d,
+ 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x4c, 0x74, 0x64, 0x2e,
+ 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x22, 0x53, 0x65,
+ 0x63, 0x75, 0x72, 0x65, 0x20, 0x44, 0x69, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x20,
+ 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x53,
+ 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x31, 0x29, 0x30, 0x27, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x20, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20,
+ 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+ 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79
+};
+
+// 0:d=0 hl=3 l= 174 cons: SEQUENCE
+// 3:d=1 hl=2 l= 11 cons: SET
+// 5:d=2 hl=2 l= 9 cons: SEQUENCE
+// 7:d=3 hl=2 l= 3 prim: OBJECT :countryName
+// 12:d=3 hl=2 l= 2 prim: PRINTABLESTRING :US
+// 16:d=1 hl=2 l= 11 cons: SET
+// 18:d=2 hl=2 l= 9 cons: SEQUENCE
+// 20:d=3 hl=2 l= 3 prim: OBJECT :stateOrProvinceName
+// 25:d=3 hl=2 l= 2 prim: PRINTABLESTRING :UT
+// 29:d=1 hl=2 l= 23 cons: SET
+// 31:d=2 hl=2 l= 21 cons: SEQUENCE
+// 33:d=3 hl=2 l= 3 prim: OBJECT :localityName
+// 38:d=3 hl=2 l= 14 prim: PRINTABLESTRING :Salt Lake City
+// 54:d=1 hl=2 l= 30 cons: SET
+// 56:d=2 hl=2 l= 28 cons: SEQUENCE
+// 58:d=3 hl=2 l= 3 prim: OBJECT :organizationName
+// 63:d=3 hl=2 l= 21 prim: PRINTABLESTRING :The USERTRUST Network
+// 86:d=1 hl=2 l= 33 cons: SET
+// 88:d=2 hl=2 l= 31 cons: SEQUENCE
+// 90:d=3 hl=2 l= 3 prim: OBJECT :organizationalUnitName
+// 95:d=3 hl=2 l= 24 prim: PRINTABLESTRING :http://www.usertrust.com
+//121:d=1 hl=2 l= 54 cons: SET
+//123:d=2 hl=2 l= 52 cons: SEQUENCE
+//125:d=3 hl=2 l= 3 prim: OBJECT :commonName
+//130:d=3 hl=2 l= 45 prim: PRINTABLESTRING :UTN-USERFirst-Client Authentication and Email
+static const uint8 UserTrustDN[] = {
+ 0x30, 0x81, 0xae, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+ 0x02, 0x55, 0x54, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13,
+ 0x0e, 0x53, 0x61, 0x6c, 0x74, 0x20, 0x4c, 0x61, 0x6b, 0x65, 0x20, 0x43, 0x69,
+ 0x74, 0x79, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x15,
+ 0x54, 0x68, 0x65, 0x20, 0x55, 0x53, 0x45, 0x52, 0x54, 0x52, 0x55, 0x53, 0x54,
+ 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x21, 0x30, 0x1f, 0x06,
+ 0x03, 0x55, 0x04, 0x0b, 0x13, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x77, 0x77, 0x77, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x31, 0x36, 0x30, 0x34, 0x06, 0x03, 0x55, 0x04, 0x03,
+ 0x13, 0x2d, 0x55, 0x54, 0x4e, 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46, 0x69, 0x72,
+ 0x73, 0x74, 0x2d, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x41, 0x75, 0x74,
+ 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61,
+ 0x6e, 0x64, 0x20, 0x45, 0x6d, 0x61, 0x69, 0x6c
+};
+
+// 0:d=0 hl=3 l= 190 cons: SEQUENCE
+// 3:d=1 hl=2 l= 63 cons: SET
+// 5:d=2 hl=2 l= 61 cons: SEQUENCE
+// 7:d=3 hl=2 l= 3 prim: OBJECT :commonName
+// 12:d=3 hl=2 l= 54 prim: UTF8STRING :TÜRKTRUST Elektronik Sertifika Hizmet SaÄŸlayıcısı
+// 68:d=1 hl=2 l= 11 cons: SET
+// 70:d=2 hl=2 l= 9 cons: SEQUENCE
+// 72:d=3 hl=2 l= 3 prim: OBJECT :countryName
+// 77:d=3 hl=2 l= 2 prim: PRINTABLESTRING :TR
+// 81:d=1 hl=2 l= 15 cons: SET
+// 83:d=2 hl=2 l= 13 cons: SEQUENCE
+// 85:d=3 hl=2 l= 3 prim: OBJECT :localityName
+// 90:d=3 hl=2 l= 6 prim: UTF8STRING :Ankara
+// 98:d=1 hl=2 l= 93 cons: SET
+//100:d=2 hl=2 l= 91 cons: SEQUENCE
+//102:d=3 hl=2 l= 3 prim: OBJECT :organizationName
+//107:d=3 hl=2 l= 84 prim: UTF8STRING :TÜRKTRUST Bilgi Ä°letiÅŸim ve BiliÅŸim GüvenliÄŸi Hizmetleri A.Åž. (c) Kasım 2005
+static const uint8 TurkTrustDN[] = {
+ 0x30, 0x81, 0xbe, 0x31, 0x3f, 0x30, 0x3d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c,
+ 0x36, 0x54, 0xc3, 0x9c, 0x52, 0x4b, 0x54, 0x52, 0x55, 0x53, 0x54, 0x20, 0x45,
+ 0x6c, 0x65, 0x6b, 0x74, 0x72, 0x6f, 0x6e, 0x69, 0x6b, 0x20, 0x53, 0x65, 0x72,
+ 0x74, 0x69, 0x66, 0x69, 0x6b, 0x61, 0x20, 0x48, 0x69, 0x7a, 0x6d, 0x65, 0x74,
+ 0x20, 0x53, 0x61, 0xc4, 0x9f, 0x6c, 0x61, 0x79, 0xc4, 0xb1, 0x63, 0xc4, 0xb1,
+ 0x73, 0xc4, 0xb1, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x54, 0x52, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c,
+ 0x06, 0x41, 0x6e, 0x6b, 0x61, 0x72, 0x61, 0x31, 0x5d, 0x30, 0x5b, 0x06, 0x03,
+ 0x55, 0x04, 0x0a, 0x0c, 0x54, 0x54, 0xc3, 0x9c, 0x52, 0x4b, 0x54, 0x52, 0x55,
+ 0x53, 0x54, 0x20, 0x42, 0x69, 0x6c, 0x67, 0x69, 0x20, 0xc4, 0xb0, 0x6c, 0x65,
+ 0x74, 0x69, 0xc5, 0x9f, 0x69, 0x6d, 0x20, 0x76, 0x65, 0x20, 0x42, 0x69, 0x6c,
+ 0x69, 0xc5, 0x9f, 0x69, 0x6d, 0x20, 0x47, 0xc3, 0xbc, 0x76, 0x65, 0x6e, 0x6c,
+ 0x69, 0xc4, 0x9f, 0x69, 0x20, 0x48, 0x69, 0x7a, 0x6d, 0x65, 0x74, 0x6c, 0x65,
+ 0x72, 0x69, 0x20, 0x41, 0x2e, 0xc5, 0x9e, 0x2e, 0x20, 0x28, 0x63, 0x29, 0x20,
+ 0x4b, 0x61, 0x73, 0xc4, 0xb1, 0x6d, 0x20, 0x32, 0x30, 0x30, 0x35, 0x30, 0x1e,
+ 0x17, 0x0d, 0x30, 0x35, 0x31, 0x31, 0x30, 0x37, 0x31, 0x30, 0x30, 0x37, 0x35,
+ 0x37
+};
+
+// 33:d=2 hl=3 l= 207 cons: SEQUENCE
+// 36:d=3 hl=2 l= 11 cons: SET
+// 38:d=4 hl=2 l= 9 cons: SEQUENCE
+// 40:d=5 hl=2 l= 3 prim: OBJECT :countryName
+// 45:d=5 hl=2 l= 2 prim: PRINTABLESTRING :AT
+// 49:d=3 hl=3 l= 139 cons: SET
+// 52:d=4 hl=3 l= 136 cons: SEQUENCE
+// 55:d=5 hl=2 l= 3 prim: OBJECT :organizationName
+// 60:d=5 hl=3 l= 128 prim: BMPSTRING :A-Trust Ges. für Sicherheitssysteme im elektr. Datenverkehr GmbH
+//191:d=3 hl=2 l= 24 cons: SET
+//193:d=4 hl=2 l= 22 cons: SEQUENCE
+//195:d=5 hl=2 l= 3 prim: OBJECT :organizationalUnitName
+//200:d=5 hl=2 l= 15 prim: PRINTABLESTRING :A-Trust-Qual-01
+//217:d=3 hl=2 l= 24 cons: SET
+//219:d=4 hl=2 l= 22 cons: SEQUENCE
+//221:d=5 hl=2 l= 3 prim: OBJECT :commonName
+//226:d=5 hl=2 l= 15 prim: PRINTABLESTRING :A-Trust-Qual-01
+static const uint8 ATrustQual01DN[] = {
+ 0x30, 0x81, 0xcf, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x41, 0x54, 0x31, 0x81, 0x8b, 0x30, 0x81, 0x88, 0x06, 0x03, 0x55, 0x04,
+ 0x0a, 0x1e, 0x81, 0x80, 0x00, 0x41, 0x00, 0x2d, 0x00, 0x54, 0x00, 0x72, 0x00,
+ 0x75, 0x00, 0x73, 0x00, 0x74, 0x00, 0x20, 0x00, 0x47, 0x00, 0x65, 0x00, 0x73,
+ 0x00, 0x2e, 0x00, 0x20, 0x00, 0x66, 0x00, 0xfc, 0x00, 0x72, 0x00, 0x20, 0x00,
+ 0x53, 0x00, 0x69, 0x00, 0x63, 0x00, 0x68, 0x00, 0x65, 0x00, 0x72, 0x00, 0x68,
+ 0x00, 0x65, 0x00, 0x69, 0x00, 0x74, 0x00, 0x73, 0x00, 0x73, 0x00, 0x79, 0x00,
+ 0x73, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x20, 0x00, 0x69,
+ 0x00, 0x6d, 0x00, 0x20, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x6b, 0x00,
+ 0x74, 0x00, 0x72, 0x00, 0x2e, 0x00, 0x20, 0x00, 0x44, 0x00, 0x61, 0x00, 0x74,
+ 0x00, 0x65, 0x00, 0x6e, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x6b, 0x00,
+ 0x65, 0x00, 0x68, 0x00, 0x72, 0x00, 0x20, 0x00, 0x47, 0x00, 0x6d, 0x00, 0x62,
+ 0x00, 0x48, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0f,
+ 0x41, 0x2d, 0x54, 0x72, 0x75, 0x73, 0x74, 0x2d, 0x51, 0x75, 0x61, 0x6c, 0x2d,
+ 0x30, 0x31, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x0f,
+ 0x41, 0x2d, 0x54, 0x72, 0x75, 0x73, 0x74, 0x2d, 0x51, 0x75, 0x61, 0x6c, 0x2d,
+ 0x30, 0x31, 0x30, 0x1e, 0x17
+};
+
+// 34:d=2 hl=3 l= 180 cons: SEQUENCE
+// 37:d=3 hl=2 l= 20 cons: SET
+// 39:d=4 hl=2 l= 18 cons: SEQUENCE
+// 41:d=5 hl=2 l= 3 prim: OBJECT :organizationName
+// 46:d=5 hl=2 l= 11 prim: PRINTABLESTRING :Entrust.net
+// 59:d=3 hl=2 l= 64 cons: SET
+// 61:d=4 hl=2 l= 62 cons: SEQUENCE
+// 63:d=5 hl=2 l= 3 prim: OBJECT :organizationalUnitName
+// 68:d=5 hl=2 l= 55 prim: T61STRING :www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)
+//125:d=3 hl=2 l= 37 cons: SET
+//127:d=4 hl=2 l= 35 cons: SEQUENCE
+//129:d=5 hl=2 l= 3 prim: OBJECT :organizationalUnitName
+//134:d=5 hl=2 l= 28 prim: PRINTABLESTRING :(c) 1999 Entrust.net Limited
+//164:d=3 hl=2 l= 51 cons: SET
+//166:d=4 hl=2 l= 49 cons: SEQUENCE
+//168:d=5 hl=2 l= 3 prim: OBJECT :commonName
+//173:d=5 hl=2 l= 42 prim: PRINTABLESTRING :Entrust.net Certification Authority (2048)
+static const uint8 EntrustDN[] = {
+ 0x30, 0x81, 0xb4, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x0b, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x31,
+ 0x40, 0x30, 0x3e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x14, 0x37, 0x77, 0x77, 0x77,
+ 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f,
+ 0x43, 0x50, 0x53, 0x5f, 0x32, 0x30, 0x34, 0x38, 0x20, 0x69, 0x6e, 0x63, 0x6f,
+ 0x72, 0x70, 0x2e, 0x20, 0x62, 0x79, 0x20, 0x72, 0x65, 0x66, 0x2e, 0x20, 0x28,
+ 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x20, 0x6c, 0x69, 0x61, 0x62, 0x2e, 0x29,
+ 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1c, 0x28, 0x63,
+ 0x29, 0x20, 0x31, 0x39, 0x39, 0x39, 0x20, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73,
+ 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64,
+ 0x31, 0x33, 0x30, 0x31, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2a, 0x45, 0x6e,
+ 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x20, 0x43, 0x65, 0x72,
+ 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75,
+ 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x28, 0x32, 0x30, 0x34, 0x38,
+ 0x29
+};
+
+namespace net {
+
+TEST(X509TypesTest, Matching) {
+ CertPrincipal spamco;
+ spamco.common_name = "SpamCo Dept. Of Certificization";
+ spamco.country_name = "EB";
+ spamco.organization_names.push_back("SpamCo Holding Company, LLC");
+ spamco.organization_names.push_back("SpamCo Evil Masterminds");
+ spamco.organization_unit_names.push_back("Class Z Obfuscation Authority");
+ ASSERT_TRUE(spamco.Matches(spamco));
+
+ CertPrincipal bogus;
+ EXPECT_FALSE(bogus.Matches(spamco));
+ EXPECT_FALSE(spamco.Matches(bogus));
+
+ bogus = spamco;
+ EXPECT_TRUE(bogus.Matches(spamco));
+ EXPECT_TRUE(spamco.Matches(bogus));
+
+ bogus.organization_names.erase(bogus.organization_names.begin(),
+ bogus.organization_names.end());
+ EXPECT_FALSE(bogus.Matches(spamco));
+ EXPECT_FALSE(spamco.Matches(bogus));
+
+ bogus.organization_names.push_back("SpamCo Holding Company, LLC");
+ bogus.organization_names.push_back("SpamCo Evil Masterminds");
+ EXPECT_TRUE(bogus.Matches(spamco));
+ EXPECT_TRUE(spamco.Matches(bogus));
+
+ bogus.locality_name = "Elbosdorf";
+ EXPECT_FALSE(bogus.Matches(spamco));
+ EXPECT_FALSE(spamco.Matches(bogus));
+
+ bogus.locality_name = "";
+ bogus.organization_unit_names.push_back("Q Division");
+ EXPECT_FALSE(bogus.Matches(spamco));
+ EXPECT_FALSE(spamco.Matches(bogus));
+}
+
+#if defined(OS_MACOSX) // ParseDistinguishedName not implemented for Win/Linux
+
+TEST(X509TypesTest, ParseDNVerisign) {
+ CertPrincipal verisign;
+ EXPECT_TRUE(verisign.ParseDistinguishedName(VerisignDN, sizeof(VerisignDN)));
+ EXPECT_EQ("", verisign.common_name);
+ EXPECT_EQ("US", verisign.country_name);
+ ASSERT_EQ(1U, verisign.organization_names.size());
+ EXPECT_EQ("VeriSign, Inc.", verisign.organization_names[0]);
+ ASSERT_EQ(1U, verisign.organization_unit_names.size());
+ EXPECT_EQ("Class 1 Public Primary Certification Authority",
+ verisign.organization_unit_names[0]);
+}
+
+TEST(X509TypesTest, ParseDNStartcom) {
+ CertPrincipal startcom;
+ EXPECT_TRUE(startcom.ParseDistinguishedName(StartComDN, sizeof(StartComDN)));
+ EXPECT_EQ("StartCom Certification Authority", startcom.common_name);
+ EXPECT_EQ("IL", startcom.country_name);
+ ASSERT_EQ(1U, startcom.organization_names.size());
+ EXPECT_EQ("StartCom Ltd.", startcom.organization_names[0]);
+ ASSERT_EQ(1U, startcom.organization_unit_names.size());
+ EXPECT_EQ("Secure Digital Certificate Signing",
+ startcom.organization_unit_names[0]);
+}
+
+TEST(X509TypesTest, ParseDNUserTrust) {
+ CertPrincipal usertrust;
+ EXPECT_TRUE(usertrust.ParseDistinguishedName(UserTrustDN,
+ sizeof(UserTrustDN)));
+ EXPECT_EQ("UTN-USERFirst-Client Authentication and Email",
+ usertrust.common_name);
+ EXPECT_EQ("US", usertrust.country_name);
+ EXPECT_EQ("UT", usertrust.state_or_province_name);
+ EXPECT_EQ("Salt Lake City", usertrust.locality_name);
+ ASSERT_EQ(1U, usertrust.organization_names.size());
+ EXPECT_EQ("The USERTRUST Network", usertrust.organization_names[0]);
+ ASSERT_EQ(1U, usertrust.organization_unit_names.size());
+ EXPECT_EQ("http://www.usertrust.com",
+ usertrust.organization_unit_names[0]);
+}
+
+TEST(X509TypesTest, ParseDNTurkTrust) {
+ // Note: This tests parsing UTF8STRINGs.
+ CertPrincipal turktrust;
+ EXPECT_TRUE(turktrust.ParseDistinguishedName(TurkTrustDN,
+ sizeof(TurkTrustDN)));
+ EXPECT_EQ("TÜRKTRUST Elektronik Sertifika Hizmet SaÄŸlayıcısı",
+ turktrust.common_name);
+ EXPECT_EQ("TR", turktrust.country_name);
+ EXPECT_EQ("Ankara", turktrust.locality_name);
+ ASSERT_EQ(1U, turktrust.organization_names.size());
+ EXPECT_EQ("TÜRKTRUST Bilgi Ä°letiÅŸim ve BiliÅŸim GüvenliÄŸi Hizmetleri A.Åž. (c) Kasım 2005",
+ turktrust.organization_names[0]);
+}
+
+TEST(X509TypesTest, ParseDNATrust) {
+ // Note: This tests parsing 16-bit BMPSTRINGs.
+ CertPrincipal atrust;
+ EXPECT_TRUE(atrust.ParseDistinguishedName(ATrustQual01DN,
+ sizeof(ATrustQual01DN)));
+ EXPECT_EQ("A-Trust-Qual-01",
+ atrust.common_name);
+ EXPECT_EQ("AT", atrust.country_name);
+ ASSERT_EQ(1U, atrust.organization_names.size());
+ EXPECT_EQ("A-Trust Ges. für Sicherheitssysteme im elektr. Datenverkehr GmbH",
+ atrust.organization_names[0]);
+ ASSERT_EQ(1U, atrust.organization_unit_names.size());
+ EXPECT_EQ("A-Trust-Qual-01",
+ atrust.organization_unit_names[0]);
+}
+
+TEST(X509TypesTest, ParseDNEntrust) {
+ // Note: This tests parsing T61STRINGs and fields with multiple values.
+ CertPrincipal entrust;
+ EXPECT_TRUE(entrust.ParseDistinguishedName(EntrustDN,
+ sizeof(EntrustDN)));
+ EXPECT_EQ("Entrust.net Certification Authority (2048)",
+ entrust.common_name);
+ EXPECT_EQ("", entrust.country_name);
+ ASSERT_EQ(1U, entrust.organization_names.size());
+ EXPECT_EQ("Entrust.net",
+ entrust.organization_names[0]);
+ ASSERT_EQ(2U, entrust.organization_unit_names.size());
+ EXPECT_EQ("www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)",
+ entrust.organization_unit_names[0]);
+ EXPECT_EQ("(c) 1999 Entrust.net Limited",
+ entrust.organization_unit_names[1]);
+}
+
+#endif
+
+}
diff --git a/net/base/x509_certificate.cc b/net/base/x509_certificate.cc
index 24388d8..f5b28a6 100644
--- a/net/base/x509_certificate.cc
+++ b/net/base/x509_certificate.cc
@@ -4,8 +4,17 @@
#include "net/base/x509_certificate.h"
+#if defined(OS_MACOSX)
+#include <Security/Security.h>
+#elif defined(USE_NSS)
+#include <cert.h>
+#endif
+
+#include <map>
+
#include "base/histogram.h"
#include "base/logging.h"
+#include "base/singleton.h"
#include "base/time.h"
namespace net {
@@ -14,7 +23,7 @@
// Returns true if this cert fingerprint is the null (all zero) fingerprint.
// We use this as a bogus fingerprint value.
-bool IsNullFingerprint(const X509Certificate::Fingerprint& fingerprint) {
+bool IsNullFingerprint(const SHA1Fingerprint& fingerprint) {
for (size_t i = 0; i < arraysize(fingerprint.data); ++i) {
if (fingerprint.data[i] != 0)
return false;
@@ -24,16 +33,31 @@
} // namespace
-bool X509Certificate::FingerprintLessThan::operator()(
- const Fingerprint& lhs,
- const Fingerprint& rhs) const {
- for (size_t i = 0; i < sizeof(lhs.data); ++i) {
- if (lhs.data[i] < rhs.data[i])
- return true;
- if (lhs.data[i] > rhs.data[i])
- return false;
- }
+// static
+bool X509Certificate::IsSameOSCert(X509Certificate::OSCertHandle a,
+ X509Certificate::OSCertHandle b) {
+ DCHECK(a && b);
+ if (a == b)
+ return true;
+#if defined(OS_WIN)
+ return a->cbCertEncoded == b->cbCertEncoded &&
+ memcmp(a->pbCertEncoded, b->pbCertEncoded, a->cbCertEncoded) == 0;
+#elif defined(OS_MACOSX)
+ if (CFEqual(a, b))
+ return true;
+ CSSM_DATA a_data, b_data;
+ return SecCertificateGetData(a, &a_data) == noErr &&
+ SecCertificateGetData(b, &b_data) == noErr &&
+ a_data.Length == b_data.Length &&
+ memcmp(a_data.Data, b_data.Data, a_data.Length) == 0;
+#elif defined(USE_NSS)
+ return a->derCert.len == b->derCert.len &&
+ memcmp(a->derCert.data, b->derCert.data, a->derCert.len) == 0;
+#else
+ // TODO(snej): not implemented
+ UNREACHED();
return false;
+#endif
}
bool X509Certificate::LessThan::operator()(X509Certificate* lhs,
@@ -41,7 +65,7 @@
if (lhs == rhs)
return false;
- X509Certificate::FingerprintLessThan fingerprint_functor;
+ SHA1FingerprintLessThan fingerprint_functor;
return fingerprint_functor(lhs->fingerprint_, rhs->fingerprint_);
}
@@ -50,6 +74,32 @@
// The cache does not hold a reference to the certificate objects. The objects
// must |Remove| themselves from the cache upon destruction (or else the cache
// will be holding dead pointers to the objects).
+// TODO(rsleevi): There exists a chance of a use-after-free, due to a race
+// between Find() and Remove(). See http://crbug.com/49377
+class X509Certificate::Cache {
+ public:
+ static Cache* GetInstance();
+ void Insert(X509Certificate* cert);
+ void Remove(X509Certificate* cert);
+ X509Certificate* Find(const SHA1Fingerprint& fingerprint);
+
+ private:
+ typedef std::map<SHA1Fingerprint, X509Certificate*, SHA1FingerprintLessThan>
+ CertMap;
+
+ // Obtain an instance of X509Certificate::Cache via GetInstance().
+ Cache() {}
+ friend struct DefaultSingletonTraits<Cache>;
+
+ // You must acquire this lock before using any private data of this object.
+ // You must not block while holding this lock.
+ Lock lock_;
+
+ // The certificate cache. You must acquire |lock_| before using |cache_|.
+ CertMap cache_;
+
+ DISALLOW_COPY_AND_ASSIGN(Cache);
+};
// Get the singleton object for the cache.
// static
@@ -57,14 +107,13 @@
return Singleton<X509Certificate::Cache>::get();
}
-// Insert |cert| into the cache. The cache does NOT AddRef |cert|. The cache
-// must not already contain a certificate with the same fingerprint.
+// Insert |cert| into the cache. The cache does NOT AddRef |cert|.
+// Any existing certificate with the same fingerprint will be replaced.
void X509Certificate::Cache::Insert(X509Certificate* cert) {
AutoLock lock(lock_);
DCHECK(!IsNullFingerprint(cert->fingerprint())) <<
"Only insert certs with real fingerprints.";
- DCHECK(cache_.find(cert->fingerprint()) == cache_.end());
cache_[cert->fingerprint()] = cert;
};
@@ -81,7 +130,8 @@
// Find a certificate in the cache with the given fingerprint. If one does
// not exist, this method returns NULL.
-X509Certificate* X509Certificate::Cache::Find(const Fingerprint& fingerprint) {
+X509Certificate* X509Certificate::Cache::Find(
+ const SHA1Fingerprint& fingerprint) {
AutoLock lock(lock_);
CertMap::iterator pos(cache_.find(fingerprint));
@@ -91,50 +141,11 @@
return pos->second;
};
-X509Certificate::Policy::Judgment X509Certificate::Policy::Check(
- X509Certificate* cert) const {
- // It shouldn't matter which set we check first, but we check denied first
- // in case something strange has happened.
-
- if (denied_.find(cert->fingerprint()) != denied_.end()) {
- // DCHECK that the order didn't matter.
- DCHECK(allowed_.find(cert->fingerprint()) == allowed_.end());
- return DENIED;
- }
-
- if (allowed_.find(cert->fingerprint()) != allowed_.end()) {
- // DCHECK that the order didn't matter.
- DCHECK(denied_.find(cert->fingerprint()) == denied_.end());
- return ALLOWED;
- }
-
- // We don't have a policy for this cert.
- return UNKNOWN;
-}
-
-void X509Certificate::Policy::Allow(X509Certificate* cert) {
- // Put the cert in the allowed set and (maybe) remove it from the denied set.
- denied_.erase(cert->fingerprint());
- allowed_.insert(cert->fingerprint());
-}
-
-void X509Certificate::Policy::Deny(X509Certificate* cert) {
- // Put the cert in the denied set and (maybe) remove it from the allowed set.
- allowed_.erase(cert->fingerprint());
- denied_.insert(cert->fingerprint());
-}
-
-bool X509Certificate::Policy::HasAllowedCert() const {
- return !allowed_.empty();
-}
-
-bool X509Certificate::Policy::HasDeniedCert() const {
- return !denied_.empty();
-}
-
// static
-X509Certificate* X509Certificate::CreateFromHandle(OSCertHandle cert_handle,
- Source source) {
+X509Certificate* X509Certificate::CreateFromHandle(
+ OSCertHandle cert_handle,
+ Source source,
+ const OSCertHandles& intermediates) {
DCHECK(cert_handle);
DCHECK(source != SOURCE_UNUSED);
@@ -144,18 +155,21 @@
cache->Find(CalculateFingerprint(cert_handle));
if (cached_cert) {
DCHECK(cached_cert->source_ != SOURCE_UNUSED);
- if (cached_cert->source_ >= source) {
- // We've found a certificate with the same fingerprint in our cache. We
- // own the |cert_handle|, which makes it our job to free it.
- FreeOSCertHandle(cert_handle);
+ if (cached_cert->source_ > source ||
+ (cached_cert->source_ == source &&
+ cached_cert->HasIntermediateCertificates(intermediates))) {
+ // Return the certificate with the same fingerprint from our cache.
DHISTOGRAM_COUNTS("X509CertificateReuseCount", 1);
return cached_cert;
}
- // Kick out the old certificate from our cache. The new one is better.
- cache->Remove(cached_cert);
+ // Else the new cert is better and will replace the old one in the cache.
}
- // Otherwise, allocate a new object.
- return new X509Certificate(cert_handle, source);
+
+ // Otherwise, allocate and cache a new object.
+ X509Certificate* cert = new X509Certificate(cert_handle, source,
+ intermediates);
+ cache->Insert(cert);
+ return cert;
}
// static
@@ -165,12 +179,24 @@
if (!cert_handle)
return NULL;
- return CreateFromHandle(cert_handle, SOURCE_LONE_CERT_IMPORT);
+ X509Certificate* cert = CreateFromHandle(cert_handle,
+ SOURCE_LONE_CERT_IMPORT,
+ OSCertHandles());
+ FreeOSCertHandle(cert_handle);
+ return cert;
}
-X509Certificate::X509Certificate(OSCertHandle cert_handle, Source source)
- : cert_handle_(cert_handle),
+X509Certificate::X509Certificate(OSCertHandle cert_handle,
+ Source source,
+ const OSCertHandles& intermediates)
+ : cert_handle_(DupOSCertHandle(cert_handle)),
source_(source) {
+#if defined(OS_MACOSX) || defined(OS_WIN)
+ // Copy/retain the intermediate cert handles.
+ for (size_t i = 0; i < intermediates.size(); ++i)
+ intermediate_ca_certs_.push_back(DupOSCertHandle(intermediates[i]));
+#endif
+ // Platform-specific initialization.
Initialize();
}
@@ -202,4 +228,24 @@
return base::Time::Now() > valid_expiry();
}
+bool X509Certificate::HasIntermediateCertificate(OSCertHandle cert) {
+#if defined(OS_MACOSX) || defined(OS_WIN)
+ for (size_t i = 0; i < intermediate_ca_certs_.size(); ++i) {
+ if (IsSameOSCert(cert, intermediate_ca_certs_[i]))
+ return true;
+ }
+ return false;
+#else
+ return true;
+#endif
+}
+
+bool X509Certificate::HasIntermediateCertificates(const OSCertHandles& certs) {
+ for (size_t i = 0; i < certs.size(); ++i) {
+ if (!HasIntermediateCertificate(certs[i]))
+ return false;
+ }
+ return true;
+}
+
} // namespace net
diff --git a/net/base/x509_certificate.h b/net/base/x509_certificate.h
index 215489b..d6b3447 100644
--- a/net/base/x509_certificate.h
+++ b/net/base/x509_certificate.h
@@ -7,21 +7,20 @@
#include <string.h>
-#include <map>
-#include <set>
#include <string>
#include <vector>
#include "base/ref_counted.h"
-#include "base/singleton.h"
#include "base/time.h"
+#include "net/base/x509_cert_types.h"
#include "testing/gtest/include/gtest/gtest_prod.h"
#if defined(OS_WIN)
#include <windows.h>
#include <wincrypt.h>
#elif defined(OS_MACOSX)
-#include <Security/Security.h>
+#include <CoreFoundation/CFArray.h>
+#include <Security/SecBase.h>
#elif defined(USE_NSS)
// Forward declaration; real one in <cert.h>
struct CERTCertificateStr;
@@ -36,28 +35,6 @@
// X509Certificate represents an X.509 certificate used by SSL.
class X509Certificate : public base::RefCountedThreadSafe<X509Certificate> {
public:
- // SHA-1 fingerprint (160 bits) of a certificate.
- struct Fingerprint {
- bool Equals(const Fingerprint& other) const {
- return memcmp(data, other.data, sizeof(data)) == 0;
- }
-
- unsigned char data[20];
- };
-
- class FingerprintLessThan
- : public std::binary_function<Fingerprint, Fingerprint, bool> {
- public:
- bool operator() (const Fingerprint& lhs, const Fingerprint& rhs) const;
- };
-
- // Predicate functor used in maps when X509Certificate is used as the key.
- class LessThan
- : public std::binary_function<X509Certificate*, X509Certificate*, bool> {
- public:
- bool operator() (X509Certificate* lhs, X509Certificate* rhs) const;
- };
-
// A handle to the certificate object in the underlying crypto library.
// We assume that OSCertHandle is a pointer type on all platforms and
// NULL is an invalid OSCertHandle.
@@ -72,62 +49,13 @@
typedef void* OSCertHandle;
#endif
- // Principal represent an X.509 principal.
- struct Principal {
- Principal() { }
- explicit Principal(const std::string& name) : common_name(name) { }
+ typedef std::vector<OSCertHandle> OSCertHandles;
- // The different attributes for a principal. They may be "".
- // Note that some of them can have several values.
-
- std::string common_name;
- std::string locality_name;
- std::string state_or_province_name;
- std::string country_name;
-
- std::vector<std::string> street_addresses;
- std::vector<std::string> organization_names;
- std::vector<std::string> organization_unit_names;
- std::vector<std::string> domain_components;
- };
-
- // This class is useful for maintaining policies about which certificates are
- // permitted or forbidden for a particular purpose.
- class Policy {
+ // Predicate functor used in maps when X509Certificate is used as the key.
+ class LessThan
+ : public std::binary_function<X509Certificate*, X509Certificate*, bool> {
public:
- // The judgments this policy can reach.
- enum Judgment {
- // We don't have policy information for this certificate.
- UNKNOWN,
-
- // This certificate is allowed.
- ALLOWED,
-
- // This certificate is denied.
- DENIED,
- };
-
- // Returns the judgment this policy makes about this certificate.
- Judgment Check(X509Certificate* cert) const;
-
- // Causes the policy to allow this certificate.
- void Allow(X509Certificate* cert);
-
- // Causes the policy to deny this certificate.
- void Deny(X509Certificate* cert);
-
- // Returns true if this policy has allowed at least one certificate.
- bool HasAllowedCert() const;
-
- // Returns true if this policy has denied at least one certificate.
- bool HasDeniedCert() const;
-
- private:
- // The set of fingerprints of allowed certificates.
- std::set<Fingerprint, FingerprintLessThan> allowed_;
-
- // The set of fingerprints of denied certificates.
- std::set<Fingerprint, FingerprintLessThan> denied_;
+ bool operator() (X509Certificate* lhs, X509Certificate* rhs) const;
};
// Where the certificate comes from. The enumeration constants are
@@ -144,18 +72,17 @@
VERIFY_EV_CERT = 1 << 1,
};
- // Create an X509Certificate from a handle to the certificate object
- // in the underlying crypto library. This is a transfer of ownership;
- // X509Certificate will properly dispose of |cert_handle| for you.
- // |source| specifies where |cert_handle| comes from. Given two
- // certificate handles for the same certificate, our certificate cache
- // prefers the handle from the network because our HTTP cache isn't
- // caching the corresponding intermediate CA certificates yet
+ // Create an X509Certificate from a handle to the certificate object in the
+ // underlying crypto library. |source| specifies where |cert_handle| comes
+ // from. Given two certificate handles for the same certificate, our
+ // certificate cache prefers the handle from the network because our HTTP
+ // cache isn't caching the corresponding intermediate CA certificates yet
// (http://crbug.com/7065).
- //
+ // The list of intermediate certificates is ignored under NSS (i.e. Linux.)
// The returned pointer must be stored in a scoped_refptr<X509Certificate>.
static X509Certificate* CreateFromHandle(OSCertHandle cert_handle,
- Source source);
+ Source source,
+ const OSCertHandles& intermediates);
// Create an X509Certificate from the BER-encoded representation.
// Returns NULL on failure.
@@ -183,10 +110,10 @@
// The subject of the certificate. For HTTPS server certificates, this
// represents the web server. The common name of the subject should match
// the host name of the web server.
- const Principal& subject() const { return subject_; }
+ const CertPrincipal& subject() const { return subject_; }
// The issuer of the certificate.
- const Principal& issuer() const { return issuer_; }
+ const CertPrincipal& issuer() const { return issuer_; }
// Time period during which the certificate is valid. More precisely, this
// certificate is invalid before the |valid_start| date and invalid after
@@ -197,7 +124,7 @@
const base::Time& valid_expiry() const { return valid_expiry_; }
// The fingerprint of this certificate.
- const Fingerprint& fingerprint() const { return fingerprint_; }
+ const SHA1Fingerprint& fingerprint() const { return fingerprint_; }
// Gets the DNS names in the certificate. Pursuant to RFC 2818, Section 3.1
// Server Identity, if the certificate has a subjectAltName extension of
@@ -210,20 +137,45 @@
bool HasExpired() const;
#if defined(OS_MACOSX) || defined(OS_WIN)
- // Adds an untrusted intermediate certificate that may be needed for
- // chain building.
- void AddIntermediateCertificate(OSCertHandle cert) {
- intermediate_ca_certs_.push_back(cert);
- }
-
// Returns intermediate certificates added via AddIntermediateCertificate().
// Ownership follows the "get" rule: it is the caller's responsibility to
// retain the elements of the result.
- const std::vector<OSCertHandle>& GetIntermediateCertificates() const {
+ const OSCertHandles& GetIntermediateCertificates() const {
return intermediate_ca_certs_;
}
#endif
+ // Returns true if I already contain the given intermediate cert.
+ bool HasIntermediateCertificate(OSCertHandle cert);
+
+ // Returns true if I already contain all the given intermediate certs.
+ bool HasIntermediateCertificates(const OSCertHandles& certs);
+
+#if defined(OS_MACOSX)
+ // Does this certificate's usage allow SSL client authentication?
+ bool SupportsSSLClientAuth() const;
+
+ // Do any of the given issuer names appear in this cert's chain of trust?
+ bool IsIssuedBy(const std::vector<CertPrincipal>& valid_issuers);
+
+ // Creates a security policy for SSL client certificates.
+ static OSStatus CreateSSLClientPolicy(SecPolicyRef* outPolicy);
+
+ // Adds all available SSL client identity certs to the given vector.
+ // |server_domain| is a hint for which domain the cert is to be sent to
+ // (a cert previously specified as the default for that domain will be given
+ // precedence and returned first in the output vector.)
+ // If valid_issuers is non-empty, only certs that were transitively issued by
+ // one of the given names will be included in the list.
+ static bool GetSSLClientCertificates(
+ const std::string& server_domain,
+ const std::vector<CertPrincipal>& valid_issuers,
+ std::vector<scoped_refptr<X509Certificate> >* certs);
+
+ // Creates the chain of certs to use for this client identity cert.
+ CFArrayRef CreateClientCertificateChain() const;
+#endif
+
// Verifies the certificate against the given hostname. Returns OK if
// successful or an error code upon failure.
//
@@ -243,39 +195,31 @@
OSCertHandle os_cert_handle() const { return cert_handle_; }
+ // Returns true if two OSCertHandles refer to identical certificates.
+ static bool IsSameOSCert(OSCertHandle a, OSCertHandle b);
+
+ // Creates an OS certificate handle from the BER-encoded representation.
+ // Returns NULL on failure.
+ static OSCertHandle CreateOSCertHandleFromBytes(const char* data,
+ int length);
+
+ // Duplicates (or adds a reference to) an OS certificate handle.
+ static OSCertHandle DupOSCertHandle(OSCertHandle cert_handle);
+
+ // Frees (or releases a reference to) an OS certificate handle.
+ static void FreeOSCertHandle(OSCertHandle cert_handle);
+
private:
friend class base::RefCountedThreadSafe<X509Certificate>;
FRIEND_TEST(X509CertificateTest, Cache);
+ FRIEND_TEST(X509CertificateTest, IntermediateCertificates);
- // A cache of X509Certificate objects.
- class Cache {
- public:
- static Cache* GetInstance();
- void Insert(X509Certificate* cert);
- void Remove(X509Certificate* cert);
- X509Certificate* Find(const Fingerprint& fingerprint);
-
- private:
- typedef std::map<Fingerprint, X509Certificate*, FingerprintLessThan>
- CertMap;
-
- // Obtain an instance of X509Certificate::Cache via GetInstance().
- Cache() { }
- friend struct DefaultSingletonTraits<Cache>;
-
- // You must acquire this lock before using any private data of this object.
- // You must not block while holding this lock.
- Lock lock_;
-
- // The certificate cache. You must acquire |lock_| before using |cache_|.
- CertMap cache_;
-
- DISALLOW_COPY_AND_ASSIGN(Cache);
- };
+ class Cache;
// Construct an X509Certificate from a handle to the certificate object
// in the underlying crypto library.
- X509Certificate(OSCertHandle cert_handle, Source source);
+ X509Certificate(OSCertHandle cert_handle, Source source,
+ const OSCertHandles& intermediates);
~X509Certificate();
@@ -284,23 +228,15 @@
bool VerifyEV() const;
- // Creates an OS certificate handle from the BER-encoded representation.
- // Returns NULL on failure.
- static OSCertHandle CreateOSCertHandleFromBytes(const char* data,
- int length);
-
- // Frees an OS certificate handle.
- static void FreeOSCertHandle(OSCertHandle cert_handle);
-
// Calculates the SHA-1 fingerprint of the certificate. Returns an empty
// (all zero) fingerprint on failure.
- static Fingerprint CalculateFingerprint(OSCertHandle cert_handle);
+ static SHA1Fingerprint CalculateFingerprint(OSCertHandle cert_handle);
// The subject of the certificate.
- Principal subject_;
+ CertPrincipal subject_;
// The issuer of the certificate.
- Principal issuer_;
+ CertPrincipal issuer_;
// This certificate is not valid before |valid_start_|
base::Time valid_start_;
@@ -309,15 +245,21 @@
base::Time valid_expiry_;
// The fingerprint of this certificate.
- Fingerprint fingerprint_;
+ SHA1Fingerprint fingerprint_;
// A handle to the certificate object in the underlying crypto library.
OSCertHandle cert_handle_;
#if defined(OS_MACOSX) || defined(OS_WIN)
// Untrusted intermediate certificates associated with this certificate
- // that may be needed for chain building.
- std::vector<OSCertHandle> intermediate_ca_certs_;
+ // that may be needed for chain building. (NSS impl does not need these.)
+ OSCertHandles intermediate_ca_certs_;
+#endif
+
+#if defined(OS_MACOSX)
+ // Blocks multiple threads from verifying the cert simultaneously.
+ // (Marked mutable because it's used in a const method.)
+ mutable Lock verification_lock_;
#endif
// Where the certificate comes from.
diff --git a/net/base/x509_certificate_mac.cc b/net/base/x509_certificate_mac.cc
index 2b96be4..ed46adc 100644
--- a/net/base/x509_certificate_mac.cc
+++ b/net/base/x509_certificate_mac.cc
@@ -5,11 +5,13 @@
#include "net/base/x509_certificate.h"
#include <CommonCrypto/CommonDigest.h>
+#include <Security/Security.h>
#include <time.h>
#include "base/scoped_cftyperef.h"
#include "base/logging.h"
#include "base/pickle.h"
+#include "base/sys_string_conversions.h"
#include "net/base/cert_status_flags.h"
#include "net/base/cert_verify_result.h"
#include "net/base/net_errors.h"
@@ -79,11 +81,6 @@
typedef OSStatus (*SecTrustCopyExtendedResultFuncPtr)(SecTrustRef,
CFDictionaryRef*);
-inline bool CSSMOIDEqual(const CSSM_OID* oid1, const CSSM_OID* oid2) {
- return oid1->Length == oid2->Length &&
- (memcmp(oid1->Data, oid2->Data, oid1->Length) == 0);
-}
-
int NetErrorFromOSStatus(OSStatus status) {
switch (status) {
case noErr:
@@ -172,64 +169,6 @@
return override_hostname_mismatch;
}
-void ParsePrincipal(const CSSM_X509_NAME* name,
- X509Certificate::Principal* principal) {
- std::vector<std::string> common_names, locality_names, state_names,
- country_names;
-
- // TODO(jcampan): add business_category and serial_number.
- const CSSM_OID* kOIDs[] = { &CSSMOID_CommonName,
- &CSSMOID_LocalityName,
- &CSSMOID_StateProvinceName,
- &CSSMOID_CountryName,
- &CSSMOID_StreetAddress,
- &CSSMOID_OrganizationName,
- &CSSMOID_OrganizationalUnitName,
- &CSSMOID_DNQualifier }; // This should be "DC"
- // but is undoubtedly
- // wrong. TODO(avi):
- // Find the right OID.
-
- std::vector<std::string>* values[] = {
- &common_names, &locality_names,
- &state_names, &country_names,
- &(principal->street_addresses),
- &(principal->organization_names),
- &(principal->organization_unit_names),
- &(principal->domain_components) };
- DCHECK(arraysize(kOIDs) == arraysize(values));
-
- for (size_t rdn = 0; rdn < name->numberOfRDNs; ++rdn) {
- CSSM_X509_RDN rdn_struct = name->RelativeDistinguishedName[rdn];
- for (size_t pair = 0; pair < rdn_struct.numberOfPairs; ++pair) {
- CSSM_X509_TYPE_VALUE_PAIR pair_struct =
- rdn_struct.AttributeTypeAndValue[pair];
- for (size_t oid = 0; oid < arraysize(kOIDs); ++oid) {
- if (CSSMOIDEqual(&pair_struct.type, kOIDs[oid])) {
- std::string value =
- std::string(reinterpret_cast<std::string::value_type*>
- (pair_struct.value.Data),
- pair_struct.value.Length);
- values[oid]->push_back(value);
- break;
- }
- }
- }
- }
-
- // We don't expect to have more than one CN, L, S, and C.
- std::vector<std::string>* single_value_lists[4] = {
- &common_names, &locality_names, &state_names, &country_names };
- std::string* single_values[4] = {
- &principal->common_name, &principal->locality_name,
- &principal->state_or_province_name, &principal->country_name };
- for (size_t i = 0; i < arraysize(single_value_lists); ++i) {
- DCHECK(single_value_lists[i]->size() <= 1);
- if (single_value_lists[i]->size() > 0)
- *(single_values[i]) = (*(single_value_lists[i]))[0];
- }
-}
-
struct CSSMFields {
CSSMFields() : cl_handle(NULL), num_of_fields(0), fields(NULL) {}
~CSSMFields() {
@@ -356,17 +295,94 @@
}
}
+// Creates a SecPolicyRef for the given OID, with optional value.
+OSStatus CreatePolicy(const CSSM_OID* policy_OID,
+ void* option_data,
+ size_t option_length,
+ SecPolicyRef* policy) {
+ SecPolicySearchRef search;
+ OSStatus err = SecPolicySearchCreate(CSSM_CERT_X_509v3, policy_OID, NULL,
+ &search);
+ if (err)
+ return err;
+ err = SecPolicySearchCopyNext(search, policy);
+ CFRelease(search);
+ if (err)
+ return err;
+
+ if (option_data) {
+ CSSM_DATA options_data = {
+ option_length,
+ reinterpret_cast<uint8_t*>(option_data)
+ };
+ err = SecPolicySetValue(*policy, &options_data);
+ if (err) {
+ CFRelease(*policy);
+ return err;
+ }
+ }
+ return noErr;
+}
+
+// Gets the issuer for a given cert, starting with the cert itself and
+// including the intermediate and finally root certificates (if any).
+// This function calls SecTrust but doesn't actually pay attention to the trust
+// result: it shouldn't be used to determine trust, just to traverse the chain.
+// Caller is responsible for releasing the value stored into *out_cert_chain.
+OSStatus CopyCertChain(SecCertificateRef cert_handle,
+ CFArrayRef* out_cert_chain) {
+ DCHECK(cert_handle && out_cert_chain);
+ // Create an SSL policy ref configured for client cert evaluation.
+ SecPolicyRef ssl_policy;
+ OSStatus result = X509Certificate::CreateSSLClientPolicy(&ssl_policy);
+ if (result)
+ return result;
+ scoped_cftyperef<SecPolicyRef> scoped_ssl_policy(ssl_policy);
+
+ // Create a SecTrustRef.
+ scoped_cftyperef<CFArrayRef> input_certs(
+ CFArrayCreate(NULL, (const void**)&cert_handle, 1,
+ &kCFTypeArrayCallBacks));
+ SecTrustRef trust_ref = NULL;
+ result = SecTrustCreateWithCertificates(input_certs, ssl_policy, &trust_ref);
+ if (result)
+ return result;
+ scoped_cftyperef<SecTrustRef> trust(trust_ref);
+
+ // Evaluate trust, which creates the cert chain.
+ SecTrustResultType status;
+ CSSM_TP_APPLE_EVIDENCE_INFO* status_chain;
+ result = SecTrustEvaluate(trust, &status);
+ if (result)
+ return result;
+ return SecTrustGetResult(trust, &status, out_cert_chain, &status_chain);
+}
+
+// Returns true if |purpose| is listed as allowed in |usage|. This
+// function also considers the "Any" purpose. If the attribute is
+// present and empty, we return false.
+bool ExtendedKeyUsageAllows(const CE_ExtendedKeyUsage* usage,
+ const CSSM_OID* purpose) {
+ for (unsigned p = 0; p < usage->numPurposes; ++p) {
+ if (CSSMOIDEqual(&usage->purposes[p], purpose))
+ return true;
+ if (CSSMOIDEqual(&usage->purposes[p], &CSSMOID_ExtendedKeyUsageAny))
+ return true;
+ }
+ return false;
+}
+
} // namespace
void X509Certificate::Initialize() {
const CSSM_X509_NAME* name;
OSStatus status = SecCertificateGetSubject(cert_handle_, &name);
if (!status) {
- ParsePrincipal(name, &subject_);
+ subject_.Parse(name);
}
status = SecCertificateGetIssuer(cert_handle_, &name);
if (!status) {
- ParsePrincipal(name, &issuer_);
+ issuer_.Parse(name);
}
GetCertDateForOID(cert_handle_, CSSMOID_X509V1ValidityNotBefore,
@@ -375,9 +391,6 @@
&valid_expiry_);
fingerprint_ = CalculateFingerprint(cert_handle_);
-
- // Store the certificate in the cache in case we need it later.
- X509Certificate::Cache::GetInstance()->Insert(this);
}
// static
@@ -419,29 +432,20 @@
// Create an SSL SecPolicyRef, and configure it to perform hostname
// validation. The hostname check does 99% of what we want, with the
// exception of dotted IPv4 addreses, which we handle ourselves below.
- SecPolicySearchRef ssl_policy_search_ref = NULL;
- OSStatus status = SecPolicySearchCreate(CSSM_CERT_X_509v3,
- &CSSMOID_APPLE_TP_SSL,
- NULL,
- &ssl_policy_search_ref);
- if (status)
- return NetErrorFromOSStatus(status);
- scoped_cftyperef<SecPolicySearchRef>
- scoped_ssl_policy_search_ref(ssl_policy_search_ref);
- SecPolicyRef ssl_policy = NULL;
- status = SecPolicySearchCopyNext(ssl_policy_search_ref, &ssl_policy);
+ CSSM_APPLE_TP_SSL_OPTIONS tp_ssl_options = {
+ CSSM_APPLE_TP_SSL_OPTS_VERSION,
+ hostname.size(),
+ hostname.data(),
+ 0
+ };
+ SecPolicyRef ssl_policy;
+ OSStatus status = CreatePolicy(&CSSMOID_APPLE_TP_SSL,
+ &tp_ssl_options,
+ sizeof(tp_ssl_options),
+ &ssl_policy);
if (status)
return NetErrorFromOSStatus(status);
scoped_cftyperef<SecPolicyRef> scoped_ssl_policy(ssl_policy);
- CSSM_APPLE_TP_SSL_OPTIONS tp_ssl_options = { CSSM_APPLE_TP_SSL_OPTS_VERSION };
- tp_ssl_options.ServerName = hostname.data();
- tp_ssl_options.ServerNameLen = hostname.size();
- CSSM_DATA tp_ssl_options_data_value;
- tp_ssl_options_data_value.Data = reinterpret_cast<uint8*>(&tp_ssl_options);
- tp_ssl_options_data_value.Length = sizeof(tp_ssl_options);
- status = SecPolicySetValue(ssl_policy, &tp_ssl_options_data_value);
- if (status)
- return NetErrorFromOSStatus(status);
// Create and configure a SecTrustRef, which takes our certificate(s)
// and our SSL SecPolicyRef. SecTrustCreateWithCertificates() takes an
@@ -457,6 +461,12 @@
for (size_t i = 0; i < intermediate_ca_certs_.size(); ++i)
CFArrayAppendValue(cert_array, intermediate_ca_certs_[i]);
+ // From here on, only one thread can be active at a time. We have had a number
+ // of sporadic crashes in the SecTrustEvaluate call below, way down inside
+ // Apple's cert code, which we suspect are caused by a thread-safety issue.
+ // So as a speculative fix allow only one thread to use SecTrust on this cert.
+ AutoLock lock(verification_lock_);
+
SecTrustRef trust_ref = NULL;
status = SecTrustCreateWithCertificates(cert_array, ssl_policy, &trust_ref);
if (status)
@@ -668,14 +678,22 @@
}
// static
+X509Certificate::OSCertHandle X509Certificate::DupOSCertHandle(
+ OSCertHandle handle) {
+ if (!handle)
+ return NULL;
+ return reinterpret_cast<OSCertHandle>(const_cast<void*>(CFRetain(handle)));
+}
+
+// static
void X509Certificate::FreeOSCertHandle(OSCertHandle cert_handle) {
CFRelease(cert_handle);
}
// static
-X509Certificate::Fingerprint X509Certificate::CalculateFingerprint(
+SHA1Fingerprint X509Certificate::CalculateFingerprint(
OSCertHandle cert) {
- Fingerprint sha1;
+ SHA1Fingerprint sha1;
memset(sha1.data, 0, sizeof(sha1.data));
CSSM_DATA cert_data;
@@ -691,4 +709,194 @@
return sha1;
}
+bool X509Certificate::SupportsSSLClientAuth() const {
+ CSSMFields fields;
+ if (GetCertFields(cert_handle_, &fields) != noErr)
+ return false;
+
+ // Gather the extensions we care about. We do not support
+ // CSSMOID_NetscapeCertType on OS X.
+ const CE_ExtendedKeyUsage* ext_key_usage = NULL;
+ const CE_KeyUsage* key_usage = NULL;
+ for (unsigned f = 0; f < fields.num_of_fields; ++f) {
+ const CSSM_FIELD& field = fields.fields[f];
+ const CSSM_X509_EXTENSION* ext =
+ reinterpret_cast<const CSSM_X509_EXTENSION*>(field.FieldValue.Data);
+ if (CSSMOIDEqual(&field.FieldOid, &CSSMOID_KeyUsage)) {
+ key_usage = reinterpret_cast<const CE_KeyUsage*>(ext->value.parsedValue);
+ } else if (CSSMOIDEqual(&field.FieldOid, &CSSMOID_ExtendedKeyUsage)) {
+ ext_key_usage =
+ reinterpret_cast<const CE_ExtendedKeyUsage*>(ext->value.parsedValue);
+ }
+ }
+
+ // RFC5280 says to take the intersection of the two extensions.
+ //
+ // Our underlying crypto libraries don't expose
+ // ClientCertificateType, so for now we will not support fixed
+ // Diffie-Hellman mechanisms. For rsa_sign, we need the
+ // digitalSignature bit.
+ //
+ // In particular, if a key has the nonRepudiation bit and not the
+ // digitalSignature one, we will not offer it to the user.
+ if (key_usage && !((*key_usage) & CE_KU_DigitalSignature))
+ return false;
+ if (ext_key_usage && !ExtendedKeyUsageAllows(ext_key_usage,
+ &CSSMOID_ClientAuth))
+ return false;
+ return true;
+}
+
+bool X509Certificate::IsIssuedBy(
+ const std::vector<CertPrincipal>& valid_issuers) {
+ // Get the cert's issuer chain.
+ CFArrayRef cert_chain = NULL;
+ OSStatus result;
+ result = CopyCertChain(os_cert_handle(), &cert_chain);
+ if (result != noErr)
+ return false;
+ scoped_cftyperef<CFArrayRef> scoped_cert_chain(cert_chain);
+
+ // Check all the certs in the chain for a match.
+ int n = CFArrayGetCount(cert_chain);
+ for (int i = 0; i < n; ++i) {
+ SecCertificateRef cert_handle = reinterpret_cast<SecCertificateRef>(
+ const_cast<void*>(CFArrayGetValueAtIndex(cert_chain, i)));
+ scoped_refptr<X509Certificate> cert = X509Certificate::CreateFromHandle(
+ cert_handle,
+ X509Certificate::SOURCE_LONE_CERT_IMPORT,
+ X509Certificate::OSCertHandles());
+ for (unsigned j = 0; j < valid_issuers.size(); j++) {
+ if (cert->subject().Matches(valid_issuers[j]))
+ return true;
+ }
+ }
+ return false;
+}
+
+// static
+OSStatus X509Certificate::CreateSSLClientPolicy(SecPolicyRef* out_policy) {
+ CSSM_APPLE_TP_SSL_OPTIONS tp_ssl_options = {
+ CSSM_APPLE_TP_SSL_OPTS_VERSION,
+ 0,
+ NULL,
+ CSSM_APPLE_TP_SSL_CLIENT
+ };
+ return CreatePolicy(&CSSMOID_APPLE_TP_SSL,
+ &tp_ssl_options,
+ sizeof(tp_ssl_options),
+ out_policy);
+}
+
+// static
+bool X509Certificate::GetSSLClientCertificates (
+ const std::string& server_domain,
+ const std::vector<CertPrincipal>& valid_issuers,
+ std::vector<scoped_refptr<X509Certificate> >* certs) {
+ scoped_cftyperef<SecIdentityRef> preferred_identity;
+ if (!server_domain.empty()) {
+ // See if there's an identity preference for this domain:
+ scoped_cftyperef<CFStringRef> domain_str(
+ base::SysUTF8ToCFStringRef("https://" + server_domain));
+ SecIdentityRef identity = NULL;
+ if (SecIdentityCopyPreference(domain_str,
+ 0,
+ NULL, // validIssuers argument is ignored :(
+ &identity) == noErr)
+ preferred_identity.reset(identity);
+ }
+
+ // Now enumerate the identities in the available keychains.
+ SecIdentitySearchRef search = nil;
+ OSStatus err = SecIdentitySearchCreate(NULL, CSSM_KEYUSE_SIGN, &search);
+ scoped_cftyperef<SecIdentitySearchRef> scoped_search(search);
+ while (!err) {
+ SecIdentityRef identity = NULL;
+ err = SecIdentitySearchCopyNext(search, &identity);
+ if (err)
+ break;
+ scoped_cftyperef<SecIdentityRef> scoped_identity(identity);
+
+ SecCertificateRef cert_handle;
+ err = SecIdentityCopyCertificate(identity, &cert_handle);
+ if (err != noErr)
+ continue;
+ scoped_cftyperef<SecCertificateRef> scoped_cert_handle(cert_handle);
+
+ scoped_refptr<X509Certificate> cert(
+ CreateFromHandle(cert_handle, SOURCE_LONE_CERT_IMPORT,
+ OSCertHandles()));
+ if (cert->HasExpired() || !cert->SupportsSSLClientAuth())
+ continue;
+
+ // Skip duplicates (a cert may be in multiple keychains).
+ const SHA1Fingerprint& fingerprint = cert->fingerprint();
+ unsigned i;
+ for (i = 0; i < certs->size(); ++i) {
+ if ((*certs)[i]->fingerprint().Equals(fingerprint))
+ break;
+ }
+ if (i < certs->size())
+ continue;
+
+ bool is_preferred = preferred_identity &&
+ CFEqual(preferred_identity, identity);
+
+ // Make sure the issuer matches valid_issuers, if given.
+ // But an explicit cert preference overrides this.
+ if (!is_preferred &&
+ valid_issuers.size() > 0 &&
+ !cert->IsIssuedBy(valid_issuers))
+ continue;
+
+ // The cert passes, so add it to the vector.
+ // If it's the preferred identity, add it at the start (so it'll be
+ // selected by default in the UI.)
+ if (is_preferred)
+ certs->insert(certs->begin(), cert);
+ else
+ certs->push_back(cert);
+ }
+
+ if (err != errSecItemNotFound) {
+ LOG(ERROR) << "SecIdentitySearch error " << err;
+ return false;
+ }
+ return true;
+}
+
+CFArrayRef X509Certificate::CreateClientCertificateChain() const {
+ // Initialize the result array with just the IdentityRef of the receiver:
+ OSStatus result;
+ SecIdentityRef identity;
+ result = SecIdentityCreateWithCertificate(NULL, cert_handle_, &identity);
+ if (result) {
+ LOG(ERROR) << "SecIdentityCreateWithCertificate error " << result;
+ return NULL;
+ }
+ scoped_cftyperef<CFMutableArrayRef> chain(
+ CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks));
+ CFArrayAppendValue(chain, identity);
+
+ CFArrayRef cert_chain = NULL;
+ result = CopyCertChain(cert_handle_, &cert_chain);
+ if (result)
+ goto exit;
+
+ // Append the intermediate certs from SecTrust to the result array:
+ if (cert_chain) {
+ int chain_count = CFArrayGetCount(cert_chain);
+ if (chain_count > 1) {
+ CFArrayAppendArray(chain,
+ cert_chain,
+ CFRangeMake(1, chain_count - 1));
+ }
+ CFRelease(cert_chain);
+ }
+exit:
+ if (result)
+ LOG(ERROR) << "CreateIdentityCertificateChain error " << result;
+ return chain.release();
+}
+
} // namespace net
diff --git a/net/base/x509_certificate_nss.cc b/net/base/x509_certificate_nss.cc
index 05ed979..8eb337f 100644
--- a/net/base/x509_certificate_nss.cc
+++ b/net/base/x509_certificate_nss.cc
@@ -5,6 +5,7 @@
#include "net/base/x509_certificate.h"
#include <cert.h>
+#include <nss.h>
#include <pk11pub.h>
#include <prerror.h>
#include <prtime.h>
@@ -138,6 +139,7 @@
case SEC_ERROR_CERT_NOT_VALID:
// TODO(port): add an ERR_CERT_WRONG_USAGE error code.
case SEC_ERROR_CERT_USAGES_INVALID:
+ case SEC_ERROR_POLICY_VALIDATION_FAILED:
return ERR_CERT_INVALID;
default:
LOG(WARNING) << "Unknown error " << err << " mapped to net::ERR_FAILED";
@@ -169,6 +171,7 @@
case SEC_ERROR_CERT_NOT_VALID:
// TODO(port): add a CERT_STATUS_WRONG_USAGE error code.
case SEC_ERROR_CERT_USAGES_INVALID:
+ case SEC_ERROR_POLICY_VALIDATION_FAILED:
return CERT_STATUS_INVALID;
default:
return 0;
@@ -216,7 +219,7 @@
typedef char* (*CERTGetNameFunc)(CERTName* name);
void ParsePrincipal(CERTName* name,
- X509Certificate::Principal* principal) {
+ CertPrincipal* principal) {
// TODO(jcampan): add business_category and serial_number.
// TODO(wtc): NSS has the CERT_GetOrgName, CERT_GetOrgUnitName, and
// CERT_GetDomainComponentName functions, but they return only the most
@@ -319,6 +322,12 @@
PORT_FreeArena(arena, PR_FALSE);
}
+// Forward declarations.
+SECStatus RetryPKIXVerifyCertWithWorkarounds(
+ X509Certificate::OSCertHandle cert_handle, int num_policy_oids,
+ std::vector<CERTValInParam>* cvin, CERTValOutParam* cvout);
+SECOidTag GetFirstCertPolicy(X509Certificate::OSCertHandle cert_handle);
+
// Call CERT_PKIXVerifyCert for the cert_handle.
// Verification results are stored in an array of CERTValOutParam.
// If policy_oids is not NULL and num_policy_oids is positive, policies
@@ -392,60 +401,160 @@
revocation_flags.chainTests.cert_rev_method_independent_flags =
revocation_method_independent_flags;
- CERTValInParam cvin[4];
- int cvin_index = 0;
+ std::vector<CERTValInParam> cvin;
+ cvin.reserve(5);
+ CERTValInParam in_param;
// No need to set cert_pi_trustAnchors here.
- cvin[cvin_index].type = cert_pi_revocationFlags;
- cvin[cvin_index].value.pointer.revocation = &revocation_flags;
- cvin_index++;
- std::vector<SECOidTag> policies;
+ in_param.type = cert_pi_revocationFlags;
+ in_param.value.pointer.revocation = &revocation_flags;
+ cvin.push_back(in_param);
if (policy_oids && num_policy_oids > 0) {
- cvin[cvin_index].type = cert_pi_policyOID;
- cvin[cvin_index].value.arraySize = num_policy_oids;
- cvin[cvin_index].value.array.oids = policy_oids;
- cvin_index++;
+ in_param.type = cert_pi_policyOID;
+ in_param.value.arraySize = num_policy_oids;
+ in_param.value.array.oids = policy_oids;
+ cvin.push_back(in_param);
}
- // Add cert_pi_useAIACertFetch last so we can easily remove it from the
- // cvin array in the workaround below.
- cvin[cvin_index].type = cert_pi_useAIACertFetch;
- cvin[cvin_index].value.scalar.b = PR_TRUE;
- cvin_index++;
- cvin[cvin_index].type = cert_pi_end;
+ in_param.type = cert_pi_end;
+ cvin.push_back(in_param);
SECStatus rv = CERT_PKIXVerifyCert(cert_handle, certificateUsageSSLServer,
- cvin, cvout, NULL);
+ &cvin[0], cvout, NULL);
if (rv != SECSuccess) {
- // cert_pi_useAIACertFetch can't handle a CA issuers access location that
- // is an LDAP URL with an empty host name (NSS bug 528741). If cert fetch
- // fails because of a network error, it also causes CERT_PKIXVerifyCert
- // to report the network error rather than SEC_ERROR_UNKNOWN_ISSUER. To
- // work around these NSS bugs, we retry without cert_pi_useAIACertFetch.
- int nss_error = PORT_GetError();
- if (nss_error == SEC_ERROR_INVALID_ARGS || !IS_SEC_ERROR(nss_error)) {
- cvin_index--;
- DCHECK_EQ(cvin[cvin_index].type, cert_pi_useAIACertFetch);
- cvin[cvin_index].type = cert_pi_end;
- rv = CERT_PKIXVerifyCert(cert_handle, certificateUsageSSLServer,
- cvin, cvout, NULL);
- }
+ rv = RetryPKIXVerifyCertWithWorkarounds(cert_handle, num_policy_oids,
+ &cvin, cvout);
}
return rv;
}
-bool CheckCertPolicies(X509Certificate::OSCertHandle cert_handle,
- SECOidTag ev_policy_tag) {
+// PKIXVerifyCert calls this function to work around some bugs in
+// CERT_PKIXVerifyCert. All the arguments of this function are either the
+// arguments or local variables of PKIXVerifyCert.
+SECStatus RetryPKIXVerifyCertWithWorkarounds(
+ X509Certificate::OSCertHandle cert_handle, int num_policy_oids,
+ std::vector<CERTValInParam>* cvin, CERTValOutParam* cvout) {
+ // We call this function when the first CERT_PKIXVerifyCert call in
+ // PKIXVerifyCert failed, so we initialize |rv| to SECFailure.
+ SECStatus rv = SECFailure;
+ int nss_error = PORT_GetError();
+ CERTValInParam in_param;
+
+ // If we get SEC_ERROR_UNKNOWN_ISSUER, we may be missing an intermediate
+ // CA certificate, so we retry with cert_pi_useAIACertFetch.
+ // cert_pi_useAIACertFetch has several bugs in its error handling and
+ // error reporting (NSS bug 528743), so we don't use it by default.
+ // Note: When building a certificate chain, CERT_PKIXVerifyCert may
+ // incorrectly pick a CA certificate with the same subject name as the
+ // missing intermediate CA certificate, and fail with the
+ // SEC_ERROR_BAD_SIGNATURE error (NSS bug 524013), so we also retry with
+ // cert_pi_useAIACertFetch on SEC_ERROR_BAD_SIGNATURE.
+ if (nss_error == SEC_ERROR_UNKNOWN_ISSUER ||
+ nss_error == SEC_ERROR_BAD_SIGNATURE) {
+ DCHECK_EQ(cvin->back().type, cert_pi_end);
+ cvin->pop_back();
+ in_param.type = cert_pi_useAIACertFetch;
+ in_param.value.scalar.b = PR_TRUE;
+ cvin->push_back(in_param);
+ in_param.type = cert_pi_end;
+ cvin->push_back(in_param);
+ rv = CERT_PKIXVerifyCert(cert_handle, certificateUsageSSLServer,
+ &(*cvin)[0], cvout, NULL);
+ if (rv == SECSuccess)
+ return rv;
+ int new_nss_error = PORT_GetError();
+ if (new_nss_error == SEC_ERROR_INVALID_ARGS ||
+ new_nss_error == SEC_ERROR_UNKNOWN_AIA_LOCATION_TYPE ||
+ new_nss_error == SEC_ERROR_BAD_HTTP_RESPONSE ||
+ new_nss_error == SEC_ERROR_BAD_LDAP_RESPONSE ||
+ !IS_SEC_ERROR(new_nss_error)) {
+ // Use the original error code because of cert_pi_useAIACertFetch's
+ // bad error reporting.
+ PORT_SetError(nss_error);
+ return rv;
+ }
+ nss_error = new_nss_error;
+ }
+
+ // If an intermediate CA certificate has requireExplicitPolicy in its
+ // policyConstraints extension, CERT_PKIXVerifyCert fails with
+ // SEC_ERROR_POLICY_VALIDATION_FAILED because we didn't specify any
+ // certificate policy (NSS bug 552775). So we retry with the certificate
+ // policy found in the server certificate.
+ if (nss_error == SEC_ERROR_POLICY_VALIDATION_FAILED &&
+ num_policy_oids == 0) {
+ SECOidTag policy = GetFirstCertPolicy(cert_handle);
+ if (policy != SEC_OID_UNKNOWN) {
+ DCHECK_EQ(cvin->back().type, cert_pi_end);
+ cvin->pop_back();
+ in_param.type = cert_pi_policyOID;
+ in_param.value.arraySize = 1;
+ in_param.value.array.oids = &policy;
+ cvin->push_back(in_param);
+ in_param.type = cert_pi_end;
+ cvin->push_back(in_param);
+ rv = CERT_PKIXVerifyCert(cert_handle, certificateUsageSSLServer,
+ &(*cvin)[0], cvout, NULL);
+ if (rv != SECSuccess) {
+ // Use the original error code.
+ PORT_SetError(nss_error);
+ }
+ }
+ }
+
+ return rv;
+}
+
+// Decodes the certificatePolicies extension of the certificate. Returns
+// NULL if the certificate doesn't have the extension or the extension can't
+// be decoded. The returned value must be freed with a
+// CERT_DestroyCertificatePoliciesExtension call.
+CERTCertificatePolicies* DecodeCertPolicies(
+ X509Certificate::OSCertHandle cert_handle) {
SECItem policy_ext;
SECStatus rv = CERT_FindCertExtension(
cert_handle, SEC_OID_X509_CERTIFICATE_POLICIES, &policy_ext);
- if (rv != SECSuccess) {
- LOG(ERROR) << "Cert has no policies extension.";
- return false;
- }
+ if (rv != SECSuccess)
+ return NULL;
CERTCertificatePolicies* policies =
CERT_DecodeCertificatePoliciesExtension(&policy_ext);
SECITEM_FreeItem(&policy_ext, PR_FALSE);
+ return policies;
+}
+
+// Returns the OID tag for the first certificate policy in the certificate's
+// certificatePolicies extension. Returns SEC_OID_UNKNOWN if the certificate
+// has no certificate policy.
+SECOidTag GetFirstCertPolicy(X509Certificate::OSCertHandle cert_handle) {
+ CERTCertificatePolicies* policies = DecodeCertPolicies(cert_handle);
+ if (!policies)
+ return SEC_OID_UNKNOWN;
+ ScopedCERTCertificatePolicies scoped_policies(policies);
+ CERTPolicyInfo* policy_info = policies->policyInfos[0];
+ if (!policy_info)
+ return SEC_OID_UNKNOWN;
+ if (policy_info->oid != SEC_OID_UNKNOWN)
+ return policy_info->oid;
+
+ // The certificate policy is unknown to NSS. We need to create a dynamic
+ // OID tag for the policy.
+ SECOidData od;
+ od.oid.len = policy_info->policyID.len;
+ od.oid.data = policy_info->policyID.data;
+ od.offset = SEC_OID_UNKNOWN;
+ // NSS doesn't allow us to pass an empty description, so I use a hardcoded,
+ // default description here. The description doesn't need to be unique for
+ // each OID.
+ od.desc = "a certificate policy";
+ od.mechanism = CKM_INVALID_MECHANISM;
+ od.supportedExtension = INVALID_CERT_EXTENSION;
+ return SECOID_AddEntry(&od);
+}
+
+bool CheckCertPolicies(X509Certificate::OSCertHandle cert_handle,
+ SECOidTag ev_policy_tag) {
+ CERTCertificatePolicies* policies = DecodeCertPolicies(cert_handle);
if (!policies) {
- LOG(ERROR) << "Failed to decode certificate policy.";
+ LOG(ERROR) << "Cert has no policies extension or extension couldn't be "
+ "decoded.";
return false;
}
ScopedCERTCertificatePolicies scoped_policies(policies);
@@ -472,9 +581,6 @@
ParseDate(&cert_handle_->validity.notAfter, &valid_expiry_);
fingerprint_ = CalculateFingerprint(cert_handle_);
-
- // Store the certificate in the cache in case we need it later.
- X509Certificate::Cache::GetInstance()->Insert(this);
}
// static
@@ -600,7 +706,7 @@
cvout[cvout_trust_anchor_index].value.pointer.cert;
if (root_ca == NULL)
return false;
- X509Certificate::Fingerprint fingerprint =
+ SHA1Fingerprint fingerprint =
X509Certificate::CalculateFingerprint(root_ca);
SECOidTag ev_policy_tag = SEC_OID_UNKNOWN;
if (!metadata->GetPolicyOID(fingerprint, &ev_policy_tag))
@@ -617,11 +723,25 @@
const char* data, int length) {
base::EnsureNSSInit();
- SECItem der_cert;
- der_cert.data = reinterpret_cast<unsigned char*>(const_cast<char*>(data));
- der_cert.len = length;
- return CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &der_cert,
- NULL, PR_FALSE, PR_TRUE);
+ if (!NSS_IsInitialized())
+ return NULL;
+
+ // Make a copy of |data| since CERT_DecodeCertPackage might modify it.
+ char* data_copy = new char[length];
+ memcpy(data_copy, data, length);
+
+ // Parse into a certificate structure.
+ CERTCertificate* cert = CERT_DecodeCertFromPackage(data_copy, length);
+ delete [] data_copy;
+ if (!cert)
+ LOG(ERROR) << "Couldn't parse a certificate from " << length << " bytes";
+ return cert;
+}
+
+// static
+X509Certificate::OSCertHandle X509Certificate::DupOSCertHandle(
+ OSCertHandle cert_handle) {
+ return CERT_DupCertificate(cert_handle);
}
// static
@@ -630,9 +750,9 @@
}
// static
-X509Certificate::Fingerprint X509Certificate::CalculateFingerprint(
+SHA1Fingerprint X509Certificate::CalculateFingerprint(
OSCertHandle cert) {
- Fingerprint sha1;
+ SHA1Fingerprint sha1;
memset(sha1.data, 0, sizeof(sha1.data));
DCHECK(NULL != cert->derCert.data);
diff --git a/net/base/x509_certificate_unittest.cc b/net/base/x509_certificate_unittest.cc
index 7904cf0..63eec15 100644
--- a/net/base/x509_certificate_unittest.cc
+++ b/net/base/x509_certificate_unittest.cc
@@ -2,8 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/path_service.h"
#include "base/pickle.h"
#include "net/base/cert_status_flags.h"
+#include "net/base/cert_test_util.h"
#include "net/base/cert_verify_result.h"
#include "net/base/net_errors.h"
#include "net/base/test_certificate_data.h"
@@ -23,6 +27,8 @@
using base::Time;
+namespace net {
+
namespace {
// Certificates for test data. They're obtained with:
@@ -70,9 +76,31 @@
0x25, 0x66, 0xf2, 0xec, 0x8b, 0x0f, 0xbf, 0xd8
};
-} // namespace
+// Returns a FilePath object representing the src/net/data/ssl/certificates
+// directory in the source tree.
+FilePath GetTestCertsDirectory() {
+ FilePath certs_dir;
+ PathService::Get(base::DIR_SOURCE_ROOT, &certs_dir);
+ certs_dir = certs_dir.AppendASCII("net");
+ certs_dir = certs_dir.AppendASCII("data");
+ certs_dir = certs_dir.AppendASCII("ssl");
+ certs_dir = certs_dir.AppendASCII("certificates");
+ return certs_dir;
+}
-namespace net {
+// Imports a certificate file in the src/net/data/ssl/certificates directory.
+// certs_dir represents the test certificates directory. cert_file is the
+// name of the certificate file.
+X509Certificate* ImportCertFromFile(const FilePath& certs_dir,
+ const std::string& cert_file) {
+ FilePath cert_path = certs_dir.AppendASCII(cert_file);
+ std::string cert_data;
+ if (!file_util::ReadFileToString(cert_path, &cert_data))
+ return NULL;
+ return X509Certificate::CreateFromBytes(cert_data.data(), cert_data.size());
+}
+
+} // namespace
TEST(X509CertificateTest, GoogleCertParsing) {
scoped_refptr<X509Certificate> google_cert = X509Certificate::CreateFromBytes(
@@ -80,7 +108,7 @@
ASSERT_NE(static_cast<X509Certificate*>(NULL), google_cert);
- const X509Certificate::Principal& subject = google_cert->subject();
+ const CertPrincipal& subject = google_cert->subject();
EXPECT_EQ("www.google.com", subject.common_name);
EXPECT_EQ("Mountain View", subject.locality_name);
EXPECT_EQ("California", subject.state_or_province_name);
@@ -91,7 +119,7 @@
EXPECT_EQ(0U, subject.organization_unit_names.size());
EXPECT_EQ(0U, subject.domain_components.size());
- const X509Certificate::Principal& issuer = google_cert->issuer();
+ const CertPrincipal& issuer = google_cert->issuer();
EXPECT_EQ("Thawte SGC CA", issuer.common_name);
EXPECT_EQ("", issuer.locality_name);
EXPECT_EQ("", issuer.state_or_province_name);
@@ -109,7 +137,7 @@
const Time& valid_expiry = google_cert->valid_expiry();
EXPECT_EQ(1269728407, valid_expiry.ToDoubleT()); // Mar 27 22:20:07 2010 GMT
- const X509Certificate::Fingerprint& fingerprint = google_cert->fingerprint();
+ const SHA1Fingerprint& fingerprint = google_cert->fingerprint();
for (size_t i = 0; i < 20; ++i)
EXPECT_EQ(google_fingerprint[i], fingerprint.data[i]);
@@ -134,7 +162,7 @@
ASSERT_NE(static_cast<X509Certificate*>(NULL), webkit_cert);
- const X509Certificate::Principal& subject = webkit_cert->subject();
+ const CertPrincipal& subject = webkit_cert->subject();
EXPECT_EQ("Cupertino", subject.locality_name);
EXPECT_EQ("California", subject.state_or_province_name);
EXPECT_EQ("US", subject.country_name);
@@ -145,7 +173,7 @@
EXPECT_EQ("Mac OS Forge", subject.organization_unit_names[0]);
EXPECT_EQ(0U, subject.domain_components.size());
- const X509Certificate::Principal& issuer = webkit_cert->issuer();
+ const CertPrincipal& issuer = webkit_cert->issuer();
EXPECT_EQ("Go Daddy Secure Certification Authority", issuer.common_name);
EXPECT_EQ("Scottsdale", issuer.locality_name);
EXPECT_EQ("Arizona", issuer.state_or_province_name);
@@ -165,7 +193,7 @@
const Time& valid_expiry = webkit_cert->valid_expiry();
EXPECT_EQ(1300491319, valid_expiry.ToDoubleT()); // Mar 18 23:35:19 2011 GMT
- const X509Certificate::Fingerprint& fingerprint = webkit_cert->fingerprint();
+ const SHA1Fingerprint& fingerprint = webkit_cert->fingerprint();
for (size_t i = 0; i < 20; ++i)
EXPECT_EQ(webkit_fingerprint[i], fingerprint.data[i]);
@@ -190,7 +218,7 @@
ASSERT_NE(static_cast<X509Certificate*>(NULL), thawte_cert);
- const X509Certificate::Principal& subject = thawte_cert->subject();
+ const CertPrincipal& subject = thawte_cert->subject();
EXPECT_EQ("www.thawte.com", subject.common_name);
EXPECT_EQ("Mountain View", subject.locality_name);
EXPECT_EQ("California", subject.state_or_province_name);
@@ -201,7 +229,7 @@
EXPECT_EQ(0U, subject.organization_unit_names.size());
EXPECT_EQ(0U, subject.domain_components.size());
- const X509Certificate::Principal& issuer = thawte_cert->issuer();
+ const CertPrincipal& issuer = thawte_cert->issuer();
EXPECT_EQ("thawte Extended Validation SSL CA", issuer.common_name);
EXPECT_EQ("", issuer.locality_name);
EXPECT_EQ("", issuer.state_or_province_name);
@@ -221,7 +249,7 @@
const Time& valid_expiry = thawte_cert->valid_expiry();
EXPECT_EQ(1263772799, valid_expiry.ToDoubleT()); // Jan 17 23:59:59 2010 GMT
- const X509Certificate::Fingerprint& fingerprint = thawte_cert->fingerprint();
+ const SHA1Fingerprint& fingerprint = thawte_cert->fingerprint();
for (size_t i = 0; i < 20; ++i)
EXPECT_EQ(thawte_fingerprint[i], fingerprint.data[i]);
@@ -253,7 +281,7 @@
ASSERT_NE(static_cast<X509Certificate*>(NULL), paypal_null_cert);
- const X509Certificate::Fingerprint& fingerprint =
+ const SHA1Fingerprint& fingerprint =
paypal_null_cert->fingerprint();
for (size_t i = 0; i < 20; ++i)
EXPECT_EQ(paypal_null_fingerprint[i], fingerprint.data[i]);
@@ -266,22 +294,22 @@
// Either the system crypto library should correctly report a certificate
// name mismatch, or our certificate blacklist should cause us to report an
// invalid certificate.
-#if defined(OS_LINUX) || defined(OS_WIN)
+#if !defined(OS_MACOSX)
EXPECT_NE(0, verify_result.cert_status &
(CERT_STATUS_COMMON_NAME_INVALID | CERT_STATUS_INVALID));
#endif
}
+// A certificate whose AIA extension contains an LDAP URL without a host name.
// This certificate will expire on 2011-09-08.
TEST(X509CertificateTest, UnoSoftCertParsing) {
+ FilePath certs_dir = GetTestCertsDirectory();
scoped_refptr<X509Certificate> unosoft_hu_cert =
- X509Certificate::CreateFromBytes(
- reinterpret_cast<const char*>(unosoft_hu_der),
- sizeof(unosoft_hu_der));
+ ImportCertFromFile(certs_dir, "unosoft_hu_cert.der");
ASSERT_NE(static_cast<X509Certificate*>(NULL), unosoft_hu_cert);
- const X509Certificate::Fingerprint& fingerprint =
+ const SHA1Fingerprint& fingerprint =
unosoft_hu_cert->fingerprint();
for (size_t i = 0; i < 20; ++i)
EXPECT_EQ(unosoft_hu_fingerprint[i], fingerprint.data[i]);
@@ -294,6 +322,39 @@
EXPECT_NE(0, verify_result.cert_status & CERT_STATUS_AUTHORITY_INVALID);
}
+#if defined(USE_NSS)
+// A regression test for http://crbug.com/31497.
+// This certificate will expire on 2012-04-08.
+// TODO(wtc): we can't run this test on Mac because MacTrustedCertificates
+// can hold only one additional trusted root certificate for unit tests.
+// TODO(wtc): we can't run this test on Windows because LoadTemporaryRootCert
+// isn't implemented (http//crbug.com/8470).
+TEST(X509CertificateTest, IntermediateCARequireExplicitPolicy) {
+ FilePath certs_dir = GetTestCertsDirectory();
+
+ scoped_refptr<X509Certificate> server_cert =
+ ImportCertFromFile(certs_dir, "www_us_army_mil_cert.der");
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), server_cert);
+
+ // The intermediate CA certificate's policyConstraints extension has a
+ // requireExplicitPolicy field with SkipCerts=0.
+ scoped_refptr<X509Certificate> intermediate_cert =
+ ImportCertFromFile(certs_dir, "dod_ca_17_cert.der");
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), intermediate_cert);
+
+ FilePath root_cert_path = certs_dir.AppendASCII("dod_root_ca_2_cert.der");
+ scoped_refptr<X509Certificate> root_cert =
+ LoadTemporaryRootCert(root_cert_path);
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), root_cert);
+
+ int flags = 0;
+ CertVerifyResult verify_result;
+ int error = server_cert->Verify("www.us.army.mil", flags, &verify_result);
+ EXPECT_EQ(OK, error);
+ EXPECT_EQ(0, verify_result.cert_status);
+}
+#endif
+
// Tests X509Certificate::Cache via X509Certificate::CreateFromHandle. We
// call X509Certificate::CreateFromHandle several times and observe whether
// it returns a cached or new X509Certificate object.
@@ -309,14 +370,18 @@
google_cert_handle = X509Certificate::CreateOSCertHandleFromBytes(
reinterpret_cast<const char*>(google_der), sizeof(google_der));
scoped_refptr<X509Certificate> cert1 = X509Certificate::CreateFromHandle(
- google_cert_handle, X509Certificate::SOURCE_LONE_CERT_IMPORT);
+ google_cert_handle, X509Certificate::SOURCE_LONE_CERT_IMPORT,
+ X509Certificate::OSCertHandles());
+ X509Certificate::FreeOSCertHandle(google_cert_handle);
// Add a certificate from the same source (SOURCE_LONE_CERT_IMPORT). This
// should return the cached certificate (cert1).
google_cert_handle = X509Certificate::CreateOSCertHandleFromBytes(
reinterpret_cast<const char*>(google_der), sizeof(google_der));
scoped_refptr<X509Certificate> cert2 = X509Certificate::CreateFromHandle(
- google_cert_handle, X509Certificate::SOURCE_LONE_CERT_IMPORT);
+ google_cert_handle, X509Certificate::SOURCE_LONE_CERT_IMPORT,
+ X509Certificate::OSCertHandles());
+ X509Certificate::FreeOSCertHandle(google_cert_handle);
EXPECT_EQ(cert1, cert2);
@@ -325,7 +390,9 @@
google_cert_handle = X509Certificate::CreateOSCertHandleFromBytes(
reinterpret_cast<const char*>(google_der), sizeof(google_der));
scoped_refptr<X509Certificate> cert3 = X509Certificate::CreateFromHandle(
- google_cert_handle, X509Certificate::SOURCE_FROM_NETWORK);
+ google_cert_handle, X509Certificate::SOURCE_FROM_NETWORK,
+ X509Certificate::OSCertHandles());
+ X509Certificate::FreeOSCertHandle(google_cert_handle);
EXPECT_NE(cert1, cert3);
@@ -334,14 +401,18 @@
google_cert_handle = X509Certificate::CreateOSCertHandleFromBytes(
reinterpret_cast<const char*>(google_der), sizeof(google_der));
scoped_refptr<X509Certificate> cert4 = X509Certificate::CreateFromHandle(
- google_cert_handle, X509Certificate::SOURCE_FROM_NETWORK);
+ google_cert_handle, X509Certificate::SOURCE_FROM_NETWORK,
+ X509Certificate::OSCertHandles());
+ X509Certificate::FreeOSCertHandle(google_cert_handle);
EXPECT_EQ(cert3, cert4);
google_cert_handle = X509Certificate::CreateOSCertHandleFromBytes(
reinterpret_cast<const char*>(google_der), sizeof(google_der));
scoped_refptr<X509Certificate> cert5 = X509Certificate::CreateFromHandle(
- google_cert_handle, X509Certificate::SOURCE_FROM_NETWORK);
+ google_cert_handle, X509Certificate::SOURCE_FROM_NETWORK,
+ X509Certificate::OSCertHandles());
+ X509Certificate::FreeOSCertHandle(google_cert_handle);
EXPECT_EQ(cert3, cert5);
}
@@ -367,33 +438,94 @@
scoped_refptr<X509Certificate> webkit_cert = X509Certificate::CreateFromBytes(
reinterpret_cast<const char*>(webkit_der), sizeof(webkit_der));
- X509Certificate::Policy policy;
+ CertPolicy policy;
- EXPECT_EQ(policy.Check(google_cert.get()), X509Certificate::Policy::UNKNOWN);
- EXPECT_EQ(policy.Check(webkit_cert.get()), X509Certificate::Policy::UNKNOWN);
+ EXPECT_EQ(policy.Check(google_cert.get()), CertPolicy::UNKNOWN);
+ EXPECT_EQ(policy.Check(webkit_cert.get()), CertPolicy::UNKNOWN);
EXPECT_FALSE(policy.HasAllowedCert());
EXPECT_FALSE(policy.HasDeniedCert());
policy.Allow(google_cert.get());
- EXPECT_EQ(policy.Check(google_cert.get()), X509Certificate::Policy::ALLOWED);
- EXPECT_EQ(policy.Check(webkit_cert.get()), X509Certificate::Policy::UNKNOWN);
+ EXPECT_EQ(policy.Check(google_cert.get()), CertPolicy::ALLOWED);
+ EXPECT_EQ(policy.Check(webkit_cert.get()), CertPolicy::UNKNOWN);
EXPECT_TRUE(policy.HasAllowedCert());
EXPECT_FALSE(policy.HasDeniedCert());
policy.Deny(google_cert.get());
- EXPECT_EQ(policy.Check(google_cert.get()), X509Certificate::Policy::DENIED);
- EXPECT_EQ(policy.Check(webkit_cert.get()), X509Certificate::Policy::UNKNOWN);
+ EXPECT_EQ(policy.Check(google_cert.get()), CertPolicy::DENIED);
+ EXPECT_EQ(policy.Check(webkit_cert.get()), CertPolicy::UNKNOWN);
EXPECT_FALSE(policy.HasAllowedCert());
EXPECT_TRUE(policy.HasDeniedCert());
policy.Allow(webkit_cert.get());
- EXPECT_EQ(policy.Check(google_cert.get()), X509Certificate::Policy::DENIED);
- EXPECT_EQ(policy.Check(webkit_cert.get()), X509Certificate::Policy::ALLOWED);
+ EXPECT_EQ(policy.Check(google_cert.get()), CertPolicy::DENIED);
+ EXPECT_EQ(policy.Check(webkit_cert.get()), CertPolicy::ALLOWED);
EXPECT_TRUE(policy.HasAllowedCert());
EXPECT_TRUE(policy.HasDeniedCert());
}
+#if defined(OS_MACOSX) || defined(OS_WIN)
+TEST(X509CertificateTest, IntermediateCertificates) {
+ scoped_refptr<X509Certificate> webkit_cert =
+ X509Certificate::CreateFromBytes(
+ reinterpret_cast<const char*>(webkit_der), sizeof(webkit_der));
+
+ scoped_refptr<X509Certificate> thawte_cert =
+ X509Certificate::CreateFromBytes(
+ reinterpret_cast<const char*>(thawte_der), sizeof(thawte_der));
+
+ scoped_refptr<X509Certificate> paypal_cert =
+ X509Certificate::CreateFromBytes(
+ reinterpret_cast<const char*>(paypal_null_der),
+ sizeof(paypal_null_der));
+
+ X509Certificate::OSCertHandle google_handle;
+ // Create object with no intermediates:
+ google_handle = X509Certificate::CreateOSCertHandleFromBytes(
+ reinterpret_cast<const char*>(google_der), sizeof(google_der));
+ X509Certificate::OSCertHandles intermediates1;
+ scoped_refptr<X509Certificate> cert1;
+ cert1 = X509Certificate::CreateFromHandle(
+ google_handle, X509Certificate::SOURCE_FROM_NETWORK, intermediates1);
+ EXPECT_TRUE(cert1->HasIntermediateCertificates(intermediates1));
+ EXPECT_FALSE(cert1->HasIntermediateCertificate(
+ webkit_cert->os_cert_handle()));
+
+ // Create object with 2 intermediates:
+ X509Certificate::OSCertHandles intermediates2;
+ intermediates2.push_back(webkit_cert->os_cert_handle());
+ intermediates2.push_back(thawte_cert->os_cert_handle());
+ scoped_refptr<X509Certificate> cert2;
+ cert2 = X509Certificate::CreateFromHandle(
+ google_handle, X509Certificate::SOURCE_FROM_NETWORK, intermediates2);
+
+ // The cache should have stored cert2 'cause it has more intermediates:
+ EXPECT_NE(cert1, cert2);
+
+ // Verify it has all the intermediates:
+ EXPECT_TRUE(cert2->HasIntermediateCertificate(
+ webkit_cert->os_cert_handle()));
+ EXPECT_TRUE(cert2->HasIntermediateCertificate(
+ thawte_cert->os_cert_handle()));
+ EXPECT_FALSE(cert2->HasIntermediateCertificate(
+ paypal_cert->os_cert_handle()));
+
+ // Create object with 1 intermediate:
+ X509Certificate::OSCertHandles intermediates3;
+ intermediates2.push_back(thawte_cert->os_cert_handle());
+ scoped_refptr<X509Certificate> cert3;
+ cert3 = X509Certificate::CreateFromHandle(
+ google_handle, X509Certificate::SOURCE_FROM_NETWORK, intermediates3);
+
+ // The cache should have returned cert2 'cause it has more intermediates:
+ EXPECT_EQ(cert3, cert2);
+
+ // Cleanup
+ X509Certificate::FreeOSCertHandle(google_handle);
+}
+#endif
+
} // namespace net
diff --git a/net/base/x509_certificate_win.cc b/net/base/x509_certificate_win.cc
index cc6fda2..901c0a6 100644
--- a/net/base/x509_certificate_win.cc
+++ b/net/base/x509_certificate_win.cc
@@ -8,6 +8,7 @@
#include "base/pickle.h"
#include "base/string_tokenizer.h"
#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
#include "net/base/cert_status_flags.h"
#include "net/base/cert_verify_result.h"
#include "net/base/ev_root_ca_metadata.h"
@@ -50,6 +51,9 @@
case SEC_E_CERT_UNKNOWN:
case CERT_E_ROLE:
return ERR_CERT_INVALID;
+ case CERT_E_WRONG_USAGE:
+ // TODO(wtc): Should we add ERR_CERT_WRONG_USAGE?
+ return ERR_CERT_INVALID;
// We received an unexpected_message or illegal_parameter alert message
// from the server.
case SEC_E_ILLEGAL_MESSAGE:
@@ -98,8 +102,8 @@
const DWORD kWrongUsageErrors = CERT_TRUST_IS_NOT_VALID_FOR_USAGE |
CERT_TRUST_CTL_IS_NOT_VALID_FOR_USAGE;
if (error_status & kWrongUsageErrors) {
- // TODO(wtc): Handle these errors.
- // cert_status = |= CERT_STATUS_WRONG_USAGE;
+ // TODO(wtc): Should we add CERT_STATUS_WRONG_USAGE?
+ cert_status |= CERT_STATUS_INVALID;
}
// The rest of the errors.
@@ -373,7 +377,7 @@
// Helper function to parse a principal from a WinInet description of that
// principal.
void ParsePrincipal(const std::string& description,
- X509Certificate::Principal* principal) {
+ CertPrincipal* principal) {
// The description of the principal is a string with each LDAP value on
// a separate line.
const std::string kDelimiters("\r\n");
@@ -460,9 +464,6 @@
valid_expiry_ = Time::FromFileTime(cert_handle_->pCertInfo->NotAfter);
fingerprint_ = CalculateFingerprint(cert_handle_);
-
- // Store the certificate in the cache in case we need it later.
- X509Certificate::Cache::GetInstance()->Insert(this);
}
// static
@@ -481,7 +482,11 @@
NULL, reinterpret_cast<const void **>(&cert_handle)))
return NULL;
- return CreateFromHandle(cert_handle, SOURCE_LONE_CERT_IMPORT);
+ X509Certificate* cert = CreateFromHandle(cert_handle,
+ SOURCE_LONE_CERT_IMPORT,
+ OSCertHandles());
+ FreeOSCertHandle(cert_handle);
+ return cert;
}
void X509Certificate::Persist(Pickle* pickle) {
@@ -534,11 +539,19 @@
CERT_CHAIN_PARA chain_para;
memset(&chain_para, 0, sizeof(chain_para));
chain_para.cbSize = sizeof(chain_para);
- // TODO(wtc): consider requesting the usage szOID_PKIX_KP_SERVER_AUTH
- // or szOID_SERVER_GATED_CRYPTO or szOID_SGC_NETSCAPE
- chain_para.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND;
- chain_para.RequestedUsage.Usage.cUsageIdentifier = 0;
- chain_para.RequestedUsage.Usage.rgpszUsageIdentifier = NULL; // LPSTR*
+ // ExtendedKeyUsage.
+ // We still need to request szOID_SERVER_GATED_CRYPTO and szOID_SGC_NETSCAPE
+ // today because some certificate chains need them. IE also requests these
+ // two usages.
+ static const LPSTR usage[] = {
+ szOID_PKIX_KP_SERVER_AUTH,
+ szOID_SERVER_GATED_CRYPTO,
+ szOID_SGC_NETSCAPE
+ };
+ chain_para.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR;
+ chain_para.RequestedUsage.Usage.cUsageIdentifier = arraysize(usage);
+ chain_para.RequestedUsage.Usage.rgpszUsageIdentifier =
+ const_cast<LPSTR*>(usage);
// We can set CERT_CHAIN_RETURN_LOWER_QUALITY_CONTEXTS to get more chains.
DWORD chain_flags = CERT_CHAIN_CACHE_END_CERT;
if (flags & VERIFY_REV_CHECKING_ENABLED) {
@@ -550,6 +563,9 @@
flags &= ~VERIFY_EV_CERT;
}
PCCERT_CHAIN_CONTEXT chain_context;
+ // IE passes a non-NULL pTime argument that specifies the current system
+ // time. IE passes CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT as the
+ // chain_flags argument.
if (!CertGetCertificateChain(
NULL, // default chain engine, HCCE_CURRENT_USER
cert_handle_,
@@ -706,7 +722,7 @@
// Look up the EV policy OID of the root CA.
PCCERT_CONTEXT root_cert = element[num_elements - 1]->pCertContext;
- Fingerprint fingerprint = CalculateFingerprint(root_cert);
+ SHA1Fingerprint fingerprint = CalculateFingerprint(root_cert);
const char* ev_policy_oid = NULL;
if (!metadata->GetPolicyOID(fingerprint, &ev_policy_oid))
return false;
@@ -737,19 +753,26 @@
return cert_handle;
}
+
+// static
+X509Certificate::OSCertHandle X509Certificate::DupOSCertHandle(
+ OSCertHandle cert_handle) {
+ return CertDuplicateCertificateContext(cert_handle);
+}
+
// static
void X509Certificate::FreeOSCertHandle(OSCertHandle cert_handle) {
CertFreeCertificateContext(cert_handle);
}
// static
-X509Certificate::Fingerprint X509Certificate::CalculateFingerprint(
+SHA1Fingerprint X509Certificate::CalculateFingerprint(
OSCertHandle cert) {
DCHECK(NULL != cert->pbCertEncoded);
DCHECK(0 != cert->cbCertEncoded);
BOOL rv;
- Fingerprint sha1;
+ SHA1Fingerprint sha1;
DWORD sha1_size = sizeof(sha1.data);
rv = CryptHashCertificate(NULL, CALG_SHA1, 0, cert->pbCertEncoded,
cert->cbCertEncoded, sha1.data, &sha1_size);
diff --git a/net/crash_cache.target.mk b/net/crash_cache.target.mk
new file mode 100644
index 0000000..d339afc
--- /dev/null
+++ b/net/crash_cache.target.mk
@@ -0,0 +1,183 @@
+# This file is generated by gyp; do not edit.
+
+TOOLSET := target
+TARGET := crash_cache
+DEFS_Debug := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-D__STDC_FORMAT_MACROS' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=1' \
+ '-D_DEBUG'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Debug := -Werror \
+ -pthread \
+ -fno-exceptions \
+ -Wall \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/atk-1.0 \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -O0 \
+ -g
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Debug :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Debug := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Debug := -I.
+
+DEFS_Release := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-D__STDC_FORMAT_MACROS' \
+ '-DNDEBUG' \
+ '-DNVALGRIND' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=0'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Release := -Werror \
+ -pthread \
+ -fno-exceptions \
+ -Wall \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/atk-1.0 \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -O2 \
+ -fno-ident \
+ -fdata-sections \
+ -ffunction-sections
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Release :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Release := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Release := -I.
+
+OBJS := $(obj).target/$(TARGET)/net/tools/crash_cache/crash_cache.o
+
+# Add to the list of files we specially track dependencies for.
+all_deps += $(OBJS)
+
+# Make sure our dependencies are built before any of us.
+$(OBJS): | $(obj).target/net/libnet.a $(obj).target/net/libnet_test_support.a $(obj).target/base/libbase.a $(obj).target/third_party/modp_b64/libmodp_b64.a $(obj).target/base/third_party/dynamic_annotations/libdynamic_annotations.a $(obj).target/base/libsymbolize.a $(obj).target/net/third_party/nss/libssl.a $(obj).target/third_party/zlib/libzlib.a $(obj).target/base/libxdg_mime.a $(obj).target/base/allocator/liballocator.a $(obj).target/third_party/libevent/libevent.a $(obj).target/base/libbase_i18n.a $(obj).target/third_party/icu/libicui18n.a $(obj).target/third_party/icu/libicuuc.a $(obj).target/third_party/icu/libicudata.a $(obj).target/build/temp_gyp/libgoogleurl.a $(obj).target/sdch/libsdch.a $(obj).target/net/libnet_base.a $(obj).target/v8/tools/gyp/libv8_snapshot.a $(obj).target/v8/tools/gyp/libv8_base.a $(obj).target/testing/libgtest.a
+
+# CFLAGS et al overrides must be target-local.
+# See "Target-specific Variable Values" in the GNU Make manual.
+$(OBJS): TOOLSET := $(TOOLSET)
+$(OBJS): GYP_CFLAGS := $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_C_$(BUILDTYPE)) $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE))
+$(OBJS): GYP_CXXFLAGS := $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_CC_$(BUILDTYPE)) $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE))
+
+# Suffix rules, putting all outputs into $(obj).
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(srcdir)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+# Try building from generated source, too.
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj).$(TOOLSET)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+# End of this set of suffix rules
+### Rules for final target.
+LDFLAGS_Debug := -pthread \
+ -Wl,-z,noexecstack \
+ -Wl,-uIsHeapProfilerRunning,-uProfilerStart \
+ -Wl,-u_Z21InitialMallocHook_NewPKvj,-u_Z22InitialMallocHook_MMapPKvS0_jiiix,-u_Z22InitialMallocHook_SbrkPKvi \
+ -Wl,-u_Z21InitialMallocHook_NewPKvm,-u_Z22InitialMallocHook_MMapPKvS0_miiil,-u_Z22InitialMallocHook_SbrkPKvl \
+ -rdynamic
+
+LDFLAGS_Release := -pthread \
+ -Wl,-z,noexecstack \
+ -Wl,-uIsHeapProfilerRunning,-uProfilerStart \
+ -Wl,-u_Z21InitialMallocHook_NewPKvj,-u_Z22InitialMallocHook_MMapPKvS0_jiiix,-u_Z22InitialMallocHook_SbrkPKvi \
+ -Wl,-u_Z21InitialMallocHook_NewPKvm,-u_Z22InitialMallocHook_MMapPKvS0_miiil,-u_Z22InitialMallocHook_SbrkPKvl \
+ -Wl,--gc-sections
+
+LIBS := -lrt \
+ -ldl \
+ -lgtk-x11-2.0 \
+ -lgdk-x11-2.0 \
+ -latk-1.0 \
+ -lgio-2.0 \
+ -lpangoft2-1.0 \
+ -lgdk_pixbuf-2.0 \
+ -lm \
+ -lpangocairo-1.0 \
+ -lcairo \
+ -lpango-1.0 \
+ -lfreetype \
+ -lfontconfig \
+ -lgobject-2.0 \
+ -lgmodule-2.0 \
+ -lgthread-2.0 \
+ -lglib-2.0 \
+ -lnss3 \
+ -lnssutil3 \
+ -lsmime3 \
+ -lplds4 \
+ -lplc4 \
+ -lnspr4 \
+ -lpthread \
+ -lz \
+ -lgconf-2
+
+$(builddir)/crash_cache: GYP_LDFLAGS := $(LDFLAGS_$(BUILDTYPE))
+$(builddir)/crash_cache: LIBS := $(LIBS)
+$(builddir)/crash_cache: TOOLSET := $(TOOLSET)
+$(builddir)/crash_cache: $(OBJS) $(obj).target/net/libnet.a $(obj).target/net/libnet_test_support.a $(obj).target/base/libbase.a $(obj).target/third_party/modp_b64/libmodp_b64.a $(obj).target/base/third_party/dynamic_annotations/libdynamic_annotations.a $(obj).target/base/libsymbolize.a $(obj).target/net/third_party/nss/libssl.a $(obj).target/third_party/zlib/libzlib.a $(obj).target/base/libxdg_mime.a $(obj).target/base/allocator/liballocator.a $(obj).target/third_party/libevent/libevent.a $(obj).target/base/libbase_i18n.a $(obj).target/third_party/icu/libicui18n.a $(obj).target/third_party/icu/libicuuc.a $(obj).target/third_party/icu/libicudata.a $(obj).target/build/temp_gyp/libgoogleurl.a $(obj).target/sdch/libsdch.a $(obj).target/net/libnet_base.a $(obj).target/v8/tools/gyp/libv8_snapshot.a $(obj).target/v8/tools/gyp/libv8_base.a $(obj).target/testing/libgtest.a FORCE_DO_CMD
+ $(call do_cmd,link)
+
+all_deps += $(builddir)/crash_cache
+# Add target alias
+.PHONY: crash_cache
+crash_cache: $(builddir)/crash_cache
+
+# Add executable to "all" target.
+.PHONY: all
+all: $(builddir)/crash_cache
+
diff --git a/net/data/ftp/dir-listing-ls-1-utf8.expected b/net/data/ftp/dir-listing-ls-1-utf8.expected
index bdb02ba..8e4508f 100644
--- a/net/data/ftp/dir-listing-ls-1-utf8.expected
+++ b/net/data/ftp/dir-listing-ls-1-utf8.expected
@@ -1,7 +1,7 @@
d
.
-1
-current
+1994
5
15
18
@@ -10,7 +10,7 @@
d
..
-1
-current
+1994
5
15
18
diff --git a/net/data/ftp/dir-listing-ls-1.expected b/net/data/ftp/dir-listing-ls-1.expected
index 6e78dc2..4037590 100644
--- a/net/data/ftp/dir-listing-ls-1.expected
+++ b/net/data/ftp/dir-listing-ls-1.expected
@@ -1,7 +1,7 @@
d
.
-1
-current
+1994
5
15
18
@@ -10,7 +10,7 @@
d
..
-1
-current
+1994
5
15
18
diff --git a/net/data/ftp/dir-listing-ls-10.expected b/net/data/ftp/dir-listing-ls-10.expected
index bbc4cd3..6e63cd0 100644
--- a/net/data/ftp/dir-listing-ls-10.expected
+++ b/net/data/ftp/dir-listing-ls-10.expected
@@ -37,7 +37,7 @@
d
lost+found
-1
-current
+1994
8
14
13
diff --git a/net/data/ftp/dir-listing-ls-12.expected b/net/data/ftp/dir-listing-ls-12.expected
index 761f7f4..c2f3232 100644
--- a/net/data/ftp/dir-listing-ls-12.expected
+++ b/net/data/ftp/dir-listing-ls-12.expected
@@ -37,7 +37,7 @@
d
pub
-1
-current
+1994
6
30
9
diff --git a/net/data/ftp/dir-listing-ls-13.expected b/net/data/ftp/dir-listing-ls-13.expected
index 7bbf9ad..049f3bf 100644
--- a/net/data/ftp/dir-listing-ls-13.expected
+++ b/net/data/ftp/dir-listing-ls-13.expected
@@ -10,7 +10,7 @@
d
kernels
-1
-current
+1993
11
17
17
diff --git a/net/data/ftp/dir-listing-ls-15.expected b/net/data/ftp/dir-listing-ls-15.expected
index fd064ad..c521d00 100644
--- a/net/data/ftp/dir-listing-ls-15.expected
+++ b/net/data/ftp/dir-listing-ls-15.expected
@@ -10,7 +10,7 @@
d
incoming
-1
-current
+1993
12
8
15
@@ -28,7 +28,7 @@
d
pub
-1
-current
+1994
9
25
9
diff --git a/net/data/ftp/dir-listing-ls-16.expected b/net/data/ftp/dir-listing-ls-16.expected
index 6a8fa74..90e39e6 100644
--- a/net/data/ftp/dir-listing-ls-16.expected
+++ b/net/data/ftp/dir-listing-ls-16.expected
@@ -37,7 +37,7 @@
d
test
-1
-current
+1994
11
2
15
@@ -46,7 +46,7 @@
d
tmp
-1
-current
+1993
11
25
10
diff --git a/net/data/ftp/dir-listing-ls-17 b/net/data/ftp/dir-listing-ls-17
new file mode 100644
index 0000000..c07fbf3
--- /dev/null
+++ b/net/data/ftp/dir-listing-ls-17
@@ -0,0 +1 @@
+ftpd-BSD: .: Permission denied
diff --git a/net/data/ftp/dir-listing-ls-17.expected b/net/data/ftp/dir-listing-ls-17.expected
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/net/data/ftp/dir-listing-ls-17.expected
diff --git a/net/data/ftp/dir-listing-ls-7.expected b/net/data/ftp/dir-listing-ls-7.expected
index 8f4cfe0..90c1351 100644
--- a/net/data/ftp/dir-listing-ls-7.expected
+++ b/net/data/ftp/dir-listing-ls-7.expected
@@ -10,7 +10,7 @@
d
OCU
-1
-current
+1994
10
19
13
diff --git a/net/data/ftp/dir-listing-ls-9.expected b/net/data/ftp/dir-listing-ls-9.expected
index 08bdb20..afc9791 100644
--- a/net/data/ftp/dir-listing-ls-9.expected
+++ b/net/data/ftp/dir-listing-ls-9.expected
@@ -1,7 +1,7 @@
-
Akademia Teatralna spot.mpg
174680068
-current
+1994
6
4
23
@@ -19,7 +19,7 @@
-
Zdjecia.zip
23197684
-current
+1994
6
9
13
diff --git a/net/data/ftp/dir-listing-netware-2.expected b/net/data/ftp/dir-listing-netware-2.expected
index ea7423e..3c78ff0 100644
--- a/net/data/ftp/dir-listing-netware-2.expected
+++ b/net/data/ftp/dir-listing-netware-2.expected
@@ -10,7 +10,7 @@
d
Driver
-1
-current
+1994
11
13
7
@@ -19,7 +19,7 @@
d
temp
-1
-current
+1993
11
16
15
diff --git a/net/data/proxy_resolver_v8_unittest/binding_from_global.js b/net/data/proxy_resolver_v8_unittest/binding_from_global.js
new file mode 100644
index 0000000..91bbcf2
--- /dev/null
+++ b/net/data/proxy_resolver_v8_unittest/binding_from_global.js
@@ -0,0 +1,8 @@
+// Calls a bindings outside of FindProxyForURL(). This causes the code to
+// get exercised during initialization.
+
+var x = myIpAddress();
+
+function FindProxyForURL(url, host) {
+ return "PROXY " + x + ":80";
+}
diff --git a/net/data/proxy_resolver_v8_unittest/bindings.js b/net/data/proxy_resolver_v8_unittest/bindings.js
index 3026569..7cf9f26 100644
--- a/net/data/proxy_resolver_v8_unittest/bindings.js
+++ b/net/data/proxy_resolver_v8_unittest/bindings.js
@@ -10,16 +10,25 @@
throw "exception from calling toString()";
}
+function expectEquals(expectation, actual) {
+ if (!(expectation === actual)) {
+ throw "FAIL: expected: " + expectation + ", actual: " + actual;
+ }
+}
+
function FindProxyForURL(url, host) {
// Call dnsResolve with some wonky arguments.
- dnsResolve();
- dnsResolve(null);
- dnsResolve(undefined);
- dnsResolve("");
- dnsResolve({foo: 'bar'});
- dnsResolve(fn);
- dnsResolve(['3']);
- dnsResolve("arg1", "arg2", "arg3", "arg4");
+ // Those expected to fail (because we have passed a non-string parameter)
+ // will return |null|, whereas those that have called through to the C++
+ // bindings will return '127.0.0.1'.
+ expectEquals(null, dnsResolve());
+ expectEquals(null, dnsResolve(null));
+ expectEquals(null, dnsResolve(undefined));
+ expectEquals('127.0.0.1', dnsResolve(""));
+ expectEquals(null, dnsResolve({foo: 'bar'}));
+ expectEquals(null, dnsResolve(fn));
+ expectEquals(null, dnsResolve(['3']));
+ expectEquals('127.0.0.1', dnsResolve("arg1", "arg2", "arg3", "arg4"));
// Call alert with some wonky arguments.
alert();
diff --git a/net/data/proxy_resolver_v8_unittest/international_domain_names.js b/net/data/proxy_resolver_v8_unittest/international_domain_names.js
new file mode 100644
index 0000000..546af13
--- /dev/null
+++ b/net/data/proxy_resolver_v8_unittest/international_domain_names.js
@@ -0,0 +1,16 @@
+// Try resolving hostnames containing non-ASCII characters.
+
+function FindProxyForURL(url, host) {
+ // This international hostname has a non-ASCII character. It is represented
+ // in punycode as 'xn--bcher-kva.ch'
+ var idn = 'B\u00fccher.ch';
+
+ // We disregard the actual return value -- all we care about is that on
+ // the C++ end the bindings were passed the punycode equivalent of this
+ // unicode hostname.
+ dnsResolve(idn);
+ dnsResolveEx(idn);
+
+ return "DIRECT";
+}
+
diff --git a/net/data/ssl/certificates/dod_ca_17_cert.der b/net/data/ssl/certificates/dod_ca_17_cert.der
new file mode 100644
index 0000000..50e509e
--- /dev/null
+++ b/net/data/ssl/certificates/dod_ca_17_cert.der
Binary files differ
diff --git a/net/data/ssl/certificates/dod_root_ca_2_cert.der b/net/data/ssl/certificates/dod_root_ca_2_cert.der
new file mode 100644
index 0000000..30dcdb9
--- /dev/null
+++ b/net/data/ssl/certificates/dod_root_ca_2_cert.der
Binary files differ
diff --git a/net/data/ssl/certificates/www_us_army_mil_cert.der b/net/data/ssl/certificates/www_us_army_mil_cert.der
new file mode 100644
index 0000000..bee38ff
--- /dev/null
+++ b/net/data/ssl/certificates/www_us_army_mil_cert.der
Binary files differ
diff --git a/net/data/valgrind/net_unittests.gtest-memcheck.txt b/net/data/valgrind/net_unittests.gtest-memcheck.txt
new file mode 100644
index 0000000..458ddef
--- /dev/null
+++ b/net/data/valgrind/net_unittests.gtest-memcheck.txt
@@ -0,0 +1,16 @@
+# These tests leak data intentionally, so are inappropriate for Valgrind tests.
+# Similar list in ../purify/net_unittests.exe.gtest.txt
+# TODO(dkegel): either merge the two files or keep them in sync,
+# see http://code.google.com/p/chromium/issues/detail?id=8951
+DiskCacheBackendTest.InvalidEntry
+DiskCacheBackendTest.InvalidEntryRead
+DiskCacheBackendTest.InvalidEntryWithLoad
+DiskCacheBackendTest.TrimInvalidEntry
+DiskCacheBackendTest.TrimInvalidEntry2
+DiskCacheBackendTest.InvalidEntryEnumeration
+DiskCacheBackendTest.NewEvictionInvalidEntry
+DiskCacheBackendTest.NewEvictionInvalidEntryRead
+DiskCacheBackendTest.NewEvictionInvalidEntryWithLoad
+DiskCacheBackendTest.NewEvictionTrimInvalidEntry
+DiskCacheBackendTest.NewEvictionTrimInvalidEntry2
+DiskCacheBackendTest.NewEvictionInvalidEntryEnumeration
diff --git a/net/data/valgrind/net_unittests.gtest-tsan_mac.txt b/net/data/valgrind/net_unittests.gtest-tsan_mac.txt
index 10e23a5..4362060 100644
--- a/net/data/valgrind/net_unittests.gtest-tsan_mac.txt
+++ b/net/data/valgrind/net_unittests.gtest-tsan_mac.txt
@@ -2,27 +2,23 @@
# Tsan still tends to hang on these tests unexpectedly on Mac OS.
# (see https://bugs.kde.org/show_bug.cgi?id=192634 and
# http://code.google.com/p/data-race-test/issues/detail?id=11)
-FileStreamTest.*Async*
-HostResolverImplTest.*
-TestCompletionCallbackTest.Simple
-FtpNetworkTransactionTest.*
HttpNetworkLayerTest.GET
HttpNetworkLayerTest.SimpleGET
-HttpNetworkTransactionTest.*
-ProxyScriptFetcherTest.*
-SOCKS5ClientSocketTest.*
-SOCKSClientSocketTest.*
-TCPClientSocketPoolTest.*
-URLRequestTest.*
-URLRequestTestHTTP.*
-URLRequestTestFTP.*
-SSLClientSocketTest.*
-DiskCacheTest.*
-DiskCacheEntryTest.*
-FlipNetworkTransactionTest.*
-SocketStreamTest.BasicAuthProxy
# WebSocketTest tests are extraordinary slow under ThreadSanitizer,
# (see http://crbug.com/25392)
# TODO(glider): investigate this.
WebSocketTest.*
+
+# These tests die because of unhandled shm_unlink call
+# (see http://crbug.com/36657)
+DiskCacheBackendTest.*InvalidRankings*
+DiskCacheBackendTest.*DisableSuccess*
+DiskCacheBackendTest.*DisableFailure*
+
+# Strange reports from __NSThread__main__ appeared with the new TSan binaries
+# See http://crbug.com/38926
+DirectoryLister*
+
+# See http://crbug.com/44570
+HttpNetworkTransactionTest.StopsReading204
diff --git a/net/data/valgrind/net_unittests.gtest-tsan_win32.txt b/net/data/valgrind/net_unittests.gtest-tsan_win32.txt
new file mode 100644
index 0000000..52de2b7
--- /dev/null
+++ b/net/data/valgrind/net_unittests.gtest-tsan_win32.txt
@@ -0,0 +1,26 @@
+# These tests fail due to unknown reasons
+# TODO(timurrrr): investigate
+CookieMonsterTest.TestLastAccess
+SpdyNetwork*Error*
+SpdyNetwork*Get*
+SpdyNetworkTransactionTest.SynReplyHeadersVary
+X509CertificateTest.UnoSoftCertParsing
+URLRequestTest.DoNotSaveCookies
+URLRequestTest.QuitTest
+DiskCacheEntryTest.*HugeSparse*
+
+# See http://crbug.com/46647
+DiskCacheBackendTest.*
+
+# See http://crbug.com/47836
+ClientSocketPoolBaseTest.CancelPendingSocketAtSocketLimit
+
+#########################################
+# These tests fail if you don't have our SSL certificate installed.
+# Please see http://dev.chromium.org/developers/testing#TOC-SSL-tests
+# if you think you want to un-comment one of the following lines.
+#SSLClientSocketTest.*
+#URLRequestTest*
+#HTTPSRequestTest.*
+#X509CertificateTest.*
+#ProxyScriptFetcherTest.*
diff --git a/net/data/valgrind/net_unittests.gtest.txt b/net/data/valgrind/net_unittests.gtest.txt
index 458ddef..0af617c 100644
--- a/net/data/valgrind/net_unittests.gtest.txt
+++ b/net/data/valgrind/net_unittests.gtest.txt
@@ -1,16 +1,6 @@
-# These tests leak data intentionally, so are inappropriate for Valgrind tests.
-# Similar list in ../purify/net_unittests.exe.gtest.txt
-# TODO(dkegel): either merge the two files or keep them in sync,
-# see http://code.google.com/p/chromium/issues/detail?id=8951
-DiskCacheBackendTest.InvalidEntry
-DiskCacheBackendTest.InvalidEntryRead
-DiskCacheBackendTest.InvalidEntryWithLoad
-DiskCacheBackendTest.TrimInvalidEntry
-DiskCacheBackendTest.TrimInvalidEntry2
-DiskCacheBackendTest.InvalidEntryEnumeration
-DiskCacheBackendTest.NewEvictionInvalidEntry
-DiskCacheBackendTest.NewEvictionInvalidEntryRead
-DiskCacheBackendTest.NewEvictionInvalidEntryWithLoad
-DiskCacheBackendTest.NewEvictionTrimInvalidEntry
-DiskCacheBackendTest.NewEvictionTrimInvalidEntry2
-DiskCacheBackendTest.NewEvictionInvalidEntryEnumeration
+# Very slow under Valgrind.
+KeygenHandlerTest.*SmokeTest
+KeygenHandlerTest.*ConcurrencyTest
+
+# Fails Valgrind with varying stack traces. http://crbug.com/43179
+SpdyNetworkTransactionTest.PostWithEarlySynReply
diff --git a/net/data/valgrind/net_unittests.gtest_linux.txt b/net/data/valgrind/net_unittests.gtest_linux.txt
new file mode 100644
index 0000000..1b69eec
--- /dev/null
+++ b/net/data/valgrind/net_unittests.gtest_linux.txt
@@ -0,0 +1,3 @@
+# These tests fail due to certificate errors; see http://crbug.com/36770
+HTTPSRequestTest.HTTPSMismatchedTest
+SSLClientSocketTest.ConnectMismatched
diff --git a/net/data/valgrind/net_unittests.gtest_mac.txt b/net/data/valgrind/net_unittests.gtest_mac.txt
new file mode 100644
index 0000000..ec7da13
--- /dev/null
+++ b/net/data/valgrind/net_unittests.gtest_mac.txt
@@ -0,0 +1,2 @@
+# Very slow under Valgrind, (see <http://crbug.com/37289>).
+KeygenHandlerTest.FLAKY_SmokeTest
diff --git a/net/data/valgrind/net_unittests.gtest_wine.txt b/net/data/valgrind/net_unittests.gtest_wine.txt
new file mode 100644
index 0000000..2492f86
--- /dev/null
+++ b/net/data/valgrind/net_unittests.gtest_wine.txt
@@ -0,0 +1,49 @@
+# crash Crashes in Wine
+# crash-valgrind Crashes in Wine + Valgrind
+# dontcare Safe to ignore
+# dontcare-hangwin Ignore, hangs on Windows too
+# dontcare-winfail Ignore, fails on Windows too
+# dontcare-flaky Ignore, flaky test
+# dontcare-hang Ignore, hangs we don't care about
+# fail Fails, needs triaging or needs to be fixed
+# fail-valgrind Fails only under Valgrind
+# fail_wine_vmware Fails in Wine under VMware? TODO(dank) clarify
+# flaky-valgrind Flaky under Valgrind, needs investigation
+# hang Test that hangs for some reason
+# hang-valgrind Test that hangs under valgrind, or just takes too long
+
+# fail
+http://bugs.winehq.org/show_bug.cgi?id=20748
+SSLClientSocketTest.Read_Interrupted
+
+# fail
+# https/ssl failing on the bot, bad Wine? TODO(thestig): investigate
+HTTPSRequestTest.HTTPSExpiredTest
+
+# fail
+# https/ssl failing on the bot, bad Wine? TODO(thestig): investigate
+HTTPSRequestTest.HTTPSGetTest
+
+# fail
+# https/ssl failing on the bot, bad Wine? TODO(thestig): investigate
+HTTPSRequestTest.HTTPSMismatchedTest
+
+# fail
+# https/ssl failing on the bot, bad Wine? TODO(thestig): investigate
+SSLClientSocketTest.Connect
+
+# fail
+# https/ssl failing on the bot, bad Wine? TODO(thestig): investigate
+SSLClientSocketTest.Read
+
+# fail
+# https/ssl failing on the bot, bad Wine? TODO(thestig): investigate
+SSLClientSocketTest.Read_FullDuplex
+
+# fail
+# https/ssl failing on the bot, bad Wine? TODO(thestig): investigate
+SSLClientSocketTest.Read_SmallChunks
+
+# fail
+# https/ssl failing on the bot, bad Wine? TODO(thestig): investigate
+URLRequestTestHTTP.HTTPSToHTTPRedirectNoRefererTest
diff --git a/net/disk_cache/addr.h b/net/disk_cache/addr.h
index a504019..938a442 100644
--- a/net/disk_cache/addr.h
+++ b/net/disk_cache/addr.h
@@ -23,7 +23,7 @@
const int kMaxBlockSize = 4096 * 4;
const int kMaxBlockFile = 255;
const int kMaxNumBlocks = 4;
-const int kFirstAdditionlBlockFile = 4;
+const int kFirstAdditionalBlockFile = 4;
// Defines a storage address for a cache record
//
diff --git a/net/disk_cache/backend_impl.cc b/net/disk_cache/backend_impl.cc
index 3b3ca6a..13b17f4 100644
--- a/net/disk_cache/backend_impl.cc
+++ b/net/disk_cache/backend_impl.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Copyright (c) 2006-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.
@@ -20,6 +20,7 @@
#include "net/disk_cache/errors.h"
#include "net/disk_cache/hash.h"
#include "net/disk_cache/file.h"
+#include "net/disk_cache/mem_backend_impl.h"
// This has to be defined before including histogram_macros.h from this file.
#define NET_DISK_CACHE_BACKEND_IMPL_CC_
@@ -27,6 +28,7 @@
using base::Time;
using base::TimeDelta;
+using base::TimeTicks;
namespace {
@@ -83,7 +85,7 @@
private:
FilePath path_;
std::string name_;
- DISALLOW_EVIL_CONSTRUCTORS(CleanupTask);
+ DISALLOW_COPY_AND_ASSIGN(CleanupTask);
};
void CleanupTask::Run() {
@@ -129,12 +131,7 @@
return false;
}
-#if defined(OS_WIN)
WorkerPool::PostTask(FROM_HERE, new CleanupTask(path, name_str), true);
-#elif defined(OS_POSIX)
- // TODO(rvargas): Use the worker pool.
- MessageLoop::current()->PostTask(FROM_HERE, new CleanupTask(path, name_str));
-#endif
return true;
}
@@ -165,42 +162,153 @@
trial1->AppendGroup(group1, FieldTrial::kAllRemainingProbability);
}
+// ------------------------------------------------------------------------
+
+// This class takes care of building an instance of the backend.
+class CacheCreator {
+ public:
+ CacheCreator(const FilePath& path, bool force, int max_bytes,
+ net::CacheType type, uint32 flags,
+ base::MessageLoopProxy* thread, disk_cache::Backend** backend,
+ net::CompletionCallback* callback)
+ : path_(path), force_(force), retry_(false), max_bytes_(max_bytes),
+ type_(type), flags_(flags), thread_(thread), backend_(backend),
+ callback_(callback), cache_(NULL),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ my_callback_(this, &CacheCreator::OnIOComplete)) {
+ }
+ ~CacheCreator() {}
+
+ // Creates the backend.
+ int Run();
+
+ // Callback implementation.
+ void OnIOComplete(int result);
+
+ private:
+ void DoCallback(int result);
+
+ const FilePath& path_;
+ bool force_;
+ bool retry_;
+ int max_bytes_;
+ net::CacheType type_;
+ uint32 flags_;
+ scoped_refptr<base::MessageLoopProxy> thread_;
+ disk_cache::Backend** backend_;
+ net::CompletionCallback* callback_;
+ disk_cache::BackendImpl* cache_;
+ net::CompletionCallbackImpl<CacheCreator> my_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(CacheCreator);
+};
+
+int CacheCreator::Run() {
+ cache_ = new disk_cache::BackendImpl(path_, thread_);
+ cache_->SetMaxSize(max_bytes_);
+ cache_->SetType(type_);
+ cache_->SetFlags(flags_);
+ int rv = cache_->Init(&my_callback_);
+ DCHECK_EQ(net::ERR_IO_PENDING, rv);
+ return rv;
+}
+
+void CacheCreator::OnIOComplete(int result) {
+ if (result == net::OK || !force_ || retry_)
+ return DoCallback(result);
+
+ // This is a failure and we are supposed to try again, so delete the object,
+ // delete all the files, and try again.
+ retry_ = true;
+ delete cache_;
+ cache_ = NULL;
+ if (!DelayedCacheCleanup(path_))
+ return DoCallback(result);
+
+ // The worker thread will start deleting files soon, but the original folder
+ // is not there anymore... let's create a new set of files.
+ int rv = Run();
+ DCHECK_EQ(net::ERR_IO_PENDING, rv);
+}
+
+void CacheCreator::DoCallback(int result) {
+ DCHECK_NE(net::ERR_IO_PENDING, result);
+ if (result == net::OK) {
+ *backend_ = cache_;
+ } else {
+ LOG(ERROR) << "Unable to create cache";
+ *backend_ = NULL;
+ delete cache_;
+ }
+ callback_->Run(result);
+ delete this;
+}
+
+// ------------------------------------------------------------------------
+
+// A task to perform final cleanup on the background thread.
+class FinalCleanup : public Task {
+ public:
+ explicit FinalCleanup(disk_cache::BackendImpl* backend) : backend_(backend) {}
+ ~FinalCleanup() {}
+
+ virtual void Run();
+ private:
+ disk_cache::BackendImpl* backend_;
+ DISALLOW_EVIL_CONSTRUCTORS(FinalCleanup);
+};
+
+void FinalCleanup::Run() {
+ backend_->StartCleanup();
+}
+
} // namespace
// ------------------------------------------------------------------------
namespace disk_cache {
-Backend* CreateCacheBackend(const FilePath& full_path, bool force,
- int max_bytes, net::CacheType type) {
- // Create a backend without extra flags.
- return BackendImpl::CreateBackend(full_path, force, max_bytes, type, kNone);
+int CreateCacheBackend(net::CacheType type, const FilePath& path, int max_bytes,
+ bool force, base::MessageLoopProxy* thread,
+ Backend** backend, CompletionCallback* callback) {
+ DCHECK(callback);
+ if (type == net::MEMORY_CACHE) {
+ *backend = MemBackendImpl::CreateBackend(max_bytes);
+ return *backend ? net::OK : net::ERR_FAILED;
+ }
+ DCHECK(thread);
+
+ return BackendImpl::CreateBackend(path, force, max_bytes, type, kNone, thread,
+ backend, callback);
}
+// Returns the preferred maximum number of bytes for the cache given the
+// number of available bytes.
int PreferedCacheSize(int64 available) {
- // If there is not enough space to use kDefaultCacheSize, use 80% of the
- // available space.
- if (available < kDefaultCacheSize)
+ // Return 80% of the available space if there is not enough space to use
+ // kDefaultCacheSize.
+ if (available < kDefaultCacheSize * 10 / 8)
return static_cast<int32>(available * 8 / 10);
- // Don't use more than 10% of the available space.
- if (available < 10 * kDefaultCacheSize)
+ // Return kDefaultCacheSize if it uses 80% to 10% of the available space.
+ if (available < kDefaultCacheSize * 10)
return kDefaultCacheSize;
- // Use 10% of the free space until we reach 2.5 * kDefaultCacheSize.
+ // Return 10% of the available space if the target size
+ // (2.5 * kDefaultCacheSize) is more than 10%.
if (available < static_cast<int64>(kDefaultCacheSize) * 25)
return static_cast<int32>(available / 10);
- // After reaching our target size (2.5 * kDefaultCacheSize), attempt to use
- // 1% of the availabe space.
- if (available < static_cast<int64>(kDefaultCacheSize) * 100)
+ // Return the target size (2.5 * kDefaultCacheSize) if it uses 10% to 1%
+ // of the available space.
+ if (available < static_cast<int64>(kDefaultCacheSize) * 250)
return kDefaultCacheSize * 5 / 2;
- int64 one_percent = available / 100;
- if (one_percent > kint32max)
- return kint32max;
+ // Return 1% of the available space if it does not exceed kint32max.
+ if (available < static_cast<int64>(kint32max) * 100)
+ return static_cast<int32>(available / 100);
- return static_cast<int32>(one_percent);
+ return kint32max;
}
// ------------------------------------------------------------------------
@@ -215,123 +323,33 @@
// desired path) cannot be created.
//
// Static.
-Backend* BackendImpl::CreateBackend(const FilePath& full_path, bool force,
- int max_bytes, net::CacheType type,
- BackendFlags flags) {
- BackendImpl* cache = new BackendImpl(full_path);
- cache->SetMaxSize(max_bytes);
- cache->SetType(type);
- cache->SetFlags(flags);
- if (cache->Init())
- return cache;
-
- delete cache;
- if (!force)
- return NULL;
-
- if (!DelayedCacheCleanup(full_path))
- return NULL;
-
- // The worker thread will start deleting files soon, but the original folder
- // is not there anymore... let's create a new set of files.
- cache = new BackendImpl(full_path);
- cache->SetMaxSize(max_bytes);
- cache->SetType(type);
- cache->SetFlags(flags);
- if (cache->Init())
- return cache;
-
- delete cache;
- LOG(ERROR) << "Unable to create cache";
- return NULL;
+int BackendImpl::CreateBackend(const FilePath& full_path, bool force,
+ int max_bytes, net::CacheType type,
+ uint32 flags, base::MessageLoopProxy* thread,
+ Backend** backend,
+ CompletionCallback* callback) {
+ CacheCreator* creator = new CacheCreator(full_path, force, max_bytes, type,
+ flags, thread, backend, callback);
+ // This object will self-destroy when finished.
+ return creator->Run();
}
-bool BackendImpl::Init() {
- DCHECK(!init_);
- if (init_)
- return false;
-
- bool create_files = false;
- if (!InitBackingStore(&create_files)) {
- ReportError(ERR_STORAGE_ERROR);
- return false;
- }
-
- num_refs_ = num_pending_io_ = max_refs_ = 0;
-
- if (!restarted_) {
- trace_object_ = TraceObject::GetTraceObject();
- // Create a recurrent timer of 30 secs.
- int timer_delay = unit_test_ ? 1000 : 30000;
- timer_.Start(TimeDelta::FromMilliseconds(timer_delay), this,
- &BackendImpl::OnStatsTimer);
- }
-
- init_ = true;
-
- if (data_->header.experiment != 0 && cache_type_ != net::DISK_CACHE) {
- // No experiment for other caches.
- return false;
- }
-
- if (!(user_flags_ & disk_cache::kNoRandom)) {
- // The unit test controls directly what to test.
- if (!InitExperiment(&data_->header.experiment))
- return false;
-
- new_eviction_ = (cache_type_ == net::DISK_CACHE);
- }
-
- if (!CheckIndex()) {
- ReportError(ERR_INIT_FAILED);
- return false;
- }
-
- // We don't care if the value overflows. The only thing we care about is that
- // the id cannot be zero, because that value is used as "not dirty".
- // Increasing the value once per second gives us many years before a we start
- // having collisions.
- data_->header.this_id++;
- if (!data_->header.this_id)
- data_->header.this_id++;
-
- if (data_->header.crash) {
- ReportError(ERR_PREVIOUS_CRASH);
- } else {
- ReportError(0);
- data_->header.crash = 1;
- }
-
- if (!block_files_.Init(create_files))
- return false;
-
- // stats_ and rankings_ may end up calling back to us so we better be enabled.
- disabled_ = false;
- if (!stats_.Init(this, &data_->header.stats))
- return false;
-
- disabled_ = !rankings_.Init(this, new_eviction_);
- eviction_.Init(this);
-
- // Setup load-time data only for the main cache.
- if (cache_type() == net::DISK_CACHE)
- SetFieldTrialInfo(GetSizeGroup());
-
- return !disabled_;
+int BackendImpl::Init(CompletionCallback* callback) {
+ background_queue_.Init(callback);
+ return net::ERR_IO_PENDING;
}
BackendImpl::~BackendImpl() {
- Trace("Backend destructor");
- if (!init_)
- return;
+ background_queue_.WaitForPendingIO();
- if (data_)
- data_->header.crash = 0;
-
- timer_.Stop();
-
- File::WaitForPendingIO(&num_pending_io_);
- DCHECK(!num_refs_);
+ if (background_queue_.BackgroundIsCurrentThread()) {
+ // Unit tests may use the same thread for everything.
+ CleanupCache();
+ } else {
+ background_queue_.background_thread()->PostTask(FROM_HERE,
+ new FinalCleanup(this));
+ done_.Wait();
+ }
}
// ------------------------------------------------------------------------
@@ -351,271 +369,57 @@
return not_deleted;
}
-bool BackendImpl::OpenEntry(const std::string& key, Entry** entry) {
- if (disabled_)
- return false;
-
- Time start = Time::Now();
- uint32 hash = Hash(key);
-
- EntryImpl* cache_entry = MatchEntry(key, hash, false);
- if (!cache_entry) {
- stats_.OnEvent(Stats::OPEN_MISS);
- return false;
- }
-
- if (ENTRY_NORMAL != cache_entry->entry()->Data()->state) {
- // The entry was already evicted.
- cache_entry->Release();
- stats_.OnEvent(Stats::OPEN_MISS);
- return false;
- }
-
- eviction_.OnOpenEntry(cache_entry);
- DCHECK(entry);
- *entry = cache_entry;
-
- CACHE_UMA(AGE_MS, "OpenTime", GetSizeGroup(), start);
- stats_.OnEvent(Stats::OPEN_HIT);
- return true;
-}
-
int BackendImpl::OpenEntry(const std::string& key, Entry** entry,
CompletionCallback* callback) {
- if (OpenEntry(key, entry))
- return net::OK;
-
- return net::ERR_FAILED;
-}
-
-bool BackendImpl::CreateEntry(const std::string& key, Entry** entry) {
- if (disabled_ || key.empty())
- return false;
-
- DCHECK(entry);
- *entry = NULL;
-
- Time start = Time::Now();
- uint32 hash = Hash(key);
-
- scoped_refptr<EntryImpl> parent;
- Addr entry_address(data_->table[hash & mask_]);
- if (entry_address.is_initialized()) {
- // We have an entry already. It could be the one we are looking for, or just
- // a hash conflict.
- EntryImpl* old_entry = MatchEntry(key, hash, false);
- if (old_entry)
- return ResurrectEntry(old_entry, entry);
-
- EntryImpl* parent_entry = MatchEntry(key, hash, true);
- if (!parent_entry) {
- NOTREACHED();
- return false;
- }
- parent.swap(&parent_entry);
- }
-
- int num_blocks;
- size_t key1_len = sizeof(EntryStore) - offsetof(EntryStore, key);
- if (key.size() < key1_len ||
- key.size() > static_cast<size_t>(kMaxInternalKeyLength))
- num_blocks = 1;
- else
- num_blocks = static_cast<int>((key.size() - key1_len) / 256 + 2);
-
- if (!block_files_.CreateBlock(BLOCK_256, num_blocks, &entry_address)) {
- LOG(ERROR) << "Create entry failed " << key.c_str();
- stats_.OnEvent(Stats::CREATE_ERROR);
- return false;
- }
-
- Addr node_address(0);
- if (!block_files_.CreateBlock(RANKINGS, 1, &node_address)) {
- block_files_.DeleteBlock(entry_address, false);
- LOG(ERROR) << "Create entry failed " << key.c_str();
- stats_.OnEvent(Stats::CREATE_ERROR);
- return false;
- }
-
- scoped_refptr<EntryImpl> cache_entry(new EntryImpl(this, entry_address));
- IncreaseNumRefs();
-
- if (!cache_entry->CreateEntry(node_address, key, hash)) {
- block_files_.DeleteBlock(entry_address, false);
- block_files_.DeleteBlock(node_address, false);
- LOG(ERROR) << "Create entry failed " << key.c_str();
- stats_.OnEvent(Stats::CREATE_ERROR);
- return false;
- }
-
- // We are not failing the operation; let's add this to the map.
- open_entries_[entry_address.value()] = cache_entry;
-
- if (parent.get())
- parent->SetNextAddress(entry_address);
-
- block_files_.GetFile(entry_address)->Store(cache_entry->entry());
- block_files_.GetFile(node_address)->Store(cache_entry->rankings());
-
- IncreaseNumEntries();
- eviction_.OnCreateEntry(cache_entry);
- if (!parent.get())
- data_->table[hash & mask_] = entry_address.value();
-
- cache_entry.swap(reinterpret_cast<EntryImpl**>(entry));
-
- CACHE_UMA(AGE_MS, "CreateTime", GetSizeGroup(), start);
- stats_.OnEvent(Stats::CREATE_HIT);
- Trace("create entry hit ");
- return true;
+ DCHECK(callback);
+ background_queue_.OpenEntry(key, entry, callback);
+ return net::ERR_IO_PENDING;
}
int BackendImpl::CreateEntry(const std::string& key, Entry** entry,
CompletionCallback* callback) {
- if (CreateEntry(key, entry))
- return net::OK;
-
- return net::ERR_FAILED;
-}
-
-bool BackendImpl::DoomEntry(const std::string& key) {
- if (disabled_)
- return false;
-
- Entry* entry;
- if (!OpenEntry(key, &entry))
- return false;
-
- // Note that you'd think you could just pass &entry_impl to OpenEntry,
- // but that triggers strict aliasing problems with gcc.
- EntryImpl* entry_impl = reinterpret_cast<EntryImpl*>(entry);
- entry_impl->Doom();
- entry_impl->Release();
- return true;
+ DCHECK(callback);
+ background_queue_.CreateEntry(key, entry, callback);
+ return net::ERR_IO_PENDING;
}
int BackendImpl::DoomEntry(const std::string& key,
CompletionCallback* callback) {
- if (DoomEntry(key))
- return net::OK;
-
- return net::ERR_FAILED;
-}
-
-bool BackendImpl::DoomAllEntries() {
- if (!num_refs_) {
- PrepareForRestart();
- DeleteCache(path_, false);
- return Init();
- } else {
- if (disabled_)
- return false;
-
- eviction_.TrimCache(true);
- stats_.OnEvent(Stats::DOOM_CACHE);
- return true;
- }
+ DCHECK(callback);
+ background_queue_.DoomEntry(key, callback);
+ return net::ERR_IO_PENDING;
}
int BackendImpl::DoomAllEntries(CompletionCallback* callback) {
- if (DoomAllEntries())
- return net::OK;
-
- return net::ERR_FAILED;
-}
-
-bool BackendImpl::DoomEntriesBetween(const Time initial_time,
- const Time end_time) {
- if (end_time.is_null())
- return DoomEntriesSince(initial_time);
-
- DCHECK(end_time >= initial_time);
-
- if (disabled_)
- return false;
-
- Entry* node, *next;
- void* iter = NULL;
- if (!OpenNextEntry(&iter, &next))
- return true;
-
- while (next) {
- node = next;
- if (!OpenNextEntry(&iter, &next))
- next = NULL;
-
- if (node->GetLastUsed() >= initial_time &&
- node->GetLastUsed() < end_time) {
- node->Doom();
- } else if (node->GetLastUsed() < initial_time) {
- if (next)
- next->Close();
- next = NULL;
- EndEnumeration(&iter);
- }
-
- node->Close();
- }
-
- return true;
+ DCHECK(callback);
+ background_queue_.DoomAllEntries(callback);
+ return net::ERR_IO_PENDING;
}
int BackendImpl::DoomEntriesBetween(const base::Time initial_time,
const base::Time end_time,
CompletionCallback* callback) {
- if (DoomEntriesBetween(initial_time, end_time))
- return net::OK;
-
- return net::ERR_FAILED;
-}
-
-// We use OpenNextEntry to retrieve elements from the cache, until we get
-// entries that are too old.
-bool BackendImpl::DoomEntriesSince(const Time initial_time) {
- if (disabled_)
- return false;
-
- for (;;) {
- Entry* entry;
- void* iter = NULL;
- if (!OpenNextEntry(&iter, &entry))
- return true;
-
- if (initial_time > entry->GetLastUsed()) {
- entry->Close();
- EndEnumeration(&iter);
- return true;
- }
-
- entry->Doom();
- entry->Close();
- EndEnumeration(&iter); // Dooming the entry invalidates the iterator.
- }
+ DCHECK(callback);
+ background_queue_.DoomEntriesBetween(initial_time, end_time, callback);
+ return net::ERR_IO_PENDING;
}
int BackendImpl::DoomEntriesSince(const base::Time initial_time,
CompletionCallback* callback) {
- if (DoomEntriesSince(initial_time))
- return net::OK;
-
- return net::ERR_FAILED;
-}
-
-bool BackendImpl::OpenNextEntry(void** iter, Entry** next_entry) {
- return OpenFollowingEntry(true, iter, next_entry);
+ DCHECK(callback);
+ background_queue_.DoomEntriesSince(initial_time, callback);
+ return net::ERR_IO_PENDING;
}
int BackendImpl::OpenNextEntry(void** iter, Entry** next_entry,
CompletionCallback* callback) {
- if (OpenNextEntry(iter, next_entry))
- return net::OK;
-
- return net::ERR_FAILED;
+ DCHECK(callback);
+ background_queue_.OpenNextEntry(iter, next_entry, callback);
+ return net::ERR_IO_PENDING;
}
void BackendImpl::EndEnumeration(void** iter) {
- scoped_ptr<Rankings::Iterator> iterator(
- reinterpret_cast<Rankings::Iterator*>(*iter));
+ background_queue_.EndEnumeration(*iter);
*iter = NULL;
}
@@ -646,6 +450,342 @@
// ------------------------------------------------------------------------
+int BackendImpl::SyncInit() {
+ DCHECK(!init_);
+ if (init_)
+ return net::ERR_FAILED;
+
+ bool create_files = false;
+ if (!InitBackingStore(&create_files)) {
+ ReportError(ERR_STORAGE_ERROR);
+ return net::ERR_FAILED;
+ }
+
+ num_refs_ = num_pending_io_ = max_refs_ = 0;
+ entry_count_ = byte_count_ = 0;
+
+ if (!restarted_) {
+ trace_object_ = TraceObject::GetTraceObject();
+ // Create a recurrent timer of 30 secs.
+ int timer_delay = unit_test_ ? 1000 : 30000;
+ timer_.Start(TimeDelta::FromMilliseconds(timer_delay), this,
+ &BackendImpl::OnStatsTimer);
+ }
+
+ init_ = true;
+
+ if (data_->header.experiment != 0 && cache_type_ != net::DISK_CACHE) {
+ // No experiment for other caches.
+ return net::ERR_FAILED;
+ }
+
+ if (!(user_flags_ & disk_cache::kNoRandom)) {
+ // The unit test controls directly what to test.
+ if (!InitExperiment(&data_->header.experiment))
+ return net::ERR_FAILED;
+
+ new_eviction_ = (cache_type_ == net::DISK_CACHE);
+ }
+
+ if (!CheckIndex()) {
+ ReportError(ERR_INIT_FAILED);
+ return net::ERR_FAILED;
+ }
+
+ // We don't care if the value overflows. The only thing we care about is that
+ // the id cannot be zero, because that value is used as "not dirty".
+ // Increasing the value once per second gives us many years before we start
+ // having collisions.
+ data_->header.this_id++;
+ if (!data_->header.this_id)
+ data_->header.this_id++;
+
+ if (data_->header.crash) {
+ ReportError(ERR_PREVIOUS_CRASH);
+ } else {
+ ReportError(0);
+ data_->header.crash = 1;
+ }
+
+ if (!block_files_.Init(create_files))
+ return net::ERR_FAILED;
+
+ // stats_ and rankings_ may end up calling back to us so we better be enabled.
+ disabled_ = false;
+ if (!stats_.Init(this, &data_->header.stats))
+ return net::ERR_FAILED;
+
+ disabled_ = !rankings_.Init(this, new_eviction_);
+ eviction_.Init(this);
+
+ // Setup load-time data only for the main cache.
+ if (cache_type() == net::DISK_CACHE)
+ SetFieldTrialInfo(GetSizeGroup());
+
+ return disabled_ ? net::ERR_FAILED : net::OK;
+}
+
+void BackendImpl::StartCleanup() {
+ Trace("Backend StartCleanup");
+ eviction_.Stop();
+
+ // Give a chance for any posted evictions to be discarded.
+ MessageLoop::current()->PostTask(FROM_HERE,
+ factory_.NewRunnableMethod(&BackendImpl::CleanupCache));
+}
+
+void BackendImpl::CleanupCache() {
+ Trace("Backend Cleanup");
+ if (init_) {
+ if (data_)
+ data_->header.crash = 0;
+
+ timer_.Stop();
+ File::WaitForPendingIO(&num_pending_io_);
+ DCHECK(!num_refs_);
+ }
+ factory_.RevokeAll();
+ done_.Signal();
+}
+
+// ------------------------------------------------------------------------
+
+int BackendImpl::OpenPrevEntry(void** iter, Entry** prev_entry,
+ CompletionCallback* callback) {
+ DCHECK(callback);
+ background_queue_.OpenPrevEntry(iter, prev_entry, callback);
+ return net::ERR_IO_PENDING;
+}
+
+int BackendImpl::SyncOpenEntry(const std::string& key, Entry** entry) {
+ DCHECK(entry);
+ *entry = OpenEntryImpl(key);
+ return (*entry) ? net::OK : net::ERR_FAILED;
+}
+
+int BackendImpl::SyncCreateEntry(const std::string& key, Entry** entry) {
+ DCHECK(entry);
+ *entry = CreateEntryImpl(key);
+ return (*entry) ? net::OK : net::ERR_FAILED;
+}
+
+int BackendImpl::SyncDoomEntry(const std::string& key) {
+ if (disabled_)
+ return net::ERR_FAILED;
+
+ EntryImpl* entry = OpenEntryImpl(key);
+ if (!entry)
+ return net::ERR_FAILED;
+
+ entry->DoomImpl();
+ entry->Release();
+ return net::OK;
+}
+
+int BackendImpl::SyncDoomAllEntries() {
+ if (!num_refs_) {
+ PrepareForRestart();
+ DeleteCache(path_, false);
+ return SyncInit();
+ } else {
+ if (disabled_)
+ return net::ERR_FAILED;
+
+ eviction_.TrimCache(true);
+ stats_.OnEvent(Stats::DOOM_CACHE);
+ return net::OK;
+ }
+}
+
+int BackendImpl::SyncDoomEntriesBetween(const base::Time initial_time,
+ const base::Time end_time) {
+ if (end_time.is_null())
+ return SyncDoomEntriesSince(initial_time);
+
+ DCHECK(end_time >= initial_time);
+
+ if (disabled_)
+ return net::ERR_FAILED;
+
+ EntryImpl* node;
+ void* iter = NULL;
+ EntryImpl* next = OpenNextEntryImpl(&iter);
+ if (!next)
+ return net::OK;
+
+ while (next) {
+ node = next;
+ next = OpenNextEntryImpl(&iter);
+
+ if (node->GetLastUsed() >= initial_time &&
+ node->GetLastUsed() < end_time) {
+ node->DoomImpl();
+ } else if (node->GetLastUsed() < initial_time) {
+ if (next)
+ next->Release();
+ next = NULL;
+ SyncEndEnumeration(iter);
+ }
+
+ node->Release();
+ }
+
+ return net::OK;
+}
+
+// We use OpenNextEntryImpl to retrieve elements from the cache, until we get
+// entries that are too old.
+int BackendImpl::SyncDoomEntriesSince(const base::Time initial_time) {
+ if (disabled_)
+ return net::ERR_FAILED;
+
+ for (;;) {
+ void* iter = NULL;
+ EntryImpl* entry = OpenNextEntryImpl(&iter);
+ if (!entry)
+ return net::OK;
+
+ if (initial_time > entry->GetLastUsed()) {
+ entry->Release();
+ SyncEndEnumeration(iter);
+ return net::OK;
+ }
+
+ entry->DoomImpl();
+ entry->Release();
+ SyncEndEnumeration(iter); // Dooming the entry invalidates the iterator.
+ }
+}
+
+int BackendImpl::SyncOpenNextEntry(void** iter, Entry** next_entry) {
+ *next_entry = OpenNextEntryImpl(iter);
+ return (*next_entry) ? net::OK : net::ERR_FAILED;
+}
+
+int BackendImpl::SyncOpenPrevEntry(void** iter, Entry** prev_entry) {
+ *prev_entry = OpenPrevEntryImpl(iter);
+ return (*prev_entry) ? net::OK : net::ERR_FAILED;
+}
+
+void BackendImpl::SyncEndEnumeration(void* iter) {
+ scoped_ptr<Rankings::Iterator> iterator(
+ reinterpret_cast<Rankings::Iterator*>(iter));
+}
+
+EntryImpl* BackendImpl::OpenEntryImpl(const std::string& key) {
+ if (disabled_)
+ return NULL;
+
+ TimeTicks start = TimeTicks::Now();
+ uint32 hash = Hash(key);
+
+ EntryImpl* cache_entry = MatchEntry(key, hash, false);
+ if (!cache_entry) {
+ stats_.OnEvent(Stats::OPEN_MISS);
+ return NULL;
+ }
+
+ if (ENTRY_NORMAL != cache_entry->entry()->Data()->state) {
+ // The entry was already evicted.
+ cache_entry->Release();
+ stats_.OnEvent(Stats::OPEN_MISS);
+ return NULL;
+ }
+
+ eviction_.OnOpenEntry(cache_entry);
+ entry_count_++;
+
+ CACHE_UMA(AGE_MS, "OpenTime", GetSizeGroup(), start);
+ stats_.OnEvent(Stats::OPEN_HIT);
+ return cache_entry;
+}
+
+EntryImpl* BackendImpl::CreateEntryImpl(const std::string& key) {
+ if (disabled_ || key.empty())
+ return NULL;
+
+ TimeTicks start = TimeTicks::Now();
+ uint32 hash = Hash(key);
+
+ scoped_refptr<EntryImpl> parent;
+ Addr entry_address(data_->table[hash & mask_]);
+ if (entry_address.is_initialized()) {
+ // We have an entry already. It could be the one we are looking for, or just
+ // a hash conflict.
+ EntryImpl* old_entry = MatchEntry(key, hash, false);
+ if (old_entry)
+ return ResurrectEntry(old_entry);
+
+ EntryImpl* parent_entry = MatchEntry(key, hash, true);
+ if (!parent_entry) {
+ NOTREACHED();
+ return NULL;
+ }
+ parent.swap(&parent_entry);
+ }
+
+ int num_blocks;
+ size_t key1_len = sizeof(EntryStore) - offsetof(EntryStore, key);
+ if (key.size() < key1_len ||
+ key.size() > static_cast<size_t>(kMaxInternalKeyLength))
+ num_blocks = 1;
+ else
+ num_blocks = static_cast<int>((key.size() - key1_len) / 256 + 2);
+
+ if (!block_files_.CreateBlock(BLOCK_256, num_blocks, &entry_address)) {
+ LOG(ERROR) << "Create entry failed " << key.c_str();
+ stats_.OnEvent(Stats::CREATE_ERROR);
+ return NULL;
+ }
+
+ Addr node_address(0);
+ if (!block_files_.CreateBlock(RANKINGS, 1, &node_address)) {
+ block_files_.DeleteBlock(entry_address, false);
+ LOG(ERROR) << "Create entry failed " << key.c_str();
+ stats_.OnEvent(Stats::CREATE_ERROR);
+ return NULL;
+ }
+
+ scoped_refptr<EntryImpl> cache_entry(new EntryImpl(this, entry_address));
+ IncreaseNumRefs();
+
+ if (!cache_entry->CreateEntry(node_address, key, hash)) {
+ block_files_.DeleteBlock(entry_address, false);
+ block_files_.DeleteBlock(node_address, false);
+ LOG(ERROR) << "Create entry failed " << key.c_str();
+ stats_.OnEvent(Stats::CREATE_ERROR);
+ return NULL;
+ }
+
+ // We are not failing the operation; let's add this to the map.
+ open_entries_[entry_address.value()] = cache_entry;
+
+ if (parent.get())
+ parent->SetNextAddress(entry_address);
+
+ block_files_.GetFile(entry_address)->Store(cache_entry->entry());
+ block_files_.GetFile(node_address)->Store(cache_entry->rankings());
+
+ IncreaseNumEntries();
+ eviction_.OnCreateEntry(cache_entry);
+ entry_count_++;
+ if (!parent.get())
+ data_->table[hash & mask_] = entry_address.value();
+
+ CACHE_UMA(AGE_MS, "CreateTime", GetSizeGroup(), start);
+ stats_.OnEvent(Stats::CREATE_HIT);
+ Trace("create entry hit ");
+ return cache_entry.release();
+}
+
+EntryImpl* BackendImpl::OpenNextEntryImpl(void** iter) {
+ return OpenFollowingEntry(true, iter);
+}
+
+EntryImpl* BackendImpl::OpenPrevEntryImpl(void** iter) {
+ return OpenFollowingEntry(false, iter);
+}
+
bool BackendImpl::SetMaxSize(int max_bytes) {
COMPILE_ASSERT(sizeof(max_bytes) == sizeof(max_size_), unsupported_int_model);
if (max_bytes < 0)
@@ -882,10 +1022,17 @@
Time create_time = Time::FromInternalValue(data_->header.create_time);
CACHE_UMA(AGE, "FillupAge", 0, create_time);
- int64 use_hours = stats_.GetCounter(Stats::TIMER) / 120;
- CACHE_UMA(HOURS, "FillupTime", 0, static_cast<int>(use_hours));
+ int64 use_time = stats_.GetCounter(Stats::TIMER);
+ CACHE_UMA(HOURS, "FillupTime", 0, static_cast<int>(use_time / 120));
CACHE_UMA(PERCENTAGE, "FirstHitRatio", 0, stats_.GetHitRatio());
+ if (!use_time)
+ use_time = 1;
+ CACHE_UMA(COUNTS_10000, "FirstEntryAccessRate", 0,
+ static_cast<int>(data_->header.num_entries / use_time));
+ CACHE_UMA(COUNTS, "FirstByteIORate", 0,
+ static_cast<int>((data_->header.num_bytes / 1024) / use_time));
+
int avg_size = data_->header.num_bytes / GetEntryCount();
CACHE_UMA(COUNTS, "FirstEntrySize", 0, avg_size);
@@ -926,7 +1073,7 @@
void BackendImpl::ReportError(int error) {
// We transmit positive numbers, instead of direct error codes.
- DCHECK(error <= 0);
+ DCHECK_LE(error, 0);
CACHE_UMA(CACHE_ERROR, "Error", 0, error * -1);
}
@@ -934,6 +1081,18 @@
stats_.OnEvent(an_event);
}
+void BackendImpl::OnRead(int32 bytes) {
+ DCHECK_GE(bytes, 0);
+ byte_count_ += bytes;
+ if (byte_count_ < 0)
+ byte_count_ = kint32max;
+}
+
+void BackendImpl::OnWrite(int32 bytes) {
+ // We use the same implementation as OnRead... just log the number of bytes.
+ OnRead(bytes);
+}
+
void BackendImpl::OnStatsTimer() {
stats_.OnEvent(Stats::TIMER);
int64 time = stats_.GetCounter(Stats::TIMER);
@@ -952,6 +1111,11 @@
CACHE_UMA(COUNTS, "NumberOfReferences", 0, num_refs_);
+ CACHE_UMA(COUNTS_10000, "EntryAccessRate", 0, entry_count_);
+ CACHE_UMA(COUNTS, "ByteIORate", 0, byte_count_ / 1024);
+ entry_count_ = 0;
+ byte_count_ = 0;
+
if (!data_)
first_timer_ = false;
if (first_timer_) {
@@ -996,6 +1160,11 @@
num_refs_ = 0;
}
+int BackendImpl::FlushQueueForTest(CompletionCallback* callback) {
+ background_queue_.FlushQueue(callback);
+ return net::ERR_IO_PENDING;
+}
+
int BackendImpl::SelfCheck() {
if (!init_) {
LOG(ERROR) << "Init failed";
@@ -1016,10 +1185,6 @@
return CheckAllEntries();
}
-bool BackendImpl::OpenPrevEntry(void** iter, Entry** prev_entry) {
- return OpenFollowingEntry(false, iter, prev_entry);
-}
-
// ------------------------------------------------------------------------
// We just created a new file so we're going to write the header and set the
@@ -1071,6 +1236,14 @@
LOG(ERROR) << "Unable to map Index file";
return false;
}
+
+ if (index_->GetLength() < sizeof(Index)) {
+ // We verify this again on CheckIndex() but it's easier to make sure now
+ // that the header is there.
+ LOG(ERROR) << "Corrupt Index file";
+ return false;
+ }
+
return true;
}
@@ -1123,7 +1296,7 @@
// trying to re-enable the cache.
if (unit_test_)
init_ = true; // Let the destructor do proper cleanup.
- else if (Init())
+ else if (SyncInit())
stats_.SetCounter(Stats::FATAL_ERROR, errors + 1);
}
@@ -1165,9 +1338,14 @@
return ERR_INVALID_ADDRESS;
}
+ TimeTicks start = TimeTicks::Now();
if (!cache_entry->entry()->Load())
return ERR_READ_FAILURE;
+ if (IsLoaded()) {
+ CACHE_UMA(AGE_MS, "LoadTime", GetSizeGroup(), start);
+ }
+
if (!cache_entry->SanityCheck()) {
LOG(WARNING) << "Messed up entry found.";
return ERR_INVALID_ENTRY;
@@ -1268,14 +1446,11 @@
}
// This is the actual implementation for OpenNextEntry and OpenPrevEntry.
-bool BackendImpl::OpenFollowingEntry(bool forward, void** iter,
- Entry** next_entry) {
+EntryImpl* BackendImpl::OpenFollowingEntry(bool forward, void** iter) {
if (disabled_)
- return false;
+ return NULL;
DCHECK(iter);
- DCHECK(next_entry);
- *next_entry = NULL;
const int kListsToSearch = 3;
scoped_refptr<EntryImpl> entries[kListsToSearch];
@@ -1295,7 +1470,7 @@
entries[i].swap(&temp); // The entry was already addref'd.
}
if (!ret)
- return false;
+ return NULL;
} else {
// Get the next entry from the last list, and the actual entries for the
// elements on the other lists.
@@ -1319,7 +1494,7 @@
if (entries[i].get()) {
access_times[i] = entries[i]->GetLastUsed();
if (newest < 0) {
- DCHECK(oldest < 0);
+ DCHECK_LT(oldest, 0);
newest = oldest = i;
continue;
}
@@ -1331,18 +1506,19 @@
}
if (newest < 0 || oldest < 0)
- return false;
+ return NULL;
+ EntryImpl* next_entry;
if (forward) {
- entries[newest].swap(reinterpret_cast<EntryImpl**>(next_entry));
+ next_entry = entries[newest].release();
iterator->list = static_cast<Rankings::List>(newest);
} else {
- entries[oldest].swap(reinterpret_cast<EntryImpl**>(next_entry));
+ next_entry = entries[oldest].release();
iterator->list = static_cast<Rankings::List>(oldest);
}
*iter = iterator.release();
- return true;
+ return next_entry;
}
bool BackendImpl::OpenFollowingEntryFromList(bool forward, Rankings::List list,
@@ -1391,26 +1567,29 @@
return NULL;
}
+ // Make sure that we save the key for later.
+ entry->GetKey();
+
return entry;
}
-bool BackendImpl::ResurrectEntry(EntryImpl* deleted_entry, Entry** entry) {
+EntryImpl* BackendImpl::ResurrectEntry(EntryImpl* deleted_entry) {
if (ENTRY_NORMAL == deleted_entry->entry()->Data()->state) {
deleted_entry->Release();
stats_.OnEvent(Stats::CREATE_MISS);
Trace("create entry miss ");
- return false;
+ return NULL;
}
// We are attempting to create an entry and found out that the entry was
// previously deleted.
eviction_.OnCreateEntry(deleted_entry);
- *entry = deleted_entry;
+ entry_count_++;
stats_.OnEvent(Stats::CREATE_HIT);
Trace("Resurrect entry hit ");
- return true;
+ return deleted_entry;
}
void BackendImpl::DestroyInvalidEntry(EntryImpl* entry) {
@@ -1444,7 +1623,7 @@
DestroyInvalidEntry(entry);
entry->Release();
}
- DoomEntry(key);
+ SyncDoomEntry(key);
if (!next_entry)
return;
@@ -1465,7 +1644,7 @@
void BackendImpl::AddStorageSize(int32 bytes) {
data_->header.num_bytes += bytes;
- DCHECK(data_->header.num_bytes >= 0);
+ DCHECK_GE(data_->header.num_bytes, 0);
if (data_->header.num_bytes > max_size_)
eviction_.TrimCache(false);
@@ -1473,7 +1652,7 @@
void BackendImpl::SubstractStorageSize(int32 bytes) {
data_->header.num_bytes -= bytes;
- DCHECK(data_->header.num_bytes >= 0);
+ DCHECK_GE(data_->header.num_bytes, 0);
}
void BackendImpl::IncreaseNumRefs() {
@@ -1493,7 +1672,7 @@
void BackendImpl::IncreaseNumEntries() {
data_->header.num_entries++;
- DCHECK(data_->header.num_entries > 0);
+ DCHECK_GT(data_->header.num_entries, 0);
}
void BackendImpl::DecreaseNumEntries() {
@@ -1515,12 +1694,12 @@
void BackendImpl::ReportStats() {
CACHE_UMA(COUNTS, "Entries", 0, data_->header.num_entries);
- CACHE_UMA(COUNTS, "Size", 0, data_->header.num_bytes / (1024 * 1024));
- CACHE_UMA(COUNTS, "MaxSize", 0, max_size_ / (1024 * 1024));
+ CACHE_UMA(COUNTS_10000, "Size2", 0, data_->header.num_bytes / (1024 * 1024));
+ CACHE_UMA(COUNTS_10000, "MaxSize2", 0, max_size_ / (1024 * 1024));
- CACHE_UMA(COUNTS, "AverageOpenEntries", 0,
+ CACHE_UMA(COUNTS_10000, "AverageOpenEntries2", 0,
static_cast<int>(stats_.GetCounter(Stats::OPEN_ENTRIES)));
- CACHE_UMA(COUNTS, "MaxOpenEntries", 0,
+ CACHE_UMA(COUNTS_10000, "MaxOpenEntries2", 0,
static_cast<int>(stats_.GetCounter(Stats::MAX_ENTRIES)));
stats_.SetCounter(Stats::MAX_ENTRIES, 0);
@@ -1552,6 +1731,10 @@
int avg_size = data_->header.num_bytes / GetEntryCount();
CACHE_UMA(COUNTS, "EntrySize", 0, avg_size);
+ CACHE_UMA(COUNTS, "EntriesFull", 0, data_->header.num_entries);
+
+ CACHE_UMA(PERCENTAGE, "IndexLoad", 0,
+ data_->header.num_entries * 100 / (mask_ + 1));
int large_entries_bytes = stats_.GetLargeEntriesSize();
int large_ratio = large_entries_bytes * 100 / data_->header.num_bytes;
@@ -1571,6 +1754,9 @@
stats_.ResetRatios();
stats_.SetCounter(Stats::TRIM_ENTRY, 0);
+
+ if (cache_type_ == net::DISK_CACHE)
+ block_files_.ReportStats();
}
void BackendImpl::UpgradeTo2_1() {
@@ -1622,7 +1808,9 @@
AdjustMaxCacheSize(data_->header.table_len);
- if (data_->header.num_bytes < 0) {
+ if (data_->header.num_bytes < 0 ||
+ (max_size_ < kint32max - kDefaultCacheSize &&
+ data_->header.num_bytes > max_size_ + kDefaultCacheSize)) {
LOG(ERROR) << "Invalid cache (current) size";
return false;
}
@@ -1635,7 +1823,9 @@
if (!mask_)
mask_ = data_->header.table_len - 1;
- return true;
+ // Load the table into memory with a single read.
+ scoped_array<char> buf(new char[current_size]);
+ return index_->Read(buf.get(), current_size, 0);
}
int BackendImpl::CheckAllEntries() {
diff --git a/net/disk_cache/backend_impl.h b/net/disk_cache/backend_impl.h
index 6267fac..90283fb 100644
--- a/net/disk_cache/backend_impl.h
+++ b/net/disk_cache/backend_impl.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Copyright (c) 2006-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.
@@ -13,6 +13,7 @@
#include "net/disk_cache/block_files.h"
#include "net/disk_cache/disk_cache.h"
#include "net/disk_cache/eviction.h"
+#include "net/disk_cache/in_flight_backend_io.h"
#include "net/disk_cache/rankings.h"
#include "net/disk_cache/stats.h"
#include "net/disk_cache/trace.h"
@@ -35,56 +36,81 @@
class BackendImpl : public Backend {
friend class Eviction;
public:
- explicit BackendImpl(const FilePath& path)
- : path_(path), block_files_(path), mask_(0), max_size_(0),
+ BackendImpl(const FilePath& path, base::MessageLoopProxy* cache_thread)
+ : ALLOW_THIS_IN_INITIALIZER_LIST(background_queue_(this, cache_thread)),
+ path_(path), block_files_(path), mask_(0), max_size_(0),
cache_type_(net::DISK_CACHE), uma_report_(0), user_flags_(0),
init_(false), restarted_(false), unit_test_(false), read_only_(false),
- new_eviction_(false), first_timer_(true),
+ new_eviction_(false), first_timer_(true), done_(true, false),
ALLOW_THIS_IN_INITIALIZER_LIST(factory_(this)) {}
// mask can be used to limit the usable size of the hash table, for testing.
- BackendImpl(const FilePath& path, uint32 mask)
- : path_(path), block_files_(path), mask_(mask), max_size_(0),
+ BackendImpl(const FilePath& path, uint32 mask,
+ base::MessageLoopProxy* cache_thread)
+ : ALLOW_THIS_IN_INITIALIZER_LIST(background_queue_(this, cache_thread)),
+ path_(path), block_files_(path), mask_(mask), max_size_(0),
cache_type_(net::DISK_CACHE), uma_report_(0), user_flags_(kMask),
init_(false), restarted_(false), unit_test_(false), read_only_(false),
- new_eviction_(false), first_timer_(true),
+ new_eviction_(false), first_timer_(true), done_(true, false),
ALLOW_THIS_IN_INITIALIZER_LIST(factory_(this)) {}
~BackendImpl();
// Returns a new backend with the desired flags. See the declaration of
// CreateCacheBackend().
- static Backend* CreateBackend(const FilePath& full_path, bool force,
- int max_bytes, net::CacheType type,
- BackendFlags flags);
+ static int CreateBackend(const FilePath& full_path, bool force,
+ int max_bytes, net::CacheType type,
+ uint32 flags, base::MessageLoopProxy* thread,
+ Backend** backend, CompletionCallback* callback);
// Performs general initialization for this current instance of the cache.
- bool Init();
+ int Init(CompletionCallback* callback);
// Backend interface.
virtual int32 GetEntryCount() const;
- virtual bool OpenEntry(const std::string& key, Entry** entry);
virtual int OpenEntry(const std::string& key, Entry** entry,
CompletionCallback* callback);
- virtual bool CreateEntry(const std::string& key, Entry** entry);
virtual int CreateEntry(const std::string& key, Entry** entry,
CompletionCallback* callback);
- virtual bool DoomEntry(const std::string& key);
virtual int DoomEntry(const std::string& key, CompletionCallback* callback);
- virtual bool DoomAllEntries();
virtual int DoomAllEntries(CompletionCallback* callback);
- virtual bool DoomEntriesBetween(const base::Time initial_time,
- const base::Time end_time);
virtual int DoomEntriesBetween(const base::Time initial_time,
const base::Time end_time,
CompletionCallback* callback);
- virtual bool DoomEntriesSince(const base::Time initial_time);
virtual int DoomEntriesSince(const base::Time initial_time,
CompletionCallback* callback);
- virtual bool OpenNextEntry(void** iter, Entry** next_entry);
virtual int OpenNextEntry(void** iter, Entry** next_entry,
CompletionCallback* callback);
virtual void EndEnumeration(void** iter);
virtual void GetStats(StatsItems* stats);
+ // Performs the actual initialization and final cleanup on destruction.
+ // Cleanup is a two step process (with a trip to the message loop in between).
+ // Note that these methods are not intended for external consumption.
+ int SyncInit();
+ void StartCleanup();
+ void CleanupCache();
+
+ // Same bahavior as OpenNextEntry but walks the list from back to front.
+ int OpenPrevEntry(void** iter, Entry** prev_entry,
+ CompletionCallback* callback);
+
+ // Synchronous implementation of the asynchronous interface.
+ int SyncOpenEntry(const std::string& key, Entry** entry);
+ int SyncCreateEntry(const std::string& key, Entry** entry);
+ int SyncDoomEntry(const std::string& key);
+ int SyncDoomAllEntries();
+ int SyncDoomEntriesBetween(const base::Time initial_time,
+ const base::Time end_time);
+ int SyncDoomEntriesSince(const base::Time initial_time);
+ int SyncOpenNextEntry(void** iter, Entry** next_entry);
+ int SyncOpenPrevEntry(void** iter, Entry** prev_entry);
+ void SyncEndEnumeration(void* iter);
+
+ // Open or create an entry for the given |key| or |iter|.
+ EntryImpl* OpenEntryImpl(const std::string& key);
+ EntryImpl* CreateEntryImpl(const std::string& key);
+ EntryImpl* OpenNextEntryImpl(void** iter);
+ EntryImpl* OpenPrevEntryImpl(void** iter);
+
// Sets the maximum size for the total amount of data stored by this instance.
bool SetMaxSize(int max_bytes);
@@ -97,6 +123,10 @@
// Returns the actual file used to store a given (non-external) address.
MappedFile* File(Addr address);
+ InFlightBackendIO* background_queue() {
+ return &background_queue_;
+ }
+
// Creates an external storage file.
bool CreateExternalFile(Addr* address);
@@ -177,6 +207,10 @@
// Called when an interesting event should be logged (counted).
void OnEvent(Stats::Counters an_event);
+ // Keeps track of paylod access (doesn't include metadata).
+ void OnRead(int bytes);
+ void OnWrite(int bytes);
+
// Timer callback to calculate usage statistics.
void OnStatsTimer();
@@ -199,13 +233,13 @@
// Clears the counter of references to test handling of corruptions.
void ClearRefCountForTest();
+ // Sends a dummy operation through the operation queue, for unit tests.
+ int FlushQueueForTest(CompletionCallback* callback);
+
// Peforms a simple self-check, and returns the number of dirty items
// or an error code (negative value).
int SelfCheck();
- // Same bahavior as OpenNextEntry but walks the list from back to front.
- bool OpenPrevEntry(void** iter, Entry** prev_entry);
-
private:
typedef base::hash_map<CacheAddr, EntryImpl*> EntriesMap;
@@ -228,7 +262,7 @@
EntryImpl* MatchEntry(const std::string& key, uint32 hash, bool find_parent);
// Opens the next or previous entry on a cache iteration.
- bool OpenFollowingEntry(bool forward, void** iter, Entry** next_entry);
+ EntryImpl* OpenFollowingEntry(bool forward, void** iter);
// Opens the next or previous entry on a single list. If successfull,
// |from_entry| will be updated to point to the new entry, otherwise it will
@@ -242,7 +276,7 @@
EntryImpl* GetEnumeratedEntry(CacheRankingsBlock* next, bool to_evict);
// Re-opens an entry that was previously deleted.
- bool ResurrectEntry(EntryImpl* deleted_entry, Entry** entry);
+ EntryImpl* ResurrectEntry(EntryImpl* deleted_entry);
void DestroyInvalidEntry(EntryImpl* entry);
void DestroyInvalidEntryFromEnumeration(EntryImpl* entry);
@@ -275,6 +309,7 @@
// Part of the self test. Returns false if the entry is corrupt.
bool CheckEntry(EntryImpl* cache_entry);
+ InFlightBackendIO background_queue_; // The controller of pending operations.
scoped_refptr<MappedFile> index_; // The main cache index.
FilePath path_; // Path to the folder used as backing storage.
Index* data_; // Pointer to the index data.
@@ -287,6 +322,8 @@
int num_refs_; // Number of referenced cache entries.
int max_refs_; // Max number of referenced cache entries.
int num_pending_io_; // Number of pending IO operations.
+ int entry_count_; // Number of entries accessed lately.
+ int byte_count_; // Number of bytes read/written lately.
net::CacheType cache_type_;
int uma_report_; // Controls transmision of UMA data.
uint32 user_flags_; // Flags set by the user.
@@ -300,10 +337,11 @@
Stats stats_; // Usage statistcs.
base::RepeatingTimer<BackendImpl> timer_; // Usage timer.
+ base::WaitableEvent done_; // Signals the end of background work.
scoped_refptr<TraceObject> trace_object_; // Inits internal tracing.
ScopedRunnableMethodFactory<BackendImpl> factory_;
- DISALLOW_EVIL_CONSTRUCTORS(BackendImpl);
+ DISALLOW_COPY_AND_ASSIGN(BackendImpl);
};
// Returns the prefered max cache size given the available disk space.
diff --git a/net/disk_cache/backend_unittest.cc b/net/disk_cache/backend_unittest.cc
index 9d4194b..7bb27d2 100644
--- a/net/disk_cache/backend_unittest.cc
+++ b/net/disk_cache/backend_unittest.cc
@@ -1,10 +1,9 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Copyright (c) 2006-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 "base/file_util.h"
-#include "base/path_service.h"
#include "base/platform_thread.h"
#include "base/string_util.h"
#include "net/base/io_buffer.h"
@@ -13,30 +12,13 @@
#include "net/disk_cache/backend_impl.h"
#include "net/disk_cache/disk_cache_test_base.h"
#include "net/disk_cache/disk_cache_test_util.h"
+#include "net/disk_cache/histogram_macros.h"
#include "net/disk_cache/mapped_file.h"
+#include "net/disk_cache/mem_backend_impl.h"
#include "testing/gtest/include/gtest/gtest.h"
using base::Time;
-namespace {
-
-// Copies a set of cache files from the data folder to the test folder.
-bool CopyTestCache(const std::wstring& name) {
- FilePath path;
- PathService::Get(base::DIR_SOURCE_ROOT, &path);
- path = path.AppendASCII("net");
- path = path.AppendASCII("data");
- path = path.AppendASCII("cache_tests");
- path = path.Append(FilePath::FromWStringHack(name));
-
- FilePath dest = GetCacheFilePath();
- if (!DeleteCache(dest))
- return false;
- return file_util::CopyDirectory(path, dest, false);
-}
-
-} // namespace
-
// Tests that can run with different types of caches.
class DiskCacheBackendTest : public DiskCacheTestWithCache {
protected:
@@ -56,11 +38,11 @@
void BackendFixEnumerators();
void BackendDoomRecent();
void BackendDoomBetween();
- void BackendTransaction(const std::wstring& name, int num_entries, bool load);
+ void BackendTransaction(const std::string& name, int num_entries, bool load);
void BackendRecoverInsert();
void BackendRecoverRemove();
void BackendInvalidEntry2();
- void BackendNotMarkedButDirty(const std::wstring& name);
+ void BackendNotMarkedButDirty(const std::string& name);
void BackendDoomAll();
void BackendDoomAll2();
void BackendInvalidRankings();
@@ -74,45 +56,45 @@
void DiskCacheBackendTest::BackendBasics() {
InitCache();
disk_cache::Entry *entry1 = NULL, *entry2 = NULL;
- EXPECT_FALSE(cache_->OpenEntry("the first key", &entry1));
- ASSERT_TRUE(cache_->CreateEntry("the first key", &entry1));
+ EXPECT_NE(net::OK, OpenEntry("the first key", &entry1));
+ ASSERT_EQ(net::OK, CreateEntry("the first key", &entry1));
ASSERT_TRUE(NULL != entry1);
entry1->Close();
entry1 = NULL;
- ASSERT_TRUE(cache_->OpenEntry("the first key", &entry1));
+ ASSERT_EQ(net::OK, OpenEntry("the first key", &entry1));
ASSERT_TRUE(NULL != entry1);
entry1->Close();
entry1 = NULL;
- EXPECT_FALSE(cache_->CreateEntry("the first key", &entry1));
- ASSERT_TRUE(cache_->OpenEntry("the first key", &entry1));
- EXPECT_FALSE(cache_->OpenEntry("some other key", &entry2));
- ASSERT_TRUE(cache_->CreateEntry("some other key", &entry2));
+ EXPECT_NE(net::OK, CreateEntry("the first key", &entry1));
+ ASSERT_EQ(net::OK, OpenEntry("the first key", &entry1));
+ EXPECT_NE(net::OK, OpenEntry("some other key", &entry2));
+ ASSERT_EQ(net::OK, CreateEntry("some other key", &entry2));
ASSERT_TRUE(NULL != entry1);
ASSERT_TRUE(NULL != entry2);
EXPECT_EQ(2, cache_->GetEntryCount());
disk_cache::Entry* entry3 = NULL;
- ASSERT_TRUE(cache_->OpenEntry("some other key", &entry3));
+ ASSERT_EQ(net::OK, OpenEntry("some other key", &entry3));
ASSERT_TRUE(NULL != entry3);
EXPECT_TRUE(entry2 == entry3);
EXPECT_EQ(2, cache_->GetEntryCount());
- EXPECT_TRUE(cache_->DoomEntry("some other key"));
+ EXPECT_EQ(net::OK, DoomEntry("some other key"));
EXPECT_EQ(1, cache_->GetEntryCount());
entry1->Close();
entry2->Close();
entry3->Close();
- EXPECT_TRUE(cache_->DoomEntry("the first key"));
+ EXPECT_EQ(net::OK, DoomEntry("the first key"));
EXPECT_EQ(0, cache_->GetEntryCount());
- ASSERT_TRUE(cache_->CreateEntry("the first key", &entry1));
- ASSERT_TRUE(cache_->CreateEntry("some other key", &entry2));
+ ASSERT_EQ(net::OK, CreateEntry("the first key", &entry1));
+ ASSERT_EQ(net::OK, CreateEntry("some other key", &entry2));
entry1->Doom();
entry1->Close();
- EXPECT_TRUE(cache_->DoomEntry("some other key"));
+ EXPECT_EQ(net::OK, DoomEntry("some other key"));
EXPECT_EQ(0, cache_->GetEntryCount());
entry2->Close();
}
@@ -136,25 +118,25 @@
const char* kName1 = "the first key";
const char* kName2 = "the first Key";
disk_cache::Entry *entry1, *entry2;
- ASSERT_TRUE(cache_->CreateEntry(kName1, &entry1));
+ ASSERT_EQ(net::OK, CreateEntry(kName1, &entry1));
- ASSERT_TRUE(cache_->CreateEntry(kName2, &entry2));
+ ASSERT_EQ(net::OK, CreateEntry(kName2, &entry2));
EXPECT_TRUE(entry1 != entry2) << "Case sensitive";
entry2->Close();
char buffer[30];
base::strlcpy(buffer, kName1, arraysize(buffer));
- ASSERT_TRUE(cache_->OpenEntry(buffer, &entry2));
+ ASSERT_EQ(net::OK, OpenEntry(buffer, &entry2));
EXPECT_TRUE(entry1 == entry2);
entry2->Close();
base::strlcpy(buffer + 1, kName1, arraysize(buffer) - 1);
- ASSERT_TRUE(cache_->OpenEntry(buffer + 1, &entry2));
+ ASSERT_EQ(net::OK, OpenEntry(buffer + 1, &entry2));
EXPECT_TRUE(entry1 == entry2);
entry2->Close();
base::strlcpy(buffer + 3, kName1, arraysize(buffer) - 3);
- ASSERT_TRUE(cache_->OpenEntry(buffer + 3, &entry2));
+ ASSERT_EQ(net::OK, OpenEntry(buffer + 3, &entry2));
EXPECT_TRUE(entry1 == entry2);
entry2->Close();
@@ -162,12 +144,12 @@
char buffer2[20000];
memset(buffer2, 's', sizeof(buffer2));
buffer2[1023] = '\0';
- ASSERT_TRUE(cache_->CreateEntry(buffer2, &entry2)) << "key on block file";
+ ASSERT_EQ(net::OK, CreateEntry(buffer2, &entry2)) << "key on block file";
entry2->Close();
buffer2[1023] = 'g';
buffer2[19999] = '\0';
- ASSERT_TRUE(cache_->CreateEntry(buffer2, &entry2)) << "key on external file";
+ ASSERT_EQ(net::OK, CreateEntry(buffer2, &entry2)) << "key on external file";
entry2->Close();
entry1->Close();
}
@@ -186,6 +168,49 @@
BackendKeying();
}
+TEST_F(DiskCacheTest, CreateBackend) {
+ TestCompletionCallback cb;
+
+ {
+ FilePath path = GetCacheFilePath();
+ ASSERT_TRUE(DeleteCache(path));
+ base::Thread cache_thread("CacheThread");
+ ASSERT_TRUE(cache_thread.StartWithOptions(
+ base::Thread::Options(MessageLoop::TYPE_IO, 0)));
+
+ // Test the private factory methods.
+ disk_cache::Backend* cache = NULL;
+ int rv = disk_cache::BackendImpl::CreateBackend(
+ path, false, 0, net::DISK_CACHE, disk_cache::kNoRandom,
+ cache_thread.message_loop_proxy(), &cache, &cb);
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
+ ASSERT_TRUE(cache);
+ delete cache;
+
+ cache = disk_cache::MemBackendImpl::CreateBackend(0);
+ ASSERT_TRUE(cache);
+ delete cache;
+ cache = NULL;
+
+ // Now test the public API.
+ rv = disk_cache::CreateCacheBackend(net::DISK_CACHE, path, 0, false,
+ cache_thread.message_loop_proxy(),
+ &cache, &cb);
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
+ ASSERT_TRUE(cache);
+ delete cache;
+ cache = NULL;
+
+ rv = disk_cache::CreateCacheBackend(net::MEMORY_CACHE, FilePath(), 0, false,
+ NULL, &cache, &cb);
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
+ ASSERT_TRUE(cache);
+ delete cache;
+ }
+
+ MessageLoop::current()->RunAllPending();
+}
+
TEST_F(DiskCacheBackendTest, ExternalFiles) {
InitCache();
// First, lets create a file on the folder.
@@ -198,7 +223,7 @@
// Now let's create a file with the cache.
disk_cache::Entry* entry;
- ASSERT_TRUE(cache_->CreateEntry("key", &entry));
+ ASSERT_EQ(net::OK, CreateEntry("key", &entry));
ASSERT_EQ(0, entry->WriteData(0, 20000, buffer1, 0, NULL, false));
entry->Close();
@@ -208,39 +233,115 @@
EXPECT_EQ(0, memcmp(buffer1->data(), buffer2->data(), kSize));
}
+// Tests that we deal with file-level pending operations at destruction time.
TEST_F(DiskCacheTest, ShutdownWithPendingIO) {
- TestCompletionCallback callback;
+ TestCompletionCallback cb;
{
FilePath path = GetCacheFilePath();
ASSERT_TRUE(DeleteCache(path));
+ base::Thread cache_thread("CacheThread");
+ ASSERT_TRUE(cache_thread.StartWithOptions(
+ base::Thread::Options(MessageLoop::TYPE_IO, 0)));
- disk_cache::Backend* cache =
- disk_cache::CreateCacheBackend(path, false, 0, net::DISK_CACHE);
+ disk_cache::Backend* cache;
+ int rv = disk_cache::BackendImpl::CreateBackend(
+ path, false, 0, net::DISK_CACHE, disk_cache::kNoRandom,
+ base::MessageLoopProxy::CreateForCurrentThread(), &cache, &cb);
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
- disk_cache::Entry* entry;
- ASSERT_TRUE(cache->CreateEntry("some key", &entry));
+ disk_cache::EntryImpl* entry;
+ rv = cache->CreateEntry("some key",
+ reinterpret_cast<disk_cache::Entry**>(&entry), &cb);
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
const int kSize = 25000;
scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer(kSize);
CacheTestFillBuffer(buffer->data(), kSize, false);
for (int i = 0; i < 10 * 1024 * 1024; i += 64 * 1024) {
- int rv = entry->WriteData(0, i, buffer, kSize, &callback, false);
+ // We are using the current thread as the cache thread because we want to
+ // be able to call directly this method to make sure that the OS (instead
+ // of us switching thread) is returning IO pending.
+ rv = entry->WriteDataImpl(0, i, buffer, kSize, &cb, false);
if (rv == net::ERR_IO_PENDING)
break;
EXPECT_EQ(kSize, rv);
}
- entry->Close();
+ // Don't call Close() to avoid going through the queue or we'll deadlock
+ // waiting for the operation to finish.
+ entry->Release();
// The cache destructor will see one pending operation here.
delete cache;
+
+ if (rv == net::ERR_IO_PENDING) {
+ EXPECT_TRUE(cb.have_result());
+ }
+ }
+
+ MessageLoop::current()->RunAllPending();
+}
+
+// Tests that we deal with background-thread pending operations.
+TEST_F(DiskCacheTest, ShutdownWithPendingIO2) {
+ TestCompletionCallback cb;
+
+ {
+ FilePath path = GetCacheFilePath();
+ ASSERT_TRUE(DeleteCache(path));
+ base::Thread cache_thread("CacheThread");
+ ASSERT_TRUE(cache_thread.StartWithOptions(
+ base::Thread::Options(MessageLoop::TYPE_IO, 0)));
+
+ disk_cache::Backend* cache;
+ int rv = disk_cache::BackendImpl::CreateBackend(
+ path, false, 0, net::DISK_CACHE, disk_cache::kNoRandom,
+ cache_thread.message_loop_proxy(), &cache, &cb);
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
+
+ disk_cache::Entry* entry;
+ rv = cache->CreateEntry("some key", &entry, &cb);
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
+
+ const int kSize = 25000;
+ scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer(kSize);
+ CacheTestFillBuffer(buffer->data(), kSize, false);
+
+ rv = entry->WriteData(0, 0, buffer, kSize, &cb, false);
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+
+ entry->Close();
+
+ // The cache destructor will see two pending operations here.
+ delete cache;
}
MessageLoop::current()->RunAllPending();
}
+TEST_F(DiskCacheTest, TruncatedIndex) {
+ FilePath path = GetCacheFilePath();
+ ASSERT_TRUE(DeleteCache(path));
+ FilePath index = path.AppendASCII("index");
+ ASSERT_EQ(5, file_util::WriteFile(index, "hello", 5));
+
+ base::Thread cache_thread("CacheThread");
+ ASSERT_TRUE(cache_thread.StartWithOptions(
+ base::Thread::Options(MessageLoop::TYPE_IO, 0)));
+ TestCompletionCallback cb;
+
+ disk_cache::Backend* backend = NULL;
+ int rv = disk_cache::BackendImpl::CreateBackend(
+ path, false, 0, net::DISK_CACHE, disk_cache::kNone,
+ cache_thread.message_loop_proxy(), &backend, &cb);
+ ASSERT_NE(net::OK, cb.GetResult(rv));
+
+ ASSERT_TRUE(backend == NULL);
+ delete backend;
+}
+
void DiskCacheBackendTest::BackendSetSize() {
SetDirectMode();
const int cache_size = 0x10000; // 64 kB
@@ -250,7 +351,7 @@
std::string first("some key");
std::string second("something else");
disk_cache::Entry* entry;
- ASSERT_TRUE(cache_->CreateEntry(first, &entry));
+ ASSERT_EQ(net::OK, CreateEntry(first, &entry));
scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer(cache_size);
memset(buffer->data(), 0, cache_size);
@@ -270,18 +371,29 @@
EXPECT_EQ(cache_size * 3 / 4, entry->WriteData(0, 0, buffer,
cache_size * 3 / 4, NULL, false));
entry->Close();
+ FlushQueueForTest();
SetMaxSize(cache_size);
// The cache is 95% full.
- ASSERT_TRUE(cache_->CreateEntry(second, &entry));
+ ASSERT_EQ(net::OK, CreateEntry(second, &entry));
EXPECT_EQ(cache_size / 10, entry->WriteData(0, 0, buffer, cache_size / 10,
- NULL, false)) << "trim the cache";
- entry->Close();
+ NULL, false));
- EXPECT_FALSE(cache_->OpenEntry(first, &entry));
- ASSERT_TRUE(cache_->OpenEntry(second, &entry));
+ disk_cache::Entry* entry2;
+ ASSERT_EQ(net::OK, CreateEntry("an extra key", &entry2));
+ EXPECT_EQ(cache_size / 10, entry2->WriteData(0, 0, buffer, cache_size / 10,
+ NULL, false));
+ entry2->Close(); // This will trigger the cache trim.
+
+ EXPECT_NE(net::OK, OpenEntry(first, &entry2));
+
+ FlushQueueForTest(); // Make sure that we are done trimming the cache.
+ FlushQueueForTest(); // We may have posted two tasks to evict stuff.
+
+ entry->Close();
+ ASSERT_EQ(net::OK, OpenEntry(second, &entry));
EXPECT_EQ(cache_size / 10, entry->GetDataSize(0));
entry->Close();
}
@@ -308,7 +420,7 @@
disk_cache::Entry* entries[100];
for (int i = 0; i < 100; i++) {
std::string key = GenerateKey(true);
- ASSERT_TRUE(cache_->CreateEntry(key, &entries[i]));
+ ASSERT_EQ(net::OK, CreateEntry(key, &entries[i]));
}
EXPECT_EQ(100, cache_->GetEntryCount());
@@ -322,12 +434,13 @@
for (int i = 0; i < 100; i++) {
disk_cache::Entry* entry;
- ASSERT_TRUE(cache_->OpenEntry(entries[i]->GetKey(), &entry));
+ ASSERT_EQ(net::OK, OpenEntry(entries[i]->GetKey(), &entry));
EXPECT_TRUE(entry == entries[i]);
entry->Close();
entries[i]->Doom();
entries[i]->Close();
}
+ FlushQueueForTest();
EXPECT_EQ(0, cache_->GetEntryCount());
}
@@ -360,7 +473,7 @@
std::string key("Some key");
disk_cache::Entry* entry1;
- ASSERT_TRUE(cache_->CreateEntry(key, &entry1));
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry1));
const int kSize = 50;
scoped_refptr<net::IOBuffer> buffer1 = new net::IOBuffer(kSize);
@@ -370,7 +483,7 @@
entry1->Close();
SimulateCrash();
- ASSERT_TRUE(cache_->OpenEntry(key, &entry1));
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry1));
scoped_refptr<net::IOBuffer> buffer2 = new net::IOBuffer(kSize);
memset(buffer2->data(), 0, kSize);
@@ -398,7 +511,7 @@
std::string key("Some key");
disk_cache::Entry* entry1;
- ASSERT_TRUE(cache_->CreateEntry(key, &entry1));
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry1));
const int kSize = 50;
scoped_refptr<net::IOBuffer> buffer1 = new net::IOBuffer(kSize);
@@ -407,7 +520,7 @@
EXPECT_EQ(kSize, entry1->WriteData(0, 0, buffer1, kSize, NULL, false));
SimulateCrash();
- EXPECT_FALSE(cache_->OpenEntry(key, &entry1));
+ EXPECT_NE(net::OK, OpenEntry(key, &entry1));
EXPECT_EQ(0, cache_->GetEntryCount());
}
@@ -436,7 +549,7 @@
std::string key("Some key");
disk_cache::Entry* entry1;
- ASSERT_TRUE(cache_->CreateEntry(key, &entry1));
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry1));
const int kSize = 50;
scoped_refptr<net::IOBuffer> buffer1 = new net::IOBuffer(kSize);
@@ -444,12 +557,12 @@
base::strlcpy(buffer1->data(), "And the data to save", kSize);
EXPECT_EQ(kSize, entry1->WriteData(0, 0, buffer1, kSize, NULL, false));
entry1->Close();
- ASSERT_TRUE(cache_->OpenEntry(key, &entry1));
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry1));
EXPECT_EQ(kSize, entry1->ReadData(0, 0, buffer1, kSize, NULL));
SimulateCrash();
- EXPECT_FALSE(cache_->OpenEntry(key, &entry1));
+ EXPECT_NE(net::OK, OpenEntry(key, &entry1));
EXPECT_EQ(0, cache_->GetEntryCount());
}
@@ -478,7 +591,7 @@
disk_cache::Entry* entries[kNumEntries];
for (int i = 0; i < kNumEntries; i++) {
std::string key = GenerateKey(true);
- ASSERT_TRUE(cache_->CreateEntry(key, &entries[i]));
+ ASSERT_EQ(net::OK, CreateEntry(key, &entries[i]));
}
EXPECT_EQ(kNumEntries, cache_->GetEntryCount());
@@ -501,12 +614,12 @@
for (int i = kNumEntries / 2; i < kNumEntries; i++) {
disk_cache::Entry* entry;
- EXPECT_FALSE(cache_->OpenEntry(keys[i], &entry));
+ EXPECT_NE(net::OK, OpenEntry(keys[i], &entry));
}
for (int i = 0; i < kNumEntries / 2; i++) {
disk_cache::Entry* entry;
- EXPECT_TRUE(cache_->OpenEntry(keys[i], &entry));
+ EXPECT_EQ(net::OK, OpenEntry(keys[i], &entry));
entry->Close();
}
@@ -536,7 +649,7 @@
std::string first("some key");
std::string second("something else");
disk_cache::Entry* entry;
- ASSERT_TRUE(cache_->CreateEntry(first, &entry));
+ ASSERT_EQ(net::OK, CreateEntry(first, &entry));
scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer(kSize);
memset(buffer->data(), 0, kSize);
@@ -545,19 +658,20 @@
// Simulate a crash.
SimulateCrash();
- ASSERT_TRUE(cache_->CreateEntry(second, &entry));
+ ASSERT_EQ(net::OK, CreateEntry(second, &entry));
EXPECT_EQ(kSize, entry->WriteData(0, 0, buffer, kSize, NULL, false));
EXPECT_EQ(2, cache_->GetEntryCount());
SetMaxSize(kSize);
entry->Close(); // Trim the cache.
+ FlushQueueForTest();
// If we evicted the entry in less than 20mS, we have one entry in the cache;
// if it took more than that, we posted a task and we'll delete the second
// entry too.
MessageLoop::current()->RunAllPending();
EXPECT_GE(1, cache_->GetEntryCount());
- EXPECT_FALSE(cache_->OpenEntry(first, &entry));
+ EXPECT_NE(net::OK, OpenEntry(first, &entry));
}
// We'll be leaking memory from this test.
@@ -588,17 +702,17 @@
// Writing 32 entries to this cache chains most of them.
for (int i = 0; i < 32; i++) {
std::string key(StringPrintf("some key %d", i));
- ASSERT_TRUE(cache_->CreateEntry(key, &entry));
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
EXPECT_EQ(kSize, entry->WriteData(0, 0, buffer, kSize, NULL, false));
entry->Close();
- ASSERT_TRUE(cache_->OpenEntry(key, &entry));
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
// Note that we are not closing the entries.
}
// Simulate a crash.
SimulateCrash();
- ASSERT_TRUE(cache_->CreateEntry("Something else", &entry));
+ ASSERT_EQ(net::OK, CreateEntry("Something else", &entry));
EXPECT_EQ(kSize, entry->WriteData(0, 0, buffer, kSize, NULL, false));
EXPECT_EQ(33, cache_->GetEntryCount());
@@ -606,10 +720,12 @@
// For the new eviction code, all corrupt entries are on the second list so
// they are not going away that easy.
- if (new_eviction_)
- cache_->DoomAllEntries();
+ if (new_eviction_) {
+ EXPECT_EQ(net::OK, DoomAllEntries());
+ }
entry->Close(); // Trim the cache.
+ FlushQueueForTest();
// We may abort the eviction before cleaning up everything.
MessageLoop::current()->RunAllPending();
@@ -637,7 +753,7 @@
for (int i = 0; i < kNumEntries; i++) {
std::string key = GenerateKey(true);
disk_cache::Entry* entry;
- ASSERT_TRUE(cache_->CreateEntry(key, &entry));
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
entry->Close();
}
EXPECT_EQ(kNumEntries, cache_->GetEntryCount());
@@ -648,7 +764,7 @@
int count = 0;
Time last_modified[kNumEntries];
Time last_used[kNumEntries];
- while (cache_->OpenNextEntry(&iter, &entry)) {
+ while (OpenNextEntry(&iter, &entry) == net::OK) {
ASSERT_TRUE(NULL != entry);
if (count < kNumEntries) {
last_modified[count] = entry->GetLastModified();
@@ -665,7 +781,7 @@
iter = NULL;
count = 0;
// The previous enumeration should not have changed the timestamps.
- while (cache_->OpenNextEntry(&iter, &entry)) {
+ while (OpenNextEntry(&iter, &entry) == net::OK) {
ASSERT_TRUE(NULL != entry);
if (count < kNumEntries) {
EXPECT_TRUE(last_modified[count] == entry->GetLastModified());
@@ -697,24 +813,24 @@
const std::string first("first");
const std::string second("second");
disk_cache::Entry *entry1, *entry2;
- ASSERT_TRUE(cache_->CreateEntry(first, &entry1));
+ ASSERT_EQ(net::OK, CreateEntry(first, &entry1));
entry1->Close();
- ASSERT_TRUE(cache_->CreateEntry(second, &entry2));
+ ASSERT_EQ(net::OK, CreateEntry(second, &entry2));
entry2->Close();
// Make sure that the timestamp is not the same.
PlatformThread::Sleep(20);
- ASSERT_TRUE(cache_->OpenEntry(second, &entry1));
+ ASSERT_EQ(net::OK, OpenEntry(second, &entry1));
void* iter = NULL;
- ASSERT_TRUE(cache_->OpenNextEntry(&iter, &entry2));
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter, &entry2));
ASSERT_EQ(entry2->GetKey(), second);
// Two entries and the iterator pointing at "first".
entry1->Close();
entry2->Close();
- // The iterator should still be valid, se we should not crash.
- ASSERT_TRUE(cache_->OpenNextEntry(&iter, &entry2));
+ // The iterator should still be valid, so we should not crash.
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter, &entry2));
ASSERT_EQ(entry2->GetKey(), first);
entry2->Close();
cache_->EndEnumeration(&iter);
@@ -744,7 +860,7 @@
std::string key("Some key");
disk_cache::Entry *entry, *entry1, *entry2;
- ASSERT_TRUE(cache_->CreateEntry(key, &entry1));
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry1));
const int kSize = 50;
scoped_refptr<net::IOBuffer> buffer1 = new net::IOBuffer(kSize);
@@ -752,11 +868,11 @@
base::strlcpy(buffer1->data(), "And the data to save", kSize);
EXPECT_EQ(kSize, entry1->WriteData(0, 0, buffer1, kSize, NULL, false));
entry1->Close();
- ASSERT_TRUE(cache_->OpenEntry(key, &entry1));
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry1));
EXPECT_EQ(kSize, entry1->ReadData(0, 0, buffer1, kSize, NULL));
std::string key2("Another key");
- ASSERT_TRUE(cache_->CreateEntry(key2, &entry2));
+ ASSERT_EQ(net::OK, CreateEntry(key2, &entry2));
entry2->Close();
ASSERT_EQ(2, cache_->GetEntryCount());
@@ -764,7 +880,7 @@
void* iter = NULL;
int count = 0;
- while (cache_->OpenNextEntry(&iter, &entry)) {
+ while (OpenNextEntry(&iter, &entry) == net::OK) {
ASSERT_TRUE(NULL != entry);
EXPECT_EQ(key2, entry->GetKey());
entry->Close();
@@ -797,7 +913,7 @@
for (int i = 0; i < kNumEntries; i++) {
std::string key = GenerateKey(true);
disk_cache::Entry* entry;
- ASSERT_TRUE(cache_->CreateEntry(key, &entry));
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
entry->Close();
}
EXPECT_EQ(kNumEntries, cache_->GetEntryCount());
@@ -805,7 +921,7 @@
disk_cache::Entry *entry1, *entry2;
void* iter1 = NULL;
void* iter2 = NULL;
- ASSERT_TRUE(cache_->OpenNextEntry(&iter1, &entry1));
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter1, &entry1));
ASSERT_TRUE(NULL != entry1);
entry1->Close();
entry1 = NULL;
@@ -814,17 +930,17 @@
for (int i = 0; i < kNumEntries / 2; i++) {
if (entry1)
entry1->Close();
- ASSERT_TRUE(cache_->OpenNextEntry(&iter1, &entry1));
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter1, &entry1));
ASSERT_TRUE(NULL != entry1);
- ASSERT_TRUE(cache_->OpenNextEntry(&iter2, &entry2));
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter2, &entry2));
ASSERT_TRUE(NULL != entry2);
entry2->Close();
}
// Messing up with entry1 will modify entry2->next.
entry1->Doom();
- ASSERT_TRUE(cache_->OpenNextEntry(&iter2, &entry2));
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter2, &entry2));
ASSERT_TRUE(NULL != entry2);
// The link entry2->entry1 should be broken.
@@ -833,7 +949,7 @@
entry2->Close();
// And the second iterator should keep working.
- ASSERT_TRUE(cache_->OpenNextEntry(&iter2, &entry2));
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter2, &entry2));
ASSERT_TRUE(NULL != entry2);
entry2->Close();
@@ -855,30 +971,30 @@
Time initial = Time::Now();
disk_cache::Entry *entry;
- ASSERT_TRUE(cache_->CreateEntry("first", &entry));
+ ASSERT_EQ(net::OK, CreateEntry("first", &entry));
entry->Close();
- ASSERT_TRUE(cache_->CreateEntry("second", &entry));
+ ASSERT_EQ(net::OK, CreateEntry("second", &entry));
entry->Close();
PlatformThread::Sleep(20);
Time middle = Time::Now();
- ASSERT_TRUE(cache_->CreateEntry("third", &entry));
+ ASSERT_EQ(net::OK, CreateEntry("third", &entry));
entry->Close();
- ASSERT_TRUE(cache_->CreateEntry("fourth", &entry));
+ ASSERT_EQ(net::OK, CreateEntry("fourth", &entry));
entry->Close();
PlatformThread::Sleep(20);
Time final = Time::Now();
ASSERT_EQ(4, cache_->GetEntryCount());
- EXPECT_TRUE(cache_->DoomEntriesSince(final));
+ EXPECT_EQ(net::OK, DoomEntriesSince(final));
ASSERT_EQ(4, cache_->GetEntryCount());
- EXPECT_TRUE(cache_->DoomEntriesSince(middle));
+ EXPECT_EQ(net::OK, DoomEntriesSince(middle));
ASSERT_EQ(2, cache_->GetEntryCount());
- ASSERT_TRUE(cache_->OpenEntry("second", &entry));
+ ASSERT_EQ(net::OK, OpenEntry("second", &entry));
entry->Close();
}
@@ -901,39 +1017,39 @@
Time initial = Time::Now();
disk_cache::Entry *entry;
- ASSERT_TRUE(cache_->CreateEntry("first", &entry));
+ ASSERT_EQ(net::OK, CreateEntry("first", &entry));
entry->Close();
PlatformThread::Sleep(20);
Time middle_start = Time::Now();
- ASSERT_TRUE(cache_->CreateEntry("second", &entry));
+ ASSERT_EQ(net::OK, CreateEntry("second", &entry));
entry->Close();
- ASSERT_TRUE(cache_->CreateEntry("third", &entry));
+ ASSERT_EQ(net::OK, CreateEntry("third", &entry));
entry->Close();
PlatformThread::Sleep(20);
Time middle_end = Time::Now();
- ASSERT_TRUE(cache_->CreateEntry("fourth", &entry));
+ ASSERT_EQ(net::OK, CreateEntry("fourth", &entry));
entry->Close();
- ASSERT_TRUE(cache_->OpenEntry("fourth", &entry));
+ ASSERT_EQ(net::OK, OpenEntry("fourth", &entry));
entry->Close();
PlatformThread::Sleep(20);
Time final = Time::Now();
ASSERT_EQ(4, cache_->GetEntryCount());
- EXPECT_TRUE(cache_->DoomEntriesBetween(middle_start, middle_end));
+ EXPECT_EQ(net::OK, DoomEntriesBetween(middle_start, middle_end));
ASSERT_EQ(2, cache_->GetEntryCount());
- ASSERT_TRUE(cache_->OpenEntry("fourth", &entry));
+ ASSERT_EQ(net::OK, OpenEntry("fourth", &entry));
entry->Close();
- EXPECT_TRUE(cache_->DoomEntriesBetween(middle_start, final));
+ EXPECT_EQ(net::OK, DoomEntriesBetween(middle_start, final));
ASSERT_EQ(1, cache_->GetEntryCount());
- ASSERT_TRUE(cache_->OpenEntry("first", &entry));
+ ASSERT_EQ(net::OK, OpenEntry("first", &entry));
entry->Close();
}
@@ -951,7 +1067,7 @@
BackendDoomBetween();
}
-void DiskCacheBackendTest::BackendTransaction(const std::wstring& name,
+void DiskCacheBackendTest::BackendTransaction(const std::string& name,
int num_entries, bool load) {
success_ = false;
ASSERT_TRUE(CopyTestCache(name));
@@ -971,7 +1087,7 @@
std::string key("the first key");
disk_cache::Entry* entry1;
- ASSERT_FALSE(cache_->OpenEntry(key, &entry1));
+ ASSERT_NE(net::OK, OpenEntry(key, &entry1));
int actual = cache_->GetEntryCount();
if (num_entries != actual) {
@@ -991,25 +1107,25 @@
void DiskCacheBackendTest::BackendRecoverInsert() {
// Tests with an empty cache.
- BackendTransaction(L"insert_empty1", 0, false);
+ BackendTransaction("insert_empty1", 0, false);
ASSERT_TRUE(success_) << "insert_empty1";
- BackendTransaction(L"insert_empty2", 0, false);
+ BackendTransaction("insert_empty2", 0, false);
ASSERT_TRUE(success_) << "insert_empty2";
- BackendTransaction(L"insert_empty3", 0, false);
+ BackendTransaction("insert_empty3", 0, false);
ASSERT_TRUE(success_) << "insert_empty3";
// Tests with one entry on the cache.
- BackendTransaction(L"insert_one1", 1, false);
+ BackendTransaction("insert_one1", 1, false);
ASSERT_TRUE(success_) << "insert_one1";
- BackendTransaction(L"insert_one2", 1, false);
+ BackendTransaction("insert_one2", 1, false);
ASSERT_TRUE(success_) << "insert_one2";
- BackendTransaction(L"insert_one3", 1, false);
+ BackendTransaction("insert_one3", 1, false);
ASSERT_TRUE(success_) << "insert_one3";
// Tests with one hundred entries on the cache, tiny index.
- BackendTransaction(L"insert_load1", 100, true);
+ BackendTransaction("insert_load1", 100, true);
ASSERT_TRUE(success_) << "insert_load1";
- BackendTransaction(L"insert_load2", 100, true);
+ BackendTransaction("insert_load2", 100, true);
ASSERT_TRUE(success_) << "insert_load2";
}
@@ -1024,42 +1140,42 @@
void DiskCacheBackendTest::BackendRecoverRemove() {
// Removing the only element.
- BackendTransaction(L"remove_one1", 0, false);
+ BackendTransaction("remove_one1", 0, false);
ASSERT_TRUE(success_) << "remove_one1";
- BackendTransaction(L"remove_one2", 0, false);
+ BackendTransaction("remove_one2", 0, false);
ASSERT_TRUE(success_) << "remove_one2";
- BackendTransaction(L"remove_one3", 0, false);
+ BackendTransaction("remove_one3", 0, false);
ASSERT_TRUE(success_) << "remove_one3";
// Removing the head.
- BackendTransaction(L"remove_head1", 1, false);
+ BackendTransaction("remove_head1", 1, false);
ASSERT_TRUE(success_) << "remove_head1";
- BackendTransaction(L"remove_head2", 1, false);
+ BackendTransaction("remove_head2", 1, false);
ASSERT_TRUE(success_) << "remove_head2";
- BackendTransaction(L"remove_head3", 1, false);
+ BackendTransaction("remove_head3", 1, false);
ASSERT_TRUE(success_) << "remove_head3";
// Removing the tail.
- BackendTransaction(L"remove_tail1", 1, false);
+ BackendTransaction("remove_tail1", 1, false);
ASSERT_TRUE(success_) << "remove_tail1";
- BackendTransaction(L"remove_tail2", 1, false);
+ BackendTransaction("remove_tail2", 1, false);
ASSERT_TRUE(success_) << "remove_tail2";
- BackendTransaction(L"remove_tail3", 1, false);
+ BackendTransaction("remove_tail3", 1, false);
ASSERT_TRUE(success_) << "remove_tail3";
// Removing with one hundred entries on the cache, tiny index.
- BackendTransaction(L"remove_load1", 100, true);
+ BackendTransaction("remove_load1", 100, true);
ASSERT_TRUE(success_) << "remove_load1";
- BackendTransaction(L"remove_load2", 100, true);
+ BackendTransaction("remove_load2", 100, true);
ASSERT_TRUE(success_) << "remove_load2";
- BackendTransaction(L"remove_load3", 100, true);
+ BackendTransaction("remove_load3", 100, true);
ASSERT_TRUE(success_) << "remove_load3";
#ifdef NDEBUG
// This case cannot be reverted, so it will assert on debug builds.
- BackendTransaction(L"remove_one4", 0, false);
+ BackendTransaction("remove_one4", 0, false);
ASSERT_TRUE(success_) << "remove_one4";
- BackendTransaction(L"remove_head4", 1, false);
+ BackendTransaction("remove_head4", 1, false);
ASSERT_TRUE(success_) << "remove_head4";
#endif
}
@@ -1074,31 +1190,37 @@
}
// Tests dealing with cache files that cannot be recovered.
-TEST_F(DiskCacheTest, Backend_DeleteOld) {
- ASSERT_TRUE(CopyTestCache(L"wrong_version"));
+TEST_F(DiskCacheTest, DeleteOld) {
+ ASSERT_TRUE(CopyTestCache("wrong_version"));
FilePath path = GetCacheFilePath();
- scoped_ptr<disk_cache::Backend> cache;
- cache.reset(disk_cache::CreateCacheBackend(path, true, 0, net::DISK_CACHE));
+ base::Thread cache_thread("CacheThread");
+ ASSERT_TRUE(cache_thread.StartWithOptions(
+ base::Thread::Options(MessageLoop::TYPE_IO, 0)));
+ TestCompletionCallback cb;
+
+ disk_cache::Backend* cache;
+ int rv = disk_cache::BackendImpl::CreateBackend(
+ path, true, 0, net::DISK_CACHE, disk_cache::kNoRandom,
+ cache_thread.message_loop_proxy(), &cache, &cb);
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
MessageLoopHelper helper;
- ASSERT_TRUE(NULL != cache.get());
+ ASSERT_TRUE(NULL != cache);
ASSERT_EQ(0, cache->GetEntryCount());
- // Wait for a callback that never comes... about 2 secs :). The message loop
- // has to run to allow destruction of the cleaner thread.
- helper.WaitUntilCacheIoFinished(1);
+ delete cache;
}
// We want to be able to deal with messed up entries on disk.
void DiskCacheBackendTest::BackendInvalidEntry2() {
- ASSERT_TRUE(CopyTestCache(L"bad_entry"));
+ ASSERT_TRUE(CopyTestCache("bad_entry"));
DisableFirstCleanup();
InitCache();
disk_cache::Entry *entry1, *entry2;
- ASSERT_TRUE(cache_->OpenEntry("the first key", &entry1));
- EXPECT_FALSE(cache_->OpenEntry("some other key", &entry2));
+ ASSERT_EQ(net::OK, OpenEntry("the first key", &entry1));
+ EXPECT_NE(net::OK, OpenEntry("some other key", &entry2));
entry1->Close();
// CheckCacheIntegrity will fail at this point.
@@ -1115,45 +1237,45 @@
}
// We want to be able to deal with abnormal dirty entries.
-void DiskCacheBackendTest::BackendNotMarkedButDirty(const std::wstring& name) {
+void DiskCacheBackendTest::BackendNotMarkedButDirty(const std::string& name) {
ASSERT_TRUE(CopyTestCache(name));
DisableFirstCleanup();
InitCache();
disk_cache::Entry *entry1, *entry2;
- ASSERT_TRUE(cache_->OpenEntry("the first key", &entry1));
- EXPECT_FALSE(cache_->OpenEntry("some other key", &entry2));
+ ASSERT_EQ(net::OK, OpenEntry("the first key", &entry1));
+ EXPECT_NE(net::OK, OpenEntry("some other key", &entry2));
entry1->Close();
}
TEST_F(DiskCacheBackendTest, NotMarkedButDirty) {
- BackendNotMarkedButDirty(L"dirty_entry");
+ BackendNotMarkedButDirty("dirty_entry");
}
TEST_F(DiskCacheBackendTest, NewEvictionNotMarkedButDirty) {
SetNewEviction();
- BackendNotMarkedButDirty(L"dirty_entry");
+ BackendNotMarkedButDirty("dirty_entry");
}
TEST_F(DiskCacheBackendTest, NotMarkedButDirty2) {
- BackendNotMarkedButDirty(L"dirty_entry2");
+ BackendNotMarkedButDirty("dirty_entry2");
}
TEST_F(DiskCacheBackendTest, NewEvictionNotMarkedButDirty2) {
SetNewEviction();
- BackendNotMarkedButDirty(L"dirty_entry2");
+ BackendNotMarkedButDirty("dirty_entry2");
}
// We want to be able to deal with messed up entries on disk.
void DiskCacheBackendTest::BackendInvalidRankings2() {
- ASSERT_TRUE(CopyTestCache(L"bad_rankings"));
+ ASSERT_TRUE(CopyTestCache("bad_rankings"));
FilePath path = GetCacheFilePath();
DisableFirstCleanup();
InitCache();
disk_cache::Entry *entry1, *entry2;
- EXPECT_FALSE(cache_->OpenEntry("the first key", &entry1));
- ASSERT_TRUE(cache_->OpenEntry("some other key", &entry2));
+ EXPECT_NE(net::OK, OpenEntry("the first key", &entry1));
+ ASSERT_EQ(net::OK, OpenEntry("some other key", &entry2));
entry2->Close();
// CheckCacheIntegrity will fail at this point.
@@ -1173,17 +1295,17 @@
void DiskCacheBackendTest::BackendInvalidRankings() {
disk_cache::Entry* entry;
void* iter = NULL;
- ASSERT_TRUE(cache_->OpenNextEntry(&iter, &entry));
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter, &entry));
entry->Close();
EXPECT_EQ(2, cache_->GetEntryCount());
- EXPECT_FALSE(cache_->OpenNextEntry(&iter, &entry));
- MessageLoop::current()->RunAllPending();
+ EXPECT_NE(net::OK, OpenNextEntry(&iter, &entry));
+ FlushQueueForTest(); // Allow the restart to finish.
EXPECT_EQ(0, cache_->GetEntryCount());
}
TEST_F(DiskCacheBackendTest, InvalidRankingsSuccess) {
- ASSERT_TRUE(CopyTestCache(L"bad_rankings"));
+ ASSERT_TRUE(CopyTestCache("bad_rankings"));
DisableFirstCleanup();
SetDirectMode();
InitCache();
@@ -1191,7 +1313,7 @@
}
TEST_F(DiskCacheBackendTest, NewEvictionInvalidRankingsSuccess) {
- ASSERT_TRUE(CopyTestCache(L"bad_rankings"));
+ ASSERT_TRUE(CopyTestCache("bad_rankings"));
DisableFirstCleanup();
SetDirectMode();
SetNewEviction();
@@ -1200,7 +1322,7 @@
}
TEST_F(DiskCacheBackendTest, InvalidRankingsFailure) {
- ASSERT_TRUE(CopyTestCache(L"bad_rankings"));
+ ASSERT_TRUE(CopyTestCache("bad_rankings"));
DisableFirstCleanup();
SetDirectMode();
InitCache();
@@ -1209,7 +1331,7 @@
}
TEST_F(DiskCacheBackendTest, NewEvictionInvalidRankingsFailure) {
- ASSERT_TRUE(CopyTestCache(L"bad_rankings"));
+ ASSERT_TRUE(CopyTestCache("bad_rankings"));
DisableFirstCleanup();
SetDirectMode();
SetNewEviction();
@@ -1222,20 +1344,21 @@
void DiskCacheBackendTest::BackendDisable() {
disk_cache::Entry *entry1, *entry2;
void* iter = NULL;
- ASSERT_TRUE(cache_->OpenNextEntry(&iter, &entry1));
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter, &entry1));
- EXPECT_FALSE(cache_->OpenNextEntry(&iter, &entry2));
+ EXPECT_NE(net::OK, OpenNextEntry(&iter, &entry2));
EXPECT_EQ(2, cache_->GetEntryCount());
- EXPECT_FALSE(cache_->CreateEntry("Something new", &entry2));
+ EXPECT_NE(net::OK, CreateEntry("Something new", &entry2));
entry1->Close();
- MessageLoop::current()->RunAllPending();
+ FlushQueueForTest(); // Flushing the Close posts a task to restart the cache.
+ FlushQueueForTest(); // This one actually allows that task to complete.
EXPECT_EQ(0, cache_->GetEntryCount());
}
TEST_F(DiskCacheBackendTest, DisableSuccess) {
- ASSERT_TRUE(CopyTestCache(L"bad_rankings"));
+ ASSERT_TRUE(CopyTestCache("bad_rankings"));
DisableFirstCleanup();
SetDirectMode();
InitCache();
@@ -1243,7 +1366,7 @@
}
TEST_F(DiskCacheBackendTest, NewEvictionDisableSuccess) {
- ASSERT_TRUE(CopyTestCache(L"bad_rankings"));
+ ASSERT_TRUE(CopyTestCache("bad_rankings"));
DisableFirstCleanup();
SetDirectMode();
SetNewEviction();
@@ -1252,7 +1375,7 @@
}
TEST_F(DiskCacheBackendTest, DisableFailure) {
- ASSERT_TRUE(CopyTestCache(L"bad_rankings"));
+ ASSERT_TRUE(CopyTestCache("bad_rankings"));
DisableFirstCleanup();
SetDirectMode();
InitCache();
@@ -1261,7 +1384,7 @@
}
TEST_F(DiskCacheBackendTest, NewEvictionDisableFailure) {
- ASSERT_TRUE(CopyTestCache(L"bad_rankings"));
+ ASSERT_TRUE(CopyTestCache("bad_rankings"));
DisableFirstCleanup();
SetDirectMode();
SetNewEviction();
@@ -1277,19 +1400,19 @@
disk_cache::Entry* entry;
void* iter = NULL;
int count = 0;
- while (cache_->OpenNextEntry(&iter, &entry)) {
+ while (OpenNextEntry(&iter, &entry) == net::OK) {
ASSERT_TRUE(NULL != entry);
entry->Close();
count++;
ASSERT_LT(count, 9);
};
- MessageLoop::current()->RunAllPending();
+ FlushQueueForTest();
EXPECT_EQ(0, cache_->GetEntryCount());
}
TEST_F(DiskCacheBackendTest, DisableSuccess2) {
- ASSERT_TRUE(CopyTestCache(L"list_loop"));
+ ASSERT_TRUE(CopyTestCache("list_loop"));
DisableFirstCleanup();
SetDirectMode();
InitCache();
@@ -1297,7 +1420,7 @@
}
TEST_F(DiskCacheBackendTest, NewEvictionDisableSuccess2) {
- ASSERT_TRUE(CopyTestCache(L"list_loop"));
+ ASSERT_TRUE(CopyTestCache("list_loop"));
DisableFirstCleanup();
SetNewEviction();
SetDirectMode();
@@ -1306,7 +1429,7 @@
}
TEST_F(DiskCacheBackendTest, DisableFailure2) {
- ASSERT_TRUE(CopyTestCache(L"list_loop"));
+ ASSERT_TRUE(CopyTestCache("list_loop"));
DisableFirstCleanup();
SetDirectMode();
InitCache();
@@ -1315,7 +1438,7 @@
}
TEST_F(DiskCacheBackendTest, NewEvictionDisableFailure2) {
- ASSERT_TRUE(CopyTestCache(L"list_loop"));
+ ASSERT_TRUE(CopyTestCache("list_loop"));
DisableFirstCleanup();
SetDirectMode();
SetNewEviction();
@@ -1329,20 +1452,20 @@
disk_cache::Entry *entry1, *entry2;
void* iter = NULL;
EXPECT_EQ(2, cache_->GetEntryCount());
- ASSERT_TRUE(cache_->OpenNextEntry(&iter, &entry1));
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter, &entry1));
entry1->Close();
- EXPECT_FALSE(cache_->OpenNextEntry(&iter, &entry2));
- MessageLoop::current()->RunAllPending();
+ EXPECT_NE(net::OK, OpenNextEntry(&iter, &entry2));
+ FlushQueueForTest();
- ASSERT_TRUE(cache_->CreateEntry("Something new", &entry2));
+ ASSERT_EQ(net::OK, CreateEntry("Something new", &entry2));
entry2->Close();
EXPECT_EQ(1, cache_->GetEntryCount());
}
TEST_F(DiskCacheBackendTest, DisableSuccess3) {
- ASSERT_TRUE(CopyTestCache(L"bad_rankings2"));
+ ASSERT_TRUE(CopyTestCache("bad_rankings2"));
DisableFirstCleanup();
SetMaxSize(20 * 1024 * 1024);
InitCache();
@@ -1350,7 +1473,7 @@
}
TEST_F(DiskCacheBackendTest, NewEvictionDisableSuccess3) {
- ASSERT_TRUE(CopyTestCache(L"bad_rankings2"));
+ ASSERT_TRUE(CopyTestCache("bad_rankings2"));
DisableFirstCleanup();
SetMaxSize(20 * 1024 * 1024);
SetNewEviction();
@@ -1362,7 +1485,7 @@
void DiskCacheBackendTest::BackendDisable4() {
disk_cache::Entry *entry1, *entry2, *entry3, *entry4;
void* iter = NULL;
- ASSERT_TRUE(cache_->OpenNextEntry(&iter, &entry1));
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter, &entry1));
char key2[2000];
char key3[20000];
@@ -1370,8 +1493,8 @@
CacheTestFillBuffer(key3, sizeof(key3), true);
key2[sizeof(key2) - 1] = '\0';
key3[sizeof(key3) - 1] = '\0';
- ASSERT_TRUE(cache_->CreateEntry(key2, &entry2));
- ASSERT_TRUE(cache_->CreateEntry(key3, &entry3));
+ ASSERT_EQ(net::OK, CreateEntry(key2, &entry2));
+ ASSERT_EQ(net::OK, CreateEntry(key3, &entry3));
const int kBufSize = 20000;
scoped_refptr<net::IOBuffer> buf = new net::IOBuffer(kBufSize);
@@ -1380,10 +1503,10 @@
EXPECT_EQ(kBufSize, entry3->WriteData(0, 0, buf, kBufSize, NULL, false));
// This line should disable the cache but not delete it.
- EXPECT_FALSE(cache_->OpenNextEntry(&iter, &entry4));
+ EXPECT_NE(net::OK, OpenNextEntry(&iter, &entry4));
EXPECT_EQ(4, cache_->GetEntryCount());
- EXPECT_FALSE(cache_->CreateEntry("cache is disabled", &entry4));
+ EXPECT_NE(net::OK, CreateEntry("cache is disabled", &entry4));
EXPECT_EQ(100, entry2->ReadData(0, 0, buf, 100, NULL));
EXPECT_EQ(100, entry2->WriteData(0, 0, buf, 100, NULL, false));
@@ -1401,23 +1524,22 @@
entry1->Close();
entry2->Close();
entry3->Close();
- MessageLoop::current()->RunAllPending();
+ FlushQueueForTest(); // Flushing the Close posts a task to restart the cache.
+ FlushQueueForTest(); // This one actually allows that task to complete.
EXPECT_EQ(0, cache_->GetEntryCount());
}
TEST_F(DiskCacheBackendTest, DisableSuccess4) {
- ASSERT_TRUE(CopyTestCache(L"bad_rankings"));
+ ASSERT_TRUE(CopyTestCache("bad_rankings"));
DisableFirstCleanup();
SetDirectMode();
InitCache();
BackendDisable4();
}
-// Flaky, http://crbug.com/21110.
-// TODO(rvargas): Add more debugging code to help identify the root cause.
-TEST_F(DiskCacheBackendTest, FLAKY_NewEvictionDisableSuccess4) {
- ASSERT_TRUE(CopyTestCache(L"bad_rankings"));
+TEST_F(DiskCacheBackendTest, NewEvictionDisableSuccess4) {
+ ASSERT_TRUE(CopyTestCache("bad_rankings"));
DisableFirstCleanup();
SetDirectMode();
SetNewEviction();
@@ -1431,10 +1553,11 @@
FilePath path = GetCacheFilePath();
ASSERT_TRUE(DeleteCache(path));
scoped_ptr<disk_cache::BackendImpl> cache;
- cache.reset(new disk_cache::BackendImpl(path));
+ cache.reset(new disk_cache::BackendImpl(
+ path, base::MessageLoopProxy::CreateForCurrentThread()));
ASSERT_TRUE(NULL != cache.get());
cache->SetUnitTestMode();
- ASSERT_TRUE(cache->Init());
+ ASSERT_EQ(net::OK, cache->SyncInit());
// Wait for a callback that never comes... about 2 secs :). The message loop
// has to run to allow invocation of the usage timer.
@@ -1446,23 +1569,26 @@
Time initial = Time::Now();
disk_cache::Entry *entry1, *entry2;
- ASSERT_TRUE(cache_->CreateEntry("first", &entry1));
- ASSERT_TRUE(cache_->CreateEntry("second", &entry2));
+ ASSERT_EQ(net::OK, CreateEntry("first", &entry1));
+ ASSERT_EQ(net::OK, CreateEntry("second", &entry2));
entry1->Close();
entry2->Close();
- ASSERT_TRUE(cache_->CreateEntry("third", &entry1));
- ASSERT_TRUE(cache_->CreateEntry("fourth", &entry2));
+ ASSERT_EQ(net::OK, CreateEntry("third", &entry1));
+ ASSERT_EQ(net::OK, CreateEntry("fourth", &entry2));
ASSERT_EQ(4, cache_->GetEntryCount());
- EXPECT_TRUE(cache_->DoomAllEntries());
+ EXPECT_EQ(net::OK, DoomAllEntries());
ASSERT_EQ(0, cache_->GetEntryCount());
- disk_cache::Entry *entry3, *entry4;
- ASSERT_TRUE(cache_->CreateEntry("third", &entry3));
- ASSERT_TRUE(cache_->CreateEntry("fourth", &entry4));
+ // We should stop posting tasks at some point (if we post any).
+ MessageLoop::current()->RunAllPending();
- EXPECT_TRUE(cache_->DoomAllEntries());
+ disk_cache::Entry *entry3, *entry4;
+ ASSERT_EQ(net::OK, CreateEntry("third", &entry3));
+ ASSERT_EQ(net::OK, CreateEntry("fourth", &entry4));
+
+ EXPECT_EQ(net::OK, DoomAllEntries());
ASSERT_EQ(0, cache_->GetEntryCount());
entry1->Close();
@@ -1472,13 +1598,13 @@
entry4->Close();
// Now try with all references released.
- ASSERT_TRUE(cache_->CreateEntry("third", &entry1));
- ASSERT_TRUE(cache_->CreateEntry("fourth", &entry2));
+ ASSERT_EQ(net::OK, CreateEntry("third", &entry1));
+ ASSERT_EQ(net::OK, CreateEntry("fourth", &entry2));
entry1->Close();
entry2->Close();
ASSERT_EQ(2, cache_->GetEntryCount());
- EXPECT_TRUE(cache_->DoomAllEntries());
+ EXPECT_EQ(net::OK, DoomAllEntries());
ASSERT_EQ(0, cache_->GetEntryCount());
}
@@ -1499,17 +1625,17 @@
// If the index size changes when we doom the cache, we should not crash.
void DiskCacheBackendTest::BackendDoomAll2() {
EXPECT_EQ(2, cache_->GetEntryCount());
- EXPECT_TRUE(cache_->DoomAllEntries());
+ EXPECT_EQ(net::OK, DoomAllEntries());
disk_cache::Entry* entry;
- ASSERT_TRUE(cache_->CreateEntry("Something new", &entry));
+ ASSERT_EQ(net::OK, CreateEntry("Something new", &entry));
entry->Close();
EXPECT_EQ(1, cache_->GetEntryCount());
}
TEST_F(DiskCacheBackendTest, DoomAll2) {
- ASSERT_TRUE(CopyTestCache(L"bad_rankings2"));
+ ASSERT_TRUE(CopyTestCache("bad_rankings2"));
DisableFirstCleanup();
SetMaxSize(20 * 1024 * 1024);
InitCache();
@@ -1517,7 +1643,7 @@
}
TEST_F(DiskCacheBackendTest, NewEvictionDoomAll2) {
- ASSERT_TRUE(CopyTestCache(L"bad_rankings2"));
+ ASSERT_TRUE(CopyTestCache("bad_rankings2"));
DisableFirstCleanup();
SetMaxSize(20 * 1024 * 1024);
SetNewEviction();
@@ -1531,51 +1657,93 @@
ScopedTestCache store1;
ScopedTestCache store2("cache_test2");
ScopedTestCache store3("cache_test3");
+ base::Thread cache_thread("CacheThread");
+ ASSERT_TRUE(cache_thread.StartWithOptions(
+ base::Thread::Options(MessageLoop::TYPE_IO, 0)));
+ TestCompletionCallback cb;
const int kNumberOfCaches = 2;
- scoped_ptr<disk_cache::Backend> cache[kNumberOfCaches];
+ disk_cache::Backend* cache[kNumberOfCaches];
- cache[0].reset(disk_cache::CreateCacheBackend(store1.path(), false, 0,
- net::DISK_CACHE));
- cache[1].reset(disk_cache::CreateCacheBackend(store2.path(), false, 0,
- net::MEDIA_CACHE));
+ int rv = disk_cache::BackendImpl::CreateBackend(
+ store1.path(), false, 0, net::DISK_CACHE, disk_cache::kNone,
+ cache_thread.message_loop_proxy(), &cache[0], &cb);
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
+ rv = disk_cache::BackendImpl::CreateBackend(
+ store2.path(), false, 0, net::MEDIA_CACHE, disk_cache::kNone,
+ cache_thread.message_loop_proxy(), &cache[1], &cb);
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
- ASSERT_TRUE(cache[0].get() != NULL && cache[1].get() != NULL);
+ ASSERT_TRUE(cache[0] != NULL && cache[1] != NULL);
std::string key("the first key");
disk_cache::Entry* entry;
for (int i = 0; i < kNumberOfCaches; i++) {
- ASSERT_TRUE(cache[i]->CreateEntry(key, &entry));
+ rv = cache[i]->CreateEntry(key, &entry, &cb);
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
entry->Close();
}
+ delete cache[0];
+ delete cache[1];
}
-// Test the four regions of the curve that determines the max cache size.
+// Test the six regions of the curve that determines the max cache size.
TEST_F(DiskCacheTest, AutomaticMaxSize) {
const int kDefaultSize = 80 * 1024 * 1024;
int64 large_size = kDefaultSize;
+ int64 largest_size = kint32max;
- EXPECT_EQ(kDefaultSize, disk_cache::PreferedCacheSize(large_size));
- EXPECT_EQ((kDefaultSize / 2) * 8 / 10,
- disk_cache::PreferedCacheSize(large_size / 2));
+ // Region 1: expected = available * 0.8
+ EXPECT_EQ((kDefaultSize - 1) * 8 / 10,
+ disk_cache::PreferedCacheSize(large_size - 1));
+ EXPECT_EQ(kDefaultSize * 8 / 10,
+ disk_cache::PreferedCacheSize(large_size));
+ EXPECT_EQ(kDefaultSize - 1,
+ disk_cache::PreferedCacheSize(large_size * 10 / 8 - 1));
- EXPECT_EQ(kDefaultSize, disk_cache::PreferedCacheSize(large_size * 2));
- EXPECT_EQ(kDefaultSize, disk_cache::PreferedCacheSize(large_size * 4));
- EXPECT_EQ(kDefaultSize, disk_cache::PreferedCacheSize(large_size * 10));
+ // Region 2: expected = default_size
+ EXPECT_EQ(kDefaultSize,
+ disk_cache::PreferedCacheSize(large_size * 10 / 8));
+ EXPECT_EQ(kDefaultSize,
+ disk_cache::PreferedCacheSize(large_size * 10 - 1));
- EXPECT_EQ(kDefaultSize * 2, disk_cache::PreferedCacheSize(large_size * 20));
- EXPECT_EQ(kDefaultSize * 5 / 2,
- disk_cache::PreferedCacheSize(large_size * 50 / 2));
+ // Region 3: expected = available * 0.1
+ EXPECT_EQ(kDefaultSize,
+ disk_cache::PreferedCacheSize(large_size * 10));
+ EXPECT_EQ((kDefaultSize * 25 - 1) / 10,
+ disk_cache::PreferedCacheSize(large_size * 25 - 1));
- EXPECT_EQ(kDefaultSize * 5 / 2,
- disk_cache::PreferedCacheSize(large_size * 51 / 2));
- EXPECT_EQ(kDefaultSize * 5 / 2,
- disk_cache::PreferedCacheSize(large_size * 100 / 2));
- EXPECT_EQ(kDefaultSize * 5 / 2,
- disk_cache::PreferedCacheSize(large_size * 500 / 2));
+ // Region 4: expected = default_size * 2.5
+ EXPECT_EQ(kDefaultSize * 25 / 10,
+ disk_cache::PreferedCacheSize(large_size * 25));
+ EXPECT_EQ(kDefaultSize * 25 / 10,
+ disk_cache::PreferedCacheSize(large_size * 100 - 1));
+ EXPECT_EQ(kDefaultSize * 25 / 10,
+ disk_cache::PreferedCacheSize(large_size * 100));
+ EXPECT_EQ(kDefaultSize * 25 / 10,
+ disk_cache::PreferedCacheSize(large_size * 250 - 1));
- EXPECT_EQ(kDefaultSize * 6 / 2,
- disk_cache::PreferedCacheSize(large_size * 600 / 2));
- EXPECT_EQ(kDefaultSize * 7 / 2,
- disk_cache::PreferedCacheSize(large_size * 700 / 2));
+ // Region 5: expected = available * 0.1
+ EXPECT_EQ(kDefaultSize * 25 / 10,
+ disk_cache::PreferedCacheSize(large_size * 250));
+ EXPECT_EQ(kint32max - 1,
+ disk_cache::PreferedCacheSize(largest_size * 100 - 1));
+
+ // Region 6: expected = kint32max
+ EXPECT_EQ(kint32max,
+ disk_cache::PreferedCacheSize(largest_size * 100));
+ EXPECT_EQ(kint32max,
+ disk_cache::PreferedCacheSize(largest_size * 10000));
+}
+
+// Tests that we can "migrate" a running instance from one experiment group to
+// another.
+TEST_F(DiskCacheBackendTest, Histograms) {
+ SetDirectMode();
+ InitCache();
+ disk_cache::BackendImpl* backend_ = cache_impl_; // Needed be the macro.
+
+ for (int i = 1; i < 3; i++) {
+ CACHE_UMA(HOURS, "FillupTime", i, 28);
+ }
}
diff --git a/net/disk_cache/block_files.cc b/net/disk_cache/block_files.cc
index fe02f67..48e5be3 100644
--- a/net/disk_cache/block_files.cc
+++ b/net/disk_cache/block_files.cc
@@ -11,7 +11,7 @@
#include "net/disk_cache/cache_util.h"
#include "net/disk_cache/file_lock.h"
-using base::Time;
+using base::TimeTicks;
namespace {
@@ -41,7 +41,7 @@
return false;
}
- Time start = Time::Now();
+ TimeTicks start = TimeTicks::Now();
// We are going to process the map on 32-block chunks (32 bits), and on every
// chunk, iterate through the 8 nibbles where the new block can be located.
int current = header->hints[target - 1];
@@ -67,7 +67,7 @@
if (target != size) {
header->empty[target - size - 1]++;
}
- HISTOGRAM_TIMES("DiskCache.CreateBlock", Time::Now() - start);
+ HISTOGRAM_TIMES("DiskCache.CreateBlock", TimeTicks::Now() - start);
return true;
}
}
@@ -86,7 +86,7 @@
NOTREACHED();
return;
}
- Time start = Time::Now();
+ TimeTicks start = TimeTicks::Now();
int byte_index = index / 8;
uint8* byte_map = reinterpret_cast<uint8*>(header->allocation_map);
uint8 map_block = byte_map[byte_index];
@@ -115,7 +115,7 @@
}
header->num_entries--;
DCHECK(header->num_entries >= 0);
- HISTOGRAM_TIMES("DiskCache.DeleteBlock", Time::Now() - start);
+ HISTOGRAM_TIMES("DiskCache.DeleteBlock", TimeTicks::Now() - start);
}
// Restores the "empty counters" and allocation hints.
@@ -172,8 +172,8 @@
if (init_)
return false;
- block_files_.resize(kFirstAdditionlBlockFile);
- for (int i = 0; i < kFirstAdditionlBlockFile; i++) {
+ block_files_.resize(kFirstAdditionalBlockFile);
+ for (int i = 0; i < kFirstAdditionalBlockFile; i++) {
if (create_files)
if (!CreateBlockFile(i, static_cast<FileType>(i + 1), true))
return false;
@@ -200,11 +200,21 @@
block_files_.clear();
}
-FilePath BlockFiles::Name(int index) {
- // The file format allows for 256 files.
- DCHECK(index < 256 || index >= 0);
- std::string tmp = StringPrintf("%s%d", kBlockName, index);
- return path_.AppendASCII(tmp);
+void BlockFiles::ReportStats() {
+ int used_blocks[kFirstAdditionalBlockFile];
+ int load[kFirstAdditionalBlockFile];
+ for (int i = 0; i < kFirstAdditionalBlockFile; i++) {
+ GetFileStats(i, &used_blocks[i], &load[i]);
+ }
+ UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_0", used_blocks[0]);
+ UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_1", used_blocks[1]);
+ UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_2", used_blocks[2]);
+ UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_3", used_blocks[3]);
+
+ UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_0", load[0], 101);
+ UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_1", load[1], 101);
+ UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_2", load[2], 101);
+ UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_3", load[3], 101);
}
bool BlockFiles::CreateBlockFile(int index, FileType file_type, bool force) {
@@ -241,7 +251,8 @@
return false;
}
- if (file->GetLength() < static_cast<size_t>(kBlockHeaderSize)) {
+ size_t file_len = file->GetLength();
+ if (file_len < static_cast<size_t>(kBlockHeaderSize)) {
LOG(ERROR) << "File too small " << name.value();
return false;
}
@@ -258,6 +269,13 @@
return false;
}
+ if (index == 0) {
+ // Load the links file into memory with a single read.
+ scoped_array<char> buf(new char[file_len]);
+ if (!file->Read(buf.get(), file_len, 0))
+ return false;
+ }
+
DCHECK(!block_files_[index]);
file.swap(&block_files_[index]);
return true;
@@ -314,7 +332,7 @@
MappedFile* file = block_files_[block_type - 1];
BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
- Time start = Time::Now();
+ TimeTicks start = TimeTicks::Now();
while (NeedToGrowBlockFile(header, block_count)) {
if (kMaxBlocks == header->max_entries) {
file = NextFile(file);
@@ -328,7 +346,7 @@
return NULL;
break;
}
- HISTOGRAM_TIMES("DiskCache.GetFileForNewBlock", Time::Now() - start);
+ HISTOGRAM_TIMES("DiskCache.GetFileForNewBlock", TimeTicks::Now() - start);
return file;
}
@@ -356,7 +374,7 @@
}
int BlockFiles::CreateNextBlockFile(FileType block_type) {
- for (int i = kFirstAdditionlBlockFile; i <= kMaxBlockFile; i++) {
+ for (int i = kFirstAdditionalBlockFile; i <= kMaxBlockFile; i++) {
if (CreateBlockFile(i, block_type, false))
return i;
}
@@ -485,4 +503,42 @@
return true;
}
+// We are interested in the total number of block used by this file type, and
+// the max number of blocks that we can store (reported as the percentage of
+// used blocks). In order to find out the number of used blocks, we have to
+// substract the empty blocks from the total blocks for each file in the chain.
+void BlockFiles::GetFileStats(int index, int* used_count, int* load) {
+ int max_blocks = 0;
+ *used_count = 0;
+ *load = 0;
+ for (;;) {
+ if (!block_files_[index] && !OpenBlockFile(index))
+ return;
+
+ BlockFileHeader* header =
+ reinterpret_cast<BlockFileHeader*>(block_files_[index]->buffer());
+
+ max_blocks += header->max_entries;
+ int used = header->max_entries;
+ for (int i = 0; i < 4; i++) {
+ used -= header->empty[i] * (i + 1);
+ DCHECK_GE(used, 0);
+ }
+ *used_count += used;
+
+ if (!header->next_file)
+ break;
+ index = header->next_file;
+ }
+ if (max_blocks)
+ *load = *used_count * 100 / max_blocks;
+}
+
+FilePath BlockFiles::Name(int index) {
+ // The file format allows for 256 files.
+ DCHECK(index < 256 || index >= 0);
+ std::string tmp = StringPrintf("%s%d", kBlockName, index);
+ return path_.AppendASCII(tmp);
+}
+
} // namespace disk_cache
diff --git a/net/disk_cache/block_files.h b/net/disk_cache/block_files.h
index 8503062..3ed19de 100644
--- a/net/disk_cache/block_files.h
+++ b/net/disk_cache/block_files.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -46,6 +46,9 @@
// cache is being purged.
void CloseFiles();
+ // Sends UMA stats.
+ void ReportStats();
+
private:
// Set force to true to overwrite the file if it exists.
bool CreateBlockFile(int index, FileType file_type, bool force);
@@ -69,6 +72,9 @@
// Restores the header of a potentially inconsistent file.
bool FixBlockFileHeader(MappedFile* file);
+ // Retrieves stats for the given file index.
+ void GetFileStats(int index, int* used_count, int* load);
+
// Returns the filename for a given file index.
FilePath Name(int index);
@@ -79,8 +85,9 @@
FRIEND_TEST(DiskCacheTest, BlockFiles_ZeroSizeFile);
FRIEND_TEST(DiskCacheTest, BlockFiles_InvalidFile);
+ FRIEND_TEST(DiskCacheTest, BlockFiles_Stats);
- DISALLOW_EVIL_CONSTRUCTORS(BlockFiles);
+ DISALLOW_COPY_AND_ASSIGN(BlockFiles);
};
} // namespace disk_cache
diff --git a/net/disk_cache/block_files_unittest.cc b/net/disk_cache/block_files_unittest.cc
index 054641c..2c87179 100644
--- a/net/disk_cache/block_files_unittest.cc
+++ b/net/disk_cache/block_files_unittest.cc
@@ -203,4 +203,26 @@
EXPECT_TRUE(NULL == files.GetFile(addr));
}
+// Tests that we generate the correct file stats.
+TEST_F(DiskCacheTest, BlockFiles_Stats) {
+ ASSERT_TRUE(CopyTestCache("remove_load1"));
+ FilePath path = GetCacheFilePath();
+
+ BlockFiles files(path);
+ ASSERT_TRUE(files.Init(false));
+ int used, load;
+
+ files.GetFileStats(0, &used, &load);
+ EXPECT_EQ(101, used);
+ EXPECT_EQ(9, load);
+
+ files.GetFileStats(1, &used, &load);
+ EXPECT_EQ(203, used);
+ EXPECT_EQ(19, load);
+
+ files.GetFileStats(2, &used, &load);
+ EXPECT_EQ(0, used);
+ EXPECT_EQ(0, load);
+}
+
} // namespace disk_cache
diff --git a/net/disk_cache/cache_util_posix.cc b/net/disk_cache/cache_util_posix.cc
index a272cb8..b393747 100644
--- a/net/disk_cache/cache_util_posix.cc
+++ b/net/disk_cache/cache_util_posix.cc
@@ -20,13 +20,17 @@
/* recursive */ false,
file_util::FileEnumerator::FILES);
for (FilePath file = iter.Next(); !file.value().empty(); file = iter.Next()) {
- if (!file_util::Delete(file, /* recursive */ false))
- NOTREACHED();
+ if (!file_util::Delete(file, /* recursive */ false)) {
+ LOG(WARNING) << "Unable to delete cache.";
+ return;
+ }
}
if (remove_folder) {
- if (!file_util::Delete(path, /* recursive */ false))
- NOTREACHED();
+ if (!file_util::Delete(path, /* recursive */ false)) {
+ LOG(WARNING) << "Unable to delete cache folder.";
+ return;
+ }
}
}
diff --git a/net/disk_cache/disk_cache.h b/net/disk_cache/disk_cache.h
index 3d2793b..b2d76a4 100644
--- a/net/disk_cache/disk_cache.h
+++ b/net/disk_cache/disk_cache.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Copyright (c) 2006-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.
@@ -18,6 +18,10 @@
class FilePath;
+namespace base {
+class MessageLoopProxy;
+}
+
namespace net {
class IOBuffer;
}
@@ -28,27 +32,6 @@
class Backend;
typedef net::CompletionCallback CompletionCallback;
-// Returns an instance of the Backend. path points to a folder where
-// the cached data will be stored. This cache instance must be the only object
-// that will be reading or writing files to that folder. The returned object
-// should be deleted when not needed anymore. If force is true, and there is
-// a problem with the cache initialization, the files will be deleted and a
-// new set will be created. max_bytes is the maximum size the cache can grow to.
-// If zero is passed in as max_bytes, the cache will determine the value to use
-// based on the available disk space. The returned pointer can be NULL if a
-// fatal error is found.
-// Note: This function is deprecated.
-Backend* CreateCacheBackend(const FilePath& path, bool force,
- int max_bytes, net::CacheType type);
-
-// Returns an instance of a Backend implemented only in memory. The returned
-// object should be deleted when not needed anymore. max_bytes is the maximum
-// size the cache can grow to. If zero is passed in as max_bytes, the cache will
-// determine the value to use based on the available memory. The returned
-// pointer can be NULL if a fatal error is found.
-// Note: This function is deprecated.
-Backend* CreateInMemoryCacheBackend(int max_bytes);
-
// Returns an instance of a Backend of the given |type|. |path| points to a
// folder where the cached data will be stored (if appropriate). This cache
// instance must be the only object that will be reading or writing files to
@@ -56,15 +39,17 @@
// If |force| is true, and there is a problem with the cache initialization, the
// files will be deleted and a new set will be created. |max_bytes| is the
// maximum size the cache can grow to. If zero is passed in as |max_bytes|, the
-// cache will determine the value to use. The returned pointer can be NULL if a
-// fatal error is found. The actual return value of the function is a net error
-// code. If this function returns ERR_IO_PENDING, the |callback| will be invoked
-// when a backend is available or a fatal error condition is reached. The
-// pointer to receive the |backend| must remain valid until the operation
-// completes.
+// cache will determine the value to use. |thread| can be used to perform IO
+// operations if a dedicated thread is required; a valid value is expected for
+// any backend that performs operations on a disk. The returned pointer can be
+// NULL if a fatal error is found. The actual return value of the function is a
+// net error code. If this function returns ERR_IO_PENDING, the |callback| will
+// be invoked when a backend is available or a fatal error condition is reached.
+// The pointer to receive the |backend| must remain valid until the operation
+// completes (the callback is notified).
int CreateCacheBackend(net::CacheType type, const FilePath& path, int max_bytes,
- bool force, Backend** backend,
- CompletionCallback* callback);
+ bool force, base::MessageLoopProxy* thread,
+ Backend** backend, CompletionCallback* callback);
// The root interface for a disk cache instance.
class Backend {
@@ -80,13 +65,6 @@
// Returns the number of entries in the cache.
virtual int32 GetEntryCount() const = 0;
- // Opens an existing entry. Upon success, the out param holds a pointer
- // to a Entry object representing the specified disk cache entry.
- // When the entry pointer is no longer needed, the Close method
- // should be called.
- // Note: This method is deprecated.
- virtual bool OpenEntry(const std::string& key, Entry** entry) = 0;
-
// Opens an existing entry. Upon success, |entry| holds a pointer to an Entry
// object representing the specified disk cache entry. When the entry pointer
// is no longer needed, its Close method should be called. The return value is
@@ -96,13 +74,6 @@
virtual int OpenEntry(const std::string& key, Entry** entry,
CompletionCallback* callback) = 0;
- // Creates a new entry. Upon success, the out param holds a pointer
- // to a Entry object representing the newly created disk cache
- // entry. When the entry pointer is no longer needed, the Close
- // method should be called.
- // Note: This method is deprecated.
- virtual bool CreateEntry(const std::string& key, Entry** entry) = 0;
-
// Creates a new entry. Upon success, the out param holds a pointer to an
// Entry object representing the newly created disk cache entry. When the
// entry pointer is no longer needed, its Close method should be called. The
@@ -112,32 +83,18 @@
virtual int CreateEntry(const std::string& key, Entry** entry,
CompletionCallback* callback) = 0;
- // Marks the entry, specified by the given key, for deletion.
- // Note: This method is deprecated.
- virtual bool DoomEntry(const std::string& key) = 0;
-
// Marks the entry, specified by the given key, for deletion. The return value
// is a net error code. If this method returns ERR_IO_PENDING, the |callback|
// will be invoked after the entry is doomed.
virtual int DoomEntry(const std::string& key,
CompletionCallback* callback) = 0;
- // Marks all entries for deletion.
- // Note: This method is deprecated.
- virtual bool DoomAllEntries() = 0;
-
// Marks all entries for deletion. The return value is a net error code. If
// this method returns ERR_IO_PENDING, the |callback| will be invoked when the
// operation completes.
virtual int DoomAllEntries(CompletionCallback* callback) = 0;
// Marks a range of entries for deletion. This supports unbounded deletes in
- // either direction by using null Time values for either argument.
- // Note: This method is deprecated.
- virtual bool DoomEntriesBetween(const base::Time initial_time,
- const base::Time end_time) = 0;
-
- // Marks a range of entries for deletion. This supports unbounded deletes in
// either direction by using null Time values for either argument. The return
// value is a net error code. If this method returns ERR_IO_PENDING, the
// |callback| will be invoked when the operation completes.
@@ -145,28 +102,12 @@
const base::Time end_time,
CompletionCallback* callback) = 0;
- // Marks all entries accessed since initial_time for deletion.
- // Note: This method is deprecated.
- virtual bool DoomEntriesSince(const base::Time initial_time) = 0;
-
// Marks all entries accessed since |initial_time| for deletion. The return
// value is a net error code. If this method returns ERR_IO_PENDING, the
// |callback| will be invoked when the operation completes.
virtual int DoomEntriesSince(const base::Time initial_time,
CompletionCallback* callback) = 0;
- // Enumerate the cache. Initialize iter to NULL before calling this method
- // the first time. That will cause the enumeration to start at the head of
- // the cache. For subsequent calls, pass the same iter pointer again without
- // changing its value. This method returns false when there are no more
- // entries to enumerate. When the entry pointer is no longer needed, the
- // Close method should be called.
- //
- // NOTE: This method does not modify the last_used field of the entry,
- // and therefore it does not impact the eviction ranking of the entry.
- // Note: This method is deprecated.
- virtual bool OpenNextEntry(void** iter, Entry** next_entry) = 0;
-
// Enumerates the cache. Initialize |iter| to NULL before calling this method
// the first time. That will cause the enumeration to start at the head of
// the cache. For subsequent calls, pass the same |iter| pointer again without
@@ -309,22 +250,22 @@
// first byte that is stored within this range, and the return value is the
// minimum number of consecutive stored bytes. Note that it is possible that
// this entry has stored more than the returned value. This method returns a
- // net error code whenever the request cannot be completed successfully.
- // Note: This method is deprecated.
- virtual int GetAvailableRange(int64 offset, int len, int64* start) = 0;
-
- // Returns information about the currently stored portion of a sparse entry.
- // |offset| and |len| describe a particular range that should be scanned to
- // find out if it is stored or not. |start| will contain the offset of the
- // first byte that is stored within this range, and the return value is the
- // minimum number of consecutive stored bytes. Note that it is possible that
- // this entry has stored more than the returned value. This method returns a
// net error code whenever the request cannot be completed successfully. If
// this method returns ERR_IO_PENDING, the |callback| will be invoked when the
// operation completes, and |start| must remain valid until that point.
virtual int GetAvailableRange(int64 offset, int len, int64* start,
CompletionCallback* callback) = 0;
+ // Returns true if this entry could be a sparse entry or false otherwise. This
+ // is a quick test that may return true even if the entry is not really
+ // sparse. This method doesn't modify the state of this entry (it will not
+ // create sparse tracking data). GetAvailableRange or ReadSparseData can be
+ // used to perfom a definitive test of wether an existing entry is sparse or
+ // not, but that method may modify the current state of the entry (making it
+ // sparse, for instance). The purpose of this method is to test an existing
+ // entry, but without generating actual IO to perform a thorough check.
+ virtual bool CouldBeSparse() const = 0;
+
// Cancels any pending sparse IO operation (if any). The completion callback
// of the operation in question will still be called when the operation
// finishes, but the operation will finish sooner when this method is used.
diff --git a/net/disk_cache/disk_cache_perftest.cc b/net/disk_cache/disk_cache_perftest.cc
index 4064341..1f1514d 100644
--- a/net/disk_cache/disk_cache_perftest.cc
+++ b/net/disk_cache/disk_cache_perftest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Copyright (c) 2006-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.
@@ -9,10 +9,12 @@
#include "base/file_util.h"
#include "base/perftimer.h"
#include "base/string_util.h"
+#include "base/thread.h"
#include "base/test/test_file_util.h"
#include "base/timer.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
#include "net/disk_cache/block_files.h"
#include "net/disk_cache/disk_cache.h"
#include "net/disk_cache/disk_cache_test_util.h"
@@ -64,7 +66,9 @@
entries->push_back(entry);
disk_cache::Entry* cache_entry;
- if (!cache->CreateEntry(entry.key, &cache_entry))
+ TestCompletionCallback cb;
+ int rv = cache->CreateEntry(entry.key, &cache_entry, &cb);
+ if (net::OK != cb.GetResult(rv))
break;
int ret = cache_entry->WriteData(0, 0, buffer1, kSize1, &callback, false);
if (net::ERR_IO_PENDING == ret)
@@ -110,7 +114,9 @@
for (int i = 0; i < num_entries; i++) {
disk_cache::Entry* cache_entry;
- if (!cache->OpenEntry(entries[i].key, &cache_entry))
+ TestCompletionCallback cb;
+ int rv = cache->OpenEntry(entries[i].key, &cache_entry, &cb);
+ if (net::OK != cb.GetResult(rv))
break;
int ret = cache_entry->ReadData(0, 0, buffer1, kSize1, &callback);
if (net::ERR_IO_PENDING == ret)
@@ -154,11 +160,18 @@
TEST_F(DiskCacheTest, CacheBackendPerformance) {
MessageLoopForIO message_loop;
+ base::Thread cache_thread("CacheThread");
+ ASSERT_TRUE(cache_thread.StartWithOptions(
+ base::Thread::Options(MessageLoop::TYPE_IO, 0)));
+
ScopedTestCache test_cache;
- disk_cache::Backend* cache =
- disk_cache::CreateCacheBackend(test_cache.path(), false, 0,
- net::DISK_CACHE);
- ASSERT_TRUE(NULL != cache);
+ TestCompletionCallback cb;
+ disk_cache::Backend* cache;
+ int rv = disk_cache::CreateCacheBackend(
+ net::DISK_CACHE, test_cache.path(), 0, false,
+ cache_thread.message_loop_proxy(), &cache, &cb);
+
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
int seed = static_cast<int>(Time::Now().ToInternalValue());
srand(seed);
@@ -183,9 +196,10 @@
ASSERT_TRUE(file_util::EvictFileFromSystemCache(
test_cache.path().AppendASCII("data_3")));
- cache = disk_cache::CreateCacheBackend(test_cache.path(), false, 0,
- net::DISK_CACHE);
- ASSERT_TRUE(NULL != cache);
+ rv = disk_cache::CreateCacheBackend(net::DISK_CACHE, test_cache.path(), 0,
+ false, cache_thread.message_loop_proxy(),
+ &cache, &cb);
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
ret = TimeRead(num_entries, cache, entries, true);
EXPECT_EQ(ret, g_cache_tests_received);
diff --git a/net/disk_cache/disk_cache_test_base.cc b/net/disk_cache/disk_cache_test_base.cc
index 21352a2..0add3c7 100644
--- a/net/disk_cache/disk_cache_test_base.cc
+++ b/net/disk_cache/disk_cache_test_base.cc
@@ -1,9 +1,11 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Copyright (c) 2006-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 "net/disk_cache/disk_cache_test_base.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
#include "net/disk_cache/backend_impl.h"
#include "net/disk_cache/disk_cache_test_util.h"
#include "net/disk_cache/mem_backend_impl.h"
@@ -37,7 +39,7 @@
void DiskCacheTestWithCache::InitMemoryCache() {
if (!implementation_) {
- cache_ = disk_cache::CreateInMemoryCacheBackend(size_);
+ cache_ = disk_cache::MemBackendImpl::CreateBackend(size_);
return;
}
@@ -56,19 +58,34 @@
if (first_cleanup_)
ASSERT_TRUE(DeleteCache(path));
+ if (!cache_thread_.IsRunning()) {
+ EXPECT_TRUE(cache_thread_.StartWithOptions(
+ base::Thread::Options(MessageLoop::TYPE_IO, 0)));
+ }
+ ASSERT_TRUE(cache_thread_.message_loop() != NULL);
+
if (implementation_)
return InitDiskCacheImpl(path);
- cache_ = disk_cache::BackendImpl::CreateBackend(path, force_creation_, size_,
- net::DISK_CACHE,
- disk_cache::kNoRandom);
+ scoped_refptr<base::MessageLoopProxy> thread =
+ use_current_thread_ ? base::MessageLoopProxy::CreateForCurrentThread() :
+ cache_thread_.message_loop_proxy();
+
+ TestCompletionCallback cb;
+ int rv = disk_cache::BackendImpl::CreateBackend(
+ path, force_creation_, size_, net::DISK_CACHE,
+ disk_cache::kNoRandom, thread, &cache_, &cb);
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
}
void DiskCacheTestWithCache::InitDiskCacheImpl(const FilePath& path) {
+ scoped_refptr<base::MessageLoopProxy> thread =
+ use_current_thread_ ? base::MessageLoopProxy::CreateForCurrentThread() :
+ cache_thread_.message_loop_proxy();
if (mask_)
- cache_impl_ = new disk_cache::BackendImpl(path, mask_);
+ cache_impl_ = new disk_cache::BackendImpl(path, mask_, thread);
else
- cache_impl_ = new disk_cache::BackendImpl(path);
+ cache_impl_ = new disk_cache::BackendImpl(path, thread);
cache_ = cache_impl_;
ASSERT_TRUE(NULL != cache_);
@@ -80,12 +97,16 @@
cache_impl_->SetNewEviction();
cache_impl_->SetFlags(disk_cache::kNoRandom);
- ASSERT_TRUE(cache_impl_->Init());
+ TestCompletionCallback cb;
+ int rv = cache_impl_->Init(&cb);
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
}
void DiskCacheTestWithCache::TearDown() {
MessageLoop::current()->RunAllPending();
delete cache_;
+ if (cache_thread_.IsRunning())
+ cache_thread_.Stop();
if (!memory_only_ && integrity_) {
FilePath path = GetCacheFilePath();
@@ -98,6 +119,9 @@
// We are expected to leak memory when simulating crashes.
void DiskCacheTestWithCache::SimulateCrash() {
ASSERT_TRUE(implementation_ && !memory_only_);
+ TestCompletionCallback cb;
+ int rv = cache_impl_->FlushQueueForTest(&cb);
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
cache_impl_->ClearRefCountForTest();
delete cache_impl_;
@@ -111,3 +135,58 @@
ASSERT_TRUE(implementation_ && !memory_only_);
cache_impl_->SetUnitTestMode();
}
+
+int DiskCacheTestWithCache::OpenEntry(const std::string& key,
+ disk_cache::Entry** entry) {
+ TestCompletionCallback cb;
+ int rv = cache_->OpenEntry(key, entry, &cb);
+ return cb.GetResult(rv);
+}
+
+int DiskCacheTestWithCache::CreateEntry(const std::string& key,
+ disk_cache::Entry** entry) {
+ TestCompletionCallback cb;
+ int rv = cache_->CreateEntry(key, entry, &cb);
+ return cb.GetResult(rv);
+}
+
+int DiskCacheTestWithCache::DoomEntry(const std::string& key) {
+ TestCompletionCallback cb;
+ int rv = cache_->DoomEntry(key, &cb);
+ return cb.GetResult(rv);
+}
+
+int DiskCacheTestWithCache::DoomAllEntries() {
+ TestCompletionCallback cb;
+ int rv = cache_->DoomAllEntries(&cb);
+ return cb.GetResult(rv);
+}
+
+int DiskCacheTestWithCache::DoomEntriesBetween(const base::Time initial_time,
+ const base::Time end_time) {
+ TestCompletionCallback cb;
+ int rv = cache_->DoomEntriesBetween(initial_time, end_time, &cb);
+ return cb.GetResult(rv);
+}
+
+int DiskCacheTestWithCache::DoomEntriesSince(const base::Time initial_time) {
+ TestCompletionCallback cb;
+ int rv = cache_->DoomEntriesSince(initial_time, &cb);
+ return cb.GetResult(rv);
+}
+
+int DiskCacheTestWithCache::OpenNextEntry(void** iter,
+ disk_cache::Entry** next_entry) {
+ TestCompletionCallback cb;
+ int rv = cache_->OpenNextEntry(iter, next_entry, &cb);
+ return cb.GetResult(rv);
+}
+
+void DiskCacheTestWithCache::FlushQueueForTest() {
+ if (memory_only_ || !cache_impl_)
+ return;
+
+ TestCompletionCallback cb;
+ int rv = cache_impl_->FlushQueueForTest(&cb);
+ EXPECT_EQ(net::OK, cb.GetResult(rv));
+}
diff --git a/net/disk_cache/disk_cache_test_base.h b/net/disk_cache/disk_cache_test_base.h
index c198e22..6c6b54b 100644
--- a/net/disk_cache/disk_cache_test_base.h
+++ b/net/disk_cache/disk_cache_test_base.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Copyright (c) 2006-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.
@@ -6,6 +6,7 @@
#define NET_DISK_CACHE_DISK_CACHE_TEST_BASE_H_
#include "base/basictypes.h"
+#include "base/thread.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
@@ -15,6 +16,7 @@
class Backend;
class BackendImpl;
+class Entry;
class MemBackendImpl;
} // namespace disk_cache
@@ -33,7 +35,8 @@
DiskCacheTestWithCache()
: cache_(NULL), cache_impl_(NULL), mem_cache_(NULL), mask_(0), size_(0),
memory_only_(false), implementation_(false), force_creation_(false),
- new_eviction_(false), first_cleanup_(true), integrity_(true) {}
+ new_eviction_(false), first_cleanup_(true), integrity_(true),
+ use_current_thread_(false), cache_thread_("CacheThread") {}
void InitCache();
virtual void TearDown();
@@ -72,6 +75,21 @@
integrity_ = false;
}
+ void UseCurrentThread() {
+ use_current_thread_ = true;
+ }
+
+ // Utility methods to access the cache and wait for each operation to finish.
+ int OpenEntry(const std::string& key, disk_cache::Entry** entry);
+ int CreateEntry(const std::string& key, disk_cache::Entry** entry);
+ int DoomEntry(const std::string& key);
+ int DoomAllEntries();
+ int DoomEntriesBetween(const base::Time initial_time,
+ const base::Time end_time);
+ int DoomEntriesSince(const base::Time initial_time);
+ int OpenNextEntry(void** iter, disk_cache::Entry** next_entry);
+ void FlushQueueForTest();
+
// cache_ will always have a valid object, regardless of how the cache was
// initialized. The implementation pointers can be NULL.
disk_cache::Backend* cache_;
@@ -86,6 +104,7 @@
bool new_eviction_;
bool first_cleanup_;
bool integrity_;
+ bool use_current_thread_;
// This is intentionally left uninitialized, to be used by any test.
bool success_;
@@ -93,6 +112,9 @@
void InitMemoryCache();
void InitDiskCache();
void InitDiskCacheImpl(const FilePath& path);
+
+ base::Thread cache_thread_;
+ DISALLOW_COPY_AND_ASSIGN(DiskCacheTestWithCache);
};
#endif // NET_DISK_CACHE_DISK_CACHE_TEST_BASE_H_
diff --git a/net/disk_cache/disk_cache_test_util.cc b/net/disk_cache/disk_cache_test_util.cc
index 36d28d2..46e33db 100644
--- a/net/disk_cache/disk_cache_test_util.cc
+++ b/net/disk_cache/disk_cache_test_util.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2006-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.
@@ -6,7 +6,9 @@
#include "base/logging.h"
#include "base/file_util.h"
+#include "base/message_loop_proxy.h"
#include "base/path_service.h"
+#include "net/base/net_errors.h"
#include "net/disk_cache/backend_impl.h"
#include "net/disk_cache/cache_util.h"
#include "net/disk_cache/file.h"
@@ -76,14 +78,29 @@
return true;
}
+bool CopyTestCache(const std::string& name) {
+ FilePath path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ path = path.AppendASCII("net");
+ path = path.AppendASCII("data");
+ path = path.AppendASCII("cache_tests");
+ path = path.AppendASCII(name);
+
+ FilePath dest = GetCacheFilePath();
+ if (!DeleteCache(dest))
+ return false;
+ return file_util::CopyDirectory(path, dest, false);
+}
+
bool CheckCacheIntegrity(const FilePath& path, bool new_eviction) {
- scoped_ptr<disk_cache::BackendImpl> cache(new disk_cache::BackendImpl(path));
+ scoped_ptr<disk_cache::BackendImpl> cache(new disk_cache::BackendImpl(
+ path, base::MessageLoopProxy::CreateForCurrentThread()));
if (!cache.get())
return false;
if (new_eviction)
cache->SetNewEviction();
cache->SetFlags(disk_cache::kNoRandom);
- if (!cache->Init())
+ if (cache->SyncInit() != net::OK)
return false;
return cache->SelfCheck() >= 0;
}
diff --git a/net/disk_cache/disk_cache_test_util.h b/net/disk_cache/disk_cache_test_util.h
index 623810b..7249ee0 100644
--- a/net/disk_cache/disk_cache_test_util.h
+++ b/net/disk_cache/disk_cache_test_util.h
@@ -7,9 +7,9 @@
#include <string>
+#include "base/callback.h"
#include "base/file_path.h"
#include "base/message_loop.h"
-#include "base/task.h"
#include "base/timer.h"
#include "build/build_config.h"
@@ -21,6 +21,9 @@
// Deletes all file son the cache.
bool DeleteCache(const FilePath& path);
+// Copies a set of cache files from the data folder to the test folder.
+bool CopyTestCache(const std::string& name);
+
// Gets the path to the cache test folder.
FilePath GetCacheFilePath();
diff --git a/net/disk_cache/entry_impl.cc b/net/disk_cache/entry_impl.cc
index e3fda2a..a017549 100644
--- a/net/disk_cache/entry_impl.cc
+++ b/net/disk_cache/entry_impl.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2006-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.
@@ -17,6 +17,7 @@
using base::Time;
using base::TimeDelta;
+using base::TimeTicks;
namespace {
@@ -29,7 +30,8 @@
public:
SyncCallback(disk_cache::EntryImpl* entry, net::IOBuffer* buffer,
net::CompletionCallback* callback )
- : entry_(entry), callback_(callback), buf_(buffer), start_(Time::Now()) {
+ : entry_(entry), callback_(callback), buf_(buffer),
+ start_(TimeTicks::Now()) {
entry->AddRef();
entry->IncrementIoCount();
}
@@ -41,9 +43,9 @@
disk_cache::EntryImpl* entry_;
net::CompletionCallback* callback_;
scoped_refptr<net::IOBuffer> buf_;
- Time start_;
+ TimeTicks start_;
- DISALLOW_EVIL_CONSTRUCTORS(SyncCallback);
+ DISALLOW_COPY_AND_ASSIGN(SyncCallback);
};
void SyncCallback::OnFileIOComplete(int bytes_copied) {
@@ -65,8 +67,8 @@
// Clears buffer before offset and after valid_len, knowing that the size of
// buffer is kMaxBlockSize.
void ClearInvalidData(char* buffer, int offset, int valid_len) {
- DCHECK(offset >= 0);
- DCHECK(valid_len >= 0);
+ DCHECK_GE(offset, 0);
+ DCHECK_GE(valid_len, 0);
DCHECK(disk_cache::kMaxBlockSize >= offset + valid_len);
if (offset)
memset(buffer, 0, offset);
@@ -87,7 +89,6 @@
for (int i = 0; i < kNumStreams; i++) {
unreported_size_[i] = 0;
}
- key_file_ = NULL;
}
// When an entry is deleted from the cache, we clean up all the data associated
@@ -130,15 +131,11 @@
}
void EntryImpl::Doom() {
- if (doomed_)
- return;
-
- SetPointerForInvalidEntry(backend_->GetCurrentEntryId());
- backend_->InternalDoomEntry(this);
+ backend_->background_queue()->DoomEntryImpl(this);
}
void EntryImpl::Close() {
- Release();
+ backend_->background_queue()->CloseEntryImpl(this);
}
std::string EntryImpl::GetKey() const {
@@ -146,26 +143,26 @@
if (entry->Data()->key_len <= kMaxInternalKeyLength)
return std::string(entry->Data()->key);
+ // We keep a copy of the key so that we can always return it, even if the
+ // backend is disabled.
+ if (!key_.empty())
+ return key_;
+
Addr address(entry->Data()->long_key);
DCHECK(address.is_initialized());
size_t offset = 0;
if (address.is_block_file())
offset = address.start_block() * address.BlockSize() + kBlockHeaderSize;
- if (!key_file_) {
- // We keep a copy of the file needed to access the key so that we can
- // always return this object's key, even if the backend is disabled.
- COMPILE_ASSERT(kNumStreams == kKeyFileIndex, invalid_key_index);
- key_file_ = const_cast<EntryImpl*>(this)->GetBackingFile(address,
- kKeyFileIndex);
- }
+ COMPILE_ASSERT(kNumStreams == kKeyFileIndex, invalid_key_index);
+ File* key_file = const_cast<EntryImpl*>(this)->GetBackingFile(address,
+ kKeyFileIndex);
- std::string key;
- if (!key_file_ ||
- !key_file_->Read(WriteInto(&key, entry->Data()->key_len + 1),
- entry->Data()->key_len + 1, offset))
- key.clear();
- return key;
+ if (!key_file ||
+ !key_file->Read(WriteInto(&key_, entry->Data()->key_len + 1),
+ entry->Data()->key_len + 1, offset))
+ key_.clear();
+ return key_;
}
Time EntryImpl::GetLastUsed() const {
@@ -187,7 +184,10 @@
}
int EntryImpl::ReadData(int index, int offset, net::IOBuffer* buf, int buf_len,
- net::CompletionCallback* completion_callback) {
+ net::CompletionCallback* callback) {
+ if (!callback)
+ return ReadDataImpl(index, offset, buf, buf_len, callback);
+
DCHECK(node_.Data()->dirty);
if (index < 0 || index >= kNumStreams)
return net::ERR_INVALID_ARGUMENT;
@@ -199,7 +199,100 @@
if (buf_len < 0)
return net::ERR_INVALID_ARGUMENT;
- Time start = Time::Now();
+ backend_->background_queue()->ReadData(this, index, offset, buf, buf_len,
+ callback);
+ return net::ERR_IO_PENDING;
+}
+
+int EntryImpl::WriteData(int index, int offset, net::IOBuffer* buf, int buf_len,
+ CompletionCallback* callback, bool truncate) {
+ if (!callback)
+ return WriteDataImpl(index, offset, buf, buf_len, callback, truncate);
+
+ DCHECK(node_.Data()->dirty);
+ if (index < 0 || index >= kNumStreams)
+ return net::ERR_INVALID_ARGUMENT;
+
+ if (offset < 0 || buf_len < 0)
+ return net::ERR_INVALID_ARGUMENT;
+
+ backend_->background_queue()->WriteData(this, index, offset, buf, buf_len,
+ truncate, callback);
+ return net::ERR_IO_PENDING;
+}
+
+int EntryImpl::ReadSparseData(int64 offset, net::IOBuffer* buf, int buf_len,
+ net::CompletionCallback* callback) {
+ if (!callback)
+ return ReadSparseDataImpl(offset, buf, buf_len, callback);
+
+ backend_->background_queue()->ReadSparseData(this, offset, buf, buf_len,
+ callback);
+ return net::ERR_IO_PENDING;
+}
+
+int EntryImpl::WriteSparseData(int64 offset, net::IOBuffer* buf, int buf_len,
+ net::CompletionCallback* callback) {
+ if (!callback)
+ return WriteSparseDataImpl(offset, buf, buf_len, callback);
+
+ backend_->background_queue()->WriteSparseData(this, offset, buf, buf_len,
+ callback);
+ return net::ERR_IO_PENDING;
+}
+
+int EntryImpl::GetAvailableRange(int64 offset, int len, int64* start,
+ CompletionCallback* callback) {
+ backend_->background_queue()->GetAvailableRange(this, offset, len, start,
+ callback);
+ return net::ERR_IO_PENDING;
+}
+
+bool EntryImpl::CouldBeSparse() const {
+ if (sparse_.get())
+ return true;
+
+ scoped_ptr<SparseControl> sparse;
+ sparse.reset(new SparseControl(const_cast<EntryImpl*>(this)));
+ return sparse->CouldBeSparse();
+}
+
+void EntryImpl::CancelSparseIO() {
+ backend_->background_queue()->CancelSparseIO(this);
+}
+
+int EntryImpl::ReadyForSparseIO(net::CompletionCallback* callback) {
+ if (!sparse_.get())
+ return net::OK;
+
+ backend_->background_queue()->ReadyForSparseIO(this, callback);
+ return net::ERR_IO_PENDING;
+}
+
+// ------------------------------------------------------------------------
+
+void EntryImpl::DoomImpl() {
+ if (doomed_)
+ return;
+
+ SetPointerForInvalidEntry(backend_->GetCurrentEntryId());
+ backend_->InternalDoomEntry(this);
+}
+
+int EntryImpl::ReadDataImpl(int index, int offset, net::IOBuffer* buf,
+ int buf_len, CompletionCallback* callback) {
+ DCHECK(node_.Data()->dirty);
+ if (index < 0 || index >= kNumStreams)
+ return net::ERR_INVALID_ARGUMENT;
+
+ int entry_size = entry_.Data()->data_size[index];
+ if (offset >= entry_size || offset < 0 || !buf_len)
+ return 0;
+
+ if (buf_len < 0)
+ return net::ERR_INVALID_ARGUMENT;
+
+ TimeTicks start = TimeTicks::Now();
if (offset + buf_len > entry_size)
buf_len = entry_size - offset;
@@ -207,6 +300,7 @@
UpdateRank(false);
backend_->OnEvent(Stats::READ_DATA);
+ backend_->OnRead(buf_len);
if (user_buffers_[index].get()) {
// Complete the operation locally.
@@ -231,8 +325,8 @@
kBlockHeaderSize;
SyncCallback* io_callback = NULL;
- if (completion_callback)
- io_callback = new SyncCallback(this, buf, completion_callback);
+ if (callback)
+ io_callback = new SyncCallback(this, buf, callback);
bool completed;
if (!file->Read(buf->data(), buf_len, file_offset, io_callback, &completed)) {
@@ -245,12 +339,12 @@
io_callback->Discard();
ReportIOTime(kRead, start);
- return (completed || !completion_callback) ? buf_len : net::ERR_IO_PENDING;
+ return (completed || !callback) ? buf_len : net::ERR_IO_PENDING;
}
-int EntryImpl::WriteData(int index, int offset, net::IOBuffer* buf, int buf_len,
- net::CompletionCallback* completion_callback,
- bool truncate) {
+int EntryImpl::WriteDataImpl(int index, int offset, net::IOBuffer* buf,
+ int buf_len, CompletionCallback* callback,
+ bool truncate) {
DCHECK(node_.Data()->dirty);
if (index < 0 || index >= kNumStreams)
return net::ERR_INVALID_ARGUMENT;
@@ -260,7 +354,7 @@
int max_file_size = backend_->MaxFileSize();
- // offset of buf_len could be negative numbers.
+ // offset or buf_len could be negative numbers.
if (offset > max_file_size || buf_len > max_file_size ||
offset + buf_len > max_file_size) {
int size = offset + buf_len;
@@ -270,7 +364,7 @@
return net::ERR_FAILED;
}
- Time start = Time::Now();
+ TimeTicks start = TimeTicks::Now();
// Read the size at this point (it may change inside prepare).
int entry_size = entry_.Data()->data_size[index];
@@ -300,6 +394,7 @@
UpdateRank(true);
backend_->OnEvent(Stats::WRITE_DATA);
+ backend_->OnWrite(buf_len);
if (user_buffers_[index].get()) {
// Complete the operation locally.
@@ -330,8 +425,8 @@
return 0;
SyncCallback* io_callback = NULL;
- if (completion_callback)
- io_callback = new SyncCallback(this, buf, completion_callback);
+ if (callback)
+ io_callback = new SyncCallback(this, buf, callback);
bool completed;
if (!file->Write(buf->data(), buf_len, file_offset, io_callback,
@@ -345,38 +440,38 @@
io_callback->Discard();
ReportIOTime(kWrite, start);
- return (completed || !completion_callback) ? buf_len : net::ERR_IO_PENDING;
+ return (completed || !callback) ? buf_len : net::ERR_IO_PENDING;
}
-int EntryImpl::ReadSparseData(int64 offset, net::IOBuffer* buf, int buf_len,
- net::CompletionCallback* completion_callback) {
+int EntryImpl::ReadSparseDataImpl(int64 offset, net::IOBuffer* buf, int buf_len,
+ CompletionCallback* callback) {
DCHECK(node_.Data()->dirty);
int result = InitSparseData();
if (net::OK != result)
return result;
- Time start = Time::Now();
+ TimeTicks start = TimeTicks::Now();
result = sparse_->StartIO(SparseControl::kReadOperation, offset, buf, buf_len,
- completion_callback);
+ callback);
ReportIOTime(kSparseRead, start);
return result;
}
-int EntryImpl::WriteSparseData(int64 offset, net::IOBuffer* buf, int buf_len,
- net::CompletionCallback* completion_callback) {
+int EntryImpl::WriteSparseDataImpl(int64 offset, net::IOBuffer* buf,
+ int buf_len, CompletionCallback* callback) {
DCHECK(node_.Data()->dirty);
int result = InitSparseData();
if (net::OK != result)
return result;
- Time start = Time::Now();
+ TimeTicks start = TimeTicks::Now();
result = sparse_->StartIO(SparseControl::kWriteOperation, offset, buf,
- buf_len, completion_callback);
+ buf_len, callback);
ReportIOTime(kSparseWrite, start);
return result;
}
-int EntryImpl::GetAvailableRange(int64 offset, int len, int64* start) {
+int EntryImpl::GetAvailableRangeImpl(int64 offset, int len, int64* start) {
int result = InitSparseData();
if (net::OK != result)
return result;
@@ -384,23 +479,16 @@
return sparse_->GetAvailableRange(offset, len, start);
}
-int EntryImpl::GetAvailableRange(int64 offset, int len, int64* start,
- CompletionCallback* callback) {
- return GetAvailableRange(offset, len, start);
-}
-
-void EntryImpl::CancelSparseIO() {
+void EntryImpl::CancelSparseIOImpl() {
if (!sparse_.get())
return;
sparse_->CancelIO();
}
-int EntryImpl::ReadyForSparseIO(net::CompletionCallback* completion_callback) {
- if (!sparse_.get())
- return net::OK;
-
- return sparse_->ReadyToUse(completion_callback);
+int EntryImpl::ReadyForSparseIOImpl(CompletionCallback* callback) {
+ DCHECK(sparse_.get());
+ return sparse_->ReadyToUse(callback);
}
// ------------------------------------------------------------------------
@@ -431,24 +519,26 @@
return false;
entry_store->long_key = address.value();
- key_file_ = GetBackingFile(address, kKeyFileIndex);
+ File* key_file = GetBackingFile(address, kKeyFileIndex);
+ key_ = key;
size_t offset = 0;
if (address.is_block_file())
offset = address.start_block() * address.BlockSize() + kBlockHeaderSize;
- if (!key_file_ || !key_file_->Write(key.data(), key.size(), offset)) {
+ if (!key_file || !key_file->Write(key.data(), key.size(), offset)) {
DeleteData(address, kKeyFileIndex);
return false;
}
if (address.is_separate_file())
- key_file_->SetLength(key.size() + 1);
+ key_file->SetLength(key.size() + 1);
} else {
memcpy(entry_store->key, key.data(), key.size());
entry_store->key[key.size()] = '\0';
}
backend_->ModifyStorageSize(0, static_cast<int32>(key.size()));
+ CACHE_UMA(COUNTS, "KeySize", 0, static_cast<int32>(key.size()));
node->dirty = backend_->GetCurrentEntryId();
Log("Create Entry ");
return true;
@@ -596,7 +686,7 @@
node_.set_modified();
}
-void EntryImpl::ReportIOTime(Operation op, const base::Time& start) {
+void EntryImpl::ReportIOTime(Operation op, const base::TimeTicks& start) {
int group = backend_->GetSizeGroup();
switch (op) {
case kRead:
@@ -859,15 +949,14 @@
if (async) {
if (!file->PostWrite(user_buffers_[index].get(), len, offset))
return false;
+ // The buffer is deleted from the PostWrite operation.
+ ignore_result(user_buffers_[index].release());
} else {
if (!file->Write(user_buffers_[index].get(), len, offset, NULL, NULL))
return false;
user_buffers_[index].reset(NULL);
}
- // The buffer is deleted from the PostWrite operation.
- user_buffers_[index].release();
-
return true;
}
@@ -875,10 +964,12 @@
if (sparse_.get())
return net::OK;
- sparse_.reset(new SparseControl(this));
- int result = sparse_->Init();
- if (net::OK != result)
- sparse_.reset();
+ // Use a local variable so that sparse_ never goes from 'valid' to NULL.
+ scoped_ptr<SparseControl> sparse(new SparseControl(this));
+ int result = sparse->Init();
+ if (net::OK == result)
+ sparse_.swap(sparse);
+
return result;
}
@@ -893,7 +984,7 @@
void EntryImpl::GetData(int index, char** buffer, Addr* address) {
if (user_buffers_[index].get()) {
- // The data is already in memory, just copy it an we're done.
+ // The data is already in memory, just copy it and we're done.
int data_len = entry_.Data()->data_size[index];
DCHECK(data_len <= kMaxBlockSize);
*buffer = new char[data_len];
diff --git a/net/disk_cache/entry_impl.h b/net/disk_cache/entry_impl.h
index 76e4965..9d37fff 100644
--- a/net/disk_cache/entry_impl.h
+++ b/net/disk_cache/entry_impl.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2006-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.
@@ -47,12 +47,26 @@
net::CompletionCallback* completion_callback);
virtual int WriteSparseData(int64 offset, net::IOBuffer* buf, int buf_len,
net::CompletionCallback* completion_callback);
- virtual int GetAvailableRange(int64 offset, int len, int64* start);
virtual int GetAvailableRange(int64 offset, int len, int64* start,
CompletionCallback* callback);
+ virtual bool CouldBeSparse() const;
virtual void CancelSparseIO();
virtual int ReadyForSparseIO(net::CompletionCallback* completion_callback);
+ // Background implementation of the Entry interface.
+ void DoomImpl();
+ int ReadDataImpl(int index, int offset, net::IOBuffer* buf, int buf_len,
+ CompletionCallback* callback);
+ int WriteDataImpl(int index, int offset, net::IOBuffer* buf, int buf_len,
+ CompletionCallback* callback, bool truncate);
+ int ReadSparseDataImpl(int64 offset, net::IOBuffer* buf, int buf_len,
+ CompletionCallback* callback);
+ int WriteSparseDataImpl(int64 offset, net::IOBuffer* buf, int buf_len,
+ CompletionCallback* callback);
+ int GetAvailableRangeImpl(int64 offset, int len, int64* start);
+ void CancelSparseIOImpl();
+ int ReadyForSparseIOImpl(CompletionCallback* callback);
+
inline CacheEntryBlock* entry() {
return &entry_;
}
@@ -112,7 +126,7 @@
void SetTimes(base::Time last_used, base::Time last_modified);
// Generates a histogram for the time spent working on this operation.
- void ReportIOTime(Operation op, const base::Time& start);
+ void ReportIOTime(Operation op, const base::TimeTicks& start);
private:
enum {
@@ -183,13 +197,12 @@
scoped_array<char> user_buffers_[kNumStreams]; // Store user data.
// Files to store external user data and key.
scoped_refptr<File> files_[kNumStreams + 1];
- // Copy of the file used to store the key. We don't own this object.
- mutable File* key_file_;
+ mutable std::string key_; // Copy of the key.
int unreported_size_[kNumStreams]; // Bytes not reported yet to the backend.
bool doomed_; // True if this entry was removed from the cache.
scoped_ptr<SparseControl> sparse_; // Support for sparse entries.
- DISALLOW_EVIL_CONSTRUCTORS(EntryImpl);
+ DISALLOW_COPY_AND_ASSIGN(EntryImpl);
};
} // namespace disk_cache
diff --git a/net/disk_cache/entry_unittest.cc b/net/disk_cache/entry_unittest.cc
index e60a3da..067f260 100644
--- a/net/disk_cache/entry_unittest.cc
+++ b/net/disk_cache/entry_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2006-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.
@@ -34,18 +34,19 @@
void ZeroLengthIO();
void ReuseEntry(int size);
void InvalidData();
- void DoomEntry();
+ void DoomNormalEntry();
void DoomedEntry();
void BasicSparseIO(bool async);
void HugeSparseIO(bool async);
void GetAvailableRange();
+ void CouldBeSparse();
void DoomSparseEntry();
void PartialSparseEntry();
};
void DiskCacheEntryTest::InternalSyncIO() {
disk_cache::Entry *entry1 = NULL;
- ASSERT_TRUE(cache_->CreateEntry("the first key", &entry1));
+ ASSERT_EQ(net::OK, CreateEntry("the first key", &entry1));
ASSERT_TRUE(NULL != entry1);
const int kSize1 = 10;
@@ -81,10 +82,12 @@
entry1->Doom();
entry1->Close();
+ FlushQueueForTest();
EXPECT_EQ(0, cache_->GetEntryCount());
}
TEST_F(DiskCacheEntryTest, InternalSyncIO) {
+ SetDirectMode();
InitCache();
InternalSyncIO();
}
@@ -97,7 +100,7 @@
void DiskCacheEntryTest::InternalAsyncIO() {
disk_cache::Entry *entry1 = NULL;
- ASSERT_TRUE(cache_->CreateEntry("the first key", &entry1));
+ ASSERT_EQ(net::OK, CreateEntry("the first key", &entry1));
ASSERT_TRUE(NULL != entry1);
// Avoid using internal buffers for the test. We have to write something to
@@ -108,7 +111,7 @@
EXPECT_EQ(0, entry1->WriteData(0, 15 * 1024, NULL, 0, NULL, false));
EXPECT_EQ(0, entry1->WriteData(1, 15 * 1024, NULL, 0, NULL, false));
entry1->Close();
- ASSERT_TRUE(cache_->OpenEntry("the first key", &entry1));
+ ASSERT_EQ(net::OK, OpenEntry("the first key", &entry1));
// Let's verify that each IO goes to the right callback object.
CallbackTest callback1(false);
@@ -222,10 +225,12 @@
entry1->Doom();
entry1->Close();
+ FlushQueueForTest();
EXPECT_EQ(0, cache_->GetEntryCount());
}
TEST_F(DiskCacheEntryTest, InternalAsyncIO) {
+ SetDirectMode();
InitCache();
InternalAsyncIO();
}
@@ -238,7 +243,7 @@
void DiskCacheEntryTest::ExternalSyncIO() {
disk_cache::Entry *entry1;
- ASSERT_TRUE(cache_->CreateEntry("the first key", &entry1));
+ ASSERT_EQ(net::OK, CreateEntry("the first key", &entry1));
const int kSize1 = 17000;
const int kSize2 = 25000;
@@ -268,10 +273,12 @@
entry1->Doom();
entry1->Close();
+ FlushQueueForTest();
EXPECT_EQ(0, cache_->GetEntryCount());
}
TEST_F(DiskCacheEntryTest, ExternalSyncIO) {
+ SetDirectMode();
InitCache();
ExternalSyncIO();
}
@@ -284,7 +291,7 @@
void DiskCacheEntryTest::ExternalAsyncIO() {
disk_cache::Entry *entry1;
- ASSERT_TRUE(cache_->CreateEntry("the first key", &entry1));
+ ASSERT_EQ(net::OK, CreateEntry("the first key", &entry1));
// Let's verify that each IO goes to the right callback object.
CallbackTest callback1(false);
@@ -366,19 +373,21 @@
EXPECT_TRUE(17000 == ret || net::ERR_IO_PENDING == ret);
if (net::ERR_IO_PENDING == ret)
expected++;
- EXPECT_EQ(37000, entry1->GetDataSize(1));
EXPECT_TRUE(helper.WaitUntilCacheIoFinished(expected));
+ EXPECT_EQ(37000, entry1->GetDataSize(1));
EXPECT_FALSE(g_cache_tests_error);
EXPECT_EQ(expected, g_cache_tests_received);
entry1->Doom();
entry1->Close();
+ FlushQueueForTest();
EXPECT_EQ(0, cache_->GetEntryCount());
}
TEST_F(DiskCacheEntryTest, ExternalAsyncIO) {
+ SetDirectMode();
InitCache();
ExternalAsyncIO();
}
@@ -391,7 +400,7 @@
void DiskCacheEntryTest::StreamAccess() {
disk_cache::Entry *entry = NULL;
- ASSERT_TRUE(cache_->CreateEntry("the first key", &entry));
+ ASSERT_EQ(net::OK, CreateEntry("the first key", &entry));
ASSERT_TRUE(NULL != entry);
const int kBufferSize = 1024;
@@ -427,7 +436,7 @@
void DiskCacheEntryTest::GetKey() {
std::string key1("the first key");
disk_cache::Entry *entry1;
- ASSERT_TRUE(cache_->CreateEntry(key1, &entry1));
+ ASSERT_EQ(net::OK, CreateEntry(key1, &entry1));
EXPECT_EQ(key1, entry1->GetKey()) << "short key";
entry1->Close();
@@ -439,14 +448,14 @@
key_buffer[1000] = '\0';
key1 = key_buffer;
- ASSERT_TRUE(cache_->CreateEntry(key1, &entry1));
+ ASSERT_EQ(net::OK, CreateEntry(key1, &entry1));
EXPECT_TRUE(key1 == entry1->GetKey()) << "1000 bytes key";
entry1->Close();
key_buffer[1000] = 'p';
key_buffer[3000] = '\0';
key1 = key_buffer;
- ASSERT_TRUE(cache_->CreateEntry(key1, &entry1));
+ ASSERT_EQ(net::OK, CreateEntry(key1, &entry1));
EXPECT_TRUE(key1 == entry1->GetKey()) << "medium size key";
entry1->Close();
@@ -454,7 +463,7 @@
key_buffer[19999] = '\0';
key1 = key_buffer;
- ASSERT_TRUE(cache_->CreateEntry(key1, &entry1));
+ ASSERT_EQ(net::OK, CreateEntry(key1, &entry1));
EXPECT_TRUE(key1 == entry1->GetKey()) << "long key";
entry1->Close();
}
@@ -473,7 +482,7 @@
void DiskCacheEntryTest::GrowData() {
std::string key1("the first key");
disk_cache::Entry *entry1, *entry2;
- ASSERT_TRUE(cache_->CreateEntry(key1, &entry1));
+ ASSERT_EQ(net::OK, CreateEntry(key1, &entry1));
const int kSize = 20000;
scoped_refptr<net::IOBuffer> buffer1 = new net::IOBuffer(kSize);
@@ -499,13 +508,13 @@
entry1->Close();
memset(buffer2->data(), 0, kSize);
- ASSERT_TRUE(cache_->CreateEntry("Second key", &entry2));
+ ASSERT_EQ(net::OK, CreateEntry("Second key", &entry2));
EXPECT_EQ(10, entry2->WriteData(0, 0, buffer1, 10, NULL, false));
EXPECT_EQ(10, entry2->GetDataSize(0));
entry2->Close();
// Go from an internal address to a bigger block size.
- ASSERT_TRUE(cache_->OpenEntry("Second key", &entry2));
+ ASSERT_EQ(net::OK, OpenEntry("Second key", &entry2));
EXPECT_EQ(2000, entry2->WriteData(0, 0, buffer1, 2000, NULL, false));
EXPECT_EQ(2000, entry2->GetDataSize(0));
EXPECT_EQ(2000, entry2->ReadData(0, 0, buffer2, 2000, NULL));
@@ -514,7 +523,7 @@
memset(buffer2->data(), 0, kSize);
// Go from an internal address to an external one.
- ASSERT_TRUE(cache_->OpenEntry("Second key", &entry2));
+ ASSERT_EQ(net::OK, OpenEntry("Second key", &entry2));
EXPECT_EQ(20000, entry2->WriteData(0, 0, buffer1, kSize, NULL, false));
EXPECT_EQ(20000, entry2->GetDataSize(0));
EXPECT_EQ(20000, entry2->ReadData(0, 0, buffer2, kSize, NULL));
@@ -536,7 +545,7 @@
void DiskCacheEntryTest::TruncateData() {
std::string key1("the first key");
disk_cache::Entry *entry1;
- ASSERT_TRUE(cache_->CreateEntry(key1, &entry1));
+ ASSERT_EQ(net::OK, CreateEntry(key1, &entry1));
const int kSize1 = 20000;
const int kSize2 = 20000;
@@ -558,7 +567,7 @@
EXPECT_EQ(0, entry1->WriteData(0, 0, buffer1, 0, NULL, true));
EXPECT_EQ(0, entry1->GetDataSize(0));
entry1->Close();
- ASSERT_TRUE(cache_->OpenEntry(key1, &entry1));
+ ASSERT_EQ(net::OK, OpenEntry(key1, &entry1));
// Go to an external file.
EXPECT_EQ(20000, entry1->WriteData(0, 0, buffer1, 20000, NULL, true));
@@ -612,7 +621,7 @@
void DiskCacheEntryTest::ZeroLengthIO() {
std::string key1("the first key");
disk_cache::Entry *entry1;
- ASSERT_TRUE(cache_->CreateEntry(key1, &entry1));
+ ASSERT_EQ(net::OK, CreateEntry(key1, &entry1));
EXPECT_EQ(0, entry1->ReadData(0, 0, NULL, 0, NULL));
EXPECT_EQ(0, entry1->WriteData(0, 0, NULL, 0, NULL, false));
@@ -641,11 +650,11 @@
void DiskCacheEntryTest::ReuseEntry(int size) {
std::string key1("the first key");
disk_cache::Entry *entry;
- ASSERT_TRUE(cache_->CreateEntry(key1, &entry));
+ ASSERT_EQ(net::OK, CreateEntry(key1, &entry));
entry->Close();
std::string key2("the second key");
- ASSERT_TRUE(cache_->CreateEntry(key2, &entry));
+ ASSERT_EQ(net::OK, CreateEntry(key2, &entry));
scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer(size);
CacheTestFillBuffer(buffer->data(), size, false);
@@ -654,11 +663,11 @@
EXPECT_EQ(0, entry->WriteData(0, 0, buffer, 0, NULL, true));
EXPECT_EQ(size, entry->WriteData(0, 0, buffer, size, NULL, false));
entry->Close();
- ASSERT_TRUE(cache_->OpenEntry(key2, &entry));
+ ASSERT_EQ(net::OK, OpenEntry(key2, &entry));
}
entry->Close();
- ASSERT_TRUE(cache_->OpenEntry(key1, &entry)) << "have not evicted this entry";
+ ASSERT_EQ(net::OK, OpenEntry(key1, &entry)) << "have not evicted this entry";
entry->Close();
}
@@ -696,7 +705,7 @@
void DiskCacheEntryTest::InvalidData() {
std::string key1("the first key");
disk_cache::Entry *entry1;
- ASSERT_TRUE(cache_->CreateEntry(key1, &entry1));
+ ASSERT_EQ(net::OK, CreateEntry(key1, &entry1));
const int kSize1 = 20000;
const int kSize2 = 20000;
@@ -714,7 +723,7 @@
EXPECT_EQ(100, entry1->ReadData(0, 300, buffer3, 100, NULL));
EXPECT_TRUE(!memcmp(buffer3->data(), buffer2->data(), 100));
entry1->Close();
- ASSERT_TRUE(cache_->OpenEntry(key1, &entry1));
+ ASSERT_EQ(net::OK, OpenEntry(key1, &entry1));
// The entry is now on disk. Load it and extend it.
EXPECT_EQ(200, entry1->WriteData(0, 800, buffer1, 200, NULL, false));
@@ -722,7 +731,7 @@
EXPECT_EQ(100, entry1->ReadData(0, 700, buffer3, 100, NULL));
EXPECT_TRUE(!memcmp(buffer3->data(), buffer2->data(), 100));
entry1->Close();
- ASSERT_TRUE(cache_->OpenEntry(key1, &entry1));
+ ASSERT_EQ(net::OK, OpenEntry(key1, &entry1));
// This time using truncate.
EXPECT_EQ(200, entry1->WriteData(0, 1800, buffer1, 200, NULL, true));
@@ -768,10 +777,10 @@
InvalidData();
}
-void DiskCacheEntryTest::DoomEntry() {
+void DiskCacheEntryTest::DoomNormalEntry() {
std::string key1("the first key");
disk_cache::Entry *entry1;
- ASSERT_TRUE(cache_->CreateEntry(key1, &entry1));
+ ASSERT_EQ(net::OK, CreateEntry(key1, &entry1));
entry1->Doom();
entry1->Close();
@@ -781,33 +790,36 @@
buffer->data()[19999] = '\0';
key1 = buffer->data();
- ASSERT_TRUE(cache_->CreateEntry(key1, &entry1));
+ ASSERT_EQ(net::OK, CreateEntry(key1, &entry1));
EXPECT_EQ(20000, entry1->WriteData(0, 0, buffer, kSize, NULL, false));
EXPECT_EQ(20000, entry1->WriteData(1, 0, buffer, kSize, NULL, false));
entry1->Doom();
entry1->Close();
+ FlushQueueForTest();
EXPECT_EQ(0, cache_->GetEntryCount());
}
TEST_F(DiskCacheEntryTest, DoomEntry) {
+ SetDirectMode();
InitCache();
- DoomEntry();
+ DoomNormalEntry();
}
TEST_F(DiskCacheEntryTest, MemoryOnlyDoomEntry) {
SetMemoryOnlyMode();
InitCache();
- DoomEntry();
+ DoomNormalEntry();
}
// Verify that basic operations work as expected with doomed entries.
void DiskCacheEntryTest::DoomedEntry() {
std::string key("the first key");
disk_cache::Entry *entry;
- ASSERT_TRUE(cache_->CreateEntry(key, &entry));
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
entry->Doom();
+ FlushQueueForTest();
EXPECT_EQ(0, cache_->GetEntryCount());
Time initial = Time::Now();
PlatformThread::Sleep(20);
@@ -830,14 +842,15 @@
}
TEST_F(DiskCacheEntryTest, DoomedEntry) {
+ SetDirectMode();
InitCache();
- DoomEntry();
+ DoomedEntry();
}
TEST_F(DiskCacheEntryTest, MemoryOnlyDoomedEntry) {
SetMemoryOnlyMode();
InitCache();
- DoomEntry();
+ DoomedEntry();
}
// Test that child entries in a memory cache backend are not visible from
@@ -852,7 +865,7 @@
std::string key("the first key");
disk_cache::Entry* parent_entry;
- ASSERT_TRUE(cache_->CreateEntry(key, &parent_entry));
+ ASSERT_EQ(net::OK, CreateEntry(key, &parent_entry));
// Writes to the parent entry.
EXPECT_EQ(kSize, parent_entry->WriteSparseData(0, buf, kSize, NULL));
@@ -866,7 +879,7 @@
void* iter = NULL;
disk_cache::Entry* entry = NULL;
int count = 0;
- while (cache_->OpenNextEntry(&iter, &entry)) {
+ while (OpenNextEntry(&iter, &entry) == net::OK) {
ASSERT_TRUE(entry != NULL);
++count;
disk_cache::MemEntryImpl* mem_entry =
@@ -919,7 +932,7 @@
void DiskCacheEntryTest::BasicSparseIO(bool async) {
std::string key("the first key");
disk_cache::Entry* entry;
- ASSERT_TRUE(cache_->CreateEntry(key, &entry));
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
const int kSize = 2048;
scoped_refptr<net::IOBuffer> buf_1 = new net::IOBuffer(kSize);
@@ -938,7 +951,7 @@
entry->Close();
// Check everything again.
- ASSERT_TRUE(cache_->OpenEntry(key, &entry));
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
VerifyContentSparseIO(entry, 0, buf_1->data(), kSize, async);
VerifyContentSparseIO(entry, 0x400000, buf_1->data(), kSize, async);
VerifyContentSparseIO(entry, 0x800000000LL, buf_1->data(), kSize, async);
@@ -970,7 +983,7 @@
void DiskCacheEntryTest::HugeSparseIO(bool async) {
std::string key("the first key");
disk_cache::Entry* entry;
- ASSERT_TRUE(cache_->CreateEntry(key, &entry));
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
// Write 1.2 MB so that we cover multiple entries.
const int kSize = 1200 * 1024;
@@ -983,7 +996,7 @@
entry->Close();
// Check it again.
- ASSERT_TRUE(cache_->OpenEntry(key, &entry));
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
VerifyContentSparseIO(entry, 0x20F0000, buf_1->data(), kSize, async);
entry->Close();
}
@@ -1013,7 +1026,7 @@
void DiskCacheEntryTest::GetAvailableRange() {
std::string key("the first key");
disk_cache::Entry* entry;
- ASSERT_TRUE(cache_->CreateEntry(key, &entry));
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
const int kSize = 16 * 1024;
scoped_refptr<net::IOBuffer> buf = new net::IOBuffer(kSize);
@@ -1025,28 +1038,37 @@
// We stop at the first empty block.
int64 start;
- EXPECT_EQ(kSize, entry->GetAvailableRange(0x20F0000, kSize * 2, &start));
+ TestCompletionCallback cb;
+ int rv = entry->GetAvailableRange(0x20F0000, kSize * 2, &start, &cb);
+ EXPECT_EQ(kSize, cb.GetResult(rv));
EXPECT_EQ(0x20F0000, start);
start = 0;
- EXPECT_EQ(0, entry->GetAvailableRange(0, kSize, &start));
- EXPECT_EQ(0, entry->GetAvailableRange(0x20F0000 - kSize, kSize, &start));
- EXPECT_EQ(kSize, entry->GetAvailableRange(0, 0x2100000, &start));
+ rv = entry->GetAvailableRange(0, kSize, &start, &cb);
+ EXPECT_EQ(0, cb.GetResult(rv));
+ rv = entry->GetAvailableRange(0x20F0000 - kSize, kSize, &start, &cb);
+ EXPECT_EQ(0, cb.GetResult(rv));
+ rv = entry->GetAvailableRange(0, 0x2100000, &start, &cb);
+ EXPECT_EQ(kSize, cb.GetResult(rv));
EXPECT_EQ(0x20F0000, start);
// We should be able to Read based on the results of GetAvailableRange.
start = -1;
- EXPECT_EQ(0, entry->GetAvailableRange(0x2100000, kSize, &start));
- EXPECT_EQ(0, entry->ReadSparseData(start, buf, kSize, NULL));
+ rv = entry->GetAvailableRange(0x2100000, kSize, &start, &cb);
+ EXPECT_EQ(0, cb.GetResult(rv));
+ rv = entry->ReadSparseData(start, buf, kSize, &cb);
+ EXPECT_EQ(0, cb.GetResult(rv));
start = 0;
- EXPECT_EQ(0x2000, entry->GetAvailableRange(0x20F2000, kSize, &start));
+ rv = entry->GetAvailableRange(0x20F2000, kSize, &start, &cb);
+ EXPECT_EQ(0x2000, cb.GetResult(rv));
EXPECT_EQ(0x20F2000, start);
EXPECT_EQ(0x2000, entry->ReadSparseData(start, buf, kSize, NULL));
// Make sure that we respect the |len| argument.
start = 0;
- EXPECT_EQ(1, entry->GetAvailableRange(0x20F0001 - kSize, kSize, &start));
+ rv = entry->GetAvailableRange(0x20F0001 - kSize, kSize, &start, &cb);
+ EXPECT_EQ(1, cb.GetResult(rv));
EXPECT_EQ(0x20F0000, start);
entry->Close();
@@ -1063,6 +1085,53 @@
GetAvailableRange();
}
+void DiskCacheEntryTest::CouldBeSparse() {
+ std::string key("the first key");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+
+ const int kSize = 16 * 1024;
+ scoped_refptr<net::IOBuffer> buf = new net::IOBuffer(kSize);
+ CacheTestFillBuffer(buf->data(), kSize, false);
+
+ // Write at offset 0x20F0000 (33 MB - 64 KB).
+ EXPECT_EQ(kSize, entry->WriteSparseData(0x20F0000, buf, kSize, NULL));
+
+ EXPECT_TRUE(entry->CouldBeSparse());
+ entry->Close();
+
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+ EXPECT_TRUE(entry->CouldBeSparse());
+ entry->Close();
+
+ // Now verify a regular entry.
+ key.assign("another key");
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+ EXPECT_FALSE(entry->CouldBeSparse());
+
+ EXPECT_EQ(kSize, entry->WriteData(0, 0, buf, kSize, NULL, false));
+ EXPECT_EQ(kSize, entry->WriteData(1, 0, buf, kSize, NULL, false));
+ EXPECT_EQ(kSize, entry->WriteData(2, 0, buf, kSize, NULL, false));
+
+ EXPECT_FALSE(entry->CouldBeSparse());
+ entry->Close();
+
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+ EXPECT_FALSE(entry->CouldBeSparse());
+ entry->Close();
+}
+
+TEST_F(DiskCacheEntryTest, CouldBeSparse) {
+ InitCache();
+ CouldBeSparse();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryCouldBeSparse) {
+ SetMemoryOnlyMode();
+ InitCache();
+ CouldBeSparse();
+}
+
TEST_F(DiskCacheEntryTest, MemoryOnlyMisalignedSparseIO) {
SetMemoryOnlyMode();
InitCache();
@@ -1074,7 +1143,7 @@
std::string key("the first key");
disk_cache::Entry* entry;
- ASSERT_TRUE(cache_->CreateEntry(key, &entry));
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
// This loop writes back to back starting from offset 0 and 9000.
for (int i = 0; i < kSize; i += 1024) {
@@ -1104,7 +1173,7 @@
disk_cache::Entry* entry;
std::string key("the first key");
- ASSERT_TRUE(cache_->CreateEntry(key, &entry));
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
// Writes in the middle of an entry.
EXPECT_EQ(1024, entry->WriteSparseData(0, buf, 1024, NULL));
@@ -1115,31 +1184,38 @@
EXPECT_EQ(8192, entry->WriteSparseData(50000, buf, 8192, NULL));
int64 start;
+ TestCompletionCallback cb;
// Test that we stop at a discontinuous child at the second block.
- EXPECT_EQ(1024, entry->GetAvailableRange(0, 10000, &start));
+ int rv = entry->GetAvailableRange(0, 10000, &start, &cb);
+ EXPECT_EQ(1024, cb.GetResult(rv));
EXPECT_EQ(0, start);
// Test that number of bytes is reported correctly when we start from the
// middle of a filled region.
- EXPECT_EQ(512, entry->GetAvailableRange(512, 10000, &start));
+ rv = entry->GetAvailableRange(512, 10000, &start, &cb);
+ EXPECT_EQ(512, cb.GetResult(rv));
EXPECT_EQ(512, start);
// Test that we found bytes in the child of next block.
- EXPECT_EQ(1024, entry->GetAvailableRange(1024, 10000, &start));
+ rv = entry->GetAvailableRange(1024, 10000, &start, &cb);
+ EXPECT_EQ(1024, cb.GetResult(rv));
EXPECT_EQ(5120, start);
// Test that the desired length is respected. It starts within a filled
// region.
- EXPECT_EQ(512, entry->GetAvailableRange(5500, 512, &start));
+ rv = entry->GetAvailableRange(5500, 512, &start, &cb);
+ EXPECT_EQ(512, cb.GetResult(rv));
EXPECT_EQ(5500, start);
// Test that the desired length is respected. It starts before a filled
// region.
- EXPECT_EQ(500, entry->GetAvailableRange(5000, 620, &start));
+ rv = entry->GetAvailableRange(5000, 620, &start, &cb);
+ EXPECT_EQ(500, cb.GetResult(rv));
EXPECT_EQ(5120, start);
// Test that multiple blocks are scanned.
- EXPECT_EQ(8192, entry->GetAvailableRange(40000, 20000, &start));
+ rv = entry->GetAvailableRange(40000, 20000, &start, &cb);
+ EXPECT_EQ(8192, cb.GetResult(rv));
EXPECT_EQ(50000, start);
entry->Close();
@@ -1149,8 +1225,8 @@
std::string key1("the first key");
std::string key2("the second key");
disk_cache::Entry *entry1, *entry2;
- ASSERT_TRUE(cache_->CreateEntry(key1, &entry1));
- ASSERT_TRUE(cache_->CreateEntry(key2, &entry2));
+ ASSERT_EQ(net::OK, CreateEntry(key1, &entry1));
+ ASSERT_EQ(net::OK, CreateEntry(key2, &entry2));
const int kSize = 4 * 1024;
scoped_refptr<net::IOBuffer> buf = new net::IOBuffer(kSize);
@@ -1177,7 +1253,7 @@
entry2->Close();
// Doom the second entry after it's fully saved.
- EXPECT_TRUE(cache_->DoomEntry(key2));
+ EXPECT_EQ(net::OK, DoomEntry(key2));
// Make sure we do all needed work. This may fail for entry2 if between Close
// and DoomEntry the system decides to remove all traces of the file from the
@@ -1199,6 +1275,8 @@
}
TEST_F(DiskCacheEntryTest, DoomSparseEntry) {
+ SetDirectMode();
+ UseCurrentThread();
InitCache();
DoomSparseEntry();
}
@@ -1212,7 +1290,7 @@
void DiskCacheEntryTest::PartialSparseEntry() {
std::string key("the first key");
disk_cache::Entry* entry;
- ASSERT_TRUE(cache_->CreateEntry(key, &entry));
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
// We should be able to deal with IO that is not aligned to the block size
// of a sparse entry, at least to write a big range without leaving holes.
@@ -1229,7 +1307,7 @@
EXPECT_EQ(kSmallSize,
entry->WriteSparseData(1080321, buf1, kSmallSize, NULL));
entry->Close();
- ASSERT_TRUE(cache_->OpenEntry(key, &entry));
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
scoped_refptr<net::IOBuffer> buf2 = new net::IOBuffer(kSize);
memset(buf2->data(), 0, kSize);
@@ -1244,37 +1322,48 @@
EXPECT_EQ(500, entry->ReadSparseData(kSize, buf2, kSize, NULL));
EXPECT_EQ(0, entry->ReadSparseData(499, buf2, kSize, NULL));
+ int rv;
int64 start;
+ TestCompletionCallback cb;
if (memory_only_) {
- EXPECT_EQ(100, entry->GetAvailableRange(0, 600, &start));
+ rv = entry->GetAvailableRange(0, 600, &start, &cb);
+ EXPECT_EQ(100, cb.GetResult(rv));
EXPECT_EQ(500, start);
} else {
- EXPECT_EQ(1024, entry->GetAvailableRange(0, 2048, &start));
+ rv = entry->GetAvailableRange(0, 2048, &start, &cb);
+ EXPECT_EQ(1024, cb.GetResult(rv));
EXPECT_EQ(1024, start);
}
- EXPECT_EQ(500, entry->GetAvailableRange(kSize, kSize, &start));
+ rv = entry->GetAvailableRange(kSize, kSize, &start, &cb);
+ EXPECT_EQ(500, cb.GetResult(rv));
EXPECT_EQ(kSize, start);
- EXPECT_EQ(3616, entry->GetAvailableRange(20 * 1024, 10000, &start));
+ rv = entry->GetAvailableRange(20 * 1024, 10000, &start, &cb);
+ EXPECT_EQ(3616, cb.GetResult(rv));
EXPECT_EQ(20 * 1024, start);
// 1. Query before a filled 1KB block.
// 2. Query within a filled 1KB block.
// 3. Query beyond a filled 1KB block.
if (memory_only_) {
- EXPECT_EQ(3496, entry->GetAvailableRange(19400, kSize, &start));
+ rv = entry->GetAvailableRange(19400, kSize, &start, &cb);
+ EXPECT_EQ(3496, cb.GetResult(rv));
EXPECT_EQ(20000, start);
} else {
- EXPECT_EQ(3016, entry->GetAvailableRange(19400, kSize, &start));
+ rv = entry->GetAvailableRange(19400, kSize, &start, &cb);
+ EXPECT_EQ(3016, cb.GetResult(rv));
EXPECT_EQ(20480, start);
}
- EXPECT_EQ(1523, entry->GetAvailableRange(3073, kSize, &start));
+ rv = entry->GetAvailableRange(3073, kSize, &start, &cb);
+ EXPECT_EQ(1523, cb.GetResult(rv));
EXPECT_EQ(3073, start);
- EXPECT_EQ(0, entry->GetAvailableRange(4600, kSize, &start));
+ rv = entry->GetAvailableRange(4600, kSize, &start, &cb);
+ EXPECT_EQ(0, cb.GetResult(rv));
EXPECT_EQ(4600, start);
// Now make another write and verify that there is no hole in between.
EXPECT_EQ(kSize, entry->WriteSparseData(500 + kSize, buf1, kSize, NULL));
- EXPECT_EQ(7 * 1024 + 500, entry->GetAvailableRange(1024, 10000, &start));
+ rv = entry->GetAvailableRange(1024, 10000, &start, &cb);
+ EXPECT_EQ(7 * 1024 + 500, cb.GetResult(rv));
EXPECT_EQ(1024, start);
EXPECT_EQ(kSize, entry->ReadSparseData(kSize, buf2, kSize, NULL));
EXPECT_EQ(0, memcmp(buf2->data(), buf1->data() + kSize - 500, 500));
@@ -1294,13 +1383,13 @@
PartialSparseEntry();
}
+// Tests that corrupt sparse children are removed automatically.
TEST_F(DiskCacheEntryTest, CleanupSparseEntry) {
InitCache();
std::string key("the first key");
disk_cache::Entry* entry;
- ASSERT_TRUE(cache_->CreateEntry(key, &entry));
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
- // Corrupt sparse children should be removed automatically.
const int kSize = 4 * 1024;
scoped_refptr<net::IOBuffer> buf1 = new net::IOBuffer(kSize);
CacheTestFillBuffer(buf1->data(), kSize, false);
@@ -1315,7 +1404,7 @@
void* iter = NULL;
int count = 0;
std::string child_key[2];
- while (cache_->OpenNextEntry(&iter, &entry)) {
+ while (OpenNextEntry(&iter, &entry) == net::OK) {
ASSERT_TRUE(entry != NULL);
// Writing to an entry will alter the LRU list and invalidate the iterator.
if (entry->GetKey() != key && count < 2)
@@ -1323,14 +1412,14 @@
entry->Close();
}
for (int i = 0; i < 2; i++) {
- ASSERT_TRUE(cache_->OpenEntry(child_key[i], &entry));
+ ASSERT_EQ(net::OK, OpenEntry(child_key[i], &entry));
// Overwrite the header's magic and signature.
EXPECT_EQ(12, entry->WriteData(2, 0, buf1, 12, NULL, false));
entry->Close();
}
EXPECT_EQ(4, cache_->GetEntryCount());
- ASSERT_TRUE(cache_->OpenEntry(key, &entry));
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
// Two children should be gone. One while reading and one while writing.
EXPECT_EQ(0, entry->ReadSparseData(2 * k1Meg + 8192, buf1, kSize, NULL));
@@ -1346,33 +1435,29 @@
}
TEST_F(DiskCacheEntryTest, CancelSparseIO) {
+ UseCurrentThread();
InitCache();
std::string key("the first key");
disk_cache::Entry* entry;
- ASSERT_TRUE(cache_->CreateEntry(key, &entry));
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
const int kSize = 40 * 1024;
scoped_refptr<net::IOBuffer> buf = new net::IOBuffer(kSize);
CacheTestFillBuffer(buf->data(), kSize, false);
- TestCompletionCallback cb1, cb2, cb3, cb4;
- int64 offset = 0;
- int tries = 0;
- const int maxtries = 100; // Avoid hang on infinitely fast disks
- for (int ret = 0; ret != net::ERR_IO_PENDING; offset += kSize * 4) {
- ret = entry->WriteSparseData(offset, buf, kSize, &cb1);
- if (++tries > maxtries) {
- LOG(ERROR) << "Data writes never come back PENDING; skipping test";
- entry->Close();
- return;
- }
- }
+ // This will open and write two "real" entries.
+ TestCompletionCallback cb1, cb2, cb3, cb4, cb5;
+ int rv = entry->WriteSparseData(1024 * 1024 - 4096, buf, kSize, &cb1);
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
- // Cannot use the entry at this point.
- offset = 0;
- EXPECT_EQ(net::ERR_CACHE_OPERATION_NOT_SUPPORTED,
- entry->GetAvailableRange(offset, kSize, &offset));
- EXPECT_EQ(net::OK, entry->ReadyForSparseIO(&cb2));
+ int64 offset = 0;
+ rv = entry->GetAvailableRange(offset, kSize, &offset, &cb5);
+ rv = cb5.GetResult(rv);
+ if (!cb1.have_result()) {
+ // We may or may not have finished writing to the entry. If we have not,
+ // we cannot start another operation at this time.
+ EXPECT_EQ(net::ERR_CACHE_OPERATION_NOT_SUPPORTED, rv);
+ }
// We cancel the pending operation, and register multiple notifications.
entry->CancelSparseIO();
@@ -1381,21 +1466,22 @@
entry->CancelSparseIO(); // Should be a no op at this point.
EXPECT_EQ(net::ERR_IO_PENDING, entry->ReadyForSparseIO(&cb4));
- offset = 0;
- EXPECT_EQ(net::ERR_CACHE_OPERATION_NOT_SUPPORTED,
- entry->GetAvailableRange(offset, kSize, &offset));
- EXPECT_EQ(net::ERR_CACHE_OPERATION_NOT_SUPPORTED,
- entry->ReadSparseData(offset, buf, kSize, NULL));
- EXPECT_EQ(net::ERR_CACHE_OPERATION_NOT_SUPPORTED,
- entry->WriteSparseData(offset, buf, kSize, NULL));
+ if (!cb1.have_result()) {
+ EXPECT_EQ(net::ERR_CACHE_OPERATION_NOT_SUPPORTED,
+ entry->ReadSparseData(offset, buf, kSize, NULL));
+ EXPECT_EQ(net::ERR_CACHE_OPERATION_NOT_SUPPORTED,
+ entry->WriteSparseData(offset, buf, kSize, NULL));
+ }
- // Now see if we receive all notifications.
- EXPECT_EQ(kSize, cb1.GetResult(net::ERR_IO_PENDING));
- EXPECT_EQ(net::OK, cb2.GetResult(net::ERR_IO_PENDING));
- EXPECT_EQ(net::OK, cb3.GetResult(net::ERR_IO_PENDING));
- EXPECT_EQ(net::OK, cb4.GetResult(net::ERR_IO_PENDING));
+ // Now see if we receive all notifications. Note that we should not be able
+ // to write everything (unless the timing of the system is really weird).
+ rv = cb1.WaitForResult();
+ EXPECT_TRUE(rv == 4096 || rv == kSize);
+ EXPECT_EQ(net::OK, cb2.WaitForResult());
+ EXPECT_EQ(net::OK, cb3.WaitForResult());
+ EXPECT_EQ(net::OK, cb4.WaitForResult());
- EXPECT_EQ(kSize, entry->GetAvailableRange(offset, kSize, &offset));
- EXPECT_EQ(net::OK, entry->ReadyForSparseIO(&cb2));
+ rv = entry->GetAvailableRange(offset, kSize, &offset, &cb5);
+ EXPECT_EQ(0, cb5.GetResult(rv));
entry->Close();
}
diff --git a/net/disk_cache/eviction.cc b/net/disk_cache/eviction.cc
index bdcef6b..91ef9b7 100644
--- a/net/disk_cache/eviction.cc
+++ b/net/disk_cache/eviction.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Copyright (c) 2006-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.
@@ -38,12 +38,14 @@
#include "net/disk_cache/trace.h"
using base::Time;
+using base::TimeTicks;
namespace {
const int kCleanUpMargin = 1024 * 1024;
const int kHighUse = 10; // Reuse count to be on the HIGH_USE list.
const int kTargetTime = 24 * 7; // Time to be evicted (hours since last use).
+const int kMaxDelayedTrims = 60;
int LowWaterAdjust(int high_water) {
if (high_water < kCleanUpMargin)
@@ -67,21 +69,35 @@
first_trim_ = true;
trimming_ = false;
delay_trim_ = false;
+ trim_delays_ = 0;
+ init_ = true;
+}
+
+void Eviction::Stop() {
+ // It is possible for the backend initialization to fail, in which case this
+ // object was never initialized... and there is nothing to do.
+ if (!init_)
+ return;
+
+ // We want to stop further evictions, so let's pretend that we are busy from
+ // this point on.
+ DCHECK(!trimming_);
+ trimming_ = true;
}
void Eviction::TrimCache(bool empty) {
- if (new_eviction_)
- return TrimCacheV2(empty);
-
if (backend_->disabled_ || trimming_)
return;
- if (!empty && backend_->IsLoaded())
+ if (!empty && !ShouldTrim())
return PostDelayedTrim();
+ if (new_eviction_)
+ return TrimCacheV2(empty);
+
Trace("*** Trim Cache ***");
trimming_ = true;
- Time start = Time::Now();
+ TimeTicks start = TimeTicks::Now();
Rankings::ScopedRankingsBlock node(rankings_);
Rankings::ScopedRankingsBlock next(rankings_,
rankings_->GetPrev(node.get(), Rankings::NO_USE));
@@ -102,7 +118,7 @@
if (!empty) {
backend_->OnEvent(Stats::TRIM_ENTRY);
- if ((Time::Now() - start).InMilliseconds() > 20) {
+ if ((TimeTicks::Now() - start).InMilliseconds() > 20) {
MessageLoop::current()->PostTask(FROM_HERE,
factory_.NewRunnableMethod(&Eviction::TrimCache, false));
break;
@@ -111,7 +127,12 @@
}
}
- CACHE_UMA(AGE_MS, "TotalTrimTime", backend_->GetSizeGroup(), start);
+ if (empty) {
+ CACHE_UMA(AGE_MS, "TotalClearTimeV1", 0, start);
+ } else {
+ CACHE_UMA(AGE_MS, "TotalTrimTimeV1", backend_->GetSizeGroup(), start);
+ }
+
trimming_ = false;
Trace("*** Trim Cache end ***");
return;
@@ -153,15 +174,28 @@
if (delay_trim_)
return;
delay_trim_ = true;
+ trim_delays_++;
MessageLoop::current()->PostDelayedTask(FROM_HERE,
factory_.NewRunnableMethod(&Eviction::DelayedTrim), 1000);
}
void Eviction::DelayedTrim() {
delay_trim_ = false;
+ if (trim_delays_ < kMaxDelayedTrims && backend_->IsLoaded())
+ return PostDelayedTrim();
+
TrimCache(false);
}
+bool Eviction::ShouldTrim() {
+ if (trim_delays_ < kMaxDelayedTrims && backend_->IsLoaded())
+ return false;
+
+ UMA_HISTOGRAM_COUNTS("DiskCache.TrimDelays", trim_delays_);
+ trim_delays_ = 0;
+ return true;
+}
+
void Eviction::ReportTrimTimes(EntryImpl* entry) {
if (first_trim_) {
first_trim_ = false;
@@ -203,7 +237,7 @@
ReportTrimTimes(entry);
if (empty || !new_eviction_) {
- entry->Doom();
+ entry->DoomImpl();
} else {
entry->DeleteEntryData(false);
EntryStore* info = entry->entry()->Data();
@@ -223,15 +257,9 @@
// -----------------------------------------------------------------------
void Eviction::TrimCacheV2(bool empty) {
- if (backend_->disabled_ || trimming_)
- return;
-
- if (!empty && backend_->IsLoaded())
- return PostDelayedTrim();
-
Trace("*** Trim Cache ***");
trimming_ = true;
- Time start = Time::Now();
+ TimeTicks start = TimeTicks::Now();
const int kListsToSearch = 3;
Rankings::ScopedRankingsBlock next[kListsToSearch];
@@ -282,7 +310,7 @@
if (!EvictEntry(node.get(), empty))
continue;
- if (!empty && (Time::Now() - start).InMilliseconds() > 20) {
+ if (!empty && (TimeTicks::Now() - start).InMilliseconds() > 20) {
MessageLoop::current()->PostTask(FROM_HERE,
factory_.NewRunnableMethod(&Eviction::TrimCache, false));
break;
@@ -300,7 +328,12 @@
factory_.NewRunnableMethod(&Eviction::TrimDeleted, empty));
}
- CACHE_UMA(AGE_MS, "TotalTrimTime", backend_->GetSizeGroup(), start);
+ if (empty) {
+ CACHE_UMA(AGE_MS, "TotalClearTimeV2", 0, start);
+ } else {
+ CACHE_UMA(AGE_MS, "TotalTrimTimeV2", backend_->GetSizeGroup(), start);
+ }
+
Trace("*** Trim Cache end ***");
trimming_ = false;
return;
@@ -396,17 +429,19 @@
if (backend_->disabled_)
return;
- Time start = Time::Now();
+ TimeTicks start = TimeTicks::Now();
Rankings::ScopedRankingsBlock node(rankings_);
Rankings::ScopedRankingsBlock next(rankings_,
rankings_->GetPrev(node.get(), Rankings::DELETED));
+ bool deleted = false;
for (int i = 0; (i < 4 || empty) && next.get(); i++) {
node.reset(next.release());
next.reset(rankings_->GetPrev(node.get(), Rankings::DELETED));
- RemoveDeletedNode(node.get());
+ deleted |= RemoveDeletedNode(node.get());
}
- if (header_->lru.sizes[Rankings::DELETED] > header_->num_entries / 4)
+ if (deleted && !empty &&
+ header_->lru.sizes[Rankings::DELETED] > header_->num_entries / 4)
MessageLoop::current()->PostTask(FROM_HERE,
factory_.NewRunnableMethod(&Eviction::TrimDeleted, false));
@@ -429,10 +464,11 @@
// We ignore the failure; we're removing the entry anyway.
entry->Update();
}
+ bool doomed = (entry->entry()->Data()->state == ENTRY_DOOMED);
entry->entry()->Data()->state = ENTRY_DOOMED;
- entry->Doom();
+ entry->DoomImpl();
entry->Release();
- return true;
+ return !doomed;
}
bool Eviction::NodeIsOldEnough(CacheRankingsBlock* node, int list) {
diff --git a/net/disk_cache/eviction.h b/net/disk_cache/eviction.h
index e3c0a72..76ee00b 100644
--- a/net/disk_cache/eviction.h
+++ b/net/disk_cache/eviction.h
@@ -20,10 +20,13 @@
// integrated with BackendImpl.
class Eviction {
public:
- Eviction() : backend_(NULL), ALLOW_THIS_IN_INITIALIZER_LIST(factory_(this)) {}
+ Eviction()
+ : backend_(NULL), init_(false),
+ ALLOW_THIS_IN_INITIALIZER_LIST(factory_(this)) {}
~Eviction() {}
void Init(BackendImpl* backend);
+ void Stop();
// Deletes entries from the cache until the current size is below the limit.
// If empty is true, the whole cache will be trimmed, regardless of being in
@@ -42,6 +45,7 @@
private:
void PostDelayedTrim();
void DelayedTrim();
+ bool ShouldTrim();
void ReportTrimTimes(EntryImpl* entry);
Rankings::List GetListForEntry(EntryImpl* entry);
bool EvictEntry(CacheRankingsBlock* node, bool empty);
@@ -67,10 +71,12 @@
Rankings* rankings_;
IndexHeader* header_;
int max_size_;
+ int trim_delays_;
bool new_eviction_;
bool first_trim_;
bool trimming_;
bool delay_trim_;
+ bool init_;
ScopedRunnableMethodFactory<Eviction> factory_;
DISALLOW_COPY_AND_ASSIGN(Eviction);
diff --git a/net/disk_cache/file_posix.cc b/net/disk_cache/file_posix.cc
index bfaad59..295f744 100644
--- a/net/disk_cache/file_posix.cc
+++ b/net/disk_cache/file_posix.cc
@@ -122,8 +122,8 @@
void PostRead(disk_cache::File* file, void* buf, size_t buf_len,
size_t offset, disk_cache::FileIOCallback* callback);
void PostWrite(disk_cache::File* file, const void* buf, size_t buf_len,
- size_t offset, disk_cache::FileIOCallback* callback,
- bool delete_buffer);
+ size_t offset, disk_cache::FileIOCallback* callback,
+ bool delete_buffer);
// Blocks the current thread until all IO operations tracked by this object
// complete.
@@ -193,6 +193,9 @@
io_list_.insert(operation.get());
file->AddRef(); // Balanced on InvokeCallback()
+ if (!callback_thread_)
+ callback_thread_ = MessageLoop::current();
+
WorkerPool::PostTask(FROM_HERE,
NewRunnableMethod(operation.get(), &BackgroundIO::Read),
true);
@@ -207,6 +210,9 @@
io_list_.insert(operation.get());
file->AddRef(); // Balanced on InvokeCallback()
+ if (!callback_thread_)
+ callback_thread_ = MessageLoop::current();
+
WorkerPool::PostTask(FROM_HERE,
NewRunnableMethod(operation.get(), &BackgroundIO::Write,
delete_buffer),
@@ -219,6 +225,8 @@
IOList::iterator it = io_list_.begin();
InvokeCallback(*it, true);
}
+ // Unit tests can use different threads.
+ callback_thread_ = NULL;
}
// Runs on a worker thread.
@@ -372,8 +380,9 @@
// Static.
void File::WaitForPendingIO(int* num_pending_io) {
- if (*num_pending_io)
- Singleton<InFlightIO>::get()->WaitForPendingIO();
+ // We may be running unit tests so we should allow InFlightIO to reset the
+ // message loop.
+ Singleton<InFlightIO>::get()->WaitForPendingIO();
}
} // namespace disk_cache
diff --git a/net/disk_cache/histogram_macros.h b/net/disk_cache/histogram_macros.h
index 17cd345..361a9d6 100644
--- a/net/disk_cache/histogram_macros.h
+++ b/net/disk_cache/histogram_macros.h
@@ -11,22 +11,70 @@
#ifndef NET_DISK_CACHE_HISTOGRAM_MACROS_H_
#define NET_DISK_CACHE_HISTOGRAM_MACROS_H_
+// -----------------------------------------------------------------------------
+
+// These histograms follow the definition of UMA_HISTOGRAMN_XXX except that
+// whenever the name changes (the experiment group changes), the histrogram
+// object is re-created.
+
+#define CACHE_HISTOGRAM_CUSTOM_COUNTS(name, sample, min, max, bucket_count) \
+ do { \
+ static scoped_refptr<Histogram> counter; \
+ if (!counter || name != counter->histogram_name()) \
+ counter = Histogram::FactoryGet(name, min, max, bucket_count, \
+ Histogram::kUmaTargetedHistogramFlag); \
+ counter->Add(sample); \
+ } while (0)
+
+#define CACHE_HISTOGRAM_COUNTS(name, sample) CACHE_HISTOGRAM_CUSTOM_COUNTS( \
+ name, sample, 1, 1000000, 50)
+
+#define CACHE_HISTOGRAM_COUNTS_10000(name, sample) \
+ CACHE_HISTOGRAM_CUSTOM_COUNTS(name, sample, 1, 10000, 50)
+
+#define CACHE_HISTOGRAM_CUSTOM_TIMES(name, sample, min, max, bucket_count) \
+ do { \
+ static scoped_refptr<Histogram> counter; \
+ if (!counter || name != counter->histogram_name()) \
+ counter = Histogram::FactoryTimeGet(name, min, max, bucket_count, \
+ Histogram::kUmaTargetedHistogramFlag); \
+ counter->AddTime(sample); \
+ } while (0)
+
+#define CACHE_HISTOGRAM_TIMES(name, sample) CACHE_HISTOGRAM_CUSTOM_TIMES( \
+ name, sample, base::TimeDelta::FromMilliseconds(1), \
+ base::TimeDelta::FromSeconds(10), 50)
+
+#define CACHE_HISTOGRAM_ENUMERATION(name, sample, boundary_value) do { \
+ static scoped_refptr<Histogram> counter; \
+ if (!counter || name != counter->histogram_name()) \
+ counter = LinearHistogram::FactoryGet( \
+ name, 1, boundary_value, boundary_value + 1, \
+ Histogram::kUmaTargetedHistogramFlag); \
+ counter->Add(sample); \
+ } while (0)
+
+#define CACHE_HISTOGRAM_PERCENTAGE(name, under_one_hundred) \
+ CACHE_HISTOGRAM_ENUMERATION(name, under_one_hundred, 101)
+
+// -----------------------------------------------------------------------------
+
// HISTOGRAM_HOURS will collect time related data with a granularity of hours
// and normal values of a few months.
-#define UMA_HISTOGRAM_HOURS UMA_HISTOGRAM_COUNTS_10000
+#define CACHE_HISTOGRAM_HOURS CACHE_HISTOGRAM_COUNTS_10000
// HISTOGRAM_AGE will collect time elapsed since |initial_time|, with a
// granularity of hours and normal values of a few months.
-#define UMA_HISTOGRAM_AGE(name, initial_time)\
- UMA_HISTOGRAM_COUNTS_10000(name, (Time::Now() - initial_time).InHours())
+#define CACHE_HISTOGRAM_AGE(name, initial_time) \
+ CACHE_HISTOGRAM_COUNTS_10000(name, (Time::Now() - initial_time).InHours())
// HISTOGRAM_AGE_MS will collect time elapsed since |initial_time|, with the
// normal resolution of the UMA_HISTOGRAM_TIMES.
-#define UMA_HISTOGRAM_AGE_MS(name, initial_time)\
- UMA_HISTOGRAM_TIMES(name, Time::Now() - initial_time)
+#define CACHE_HISTOGRAM_AGE_MS(name, initial_time)\
+ CACHE_HISTOGRAM_TIMES(name, TimeTicks::Now() - initial_time)
-#define UMA_HISTOGRAM_CACHE_ERROR(name, sample) \
- UMA_HISTOGRAM_ENUMERATION(name, sample, 50)
+#define CACHE_HISTOGRAM_CACHE_ERROR(name, sample) \
+ CACHE_HISTOGRAM_ENUMERATION(name, sample, 50)
#ifdef NET_DISK_CACHE_BACKEND_IMPL_CC_
#define BACKEND_OBJ this
@@ -48,10 +96,13 @@
const std::string my_name = BACKEND_OBJ->HistogramName(name, experiment);\
switch (BACKEND_OBJ->cache_type()) {\
case net::DISK_CACHE:\
- UMA_HISTOGRAM_##type(my_name.data(), sample);\
+ CACHE_HISTOGRAM_##type(my_name.data(), sample);\
break;\
case net::MEDIA_CACHE:\
- UMA_HISTOGRAM_##type(my_name.data(), sample);\
+ CACHE_HISTOGRAM_##type(my_name.data(), sample);\
+ break;\
+ case net::APP_CACHE:\
+ CACHE_HISTOGRAM_##type(my_name.data(), sample);\
break;\
default:\
NOTREACHED();\
diff --git a/net/disk_cache/in_flight_backend_io.cc b/net/disk_cache/in_flight_backend_io.cc
new file mode 100644
index 0000000..1e2ce9f
--- /dev/null
+++ b/net/disk_cache/in_flight_backend_io.cc
@@ -0,0 +1,448 @@
+// 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 "net/disk_cache/in_flight_backend_io.h"
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/backend_impl.h"
+#include "net/disk_cache/entry_impl.h"
+
+namespace disk_cache {
+
+BackendIO::BackendIO(InFlightIO* controller, BackendImpl* backend,
+ net::CompletionCallback* callback)
+ : BackgroundIO(controller), backend_(backend), callback_(callback),
+ operation_(OP_NONE),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ my_callback_(this, &BackendIO::OnIOComplete)) {
+}
+
+// Runs on the background thread.
+void BackendIO::ExecuteOperation() {
+ if (IsEntryOperation())
+ return ExecuteEntryOperation();
+
+ ExecuteBackendOperation();
+}
+
+// Runs on the background thread.
+void BackendIO::OnIOComplete(int result) {
+ DCHECK(IsEntryOperation());
+ DCHECK_NE(result, net::ERR_IO_PENDING);
+ result_ = result;
+ controller_->OnIOComplete(this);
+}
+
+bool BackendIO::IsEntryOperation() {
+ return operation_ > OP_MAX_BACKEND;
+}
+
+void BackendIO::ReleaseEntry() {
+ entry_ = NULL;
+}
+
+void BackendIO::Init() {
+ operation_ = OP_INIT;
+}
+
+void BackendIO::OpenEntry(const std::string& key, Entry** entry) {
+ operation_ = OP_OPEN;
+ key_ = key;
+ entry_ptr_ = entry;
+}
+
+void BackendIO::CreateEntry(const std::string& key, Entry** entry) {
+ operation_ = OP_CREATE;
+ key_ = key;
+ entry_ptr_ = entry;
+}
+
+void BackendIO::DoomEntry(const std::string& key) {
+ operation_ = OP_DOOM;
+ key_ = key;
+}
+
+void BackendIO::DoomAllEntries() {
+ operation_ = OP_DOOM_ALL;
+}
+
+void BackendIO::DoomEntriesBetween(const base::Time initial_time,
+ const base::Time end_time) {
+ operation_ = OP_DOOM_BETWEEN;
+ initial_time_ = initial_time;
+ end_time_ = end_time;
+}
+
+void BackendIO::DoomEntriesSince(const base::Time initial_time) {
+ operation_ = OP_DOOM_SINCE;
+ initial_time_ = initial_time;
+}
+
+void BackendIO::OpenNextEntry(void** iter, Entry** next_entry) {
+ operation_ = OP_OPEN_NEXT;
+ iter_ptr_ = iter;
+ entry_ptr_ = next_entry;
+}
+
+void BackendIO::OpenPrevEntry(void** iter, Entry** prev_entry) {
+ operation_ = OP_OPEN_PREV;
+ iter_ptr_ = iter;
+ entry_ptr_ = prev_entry;
+}
+
+void BackendIO::EndEnumeration(void* iterator) {
+ operation_ = OP_END_ENUMERATION;
+ iter_ = iterator;
+}
+
+void BackendIO::CloseEntryImpl(EntryImpl* entry) {
+ operation_ = OP_CLOSE_ENTRY;
+ entry_ = entry;
+}
+
+void BackendIO::DoomEntryImpl(EntryImpl* entry) {
+ operation_ = OP_DOOM_ENTRY;
+ entry_ = entry;
+}
+
+void BackendIO::FlushQueue() {
+ operation_ = OP_FLUSH_QUEUE;
+}
+
+void BackendIO::ReadData(EntryImpl* entry, int index, int offset,
+ net::IOBuffer* buf, int buf_len) {
+ operation_ = OP_READ;
+ entry_ = entry;
+ index_ = index;
+ offset_ = offset;
+ buf_ = buf;
+ buf_len_ = buf_len;
+}
+
+void BackendIO::WriteData(EntryImpl* entry, int index, int offset,
+ net::IOBuffer* buf, int buf_len, bool truncate) {
+ operation_ = OP_WRITE;
+ entry_ = entry;
+ index_ = index;
+ offset_ = offset;
+ buf_ = buf;
+ buf_len_ = buf_len;
+ truncate_ = truncate;
+}
+
+void BackendIO::ReadSparseData(EntryImpl* entry, int64 offset,
+ net::IOBuffer* buf, int buf_len) {
+ operation_ = OP_READ_SPARSE;
+ entry_ = entry;
+ offset64_ = offset;
+ buf_ = buf;
+ buf_len_ = buf_len;
+}
+
+void BackendIO::WriteSparseData(EntryImpl* entry, int64 offset,
+ net::IOBuffer* buf, int buf_len) {
+ operation_ = OP_WRITE_SPARSE;
+ entry_ = entry;
+ offset64_ = offset;
+ buf_ = buf;
+ buf_len_ = buf_len;
+}
+
+void BackendIO::GetAvailableRange(EntryImpl* entry, int64 offset, int len,
+ int64* start) {
+ operation_ = OP_GET_RANGE;
+ entry_ = entry;
+ offset64_ = offset;
+ buf_len_ = len;
+ start_ = start;
+}
+
+void BackendIO::CancelSparseIO(EntryImpl* entry) {
+ operation_ = OP_CANCEL_IO;
+ entry_ = entry;
+}
+
+void BackendIO::ReadyForSparseIO(EntryImpl* entry) {
+ operation_ = OP_IS_READY;
+ entry_ = entry;
+}
+
+// Runs on the background thread.
+void BackendIO::ExecuteBackendOperation() {
+ switch (operation_) {
+ case OP_INIT:
+ result_ = backend_->SyncInit();
+ break;
+ case OP_OPEN:
+ result_ = backend_->SyncOpenEntry(key_, entry_ptr_);
+ break;
+ case OP_CREATE:
+ result_ = backend_->SyncCreateEntry(key_, entry_ptr_);
+ break;
+ case OP_DOOM:
+ result_ = backend_->SyncDoomEntry(key_);
+ break;
+ case OP_DOOM_ALL:
+ result_ = backend_->SyncDoomAllEntries();
+ break;
+ case OP_DOOM_BETWEEN:
+ result_ = backend_->SyncDoomEntriesBetween(initial_time_, end_time_);
+ break;
+ case OP_DOOM_SINCE:
+ result_ = backend_->SyncDoomEntriesSince(initial_time_);
+ break;
+ case OP_OPEN_NEXT:
+ result_ = backend_->SyncOpenNextEntry(iter_ptr_, entry_ptr_);
+ break;
+ case OP_OPEN_PREV:
+ result_ = backend_->SyncOpenPrevEntry(iter_ptr_, entry_ptr_);
+ break;
+ case OP_END_ENUMERATION:
+ backend_->SyncEndEnumeration(iter_);
+ result_ = net::OK;
+ break;
+ case OP_CLOSE_ENTRY:
+ entry_->Release();
+ result_ = net::OK;
+ break;
+ case OP_DOOM_ENTRY:
+ entry_->DoomImpl();
+ result_ = net::OK;
+ break;
+ case OP_FLUSH_QUEUE:
+ result_ = net::OK;
+ break;
+ default:
+ NOTREACHED() << "Invalid Operation";
+ result_ = net::ERR_UNEXPECTED;
+ }
+ DCHECK_NE(net::ERR_IO_PENDING, result_);
+ controller_->OnIOComplete(this);
+}
+
+// Runs on the background thread.
+void BackendIO::ExecuteEntryOperation() {
+ switch (operation_) {
+ case OP_READ:
+ result_ = entry_->ReadDataImpl(index_, offset_, buf_, buf_len_,
+ &my_callback_);
+ break;
+ case OP_WRITE:
+ result_ = entry_->WriteDataImpl(index_, offset_, buf_, buf_len_,
+ &my_callback_, truncate_);
+ break;
+ case OP_READ_SPARSE:
+ result_ = entry_->ReadSparseDataImpl(offset64_, buf_, buf_len_,
+ &my_callback_);
+ break;
+ case OP_WRITE_SPARSE:
+ result_ = entry_->WriteSparseDataImpl(offset64_, buf_, buf_len_,
+ &my_callback_);
+ break;
+ case OP_GET_RANGE:
+ result_ = entry_->GetAvailableRangeImpl(offset64_, buf_len_, start_);
+ break;
+ case OP_CANCEL_IO:
+ entry_->CancelSparseIOImpl();
+ result_ = net::OK;
+ break;
+ case OP_IS_READY:
+ result_ = entry_->ReadyForSparseIOImpl(&my_callback_);
+ break;
+ default:
+ NOTREACHED() << "Invalid Operation";
+ result_ = net::ERR_UNEXPECTED;
+ }
+ if (result_ != net::ERR_IO_PENDING)
+ controller_->OnIOComplete(this);
+}
+
+// ---------------------------------------------------------------------------
+
+void InFlightBackendIO::Init(CompletionCallback* callback) {
+ scoped_refptr<BackendIO> operation = new BackendIO(this, backend_, callback);
+ operation->Init();
+ QueueOperation(operation);
+}
+
+void InFlightBackendIO::OpenEntry(const std::string& key, Entry** entry,
+ CompletionCallback* callback) {
+ scoped_refptr<BackendIO> operation = new BackendIO(this, backend_, callback);
+ operation->OpenEntry(key, entry);
+ QueueOperation(operation);
+}
+
+void InFlightBackendIO::CreateEntry(const std::string& key, Entry** entry,
+ CompletionCallback* callback) {
+ scoped_refptr<BackendIO> operation = new BackendIO(this, backend_, callback);
+ operation->CreateEntry(key, entry);
+ QueueOperation(operation);
+}
+
+void InFlightBackendIO::DoomEntry(const std::string& key,
+ CompletionCallback* callback) {
+ scoped_refptr<BackendIO> operation = new BackendIO(this, backend_, callback);
+ operation->DoomEntry(key);
+ QueueOperation(operation);
+}
+
+void InFlightBackendIO::DoomAllEntries(CompletionCallback* callback) {
+ scoped_refptr<BackendIO> operation = new BackendIO(this, backend_, callback);
+ operation->DoomAllEntries();
+ QueueOperation(operation);
+}
+
+void InFlightBackendIO::DoomEntriesBetween(const base::Time initial_time,
+ const base::Time end_time,
+ CompletionCallback* callback) {
+ scoped_refptr<BackendIO> operation = new BackendIO(this, backend_, callback);
+ operation->DoomEntriesBetween(initial_time, end_time);
+ QueueOperation(operation);
+}
+
+void InFlightBackendIO::DoomEntriesSince(const base::Time initial_time,
+ CompletionCallback* callback) {
+ scoped_refptr<BackendIO> operation = new BackendIO(this, backend_, callback);
+ operation->DoomEntriesSince(initial_time);
+ QueueOperation(operation);
+}
+
+void InFlightBackendIO::OpenNextEntry(void** iter, Entry** next_entry,
+ CompletionCallback* callback) {
+ scoped_refptr<BackendIO> operation = new BackendIO(this, backend_, callback);
+ operation->OpenNextEntry(iter, next_entry);
+ QueueOperation(operation);
+}
+
+void InFlightBackendIO::OpenPrevEntry(void** iter, Entry** prev_entry,
+ CompletionCallback* callback) {
+ scoped_refptr<BackendIO> operation = new BackendIO(this, backend_, callback);
+ operation->OpenPrevEntry(iter, prev_entry);
+ QueueOperation(operation);
+}
+
+void InFlightBackendIO::EndEnumeration(void* iterator) {
+ scoped_refptr<BackendIO> operation = new BackendIO(this, backend_, NULL);
+ operation->EndEnumeration(iterator);
+ QueueOperation(operation);
+}
+
+void InFlightBackendIO::CloseEntryImpl(EntryImpl* entry) {
+ scoped_refptr<BackendIO> operation = new BackendIO(this, backend_, NULL);
+ operation->CloseEntryImpl(entry);
+ QueueOperation(operation);
+}
+
+void InFlightBackendIO::DoomEntryImpl(EntryImpl* entry) {
+ scoped_refptr<BackendIO> operation = new BackendIO(this, backend_, NULL);
+ operation->DoomEntryImpl(entry);
+ QueueOperation(operation);
+}
+
+void InFlightBackendIO::FlushQueue(net::CompletionCallback* callback) {
+ scoped_refptr<BackendIO> operation = new BackendIO(this, backend_, callback);
+ operation->FlushQueue();
+ QueueOperation(operation);
+}
+
+void InFlightBackendIO::ReadData(EntryImpl* entry, int index, int offset,
+ net::IOBuffer* buf, int buf_len,
+ CompletionCallback* callback) {
+ scoped_refptr<BackendIO> operation = new BackendIO(this, backend_, callback);
+ operation->ReadData(entry, index, offset, buf, buf_len);
+ QueueOperation(operation);
+}
+
+void InFlightBackendIO::WriteData(EntryImpl* entry, int index, int offset,
+ net::IOBuffer* buf, int buf_len,
+ bool truncate,
+ CompletionCallback* callback) {
+ scoped_refptr<BackendIO> operation = new BackendIO(this, backend_, callback);
+ operation->WriteData(entry, index, offset, buf, buf_len, truncate);
+ QueueOperation(operation);
+}
+
+void InFlightBackendIO::ReadSparseData(EntryImpl* entry, int64 offset,
+ net::IOBuffer* buf, int buf_len,
+ CompletionCallback* callback) {
+ scoped_refptr<BackendIO> operation = new BackendIO(this, backend_, callback);
+ operation->ReadSparseData(entry, offset, buf, buf_len);
+ QueueOperation(operation);
+}
+
+void InFlightBackendIO::WriteSparseData(EntryImpl* entry, int64 offset,
+ net::IOBuffer* buf, int buf_len,
+ CompletionCallback* callback) {
+ scoped_refptr<BackendIO> operation = new BackendIO(this, backend_, callback);
+ operation->WriteSparseData(entry, offset, buf, buf_len);
+ QueueOperation(operation);
+}
+
+void InFlightBackendIO::GetAvailableRange(EntryImpl* entry, int64 offset,
+ int len, int64* start,
+ CompletionCallback* callback) {
+ scoped_refptr<BackendIO> operation = new BackendIO(this, backend_, callback);
+ operation->GetAvailableRange(entry, offset, len, start);
+ QueueOperation(operation);
+}
+
+void InFlightBackendIO::CancelSparseIO(EntryImpl* entry) {
+ scoped_refptr<BackendIO> operation = new BackendIO(this, backend_, NULL);
+ operation->CancelSparseIO(entry);
+ QueueOperation(operation);
+}
+
+void InFlightBackendIO::ReadyForSparseIO(EntryImpl* entry,
+ CompletionCallback* callback) {
+ scoped_refptr<BackendIO> operation = new BackendIO(this, backend_, callback);
+ operation->ReadyForSparseIO(entry);
+ QueueOperation(operation);
+}
+
+void InFlightBackendIO::WaitForPendingIO() {
+ // We clear the list first so that we don't post more operations after this
+ // point.
+ pending_ops_.clear();
+ InFlightIO::WaitForPendingIO();
+}
+
+void InFlightBackendIO::OnOperationComplete(BackgroundIO* operation,
+ bool cancel) {
+ BackendIO* op = static_cast<BackendIO*>(operation);
+
+ if (!op->IsEntryOperation() && !pending_ops_.empty()) {
+ // Process the next request. Note that invoking the callback may result
+ // in the backend destruction (and with it this object), so we should deal
+ // with the next operation before invoking the callback.
+ scoped_refptr<BackendIO> next_op = pending_ops_.front();
+ pending_ops_.pop_front();
+ PostOperation(next_op);
+ }
+
+ if (op->callback() && (!cancel || op->IsEntryOperation()))
+ op->callback()->Run(op->result());
+
+ if (cancel)
+ op->ReleaseEntry();
+}
+
+void InFlightBackendIO::QueueOperation(BackendIO* operation) {
+ if (operation->IsEntryOperation())
+ return PostOperation(operation);
+
+ if (pending_ops_.empty())
+ return PostOperation(operation);
+
+ pending_ops_.push_back(operation);
+}
+
+void InFlightBackendIO::PostOperation(BackendIO* operation) {
+ background_thread_->PostTask(FROM_HERE,
+ NewRunnableMethod(operation, &BackendIO::ExecuteOperation));
+ OnOperationPosted(operation);
+}
+
+} // namespace
diff --git a/net/disk_cache/in_flight_backend_io.h b/net/disk_cache/in_flight_backend_io.h
new file mode 100644
index 0000000..1cd0d41
--- /dev/null
+++ b/net/disk_cache/in_flight_backend_io.h
@@ -0,0 +1,201 @@
+// 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.
+
+#ifndef NET_DISK_CACHE_IN_FLIGHT_BACKEND_IO_H_
+#define NET_DISK_CACHE_IN_FLIGHT_BACKEND_IO_H_
+
+#include <list>
+#include <string>
+
+#include "base/message_loop_proxy.h"
+#include "net/base/completion_callback.h"
+#include "net/base/io_buffer.h"
+#include "net/disk_cache/entry_impl.h"
+#include "net/disk_cache/in_flight_io.h"
+
+namespace disk_cache {
+
+class BackendImpl;
+
+// This class represents a single asynchronous disk cache IO operation while it
+// is being bounced between threads.
+class BackendIO : public BackgroundIO {
+ public:
+ BackendIO(InFlightIO* controller, BackendImpl* backend,
+ net::CompletionCallback* callback);
+
+ // Runs the actual operation on the background thread.
+ void ExecuteOperation();
+
+ // Callback implementation.
+ void OnIOComplete(int result);
+
+ // Returns true if this operation is directed to an entry (vs. the backend).
+ bool IsEntryOperation();
+
+ net::CompletionCallback* callback() { return callback_; }
+
+ void ReleaseEntry();
+
+ // The operations we proxy:
+ void Init();
+ void OpenEntry(const std::string& key, Entry** entry);
+ void CreateEntry(const std::string& key, Entry** entry);
+ void DoomEntry(const std::string& key);
+ void DoomAllEntries();
+ void DoomEntriesBetween(const base::Time initial_time,
+ const base::Time end_time);
+ void DoomEntriesSince(const base::Time initial_time);
+ void OpenNextEntry(void** iter, Entry** next_entry);
+ void OpenPrevEntry(void** iter, Entry** prev_entry);
+ void EndEnumeration(void* iterator);
+ void CloseEntryImpl(EntryImpl* entry);
+ void DoomEntryImpl(EntryImpl* entry);
+ void FlushQueue(); // Dummy operation.
+ void ReadData(EntryImpl* entry, int index, int offset, net::IOBuffer* buf,
+ int buf_len);
+ void WriteData(EntryImpl* entry, int index, int offset, net::IOBuffer* buf,
+ int buf_len, bool truncate);
+ void ReadSparseData(EntryImpl* entry, int64 offset, net::IOBuffer* buf,
+ int buf_len);
+ void WriteSparseData(EntryImpl* entry, int64 offset, net::IOBuffer* buf,
+ int buf_len);
+ void GetAvailableRange(EntryImpl* entry, int64 offset, int len, int64* start);
+ void CancelSparseIO(EntryImpl* entry);
+ void ReadyForSparseIO(EntryImpl* entry);
+
+ private:
+ // There are two types of operations to proxy: regular backend operations are
+ // queued so that we don't have more than one operation going on at the same
+ // time (for instance opening an entry and creating the same entry). On the
+ // other hand, operations targeted to a given entry can be long lived and
+ // support multiple simultaneous users (multiple reads or writes to the same
+ // entry), so they are not queued, just posted to the worker thread as they
+ // come.
+ enum Operation {
+ OP_NONE = 0,
+ OP_INIT,
+ OP_OPEN,
+ OP_CREATE,
+ OP_DOOM,
+ OP_DOOM_ALL,
+ OP_DOOM_BETWEEN,
+ OP_DOOM_SINCE,
+ OP_OPEN_NEXT,
+ OP_OPEN_PREV,
+ OP_END_ENUMERATION,
+ OP_CLOSE_ENTRY,
+ OP_DOOM_ENTRY,
+ OP_FLUSH_QUEUE,
+ OP_MAX_BACKEND,
+ OP_READ,
+ OP_WRITE,
+ OP_READ_SPARSE,
+ OP_WRITE_SPARSE,
+ OP_GET_RANGE,
+ OP_CANCEL_IO,
+ OP_IS_READY
+ };
+
+ ~BackendIO() {}
+
+ void ExecuteBackendOperation();
+ void ExecuteEntryOperation();
+
+ BackendImpl* backend_;
+ net::CompletionCallback* callback_;
+ Operation operation_;
+ net::CompletionCallbackImpl<BackendIO> my_callback_;
+
+ // The arguments of all the operations we proxy:
+ std::string key_;
+ Entry** entry_ptr_;
+ base::Time initial_time_;
+ base::Time end_time_;
+ void** iter_ptr_;
+ void* iter_;
+ EntryImpl* entry_;
+ int index_;
+ int offset_;
+ scoped_refptr<net::IOBuffer> buf_;
+ int buf_len_;
+ bool truncate_;
+ int64 offset64_;
+ int64* start_;
+
+ DISALLOW_COPY_AND_ASSIGN(BackendIO);
+};
+
+// The specialized controller that keeps track of current operations.
+class InFlightBackendIO : public InFlightIO {
+ public:
+ InFlightBackendIO(BackendImpl* backend,
+ base::MessageLoopProxy* background_thread)
+ : backend_(backend), background_thread_(background_thread) {}
+ ~InFlightBackendIO() {}
+
+ // The operations we proxy:
+ void Init(net::CompletionCallback* callback);
+ void OpenEntry(const std::string& key, Entry** entry,
+ net::CompletionCallback* callback);
+ void CreateEntry(const std::string& key, Entry** entry,
+ net::CompletionCallback* callback);
+ void DoomEntry(const std::string& key, net::CompletionCallback* callback);
+ void DoomAllEntries(net::CompletionCallback* callback);
+ void DoomEntriesBetween(const base::Time initial_time,
+ const base::Time end_time,
+ net::CompletionCallback* callback);
+ void DoomEntriesSince(const base::Time initial_time,
+ net::CompletionCallback* callback);
+ void OpenNextEntry(void** iter, Entry** next_entry,
+ net::CompletionCallback* callback);
+ void OpenPrevEntry(void** iter, Entry** prev_entry,
+ net::CompletionCallback* callback);
+ void EndEnumeration(void* iterator);
+ void CloseEntryImpl(EntryImpl* entry);
+ void DoomEntryImpl(EntryImpl* entry);
+ void FlushQueue(net::CompletionCallback* callback);
+ void ReadData(EntryImpl* entry, int index, int offset, net::IOBuffer* buf,
+ int buf_len, net::CompletionCallback* callback);
+ void WriteData(EntryImpl* entry, int index, int offset, net::IOBuffer* buf,
+ int buf_len, bool truncate, net::CompletionCallback* callback);
+ void ReadSparseData(EntryImpl* entry, int64 offset, net::IOBuffer* buf,
+ int buf_len, net::CompletionCallback* callback);
+ void WriteSparseData(EntryImpl* entry, int64 offset, net::IOBuffer* buf,
+ int buf_len, net::CompletionCallback* callback);
+ void GetAvailableRange(EntryImpl* entry, int64 offset, int len, int64* start,
+ net::CompletionCallback* callback);
+ void CancelSparseIO(EntryImpl* entry);
+ void ReadyForSparseIO(EntryImpl* entry, net::CompletionCallback* callback);
+
+ // Blocks until all operations are cancelled or completed.
+ void WaitForPendingIO();
+
+ scoped_refptr<base::MessageLoopProxy> background_thread() {
+ return background_thread_;
+ }
+
+ // Returns true if the current thread is the background thread.
+ bool BackgroundIsCurrentThread() {
+ return background_thread_->BelongsToCurrentThread();
+ }
+
+ protected:
+ virtual void OnOperationComplete(BackgroundIO* operation, bool cancel);
+
+ private:
+ typedef std::list<scoped_refptr<BackendIO> > OperationList;
+ void QueueOperation(BackendIO* operation);
+ void PostOperation(BackendIO* operation);
+
+ BackendImpl* backend_;
+ scoped_refptr<base::MessageLoopProxy> background_thread_;
+ OperationList pending_ops_; // The list of operations to be posted.
+
+ DISALLOW_COPY_AND_ASSIGN(InFlightBackendIO);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_IN_FLIGHT_BACKEND_IO_H_
diff --git a/net/disk_cache/in_flight_io.cc b/net/disk_cache/in_flight_io.cc
new file mode 100644
index 0000000..6112a9c
--- /dev/null
+++ b/net/disk_cache/in_flight_io.cc
@@ -0,0 +1,73 @@
+// Copyright (c) 2006-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 "net/disk_cache/in_flight_io.h"
+
+#include "base/logging.h"
+
+namespace disk_cache {
+
+// Runs on the primary thread.
+void BackgroundIO::OnIOSignalled() {
+ if (controller_)
+ controller_->InvokeCallback(this, false);
+}
+
+void BackgroundIO::Cancel() {
+ DCHECK(controller_);
+ controller_ = NULL;
+}
+
+// Runs on the background thread.
+void BackgroundIO::NotifyController() {
+ controller_->OnIOComplete(this);
+}
+
+// ---------------------------------------------------------------------------
+
+void InFlightIO::WaitForPendingIO() {
+ while (!io_list_.empty()) {
+ // Block the current thread until all pending IO completes.
+ IOList::iterator it = io_list_.begin();
+ InvokeCallback(*it, true);
+ }
+}
+
+// Runs on a background thread.
+void InFlightIO::OnIOComplete(BackgroundIO* operation) {
+#ifndef NDEBUG
+ if (callback_thread_->BelongsToCurrentThread()) {
+ DCHECK(single_thread_ || !running_);
+ single_thread_ = true;
+ }
+ running_ = true;
+#endif
+
+ callback_thread_->PostTask(FROM_HERE,
+ NewRunnableMethod(operation,
+ &BackgroundIO::OnIOSignalled));
+ operation->io_completed()->Signal();
+}
+
+// Runs on the primary thread.
+void InFlightIO::InvokeCallback(BackgroundIO* operation, bool cancel_task) {
+ operation->io_completed()->Wait();
+
+ if (cancel_task)
+ operation->Cancel();
+
+ // Make sure that we remove the operation from the list before invoking the
+ // callback (so that a subsequent cancel does not invoke the callback again).
+ DCHECK(io_list_.find(operation) != io_list_.end());
+ io_list_.erase(operation);
+ OnOperationComplete(operation, cancel_task);
+}
+
+// Runs on the primary thread.
+void InFlightIO::OnOperationPosted(BackgroundIO* operation) {
+ DCHECK(callback_thread_->BelongsToCurrentThread());
+ io_list_.insert(operation);
+}
+
+} // namespace disk_cache
diff --git a/net/disk_cache/in_flight_io.h b/net/disk_cache/in_flight_io.h
new file mode 100644
index 0000000..4486f44
--- /dev/null
+++ b/net/disk_cache/in_flight_io.h
@@ -0,0 +1,134 @@
+// Copyright (c) 2006-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.
+
+#ifndef NET_DISK_CACHE_IN_FLIGHT_IO_H_
+#define NET_DISK_CACHE_IN_FLIGHT_IO_H_
+
+#include <set>
+
+#include "base/message_loop_proxy.h"
+#include "base/waitable_event.h"
+
+namespace disk_cache {
+
+class InFlightIO;
+
+// This class represents a single asynchronous IO operation while it is being
+// bounced between threads.
+class BackgroundIO : public base::RefCountedThreadSafe<BackgroundIO> {
+ public:
+ // Other than the actual parameters for the IO operation (including the
+ // |callback| that must be notified at the end), we need the controller that
+ // is keeping track of all operations. When done, we notify the controller
+ // (we do NOT invoke the callback), in the worker thead that completed the
+ // operation.
+ explicit BackgroundIO(InFlightIO* controller)
+ : controller_(controller), result_(-1), io_completed_(true, false) {}
+
+ // This method signals the controller that this operation is finished, in the
+ // original thread. In practice, this is a RunableMethod that allows
+ // cancellation.
+ void OnIOSignalled();
+
+ // Allows the cancellation of the task to notify the controller (step number 8
+ // in the diagram below). In practice, if the controller waits for the
+ // operation to finish it doesn't have to wait for the final task to be
+ // processed by the message loop so calling this method prevents its delivery.
+ // Note that this method is not intended to cancel the actual IO operation or
+ // to prevent the first notification to take place (OnIOComplete).
+ void Cancel();
+
+ int result() { return result_; }
+
+ base::WaitableEvent* io_completed() {
+ return &io_completed_;
+ }
+
+ protected:
+ virtual ~BackgroundIO() {}
+
+ InFlightIO* controller_; // The controller that tracks all operations.
+ int result_; // Final operation result.
+
+ private:
+ friend class base::RefCountedThreadSafe<BackgroundIO>;
+
+ // Notifies the controller about the end of the operation, from the background
+ // thread.
+ void NotifyController();
+
+ // An event to signal when the operation completes.
+ base::WaitableEvent io_completed_;
+
+ DISALLOW_COPY_AND_ASSIGN(BackgroundIO);
+};
+
+// This class keeps track of asynchronous IO operations. A single instance
+// of this class is meant to be used to start an asynchronous operation (using
+// PostXX, exposed by a derived class). This class will post the operation to a
+// worker thread, hanlde the notification when the operation finishes and
+// perform the callback on the same thread that was used to start the operation.
+//
+// The regular sequence of calls is:
+// Thread_1 Worker_thread
+// 1. DerivedInFlightIO::PostXX()
+// 2. -> PostTask ->
+// 3. InFlightIO::OnOperationPosted()
+// 4. DerivedBackgroundIO::XX()
+// 5. IO operation completes
+// 6. InFlightIO::OnIOComplete()
+// 7. <- PostTask <-
+// 8. BackgroundIO::OnIOSignalled()
+// 9. InFlightIO::InvokeCallback()
+// 10. DerivedInFlightIO::OnOperationComplete()
+// 11. invoke callback
+//
+// Shutdown is a special case that is handled though WaitForPendingIO() instead
+// of just waiting for step 7.
+class InFlightIO {
+ public:
+ InFlightIO()
+ : callback_thread_(base::MessageLoopProxy::CreateForCurrentThread()),
+ running_(false), single_thread_(false) {}
+ virtual ~InFlightIO() {}
+
+ // Blocks the current thread until all IO operations tracked by this object
+ // complete.
+ void WaitForPendingIO();
+
+ // Called on a background thread when |operation| completes.
+ void OnIOComplete(BackgroundIO* operation);
+
+ // Invokes the users' completion callback at the end of the IO operation.
+ // |cancel_task| is true if the actual task posted to the thread is still
+ // queued (because we are inside WaitForPendingIO), and false if said task is
+ // the one performing the call.
+ void InvokeCallback(BackgroundIO* operation, bool cancel_task);
+
+ protected:
+ // This method is called to signal the completion of the |operation|. |cancel|
+ // is true if the operation is being cancelled. This method is called on the
+ // thread that created this object.
+ virtual void OnOperationComplete(BackgroundIO* operation, bool cancel) = 0;
+
+ // Signals this object that the derived class just posted the |operation| to
+ // be executed on a background thread. This method must be called on the same
+ // thread used to create this object.
+ void OnOperationPosted(BackgroundIO* operation);
+
+ private:
+ typedef std::set<scoped_refptr<BackgroundIO> > IOList;
+
+ IOList io_list_; // List of pending, in-flight io operations.
+ scoped_refptr<base::MessageLoopProxy> callback_thread_;
+
+ bool running_; // True after the first posted operation completes.
+ bool single_thread_; // True if we only have one thread.
+
+ DISALLOW_COPY_AND_ASSIGN(InFlightIO);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_IN_FLIGHT_IO_H_
diff --git a/net/disk_cache/mapped_file_unittest.cc b/net/disk_cache/mapped_file_unittest.cc
index ea28f32..8b01a7d 100644
--- a/net/disk_cache/mapped_file_unittest.cc
+++ b/net/disk_cache/mapped_file_unittest.cc
@@ -19,26 +19,18 @@
// Implementation of FileIOCallback for the tests.
class FileCallbackTest: public disk_cache::FileIOCallback {
public:
- explicit FileCallbackTest(int id) : id_(id), reuse_(0) {}
- explicit FileCallbackTest(int id, bool reuse)
- : id_(id), reuse_(reuse_ ? 0 : 1) {}
+ explicit FileCallbackTest(int id) : id_(id) {}
~FileCallbackTest() {}
virtual void OnFileIOComplete(int bytes_copied);
private:
int id_;
- int reuse_;
};
void FileCallbackTest::OnFileIOComplete(int bytes_copied) {
if (id_ > g_cache_tests_max_id) {
NOTREACHED();
g_cache_tests_error = true;
- } else if (reuse_) {
- DCHECK(1 == reuse_);
- if (2 == reuse_)
- g_cache_tests_error = true;
- reuse_++;
}
g_cache_tests_received++;
diff --git a/net/disk_cache/mem_backend_impl.cc b/net/disk_cache/mem_backend_impl.cc
index 875efdc..fe57d7d 100644
--- a/net/disk_cache/mem_backend_impl.cc
+++ b/net/disk_cache/mem_backend_impl.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2006-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.
@@ -28,7 +28,8 @@
namespace disk_cache {
-Backend* CreateInMemoryCacheBackend(int max_bytes) {
+// Static.
+Backend* MemBackendImpl::CreateBackend(int max_bytes) {
MemBackendImpl* cache = new MemBackendImpl();
cache->SetMaxSize(max_bytes);
if (cache->Init())
@@ -39,8 +40,6 @@
return NULL;
}
-// ------------------------------------------------------------------------
-
bool MemBackendImpl::Init() {
if (max_size_)
return true;
diff --git a/net/disk_cache/mem_backend_impl.h b/net/disk_cache/mem_backend_impl.h
index 48d196c..c5e947f 100644
--- a/net/disk_cache/mem_backend_impl.h
+++ b/net/disk_cache/mem_backend_impl.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -23,30 +23,29 @@
MemBackendImpl() : max_size_(0), current_size_(0) {}
~MemBackendImpl();
+ // Returns an instance of a Backend implemented only in memory. The returned
+ // object should be deleted when not needed anymore. max_bytes is the maximum
+ // size the cache can grow to. If zero is passed in as max_bytes, the cache
+ // will determine the value to use based on the available memory. The returned
+ // pointer can be NULL if a fatal error is found.
+ static Backend* CreateBackend(int max_bytes);
+
// Performs general initialization for this current instance of the cache.
bool Init();
// Backend interface.
virtual int32 GetEntryCount() const;
- virtual bool OpenEntry(const std::string& key, Entry** entry);
virtual int OpenEntry(const std::string& key, Entry** entry,
CompletionCallback* callback);
- virtual bool CreateEntry(const std::string& key, Entry** entry);
virtual int CreateEntry(const std::string& key, Entry** entry,
CompletionCallback* callback);
- virtual bool DoomEntry(const std::string& key);
virtual int DoomEntry(const std::string& key, CompletionCallback* callback);
- virtual bool DoomAllEntries();
virtual int DoomAllEntries(CompletionCallback* callback);
- virtual bool DoomEntriesBetween(const base::Time initial_time,
- const base::Time end_time);
virtual int DoomEntriesBetween(const base::Time initial_time,
const base::Time end_time,
CompletionCallback* callback);
- virtual bool DoomEntriesSince(const base::Time initial_time);
virtual int DoomEntriesSince(const base::Time initial_time,
CompletionCallback* callback);
- virtual bool OpenNextEntry(void** iter, Entry** next_entry);
virtual int OpenNextEntry(void** iter, Entry** next_entry,
CompletionCallback* callback);
virtual void EndEnumeration(void** iter);
@@ -78,6 +77,16 @@
void RemoveFromRankingList(MemEntryImpl* entry);
private:
+ // Old Backend interface.
+ bool OpenEntry(const std::string& key, Entry** entry);
+ bool CreateEntry(const std::string& key, Entry** entry);
+ bool DoomEntry(const std::string& key);
+ bool DoomAllEntries();
+ bool DoomEntriesBetween(const base::Time initial_time,
+ const base::Time end_time);
+ bool DoomEntriesSince(const base::Time initial_time);
+ bool OpenNextEntry(void** iter, Entry** next_entry);
+
// Deletes entries from the cache until the current size is below the limit.
// If empty is true, the whole cache will be trimmed, regardless of being in
// use.
@@ -94,7 +103,7 @@
int32 max_size_; // Maximum data size for this instance.
int32 current_size_;
- DISALLOW_EVIL_CONSTRUCTORS(MemBackendImpl);
+ DISALLOW_COPY_AND_ASSIGN(MemBackendImpl);
};
} // namespace disk_cache
diff --git a/net/disk_cache/mem_entry_impl.cc b/net/disk_cache/mem_entry_impl.cc
index 002b514..645619e 100644
--- a/net/disk_cache/mem_entry_impl.cc
+++ b/net/disk_cache/mem_entry_impl.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2006-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.
@@ -312,6 +312,11 @@
return GetAvailableRange(offset, len, start);
}
+bool MemEntryImpl::CouldBeSparse() const {
+ DCHECK_EQ(kParentEntry, type());
+ return (children_.get() != NULL);
+}
+
int MemEntryImpl::ReadyForSparseIO(
net::CompletionCallback* completion_callback) {
return net::OK;
diff --git a/net/disk_cache/mem_entry_impl.h b/net/disk_cache/mem_entry_impl.h
index 4ff7a0f..66535d5 100644
--- a/net/disk_cache/mem_entry_impl.h
+++ b/net/disk_cache/mem_entry_impl.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2006-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.
@@ -67,9 +67,9 @@
net::CompletionCallback* completion_callback);
virtual int WriteSparseData(int64 offset, net::IOBuffer* buf, int buf_len,
net::CompletionCallback* completion_callback);
- virtual int GetAvailableRange(int64 offset, int len, int64* start);
virtual int GetAvailableRange(int64 offset, int len, int64* start,
CompletionCallback* callback);
+ virtual bool CouldBeSparse() const;
virtual void CancelSparseIO() {}
virtual int ReadyForSparseIO(net::CompletionCallback* completion_callback);
@@ -112,6 +112,9 @@
~MemEntryImpl();
+ // Old Entry interface.
+ int GetAvailableRange(int64 offset, int len, int64* start);
+
// Grows and cleans up the data buffer.
void PrepareTarget(int index, int offset, int buf_len);
@@ -159,7 +162,7 @@
MemBackendImpl* backend_; // Back pointer to the cache.
bool doomed_; // True if this entry was removed from the cache.
- DISALLOW_EVIL_CONSTRUCTORS(MemEntryImpl);
+ DISALLOW_COPY_AND_ASSIGN(MemEntryImpl);
};
} // namespace disk_cache
diff --git a/net/disk_cache/mem_rankings.h b/net/disk_cache/mem_rankings.h
index 680be4c..fa90688 100644
--- a/net/disk_cache/mem_rankings.h
+++ b/net/disk_cache/mem_rankings.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -36,7 +36,7 @@
MemEntryImpl* head_;
MemEntryImpl* tail_;
- DISALLOW_EVIL_CONSTRUCTORS(MemRankings);
+ DISALLOW_COPY_AND_ASSIGN(MemRankings);
};
} // namespace disk_cache
diff --git a/net/disk_cache/rankings.cc b/net/disk_cache/rankings.cc
index d2250df..34c002b 100644
--- a/net/disk_cache/rankings.cc
+++ b/net/disk_cache/rankings.cc
@@ -11,6 +11,7 @@
#include "net/disk_cache/histogram_macros.h"
using base::Time;
+using base::TimeTicks;
// This is used by crash_cache.exe to generate unit test files.
disk_cache::RankCrashes g_rankings_crash = disk_cache::NO_CRASH;
@@ -207,7 +208,7 @@
if (!rankings->address().is_initialized())
return false;
- Time start = Time::Now();
+ TimeTicks start = TimeTicks::Now();
if (!rankings->Load())
return false;
@@ -406,7 +407,7 @@
// but the net effect is just an assert on debug when attempting to remove the
// entry. Otherwise we'll need reentrant transactions, which is an overkill.
void Rankings::UpdateRank(CacheRankingsBlock* node, bool modified, List list) {
- Time start = Time::Now();
+ TimeTicks start = TimeTicks::Now();
Remove(node, list);
Insert(node, modified, list);
CACHE_UMA(AGE_MS, "UpdateRank", 0, start);
diff --git a/net/disk_cache/rankings.h b/net/disk_cache/rankings.h
index c9fc8d2..f635355 100644
--- a/net/disk_cache/rankings.h
+++ b/net/disk_cache/rankings.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -86,7 +86,7 @@
private:
Rankings* rankings_;
- DISALLOW_EVIL_CONSTRUCTORS(ScopedRankingsBlock);
+ DISALLOW_COPY_AND_ASSIGN(ScopedRankingsBlock);
};
// If we have multiple lists, we have to iterate through all at the same time.
diff --git a/net/disk_cache/sparse_control.cc b/net/disk_cache/sparse_control.cc
index 9f0f537..884a1b7 100644
--- a/net/disk_cache/sparse_control.cc
+++ b/net/disk_cache/sparse_control.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-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.
@@ -71,7 +71,7 @@
disk_cache::Bitmap children_map_;
int64 signature_;
scoped_array<char> buffer_;
- DISALLOW_EVIL_CONSTRUCTORS(ChildrenDeleter);
+ DISALLOW_COPY_AND_ASSIGN(ChildrenDeleter);
};
// This is the callback of the file operation.
@@ -126,7 +126,7 @@
return Release();
}
std::string child_name = GenerateChildName(name_, signature_, child_id);
- backend_->DoomEntry(child_name);
+ backend_->SyncDoomEntry(child_name);
children_map_.Set(child_id, false);
// Post a task to delete the next child.
@@ -166,6 +166,16 @@
return rv;
}
+bool SparseControl::CouldBeSparse() const {
+ DCHECK(!init_);
+
+ if (entry_->GetDataSize(kSparseData))
+ return false;
+
+ // We don't verify the data, just see if it could be there.
+ return (entry_->GetDataSize(kSparseIndex) != 0);
+}
+
int SparseControl::StartIO(SparseOperation op, int64 offset, net::IOBuffer* buf,
int buf_len, net::CompletionCallback* callback) {
DCHECK(init_);
@@ -363,9 +373,12 @@
CloseChild();
}
- // Se if we are tracking this child.
- bool child_present = ChildPresent();
- if (!child_present || !entry_->backend_->OpenEntry(key, &child_))
+ // See if we are tracking this child.
+ if (!ChildPresent())
+ return ContinueWithoutChild(key);
+
+ child_ = entry_->backend_->OpenEntryImpl(key);
+ if (!child_)
return ContinueWithoutChild(key);
EntryImpl* child = static_cast<EntryImpl*>(child_);
@@ -388,7 +401,7 @@
if (child_data_.header.last_block_len < 0 ||
child_data_.header.last_block_len > kBlockSize) {
- // Make sure this values are always within range.
+ // Make sure these values are always within range.
child_data_.header.last_block_len = 0;
child_data_.header.last_block = -1;
}
@@ -405,7 +418,7 @@
NULL, false);
if (rv != sizeof(child_data_))
DLOG(ERROR) << "Failed to save child data";
- child_->Close();
+ child_->Release();
child_ = NULL;
}
@@ -417,8 +430,8 @@
// We are deleting the child because something went wrong.
bool SparseControl::KillChildAndContinue(const std::string& key, bool fatal) {
SetChildBit(false);
- child_->Doom();
- child_->Close();
+ child_->DoomImpl();
+ child_->Release();
child_ = NULL;
if (fatal) {
result_ = net::ERR_CACHE_READ_FAILURE;
@@ -434,7 +447,8 @@
if (kGetRangeOperation == operation_)
return true;
- if (!entry_->backend_->CreateEntry(key, &child_)) {
+ child_ = entry_->backend_->CreateEntryImpl(key);
+ if (!child_) {
child_ = NULL;
result_ = net::ERR_CACHE_READ_FAILURE;
return false;
@@ -603,12 +617,12 @@
int rv = 0;
switch (operation_) {
case kReadOperation:
- rv = child_->ReadData(kSparseData, child_offset_, user_buf_, child_len_,
- callback);
+ rv = child_->ReadDataImpl(kSparseData, child_offset_, user_buf_,
+ child_len_, callback);
break;
case kWriteOperation:
- rv = child_->WriteData(kSparseData, child_offset_, user_buf_, child_len_,
- callback, false);
+ rv = child_->WriteDataImpl(kSparseData, child_offset_, user_buf_,
+ child_len_, callback, false);
break;
case kGetRangeOperation:
rv = DoGetAvailableRange();
diff --git a/net/disk_cache/sparse_control.h b/net/disk_cache/sparse_control.h
index 0005f58..b88f258 100644
--- a/net/disk_cache/sparse_control.h
+++ b/net/disk_cache/sparse_control.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-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.
@@ -52,6 +52,10 @@
// on disk and returns net::OK. Otherwise it returns a net error code.
int Init();
+ // Performs a quick test to see if the entry is sparse or not, without
+ // generating disk IO (so the answer provided is only a best effort).
+ bool CouldBeSparse() const;
+
// Performs an actual sparse read or write operation for this entry. |op| is
// the operation to perform, |offset| is the desired sparse offset, |buf| and
// |buf_len| specify the actual data to use and |callback| is the callback
@@ -146,7 +150,7 @@
void DoAbortCallbacks();
EntryImpl* entry_; // The sparse entry.
- Entry* child_; // The current child entry.
+ EntryImpl* child_; // The current child entry.
SparseOperation operation_;
bool pending_; // True if any child IO operation returned pending.
bool finished_;
diff --git a/net/disk_cache/storage_block.h b/net/disk_cache/storage_block.h
index 0d94b82..e77c5bb 100644
--- a/net/disk_cache/storage_block.h
+++ b/net/disk_cache/storage_block.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -82,7 +82,7 @@
bool own_data_; // Is data_ owned by this object or shared with someone else.
bool extended_; // Used to store an entry of more than one block.
- DISALLOW_EVIL_CONSTRUCTORS(StorageBlock);
+ DISALLOW_COPY_AND_ASSIGN(StorageBlock);
};
typedef StorageBlock<EntryStore> CacheEntryBlock;
diff --git a/net/disk_cache/stress_cache.cc b/net/disk_cache/stress_cache.cc
index 67c396c..b153659 100644
--- a/net/disk_cache/stress_cache.cc
+++ b/net/disk_cache/stress_cache.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Copyright (c) 2006-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.
@@ -24,6 +24,8 @@
#include "base/process_util.h"
#include "base/string_util.h"
#include "base/thread.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
#include "net/base/io_buffer.h"
#include "net/disk_cache/backend_impl.h"
#include "net/disk_cache/disk_cache.h"
@@ -77,11 +79,20 @@
void StressTheCache(int iteration) {
int cache_size = 0x800000; // 8MB
FilePath path = GetCacheFilePath().AppendASCII("_stress");
- disk_cache::BackendImpl* cache = new disk_cache::BackendImpl(path);
- cache->SetFlags(disk_cache::kNoLoadProtection | disk_cache::kNoRandom);
- cache->SetMaxSize(cache_size);
- cache->SetType(net::DISK_CACHE);
- if (!cache->Init()) {
+
+ base::Thread cache_thread("CacheThread");
+ if (!cache_thread.StartWithOptions(
+ base::Thread::Options(MessageLoop::TYPE_IO, 0)))
+ return;
+
+ TestCompletionCallback cb;
+ disk_cache::Backend* cache;
+ int rv = disk_cache::BackendImpl::CreateBackend(
+ path, false, cache_size, net::DISK_CACHE,
+ disk_cache::kNoLoadProtection | disk_cache::kNoRandom,
+ cache_thread.message_loop_proxy(), &cache, &cb);
+
+ if (cb.GetResult(rv) != net::OK) {
printf("Unable to initialize cache.\n");
return;
}
@@ -111,20 +122,24 @@
if (entries[slot])
entries[slot]->Close();
- if (!cache->OpenEntry(keys[key], &entries[slot]))
- CHECK(cache->CreateEntry(keys[key], &entries[slot]));
+ rv = cache->OpenEntry(keys[key], &entries[slot], &cb);
+ if (cb.GetResult(rv) != net::OK) {
+ rv = cache->CreateEntry(keys[key], &entries[slot], &cb);
+ CHECK_EQ(net::OK, cb.GetResult(rv));
+ }
base::snprintf(buffer->data(), kSize, "%d %d", iteration, i);
- CHECK(kSize == entries[slot]->WriteData(0, 0, buffer, kSize, NULL, false));
+ rv = entries[slot]->WriteData(0, 0, buffer, kSize, &cb, false);
+ CHECK_EQ(kSize, cb.GetResult(rv));
if (rand() % 100 > 80) {
key = rand() % kNumKeys;
- cache->DoomEntry(keys[key]);
+ rv = cache->DoomEntry(keys[key], &cb);
+ cb.GetResult(rv);
}
if (!(i % 100))
printf("Entries: %d \r", i);
- MessageLoop::current()->RunAllPending();
}
}
diff --git a/net/disk_cache/trace.cc b/net/disk_cache/trace.cc
index 94d9fad..7a61e20 100644
--- a/net/disk_cache/trace.cc
+++ b/net/disk_cache/trace.cc
@@ -70,14 +70,15 @@
}
void DestroyTrace(void) {
- DCHECK(s_trace_buffer);
delete s_trace_buffer;
s_trace_buffer = NULL;
s_trace_object = NULL;
}
void Trace(const char* format, ...) {
- DCHECK(s_trace_buffer);
+ if (!s_trace_buffer)
+ return;
+
va_list ap;
va_start(ap, format);
diff --git a/net/disk_cache/trace.h b/net/disk_cache/trace.h
index be50417..a431890 100644
--- a/net/disk_cache/trace.h
+++ b/net/disk_cache/trace.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -36,7 +36,7 @@
~TraceObject() {
DestroyTrace();
}
- DISALLOW_EVIL_CONSTRUCTORS(TraceObject);
+ DISALLOW_COPY_AND_ASSIGN(TraceObject);
};
// Traces to the internal buffer.
diff --git a/net/fetch_client.target.mk b/net/fetch_client.target.mk
new file mode 100644
index 0000000..114ceb5
--- /dev/null
+++ b/net/fetch_client.target.mk
@@ -0,0 +1,189 @@
+# This file is generated by gyp; do not edit.
+
+TOOLSET := target
+TARGET := fetch_client
+DEFS_Debug := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-DUNIT_TEST' \
+ '-DGTEST_HAS_RTTI=0' \
+ '-D__STDC_FORMAT_MACROS' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=1' \
+ '-D_DEBUG'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Debug := -Werror \
+ -pthread \
+ -fno-exceptions \
+ -Wall \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/atk-1.0 \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -O0 \
+ -g
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Debug :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Debug := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Debug := -I. \
+ -Itesting/gtest/include
+
+DEFS_Release := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-DUNIT_TEST' \
+ '-DGTEST_HAS_RTTI=0' \
+ '-D__STDC_FORMAT_MACROS' \
+ '-DNDEBUG' \
+ '-DNVALGRIND' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=0'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Release := -Werror \
+ -pthread \
+ -fno-exceptions \
+ -Wall \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/atk-1.0 \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -O2 \
+ -fno-ident \
+ -fdata-sections \
+ -ffunction-sections
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Release :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Release := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Release := -I. \
+ -Itesting/gtest/include
+
+OBJS := $(obj).target/$(TARGET)/net/tools/fetch/fetch_client.o
+
+# Add to the list of files we specially track dependencies for.
+all_deps += $(OBJS)
+
+# Make sure our dependencies are built before any of us.
+$(OBJS): | $(obj).target/net/libnet.a $(obj).target/base/libbase.a $(obj).target/testing/libgtest.a $(obj).target/third_party/modp_b64/libmodp_b64.a $(obj).target/base/third_party/dynamic_annotations/libdynamic_annotations.a $(obj).target/base/libsymbolize.a $(obj).target/net/third_party/nss/libssl.a $(obj).target/third_party/zlib/libzlib.a $(obj).target/base/libxdg_mime.a $(obj).target/base/allocator/liballocator.a $(obj).target/third_party/libevent/libevent.a $(obj).target/base/libbase_i18n.a $(obj).target/third_party/icu/libicui18n.a $(obj).target/third_party/icu/libicuuc.a $(obj).target/third_party/icu/libicudata.a $(obj).target/build/temp_gyp/libgoogleurl.a $(obj).target/sdch/libsdch.a $(obj).target/net/libnet_base.a $(obj).target/v8/tools/gyp/libv8_snapshot.a $(obj).target/v8/tools/gyp/libv8_base.a
+
+# CFLAGS et al overrides must be target-local.
+# See "Target-specific Variable Values" in the GNU Make manual.
+$(OBJS): TOOLSET := $(TOOLSET)
+$(OBJS): GYP_CFLAGS := $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_C_$(BUILDTYPE)) $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE))
+$(OBJS): GYP_CXXFLAGS := $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_CC_$(BUILDTYPE)) $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE))
+
+# Suffix rules, putting all outputs into $(obj).
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(srcdir)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+# Try building from generated source, too.
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj).$(TOOLSET)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+# End of this set of suffix rules
+### Rules for final target.
+LDFLAGS_Debug := -pthread \
+ -Wl,-z,noexecstack \
+ -Wl,-uIsHeapProfilerRunning,-uProfilerStart \
+ -Wl,-u_Z21InitialMallocHook_NewPKvj,-u_Z22InitialMallocHook_MMapPKvS0_jiiix,-u_Z22InitialMallocHook_SbrkPKvi \
+ -Wl,-u_Z21InitialMallocHook_NewPKvm,-u_Z22InitialMallocHook_MMapPKvS0_miiil,-u_Z22InitialMallocHook_SbrkPKvl \
+ -rdynamic
+
+LDFLAGS_Release := -pthread \
+ -Wl,-z,noexecstack \
+ -Wl,-uIsHeapProfilerRunning,-uProfilerStart \
+ -Wl,-u_Z21InitialMallocHook_NewPKvj,-u_Z22InitialMallocHook_MMapPKvS0_jiiix,-u_Z22InitialMallocHook_SbrkPKvi \
+ -Wl,-u_Z21InitialMallocHook_NewPKvm,-u_Z22InitialMallocHook_MMapPKvS0_miiil,-u_Z22InitialMallocHook_SbrkPKvl \
+ -Wl,--gc-sections
+
+LIBS := -lrt \
+ -ldl \
+ -lgtk-x11-2.0 \
+ -lgdk-x11-2.0 \
+ -latk-1.0 \
+ -lgio-2.0 \
+ -lpangoft2-1.0 \
+ -lgdk_pixbuf-2.0 \
+ -lm \
+ -lpangocairo-1.0 \
+ -lcairo \
+ -lpango-1.0 \
+ -lfreetype \
+ -lfontconfig \
+ -lgobject-2.0 \
+ -lgmodule-2.0 \
+ -lgthread-2.0 \
+ -lglib-2.0 \
+ -lnss3 \
+ -lnssutil3 \
+ -lsmime3 \
+ -lplds4 \
+ -lplc4 \
+ -lnspr4 \
+ -lpthread \
+ -lz \
+ -lgconf-2
+
+$(builddir)/fetch_client: GYP_LDFLAGS := $(LDFLAGS_$(BUILDTYPE))
+$(builddir)/fetch_client: LIBS := $(LIBS)
+$(builddir)/fetch_client: TOOLSET := $(TOOLSET)
+$(builddir)/fetch_client: $(OBJS) $(obj).target/net/libnet.a $(obj).target/base/libbase.a $(obj).target/testing/libgtest.a $(obj).target/third_party/modp_b64/libmodp_b64.a $(obj).target/base/third_party/dynamic_annotations/libdynamic_annotations.a $(obj).target/base/libsymbolize.a $(obj).target/net/third_party/nss/libssl.a $(obj).target/third_party/zlib/libzlib.a $(obj).target/base/libxdg_mime.a $(obj).target/base/allocator/liballocator.a $(obj).target/third_party/libevent/libevent.a $(obj).target/base/libbase_i18n.a $(obj).target/third_party/icu/libicui18n.a $(obj).target/third_party/icu/libicuuc.a $(obj).target/third_party/icu/libicudata.a $(obj).target/build/temp_gyp/libgoogleurl.a $(obj).target/sdch/libsdch.a $(obj).target/net/libnet_base.a $(obj).target/v8/tools/gyp/libv8_snapshot.a $(obj).target/v8/tools/gyp/libv8_base.a FORCE_DO_CMD
+ $(call do_cmd,link)
+
+all_deps += $(builddir)/fetch_client
+# Add target alias
+.PHONY: fetch_client
+fetch_client: $(builddir)/fetch_client
+
+# Add executable to "all" target.
+.PHONY: all
+all: $(builddir)/fetch_client
+
diff --git a/net/fetch_server.target.mk b/net/fetch_server.target.mk
new file mode 100644
index 0000000..7d176e6
--- /dev/null
+++ b/net/fetch_server.target.mk
@@ -0,0 +1,192 @@
+# This file is generated by gyp; do not edit.
+
+TOOLSET := target
+TARGET := fetch_server
+DEFS_Debug := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-DUNIT_TEST' \
+ '-DGTEST_HAS_RTTI=0' \
+ '-D__STDC_FORMAT_MACROS' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=1' \
+ '-D_DEBUG'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Debug := -Werror \
+ -pthread \
+ -fno-exceptions \
+ -Wall \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/atk-1.0 \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -O0 \
+ -g
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Debug :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Debug := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Debug := -I. \
+ -Itesting/gtest/include
+
+DEFS_Release := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-DUNIT_TEST' \
+ '-DGTEST_HAS_RTTI=0' \
+ '-D__STDC_FORMAT_MACROS' \
+ '-DNDEBUG' \
+ '-DNVALGRIND' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=0'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Release := -Werror \
+ -pthread \
+ -fno-exceptions \
+ -Wall \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/atk-1.0 \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -O2 \
+ -fno-ident \
+ -fdata-sections \
+ -ffunction-sections
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Release :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Release := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Release := -I. \
+ -Itesting/gtest/include
+
+OBJS := $(obj).target/$(TARGET)/net/tools/fetch/fetch_server.o \
+ $(obj).target/$(TARGET)/net/tools/fetch/http_listen_socket.o \
+ $(obj).target/$(TARGET)/net/tools/fetch/http_server.o \
+ $(obj).target/$(TARGET)/net/tools/fetch/http_session.o
+
+# Add to the list of files we specially track dependencies for.
+all_deps += $(OBJS)
+
+# Make sure our dependencies are built before any of us.
+$(OBJS): | $(obj).target/net/libnet.a $(obj).target/base/libbase.a $(obj).target/testing/libgtest.a $(obj).target/third_party/modp_b64/libmodp_b64.a $(obj).target/base/third_party/dynamic_annotations/libdynamic_annotations.a $(obj).target/base/libsymbolize.a $(obj).target/net/third_party/nss/libssl.a $(obj).target/third_party/zlib/libzlib.a $(obj).target/base/libxdg_mime.a $(obj).target/base/allocator/liballocator.a $(obj).target/third_party/libevent/libevent.a $(obj).target/base/libbase_i18n.a $(obj).target/third_party/icu/libicui18n.a $(obj).target/third_party/icu/libicuuc.a $(obj).target/third_party/icu/libicudata.a $(obj).target/build/temp_gyp/libgoogleurl.a $(obj).target/sdch/libsdch.a $(obj).target/net/libnet_base.a $(obj).target/v8/tools/gyp/libv8_snapshot.a $(obj).target/v8/tools/gyp/libv8_base.a
+
+# CFLAGS et al overrides must be target-local.
+# See "Target-specific Variable Values" in the GNU Make manual.
+$(OBJS): TOOLSET := $(TOOLSET)
+$(OBJS): GYP_CFLAGS := $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_C_$(BUILDTYPE)) $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE))
+$(OBJS): GYP_CXXFLAGS := $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_CC_$(BUILDTYPE)) $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE))
+
+# Suffix rules, putting all outputs into $(obj).
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(srcdir)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+# Try building from generated source, too.
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj).$(TOOLSET)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+# End of this set of suffix rules
+### Rules for final target.
+LDFLAGS_Debug := -pthread \
+ -Wl,-z,noexecstack \
+ -Wl,-uIsHeapProfilerRunning,-uProfilerStart \
+ -Wl,-u_Z21InitialMallocHook_NewPKvj,-u_Z22InitialMallocHook_MMapPKvS0_jiiix,-u_Z22InitialMallocHook_SbrkPKvi \
+ -Wl,-u_Z21InitialMallocHook_NewPKvm,-u_Z22InitialMallocHook_MMapPKvS0_miiil,-u_Z22InitialMallocHook_SbrkPKvl \
+ -rdynamic
+
+LDFLAGS_Release := -pthread \
+ -Wl,-z,noexecstack \
+ -Wl,-uIsHeapProfilerRunning,-uProfilerStart \
+ -Wl,-u_Z21InitialMallocHook_NewPKvj,-u_Z22InitialMallocHook_MMapPKvS0_jiiix,-u_Z22InitialMallocHook_SbrkPKvi \
+ -Wl,-u_Z21InitialMallocHook_NewPKvm,-u_Z22InitialMallocHook_MMapPKvS0_miiil,-u_Z22InitialMallocHook_SbrkPKvl \
+ -Wl,--gc-sections
+
+LIBS := -lrt \
+ -ldl \
+ -lgtk-x11-2.0 \
+ -lgdk-x11-2.0 \
+ -latk-1.0 \
+ -lgio-2.0 \
+ -lpangoft2-1.0 \
+ -lgdk_pixbuf-2.0 \
+ -lm \
+ -lpangocairo-1.0 \
+ -lcairo \
+ -lpango-1.0 \
+ -lfreetype \
+ -lfontconfig \
+ -lgobject-2.0 \
+ -lgmodule-2.0 \
+ -lgthread-2.0 \
+ -lglib-2.0 \
+ -lnss3 \
+ -lnssutil3 \
+ -lsmime3 \
+ -lplds4 \
+ -lplc4 \
+ -lnspr4 \
+ -lpthread \
+ -lz \
+ -lgconf-2
+
+$(builddir)/fetch_server: GYP_LDFLAGS := $(LDFLAGS_$(BUILDTYPE))
+$(builddir)/fetch_server: LIBS := $(LIBS)
+$(builddir)/fetch_server: TOOLSET := $(TOOLSET)
+$(builddir)/fetch_server: $(OBJS) $(obj).target/net/libnet.a $(obj).target/base/libbase.a $(obj).target/testing/libgtest.a $(obj).target/third_party/modp_b64/libmodp_b64.a $(obj).target/base/third_party/dynamic_annotations/libdynamic_annotations.a $(obj).target/base/libsymbolize.a $(obj).target/net/third_party/nss/libssl.a $(obj).target/third_party/zlib/libzlib.a $(obj).target/base/libxdg_mime.a $(obj).target/base/allocator/liballocator.a $(obj).target/third_party/libevent/libevent.a $(obj).target/base/libbase_i18n.a $(obj).target/third_party/icu/libicui18n.a $(obj).target/third_party/icu/libicuuc.a $(obj).target/third_party/icu/libicudata.a $(obj).target/build/temp_gyp/libgoogleurl.a $(obj).target/sdch/libsdch.a $(obj).target/net/libnet_base.a $(obj).target/v8/tools/gyp/libv8_snapshot.a $(obj).target/v8/tools/gyp/libv8_base.a FORCE_DO_CMD
+ $(call do_cmd,link)
+
+all_deps += $(builddir)/fetch_server
+# Add target alias
+.PHONY: fetch_server
+fetch_server: $(builddir)/fetch_server
+
+# Add executable to "all" target.
+.PHONY: all
+all: $(builddir)/fetch_server
+
diff --git a/net/ftp/ftp_directory_listing_buffer.cc b/net/ftp/ftp_directory_listing_buffer.cc
index 2376b8f..8399f13 100644
--- a/net/ftp/ftp_directory_listing_buffer.cc
+++ b/net/ftp/ftp_directory_listing_buffer.cc
@@ -4,6 +4,7 @@
#include "net/ftp/ftp_directory_listing_buffer.h"
+#include "base/i18n/icu_encoding_detection.h"
#include "base/i18n/icu_string_conversions.h"
#include "base/stl_util-inl.h"
#include "base/string_util.h"
@@ -13,41 +14,15 @@
#include "net/ftp/ftp_directory_listing_parser_netware.h"
#include "net/ftp/ftp_directory_listing_parser_vms.h"
#include "net/ftp/ftp_directory_listing_parser_windows.h"
-#include "unicode/ucsdet.h"
-
-namespace {
-
-// A very simple-minded character encoding detection.
-// TODO(jungshik): We can apply more heuristics here (e.g. using various hints
-// like TLD, the UI language/default encoding of a client, etc). In that case,
-// this should be pulled out of here and moved somewhere in base because there
-// can be other use cases.
-std::string DetectEncoding(const std::string& text) {
- if (IsStringASCII(text))
- return std::string();
- UErrorCode status = U_ZERO_ERROR;
- UCharsetDetector* detector = ucsdet_open(&status);
- ucsdet_setText(detector, text.data(), static_cast<int32_t>(text.length()),
- &status);
- const UCharsetMatch* match = ucsdet_detect(detector, &status);
- const char* encoding = ucsdet_getName(match, &status);
- ucsdet_close(detector);
- // Should we check the quality of the match? A rather arbitrary number is
- // assigned by ICU and it's hard to come up with a lower limit.
- if (U_FAILURE(status))
- return std::string();
- return encoding;
-}
-
-} // namespace
namespace net {
-FtpDirectoryListingBuffer::FtpDirectoryListingBuffer()
+FtpDirectoryListingBuffer::FtpDirectoryListingBuffer(
+ const base::Time& current_time)
: current_parser_(NULL) {
- parsers_.insert(new FtpDirectoryListingParserLs());
+ parsers_.insert(new FtpDirectoryListingParserLs(current_time));
parsers_.insert(new FtpDirectoryListingParserMlsd());
- parsers_.insert(new FtpDirectoryListingParserNetware());
+ parsers_.insert(new FtpDirectoryListingParserNetware(current_time));
parsers_.insert(new FtpDirectoryListingParserVms());
parsers_.insert(new FtpDirectoryListingParserWindows());
}
@@ -108,8 +83,10 @@
}
int FtpDirectoryListingBuffer::ExtractFullLinesFromBuffer() {
- if (encoding_.empty())
- encoding_ = DetectEncoding(buffer_);
+ if (encoding_.empty()) {
+ if (!base::DetectEncoding(buffer_, &encoding_))
+ return ERR_ENCODING_DETECTION_FAILED;
+ }
int cut_pos = 0;
// TODO(phajdan.jr): This code accepts all endlines matching \r*\n. Should it
@@ -172,7 +149,7 @@
}
if (parsers_.size() != 1) {
- DCHECK(!current_parser_);
+ current_parser_ = NULL;
// We may hit an ambiguity in case of listings which have no entries. That's
// fine, as long as all remaining parsers agree that the listing is empty.
diff --git a/net/ftp/ftp_directory_listing_buffer.h b/net/ftp/ftp_directory_listing_buffer.h
index f71685c..4123cf0 100644
--- a/net/ftp/ftp_directory_listing_buffer.h
+++ b/net/ftp/ftp_directory_listing_buffer.h
@@ -21,8 +21,10 @@
class FtpDirectoryListingBuffer {
public:
- FtpDirectoryListingBuffer();
-
+ // Constructor. When the current time is needed to guess the year on partial
+ // date strings, |current_time| will be used. This allows passing a specific
+ // date during testing.
+ explicit FtpDirectoryListingBuffer(const base::Time& current_time);
~FtpDirectoryListingBuffer();
// Called when data is received from the data socket. Returns network
@@ -43,6 +45,8 @@
// time, although it will return SERVER_UNKNOWN if it doesn't know the answer.
FtpServerType GetServerType() const;
+ const std::string& encoding() const { return encoding_; }
+
private:
typedef std::set<FtpDirectoryListingParser*> ParserSet;
diff --git a/net/ftp/ftp_directory_listing_buffer_unittest.cc b/net/ftp/ftp_directory_listing_buffer_unittest.cc
index cb24489..c3c55d0 100644
--- a/net/ftp/ftp_directory_listing_buffer_unittest.cc
+++ b/net/ftp/ftp_directory_listing_buffer_unittest.cc
@@ -9,6 +9,7 @@
#include "base/path_service.h"
#include "base/string_tokenizer.h"
#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
#include "net/base/net_errors.h"
#include "net/ftp/ftp_directory_listing_parser.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -34,6 +35,7 @@
"dir-listing-ls-14",
"dir-listing-ls-15",
"dir-listing-ls-16",
+ "dir-listing-ls-17",
"dir-listing-mlsd-1",
"dir-listing-mlsd-2",
"dir-listing-netware-1",
@@ -53,10 +55,14 @@
test_dir = test_dir.AppendASCII("data");
test_dir = test_dir.AppendASCII("ftp");
+ base::Time mock_current_time;
+ ASSERT_TRUE(base::Time::FromString(L"Tue, 15 Nov 1994 12:45:26 GMT",
+ &mock_current_time));
+
for (size_t i = 0; i < arraysize(test_files); i++) {
SCOPED_TRACE(StringPrintf("Test[%" PRIuS "]: %s", i, test_files[i]));
- net::FtpDirectoryListingBuffer buffer;
+ net::FtpDirectoryListingBuffer buffer(mock_current_time);
std::string test_listing;
EXPECT_TRUE(file_util::ReadFileToString(test_dir.AppendASCII(test_files[i]),
@@ -84,14 +90,7 @@
SCOPED_TRACE(StringPrintf("Filename: %s", name.c_str()));
- int year;
- if (lines[8 * i + 3] == "current") {
- base::Time::Exploded now_exploded;
- base::Time::Now().LocalExplode(&now_exploded);
- year = now_exploded.year;
- } else {
- year = StringToInt(lines[8 * i + 3]);
- }
+ int year = StringToInt(lines[8 * i + 3]);
int month = StringToInt(lines[8 * i + 4]);
int day_of_month = StringToInt(lines[8 * i + 5]);
int hour = StringToInt(lines[8 * i + 6]);
diff --git a/net/ftp/ftp_directory_listing_parser_ls.cc b/net/ftp/ftp_directory_listing_parser_ls.cc
index 1098cda..55f7844 100644
--- a/net/ftp/ftp_directory_listing_parser_ls.cc
+++ b/net/ftp/ftp_directory_listing_parser_ls.cc
@@ -42,12 +42,13 @@
(text.substr(10).empty() || text.substr(10) == ASCIIToUTF16("+")));
}
-bool DetectColumnOffset(const std::vector<string16>& columns, int* offset) {
+bool DetectColumnOffset(const std::vector<string16>& columns,
+ const base::Time& current_time, int* offset) {
base::Time time;
if (columns.size() >= 8 &&
net::FtpUtil::LsDateListingToTime(columns[5], columns[6], columns[7],
- &time)) {
+ current_time, &time)) {
// Standard listing, exactly like ls -l.
*offset = 2;
return true;
@@ -55,7 +56,7 @@
if (columns.size() >= 7 &&
net::FtpUtil::LsDateListingToTime(columns[4], columns[5], columns[6],
- &time)) {
+ current_time, &time)) {
// wu-ftpd listing, no "number of links" column.
*offset = 1;
return true;
@@ -63,7 +64,7 @@
if (columns.size() >= 6 &&
net::FtpUtil::LsDateListingToTime(columns[3], columns[4], columns[5],
- &time)) {
+ current_time, &time)) {
// Xplain FTP Server listing for folders, like this:
// drwxr-xr-x folder 0 Jul 17 2006 online
*offset = 0;
@@ -78,9 +79,11 @@
namespace net {
-FtpDirectoryListingParserLs::FtpDirectoryListingParserLs()
+FtpDirectoryListingParserLs::FtpDirectoryListingParserLs(
+ const base::Time& current_time)
: received_nonempty_line_(false),
- received_total_line_(false) {
+ received_total_line_(false),
+ current_time_(current_time) {
}
bool FtpDirectoryListingParserLs::ConsumeLine(const string16& line) {
@@ -113,7 +116,7 @@
}
int column_offset;
- if (!DetectColumnOffset(columns, &column_offset))
+ if (!DetectColumnOffset(columns, current_time_, &column_offset))
return false;
// We may receive file names containing spaces, which can make the number of
@@ -144,6 +147,7 @@
if (!FtpUtil::LsDateListingToTime(columns[3 + column_offset],
columns[4 + column_offset],
columns[5 + column_offset],
+ current_time_,
&entry.last_modified)) {
return false;
}
diff --git a/net/ftp/ftp_directory_listing_parser_ls.h b/net/ftp/ftp_directory_listing_parser_ls.h
index 3fd0d32..0d52791 100644
--- a/net/ftp/ftp_directory_listing_parser_ls.h
+++ b/net/ftp/ftp_directory_listing_parser_ls.h
@@ -7,6 +7,7 @@
#include <queue>
+#include "base/time.h"
#include "net/ftp/ftp_directory_listing_parser.h"
namespace net {
@@ -14,7 +15,10 @@
// Parser for "ls -l"-style directory listing.
class FtpDirectoryListingParserLs : public FtpDirectoryListingParser {
public:
- FtpDirectoryListingParserLs();
+ // Constructor. When the current time is needed to guess the year on partial
+ // date strings, |current_time| will be used. This allows passing a specific
+ // date during testing.
+ explicit FtpDirectoryListingParserLs(const base::Time& current_time);
// FtpDirectoryListingParser methods:
virtual FtpServerType GetServerType() const { return SERVER_LS; }
@@ -30,6 +34,9 @@
// integer. Only one such header is allowed per listing.
bool received_total_line_;
+ // Store the current time. We need it to correctly parse received dates.
+ const base::Time current_time_;
+
std::queue<FtpDirectoryListingEntry> entries_;
DISALLOW_COPY_AND_ASSIGN(FtpDirectoryListingParserLs);
diff --git a/net/ftp/ftp_directory_listing_parser_ls_unittest.cc b/net/ftp/ftp_directory_listing_parser_ls_unittest.cc
index ad7edc1..608e0e2 100644
--- a/net/ftp/ftp_directory_listing_parser_ls_unittest.cc
+++ b/net/ftp/ftp_directory_listing_parser_ls_unittest.cc
@@ -5,6 +5,7 @@
#include "net/ftp/ftp_directory_listing_parser_unittest.h"
#include "base/format_macros.h"
+#include "base/string_util.h"
#include "net/ftp/ftp_directory_listing_parser_ls.h"
namespace {
@@ -12,8 +13,9 @@
typedef net::FtpDirectoryListingParserTest FtpDirectoryListingParserLsTest;
TEST_F(FtpDirectoryListingParserLsTest, Good) {
- base::Time::Exploded now_exploded;
- base::Time::Now().LocalExplode(&now_exploded);
+ base::Time mock_current_time;
+ ASSERT_TRUE(base::Time::FromString(L"Tue, 15 Nov 1994 12:45:26 GMT",
+ &mock_current_time));
const struct SingleLineTestData good_cases[] = {
{ "-rw-r--r-- 1 ftp ftp 528 Nov 01 2007 README",
@@ -21,13 +23,13 @@
2007, 11, 1, 0, 0 },
{ "drwxr-xr-x 3 ftp ftp 4096 May 15 18:11 directory",
net::FtpDirectoryListingEntry::DIRECTORY, "directory", -1,
- now_exploded.year, 5, 15, 18, 11 },
+ 1994, 5, 15, 18, 11 },
{ "lrwxrwxrwx 1 0 0 26 Sep 18 2008 pub -> vol/1/.CLUSTER/var_ftp/pub",
net::FtpDirectoryListingEntry::SYMLINK, "pub", -1,
2008, 9, 18, 0, 0 },
{ "lrwxrwxrwx 1 0 0 3 Oct 12 13:37 mirror -> pub",
net::FtpDirectoryListingEntry::SYMLINK, "mirror", -1,
- now_exploded.year, 10, 12, 13, 37 },
+ 1994, 10, 12, 13, 37 },
{ "drwxrwsr-x 4 501 501 4096 Feb 20 2007 pub",
net::FtpDirectoryListingEntry::DIRECTORY, "pub", -1,
2007, 2, 20, 0, 0 },
@@ -36,13 +38,13 @@
2007, 4, 8, 0, 0 },
{ "drwx-wx-wt 2 root wheel 512 Jul 1 02:15 incoming",
net::FtpDirectoryListingEntry::DIRECTORY, "incoming", -1,
- now_exploded.year, 7, 1, 2, 15 },
+ 1994, 7, 1, 2, 15 },
{ "-rw-r--r-- 1 2 3 3447432 May 18 2009 Foo - Manual.pdf",
net::FtpDirectoryListingEntry::FILE, "Foo - Manual.pdf", 3447432,
2009, 5, 18, 0, 0 },
{ "d-wx-wx-wt+ 4 ftp 989 512 Dec 8 15:54 incoming",
net::FtpDirectoryListingEntry::DIRECTORY, "incoming", -1,
- now_exploded.year, 12, 8, 15, 54 },
+ 1993, 12, 8, 15, 54 },
// Tests for the wu-ftpd variant:
{ "drwxr-xr-x 2 sys 512 Mar 27 2009 pub",
@@ -64,7 +66,7 @@
2006, 3, 1, 0, 0 },
{ "dr--r--r-- 1 ftp ----- 0 Nov 17 17:08 kernels",
net::FtpDirectoryListingEntry::DIRECTORY, "kernels", -1,
- now_exploded.year, 11, 17, 17, 8 },
+ 1993, 11, 17, 17, 8 },
// Tests for "ls -l" style listing sent by Xplain FTP Server.
{ "drwxr-xr-x folder 0 Jul 17 2006 online",
@@ -74,12 +76,16 @@
for (size_t i = 0; i < arraysize(good_cases); i++) {
SCOPED_TRACE(StringPrintf("Test[%" PRIuS "]: %s", i, good_cases[i].input));
- net::FtpDirectoryListingParserLs parser;
+ net::FtpDirectoryListingParserLs parser(mock_current_time);
RunSingleLineTestCase(&parser, good_cases[i]);
}
}
TEST_F(FtpDirectoryListingParserLsTest, Bad) {
+ base::Time mock_current_time;
+ ASSERT_TRUE(base::Time::FromString(L"Tue, 15 Nov 1994 12:45:26 GMT",
+ &mock_current_time));
+
const char* bad_cases[] = {
"garbage",
"-rw-r--r-- ftp ftp",
@@ -106,7 +112,7 @@
"drwxr-xr-x folder 0 May 15 18:11",
};
for (size_t i = 0; i < arraysize(bad_cases); i++) {
- net::FtpDirectoryListingParserLs parser;
+ net::FtpDirectoryListingParserLs parser(mock_current_time);
EXPECT_FALSE(parser.ConsumeLine(UTF8ToUTF16(bad_cases[i]))) << bad_cases[i];
}
}
diff --git a/net/ftp/ftp_directory_listing_parser_mlsd.cc b/net/ftp/ftp_directory_listing_parser_mlsd.cc
index 3929bf2..7715dd3 100644
--- a/net/ftp/ftp_directory_listing_parser_mlsd.cc
+++ b/net/ftp/ftp_directory_listing_parser_mlsd.cc
@@ -9,6 +9,7 @@
#include "base/stl_util-inl.h"
#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
// You can read the specification of the MLSD format at
// http://tools.ietf.org/html/rfc3659#page-23.
diff --git a/net/ftp/ftp_directory_listing_parser_mlsd_unittest.cc b/net/ftp/ftp_directory_listing_parser_mlsd_unittest.cc
index 34c2481..9663664 100644
--- a/net/ftp/ftp_directory_listing_parser_mlsd_unittest.cc
+++ b/net/ftp/ftp_directory_listing_parser_mlsd_unittest.cc
@@ -5,6 +5,7 @@
#include "net/ftp/ftp_directory_listing_parser_unittest.h"
#include "base/format_macros.h"
+#include "base/string_util.h"
#include "net/ftp/ftp_directory_listing_parser_mlsd.h"
namespace {
diff --git a/net/ftp/ftp_directory_listing_parser_netware.cc b/net/ftp/ftp_directory_listing_parser_netware.cc
index 20d2b25..60c6c6a 100644
--- a/net/ftp/ftp_directory_listing_parser_netware.cc
+++ b/net/ftp/ftp_directory_listing_parser_netware.cc
@@ -31,8 +31,10 @@
namespace net {
-FtpDirectoryListingParserNetware::FtpDirectoryListingParserNetware()
- : received_first_line_(false) {
+FtpDirectoryListingParserNetware::FtpDirectoryListingParserNetware(
+ const base::Time& current_time)
+ : received_first_line_(false),
+ current_time_(current_time) {
}
bool FtpDirectoryListingParserNetware::ConsumeLine(const string16& line) {
@@ -75,7 +77,7 @@
// Netware uses the same date listing format as Unix "ls -l".
if (!FtpUtil::LsDateListingToTime(columns[4], columns[5], columns[6],
- &entry.last_modified)) {
+ current_time_, &entry.last_modified)) {
return false;
}
diff --git a/net/ftp/ftp_directory_listing_parser_netware.h b/net/ftp/ftp_directory_listing_parser_netware.h
index a3b2cef..aef490a 100644
--- a/net/ftp/ftp_directory_listing_parser_netware.h
+++ b/net/ftp/ftp_directory_listing_parser_netware.h
@@ -7,6 +7,7 @@
#include <queue>
+#include "base/time.h"
#include "net/ftp/ftp_directory_listing_parser.h"
namespace net {
@@ -14,7 +15,10 @@
// Parser for Netware-style directory listing.
class FtpDirectoryListingParserNetware : public FtpDirectoryListingParser {
public:
- FtpDirectoryListingParserNetware();
+ // Constructor. When the current time is needed to guess the year on partial
+ // date strings, |current_time| will be used. This allows passing a specific
+ // date during testing.
+ explicit FtpDirectoryListingParserNetware(const base::Time& current_time);
// FtpDirectoryListingParser methods:
virtual FtpServerType GetServerType() const { return SERVER_NETWARE; }
@@ -27,6 +31,9 @@
// True after we have received the first line of input.
bool received_first_line_;
+ // Store the current time. We need it to correctly parse received dates.
+ const base::Time current_time_;
+
std::queue<FtpDirectoryListingEntry> entries_;
DISALLOW_COPY_AND_ASSIGN(FtpDirectoryListingParserNetware);
diff --git a/net/ftp/ftp_directory_listing_parser_netware_unittest.cc b/net/ftp/ftp_directory_listing_parser_netware_unittest.cc
index 7076a3f..6570846 100644
--- a/net/ftp/ftp_directory_listing_parser_netware_unittest.cc
+++ b/net/ftp/ftp_directory_listing_parser_netware_unittest.cc
@@ -5,6 +5,8 @@
#include "net/ftp/ftp_directory_listing_parser_unittest.h"
#include "base/format_macros.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
#include "net/ftp/ftp_directory_listing_parser_netware.h"
namespace {
@@ -12,8 +14,9 @@
typedef net::FtpDirectoryListingParserTest FtpDirectoryListingParserNetwareTest;
TEST_F(FtpDirectoryListingParserNetwareTest, Good) {
- base::Time::Exploded now_exploded;
- base::Time::Now().LocalExplode(&now_exploded);
+ base::Time mock_current_time;
+ ASSERT_TRUE(base::Time::FromString(L"Tue, 15 Nov 1994 12:45:26 GMT",
+ &mock_current_time));
const struct SingleLineTestData good_cases[] = {
{ "d [RWCEAFMS] ftpadmin 512 Jan 29 2004 pub",
@@ -21,12 +24,12 @@
2004, 1, 29, 0, 0 },
{ "- [RW------] ftpadmin 123 Nov 11 18:25 afile",
net::FtpDirectoryListingEntry::FILE, "afile", 123,
- now_exploded.year, 11, 11, 18, 25 },
+ 1994, 11, 11, 18, 25 },
};
for (size_t i = 0; i < arraysize(good_cases); i++) {
SCOPED_TRACE(StringPrintf("Test[%" PRIuS "]: %s", i, good_cases[i].input));
- net::FtpDirectoryListingParserNetware parser;
+ net::FtpDirectoryListingParserNetware parser(mock_current_time);
// The parser requires a "total n" like before accepting regular input.
ASSERT_TRUE(parser.ConsumeLine(UTF8ToUTF16("total 1")));
RunSingleLineTestCase(&parser, good_cases[i]);
@@ -34,6 +37,10 @@
}
TEST_F(FtpDirectoryListingParserNetwareTest, Bad) {
+ base::Time mock_current_time;
+ ASSERT_TRUE(base::Time::FromString(L"Tue, 15 Nov 1994 12:45:26 GMT",
+ &mock_current_time));
+
const char* bad_cases[] = {
"garbage",
"d [] ftpadmin 512 Jan 29 2004 pub",
@@ -43,7 +50,7 @@
"l [RW------] ftpadmin 512 Jan 29 2004 pub",
};
for (size_t i = 0; i < arraysize(bad_cases); i++) {
- net::FtpDirectoryListingParserNetware parser;
+ net::FtpDirectoryListingParserNetware parser(mock_current_time);
// The parser requires a "total n" like before accepting regular input.
ASSERT_TRUE(parser.ConsumeLine(UTF8ToUTF16("total 1")));
EXPECT_FALSE(parser.ConsumeLine(UTF8ToUTF16(bad_cases[i]))) << bad_cases[i];
diff --git a/net/ftp/ftp_directory_listing_parser_unittest.h b/net/ftp/ftp_directory_listing_parser_unittest.h
index 7653d21..90670fd 100644
--- a/net/ftp/ftp_directory_listing_parser_unittest.h
+++ b/net/ftp/ftp_directory_listing_parser_unittest.h
@@ -5,7 +5,7 @@
#ifndef NET_FTP_FTP_DIRECTORY_LISTING_PARSER_UNITTEST_H_
#define NET_FTP_FTP_DIRECTORY_LISTING_PARSER_UNITTEST_H_
-#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
#include "net/ftp/ftp_directory_listing_parser.h"
#include "testing/gtest/include/gtest/gtest.h"
diff --git a/net/ftp/ftp_directory_listing_parser_vms.cc b/net/ftp/ftp_directory_listing_parser_vms.cc
index bfb14d4..f8d3fbb 100644
--- a/net/ftp/ftp_directory_listing_parser_vms.cc
+++ b/net/ftp/ftp_directory_listing_parser_vms.cc
@@ -7,6 +7,7 @@
#include <vector>
#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
#include "net/ftp/ftp_util.h"
namespace {
diff --git a/net/ftp/ftp_directory_listing_parser_vms_unittest.cc b/net/ftp/ftp_directory_listing_parser_vms_unittest.cc
index 723c2ac..363c99c 100644
--- a/net/ftp/ftp_directory_listing_parser_vms_unittest.cc
+++ b/net/ftp/ftp_directory_listing_parser_vms_unittest.cc
@@ -5,6 +5,7 @@
#include "net/ftp/ftp_directory_listing_parser_unittest.h"
#include "base/format_macros.h"
+#include "base/string_util.h"
#include "net/ftp/ftp_directory_listing_parser_vms.h"
namespace {
diff --git a/net/ftp/ftp_directory_listing_parser_windows_unittest.cc b/net/ftp/ftp_directory_listing_parser_windows_unittest.cc
index bbab699..a768d05 100644
--- a/net/ftp/ftp_directory_listing_parser_windows_unittest.cc
+++ b/net/ftp/ftp_directory_listing_parser_windows_unittest.cc
@@ -5,6 +5,7 @@
#include "net/ftp/ftp_directory_listing_parser_unittest.h"
#include "base/format_macros.h"
+#include "base/string_util.h"
#include "net/ftp/ftp_directory_listing_parser_windows.h"
namespace {
diff --git a/net/ftp/ftp_network_transaction.cc b/net/ftp/ftp_network_transaction.cc
index 06c9c25..f6b7203 100644
--- a/net/ftp/ftp_network_transaction.cc
+++ b/net/ftp/ftp_network_transaction.cc
@@ -1,16 +1,17 @@
-// Copyright (c) 2008 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.
+// 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 "net/ftp/ftp_network_transaction.h"
#include "base/compiler_specific.h"
#include "base/histogram.h"
#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
#include "net/base/connection_type_histograms.h"
#include "net/base/escape.h"
-#include "net/base/load_log.h"
#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
#include "net/base/net_util.h"
#include "net/ftp/ftp_network_session.h"
#include "net/ftp/ftp_request_info.h"
@@ -42,6 +43,134 @@
return true;
}
+enum ErrorClass {
+ // The requested action was initiated. The client should expect another
+ // reply before issuing the next command.
+ ERROR_CLASS_INITIATED,
+
+ // The requested action has been successfully completed.
+ ERROR_CLASS_OK,
+
+ // The command has been accepted, but to complete the operation, more
+ // information must be sent by the client.
+ ERROR_CLASS_INFO_NEEDED,
+
+ // The command was not accepted and the requested action did not take place.
+ // This condition is temporary, and the client is encouraged to restart the
+ // command sequence.
+ ERROR_CLASS_TRANSIENT_ERROR,
+
+ // The command was not accepted and the requested action did not take place.
+ // This condition is rather permanent, and the client is discouraged from
+ // repeating the exact request.
+ ERROR_CLASS_PERMANENT_ERROR,
+};
+
+// Returns the error class for given response code. Caller should ensure
+// that |response_code| is in range 100-599.
+ErrorClass GetErrorClass(int response_code) {
+ if (response_code >= 100 && response_code <= 199)
+ return ERROR_CLASS_INITIATED;
+
+ if (response_code >= 200 && response_code <= 299)
+ return ERROR_CLASS_OK;
+
+ if (response_code >= 300 && response_code <= 399)
+ return ERROR_CLASS_INFO_NEEDED;
+
+ if (response_code >= 400 && response_code <= 499)
+ return ERROR_CLASS_TRANSIENT_ERROR;
+
+ if (response_code >= 500 && response_code <= 599)
+ return ERROR_CLASS_PERMANENT_ERROR;
+
+ // We should not be called on invalid error codes.
+ NOTREACHED() << response_code;
+ return ERROR_CLASS_PERMANENT_ERROR;
+}
+
+// Returns network error code for received FTP |response_code|.
+int GetNetErrorCodeForFtpResponseCode(int response_code) {
+ switch (response_code) {
+ case 421:
+ return net::ERR_FTP_SERVICE_UNAVAILABLE;
+ case 426:
+ return net::ERR_FTP_TRANSFER_ABORTED;
+ case 450:
+ return net::ERR_FTP_FILE_BUSY;
+ case 500:
+ case 501:
+ return net::ERR_FTP_SYNTAX_ERROR;
+ case 502:
+ case 504:
+ return net::ERR_FTP_COMMAND_NOT_SUPPORTED;
+ case 503:
+ return net::ERR_FTP_BAD_COMMAND_SEQUENCE;
+ default:
+ return net::ERR_FTP_FAILED;
+ }
+}
+
+// From RFC 2428 Section 3:
+// The text returned in response to the EPSV command MUST be:
+// <some text> (<d><d><d><tcp-port><d>)
+// <d> is a delimiter character, ideally to be |
+bool ExtractPortFromEPSVResponse(const net::FtpCtrlResponse& response,
+ int* port) {
+ if (response.lines.size() != 1)
+ return false;
+ const char* ptr = response.lines[0].c_str();
+ while (*ptr && *ptr != '(')
+ ++ptr;
+ if (!*ptr)
+ return false;
+ char sep = *(++ptr);
+ if (!sep || isdigit(sep) || *(++ptr) != sep || *(++ptr) != sep)
+ return false;
+ if (!isdigit(*(++ptr)))
+ return false;
+ *port = *ptr - '0';
+ while (isdigit(*(++ptr))) {
+ *port *= 10;
+ *port += *ptr - '0';
+ }
+ if (*ptr != sep)
+ return false;
+
+ return true;
+}
+
+// There are two way we can receive IP address and port.
+// (127,0,0,1,23,21) IP address and port encapsulated in ().
+// 127,0,0,1,23,21 IP address and port without ().
+//
+// See RFC 959, Section 4.1.2
+bool ExtractPortFromPASVResponse(const net::FtpCtrlResponse& response,
+ int* port) {
+ if (response.lines.size() != 1)
+ return false;
+ const char* ptr = response.lines[0].c_str();
+ while (*ptr && *ptr != '(') // Try with bracket.
+ ++ptr;
+ if (*ptr) {
+ ++ptr;
+ } else {
+ ptr = response.lines[0].c_str(); // Try without bracket.
+ while (*ptr && *ptr != ',')
+ ++ptr;
+ while (*ptr && *ptr != ' ')
+ --ptr;
+ }
+ int i0, i1, i2, i3, p0, p1;
+ if (sscanf_s(ptr, "%d,%d,%d,%d,%d,%d", &i0, &i1, &i2, &i3, &p0, &p1) != 6)
+ return false;
+
+ // Ignore the IP address supplied in the response. We are always going
+ // to connect back to the same server to prevent FTP PASV port scanning.
+ *port = (p0 << 8) + p1;
+ return true;
+}
+
} // namespace
namespace net {
@@ -59,10 +188,13 @@
read_ctrl_buf_(new IOBuffer(kCtrlBufLen)),
ctrl_response_buffer_(new FtpCtrlResponseBuffer()),
read_data_buf_len_(0),
- file_data_len_(0),
last_error_(OK),
system_type_(SYSTEM_TYPE_UNKNOWN),
- retr_failed_(false),
+ // Use image (binary) transfer by default. It should always work,
+ // whereas the ascii transfer may damage binary data.
+ data_type_(DATA_TYPE_IMAGE),
+ resource_type_(RESOURCE_TYPE_UNKNOWN),
+ use_epsv_(true),
data_connection_port_(0),
socket_factory_(socket_factory),
next_state_(STATE_NONE) {
@@ -73,8 +205,8 @@
int FtpNetworkTransaction::Start(const FtpRequestInfo* request_info,
CompletionCallback* callback,
- LoadLog* load_log) {
- load_log_ = load_log;
+ const BoundNetLog& net_log) {
+ net_log_ = net_log;
request_ = request_info;
if (request_->url.has_username()) {
@@ -84,7 +216,9 @@
password_ = L"chrome@example.com";
}
- next_state_ = STATE_CTRL_INIT;
+ DetectTypecode();
+
+ next_state_ = STATE_CTRL_RESOLVE_HOST;
int rv = DoLoop(OK);
if (rv == ERR_IO_PENDING)
user_callback_ = callback;
@@ -108,7 +242,7 @@
username_ = username;
password_ = password;
- next_state_ = STATE_CTRL_INIT;
+ next_state_ = STATE_CTRL_RESOLVE_HOST;
int rv = DoLoop(OK);
if (rv == ERR_IO_PENDING)
user_callback_ = callback;
@@ -117,7 +251,7 @@
int FtpNetworkTransaction::RestartIgnoringLastError(
CompletionCallback* callback) {
- return ERR_FAILED;
+ return ERR_NOT_IMPLEMENTED;
}
int FtpNetworkTransaction::Read(IOBuffer* buf,
@@ -197,29 +331,6 @@
return OK;
}
-// static
-FtpNetworkTransaction::ErrorClass FtpNetworkTransaction::GetErrorClass(
- int response_code) {
- if (response_code >= 100 && response_code <= 199)
- return ERROR_CLASS_INITIATED;
-
- if (response_code >= 200 && response_code <= 299)
- return ERROR_CLASS_OK;
-
- if (response_code >= 300 && response_code <= 399)
- return ERROR_CLASS_INFO_NEEDED;
-
- if (response_code >= 400 && response_code <= 499)
- return ERROR_CLASS_TRANSIENT_ERROR;
-
- if (response_code >= 500 && response_code <= 599)
- return ERROR_CLASS_PERMANENT_ERROR;
-
- // We should not be called on invalid error codes.
- NOTREACHED();
- return ERROR_CLASS_PERMANENT_ERROR;
-}
-
int FtpNetworkTransaction::ProcessCtrlResponse() {
FtpCtrlResponse response = ctrl_response_buffer_->PopResponse();
@@ -235,9 +346,6 @@
case COMMAND_PASS:
rv = ProcessResponsePASS(response);
break;
- case COMMAND_ACCT:
- rv = ProcessResponseACCT(response);
- break;
case COMMAND_SYST:
rv = ProcessResponseSYST(response);
break;
@@ -247,6 +355,9 @@
case COMMAND_TYPE:
rv = ProcessResponseTYPE(response);
break;
+ case COMMAND_EPSV:
+ rv = ProcessResponseEPSV(response);
+ break;
case COMMAND_PASV:
rv = ProcessResponsePASV(response);
break;
@@ -265,9 +376,6 @@
case COMMAND_LIST:
rv = ProcessResponseLIST(response);
break;
- case COMMAND_MDTM:
- rv = ProcessResponseMDTM(response);
- break;
case COMMAND_QUIT:
rv = ProcessResponseQUIT(response);
break;
@@ -308,11 +416,9 @@
ctrl_response_buffer_.reset(new FtpCtrlResponseBuffer());
read_data_buf_ = NULL;
read_data_buf_len_ = 0;
- file_data_len_ = 0;
if (write_buf_)
write_buf_->SetOffset(0);
last_error_ = OK;
- retr_failed_ = false;
data_connection_port_ = 0;
ctrl_socket_.reset();
data_socket_.reset();
@@ -338,8 +444,16 @@
std::string FtpNetworkTransaction::GetRequestPathForFtpCommand(
bool is_directory) const {
std::string path(current_remote_directory_);
- if (request_->url.has_path())
- path.append(request_->url.path());
+ if (request_->url.has_path()) {
+ std::string gurl_path(request_->url.path());
+
+ // Get rid of the typecode, see RFC 1738 section 3.2.2. FTP url-path.
+ std::string::size_type pos = gurl_path.rfind(';');
+ if (pos != std::string::npos)
+ gurl_path.resize(pos);
+
+ path.append(gurl_path);
+ }
// Make sure that if the path is expected to be a file, it won't end
// with a trailing slash.
if (!is_directory && path.length() > 1 && path[path.length() - 1] == '/')
@@ -361,6 +475,27 @@
return path;
}
+void FtpNetworkTransaction::DetectTypecode() {
+ if (!request_->url.has_path())
+ return;
+ std::string gurl_path(request_->url.path());
+
+ // Extract the typecode, see RFC 1738 section 3.2.2. FTP url-path.
+ std::string::size_type pos = gurl_path.rfind(';');
+ if (pos == std::string::npos)
+ return;
+ std::string typecode_string(gurl_path.substr(pos));
+ if (typecode_string == ";type=a") {
+ data_type_ = DATA_TYPE_ASCII;
+ resource_type_ = RESOURCE_TYPE_FILE;
+ } else if (typecode_string == ";type=i") {
+ data_type_ = DATA_TYPE_IMAGE;
+ resource_type_ = RESOURCE_TYPE_FILE;
+ } else if (typecode_string == ";type=d") {
+ resource_type_ = RESOURCE_TYPE_DIRECTORY;
+ }
+}
+
int FtpNetworkTransaction::DoLoop(int result) {
DCHECK(next_state_ != STATE_NONE);
@@ -369,13 +504,6 @@
State state = next_state_;
next_state_ = STATE_NONE;
switch (state) {
- case STATE_CTRL_INIT:
- DCHECK(rv == OK);
- rv = DoCtrlInit();
- break;
- case STATE_CTRL_INIT_COMPLETE:
- rv = DoCtrlInitComplete(rv);
- break;
case STATE_CTRL_RESOLVE_HOST:
DCHECK(rv == OK);
rv = DoCtrlResolveHost();
@@ -416,10 +544,6 @@
DCHECK(rv == OK);
rv = DoCtrlWriteSYST();
break;
- case STATE_CTRL_WRITE_ACCT:
- DCHECK(rv == OK);
- rv = DoCtrlWriteACCT();
- break;
case STATE_CTRL_WRITE_PWD:
DCHECK(rv == OK);
rv = DoCtrlWritePWD();
@@ -428,6 +552,10 @@
DCHECK(rv == OK);
rv = DoCtrlWriteTYPE();
break;
+ case STATE_CTRL_WRITE_EPSV:
+ DCHECK(rv == OK);
+ rv = DoCtrlWriteEPSV();
+ break;
case STATE_CTRL_WRITE_PASV:
DCHECK(rv == OK);
rv = DoCtrlWritePASV();
@@ -452,15 +580,10 @@
DCHECK(rv == OK);
rv = DoCtrlWriteLIST();
break;
- case STATE_CTRL_WRITE_MDTM:
- DCHECK(rv == OK);
- rv = DoCtrlWriteMDTM();
- break;
case STATE_CTRL_WRITE_QUIT:
DCHECK(rv == OK);
rv = DoCtrlWriteQUIT();
break;
-
case STATE_DATA_CONNECT:
DCHECK(rv == OK);
rv = DoDataConnect();
@@ -484,29 +607,18 @@
return rv;
}
-// TODO(ibrar): Yet to see if we need any intialization
-int FtpNetworkTransaction::DoCtrlInit() {
- next_state_ = STATE_CTRL_INIT_COMPLETE;
- return OK;
-}
-
-int FtpNetworkTransaction::DoCtrlInitComplete(int result) {
- next_state_ = STATE_CTRL_RESOLVE_HOST;
- return OK;
-}
-
int FtpNetworkTransaction::DoCtrlResolveHost() {
next_state_ = STATE_CTRL_RESOLVE_HOST_COMPLETE;
std::string host;
int port;
- host = request_->url.host();
+ host = request_->url.HostNoBrackets();
port = request_->url.EffectiveIntPort();
HostResolver::RequestInfo info(host, port);
// No known referrer.
- return resolver_.Resolve(info, &addresses_, &io_callback_, load_log_);
+ return resolver_.Resolve(info, &addresses_, &io_callback_, net_log_);
}
int FtpNetworkTransaction::DoCtrlResolveHostComplete(int result) {
@@ -517,8 +629,9 @@
int FtpNetworkTransaction::DoCtrlConnect() {
next_state_ = STATE_CTRL_CONNECT_COMPLETE;
- ctrl_socket_.reset(socket_factory_->CreateTCPClientSocket(addresses_));
- return ctrl_socket_->Connect(&io_callback_, load_log_);
+ ctrl_socket_.reset(socket_factory_->CreateTCPClientSocket(
+ addresses_, net_log_.net_log()));
+ return ctrl_socket_->Connect(&io_callback_);
}
int FtpNetworkTransaction::DoCtrlConnectComplete(int result) {
@@ -606,11 +719,10 @@
next_state_ = STATE_CTRL_WRITE_PASS;
break;
case ERROR_CLASS_TRANSIENT_ERROR:
- if (response.status_code == 421)
- return Stop(ERR_FAILED);
- break;
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
case ERROR_CLASS_PERMANENT_ERROR:
- return Stop(ERR_FAILED);
+ response_.needs_auth = true;
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
default:
NOTREACHED();
return Stop(ERR_UNEXPECTED);
@@ -636,21 +748,12 @@
next_state_ = STATE_CTRL_WRITE_SYST;
break;
case ERROR_CLASS_INFO_NEEDED:
- next_state_ = STATE_CTRL_WRITE_ACCT;
- break;
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
case ERROR_CLASS_TRANSIENT_ERROR:
- if (response.status_code == 421) {
- // TODO(ibrar): Retry here.
- }
- return Stop(ERR_FAILED);
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
case ERROR_CLASS_PERMANENT_ERROR:
- if (response.status_code == 503) {
- next_state_ = STATE_CTRL_WRITE_USER;
- } else {
- response_.needs_auth = true;
- return Stop(ERR_FAILED);
- }
- break;
+ response_.needs_auth = true;
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
default:
NOTREACHED();
return Stop(ERR_UNEXPECTED);
@@ -699,7 +802,7 @@
case ERROR_CLASS_INFO_NEEDED:
return Stop(ERR_INVALID_RESPONSE);
case ERROR_CLASS_TRANSIENT_ERROR:
- return Stop(ERR_FAILED);
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
case ERROR_CLASS_PERMANENT_ERROR:
// Server does not recognize the SYST command so proceed.
next_state_ = STATE_CTRL_WRITE_PWD;
@@ -737,7 +840,7 @@
}
if (system_type_ == SYSTEM_TYPE_VMS)
line = FtpUtil::VMSPathToUnix(line);
- if (line[line.length() - 1] == '/')
+ if (line.length() && line[line.length() - 1] == '/')
line.erase(line.length() - 1);
current_remote_directory_ = line;
next_state_ = STATE_CTRL_WRITE_TYPE;
@@ -746,9 +849,9 @@
case ERROR_CLASS_INFO_NEEDED:
return Stop(ERR_INVALID_RESPONSE);
case ERROR_CLASS_TRANSIENT_ERROR:
- return Stop(ERR_FAILED);
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
case ERROR_CLASS_PERMANENT_ERROR:
- return Stop(ERR_FAILED);
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
default:
NOTREACHED();
return Stop(ERR_UNEXPECTED);
@@ -758,7 +861,15 @@
// TYPE command.
int FtpNetworkTransaction::DoCtrlWriteTYPE() {
- std::string command = "TYPE I";
+ std::string command = "TYPE ";
+ if (data_type_ == DATA_TYPE_ASCII) {
+ command += "A";
+ } else if (data_type_ == DATA_TYPE_IMAGE) {
+ command += "I";
+ } else {
+ NOTREACHED();
+ return Stop(ERR_UNEXPECTED);
+ }
next_state_ = STATE_CTRL_READ;
return SendFtpCommand(command, COMMAND_TYPE);
}
@@ -769,14 +880,14 @@
case ERROR_CLASS_INITIATED:
return Stop(ERR_INVALID_RESPONSE);
case ERROR_CLASS_OK:
- next_state_ = STATE_CTRL_WRITE_PASV;
+ next_state_ = use_epsv_ ? STATE_CTRL_WRITE_EPSV : STATE_CTRL_WRITE_PASV;
break;
case ERROR_CLASS_INFO_NEEDED:
return Stop(ERR_INVALID_RESPONSE);
case ERROR_CLASS_TRANSIENT_ERROR:
- return Stop(ERR_FAILED);
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
case ERROR_CLASS_PERMANENT_ERROR:
- return Stop(ERR_FAILED);
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
default:
NOTREACHED();
return Stop(ERR_UNEXPECTED);
@@ -784,27 +895,33 @@
return OK;
}
-// ACCT command.
-int FtpNetworkTransaction::DoCtrlWriteACCT() {
- std::string command = "ACCT noaccount";
+// EPSV command
+int FtpNetworkTransaction::DoCtrlWriteEPSV() {
+ const std::string command = "EPSV";
next_state_ = STATE_CTRL_READ;
- return SendFtpCommand(command, COMMAND_ACCT);
+ return SendFtpCommand(command, COMMAND_EPSV);
}
-int FtpNetworkTransaction::ProcessResponseACCT(
+int FtpNetworkTransaction::ProcessResponseEPSV(
const FtpCtrlResponse& response) {
switch (GetErrorClass(response.status_code)) {
case ERROR_CLASS_INITIATED:
return Stop(ERR_INVALID_RESPONSE);
case ERROR_CLASS_OK:
- next_state_ = STATE_CTRL_WRITE_SYST;
+ if (!ExtractPortFromEPSVResponse( response, &data_connection_port_))
+ return Stop(ERR_INVALID_RESPONSE);
+ if (data_connection_port_ < 1024 ||
+ !IsPortAllowedByFtp(data_connection_port_))
+ return Stop(ERR_UNSAFE_PORT);
+ next_state_ = STATE_DATA_CONNECT;
break;
case ERROR_CLASS_INFO_NEEDED:
return Stop(ERR_INVALID_RESPONSE);
case ERROR_CLASS_TRANSIENT_ERROR:
- return Stop(ERR_FAILED);
case ERROR_CLASS_PERMANENT_ERROR:
- return Stop(ERR_FAILED);
+ use_epsv_ = false;
+ next_state_ = STATE_CTRL_WRITE_PASV;
+ return OK;
default:
NOTREACHED();
return Stop(ERR_UNEXPECTED);
@@ -819,54 +936,25 @@
return SendFtpCommand(command, COMMAND_PASV);
}
-// There are two way we can receive IP address and port.
-// TODO(phajdan.jr): Figure out how this should work for IPv6.
-// (127,0,0,1,23,21) IP address and port encapsulated in ().
-// 127,0,0,1,23,21 IP address and port without ().
int FtpNetworkTransaction::ProcessResponsePASV(
const FtpCtrlResponse& response) {
switch (GetErrorClass(response.status_code)) {
case ERROR_CLASS_INITIATED:
return Stop(ERR_INVALID_RESPONSE);
case ERROR_CLASS_OK:
- const char* ptr;
- int i0, i1, i2, i3, p0, p1;
- if (response.lines.size() != 1)
+ if (!ExtractPortFromPASVResponse(response, &data_connection_port_))
return Stop(ERR_INVALID_RESPONSE);
- ptr = response.lines[0].c_str(); // Try with bracket.
- while (*ptr && *ptr != '(')
- ++ptr;
- if (*ptr) {
- ++ptr;
- } else {
- ptr = response.lines[0].c_str(); // Try without bracket.
- while (*ptr && *ptr != ',')
- ++ptr;
- while (*ptr && *ptr != ' ')
- --ptr;
- }
- if (sscanf_s(ptr, "%d,%d,%d,%d,%d,%d",
- &i0, &i1, &i2, &i3, &p0, &p1) == 6) {
- // Ignore the IP address supplied in the response. We are always going
- // to connect back to the same server to prevent FTP PASV port scanning.
-
- data_connection_port_ = (p0 << 8) + p1;
-
- if (data_connection_port_ < 1024 ||
- !IsPortAllowedByFtp(data_connection_port_))
- return Stop(ERR_UNSAFE_PORT);
-
- next_state_ = STATE_DATA_CONNECT;
- } else {
- return Stop(ERR_INVALID_RESPONSE);
- }
+ if (data_connection_port_ < 1024 ||
+ !IsPortAllowedByFtp(data_connection_port_))
+ return Stop(ERR_UNSAFE_PORT);
+ next_state_ = STATE_DATA_CONNECT;
break;
case ERROR_CLASS_INFO_NEEDED:
return Stop(ERR_INVALID_RESPONSE);
case ERROR_CLASS_TRANSIENT_ERROR:
- return Stop(ERR_FAILED);
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
case ERROR_CLASS_PERMANENT_ERROR:
- return Stop(ERR_FAILED);
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
default:
NOTREACHED();
return Stop(ERR_UNEXPECTED);
@@ -889,22 +977,36 @@
case ERROR_CLASS_OK:
if (response.lines.size() != 1)
return Stop(ERR_INVALID_RESPONSE);
- if (!StringToInt(response.lines[0], &file_data_len_))
+ int64 size;
+ if (!StringToInt64(response.lines[0], &size))
return Stop(ERR_INVALID_RESPONSE);
- if (file_data_len_ < 0)
+ if (size < 0)
return Stop(ERR_INVALID_RESPONSE);
+ response_.expected_content_size = size;
break;
case ERROR_CLASS_INFO_NEEDED:
break;
case ERROR_CLASS_TRANSIENT_ERROR:
break;
case ERROR_CLASS_PERMANENT_ERROR:
+ // It's possible that SIZE failed because the path is a directory.
+ if (response.status_code == 550 &&
+ resource_type_ == RESOURCE_TYPE_UNKNOWN) {
+ resource_type_ = RESOURCE_TYPE_DIRECTORY;
+ } else if (resource_type_ != RESOURCE_TYPE_DIRECTORY) {
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ }
break;
default:
NOTREACHED();
return Stop(ERR_UNEXPECTED);
}
- next_state_ = STATE_CTRL_WRITE_MDTM;
+
+ if (resource_type_ == RESOURCE_TYPE_DIRECTORY)
+ next_state_ = STATE_CTRL_WRITE_CWD;
+ else
+ next_state_ = STATE_CTRL_WRITE_RETR;
+
return OK;
}
@@ -928,27 +1030,22 @@
next_state_ = STATE_CTRL_WRITE_QUIT;
break;
case ERROR_CLASS_INFO_NEEDED:
- next_state_ = STATE_CTRL_WRITE_PASV;
- break;
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
case ERROR_CLASS_TRANSIENT_ERROR:
- if (response.status_code == 421 || response.status_code == 425 ||
- response.status_code == 426)
- return Stop(ERR_FAILED);
- return ERR_FAILED; // TODO(ibrar): Retry here.
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
case ERROR_CLASS_PERMANENT_ERROR:
// Code 550 means "Failed to open file". Other codes are unrelated,
// like "Not logged in" etc.
if (response.status_code != 550)
- return Stop(ERR_FAILED);
-
- DCHECK(!retr_failed_); // Should not get here twice.
- retr_failed_ = true;
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
// It's possible that RETR failed because the path is a directory.
+ resource_type_ = RESOURCE_TYPE_DIRECTORY;
+
// We're going to try CWD next, but first send a PASV one more time,
// because some FTP servers, including FileZilla, require that.
// See http://crbug.com/25316.
- next_state_ = STATE_CTRL_WRITE_PASV;
+ next_state_ = use_epsv_ ? STATE_CTRL_WRITE_EPSV : STATE_CTRL_WRITE_PASV;
break;
default:
NOTREACHED();
@@ -957,36 +1054,6 @@
return OK;
}
-// MDMT command
-int FtpNetworkTransaction::DoCtrlWriteMDTM() {
- std::string command = "MDTM " + GetRequestPathForFtpCommand(false);
- next_state_ = STATE_CTRL_READ;
- return SendFtpCommand(command, COMMAND_MDTM);
-}
-
-int FtpNetworkTransaction::ProcessResponseMDTM(
- const FtpCtrlResponse& response) {
- switch (GetErrorClass(response.status_code)) {
- case ERROR_CLASS_INITIATED:
- return Stop(ERR_FAILED);
- case ERROR_CLASS_OK:
- next_state_ = STATE_CTRL_WRITE_RETR;
- break;
- case ERROR_CLASS_INFO_NEEDED:
- return Stop(ERR_FAILED);
- case ERROR_CLASS_TRANSIENT_ERROR:
- return Stop(ERR_FAILED);
- case ERROR_CLASS_PERMANENT_ERROR:
- next_state_ = STATE_CTRL_WRITE_RETR;
- break;
- default:
- NOTREACHED();
- return Stop(ERR_UNEXPECTED);
- }
- return OK;
-}
-
-
// CWD command
int FtpNetworkTransaction::DoCtrlWriteCWD() {
std::string command = "CWD " + GetRequestPathForFtpCommand(true);
@@ -1004,16 +1071,16 @@
case ERROR_CLASS_INFO_NEEDED:
return Stop(ERR_INVALID_RESPONSE);
case ERROR_CLASS_TRANSIENT_ERROR:
- return Stop(ERR_FAILED);
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
case ERROR_CLASS_PERMANENT_ERROR:
- if (retr_failed_ && response.status_code == 550) {
- // Both RETR and CWD failed with codes 550. That means that the path
- // we're trying to access is not a file, and not a directory. The most
- // probable interpretation is that it doesn't exist (with FTP we can't
- // be sure).
+ if (resource_type_ == RESOURCE_TYPE_DIRECTORY &&
+ response.status_code == 550) {
+ // We're assuming that the resource is a directory, but the server says
+ // it's not true. The most probable interpretation is that it doesn't
+ // exist (with FTP we can't be sure).
return Stop(ERR_FILE_NOT_FOUND);
}
- return Stop(ERR_FAILED);
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
default:
NOTREACHED();
return Stop(ERR_UNEXPECTED);
@@ -1031,8 +1098,11 @@
const FtpCtrlResponse& response) {
switch (GetErrorClass(response.status_code)) {
case ERROR_CLASS_INITIATED:
+ // We want the client to start reading the response at this point.
+ // It got here either through Start or RestartWithAuth. We want that
+ // method to complete. Not setting next state here will make DoLoop exit
+ // and in turn make Start/RestartWithAuth complete.
response_.is_directory_listing = true;
- next_state_ = STATE_CTRL_READ;
break;
case ERROR_CLASS_OK:
response_.is_directory_listing = true;
@@ -1064,8 +1134,11 @@
const FtpCtrlResponse& response) {
switch (GetErrorClass(response.status_code)) {
case ERROR_CLASS_INITIATED:
+ // We want the client to start reading the response at this point.
+ // It got here either through Start or RestartWithAuth. We want that
+ // method to complete. Not setting next state here will make DoLoop exit
+ // and in turn make Start/RestartWithAuth complete.
response_.is_directory_listing = true;
- next_state_ = STATE_CTRL_READ;
break;
case ERROR_CLASS_OK:
response_.is_directory_listing = true;
@@ -1074,9 +1147,9 @@
case ERROR_CLASS_INFO_NEEDED:
return Stop(ERR_INVALID_RESPONSE);
case ERROR_CLASS_TRANSIENT_ERROR:
- return Stop(ERR_FAILED);
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
case ERROR_CLASS_PERMANENT_ERROR:
- return Stop(ERR_FAILED);
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
default:
NOTREACHED();
return Stop(ERR_UNEXPECTED);
@@ -1101,24 +1174,21 @@
int FtpNetworkTransaction::DoDataConnect() {
next_state_ = STATE_DATA_CONNECT_COMPLETE;
- AddressList data_addresses;
- // TODO(phajdan.jr): Use exactly same IP address as the control socket.
- // If the DNS name resolves to several different IPs, and they are different
- // physical servers, this will break. However, that configuration is very rare
- // in practice.
- data_addresses.Copy(addresses_.head());
- data_addresses.SetPort(data_connection_port_);
- data_socket_.reset(socket_factory_->CreateTCPClientSocket(data_addresses));
- return data_socket_->Connect(&io_callback_, load_log_);
+ AddressList data_address;
+ // Connect to the same host as the control socket to prevent PASV port
+ // scanning attacks.
+ int rv = ctrl_socket_->GetPeerAddress(&data_address);
+ if (rv != OK)
+ return Stop(rv);
+ data_address.SetPort(data_connection_port_);
+ data_socket_.reset(socket_factory_->CreateTCPClientSocket(
+ data_address, net_log_.net_log()));
+ return data_socket_->Connect(&io_callback_);
}
int FtpNetworkTransaction::DoDataConnectComplete(int result) {
RecordDataConnectionError(result);
- if (retr_failed_) {
- next_state_ = STATE_CTRL_WRITE_CWD;
- } else {
- next_state_ = STATE_CTRL_WRITE_SIZE;
- }
+ next_state_ = STATE_CTRL_WRITE_SIZE;
return OK;
}
@@ -1132,7 +1202,13 @@
// to be closed on our side too.
data_socket_.reset();
- // No more data so send QUIT Command now and wait for response.
+ if (ctrl_socket_->IsConnected()) {
+ // Wait for the server's response, we should get it before sending QUIT.
+ next_state_ = STATE_CTRL_READ;
+ return OK;
+ }
+
+ // We are no longer connected to the server, so just finish the transaction.
return Stop(OK);
}
diff --git a/net/ftp/ftp_network_transaction.h b/net/ftp/ftp_network_transaction.h
index e8140f4..31c38c7 100644
--- a/net/ftp/ftp_network_transaction.h
+++ b/net/ftp/ftp_network_transaction.h
@@ -1,6 +1,6 @@
-// Copyright (c) 2008 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.
+// 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.
#ifndef NET_FTP_FTP_NETWORK_TRANSACTION_H_
#define NET_FTP_FTP_NETWORK_TRANSACTION_H_
@@ -14,6 +14,7 @@
#include "base/scoped_ptr.h"
#include "net/base/address_list.h"
#include "net/base/host_resolver.h"
+#include "net/base/net_log.h"
#include "net/ftp/ftp_ctrl_response_buffer.h"
#include "net/ftp/ftp_response_info.h"
#include "net/ftp/ftp_transaction.h"
@@ -33,7 +34,7 @@
// FtpTransaction methods:
virtual int Start(const FtpRequestInfo* request_info,
CompletionCallback* callback,
- LoadLog* load_log);
+ const BoundNetLog& net_log);
virtual int Stop(int error);
virtual int RestartWithAuth(const std::wstring& username,
const std::wstring& password,
@@ -49,9 +50,9 @@
COMMAND_NONE,
COMMAND_USER,
COMMAND_PASS,
- COMMAND_ACCT,
COMMAND_SYST,
COMMAND_TYPE,
+ COMMAND_EPSV,
COMMAND_PASV,
COMMAND_PWD,
COMMAND_SIZE,
@@ -59,31 +60,7 @@
COMMAND_CWD,
COMMAND_MLSD,
COMMAND_LIST,
- COMMAND_MDTM,
- COMMAND_QUIT
- };
-
- enum ErrorClass {
- // The requested action was initiated. The client should expect another
- // reply before issuing the next command.
- ERROR_CLASS_INITIATED,
-
- // The requested action has been successfully completed.
- ERROR_CLASS_OK,
-
- // The command has been accepted, but to complete the operation, more
- // information must be sent by the client.
- ERROR_CLASS_INFO_NEEDED,
-
- // The command was not accepted and the requested action did not take place.
- // This condition is temporary, and the client is encouraged to restart the
- // command sequence.
- ERROR_CLASS_TRANSIENT_ERROR,
-
- // The command was not accepted and the requested action did not take place.
- // This condition is rather permanent, and the client is discouraged from
- // repeating the exact request.
- ERROR_CLASS_PERMANENT_ERROR,
+ COMMAND_QUIT,
};
// Major categories of remote system types, as returned by SYST command.
@@ -95,6 +72,22 @@
SYSTEM_TYPE_VMS,
};
+ // Data representation type, see RFC 959 section 3.1.1. Data Types.
+ // We only support the two most popular data types.
+ enum DataType {
+ DATA_TYPE_ASCII,
+ DATA_TYPE_IMAGE,
+ };
+
+ // In FTP we need to issue different commands depending on whether a resource
+ // is a file or directory. If we don't know that, we're going to autodetect
+ // it.
+ enum ResourceType {
+ RESOURCE_TYPE_UNKNOWN,
+ RESOURCE_TYPE_FILE,
+ RESOURCE_TYPE_DIRECTORY,
+ };
+
// Resets the members of the transaction so it can be restarted.
void ResetStateForRestart();
@@ -107,14 +100,13 @@
int SendFtpCommand(const std::string& command, Command cmd);
- // Return the error class for given response code. You should validate the
- // code to be in range 100-599.
- static ErrorClass GetErrorClass(int response_code);
-
// Returns request path suitable to be included in an FTP command. If the path
// will be used as a directory, |is_directory| should be true.
std::string GetRequestPathForFtpCommand(bool is_directory) const;
+ // See if the request URL contains a typecode and make us respect it.
+ void DetectTypecode();
+
// Runs the state transition loop.
int DoLoop(int result);
@@ -122,8 +114,6 @@
// argument receive the result from the previous state. If a method returns
// ERR_IO_PENDING, then the result from OnIOComplete will be passed to the
// next state method as the result arg.
- int DoCtrlInit();
- int DoCtrlInitComplete(int result);
int DoCtrlResolveHost();
int DoCtrlResolveHostComplete(int result);
int DoCtrlConnect();
@@ -136,14 +126,14 @@
int ProcessResponseUSER(const FtpCtrlResponse& response);
int DoCtrlWritePASS();
int ProcessResponsePASS(const FtpCtrlResponse& response);
- int DoCtrlWriteACCT();
- int ProcessResponseACCT(const FtpCtrlResponse& response);
int DoCtrlWriteSYST();
int ProcessResponseSYST(const FtpCtrlResponse& response);
int DoCtrlWritePWD();
int ProcessResponsePWD(const FtpCtrlResponse& response);
int DoCtrlWriteTYPE();
int ProcessResponseTYPE(const FtpCtrlResponse& response);
+ int DoCtrlWriteEPSV();
+ int ProcessResponseEPSV(const FtpCtrlResponse& response);
int DoCtrlWritePASV();
int ProcessResponsePASV(const FtpCtrlResponse& response);
int DoCtrlWriteRETR();
@@ -156,8 +146,6 @@
int ProcessResponseMLSD(const FtpCtrlResponse& response);
int DoCtrlWriteLIST();
int ProcessResponseLIST(const FtpCtrlResponse& response);
- int DoCtrlWriteMDTM();
- int ProcessResponseMDTM(const FtpCtrlResponse& response);
int DoCtrlWriteQUIT();
int ProcessResponseQUIT(const FtpCtrlResponse& response);
@@ -175,7 +163,7 @@
scoped_refptr<FtpNetworkSession> session_;
- scoped_refptr<LoadLog> load_log_;
+ BoundNetLog net_log_;
const FtpRequestInfo* request_;
FtpResponseInfo response_;
@@ -190,7 +178,6 @@
scoped_refptr<IOBuffer> read_data_buf_;
int read_data_buf_len_;
- int file_data_len_;
// Buffer holding the command line to be written to the control socket.
scoped_refptr<IOBufferWithSize> write_command_buf_;
@@ -203,6 +190,16 @@
SystemType system_type_;
+ // Data type to be used for the TYPE command.
+ DataType data_type_;
+
+ // Detected resource type (file or directory).
+ ResourceType resource_type_;
+
+ // Initially we favour EPSV over PASV for transfers but should any
+ // EPSV fail, we fall back to PASV for the duration of connection.
+ bool use_epsv_;
+
// We get username and password as wstrings in RestartWithAuth, so they are
// also kept as wstrings here.
std::wstring username_;
@@ -212,8 +209,6 @@
// with any trailing slash removed.
std::string current_remote_directory_;
- bool retr_failed_;
-
int data_connection_port_;
ClientSocketFactory* socket_factory_;
@@ -223,8 +218,6 @@
enum State {
// Control connection states:
- STATE_CTRL_INIT,
- STATE_CTRL_INIT_COMPLETE,
STATE_CTRL_RESOLVE_HOST,
STATE_CTRL_RESOLVE_HOST_COMPLETE,
STATE_CTRL_CONNECT,
@@ -235,9 +228,9 @@
STATE_CTRL_WRITE_COMPLETE,
STATE_CTRL_WRITE_USER,
STATE_CTRL_WRITE_PASS,
- STATE_CTRL_WRITE_ACCT,
STATE_CTRL_WRITE_SYST,
STATE_CTRL_WRITE_TYPE,
+ STATE_CTRL_WRITE_EPSV,
STATE_CTRL_WRITE_PASV,
STATE_CTRL_WRITE_PWD,
STATE_CTRL_WRITE_RETR,
@@ -245,7 +238,6 @@
STATE_CTRL_WRITE_CWD,
STATE_CTRL_WRITE_MLSD,
STATE_CTRL_WRITE_LIST,
- STATE_CTRL_WRITE_MDTM,
STATE_CTRL_WRITE_QUIT,
// Data connection states:
STATE_DATA_CONNECT,
diff --git a/net/ftp/ftp_network_transaction_unittest.cc b/net/ftp/ftp_network_transaction_unittest.cc
index d8479aa..b7cc29d 100644
--- a/net/ftp/ftp_network_transaction_unittest.cc
+++ b/net/ftp/ftp_network_transaction_unittest.cc
@@ -36,21 +36,22 @@
PRE_SYST,
PRE_PWD,
PRE_TYPE,
- PRE_PASV,
PRE_SIZE,
- PRE_MDTM,
+ PRE_EPSV,
+ PRE_PASV,
PRE_MLSD,
PRE_LIST,
PRE_RETR,
- PRE_PASV2,
PRE_CWD,
PRE_QUIT,
+ PRE_NOPASV,
QUIT
};
FtpSocketDataProvider()
: failure_injection_state_(NONE),
- multiline_welcome_(false) {
+ multiline_welcome_(false),
+ data_type_('I') {
Init();
}
@@ -74,15 +75,14 @@
return Verify("PWD\r\n", data, PRE_TYPE,
"257 \"/\" is your current location\r\n");
case PRE_TYPE:
- return Verify("TYPE I\r\n", data, PRE_PASV,
- "200 TYPE is now 8-bit binary\r\n");
- case PRE_PASV:
- return Verify("PASV\r\n", data, PRE_SIZE,
- "227 Entering Passive Mode 127,0,0,1,123,456\r\n");
- case PRE_PASV2:
- // Parser should also accept format without parentheses.
- return Verify("PASV\r\n", data, PRE_CWD,
- "227 Entering Passive Mode 127,0,0,1,123,456\r\n");
+ return Verify(std::string("TYPE ") + data_type_ + "\r\n", data,
+ PRE_EPSV, "200 TYPE set successfully\r\n");
+ case PRE_EPSV:
+ return Verify("EPSV\r\n", data, PRE_SIZE,
+ "227 Entering Extended Passive Mode (|||31744|)\r\n");
+ case PRE_NOPASV:
+ return Verify("PASV\r\n", data, PRE_QUIT,
+ "599 fail\r\n");
case PRE_QUIT:
return Verify("QUIT\r\n", data, QUIT, "221 Goodbye.\r\n");
default:
@@ -114,6 +114,10 @@
multiline_welcome_ = multiline;
}
+ void set_data_type(char data_type) {
+ data_type_ = data_type;
+ }
+
protected:
void Init() {
state_ = PRE_USER;
@@ -133,16 +137,26 @@
MockWriteResult Verify(const std::string& expected,
const std::string& data,
State next_state,
- const char* next_read) {
+ const char* next_read,
+ const size_t next_read_length) {
EXPECT_EQ(expected, data);
if (expected == data) {
state_ = next_state;
- SimulateRead(next_read);
+ SimulateRead(next_read, next_read_length);
return MockWriteResult(true, data.length());
}
return MockWriteResult(true, ERR_UNEXPECTED);
}
+ MockWriteResult Verify(const std::string& expected,
+ const std::string& data,
+ State next_state,
+ const char* next_read) {
+ return Verify(expected, data, next_state,
+ next_read, std::strlen(next_read));
+ }
+
+
private:
State state_;
State failure_injection_state_;
@@ -152,6 +166,9 @@
// If true, we will send multiple 230 lines as response after PASS.
bool multiline_welcome_;
+ // Data type to be used for TYPE command.
+ char data_type_;
+
DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProvider);
};
@@ -165,15 +182,8 @@
return MockWriteResult(true, data.length());
switch (state()) {
case PRE_SIZE:
- return Verify("SIZE /\r\n", data, PRE_MDTM,
+ return Verify("SIZE /\r\n", data, PRE_CWD,
"550 I can only retrieve regular files\r\n");
- case PRE_MDTM:
- return Verify("MDTM /\r\n", data, PRE_RETR,
- "213 20070221112533\r\n");
- case PRE_RETR:
- return Verify("RETR /\r\n", data, PRE_PASV2,
- "550 Can't download directory\r\n");
-
case PRE_CWD:
return Verify("CWD /\r\n", data, PRE_MLSD, "200 OK\r\n");
case PRE_MLSD:
@@ -191,6 +201,32 @@
DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderDirectoryListing);
};
+class FtpSocketDataProviderDirectoryListingWithPasvFallback
+ : public FtpSocketDataProviderDirectoryListing {
+ public:
+ FtpSocketDataProviderDirectoryListingWithPasvFallback() {
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) {
+ if (InjectFault())
+ return MockWriteResult(true, data.length());
+ switch (state()) {
+ case PRE_EPSV:
+ return Verify("EPSV\r\n", data, PRE_PASV,
+ "500 no EPSV for you\r\n");
+ case PRE_PASV:
+ return Verify("PASV\r\n", data, PRE_SIZE,
+ "227 Entering Passive Mode 127,0,0,1,123,456\r\n");
+ default:
+ return FtpSocketDataProviderDirectoryListing::OnWrite(data);
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(
+ FtpSocketDataProviderDirectoryListingWithPasvFallback);
+};
+
class FtpSocketDataProviderVMSDirectoryListing : public FtpSocketDataProvider {
public:
FtpSocketDataProviderVMSDirectoryListing() {
@@ -205,15 +241,14 @@
case PRE_PWD:
return Verify("PWD\r\n", data, PRE_TYPE,
"257 \"ANONYMOUS_ROOT:[000000]\"\r\n");
+ case PRE_EPSV:
+ return Verify("EPSV\r\n", data, PRE_PASV, "500 Invalid command\r\n");
+ case PRE_PASV:
+ return Verify("PASV\r\n", data, PRE_SIZE,
+ "227 Entering Passive Mode 127,0,0,1,123,456\r\n");
case PRE_SIZE:
- return Verify("SIZE ANONYMOUS_ROOT:[000000]dir\r\n", data, PRE_MDTM,
+ return Verify("SIZE ANONYMOUS_ROOT:[000000]dir\r\n", data, PRE_CWD,
"550 I can only retrieve regular files\r\n");
- case PRE_MDTM:
- return Verify("MDTM ANONYMOUS_ROOT:[000000]dir\r\n", data, PRE_RETR,
- "213 20070221112533\r\n");
- case PRE_RETR:
- return Verify("RETR ANONYMOUS_ROOT:[000000]dir\r\n", data, PRE_PASV2,
- "550 Can't download directory\r\n");
case PRE_CWD:
return Verify("CWD ANONYMOUS_ROOT:[dir]\r\n", data, PRE_MLSD,
"200 OK\r\n");
@@ -245,15 +280,15 @@
case PRE_PWD:
return Verify("PWD\r\n", data, PRE_TYPE,
"257 \"ANONYMOUS_ROOT:[000000]\"\r\n");
+ case PRE_EPSV:
+ return Verify("EPSV\r\n", data, PRE_PASV,
+ "500 EPSV command unknown\r\n");
+ case PRE_PASV:
+ return Verify("PASV\r\n", data, PRE_SIZE,
+ "227 Entering Passive Mode 127,0,0,1,123,456\r\n");
case PRE_SIZE:
- return Verify("SIZE ANONYMOUS_ROOT\r\n", data, PRE_MDTM,
+ return Verify("SIZE ANONYMOUS_ROOT\r\n", data, PRE_CWD,
"550 I can only retrieve regular files\r\n");
- case PRE_MDTM:
- return Verify("MDTM ANONYMOUS_ROOT\r\n", data, PRE_RETR,
- "213 20070221112533\r\n");
- case PRE_RETR:
- return Verify("RETR ANONYMOUS_ROOT\r\n", data, PRE_PASV2,
- "550 Can't download directory\r\n");
case PRE_CWD:
return Verify("CWD ANONYMOUS_ROOT:[000000]\r\n", data, PRE_MLSD,
"200 OK\r\n");
@@ -281,11 +316,8 @@
return MockWriteResult(true, data.length());
switch (state()) {
case PRE_SIZE:
- return Verify("SIZE /file\r\n", data, PRE_MDTM,
+ return Verify("SIZE /file\r\n", data, PRE_RETR,
"213 18\r\n");
- case PRE_MDTM:
- return Verify("MDTM /file\r\n", data, PRE_RETR,
- "213 20070221112533\r\n");
case PRE_RETR:
return Verify("RETR /file\r\n", data, PRE_QUIT, "200 OK\r\n");
default:
@@ -297,6 +329,31 @@
DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderFileDownload);
};
+class FtpSocketDataProviderFileDownloadWithPasvFallback
+ : public FtpSocketDataProviderFileDownload {
+ public:
+ FtpSocketDataProviderFileDownloadWithPasvFallback() {
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) {
+ if (InjectFault())
+ return MockWriteResult(true, data.length());
+ switch (state()) {
+ case PRE_EPSV:
+ return Verify("EPSV\r\n", data, PRE_PASV,
+ "500 No can do\r\n");
+ case PRE_PASV:
+ return Verify("PASV\r\n", data, PRE_SIZE,
+ "227 Entering Passive Mode 127,0,0,1,123,456\r\n");
+ default:
+ return FtpSocketDataProviderFileDownload::OnWrite(data);
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderFileDownloadWithPasvFallback);
+};
+
class FtpSocketDataProviderVMSFileDownload : public FtpSocketDataProvider {
public:
FtpSocketDataProviderVMSFileDownload() {
@@ -311,12 +368,15 @@
case PRE_PWD:
return Verify("PWD\r\n", data, PRE_TYPE,
"257 \"ANONYMOUS_ROOT:[000000]\"\r\n");
+ case PRE_EPSV:
+ return Verify("EPSV\r\n", data, PRE_PASV,
+ "500 EPSV command unknown\r\n");
+ case PRE_PASV:
+ return Verify("PASV\r\n", data, PRE_SIZE,
+ "227 Entering Passive Mode 127,0,0,1,123,456\r\n");
case PRE_SIZE:
- return Verify("SIZE ANONYMOUS_ROOT:[000000]file\r\n", data, PRE_MDTM,
+ return Verify("SIZE ANONYMOUS_ROOT:[000000]file\r\n", data, PRE_RETR,
"213 18\r\n");
- case PRE_MDTM:
- return Verify("MDTM ANONYMOUS_ROOT:[000000]file\r\n", data, PRE_RETR,
- "213 20070221112533\r\n");
case PRE_RETR:
return Verify("RETR ANONYMOUS_ROOT:[000000]file\r\n", data, PRE_QUIT,
"200 OK\r\n");
@@ -329,7 +389,7 @@
DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderVMSFileDownload);
};
-class FtpSocketDataProviderEscaping : public FtpSocketDataProvider {
+class FtpSocketDataProviderEscaping : public FtpSocketDataProviderFileDownload {
public:
FtpSocketDataProviderEscaping() {
}
@@ -339,44 +399,18 @@
return MockWriteResult(true, data.length());
switch (state()) {
case PRE_SIZE:
- return Verify("SIZE / !\"#$%y\200\201\r\n", data, PRE_MDTM,
+ return Verify("SIZE / !\"#$%y\200\201\r\n", data, PRE_RETR,
"213 18\r\n");
- case PRE_MDTM:
- return Verify("MDTM / !\"#$%y\200\201\r\n", data, PRE_RETR,
- "213 20070221112533\r\n");
case PRE_RETR:
return Verify("RETR / !\"#$%y\200\201\r\n", data, PRE_QUIT,
"200 OK\r\n");
default:
- return FtpSocketDataProvider::OnWrite(data);
- }
- }
-
- private:
- DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderEscaping);
-};
-
-class FtpSocketDataProviderFileDownloadAcceptedDataConnection
- : public FtpSocketDataProviderFileDownload {
- public:
- FtpSocketDataProviderFileDownloadAcceptedDataConnection() {
- }
-
- virtual MockWriteResult OnWrite(const std::string& data) {
- if (InjectFault())
- return MockWriteResult(true, data.length());
- switch (state()) {
- case PRE_RETR:
- return Verify("RETR /file\r\n", data, PRE_QUIT,
- "150 Accepted Data Connection\r\n");
- default:
return FtpSocketDataProviderFileDownload::OnWrite(data);
}
}
private:
- DISALLOW_COPY_AND_ASSIGN(
- FtpSocketDataProviderFileDownloadAcceptedDataConnection);
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderEscaping);
};
class FtpSocketDataProviderFileDownloadTransferStarting
@@ -440,8 +474,8 @@
switch (state()) {
case PRE_SIZE:
return Verify("SIZE /file\r\n", data, PRE_QUIT,
- "500 Evil Response\r\n"
- "500 More Evil\r\n");
+ "599 Evil Response\r\n"
+ "599 More Evil\r\n");
default:
return FtpSocketDataProviderFileDownload::OnWrite(data);
}
@@ -451,32 +485,46 @@
DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderFileDownloadInvalidResponse);
};
-class FtpSocketDataProviderFileDownloadRetrFail
- : public FtpSocketDataProviderFileDownload {
+class FtpSocketDataProviderEvilEpsv : public FtpSocketDataProviderFileDownload {
public:
- FtpSocketDataProviderFileDownloadRetrFail() {
- }
+ FtpSocketDataProviderEvilEpsv(const char* epsv_response,
+ State expected_state)
+ : epsv_response_(epsv_response),
+ epsv_response_length_(std::strlen(epsv_response)),
+ expected_state_(expected_state) {}
+
+ FtpSocketDataProviderEvilEpsv(const char* epsv_response,
+ size_t epsv_response_length,
+ State expected_state)
+ : epsv_response_(epsv_response),
+ epsv_response_length_(epsv_response_length),
+ expected_state_(expected_state) {}
virtual MockWriteResult OnWrite(const std::string& data) {
if (InjectFault())
return MockWriteResult(true, data.length());
switch (state()) {
- case PRE_CWD:
- return Verify("CWD /file\r\n", data, PRE_QUIT,
- "550 file is a directory\r\n");
+ case PRE_EPSV:
+ return Verify("EPSV\r\n", data, expected_state_,
+ epsv_response_, epsv_response_length_);
default:
return FtpSocketDataProviderFileDownload::OnWrite(data);
}
}
private:
- DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderFileDownloadRetrFail);
+ const char* epsv_response_;
+ const size_t epsv_response_length_;
+ const State expected_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderEvilEpsv);
};
-class FtpSocketDataProviderEvilPasv : public FtpSocketDataProviderFileDownload {
+class FtpSocketDataProviderEvilPasv
+ : public FtpSocketDataProviderFileDownloadWithPasvFallback {
public:
explicit FtpSocketDataProviderEvilPasv(const char* pasv_response,
- State expected_state)
+ State expected_state)
: pasv_response_(pasv_response),
expected_state_(expected_state) {
}
@@ -488,7 +536,7 @@
case PRE_PASV:
return Verify("PASV\r\n", data, expected_state_, pasv_response_);
default:
- return FtpSocketDataProviderFileDownload::OnWrite(data);
+ return FtpSocketDataProviderFileDownloadWithPasvFallback::OnWrite(data);
}
}
@@ -499,7 +547,33 @@
DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderEvilPasv);
};
-class FtpSocketDataProviderEvilLogin : public FtpSocketDataProviderFileDownload {
+class FtpSocketDataProviderEvilSize : public FtpSocketDataProviderFileDownload {
+ public:
+ FtpSocketDataProviderEvilSize(const char* size_response, State expected_state)
+ : size_response_(size_response),
+ expected_state_(expected_state) {
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) {
+ if (InjectFault())
+ return MockWriteResult(true, data.length());
+ switch (state()) {
+ case PRE_SIZE:
+ return Verify("SIZE /file\r\n", data, expected_state_, size_response_);
+ default:
+ return FtpSocketDataProviderFileDownload::OnWrite(data);
+ }
+ }
+
+ private:
+ const char* size_response_;
+ const State expected_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderEvilSize);
+};
+
+class FtpSocketDataProviderEvilLogin
+ : public FtpSocketDataProviderFileDownload {
public:
FtpSocketDataProviderEvilLogin(const char* expected_user,
const char* expected_password)
@@ -570,31 +644,42 @@
int expected_result) {
std::string mock_data("mock-data");
MockRead data_reads[] = {
+ // Usually FTP servers close the data connection after the entire data has
+ // been received.
+ MockRead(false, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
MockRead(mock_data.c_str()),
};
// For compatibility with FileZilla, the transaction code will use two data
// sockets for directory requests. For more info see http://crbug.com/25316.
- StaticSocketDataProvider data1(data_reads, NULL);
- StaticSocketDataProvider data2(data_reads, NULL);
+ StaticSocketDataProvider data1(data_reads, arraysize(data_reads), NULL, 0);
+ StaticSocketDataProvider data2(data_reads, arraysize(data_reads), NULL, 0);
mock_socket_factory_.AddSocketDataProvider(ctrl_socket);
mock_socket_factory_.AddSocketDataProvider(&data1);
mock_socket_factory_.AddSocketDataProvider(&data2);
FtpRequestInfo request_info = GetRequestInfo(request);
EXPECT_EQ(LOAD_STATE_IDLE, transaction_.GetLoadState());
ASSERT_EQ(ERR_IO_PENDING,
- transaction_.Start(&request_info, &callback_, NULL));
+ transaction_.Start(&request_info, &callback_, BoundNetLog()));
EXPECT_NE(LOAD_STATE_IDLE, transaction_.GetLoadState());
- EXPECT_EQ(expected_result, callback_.WaitForResult());
- EXPECT_EQ(FtpSocketDataProvider::QUIT, ctrl_socket->state());
+ ASSERT_EQ(expected_result, callback_.WaitForResult());
if (expected_result == OK) {
scoped_refptr<IOBuffer> io_buffer(new IOBuffer(kBufferSize));
memset(io_buffer->data(), 0, kBufferSize);
ASSERT_EQ(ERR_IO_PENDING,
transaction_.Read(io_buffer.get(), kBufferSize, &callback_));
- EXPECT_EQ(static_cast<int>(mock_data.length()),
+ ASSERT_EQ(static_cast<int>(mock_data.length()),
callback_.WaitForResult());
EXPECT_EQ(mock_data, std::string(io_buffer->data(), mock_data.length()));
+
+ // Do another Read to detect that the data socket is now closed.
+ int rv = transaction_.Read(io_buffer.get(), kBufferSize, &callback_);
+ if (rv == ERR_IO_PENDING) {
+ EXPECT_EQ(0, callback_.WaitForResult());
+ } else {
+ EXPECT_EQ(0, rv);
+ }
}
+ EXPECT_EQ(FtpSocketDataProvider::QUIT, ctrl_socket->state());
EXPECT_EQ(LOAD_STATE_IDLE, transaction_.GetLoadState());
}
@@ -620,14 +705,48 @@
host_resolver_->rules()->AddSimulatedFailure("badhost");
EXPECT_EQ(LOAD_STATE_IDLE, transaction_.GetLoadState());
ASSERT_EQ(ERR_IO_PENDING,
- transaction_.Start(&request_info, &callback_, NULL));
- EXPECT_EQ(ERR_NAME_NOT_RESOLVED, callback_.WaitForResult());
+ transaction_.Start(&request_info, &callback_, BoundNetLog()));
+ ASSERT_EQ(ERR_NAME_NOT_RESOLVED, callback_.WaitForResult());
EXPECT_EQ(LOAD_STATE_IDLE, transaction_.GetLoadState());
}
+// Check that when determining the host, the square brackets decorating IPv6
+// literals in URLs are stripped.
+TEST_F(FtpNetworkTransactionTest, StripBracketsFromIPv6Literals) {
+ host_resolver_->rules()->AddSimulatedFailure("[::1]");
+
+ // We start a transaction that is expected to fail with ERR_INVALID_RESPONSE.
+ // The important part of this test is to make sure that we don't fail with
+ // ERR_NAME_NOT_RESOLVED, since that would mean the decorated hostname
+ // was used.
+ FtpSocketDataProviderEvilSize ctrl_socket(
+ "213 99999999999999999999999999999999\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://[::1]/file", ERR_INVALID_RESPONSE);
+}
+
TEST_F(FtpNetworkTransactionTest, DirectoryTransaction) {
FtpSocketDataProviderDirectoryListing ctrl_socket;
ExecuteTransaction(&ctrl_socket, "ftp://host", OK);
+
+ EXPECT_TRUE(transaction_.GetResponseInfo()->is_directory_listing);
+ EXPECT_EQ(-1, transaction_.GetResponseInfo()->expected_content_size);
+}
+
+TEST_F(FtpNetworkTransactionTest, DirectoryTransactionWithPasvFallback) {
+ FtpSocketDataProviderDirectoryListingWithPasvFallback ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host", OK);
+
+ EXPECT_TRUE(transaction_.GetResponseInfo()->is_directory_listing);
+ EXPECT_EQ(-1, transaction_.GetResponseInfo()->expected_content_size);
+}
+
+TEST_F(FtpNetworkTransactionTest, DirectoryTransactionWithTypecode) {
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host;type=d", OK);
+
+ EXPECT_TRUE(transaction_.GetResponseInfo()->is_directory_listing);
+ EXPECT_EQ(-1, transaction_.GetResponseInfo()->expected_content_size);
}
TEST_F(FtpNetworkTransactionTest, DirectoryTransactionMultilineWelcome) {
@@ -676,6 +795,34 @@
TEST_F(FtpNetworkTransactionTest, DownloadTransaction) {
FtpSocketDataProviderFileDownload ctrl_socket;
ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK);
+
+ // We pass an artificial value of 18 as a response to the SIZE command.
+ EXPECT_EQ(18, transaction_.GetResponseInfo()->expected_content_size);
+}
+
+TEST_F(FtpNetworkTransactionTest, DownloadTransactionWithPasvFallback) {
+ FtpSocketDataProviderFileDownloadWithPasvFallback ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK);
+
+ // We pass an artificial value of 18 as a response to the SIZE command.
+ EXPECT_EQ(18, transaction_.GetResponseInfo()->expected_content_size);
+}
+
+TEST_F(FtpNetworkTransactionTest, DownloadTransactionWithTypecodeA) {
+ FtpSocketDataProviderFileDownload ctrl_socket;
+ ctrl_socket.set_data_type('A');
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file;type=a", OK);
+
+ // We pass an artificial value of 18 as a response to the SIZE command.
+ EXPECT_EQ(18, transaction_.GetResponseInfo()->expected_content_size);
+}
+
+TEST_F(FtpNetworkTransactionTest, DownloadTransactionWithTypecodeI) {
+ FtpSocketDataProviderFileDownload ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file;type=i", OK);
+
+ // We pass an artificial value of 18 as a response to the SIZE command.
+ EXPECT_EQ(18, transaction_.GetResponseInfo()->expected_content_size);
}
TEST_F(FtpNetworkTransactionTest, DownloadTransactionMultilineWelcome) {
@@ -701,52 +848,6 @@
ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK);
}
-TEST_F(FtpNetworkTransactionTest, DownloadTransactionAcceptedDataConnection) {
- FtpSocketDataProviderFileDownloadAcceptedDataConnection ctrl_socket;
- std::string mock_data("mock-data");
- MockRead data_reads[] = {
- MockRead(mock_data.c_str()),
- };
- StaticSocketDataProvider data_socket1(data_reads, NULL);
- mock_socket_factory_.AddSocketDataProvider(&ctrl_socket);
- mock_socket_factory_.AddSocketDataProvider(&data_socket1);
- FtpRequestInfo request_info = GetRequestInfo("ftp://host/file");
-
- // Start the transaction.
- ASSERT_EQ(ERR_IO_PENDING,
- transaction_.Start(&request_info, &callback_, NULL));
- EXPECT_EQ(OK, callback_.WaitForResult());
-
- // The transaction fires the callback when we can start reading data.
- EXPECT_EQ(FtpSocketDataProvider::PRE_QUIT, ctrl_socket.state());
- EXPECT_EQ(LOAD_STATE_SENDING_REQUEST, transaction_.GetLoadState());
- scoped_refptr<IOBuffer> io_buffer(new IOBuffer(kBufferSize));
- memset(io_buffer->data(), 0, kBufferSize);
- ASSERT_EQ(ERR_IO_PENDING,
- transaction_.Read(io_buffer.get(), kBufferSize, &callback_));
- EXPECT_EQ(LOAD_STATE_READING_RESPONSE, transaction_.GetLoadState());
- EXPECT_EQ(static_cast<int>(mock_data.length()),
- callback_.WaitForResult());
- EXPECT_EQ(LOAD_STATE_READING_RESPONSE, transaction_.GetLoadState());
- EXPECT_EQ(mock_data, std::string(io_buffer->data(), mock_data.length()));
-
- // FTP server should disconnect the data socket. It is also a signal for the
- // FtpNetworkTransaction that the data transfer is finished.
- ClientSocket* data_socket = mock_socket_factory_.GetMockTCPClientSocket(1);
- ASSERT_TRUE(data_socket);
- data_socket->Disconnect();
-
- // We should issue Reads until one returns EOF...
- ASSERT_EQ(ERR_IO_PENDING,
- transaction_.Read(io_buffer.get(), kBufferSize, &callback_));
-
- // Make sure the transaction finishes cleanly.
- EXPECT_EQ(LOAD_STATE_IDLE, transaction_.GetLoadState());
- EXPECT_EQ(OK, callback_.WaitForResult());
- EXPECT_EQ(FtpSocketDataProvider::QUIT, ctrl_socket.state());
- EXPECT_EQ(LOAD_STATE_IDLE, transaction_.GetLoadState());
-}
-
TEST_F(FtpNetworkTransactionTest, DownloadTransactionTransferStarting) {
FtpSocketDataProviderFileDownloadTransferStarting ctrl_socket;
ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK);
@@ -757,6 +858,12 @@
ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
}
+TEST_F(FtpNetworkTransactionTest, DownloadTransactionEvilPasvReallyBadFormat) {
+ FtpSocketDataProviderEvilPasv ctrl_socket("227 Portscan (127,0,0,\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
+}
+
TEST_F(FtpNetworkTransactionTest, DownloadTransactionEvilPasvUnsafePort1) {
FtpSocketDataProviderEvilPasv ctrl_socket("227 Portscan (127,0,0,1,0,22)\r\n",
FtpSocketDataProvider::PRE_QUIT);
@@ -791,15 +898,16 @@
MockRead data_reads[] = {
MockRead(mock_data.c_str()),
};
- StaticSocketDataProvider data_socket1(data_reads, NULL);
+ StaticSocketDataProvider data_socket1(data_reads, arraysize(data_reads),
+ NULL, 0);
mock_socket_factory_.AddSocketDataProvider(&ctrl_socket);
mock_socket_factory_.AddSocketDataProvider(&data_socket1);
FtpRequestInfo request_info = GetRequestInfo("ftp://host/file");
// Start the transaction.
ASSERT_EQ(ERR_IO_PENDING,
- transaction_.Start(&request_info, &callback_, NULL));
- EXPECT_EQ(OK, callback_.WaitForResult());
+ transaction_.Start(&request_info, &callback_, BoundNetLog()));
+ ASSERT_EQ(OK, callback_.WaitForResult());
// The transaction fires the callback when we can start reading data. That
// means that the data socket should be open.
@@ -809,11 +917,87 @@
ASSERT_TRUE(data_socket->IsConnected());
// Even if the PASV response specified some other address, we connect
- // to the address we used for control connection.
- EXPECT_EQ("127.0.0.1", NetAddressToString(data_socket->addresses().head()));
+ // to the address we used for control connection (which could be 127.0.0.1
+ // or ::1 depending on whether we use IPv6).
+ const struct addrinfo* addrinfo = data_socket->addresses().head();
+ while (addrinfo) {
+ EXPECT_NE("1.2.3.4", NetAddressToString(addrinfo));
+ addrinfo = addrinfo->ai_next;
+ }
+}
- // Make sure we have only one host entry in the AddressList.
- EXPECT_FALSE(data_socket->addresses().head()->ai_next);
+TEST_F(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvReallyBadFormat1) {
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (|||22)\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
+}
+
+TEST_F(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvReallyBadFormat2) {
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (||\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
+}
+
+TEST_F(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvReallyBadFormat3) {
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
+}
+
+TEST_F(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvReallyBadFormat4) {
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (||||)\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
+}
+
+TEST_F(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvReallyBadFormat5) {
+ const char response[] = "227 Portscan (\0\0\031773\0)\r\n";
+ FtpSocketDataProviderEvilEpsv ctrl_socket(response, sizeof(response)-1,
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
+}
+
+TEST_F(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvUnsafePort1) {
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (|||22|)\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_UNSAFE_PORT);
+}
+
+TEST_F(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvUnsafePort2) {
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (|||258|)\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_UNSAFE_PORT);
+}
+
+TEST_F(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvUnsafePort3) {
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (|||772|)\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_UNSAFE_PORT);
+}
+
+TEST_F(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvUnsafePort4) {
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (|||2049|)\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_UNSAFE_PORT);
+}
+
+TEST_F(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvWeirdSep) {
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan ($$$31744$)\r\n",
+ FtpSocketDataProvider::PRE_SIZE);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK);
+}
+
+TEST_F(FtpNetworkTransactionTest,
+ DownloadTransactionEvilEpsvWeirdSepUnsafePort) {
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan ($$$317$)\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_UNSAFE_PORT);
+}
+
+TEST_F(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvIllegalHost) {
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (|2|::1|31744|)\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
}
TEST_F(FtpNetworkTransactionTest, DownloadTransactionEvilLoginBadUsername) {
@@ -846,8 +1030,8 @@
FtpRequestInfo request_info = GetRequestInfo("ftp://host/file");
ASSERT_EQ(ERR_IO_PENDING,
- transaction_.Start(&request_info, &callback_, NULL));
- EXPECT_EQ(ERR_FAILED, callback_.WaitForResult());
+ transaction_.Start(&request_info, &callback_, BoundNetLog()));
+ ASSERT_EQ(ERR_FTP_FAILED, callback_.WaitForResult());
MockRead ctrl_reads[] = {
MockRead("220 host TestFTPd\r\n"),
@@ -857,7 +1041,8 @@
MockWrite ctrl_writes[] = {
MockWrite("QUIT\r\n"),
};
- StaticSocketDataProvider ctrl_socket2(ctrl_reads, ctrl_writes);
+ StaticSocketDataProvider ctrl_socket2(ctrl_reads, arraysize(ctrl_reads),
+ ctrl_writes, arraysize(ctrl_writes));
mock_socket_factory_.AddSocketDataProvider(&ctrl_socket2);
ASSERT_EQ(ERR_IO_PENDING, transaction_.RestartWithAuth(L"foo\nownz0red",
L"innocent",
@@ -875,8 +1060,8 @@
FtpRequestInfo request_info = GetRequestInfo("ftp://host/file");
ASSERT_EQ(ERR_IO_PENDING,
- transaction_.Start(&request_info, &callback_, NULL));
- EXPECT_EQ(ERR_FAILED, callback_.WaitForResult());
+ transaction_.Start(&request_info, &callback_, BoundNetLog()));
+ ASSERT_EQ(ERR_FTP_FAILED, callback_.WaitForResult());
MockRead ctrl_reads[] = {
MockRead("220 host TestFTPd\r\n"),
@@ -888,7 +1073,8 @@
MockWrite("USER innocent\r\n"),
MockWrite("QUIT\r\n"),
};
- StaticSocketDataProvider ctrl_socket2(ctrl_reads, ctrl_writes);
+ StaticSocketDataProvider ctrl_socket2(ctrl_reads, arraysize(ctrl_reads),
+ ctrl_writes, arraysize(ctrl_writes));
mock_socket_factory_.AddSocketDataProvider(&ctrl_socket2);
ASSERT_EQ(ERR_IO_PENDING, transaction_.RestartWithAuth(L"innocent",
L"foo\nownz0red",
@@ -902,6 +1088,26 @@
OK);
}
+// Test for http://crbug.com/23794.
+TEST_F(FtpNetworkTransactionTest, DownloadTransactionEvilSize) {
+ // Try to overflow int64 in the response.
+ FtpSocketDataProviderEvilSize ctrl_socket(
+ "213 99999999999999999999999999999999\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
+}
+
+// Test for http://crbug.com/36360.
+TEST_F(FtpNetworkTransactionTest, DownloadTransactionBigSize) {
+ // Pass a valid, but large file size. The transaction should not fail.
+ FtpSocketDataProviderEvilSize ctrl_socket(
+ "213 3204427776\r\n",
+ FtpSocketDataProvider::PRE_RETR);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK);
+ EXPECT_EQ(3204427776LL,
+ transaction_.GetResponseInfo()->expected_content_size);
+}
+
// Regression test for http://crbug.com/25023.
TEST_F(FtpNetworkTransactionTest, CloseConnection) {
FtpSocketDataProviderCloseConnection ctrl_socket;
@@ -914,8 +1120,8 @@
"ftp://host",
FtpSocketDataProvider::PRE_USER,
FtpSocketDataProvider::PRE_QUIT,
- "500 no such user\r\n",
- ERR_FAILED);
+ "599 fail\r\n",
+ ERR_FTP_FAILED);
}
TEST_F(FtpNetworkTransactionTest, DirectoryTransactionFailPass) {
@@ -925,7 +1131,18 @@
FtpSocketDataProvider::PRE_PASSWD,
FtpSocketDataProvider::PRE_QUIT,
"530 Login authentication failed\r\n",
- ERR_FAILED);
+ ERR_FTP_FAILED);
+}
+
+// Regression test for http://crbug.com/38707.
+TEST_F(FtpNetworkTransactionTest, DirectoryTransactionFailPass503) {
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
+ TransactionFailHelper(&ctrl_socket,
+ "ftp://host",
+ FtpSocketDataProvider::PRE_PASSWD,
+ FtpSocketDataProvider::PRE_QUIT,
+ "503 Bad sequence of commands\r\n",
+ ERR_FTP_BAD_COMMAND_SEQUENCE);
}
TEST_F(FtpNetworkTransactionTest, DirectoryTransactionFailSyst) {
@@ -934,7 +1151,7 @@
"ftp://host",
FtpSocketDataProvider::PRE_SYST,
FtpSocketDataProvider::PRE_PWD,
- "500 failed syst\r\n",
+ "599 fail\r\n",
OK);
}
@@ -944,8 +1161,8 @@
"ftp://host",
FtpSocketDataProvider::PRE_PWD,
FtpSocketDataProvider::PRE_QUIT,
- "500 failed pwd\r\n",
- ERR_FAILED);
+ "599 fail\r\n",
+ ERR_FTP_FAILED);
}
TEST_F(FtpNetworkTransactionTest, DirectoryTransactionFailType) {
@@ -954,48 +1171,18 @@
"ftp://host",
FtpSocketDataProvider::PRE_TYPE,
FtpSocketDataProvider::PRE_QUIT,
- "500 failed type\r\n",
- ERR_FAILED);
+ "599 fail\r\n",
+ ERR_FTP_FAILED);
}
-TEST_F(FtpNetworkTransactionTest, DirectoryTransactionFailPasv) {
+TEST_F(FtpNetworkTransactionTest, DirectoryTransactionFailEpsv) {
FtpSocketDataProviderDirectoryListing ctrl_socket;
TransactionFailHelper(&ctrl_socket,
"ftp://host",
- FtpSocketDataProvider::PRE_PASV,
- FtpSocketDataProvider::PRE_QUIT,
- "500 failed pasv\r\n",
- ERR_FAILED);
-}
-
-TEST_F(FtpNetworkTransactionTest, DirectoryTransactionMalformedMdtm) {
- FtpSocketDataProviderDirectoryListing ctrl_socket;
- TransactionFailHelper(&ctrl_socket,
- "ftp://host",
- FtpSocketDataProvider::PRE_MDTM,
- FtpSocketDataProvider::PRE_RETR,
- "213 foobar\r\n",
- OK);
-}
-
-TEST_F(FtpNetworkTransactionTest, DirectoryTransactionFailMdtm) {
- FtpSocketDataProviderDirectoryListing ctrl_socket;
- TransactionFailHelper(&ctrl_socket,
- "ftp://host",
- FtpSocketDataProvider::PRE_MDTM,
- FtpSocketDataProvider::PRE_RETR,
- "500 failed mdtm\r\n",
- OK);
-}
-
-TEST_F(FtpNetworkTransactionTest, DirectoryTransactionFailPasv2) {
- FtpSocketDataProviderDirectoryListing ctrl_socket;
- TransactionFailHelper(&ctrl_socket,
- "ftp://host",
- FtpSocketDataProvider::PRE_PASV2,
- FtpSocketDataProvider::PRE_QUIT,
- "500 failed pasv2\r\n",
- ERR_FAILED);
+ FtpSocketDataProvider::PRE_EPSV,
+ FtpSocketDataProvider::PRE_NOPASV,
+ "599 fail\r\n",
+ ERR_FTP_FAILED);
}
TEST_F(FtpNetworkTransactionTest, DirectoryTransactionFailCwd) {
@@ -1004,8 +1191,8 @@
"ftp://host",
FtpSocketDataProvider::PRE_CWD,
FtpSocketDataProvider::PRE_QUIT,
- "500 failed cwd\r\n",
- ERR_FAILED);
+ "599 fail\r\n",
+ ERR_FTP_FAILED);
}
TEST_F(FtpNetworkTransactionTest, DirectoryTransactionFileNotFound) {
@@ -1034,8 +1221,8 @@
"ftp://host/dir",
FtpSocketDataProvider::PRE_LIST,
FtpSocketDataProvider::PRE_QUIT,
- "500 failed list\r\n",
- ERR_FAILED);
+ "599 fail\r\n",
+ ERR_FTP_FAILED);
}
TEST_F(FtpNetworkTransactionTest, DownloadTransactionFailUser) {
@@ -1044,8 +1231,8 @@
"ftp://host/file",
FtpSocketDataProvider::PRE_USER,
FtpSocketDataProvider::PRE_QUIT,
- "500 no such user\r\n",
- ERR_FAILED);
+ "599 fail\r\n",
+ ERR_FTP_FAILED);
}
TEST_F(FtpNetworkTransactionTest, DownloadTransactionFailPass) {
@@ -1055,7 +1242,7 @@
FtpSocketDataProvider::PRE_PASSWD,
FtpSocketDataProvider::PRE_QUIT,
"530 Login authentication failed\r\n",
- ERR_FAILED);
+ ERR_FTP_FAILED);
}
TEST_F(FtpNetworkTransactionTest, DownloadTransactionFailSyst) {
@@ -1064,7 +1251,7 @@
"ftp://host/file",
FtpSocketDataProvider::PRE_SYST,
FtpSocketDataProvider::PRE_PWD,
- "500 failed syst\r\n",
+ "599 fail\r\n",
OK);
}
@@ -1074,8 +1261,8 @@
"ftp://host/file",
FtpSocketDataProvider::PRE_PWD,
FtpSocketDataProvider::PRE_QUIT,
- "500 failed pwd\r\n",
- ERR_FAILED);
+ "599 fail\r\n",
+ ERR_FTP_FAILED);
}
TEST_F(FtpNetworkTransactionTest, DownloadTransactionFailType) {
@@ -1084,48 +1271,49 @@
"ftp://host/file",
FtpSocketDataProvider::PRE_TYPE,
FtpSocketDataProvider::PRE_QUIT,
- "500 failed type\r\n",
- ERR_FAILED);
+ "599 fail\r\n",
+ ERR_FTP_FAILED);
}
-TEST_F(FtpNetworkTransactionTest, DownloadTransactionFailPasv) {
+TEST_F(FtpNetworkTransactionTest, DownloadTransactionFailEpsv) {
FtpSocketDataProviderFileDownload ctrl_socket;
TransactionFailHelper(&ctrl_socket,
"ftp://host/file",
- FtpSocketDataProvider::PRE_PASV,
- FtpSocketDataProvider::PRE_QUIT,
- "500 failed pasv\r\n",
- ERR_FAILED);
-}
-
-TEST_F(FtpNetworkTransactionTest, DownloadTransactionFailMdtm) {
- FtpSocketDataProviderFileDownload ctrl_socket;
- TransactionFailHelper(&ctrl_socket,
- "ftp://host/file",
- FtpSocketDataProvider::PRE_MDTM,
- FtpSocketDataProvider::PRE_RETR,
- "500 failed mdtm\r\n",
- OK);
+ FtpSocketDataProvider::PRE_EPSV,
+ FtpSocketDataProvider::PRE_NOPASV,
+ "599 fail\r\n",
+ ERR_FTP_FAILED);
}
TEST_F(FtpNetworkTransactionTest, DownloadTransactionFailRetr) {
- FtpSocketDataProviderFileDownloadRetrFail ctrl_socket;
+ FtpSocketDataProviderFileDownload ctrl_socket;
TransactionFailHelper(&ctrl_socket,
"ftp://host/file",
FtpSocketDataProvider::PRE_RETR,
FtpSocketDataProvider::PRE_QUIT,
- "500 failed retr\r\n",
- ERR_FAILED);
+ "599 fail\r\n",
+ ERR_FTP_FAILED);
}
TEST_F(FtpNetworkTransactionTest, DownloadTransactionFileNotFound) {
- FtpSocketDataProviderFileDownloadRetrFail ctrl_socket;
+ FtpSocketDataProviderFileDownload ctrl_socket;
+ TransactionFailHelper(&ctrl_socket,
+ "ftp://host/file;type=i",
+ FtpSocketDataProvider::PRE_SIZE,
+ FtpSocketDataProvider::PRE_QUIT,
+ "550 File Not Found\r\n",
+ ERR_FTP_FAILED);
+}
+
+// Test for http://crbug.com/38845.
+TEST_F(FtpNetworkTransactionTest, ZeroLengthDirInPWD) {
+ FtpSocketDataProviderFileDownload ctrl_socket;
TransactionFailHelper(&ctrl_socket,
"ftp://host/file",
- FtpSocketDataProvider::PRE_RETR,
- FtpSocketDataProvider::PRE_PASV2,
- "550 cannot open file\r\n",
- ERR_FILE_NOT_FOUND);
+ FtpSocketDataProvider::PRE_PWD,
+ FtpSocketDataProvider::PRE_TYPE,
+ "257 \"\"\r\n",
+ OK);
}
} // namespace net
diff --git a/net/ftp/ftp_response_info.h b/net/ftp/ftp_response_info.h
index 9c94064..daec6c2 100644
--- a/net/ftp/ftp_response_info.h
+++ b/net/ftp/ftp_response_info.h
@@ -11,7 +11,10 @@
class FtpResponseInfo {
public:
- FtpResponseInfo() : needs_auth(false), is_directory_listing(false) {
+ FtpResponseInfo()
+ : needs_auth(false),
+ expected_content_size(-1),
+ is_directory_listing(false) {
}
// True if authentication failed and valid authentication credentials are
@@ -26,6 +29,10 @@
// responses, this time could be "far" in the past.
base::Time response_time;
+ // Expected content size, in bytes, as reported by SIZE command. Only valid
+ // for file downloads. -1 means unknown size.
+ int64 expected_content_size;
+
// True if the response data is of a directory listing.
bool is_directory_listing;
};
diff --git a/net/ftp/ftp_transaction.h b/net/ftp/ftp_transaction.h
index da7f73e..881a977 100644
--- a/net/ftp/ftp_transaction.h
+++ b/net/ftp/ftp_transaction.h
@@ -13,7 +13,7 @@
class FtpResponseInfo;
class FtpRequestInfo;
-class LoadLog;
+class BoundNetLog;
// Represents a single FTP transaction.
class FtpTransaction {
@@ -35,10 +35,10 @@
//
// NOTE: The transaction is not responsible for deleting the callback object.
//
- // Profiling information for the request is saved to |load_log| if non-NULL.
+ // Profiling information for the request is saved to |net_log| if non-NULL.
virtual int Start(const FtpRequestInfo* request_info,
CompletionCallback* callback,
- LoadLog* load_log) = 0;
+ const BoundNetLog& net_log) = 0;
// Restarts the FTP transaction with authentication credentials.
virtual int RestartWithAuth(const std::wstring& username,
diff --git a/net/ftp/ftp_util.cc b/net/ftp/ftp_util.cc
index e76adc0..79e6b71 100644
--- a/net/ftp/ftp_util.cc
+++ b/net/ftp/ftp_util.cc
@@ -10,6 +10,7 @@
#include "base/string_tokenizer.h"
#include "base/string_util.h"
#include "base/time.h"
+#include "base/utf_string_conversions.h"
// For examples of Unix<->VMS path conversions, see the unit test file. On VMS
// a path looks differently depending on whether it's a file or directory.
@@ -99,7 +100,7 @@
std::replace(result.begin(), result.end(), ']', '/');
// Make sure the result doesn't end with a slash.
- if (result[result.length() - 1] == '/')
+ if (result.length() && result[result.length() - 1] == '/')
result = result.substr(0, result.length() - 1);
return result;
@@ -146,7 +147,9 @@
// static
bool FtpUtil::LsDateListingToTime(const string16& month, const string16& day,
- const string16& rest, base::Time* time) {
+ const string16& rest,
+ const base::Time& current_time,
+ base::Time* result) {
base::Time::Exploded time_exploded = { 0 };
if (!ThreeLetterMonthToNumber(month, &time_exploded.month))
@@ -166,14 +169,23 @@
if (!StringToInt(rest.substr(3, 2), &time_exploded.minute))
return false;
- // Use current year.
- base::Time::Exploded now_exploded;
- base::Time::Now().LocalExplode(&now_exploded);
- time_exploded.year = now_exploded.year;
+ // Guess the year.
+ base::Time::Exploded current_exploded;
+ current_time.LocalExplode(¤t_exploded);
+
+ // If it's not possible for the parsed date to be in the current year,
+ // use the previous year.
+ if (time_exploded.month > current_exploded.month ||
+ (time_exploded.month == current_exploded.month &&
+ time_exploded.day_of_month > current_exploded.day_of_month)) {
+ time_exploded.year = current_exploded.year - 1;
+ } else {
+ time_exploded.year = current_exploded.year;
+ }
}
// We don't know the time zone of the listing, so just use local time.
- *time = base::Time::FromLocalExploded(time_exploded);
+ *result = base::Time::FromLocalExploded(time_exploded);
return true;
}
diff --git a/net/ftp/ftp_util.h b/net/ftp/ftp_util.h
index 5643606..37a51c2 100644
--- a/net/ftp/ftp_util.h
+++ b/net/ftp/ftp_util.h
@@ -32,12 +32,13 @@
// Convert a "ls -l" date listing to time. The listing comes in three columns.
// The first one contains month, the second one contains day of month.
- // The first one is either a time (and then the current year is assumed),
- // or is a year (and then we don't know the time).
+ // The first one is either a time (and then we guess the year based
+ // on |current_time|), or is a year (and then we don't know the time).
static bool LsDateListingToTime(const string16& month,
const string16& day,
const string16& rest,
- base::Time* time);
+ const base::Time& current_time,
+ base::Time* result);
// Skip |columns| columns from |text| (whitespace-delimited), and return the
// remaining part, without leading/trailing whitespace.
diff --git a/net/ftp/ftp_util_unittest.cc b/net/ftp/ftp_util_unittest.cc
index e929aed..20450c2 100644
--- a/net/ftp/ftp_util_unittest.cc
+++ b/net/ftp/ftp_util_unittest.cc
@@ -7,6 +7,7 @@
#include "base/basictypes.h"
#include "base/format_macros.h"
#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
#include "base/time.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -93,6 +94,7 @@
{ "[.a.b.c]", "a/b/c" },
{ "[.a.b.c]d", "a/b/c/d" },
{ "[.a.b.c.d]", "a/b/c/d" },
+ { "[.", "" },
};
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); i++) {
EXPECT_EQ(kTestCases[i].expected_output,
@@ -102,8 +104,9 @@
}
TEST(FtpUtilTest, LsDateListingToTime) {
- base::Time::Exploded now_exploded;
- base::Time::Now().LocalExplode(&now_exploded);
+ base::Time mock_current_time;
+ ASSERT_TRUE(base::Time::FromString(L"Tue, 15 Nov 1994 12:45:26 GMT",
+ &mock_current_time));
const struct {
// Input.
@@ -119,14 +122,22 @@
int expected_minute;
} kTestCases[] = {
{ "Nov", "01", "2007", 2007, 11, 1, 0, 0 },
- { "Jul", "25", "13:37", now_exploded.year, 7, 25, 13, 37 },
+ { "Jul", "25", "13:37", 1994, 7, 25, 13, 37 },
// Test date listings in German, we should support them for FTP servers
// giving localized listings.
{ "M\xc3\xa4r", "13", "2009", 2009, 3, 13, 0, 0 },
- { "Mai", "1", "10:10", now_exploded.year, 5, 1, 10, 10 },
- { "Okt", "14", "21:18", now_exploded.year, 10, 14, 21, 18 },
+ { "Mai", "1", "10:10", 1994, 5, 1, 10, 10 },
+ { "Okt", "14", "21:18", 1994, 10, 14, 21, 18 },
{ "Dez", "25", "2008", 2008, 12, 25, 0, 0 },
+
+ // Test current year detection.
+ { "Nov", "01", "12:00", 1994, 11, 1, 12, 0 },
+ { "Nov", "15", "12:00", 1994, 11, 15, 12, 0 },
+ { "Nov", "16", "12:00", 1993, 11, 16, 12, 0 },
+ { "Jan", "01", "08:30", 1994, 1, 1, 8, 30 },
+ { "Sep", "02", "09:00", 1994, 9, 2, 9, 0 },
+ { "Dec", "06", "21:00", 1993, 12, 6, 21, 0 },
};
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); i++) {
SCOPED_TRACE(StringPrintf("Test[%" PRIuS "]: %s %s %s", i,
@@ -136,7 +147,7 @@
base::Time time;
ASSERT_TRUE(net::FtpUtil::LsDateListingToTime(
UTF8ToUTF16(kTestCases[i].month), UTF8ToUTF16(kTestCases[i].day),
- UTF8ToUTF16(kTestCases[i].rest), &time));
+ UTF8ToUTF16(kTestCases[i].rest), mock_current_time, &time));
base::Time::Exploded time_exploded;
time.LocalExplode(&time_exploded);
diff --git a/net/hresolv.scons b/net/hresolv.scons
new file mode 100644
index 0000000..ce95aa0
--- /dev/null
+++ b/net/hresolv.scons
@@ -0,0 +1,449 @@
+# This file is generated; do not edit.
+
+import os
+
+Import("env")
+
+env = env.Clone(COMPONENT_NAME='net',
+ TARGET_NAME='hresolv')
+
+configurations = {
+ 'Release' : {
+ 'Append' : dict(
+ CCFLAGS = [
+ '-Werror',
+ '-pthread',
+ '-fno-exceptions',
+ '-Wall',
+ '-D_FILE_OFFSET_BITS=64',
+ '-fvisibility=hidden',
+ '-fno-strict-aliasing',
+ '-pthread',
+ '-D_REENTRANT',
+ '-I/usr/include/gtk-2.0',
+ '-I/usr/lib/gtk-2.0/include',
+ '-I/usr/include/atk-1.0',
+ '-I/usr/include/cairo',
+ '-I/usr/include/pango-1.0',
+ '-I/usr/include/gio-unix-2.0/',
+ '-I/usr/include/glib-2.0',
+ '-I/usr/lib/glib-2.0/include',
+ '-I/usr/include/pixman-1',
+ '-I/usr/include/freetype2',
+ '-I/usr/include/directfb',
+ '-I/usr/include/libpng12',
+ '-O2',
+ '-fno-ident',
+ '-fdata-sections',
+ '-ffunction-sections',
+ '-fno-asynchronous-unwind-tables'
+ ],
+ CPPDEFINES = [
+ '__STDC_FORMAT_MACROS',
+ 'CHROMIUM_BUILD',
+ 'ENABLE_GPU=1',
+ 'NDEBUG',
+ 'NVALGRIND'
+ ],
+ CPPPATH = [
+ env.Dir('$SRC_DIR/net/..')
+ ],
+ CXXFLAGS = [
+ '-fno-rtti',
+ '-fno-threadsafe-statics',
+ '-fvisibility-inlines-hidden'
+ ],
+ LINKFLAGS = [
+ '-pthread',
+ '-Wl,--gc-sections'
+ ],
+ LIBS = [
+ '-lrt',
+ '-lgtk-x11-2.0',
+ '-lgdk-x11-2.0',
+ '-latk-1.0',
+ '-lgio-2.0',
+ '-lpangoft2-1.0',
+ '-lgdk_pixbuf-2.0',
+ '-lm',
+ '-lpangocairo-1.0',
+ '-lcairo',
+ '-lpango-1.0',
+ '-lfreetype',
+ '-lfontconfig',
+ '-lgobject-2.0',
+ '-lgmodule-2.0',
+ '-lgthread-2.0',
+ '-lrt',
+ '-lglib-2.0',
+ '-lnss3',
+ '-lnssutil3',
+ '-lsmime3',
+ '-lplds4',
+ '-lplc4',
+ '-lnspr4',
+ '-lpthread',
+ '-ldl',
+ '-lz',
+ '-lrt',
+ '-lgconf-2',
+ '-lglib-2.0',
+ '-lgdk-x11-2.0',
+ '-lgdk_pixbuf-2.0',
+ '-lm',
+ '-lpangocairo-1.0',
+ '-lpango-1.0',
+ '-lcairo',
+ '-lgobject-2.0',
+ '-lgmodule-2.0',
+ '-lgthread-2.0',
+ '-lrt',
+ '-lglib-2.0',
+ 'libnet_base',
+ 'libbase',
+ 'libmodp_b64',
+ 'libssl',
+ 'libzlib',
+ 'libsymbolize',
+ 'libxdg_mime',
+ 'libevent',
+ 'libbase_i18n',
+ 'libicui18n',
+ 'libicuuc',
+ 'libicudata',
+ 'libgoogleurl',
+ 'libsdch'
+ ],
+ ),
+ 'FilterOut' : dict(
+ ),
+ 'Replace' : dict(
+ FLOCK_LDMODULE = ['flock', '$TOP_BUILDDIR/linker.lock', '$LDMODULE'],
+ FLOCK_LINK = ['flock', '$TOP_BUILDDIR/linker.lock', '$LINK'],
+ FLOCK_SHLINK = ['flock', '$TOP_BUILDDIR/linker.lock', '$SHLINK'],
+ IMPLICIT_COMMAND_DEPENDENCIES = '0',
+ LDMODULECOM = [['$FLOCK_LDMODULE',
+ '-o',
+ '$TARGET',
+ '$_LIBDIRFLAGS',
+ '$LDMODULEFLAGS',
+ '$SOURCES',
+ '-Wl,--start-group',
+ '$_LIBFLAGS',
+ '-Wl,--end-group']],
+ LIBPATH = ['$LIB_DIR'],
+ LINKCOM = [['$FLOCK_LINK',
+ '-o',
+ '$TARGET',
+ '$_LIBDIRFLAGS',
+ '$LINKFLAGS',
+ '$SOURCES',
+ '-Wl,--start-group',
+ '$_LIBFLAGS',
+ '-Wl,--end-group']],
+ SHLINKCOM = [['$FLOCK_SHLINK',
+ '-o',
+ '$TARGET',
+ '$_LIBDIRFLAGS',
+ '$SHLINKFLAGS',
+ '$SOURCES',
+ '-Wl,--start-group',
+ '$_LIBFLAGS',
+ '-Wl,--end-group']],
+ ),
+ 'ImportExternal' : [
+ 'AS',
+ 'CC',
+ 'CXX',
+ 'LINK',
+ ],
+ 'PropagateExternal' : [
+ 'AS',
+ 'CC',
+ 'CCACHE_DIR',
+ 'CXX',
+ 'DISTCC_DIR',
+ 'DISTCC_HOSTS',
+ 'HOME',
+ 'INCLUDE_SERVER_ARGS',
+ 'INCLUDE_SERVER_PORT',
+ 'LINK',
+ 'CHROME_BUILD_TYPE',
+ 'CHROMIUM_BUILD',
+ 'OFFICIAL_BUILD',
+ ],
+ },
+ 'Debug' : {
+ 'Append' : dict(
+ CCFLAGS = [
+ '-Werror',
+ '-pthread',
+ '-fno-exceptions',
+ '-Wall',
+ '-D_FILE_OFFSET_BITS=64',
+ '-fvisibility=hidden',
+ '-fno-strict-aliasing',
+ '-pthread',
+ '-D_REENTRANT',
+ '-I/usr/include/gtk-2.0',
+ '-I/usr/lib/gtk-2.0/include',
+ '-I/usr/include/atk-1.0',
+ '-I/usr/include/cairo',
+ '-I/usr/include/pango-1.0',
+ '-I/usr/include/gio-unix-2.0/',
+ '-I/usr/include/glib-2.0',
+ '-I/usr/lib/glib-2.0/include',
+ '-I/usr/include/pixman-1',
+ '-I/usr/include/freetype2',
+ '-I/usr/include/directfb',
+ '-I/usr/include/libpng12',
+ '-O0',
+ '-g'
+ ],
+ CPPDEFINES = [
+ '__STDC_FORMAT_MACROS',
+ 'CHROMIUM_BUILD',
+ 'ENABLE_GPU=1',
+ '_DEBUG'
+ ],
+ CPPPATH = [
+ env.Dir('$SRC_DIR/net/..')
+ ],
+ CXXFLAGS = [
+ '-fno-rtti',
+ '-fno-threadsafe-statics',
+ '-fvisibility-inlines-hidden'
+ ],
+ LINKFLAGS = [
+ '-pthread',
+ '-rdynamic'
+ ],
+ LIBS = [
+ '-lrt',
+ '-lgtk-x11-2.0',
+ '-lgdk-x11-2.0',
+ '-latk-1.0',
+ '-lgio-2.0',
+ '-lpangoft2-1.0',
+ '-lgdk_pixbuf-2.0',
+ '-lm',
+ '-lpangocairo-1.0',
+ '-lcairo',
+ '-lpango-1.0',
+ '-lfreetype',
+ '-lfontconfig',
+ '-lgobject-2.0',
+ '-lgmodule-2.0',
+ '-lgthread-2.0',
+ '-lrt',
+ '-lglib-2.0',
+ '-lnss3',
+ '-lnssutil3',
+ '-lsmime3',
+ '-lplds4',
+ '-lplc4',
+ '-lnspr4',
+ '-lpthread',
+ '-ldl',
+ '-lz',
+ '-lrt',
+ '-lgconf-2',
+ '-lglib-2.0',
+ '-lgdk-x11-2.0',
+ '-lgdk_pixbuf-2.0',
+ '-lm',
+ '-lpangocairo-1.0',
+ '-lpango-1.0',
+ '-lcairo',
+ '-lgobject-2.0',
+ '-lgmodule-2.0',
+ '-lgthread-2.0',
+ '-lrt',
+ '-lglib-2.0',
+ 'libnet_base',
+ 'libbase',
+ 'libmodp_b64',
+ 'libssl',
+ 'libzlib',
+ 'libsymbolize',
+ 'libxdg_mime',
+ 'libevent',
+ 'libbase_i18n',
+ 'libicui18n',
+ 'libicuuc',
+ 'libicudata',
+ 'libgoogleurl',
+ 'libsdch'
+ ],
+ ),
+ 'FilterOut' : dict(
+ ),
+ 'Replace' : dict(
+ FLOCK_LDMODULE = ['flock', '$TOP_BUILDDIR/linker.lock', '$LDMODULE'],
+ FLOCK_LINK = ['flock', '$TOP_BUILDDIR/linker.lock', '$LINK'],
+ FLOCK_SHLINK = ['flock', '$TOP_BUILDDIR/linker.lock', '$SHLINK'],
+ IMPLICIT_COMMAND_DEPENDENCIES = '0',
+ LDMODULECOM = [['$FLOCK_LDMODULE',
+ '-o',
+ '$TARGET',
+ '$_LIBDIRFLAGS',
+ '$LDMODULEFLAGS',
+ '$SOURCES',
+ '-Wl,--start-group',
+ '$_LIBFLAGS',
+ '-Wl,--end-group']],
+ LIBPATH = ['$LIB_DIR'],
+ LINKCOM = [['$FLOCK_LINK',
+ '-o',
+ '$TARGET',
+ '$_LIBDIRFLAGS',
+ '$LINKFLAGS',
+ '$SOURCES',
+ '-Wl,--start-group',
+ '$_LIBFLAGS',
+ '-Wl,--end-group']],
+ SHLINKCOM = [['$FLOCK_SHLINK',
+ '-o',
+ '$TARGET',
+ '$_LIBDIRFLAGS',
+ '$SHLINKFLAGS',
+ '$SOURCES',
+ '-Wl,--start-group',
+ '$_LIBFLAGS',
+ '-Wl,--end-group']],
+ ),
+ 'ImportExternal' : [
+ 'AS',
+ 'CC',
+ 'CXX',
+ 'LINK',
+ ],
+ 'PropagateExternal' : [
+ 'AS',
+ 'CC',
+ 'CCACHE_DIR',
+ 'CXX',
+ 'DISTCC_DIR',
+ 'DISTCC_HOSTS',
+ 'HOME',
+ 'INCLUDE_SERVER_ARGS',
+ 'INCLUDE_SERVER_PORT',
+ 'LINK',
+ 'CHROME_BUILD_TYPE',
+ 'CHROMIUM_BUILD',
+ 'OFFICIAL_BUILD',
+ ],
+ },
+}
+
+config = configurations[env['CONFIG_NAME']]
+env.Append(**config['Append'])
+env.FilterOut(**config['FilterOut'])
+env.Replace(**config['Replace'])
+
+# Scons forces -fPIC for SHCCFLAGS on some platforms.
+# Disable that so we can control it from cflags in gyp.
+# Note that Scons itself is inconsistent with its -fPIC
+# setting. SHCCFLAGS forces -fPIC, and SHCFLAGS does not.
+# This will make SHCCFLAGS consistent with SHCFLAGS.
+env['SHCCFLAGS'] = ['$CCFLAGS']
+
+for _var in config['ImportExternal']:
+ if _var in ARGUMENTS:
+ env[_var] = ARGUMENTS[_var]
+ elif _var in os.environ:
+ env[_var] = os.environ[_var]
+for _var in config['PropagateExternal']:
+ if _var in ARGUMENTS:
+ env[_var] = ARGUMENTS[_var]
+ elif _var in os.environ:
+ env['ENV'][_var] = os.environ[_var]
+
+env['ENV']['LD_LIBRARY_PATH'] = env.subst('$LIB_DIR')
+
+if ARGUMENTS.get('COVERAGE') not in (None, '0'):
+ env.AppendUnique(
+ CCFLAGS = [
+ '-fprofile-arcs',
+ '-ftest-coverage'
+ ],
+ LINKFLAGS = [
+ '-fprofile-arcs'
+ ],
+ )
+
+if ARGUMENTS.get('PROFILE') not in (None, '0'):
+ env.AppendUnique(
+ CCFLAGS = [
+ '-pg',
+ '-g'
+ ],
+ LINKFLAGS = [
+ '-pg'
+ ],
+ )
+
+if ARGUMENTS.get('SYMBOLS') not in (None, '0'):
+ env.AppendUnique(
+ CCFLAGS = [
+ '-g'
+ ],
+ )
+
+input_files = [
+ 'tools/hresolv/hresolv.cc',
+]
+
+target_files = []
+prerequisites = []
+
+_result = []
+for infile in input_files:
+ if env.compilable(infile):
+ if (type(infile) == type('')
+ and (infile.startswith('$SRC_DIR/net/')
+ or not os.path.isabs(env.subst(infile)))):
+ # Force files below the build directory by replacing all '..'
+ # elements in the path with '__':
+ base, ext = os.path.splitext(os.path.normpath(infile))
+ base = [d == '..' and '__' or d for d in base.split('/')]
+ base = os.path.join(*base)
+ object = '${OBJ_DIR}/${COMPONENT_NAME}/${TARGET_NAME}/' + base
+ if not infile.startswith('$SRC_DIR/net/'):
+ infile = '$SRC_DIR/net/' + infile
+ infile = env.StaticObject(object, infile)[0]
+ else:
+ infile = env.StaticObject(infile)[0]
+ _result.append(infile)
+input_files = _result
+
+_outputs = env.GypProgram(env.File('${TOP_BUILDDIR}/${PROGPREFIX}hresolv${PROGSUFFIX}'), input_files)
+target_files.extend(_outputs)
+
+gyp_target = env.Alias('hresolv', target_files)
+dependencies = [
+ Alias('net_base'),
+ Alias('base'),
+ Alias('modp_b64'),
+ Alias('gtk'),
+ Alias('nss'),
+ Alias('ssl'),
+ Alias('zlib'),
+ Alias('symbolize'),
+ Alias('xdg_mime'),
+ Alias('libevent'),
+ Alias('base_i18n'),
+ Alias('icui18n'),
+ Alias('icuuc'),
+ Alias('icudata'),
+ Alias('googleurl'),
+ Alias('sdch'),
+ Alias('gconf'),
+ Alias('gdk')
+]
+env.Requires(target_files, dependencies)
+env.Requires(gyp_target, dependencies)
+for prerequisite in prerequisites:
+ env.Requires(prerequisite, dependencies)
+env.Requires(gyp_target, prerequisites)
+Return("gyp_target")
diff --git a/net/hresolv.target.mk b/net/hresolv.target.mk
new file mode 100644
index 0000000..95b02a8
--- /dev/null
+++ b/net/hresolv.target.mk
@@ -0,0 +1,183 @@
+# This file is generated by gyp; do not edit.
+
+TOOLSET := target
+TARGET := hresolv
+DEFS_Debug := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-D__STDC_FORMAT_MACROS' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=1' \
+ '-D_DEBUG'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Debug := -Werror \
+ -pthread \
+ -fno-exceptions \
+ -Wall \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/atk-1.0 \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -O0 \
+ -g
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Debug :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Debug := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Debug := -I.
+
+DEFS_Release := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-D__STDC_FORMAT_MACROS' \
+ '-DNDEBUG' \
+ '-DNVALGRIND' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=0'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Release := -Werror \
+ -pthread \
+ -fno-exceptions \
+ -Wall \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/atk-1.0 \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -O2 \
+ -fno-ident \
+ -fdata-sections \
+ -ffunction-sections
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Release :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Release := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Release := -I.
+
+OBJS := $(obj).target/$(TARGET)/net/tools/hresolv/hresolv.o
+
+# Add to the list of files we specially track dependencies for.
+all_deps += $(OBJS)
+
+# Make sure our dependencies are built before any of us.
+$(OBJS): | $(obj).target/net/libnet_base.a $(obj).target/base/libbase.a $(obj).target/third_party/modp_b64/libmodp_b64.a $(obj).target/base/third_party/dynamic_annotations/libdynamic_annotations.a $(obj).target/base/libsymbolize.a $(obj).target/net/third_party/nss/libssl.a $(obj).target/third_party/zlib/libzlib.a $(obj).target/base/libxdg_mime.a $(obj).target/base/allocator/liballocator.a $(obj).target/third_party/libevent/libevent.a $(obj).target/base/libbase_i18n.a $(obj).target/third_party/icu/libicui18n.a $(obj).target/third_party/icu/libicuuc.a $(obj).target/third_party/icu/libicudata.a $(obj).target/build/temp_gyp/libgoogleurl.a $(obj).target/sdch/libsdch.a
+
+# CFLAGS et al overrides must be target-local.
+# See "Target-specific Variable Values" in the GNU Make manual.
+$(OBJS): TOOLSET := $(TOOLSET)
+$(OBJS): GYP_CFLAGS := $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_C_$(BUILDTYPE)) $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE))
+$(OBJS): GYP_CXXFLAGS := $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_CC_$(BUILDTYPE)) $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE))
+
+# Suffix rules, putting all outputs into $(obj).
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(srcdir)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+# Try building from generated source, too.
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj).$(TOOLSET)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+# End of this set of suffix rules
+### Rules for final target.
+LDFLAGS_Debug := -pthread \
+ -Wl,-z,noexecstack \
+ -Wl,-uIsHeapProfilerRunning,-uProfilerStart \
+ -Wl,-u_Z21InitialMallocHook_NewPKvj,-u_Z22InitialMallocHook_MMapPKvS0_jiiix,-u_Z22InitialMallocHook_SbrkPKvi \
+ -Wl,-u_Z21InitialMallocHook_NewPKvm,-u_Z22InitialMallocHook_MMapPKvS0_miiil,-u_Z22InitialMallocHook_SbrkPKvl \
+ -rdynamic
+
+LDFLAGS_Release := -pthread \
+ -Wl,-z,noexecstack \
+ -Wl,-uIsHeapProfilerRunning,-uProfilerStart \
+ -Wl,-u_Z21InitialMallocHook_NewPKvj,-u_Z22InitialMallocHook_MMapPKvS0_jiiix,-u_Z22InitialMallocHook_SbrkPKvi \
+ -Wl,-u_Z21InitialMallocHook_NewPKvm,-u_Z22InitialMallocHook_MMapPKvS0_miiil,-u_Z22InitialMallocHook_SbrkPKvl \
+ -Wl,--gc-sections
+
+LIBS := -lrt \
+ -ldl \
+ -lgtk-x11-2.0 \
+ -lgdk-x11-2.0 \
+ -latk-1.0 \
+ -lgio-2.0 \
+ -lpangoft2-1.0 \
+ -lgdk_pixbuf-2.0 \
+ -lm \
+ -lpangocairo-1.0 \
+ -lcairo \
+ -lpango-1.0 \
+ -lfreetype \
+ -lfontconfig \
+ -lgobject-2.0 \
+ -lgmodule-2.0 \
+ -lgthread-2.0 \
+ -lglib-2.0 \
+ -lnss3 \
+ -lnssutil3 \
+ -lsmime3 \
+ -lplds4 \
+ -lplc4 \
+ -lnspr4 \
+ -lpthread \
+ -lz \
+ -lgconf-2
+
+$(builddir)/hresolv: GYP_LDFLAGS := $(LDFLAGS_$(BUILDTYPE))
+$(builddir)/hresolv: LIBS := $(LIBS)
+$(builddir)/hresolv: TOOLSET := $(TOOLSET)
+$(builddir)/hresolv: $(OBJS) $(obj).target/net/libnet_base.a $(obj).target/base/libbase.a $(obj).target/third_party/modp_b64/libmodp_b64.a $(obj).target/base/third_party/dynamic_annotations/libdynamic_annotations.a $(obj).target/base/libsymbolize.a $(obj).target/net/third_party/nss/libssl.a $(obj).target/third_party/zlib/libzlib.a $(obj).target/base/libxdg_mime.a $(obj).target/base/allocator/liballocator.a $(obj).target/third_party/libevent/libevent.a $(obj).target/base/libbase_i18n.a $(obj).target/third_party/icu/libicui18n.a $(obj).target/third_party/icu/libicuuc.a $(obj).target/third_party/icu/libicudata.a $(obj).target/build/temp_gyp/libgoogleurl.a $(obj).target/sdch/libsdch.a FORCE_DO_CMD
+ $(call do_cmd,link)
+
+all_deps += $(builddir)/hresolv
+# Add target alias
+.PHONY: hresolv
+hresolv: $(builddir)/hresolv
+
+# Add executable to "all" target.
+.PHONY: all
+all: $(builddir)/hresolv
+
diff --git a/net/http/http_alternate_protocols.cc b/net/http/http_alternate_protocols.cc
new file mode 100644
index 0000000..cd42ed5
--- /dev/null
+++ b/net/http/http_alternate_protocols.cc
@@ -0,0 +1,89 @@
+// 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 "net/http/http_alternate_protocols.h"
+
+#include "base/logging.h"
+#include "base/stl_util-inl.h"
+
+namespace net {
+
+const char HttpAlternateProtocols::kHeader[] = "Alternate-Protocol";
+const char* const HttpAlternateProtocols::kProtocolStrings[] = {
+ "npn-spdy/1",
+};
+
+HttpAlternateProtocols::HttpAlternateProtocols() {}
+HttpAlternateProtocols::~HttpAlternateProtocols() {}
+
+bool HttpAlternateProtocols::HasAlternateProtocolFor(
+ const HostPortPair& http_host_port_pair) const {
+ return ContainsKey(protocol_map_, http_host_port_pair);
+}
+
+bool HttpAlternateProtocols::HasAlternateProtocolFor(
+ const std::string& host, uint16 port) const {
+ struct HostPortPair http_host_port_pair;
+ http_host_port_pair.host = host;
+ http_host_port_pair.port = port;
+ return HasAlternateProtocolFor(http_host_port_pair);
+}
+
+HttpAlternateProtocols::PortProtocolPair
+HttpAlternateProtocols::GetAlternateProtocolFor(
+ const HostPortPair& http_host_port_pair) const {
+ DCHECK(ContainsKey(protocol_map_, http_host_port_pair));
+ return protocol_map_.find(http_host_port_pair)->second;
+}
+
+HttpAlternateProtocols::PortProtocolPair
+HttpAlternateProtocols::GetAlternateProtocolFor(
+ const std::string& host, uint16 port) const {
+ struct HostPortPair http_host_port_pair;
+ http_host_port_pair.host = host;
+ http_host_port_pair.port = port;
+ return GetAlternateProtocolFor(http_host_port_pair);
+}
+
+void HttpAlternateProtocols::SetAlternateProtocolFor(
+ const HostPortPair& http_host_port_pair,
+ uint16 alternate_port,
+ Protocol alternate_protocol) {
+ if (alternate_protocol == BROKEN) {
+ LOG(DFATAL) << "Call MarkBrokenAlternateProtocolFor() instead.";
+ return;
+ }
+
+ PortProtocolPair alternate;
+ alternate.port = alternate_port;
+ alternate.protocol = alternate_protocol;
+ if (HasAlternateProtocolFor(http_host_port_pair)) {
+ const PortProtocolPair existing_alternate =
+ GetAlternateProtocolFor(http_host_port_pair);
+
+ if (existing_alternate.protocol == BROKEN) {
+ DLOG(INFO) << "Ignore alternate protocol since it's known to be broken.";
+ return;
+ }
+
+ if (alternate_protocol != BROKEN && !existing_alternate.Equals(alternate)) {
+ LOG(WARNING) << "Changing the alternate protocol for: "
+ << http_host_port_pair.ToString()
+ << " from [Port: " << existing_alternate.port
+ << ", Protocol: " << existing_alternate.protocol
+ << "] to [Port: " << alternate_port
+ << ", Protocol: " << alternate_protocol
+ << "].";
+ }
+ }
+
+ protocol_map_[http_host_port_pair] = alternate;
+}
+
+void HttpAlternateProtocols::MarkBrokenAlternateProtocolFor(
+ const HostPortPair& http_host_port_pair) {
+ protocol_map_[http_host_port_pair].protocol = BROKEN;
+}
+
+} // namespace net
diff --git a/net/http/http_alternate_protocols.h b/net/http/http_alternate_protocols.h
new file mode 100644
index 0000000..56fb49a
--- /dev/null
+++ b/net/http/http_alternate_protocols.h
@@ -0,0 +1,72 @@
+// 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.
+//
+// HttpAlternateProtocols is an in-memory data structure used for keeping track
+// of which HTTP HostPortPairs have an alternate protocol that can be used
+// instead of HTTP on a different port.
+
+#ifndef NET_HTTP_HTTP_ALTERNATE_PROTOCOLS_H_
+#define NET_HTTP_HTTP_ALTERNATE_PROTOCOLS_H_
+
+#include <map>
+#include <utility>
+#include "base/basictypes.h"
+#include "net/base/host_port_pair.h"
+
+namespace net {
+
+class HttpAlternateProtocols {
+ public:
+ enum Protocol {
+ NPN_SPDY_1,
+ NUM_ALTERNATE_PROTOCOLS,
+ BROKEN, // The alternate protocol is known to be broken.
+ };
+
+ struct PortProtocolPair {
+ bool Equals(const PortProtocolPair& other) const {
+ return port == other.port && protocol == other.protocol;
+ }
+
+ uint16 port;
+ Protocol protocol;
+ };
+
+ static const char kHeader[];
+ static const char* const kProtocolStrings[NUM_ALTERNATE_PROTOCOLS];
+
+ HttpAlternateProtocols();
+ ~HttpAlternateProtocols();
+
+ // Reports whether or not we have received Alternate-Protocol for
+ // |http_host_port_pair|.
+ bool HasAlternateProtocolFor(const HostPortPair& http_host_port_pair) const;
+ bool HasAlternateProtocolFor(const std::string& host, uint16 port) const;
+
+ PortProtocolPair GetAlternateProtocolFor(
+ const HostPortPair& http_host_port_pair) const;
+ PortProtocolPair GetAlternateProtocolFor(
+ const std::string& host, uint16 port) const;
+
+ // SetAlternateProtocolFor() will ignore the request if the alternate protocol
+ // has already been marked broken via MarkBrokenAlternateProtocolFor().
+ void SetAlternateProtocolFor(const HostPortPair& http_host_port_pair,
+ uint16 alternate_port,
+ Protocol alternate_protocol);
+
+ // Marks the alternate protocol as broken. Once marked broken, any further
+ // attempts to set the alternate protocol for |http_host_port_pair| will fail.
+ void MarkBrokenAlternateProtocolFor(const HostPortPair& http_host_port_pair);
+
+ private:
+ typedef std::map<HostPortPair, PortProtocolPair> ProtocolMap;
+
+ ProtocolMap protocol_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpAlternateProtocols);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_ALTERNATE_PROTOCOLS_H_
diff --git a/net/http/http_alternate_protocols_unittest.cc b/net/http/http_alternate_protocols_unittest.cc
new file mode 100644
index 0000000..d82f850
--- /dev/null
+++ b/net/http/http_alternate_protocols_unittest.cc
@@ -0,0 +1,52 @@
+// 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.
+//
+// HttpAlternateProtocols is an in-memory data structure used for keeping track
+// of which HTTP HostPortPairs have an alternate protocol that can be used
+// instead of HTTP on a different port.
+
+#include "net/http/http_alternate_protocols.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace {
+
+TEST(HttpAlternateProtocols, Basic) {
+ HttpAlternateProtocols alternate_protocols;
+ HostPortPair test_host_port_pair;
+ test_host_port_pair.host = "foo";
+ test_host_port_pair.port = 80;
+ EXPECT_FALSE(
+ alternate_protocols.HasAlternateProtocolFor(test_host_port_pair));
+ alternate_protocols.SetAlternateProtocolFor(
+ test_host_port_pair, 443, HttpAlternateProtocols::NPN_SPDY_1);
+ ASSERT_TRUE(alternate_protocols.HasAlternateProtocolFor(test_host_port_pair));
+ const HttpAlternateProtocols::PortProtocolPair alternate =
+ alternate_protocols.GetAlternateProtocolFor(test_host_port_pair);
+ EXPECT_EQ(443, alternate.port);
+ EXPECT_EQ(HttpAlternateProtocols::NPN_SPDY_1, alternate.protocol);
+}
+
+TEST(HttpAlternateProtocols, SetBroken) {
+ HttpAlternateProtocols alternate_protocols;
+ HostPortPair test_host_port_pair;
+ test_host_port_pair.host = "foo";
+ test_host_port_pair.port = 80;
+ alternate_protocols.MarkBrokenAlternateProtocolFor(test_host_port_pair);
+ ASSERT_TRUE(alternate_protocols.HasAlternateProtocolFor(test_host_port_pair));
+ HttpAlternateProtocols::PortProtocolPair alternate =
+ alternate_protocols.GetAlternateProtocolFor(test_host_port_pair);
+ EXPECT_EQ(HttpAlternateProtocols::BROKEN, alternate.protocol);
+
+ alternate_protocols.SetAlternateProtocolFor(
+ test_host_port_pair,
+ 1234,
+ HttpAlternateProtocols::NPN_SPDY_1);
+ alternate = alternate_protocols.GetAlternateProtocolFor(test_host_port_pair);
+ EXPECT_EQ(HttpAlternateProtocols::BROKEN, alternate.protocol)
+ << "Second attempt should be ignored.";
+}
+
+} // namespace
+} // namespace net
diff --git a/net/http/http_auth.cc b/net/http/http_auth.cc
index aa97640..ff2ac4f 100644
--- a/net/http/http_auth.cc
+++ b/net/http/http_auth.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -8,6 +8,7 @@
#include "base/basictypes.h"
#include "base/string_util.h"
+#include "net/base/net_errors.h"
#include "net/http/http_auth_handler_basic.h"
#include "net/http/http_auth_handler_digest.h"
#include "net/http/http_auth_handler_negotiate.h"
@@ -18,69 +19,50 @@
namespace net {
// static
-void HttpAuth::ChooseBestChallenge(const HttpResponseHeaders* headers,
- Target target,
- const GURL& origin,
- scoped_refptr<HttpAuthHandler>* handler) {
+void HttpAuth::ChooseBestChallenge(
+ HttpAuthHandlerFactory* http_auth_handler_factory,
+ const HttpResponseHeaders* headers,
+ Target target,
+ const GURL& origin,
+ const std::set<std::string>& disabled_schemes,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) {
+ DCHECK(http_auth_handler_factory);
+
// A connection-based authentication scheme must continue to use the
// existing handler object in |*handler|.
- if (*handler && (*handler)->is_connection_based()) {
+ if (handler->get() && (*handler)->is_connection_based()) {
const std::string header_name = GetChallengeHeaderName(target);
std::string challenge;
void* iter = NULL;
while (headers->EnumerateHeader(&iter, header_name, &challenge)) {
ChallengeTokenizer props(challenge.begin(), challenge.end());
if (LowerCaseEqualsASCII(props.scheme(), (*handler)->scheme().c_str()) &&
- (*handler)->InitFromChallenge(challenge.begin(), challenge.end(),
- target, origin))
+ (*handler)->InitFromChallenge(&props, target, origin, net_log))
return;
}
}
// Choose the challenge whose authentication handler gives the maximum score.
- scoped_refptr<HttpAuthHandler> best;
+ scoped_ptr<HttpAuthHandler> best;
const std::string header_name = GetChallengeHeaderName(target);
std::string cur_challenge;
void* iter = NULL;
while (headers->EnumerateHeader(&iter, header_name, &cur_challenge)) {
- scoped_refptr<HttpAuthHandler> cur;
- CreateAuthHandler(cur_challenge, target, origin, &cur);
- if (cur && (!best || best->score() < cur->score()))
- best.swap(cur);
- }
- handler->swap(best);
-}
-
-// static
-void HttpAuth::CreateAuthHandler(const std::string& challenge,
- Target target,
- const GURL& origin,
- scoped_refptr<HttpAuthHandler>* handler) {
- // Find the right auth handler for the challenge's scheme.
- ChallengeTokenizer props(challenge.begin(), challenge.end());
- if (!props.valid()) {
- *handler = NULL;
- return;
- }
-
- scoped_refptr<HttpAuthHandler> tmp_handler;
- if (LowerCaseEqualsASCII(props.scheme(), "basic")) {
- tmp_handler = new HttpAuthHandlerBasic();
- } else if (LowerCaseEqualsASCII(props.scheme(), "digest")) {
- tmp_handler = new HttpAuthHandlerDigest();
- } else if (LowerCaseEqualsASCII(props.scheme(), "negotiate")) {
- tmp_handler = new HttpAuthHandlerNegotiate();
- } else if (LowerCaseEqualsASCII(props.scheme(), "ntlm")) {
- tmp_handler = new HttpAuthHandlerNTLM();
- }
- if (tmp_handler) {
- if (!tmp_handler->InitFromChallenge(challenge.begin(), challenge.end(),
- target, origin)) {
- // Invalid/unsupported challenge.
- tmp_handler = NULL;
+ scoped_ptr<HttpAuthHandler> cur;
+ int rv = http_auth_handler_factory->CreateAuthHandlerFromString(
+ cur_challenge, target, origin, net_log, &cur);
+ if (rv != OK) {
+ LOG(WARNING) << "Unable to create AuthHandler. Status: "
+ << ErrorToString(rv) << " Challenge: " << cur_challenge;
+ continue;
+ }
+ if (cur.get() && (!best.get() || best->score() < cur->score())) {
+ if (disabled_schemes.find(cur->scheme()) == disabled_schemes.end())
+ best.swap(cur);
}
}
- handler->swap(tmp_handler);
+ handler->swap(best);
}
void HttpAuth::ChallengeTokenizer::Init(std::string::const_iterator begin,
@@ -106,6 +88,9 @@
// name="value"
// name=value
// name=
+// Due to buggy implementations found in some embedded devices, we also
+// accept values with missing close quotemark (http://crbug.com/39836):
+// name="value
bool HttpAuth::ChallengeTokenizer::GetNext() {
if (!props_.GetNext())
return false;
@@ -115,6 +100,21 @@
value_end_ = props_.value_end();
name_begin_ = name_end_ = value_end_;
+ if (expect_base64_token_) {
+ expect_base64_token_ = false;
+ // Strip off any padding.
+ // (See https://bugzilla.mozilla.org/show_bug.cgi?id=230351.)
+ //
+ // Our base64 decoder requires that the length be a multiple of 4.
+ int encoded_length = value_end_ - value_begin_;
+ while (encoded_length > 0 && encoded_length % 4 != 0 &&
+ value_begin_[encoded_length - 1] == '=') {
+ --encoded_length;
+ --value_end_;
+ }
+ return true;
+ }
+
// Scan for the equals sign.
std::string::const_iterator equals = std::find(value_begin_, value_end_, '=');
if (equals == value_end_ || equals == value_begin_)
@@ -130,13 +130,13 @@
name_end_ = equals;
value_begin_ = equals + 1;
+ value_is_quoted_ = false;
if (value_begin_ != value_end_ && HttpUtil::IsQuote(*value_begin_)) {
// Trim surrounding quotemarks off the value
- if (*value_begin_ != *(value_end_ - 1))
- return valid_ = false; // Malformed -- mismatching quotes.
- value_is_quoted_ = true;
- } else {
- value_is_quoted_ = false;
+ if (*value_begin_ != *(value_end_ - 1) || value_begin_ + 1 == value_end_)
+ value_begin_ = equals + 2; // Gracefully recover from mismatching quotes.
+ else
+ value_is_quoted_ = true;
}
return true;
}
@@ -172,4 +172,10 @@
}
}
+// static
+std::string HttpAuth::GetAuthTargetString(
+ HttpAuth::Target target) {
+ return target == HttpAuth::AUTH_PROXY ? "proxy" : "server";
+}
+
} // namespace net
diff --git a/net/http/http_auth.h b/net/http/http_auth.h
index 38c9918..01afcc0 100644
--- a/net/http/http_auth.h
+++ b/net/http/http_auth.h
@@ -1,66 +1,76 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
#ifndef NET_HTTP_HTTP_AUTH_H_
#define NET_HTTP_HTTP_AUTH_H_
+#include <set>
+
+#include "base/scoped_ptr.h"
#include "net/http/http_util.h"
template <class T> class scoped_refptr;
namespace net {
+class BoundNetLog;
class HttpAuthHandler;
+class HttpAuthHandlerFactory;
class HttpResponseHeaders;
// Utility class for http authentication.
class HttpAuth {
public:
- // Http authentication can be done the the proxy server, origin server,
- // or both. This enum tracks who the target is.
- enum Target {
- AUTH_NONE = -1,
- // We depend on the valid targets (!= AUTH_NONE) being usable as indexes
- // in an array, so start from 0.
- AUTH_PROXY = 0,
- AUTH_SERVER = 1,
- };
+ // Http authentication can be done the the proxy server, origin server,
+ // or both. This enum tracks who the target is.
+ enum Target {
+ AUTH_NONE = -1,
+ // We depend on the valid targets (!= AUTH_NONE) being usable as indexes
+ // in an array, so start from 0.
+ AUTH_PROXY = 0,
+ AUTH_SERVER = 1,
+ AUTH_NUM_TARGETS = 2,
+ };
- // Describes where the identity used for authentication came from.
- enum IdentitySource {
- // Came from nowhere -- the identity is not initialized.
- IDENT_SRC_NONE,
+ // Describes where the identity used for authentication came from.
+ enum IdentitySource {
+ // Came from nowhere -- the identity is not initialized.
+ IDENT_SRC_NONE,
- // The identity came from the auth cache, by doing a path-based
- // lookup (premptive authorization).
- IDENT_SRC_PATH_LOOKUP,
+ // The identity came from the auth cache, by doing a path-based
+ // lookup (premptive authorization).
+ IDENT_SRC_PATH_LOOKUP,
- // The identity was extracted from a URL of the form:
- // http://<username>:<password>@host:port
- IDENT_SRC_URL,
+ // The identity was extracted from a URL of the form:
+ // http://<username>:<password>@host:port
+ IDENT_SRC_URL,
- // The identity was retrieved from the auth cache, by doing a
- // realm lookup.
- IDENT_SRC_REALM_LOOKUP,
+ // The identity was retrieved from the auth cache, by doing a
+ // realm lookup.
+ IDENT_SRC_REALM_LOOKUP,
- // The identity was provided by RestartWithAuth -- it likely
- // came from a prompt (or maybe the password manager).
- IDENT_SRC_EXTERNAL,
- };
+ // The identity was provided by RestartWithAuth -- it likely
+ // came from a prompt (or maybe the password manager).
+ IDENT_SRC_EXTERNAL,
- // Helper structure used by HttpNetworkTransaction to track
- // the current identity being used for authorization.
- struct Identity {
- Identity() : source(IDENT_SRC_NONE), invalid(true) { }
+ // The identity used the default credentials for the computer,
+ // on schemes that support single sign-on.
+ IDENT_SRC_DEFAULT_CREDENTIALS,
+ };
- IdentitySource source;
- bool invalid;
- // TODO(wtc): |username| and |password| should be string16.
- std::wstring username;
- std::wstring password;
- };
+ // Helper structure used by HttpNetworkTransaction to track
+ // the current identity being used for authorization.
+ struct Identity {
+ Identity() : source(IDENT_SRC_NONE), invalid(true) { }
+
+ IdentitySource source;
+ bool invalid;
+ // TODO(wtc): |username| and |password| should be string16.
+ std::wstring username;
+ std::wstring password;
+ };
// Get the name of the header containing the auth challenge
// (either WWW-Authenticate or Proxy-Authenticate).
@@ -70,13 +80,9 @@
// (either Authorization or Proxy-Authorization).
static std::string GetAuthorizationHeaderName(Target target);
- // Create a handler to generate credentials for the challenge, and pass
- // it back in |*handler|. If the challenge is unsupported or invalid
- // |*handler| is set to NULL.
- static void CreateAuthHandler(const std::string& challenge,
- Target target,
- const GURL& origin,
- scoped_refptr<HttpAuthHandler>* handler);
+ // Returns a string representation of a Target value that can be used in log
+ // messages.
+ static std::string GetAuthTargetString(Target target);
// Iterate through the challenge headers, and pick the best one that
// we support. Obtains the implementation class for handling the challenge,
@@ -85,16 +91,22 @@
// |*handler| is unchanged. If no supported challenge was found, |*handler|
// is set to NULL.
//
+ // |disabled_schemes| is the set of schemes that we should not use.
+ //
// |origin| is used by the NTLM authentication scheme to construct the
// service principal name. It is ignored by other schemes.
//
// TODO(wtc): Continuing to use the existing handler in |*handler| (for
// NTLM) is new behavior. Rename ChooseBestChallenge to fully encompass
// what it does now.
- static void ChooseBestChallenge(const HttpResponseHeaders* headers,
- Target target,
- const GURL& origin,
- scoped_refptr<HttpAuthHandler>* handler);
+ static void ChooseBestChallenge(
+ HttpAuthHandlerFactory* http_auth_handler_factory,
+ const HttpResponseHeaders* headers,
+ Target target,
+ const GURL& origin,
+ const std::set<std::string>& disabled_schemes,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler);
// ChallengeTokenizer breaks up a challenge string into the the auth scheme
// and parameter list, according to RFC 2617 Sec 1.2:
@@ -107,10 +119,16 @@
public:
ChallengeTokenizer(std::string::const_iterator begin,
std::string::const_iterator end)
- : props_(begin, end, ','), valid_(true) {
+ : props_(begin, end, ','), valid_(true), begin_(begin), end_(end),
+ expect_base64_token_(false) {
Init(begin, end);
}
+ // Get the original text.
+ std::string challenge_text() const {
+ return std::string(begin_, end_);
+ }
+
// Get the auth scheme of the challenge.
std::string::const_iterator scheme_begin() const { return scheme_begin_; }
std::string::const_iterator scheme_end() const { return scheme_end_; }
@@ -127,6 +145,17 @@
// Returns true if there is none to consume.
bool GetNext();
+ // Inform the tokenizer whether the next token should be treated as a base64
+ // encoded value. If |expect_base64_token| is true, |GetNext| will treat the
+ // next token as a base64 encoded value, and will include the trailing '='
+ // padding rather than attempt to split the token into a name/value pair.
+ // In this case, |name| will be empty, and |value| will contain the token.
+ // Subsequent calls to |GetNext()| will not treat the token like a base64
+ // encoded token unless the caller again calls |set_expect_base64_token|.
+ void set_expect_base64_token(bool expect_base64_token) {
+ expect_base64_token_ = expect_base64_token;
+ }
+
// The name of the current name-value pair.
std::string::const_iterator name_begin() const { return name_begin_; }
std::string::const_iterator name_end() const { return name_end_; }
@@ -154,6 +183,9 @@
HttpUtil::ValuesIterator props_;
bool valid_;
+ std::string::const_iterator begin_;
+ std::string::const_iterator end_;
+
std::string::const_iterator scheme_begin_;
std::string::const_iterator scheme_end_;
@@ -164,6 +196,7 @@
std::string::const_iterator value_end_;
bool value_is_quoted_;
+ bool expect_base64_token_;
};
};
diff --git a/net/http/http_auth_cache.cc b/net/http/http_auth_cache.cc
index 3076a89..21db55d 100644
--- a/net/http/http_auth_cache.cc
+++ b/net/http/http_auth_cache.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -35,8 +35,8 @@
// |container| an ancestor of |path|?
bool IsEnclosingPath(const std::string& container, const std::string& path) {
DCHECK(container.empty() || *(container.end() - 1) == '/');
- return (container.empty() && path.empty()) ||
- (!container.empty() && StartsWithASCII(path, container, true));
+ return ((container.empty() && path.empty()) ||
+ (!container.empty() && StartsWithASCII(path, container, true)));
}
// Debug helper to check that |origin| arguments are properly formed.
@@ -60,13 +60,15 @@
namespace net {
// Performance: O(n), where n is the number of realm entries.
-HttpAuthCache::Entry* HttpAuthCache::LookupByRealm(const GURL& origin,
- const std::string& realm) {
+HttpAuthCache::Entry* HttpAuthCache::Lookup(const GURL& origin,
+ const std::string& realm,
+ const std::string& scheme) {
CheckOriginIsValid(origin);
// Linear scan through the realm entries.
for (EntryList::iterator it = entries_.begin(); it != entries_.end(); ++it) {
- if (it->origin() == origin && it->realm() == realm)
+ if (it->origin() == origin && it->realm() == realm &&
+ it->scheme() == scheme)
return &(*it);
}
return NULL; // No realm entry found.
@@ -95,7 +97,9 @@
}
HttpAuthCache::Entry* HttpAuthCache::Add(const GURL& origin,
- HttpAuthHandler* handler,
+ const std::string& realm,
+ const std::string& scheme,
+ const std::string& auth_challenge,
const std::wstring& username,
const std::wstring& password,
const std::string& path) {
@@ -103,8 +107,7 @@
CheckPathIsValid(path);
// Check for existing entry (we will re-use it if present).
- HttpAuthCache::Entry* entry = LookupByRealm(origin, handler->realm());
-
+ HttpAuthCache::Entry* entry = Lookup(origin, realm, scheme);
if (!entry) {
// Failsafe to prevent unbounded memory growth of the cache.
if (entries_.size() >= kMaxNumRealmEntries) {
@@ -115,11 +118,17 @@
entries_.push_front(Entry());
entry = &entries_.front();
entry->origin_ = origin;
+ entry->realm_ = realm;
+ entry->scheme_ = scheme;
}
+ DCHECK_EQ(origin, entry->origin_);
+ DCHECK_EQ(realm, entry->realm_);
+ DCHECK_EQ(scheme, entry->scheme_);
+ entry->auth_challenge_ = auth_challenge;
entry->username_ = username;
entry->password_ = password;
- entry->handler_ = handler;
+ entry->nonce_count_ = 1;
entry->AddPath(path);
return entry;
@@ -155,10 +164,12 @@
bool HttpAuthCache::Remove(const GURL& origin,
const std::string& realm,
+ const std::string& scheme,
const std::wstring& username,
const std::wstring& password) {
for (EntryList::iterator it = entries_.begin(); it != entries_.end(); ++it) {
- if (it->origin() == origin && it->realm() == realm) {
+ if (it->origin() == origin && it->realm() == realm &&
+ it->scheme() == scheme) {
if (username == it->username() && password == it->password()) {
entries_.erase(it);
return true;
diff --git a/net/http/http_auth_cache.h b/net/http/http_auth_cache.h
index 62c09e9..1d238af 100644
--- a/net/http/http_auth_cache.h
+++ b/net/http/http_auth_cache.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -10,34 +10,33 @@
#include "base/ref_counted.h"
#include "googleurl/src/gurl.h"
-#include "net/http/http_auth_handler.h"
// This is needed for the FRIEND_TEST() macro.
#include "testing/gtest/include/gtest/gtest_prod.h"
namespace net {
-// TODO(eroman): Can we change the key from (origin, realm) to
-// (origin, realm, auth_scheme)?
-
// HttpAuthCache stores HTTP authentication identities and challenge info.
-// For each realm the cache stores a HttpAuthCache::Entry, which holds:
-// - the realm name
-// - the origin server {scheme, host, port}
+// For each (origin, realm, scheme) triple the cache stores a
+// HttpAuthCache::Entry, which holds:
+// - the origin server {protocol scheme, host, port}
// - the last identity used (username/password)
-// - the last auth handler used
+// - the last auth handler used (contains realm and authentication scheme)
// - the list of paths which used this realm
-// Entries can be looked up by either (origin, realm) or (origin, path).
+// Entries can be looked up by either (origin, realm, scheme) or (origin, path).
class HttpAuthCache {
public:
class Entry;
- // Find the realm entry on server |origin| for realm |realm|.
+ // Find the realm entry on server |origin| for realm |realm| and
+ // scheme |scheme|.
// |origin| - the {scheme, host, port} of the server.
// |realm| - case sensitive realm string.
+ // |scheme| - case sensitive authentication scheme, should be lower-case.
// returns - the matched entry or NULL.
- Entry* LookupByRealm(const GURL& origin, const std::string& realm);
+ Entry* Lookup(const GURL& origin, const std::string& realm,
+ const std::string& scheme);
- // Find the realm entry on server |origin| whose protection space includes
+ // Find the entry on server |origin| whose protection space includes
// |path|. This uses the assumption in RFC 2617 section 2 that deeper
// paths lie in the same protection space.
// |origin| - the {scheme, host, port} of the server.
@@ -46,31 +45,37 @@
// returns - the matched entry or NULL.
Entry* LookupByPath(const GURL& origin, const std::string& path);
- // Add a realm entry on server |origin| for realm |handler->realm()|, If an
- // entry for this realm already exists, update it rather than replace it --
- // this preserves the realm's paths list.
+ // Add an entry on server |origin| for realm |handler->realm()| and
+ // scheme |handler->scheme()|. If an entry for this (realm,scheme)
+ // already exists, update it rather than replace it -- this preserves the
+ // paths list.
// |origin| - the {scheme, host, port} of the server.
- // |handler| - handler for the challenge.
+ // |realm| - the auth realm for the challenge.
+ // |scheme| - the authentication scheme for the challenge.
// |username| - login information for the realm.
// |password| - login information for the realm.
// |path| - absolute path for a resource contained in the protection
// space; this will be added to the list of known paths.
// returns - the entry that was just added/updated.
Entry* Add(const GURL& origin,
- HttpAuthHandler* handler,
+ const std::string& realm,
+ const std::string& scheme,
+ const std::string& auth_challenge,
const std::wstring& username,
const std::wstring& password,
const std::string& path);
- // Remove realm entry on server |origin| for realm |realm| if one exists
- // AND if the cached identity matches (|username|, |password|).
+ // Remove entry on server |origin| for realm |realm| and scheme |scheme|
+ // if one exists AND if the cached identity matches (|username|, |password|).
// |origin| - the {scheme, host, port} of the server.
// |realm| - case sensitive realm string.
+ // |scheme| - authentication scheme
// |username| - condition to match.
// |password| - condition to match.
// returns - true if an entry was removed.
bool Remove(const GURL& origin,
const std::string& realm,
+ const std::string& scheme,
const std::wstring& username,
const std::wstring& password);
@@ -95,24 +100,33 @@
// The case-sensitive realm string of the challenge.
const std::string realm() const {
- return handler_->realm();
+ return realm_;
}
- // The handler for the challenge.
- HttpAuthHandler* handler() const {
- return handler_.get();
+ // The authentication scheme string of the challenge
+ const std::string scheme() const {
+ return scheme_;
+ }
+
+ // The authentication challenge.
+ const std::string auth_challenge() const {
+ return auth_challenge_;
}
// The login username.
- const std::wstring& username() const {
+ const std::wstring username() const {
return username_;
}
// The login password.
- const std::wstring& password() const {
+ const std::wstring password() const {
return password_;
}
+ int IncrementNonceCount() {
+ return ++nonce_count_;
+ }
+
private:
friend class HttpAuthCache;
FRIEND_TEST(HttpAuthCacheTest, AddPath);
@@ -129,13 +143,15 @@
// |origin_| contains the {scheme, host, port} of the server.
GURL origin_;
+ std::string realm_;
+ std::string scheme_;
// Identity.
+ std::string auth_challenge_;
std::wstring username_;
std::wstring password_;
- // Auth handler for the challenge.
- scoped_refptr<HttpAuthHandler> handler_;
+ int nonce_count_;
// List of paths that define the realm's protection space.
typedef std::list<std::string> PathList;
diff --git a/net/http/http_auth_cache_unittest.cc b/net/http/http_auth_cache_unittest.cc
index 52fa5cd..4e2af9d 100644
--- a/net/http/http_auth_cache_unittest.cc
+++ b/net/http/http_auth_cache_unittest.cc
@@ -1,9 +1,11 @@
-// Copyright (c) 2008 The Chromium Authors. All rights reserved.
+// 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/string_util.h"
+#include "net/base/net_errors.h"
#include "net/http/http_auth_cache.h"
+#include "net/http/http_auth_handler.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -23,18 +25,21 @@
properties_ = 0;
}
- virtual std::string GenerateCredentials(const std::wstring&,
- const std::wstring&,
- const HttpRequestInfo*,
- const ProxyInfo*) {
- return "mock-credentials"; // Unused.
- }
-
protected:
- virtual bool Init(std::string::const_iterator, std::string::const_iterator) {
+ virtual bool Init(HttpAuth::ChallengeTokenizer* challenge) {
return false; // Unused.
}
+ virtual int GenerateAuthTokenImpl(const std::wstring*,
+ const std::wstring*,
+ const HttpRequestInfo*,
+ CompletionCallback* callback,
+ std::string* auth_token) {
+ *auth_token = "mock-credentials";
+ return OK;
+ }
+
+
private:
~MockAuthHandler() {}
};
@@ -49,68 +54,121 @@
// Add cache entries for 3 realms: "Realm1", "Realm2", "Realm3"
- scoped_refptr<HttpAuthHandler> realm1_handler =
- new MockAuthHandler("basic", "Realm1", HttpAuth::AUTH_SERVER);
- cache.Add(origin, realm1_handler, L"realm1-user", L"realm1-password",
- "/foo/bar/index.html");
+ scoped_ptr<HttpAuthHandler> realm1_handler(
+ new MockAuthHandler("basic", "Realm1", HttpAuth::AUTH_SERVER));
+ cache.Add(origin, realm1_handler->realm(), realm1_handler->scheme(),
+ "Basic realm=Realm1", L"realm1-user", L"realm1-password",
+ "/foo/bar/index.html");
- scoped_refptr<HttpAuthHandler> realm2_handler =
- new MockAuthHandler("basic", "Realm2", HttpAuth::AUTH_SERVER);
- cache.Add(origin, realm2_handler, L"realm2-user", L"realm2-password",
- "/foo2/index.html");
+ scoped_ptr<HttpAuthHandler> realm2_handler(
+ new MockAuthHandler("basic", "Realm2", HttpAuth::AUTH_SERVER));
+ cache.Add(origin, realm2_handler->realm(), realm2_handler->scheme(),
+ "Basic realm=Realm2", L"realm2-user", L"realm2-password",
+ "/foo2/index.html");
- scoped_refptr<HttpAuthHandler> realm3_handler =
- new MockAuthHandler("basic", "Realm3", HttpAuth::AUTH_PROXY);
- cache.Add(origin, realm3_handler, L"realm3-user", L"realm3-password", "");
+ scoped_ptr<HttpAuthHandler> realm3_basic_handler(
+ new MockAuthHandler("basic", "Realm3", HttpAuth::AUTH_PROXY));
+ cache.Add(origin, realm3_basic_handler->realm(),
+ realm3_basic_handler->scheme(), "Basic realm=Realm3",
+ L"realm3-basic-user", L"realm3-basic-password", "");
+
+ scoped_ptr<HttpAuthHandler> realm3_digest_handler(
+ new MockAuthHandler("digest", "Realm3", HttpAuth::AUTH_PROXY));
+ cache.Add(origin, realm3_digest_handler->realm(),
+ realm3_digest_handler->scheme(), "Digest realm=Realm3",
+ L"realm3-digest-user", L"realm3-digest-password",
+ "/baz/index.html");
// There is no Realm4
- entry = cache.LookupByRealm(origin, "Realm4");
+ entry = cache.Lookup(origin, "Realm4", "basic");
EXPECT_TRUE(NULL == entry);
// While Realm3 does exist, the origin scheme is wrong.
- entry = cache.LookupByRealm(GURL("https://www.google.com"), "Realm3");
+ entry = cache.Lookup(GURL("https://www.google.com"), "Realm3",
+ "basic");
EXPECT_TRUE(NULL == entry);
- // Valid lookup by realm.
- entry = cache.LookupByRealm(GURL("http://www.google.com:80"), "Realm3");
- EXPECT_FALSE(NULL == entry);
- EXPECT_TRUE(entry->handler() == realm3_handler.get());
- EXPECT_EQ(L"realm3-user", entry->username());
- EXPECT_EQ(L"realm3-password", entry->password());
+ // Realm, origin scheme ok, authentication scheme wrong
+ entry = cache.Lookup(GURL("http://www.google.com"), "Realm1", "digest");
+ EXPECT_TRUE(NULL == entry);
+
+ // Valid lookup by origin, realm, scheme.
+ entry = cache.Lookup(GURL("http://www.google.com:80"), "Realm3", "basic");
+ ASSERT_FALSE(NULL == entry);
+ EXPECT_EQ("basic", entry->scheme());
+ EXPECT_EQ("Realm3", entry->realm());
+ EXPECT_EQ("Basic realm=Realm3", entry->auth_challenge());
+ EXPECT_EQ(L"realm3-basic-user", entry->username());
+ EXPECT_EQ(L"realm3-basic-password", entry->password());
+
+ // Valid lookup by origin, realm, scheme when there's a duplicate
+ // origin, realm in the cache
+ entry = cache.Lookup(GURL("http://www.google.com:80"), "Realm3", "digest");
+ ASSERT_FALSE(NULL == entry);
+ EXPECT_EQ("digest", entry->scheme());
+ EXPECT_EQ("Realm3", entry->realm());
+ EXPECT_EQ("Digest realm=Realm3", entry->auth_challenge());
+ EXPECT_EQ(L"realm3-digest-user", entry->username());
+ EXPECT_EQ(L"realm3-digest-password", entry->password());
// Valid lookup by realm.
- entry = cache.LookupByRealm(origin, "Realm2");
- EXPECT_FALSE(NULL == entry);
- EXPECT_TRUE(entry->handler() == realm2_handler.get());
+ entry = cache.Lookup(origin, "Realm2", "basic");
+ ASSERT_FALSE(NULL == entry);
+ EXPECT_EQ("basic", entry->scheme());
+ EXPECT_EQ("Realm2", entry->realm());
+ EXPECT_EQ("Basic realm=Realm2", entry->auth_challenge());
EXPECT_EQ(L"realm2-user", entry->username());
EXPECT_EQ(L"realm2-password", entry->password());
// Check that subpaths are recognized.
- HttpAuthCache::Entry* realm2Entry = cache.LookupByRealm(origin, "Realm2");
- EXPECT_FALSE(NULL == realm2Entry);
+ HttpAuthCache::Entry* realm2_entry = cache.Lookup(origin, "Realm2", "basic");
+ EXPECT_FALSE(NULL == realm2_entry);
// Positive tests:
entry = cache.LookupByPath(origin, "/foo2/index.html");
- EXPECT_TRUE(realm2Entry == entry);
+ EXPECT_TRUE(realm2_entry == entry);
entry = cache.LookupByPath(origin, "/foo2/foobar.html");
- EXPECT_TRUE(realm2Entry == entry);
+ EXPECT_TRUE(realm2_entry == entry);
entry = cache.LookupByPath(origin, "/foo2/bar/index.html");
- EXPECT_TRUE(realm2Entry == entry);
+ EXPECT_TRUE(realm2_entry == entry);
entry = cache.LookupByPath(origin, "/foo2/");
- EXPECT_TRUE(realm2Entry == entry);
+ EXPECT_TRUE(realm2_entry == entry);
+
// Negative tests:
entry = cache.LookupByPath(origin, "/foo2");
- EXPECT_FALSE(realm2Entry == entry);
+ EXPECT_FALSE(realm2_entry == entry);
entry = cache.LookupByPath(origin, "/foo3/index.html");
- EXPECT_FALSE(realm2Entry == entry);
+ EXPECT_FALSE(realm2_entry == entry);
entry = cache.LookupByPath(origin, "");
- EXPECT_FALSE(realm2Entry == entry);
+ EXPECT_FALSE(realm2_entry == entry);
entry = cache.LookupByPath(origin, "/");
- EXPECT_FALSE(realm2Entry == entry);
+ EXPECT_FALSE(realm2_entry == entry);
+
+ // Confirm we find the same realm, different auth scheme by path lookup
+ HttpAuthCache::Entry* realm3_digest_entry =
+ cache.Lookup(origin, "Realm3", "digest");
+ EXPECT_FALSE(NULL == realm3_digest_entry);
+ entry = cache.LookupByPath(origin, "/baz/index.html");
+ EXPECT_TRUE(realm3_digest_entry == entry);
+ entry = cache.LookupByPath(origin, "/baz/");
+ EXPECT_TRUE(realm3_digest_entry == entry);
+ entry = cache.LookupByPath(origin, "/baz");
+ EXPECT_FALSE(realm3_digest_entry == entry);
+
+ // Confirm we find the same realm, different auth scheme by path lookup
+ HttpAuthCache::Entry* realm3DigestEntry =
+ cache.Lookup(origin, "Realm3", "digest");
+ EXPECT_FALSE(NULL == realm3DigestEntry);
+ entry = cache.LookupByPath(origin, "/baz/index.html");
+ EXPECT_TRUE(realm3DigestEntry == entry);
+ entry = cache.LookupByPath(origin, "/baz/");
+ EXPECT_TRUE(realm3DigestEntry == entry);
+ entry = cache.LookupByPath(origin, "/baz");
+ EXPECT_FALSE(realm3DigestEntry == entry);
// Lookup using empty path (may be used for proxy).
entry = cache.LookupByPath(origin, "");
EXPECT_FALSE(NULL == entry);
- EXPECT_TRUE(entry->handler() == realm3_handler.get());
+ EXPECT_EQ("basic", entry->scheme());
EXPECT_EQ("Realm3", entry->realm());
}
@@ -151,16 +209,20 @@
TEST(HttpAuthCacheTest, AddToExistingEntry) {
HttpAuthCache cache;
GURL origin("http://www.foobar.com:70");
+ const std::string auth_challenge = "Basic realm=MyRealm";
- scoped_refptr<HttpAuthHandler> handler =
- new MockAuthHandler("basic", "MyRealm", HttpAuth::AUTH_SERVER);
+ scoped_ptr<HttpAuthHandler> handler(
+ new MockAuthHandler("basic", "MyRealm", HttpAuth::AUTH_SERVER));
HttpAuthCache::Entry* orig_entry = cache.Add(
- origin, handler, L"user1", L"password1", "/x/y/z/");
- cache.Add(origin, handler, L"user2", L"password2", "/z/y/x/");
- cache.Add(origin, handler, L"user3", L"password3", "/z/y");
+ origin, handler->realm(), handler->scheme(), auth_challenge,
+ L"user1", L"password1", "/x/y/z/");
+ cache.Add(origin, handler->realm(), handler->scheme(), auth_challenge,
+ L"user2", L"password2", "/z/y/x/");
+ cache.Add(origin, handler->realm(), handler->scheme(), auth_challenge,
+ L"user3", L"password3", "/z/y");
- HttpAuthCache::Entry* entry = cache.LookupByRealm(origin, "MyRealm");
+ HttpAuthCache::Entry* entry = cache.Lookup(origin, "MyRealm", "basic");
EXPECT_TRUE(entry == orig_entry);
EXPECT_EQ(L"user3", entry->username());
@@ -174,38 +236,65 @@
TEST(HttpAuthCacheTest, Remove) {
GURL origin("http://foobar2.com");
- scoped_refptr<HttpAuthHandler> realm1_handler =
- new MockAuthHandler("basic", "Realm1", HttpAuth::AUTH_SERVER);
+ scoped_ptr<HttpAuthHandler> realm1_handler(
+ new MockAuthHandler("basic", "Realm1", HttpAuth::AUTH_SERVER));
- scoped_refptr<HttpAuthHandler> realm2_handler =
- new MockAuthHandler("basic", "Realm2", HttpAuth::AUTH_SERVER);
+ scoped_ptr<HttpAuthHandler> realm2_handler(
+ new MockAuthHandler("basic", "Realm2", HttpAuth::AUTH_SERVER));
- scoped_refptr<HttpAuthHandler> realm3_handler =
- new MockAuthHandler("basic", "Realm3", HttpAuth::AUTH_SERVER);
+ scoped_ptr<HttpAuthHandler> realm3_basic_handler(
+ new MockAuthHandler("basic", "Realm3", HttpAuth::AUTH_SERVER));
+
+ scoped_ptr<HttpAuthHandler> realm3_digest_handler(
+ new MockAuthHandler("digest", "Realm3", HttpAuth::AUTH_SERVER));
HttpAuthCache cache;
- cache.Add(origin, realm1_handler, L"alice", L"123", "/");
- cache.Add(origin, realm2_handler, L"bob", L"princess", "/");
- cache.Add(origin, realm3_handler, L"admin", L"password", "/");
+ cache.Add(origin, realm1_handler->realm(), realm1_handler->scheme(),
+ "basic realm=Realm1", L"alice", L"123", "/");
+ cache.Add(origin, realm2_handler->realm(), realm2_handler->scheme(),
+ "basic realm=Realm2", L"bob", L"princess", "/");
+ cache.Add(origin, realm3_basic_handler->realm(),
+ realm3_basic_handler->scheme(), "basic realm=Realm3", L"admin",
+ L"password", "/");
+ cache.Add(origin, realm3_digest_handler->realm(),
+ realm3_digest_handler->scheme(), "digest realm=Realm3", L"root",
+ L"wilecoyote", "/");
// Fails, because there is no realm "Realm4".
- EXPECT_FALSE(cache.Remove(origin, "Realm4", L"alice", L"123"));
+ EXPECT_FALSE(cache.Remove(origin, "Realm4", "basic", L"alice", L"123"));
// Fails because the origin is wrong.
EXPECT_FALSE(cache.Remove(
- GURL("http://foobar2.com:100"), "Realm1", L"alice", L"123"));
+ GURL("http://foobar2.com:100"), "Realm1", "basic", L"alice", L"123"));
// Fails because the username is wrong.
- EXPECT_FALSE(cache.Remove(origin, "Realm1", L"alice2", L"123"));
+ EXPECT_FALSE(cache.Remove(origin, "Realm1", "basic", L"alice2", L"123"));
// Fails because the password is wrong.
- EXPECT_FALSE(cache.Remove(origin, "Realm1", L"alice", L"1234"));
+ EXPECT_FALSE(cache.Remove(origin, "Realm1", "basic", L"alice", L"1234"));
+
+ // Fails because the authentication type is wrong.
+ EXPECT_FALSE(cache.Remove(origin, "Realm1", "digest", L"alice", L"123"));
// Succeeds.
- EXPECT_TRUE(cache.Remove(origin, "Realm1", L"alice", L"123"));
+ EXPECT_TRUE(cache.Remove(origin, "Realm1", "basic", L"alice", L"123"));
// Fails because we just deleted the entry!
- EXPECT_FALSE(cache.Remove(origin, "Realm1", L"alice", L"123"));
+ EXPECT_FALSE(cache.Remove(origin, "Realm1", "basic", L"alice", L"123"));
+
+ // Succeed when there are two authentication types for the same origin,realm.
+ EXPECT_TRUE(cache.Remove(origin, "Realm3", "digest", L"root", L"wilecoyote"));
+
+ // Succeed as above, but when entries were added in opposite order
+ cache.Add(origin, realm3_digest_handler->realm(),
+ realm3_digest_handler->scheme(), "digest realm=Realm3", L"root",
+ L"wilecoyote", "/");
+ EXPECT_TRUE(cache.Remove(origin, "Realm3", "basic", L"admin", L"password"));
+
+ // Make sure that removing one entry still leaves the other available for
+ // lookup.
+ HttpAuthCache::Entry* entry = cache.Lookup(origin, "Realm3", "digest");
+ EXPECT_FALSE(NULL == entry);
}
// Test fixture class for eviction tests (contains helpers for bulk
@@ -227,15 +316,13 @@
}
void AddPathToRealm(int realm_i, int path_i) {
- scoped_refptr<HttpAuthHandler> handler = new MockAuthHandler("basic",
- GenerateRealm(realm_i), HttpAuth::AUTH_SERVER);
- std::string path = GeneratePath(realm_i, path_i);
- cache_.Add(origin_, handler, L"username", L"password", path);
+ cache_.Add(origin_, GenerateRealm(realm_i), "basic", "",
+ L"username", L"password", GeneratePath(realm_i, path_i));
}
void CheckRealmExistence(int realm_i, bool exists) {
const HttpAuthCache::Entry* entry =
- cache_.LookupByRealm(origin_, GenerateRealm(realm_i));
+ cache_.Lookup(origin_, GenerateRealm(realm_i), "basic");
if (exists) {
EXPECT_FALSE(entry == NULL);
EXPECT_EQ(GenerateRealm(realm_i), entry->realm());
diff --git a/net/http/http_auth_controller.cc b/net/http/http_auth_controller.cc
new file mode 100644
index 0000000..c077ee8
--- /dev/null
+++ b/net/http/http_auth_controller.cc
@@ -0,0 +1,363 @@
+// 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 "net/http/http_auth_controller.h"
+
+#include "base/string_util.h"
+#include "net/base/host_resolver.h"
+#include "net/base/net_util.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_request_info.h"
+
+namespace net {
+
+namespace {
+
+// Returns a log message for all the response headers related to the auth
+// challenge.
+std::string AuthChallengeLogMessage(HttpResponseHeaders* headers) {
+ std::string msg;
+ std::string header_val;
+ void* iter = NULL;
+ while (headers->EnumerateHeader(&iter, "proxy-authenticate", &header_val)) {
+ msg.append("\n Has header Proxy-Authenticate: ");
+ msg.append(header_val);
+ }
+
+ iter = NULL;
+ while (headers->EnumerateHeader(&iter, "www-authenticate", &header_val)) {
+ msg.append("\n Has header WWW-Authenticate: ");
+ msg.append(header_val);
+ }
+
+ // RFC 4559 requires that a proxy indicate its support of NTLM/Negotiate
+ // authentication with a "Proxy-Support: Session-Based-Authentication"
+ // response header.
+ iter = NULL;
+ while (headers->EnumerateHeader(&iter, "proxy-support", &header_val)) {
+ msg.append("\n Has header Proxy-Support: ");
+ msg.append(header_val);
+ }
+
+ return msg;
+}
+
+} // namespace
+
+HttpAuthController::HttpAuthController(
+ HttpAuth::Target target,
+ const GURL& auth_url,
+ scoped_refptr<HttpNetworkSession> session)
+ : target_(target),
+ auth_url_(auth_url),
+ auth_origin_(auth_url.GetOrigin()),
+ auth_path_(HttpAuth::AUTH_PROXY ? std::string() : auth_url.path()),
+ embedded_identity_used_(false),
+ default_credentials_used_(false),
+ session_(session),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ io_callback_(this, &HttpAuthController::OnIOComplete)),
+ user_callback_(NULL) {
+}
+
+HttpAuthController::~HttpAuthController() {
+ user_callback_ = NULL;
+}
+
+int HttpAuthController::MaybeGenerateAuthToken(const HttpRequestInfo* request,
+ CompletionCallback* callback,
+ const BoundNetLog& net_log) {
+ bool needs_auth = HaveAuth() || SelectPreemptiveAuth(net_log);
+ if (!needs_auth)
+ return OK;
+ const std::wstring* username = NULL;
+ const std::wstring* password = NULL;
+ if (identity_.source != HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS) {
+ username = &identity_.username;
+ password = &identity_.password;
+ }
+ DCHECK(auth_token_.empty());
+ DCHECK(NULL == user_callback_);
+ int rv = handler_->GenerateAuthToken(username,
+ password,
+ request,
+ &io_callback_,
+ &auth_token_);
+ if (rv == ERR_IO_PENDING)
+ user_callback_ = callback;
+ else
+ OnIOComplete(rv);
+ // This error occurs with GSSAPI, if the user has not already logged in.
+ if (rv == ERR_MISSING_AUTH_CREDENTIALS)
+ rv = OK;
+ return rv;
+}
+
+bool HttpAuthController::SelectPreemptiveAuth(const BoundNetLog& net_log) {
+ DCHECK(!HaveAuth());
+ DCHECK(identity_.invalid);
+
+ // Don't do preemptive authorization if the URL contains a username/password,
+ // since we must first be challenged in order to use the URL's identity.
+ if (auth_url_.has_username())
+ return false;
+
+ // SelectPreemptiveAuth() is on the critical path for each request, so it
+ // is expected to be fast. LookupByPath() is fast in the common case, since
+ // the number of http auth cache entries is expected to be very small.
+ // (For most users in fact, it will be 0.)
+ HttpAuthCache::Entry* entry = session_->auth_cache()->LookupByPath(
+ auth_origin_, auth_path_);
+ if (!entry)
+ return false;
+
+ // Try to create a handler using the previous auth challenge.
+ scoped_ptr<HttpAuthHandler> handler_preemptive;
+ int rv_create = session_->http_auth_handler_factory()->
+ CreatePreemptiveAuthHandlerFromString(entry->auth_challenge(), target_,
+ auth_origin_,
+ entry->IncrementNonceCount(),
+ net_log, &handler_preemptive);
+ if (rv_create != OK)
+ return false;
+
+ // Set the state
+ identity_.source = HttpAuth::IDENT_SRC_PATH_LOOKUP;
+ identity_.invalid = false;
+ identity_.username = entry->username();
+ identity_.password = entry->password();
+ handler_.swap(handler_preemptive);
+ return true;
+}
+
+void HttpAuthController::AddAuthorizationHeader(
+ HttpRequestHeaders* authorization_headers) {
+ DCHECK(HaveAuth());
+ authorization_headers->SetHeader(
+ HttpAuth::GetAuthorizationHeaderName(target_), auth_token_);
+ auth_token_.clear();
+}
+
+int HttpAuthController::HandleAuthChallenge(
+ scoped_refptr<HttpResponseHeaders> headers,
+ bool do_not_send_server_auth,
+ bool establishing_tunnel,
+ const BoundNetLog& net_log) {
+ DCHECK(headers);
+ DCHECK(auth_origin_.is_valid());
+
+ LOG(INFO) << "The " << HttpAuth::GetAuthTargetString(target_) << " "
+ << auth_origin_ << " requested auth"
+ << AuthChallengeLogMessage(headers.get());
+
+ // The auth we tried just failed, hence it can't be valid. Remove it from
+ // the cache so it won't be used again.
+ // TODO(wtc): IsFinalRound is not the right condition. In a multi-round
+ // auth sequence, the server may fail the auth in round 1 if our first
+ // authorization header is broken. We should inspect response_.headers to
+ // determine if the server already failed the auth or wants us to continue.
+ // See http://crbug.com/21015.
+ if (HaveAuth() && handler_->IsFinalRound()) {
+ InvalidateRejectedAuthFromCache();
+ handler_.reset();
+ identity_ = HttpAuth::Identity();
+ }
+
+ identity_.invalid = true;
+
+ if (target_ != HttpAuth::AUTH_SERVER || !do_not_send_server_auth) {
+ // Find the best authentication challenge that we support.
+ HttpAuth::ChooseBestChallenge(session_->http_auth_handler_factory(),
+ headers, target_, auth_origin_,
+ disabled_schemes_, net_log,
+ &handler_);
+ }
+
+ if (!handler_.get()) {
+ if (establishing_tunnel) {
+ LOG(ERROR) << "Can't perform auth to the "
+ << HttpAuth::GetAuthTargetString(target_) << " "
+ << auth_origin_ << " when establishing a tunnel"
+ << AuthChallengeLogMessage(headers.get());
+
+ // We are establishing a tunnel, we can't show the error page because an
+ // active network attacker could control its contents. Instead, we just
+ // fail to establish the tunnel.
+ DCHECK(target_ == HttpAuth::AUTH_PROXY);
+ return ERR_PROXY_AUTH_UNSUPPORTED;
+ }
+ // We found no supported challenge -- let the transaction continue
+ // so we end up displaying the error page.
+ return OK;
+ }
+
+ if (handler_->NeedsIdentity()) {
+ // Pick a new auth identity to try, by looking to the URL and auth cache.
+ // If an identity to try is found, it is saved to identity_.
+ SelectNextAuthIdentityToTry();
+ } else {
+ // Proceed with the existing identity or a null identity.
+ //
+ // TODO(wtc): Add a safeguard against infinite transaction restarts, if
+ // the server keeps returning "NTLM".
+ identity_.invalid = false;
+ }
+
+ // From this point on, we are restartable.
+
+ if (identity_.invalid) {
+ // We have exhausted all identity possibilities, all we can do now is
+ // pass the challenge information back to the client.
+ PopulateAuthChallenge();
+ } else {
+ auth_info_ = NULL;
+ }
+
+ return OK;
+}
+
+void HttpAuthController::ResetAuth(const std::wstring& username,
+ const std::wstring& password) {
+ DCHECK(identity_.invalid || (username.empty() && password.empty()));
+
+ if (identity_.invalid) {
+ // Update the username/password.
+ identity_.source = HttpAuth::IDENT_SRC_EXTERNAL;
+ identity_.invalid = false;
+ identity_.username = username;
+ identity_.password = password;
+ }
+
+ DCHECK(identity_.source != HttpAuth::IDENT_SRC_PATH_LOOKUP);
+
+ // Add the auth entry to the cache before restarting. We don't know whether
+ // the identity is valid yet, but if it is valid we want other transactions
+ // to know about it. If an entry for (origin, handler->realm()) already
+ // exists, we update it.
+ //
+ // If identity_.source is HttpAuth::IDENT_SRC_NONE or
+ // HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS, identity_ contains no
+ // identity because identity is not required yet or we're using default
+ // credentials.
+ //
+ // TODO(wtc): For NTLM_SSPI, we add the same auth entry to the cache in
+ // round 1 and round 2, which is redundant but correct. It would be nice
+ // to add an auth entry to the cache only once, preferrably in round 1.
+ // See http://crbug.com/21015.
+ switch (identity_.source) {
+ case HttpAuth::IDENT_SRC_NONE:
+ case HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS:
+ break;
+ default:
+ session_->auth_cache()->Add(auth_origin_, handler_->realm(),
+ handler_->scheme(), handler_->challenge(),
+ identity_.username, identity_.password,
+ auth_path_);
+ break;
+ }
+}
+
+void HttpAuthController::InvalidateRejectedAuthFromCache() {
+ DCHECK(HaveAuth());
+
+ // TODO(eroman): this short-circuit can be relaxed. If the realm of
+ // the preemptively used auth entry matches the realm of the subsequent
+ // challenge, then we can invalidate the preemptively used entry.
+ // Otherwise as-is we may send the failed credentials one extra time.
+ if (identity_.source == HttpAuth::IDENT_SRC_PATH_LOOKUP)
+ return;
+
+ // Clear the cache entry for the identity we just failed on.
+ // Note: we require the username/password to match before invalidating
+ // since the entry in the cache may be newer than what we used last time.
+ session_->auth_cache()->Remove(auth_origin_, handler_->realm(),
+ handler_->scheme(), identity_.username,
+ identity_.password);
+}
+
+bool HttpAuthController::SelectNextAuthIdentityToTry() {
+ DCHECK(handler_.get());
+ DCHECK(identity_.invalid);
+
+ // Try to use the username/password encoded into the URL first.
+ if (target_ == HttpAuth::AUTH_SERVER && auth_url_.has_username() &&
+ !embedded_identity_used_) {
+ identity_.source = HttpAuth::IDENT_SRC_URL;
+ identity_.invalid = false;
+ // Extract the username:password from the URL.
+ GetIdentityFromURL(auth_url_,
+ &identity_.username,
+ &identity_.password);
+ embedded_identity_used_ = true;
+ // TODO(eroman): If the password is blank, should we also try combining
+ // with a password from the cache?
+ return true;
+ }
+
+ // Check the auth cache for a realm entry.
+ HttpAuthCache::Entry* entry =
+ session_->auth_cache()->Lookup(auth_origin_, handler_->realm(),
+ handler_->scheme());
+
+ if (entry) {
+ identity_.source = HttpAuth::IDENT_SRC_REALM_LOOKUP;
+ identity_.invalid = false;
+ identity_.username = entry->username();
+ identity_.password = entry->password();
+ return true;
+ }
+
+ // Use default credentials (single sign on) if this is the first attempt
+ // at identity. Do not allow multiple times as it will infinite loop.
+ // We use default credentials after checking the auth cache so that if
+ // single sign-on doesn't work, we won't try default credentials for future
+ // transactions.
+ if (!default_credentials_used_ && handler_->AllowsDefaultCredentials()) {
+ identity_.source = HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS;
+ identity_.invalid = false;
+ default_credentials_used_ = true;
+ return true;
+ }
+
+ return false;
+}
+
+void HttpAuthController::PopulateAuthChallenge() {
+ // Populates response_.auth_challenge with the authentication challenge info.
+ // This info is consumed by URLRequestHttpJob::GetAuthChallengeInfo().
+
+ auth_info_ = new AuthChallengeInfo;
+ auth_info_->is_proxy = target_ == HttpAuth::AUTH_PROXY;
+ auth_info_->host_and_port = ASCIIToWide(GetHostAndPort(auth_origin_));
+ auth_info_->scheme = ASCIIToWide(handler_->scheme());
+ // TODO(eroman): decode realm according to RFC 2047.
+ auth_info_->realm = ASCIIToWide(handler_->realm());
+}
+
+void HttpAuthController::OnIOComplete(int result) {
+ // This error occurs with GSSAPI, if the user has not already logged in.
+ // In that case, disable the current scheme as it cannot succeed.
+ if (result == ERR_MISSING_AUTH_CREDENTIALS) {
+ DisableAuthScheme(handler_->scheme());
+ auth_token_.clear();
+ result = OK;
+ }
+ if (user_callback_) {
+ CompletionCallback* c = user_callback_;
+ user_callback_ = NULL;
+ c->Run(result);
+ }
+}
+
+bool HttpAuthController::IsAuthSchemeDisabled(const std::string& scheme) const {
+ return disabled_schemes_.find(scheme) != disabled_schemes_.end();
+}
+
+void HttpAuthController::DisableAuthScheme(const std::string& scheme) {
+ disabled_schemes_.insert(scheme);
+}
+
+} // namespace net
diff --git a/net/http/http_auth_controller.h b/net/http/http_auth_controller.h
new file mode 100644
index 0000000..9bc8d59
--- /dev/null
+++ b/net/http/http_auth_controller.h
@@ -0,0 +1,149 @@
+// 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.
+
+#ifndef NET_HTTP_HTTP_AUTH_CONTROLLER_H_
+#define NET_HTTP_HTTP_AUTH_CONTROLLER_H_
+
+#include <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_log.h"
+#include "net/http/http_auth.h"
+
+namespace net {
+
+class AuthChallengeInfo;
+class HostResolver;
+class HttpAuthHandler;
+class HttpNetworkSession;
+class HttpRequestHeaders;
+struct HttpRequestInfo;
+
+class HttpAuthController : public base::RefCounted<HttpAuthController> {
+ public:
+ // The arguments are self explanatory except possibly for |auth_url|, which
+ // should be both the auth target and auth path in a single url argument.
+ HttpAuthController(HttpAuth::Target target, const GURL& auth_url,
+ scoped_refptr<HttpNetworkSession> session);
+
+ // Generate an authentication token for |target| if necessary. The return
+ // value is a net error code. |OK| will be returned both in the case that
+ // a token is correctly generated synchronously, as well as when no tokens
+ // were necessary.
+ virtual int MaybeGenerateAuthToken(const HttpRequestInfo* request,
+ CompletionCallback* callback,
+ const BoundNetLog& net_log);
+
+ // Adds either the proxy auth header, or the origin server auth header,
+ // as specified by |target_|.
+ virtual void AddAuthorizationHeader(
+ HttpRequestHeaders* authorization_headers);
+
+ // Checks for and handles HTTP status code 401 or 407.
+ // |HandleAuthChallenge()| returns OK on success, or a network error code
+ // otherwise. It may also populate |auth_info_|.
+ virtual int HandleAuthChallenge(scoped_refptr<HttpResponseHeaders> headers,
+ bool do_not_send_server_auth,
+ bool establishing_tunnel,
+ const BoundNetLog& net_log);
+
+ // Store the supplied credentials and prepare to restart the auth.
+ virtual void ResetAuth(const std::wstring& username,
+ const std::wstring& password);
+
+ virtual bool HaveAuthHandler() const {
+ return handler_.get() != NULL;
+ }
+
+ virtual bool HaveAuth() const {
+ return handler_.get() && !identity_.invalid;
+ }
+
+ virtual scoped_refptr<AuthChallengeInfo> auth_info() {
+ return auth_info_;
+ }
+
+ virtual bool IsAuthSchemeDisabled(const std::string& scheme) const;
+ virtual void DisableAuthScheme(const std::string& scheme);
+
+ protected: // So that we can mock this object.
+ friend class base::RefCounted<HttpAuthController>;
+ virtual ~HttpAuthController();
+
+ private:
+ // Searches the auth cache for an entry that encompasses the request's path.
+ // If such an entry is found, updates |identity_| and |handler_| with the
+ // cache entry's data and returns true.
+ bool SelectPreemptiveAuth(const BoundNetLog& net_log);
+
+ // Invalidates any auth cache entries after authentication has failed.
+ // The identity that was rejected is |identity_|.
+ void InvalidateRejectedAuthFromCache();
+
+ // Sets |identity_| to the next identity that the transaction should try. It
+ // chooses candidates by searching the auth cache and the URL for a
+ // username:password. Returns true if an identity was found.
+ bool SelectNextAuthIdentityToTry();
+
+ // Populates auth_info_ with the challenge information, so that
+ // URLRequestHttpJob can prompt for a username/password.
+ void PopulateAuthChallenge();
+
+ void OnIOComplete(int result);
+
+ // Indicates if this handler is for Proxy auth or Server auth.
+ HttpAuth::Target target_;
+
+ // Holds the {scheme, host, path, port} for the authentication target.
+ const GURL auth_url_;
+
+ // Holds the {scheme, host, port} for the authentication target.
+ const GURL auth_origin_;
+
+ // The absolute path of the resource needing authentication.
+ // For proxy authentication the path is empty.
+ const std::string auth_path_;
+
+ // |handler_| encapsulates the logic for the particular auth-scheme.
+ // This includes the challenge's parameters. If NULL, then there is no
+ // associated auth handler.
+ scoped_ptr<HttpAuthHandler> handler_;
+
+ // |identity_| holds the (username/password) that should be used by
+ // the handler_ to generate credentials. This identity can come from
+ // a number of places (url, cache, prompt).
+ HttpAuth::Identity identity_;
+
+ // |auth_token_| contains the opaque string to pass to the proxy or
+ // server to authenticate the client.
+ std::string auth_token_;
+
+ // Contains information about the auth challenge.
+ scoped_refptr<AuthChallengeInfo> auth_info_;
+
+ // True if we've used the username/password embedded in the URL. This
+ // makes sure we use the embedded identity only once for the transaction,
+ // preventing an infinite auth restart loop.
+ bool embedded_identity_used_;
+
+ // True if default credentials have already been tried for this transaction
+ // in response to an HTTP authentication challenge.
+ bool default_credentials_used_;
+
+ scoped_refptr<HttpNetworkSession> session_;
+
+ std::set<std::string> disabled_schemes_;
+
+ CompletionCallbackImpl<HttpAuthController> io_callback_;
+ CompletionCallback* user_callback_;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_AUTH_CONTROLLER_H_
diff --git a/net/http/http_auth_filter.cc b/net/http/http_auth_filter.cc
new file mode 100644
index 0000000..80d6e0c
--- /dev/null
+++ b/net/http/http_auth_filter.cc
@@ -0,0 +1,56 @@
+// 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 "net/http/http_auth_filter.h"
+#include "base/string_util.h"
+#include "googleurl/src/gurl.h"
+
+namespace net {
+
+// Using a std::set<> has the benefit of removing duplicates automatically.
+typedef std::set<string16> RegistryWhitelist;
+
+// TODO(ahendrickson) -- Determine if we want separate whitelists for HTTP and
+// HTTPS, one for both, or only an HTTP one. My understanding is that the HTTPS
+// entries in the registry mean that you are only allowed to connect to the site
+// via HTTPS and still be considered 'safe'.
+
+HttpAuthFilterWhitelist::HttpAuthFilterWhitelist() {
+}
+
+HttpAuthFilterWhitelist::~HttpAuthFilterWhitelist() {
+}
+
+void HttpAuthFilterWhitelist::SetWhitelist(
+ const std::string& server_whitelist) {
+ rules_.ParseFromString(server_whitelist);
+}
+
+bool HttpAuthFilterWhitelist::IsValid(const GURL& url,
+ HttpAuth::Target target) const {
+ if ((target != HttpAuth::AUTH_SERVER) && (target != HttpAuth::AUTH_PROXY))
+ return false;
+ // All proxies pass
+ if (target == HttpAuth::AUTH_PROXY)
+ return true;
+ return rules_.Matches(url);
+}
+
+// Add a new domain |filter| to the whitelist, if it's not already there
+bool HttpAuthFilterWhitelist::AddFilter(const std::string& filter,
+ HttpAuth::Target target) {
+ if ((target != HttpAuth::AUTH_SERVER) && (target != HttpAuth::AUTH_PROXY))
+ return false;
+ // All proxies pass
+ if (target == HttpAuth::AUTH_PROXY)
+ return true;
+ rules_.AddRuleFromString(filter);
+ return true;
+}
+
+void HttpAuthFilterWhitelist::AddRuleToBypassLocal() {
+ rules_.AddRuleToBypassLocal();
+}
+
+} // namespace net
diff --git a/net/http/http_auth_filter.h b/net/http/http_auth_filter.h
new file mode 100644
index 0000000..8a2524c
--- /dev/null
+++ b/net/http/http_auth_filter.h
@@ -0,0 +1,66 @@
+// 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.
+
+#ifndef NET_HTTP_HTTP_AUTH_FILTER_H_
+#define NET_HTTP_HTTP_AUTH_FILTER_H_
+
+#include <list>
+#include <set>
+#include <string>
+
+#include "base/string_util.h"
+#include "net/http/http_auth.h"
+#include "net/proxy/proxy_bypass_rules.h"
+
+class GURL;
+
+namespace net {
+
+// |HttpAuthFilter|s determine whether an authentication scheme should be
+// allowed for a particular peer.
+class HttpAuthFilter {
+ public:
+ virtual ~HttpAuthFilter() {}
+
+ // Checks if (|url|, |target|) is supported by the authentication scheme.
+ // Only the host of |url| is examined.
+ virtual bool IsValid(const GURL& url, HttpAuth::Target target) const = 0;
+};
+
+// Whitelist HTTP authentication filter.
+// Explicit whitelists of domains are set via SetWhitelist().
+//
+// Uses the ProxyBypassRules class to do whitelisting for servers.
+// All proxies are allowed.
+class HttpAuthFilterWhitelist : public HttpAuthFilter {
+ public:
+ HttpAuthFilterWhitelist();
+ virtual ~HttpAuthFilterWhitelist();
+
+ // HttpAuthFilter methods:
+ virtual bool IsValid(const GURL& url, HttpAuth::Target target) const;
+
+ // Installs the whitelist.
+ // |server_whitelist| is parsed by ProxyBypassRules.
+ void SetWhitelist(const std::string& server_whitelist);
+
+ // Adds an individual URL |filter| to the list, of the specified |target|.
+ bool AddFilter(const std::string& filter, HttpAuth::Target target);
+
+ // Adds a rule that bypasses all "local" hostnames.
+ void AddRuleToBypassLocal();
+
+ const ProxyBypassRules& rules() const { return rules_; }
+
+ private:
+ // We are using ProxyBypassRules because they have the functionality that we
+ // want, but we are not using it for proxy bypass.
+ ProxyBypassRules rules_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpAuthFilterWhitelist);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_AUTH_FILTER_H_
diff --git a/net/http/http_auth_filter_unittest.cc b/net/http/http_auth_filter_unittest.cc
new file mode 100644
index 0000000..df61e14
--- /dev/null
+++ b/net/http/http_auth_filter_unittest.cc
@@ -0,0 +1,106 @@
+// 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 <iostream>
+
+#include "base/logging.h"
+
+#include "base/scoped_ptr.h"
+#include "googleurl/src/gurl.h"
+#include "net/http/http_auth_filter.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+static const char* const server_whitelist_array[] = {
+ "google.com",
+ "linkedin.com",
+ "book.com",
+ ".chromium.org",
+ ".gag",
+ "gog"
+};
+
+enum {
+ ALL_SERVERS_MATCH = (1 << arraysize(server_whitelist_array)) - 1
+};
+
+struct UrlData {
+ GURL url;
+ HttpAuth::Target target;
+ bool matches;
+ int match_bits;
+};
+
+static const UrlData urls[] = {
+ { GURL(""),
+ HttpAuth::AUTH_NONE, false, 0 },
+ { GURL("http://foo.cn"),
+ HttpAuth::AUTH_PROXY, true, ALL_SERVERS_MATCH },
+ { GURL("http://foo.cn"),
+ HttpAuth::AUTH_SERVER, false, 0 },
+ { GURL("http://slashdot.org"),
+ HttpAuth::AUTH_NONE, false, 0 },
+ { GURL("http://www.google.com"),
+ HttpAuth::AUTH_SERVER, true, 1 << 0 },
+ { GURL("http://www.google.com"),
+ HttpAuth::AUTH_PROXY, true, ALL_SERVERS_MATCH },
+ { GURL("https://login.facebook.com/login.php?login_attempt=1"),
+ HttpAuth::AUTH_NONE, false, 0 },
+ { GURL("http://codereview.chromium.org/634002/show"),
+ HttpAuth::AUTH_SERVER, true, 1 << 3 },
+ { GURL("http://code.google.com/p/chromium/issues/detail?id=34505"),
+ HttpAuth::AUTH_SERVER, true, 1 << 0 },
+ { GURL("http://code.google.com/p/chromium/issues/list?can=2&q=label:"
+ "spdy&sort=owner&colspec=ID%20Stars%20Pri%20Area%20Type%20Status%20"
+ "Summary%20Modified%20Owner%20Mstone%20OS"),
+ HttpAuth::AUTH_SERVER, true, 1 << 3 },
+ { GURL("https://www.linkedin.com/secure/login?trk=hb_signin"),
+ HttpAuth::AUTH_SERVER, true, 1 << 1 },
+ { GURL("http://www.linkedin.com/mbox?displayMBoxItem=&"
+ "itemID=I1717980652_2&trk=COMM_HP_MSGVW_MEBC_MEBC&goback=.hom"),
+ HttpAuth::AUTH_SERVER, true, 1 << 1 },
+ { GURL("http://news.slashdot.org/story/10/02/18/190236/"
+ "New-Plan-Lets-Top-HS-Students-Graduate-2-Years-Early"),
+ HttpAuth::AUTH_PROXY, true, ALL_SERVERS_MATCH },
+ { GURL("http://codereview.chromium.org/646068/diff/4001/5003"),
+ HttpAuth::AUTH_SERVER, true, 1 << 3 },
+ { GURL("http://codereview.chromium.gag/646068/diff/4001/5003"),
+ HttpAuth::AUTH_SERVER, true, 1 << 4 },
+ { GURL("http://codereview.chromium.gog/646068/diff/4001/5003"),
+ HttpAuth::AUTH_SERVER, true, 1 << 5 },
+};
+
+} // namespace
+
+TEST(HttpAuthFilterTest, EmptyFilter) {
+ // Create an empty filter
+ HttpAuthFilterWhitelist filter;
+ for (size_t i = 0; i < arraysize(urls); i++) {
+ EXPECT_EQ(urls[i].target == HttpAuth::AUTH_PROXY,
+ filter.IsValid(urls[i].url, urls[i].target))
+ << " " << i << ": " << urls[i].url;
+ }
+}
+
+TEST(HttpAuthFilterTest, NonEmptyFilter) {
+ // Create an non-empty filter
+ HttpAuthFilterWhitelist filter;
+ std::string server_whitelist_filter_string;
+ for (size_t i = 0; i < arraysize(server_whitelist_array); ++i) {
+ if (!server_whitelist_filter_string.empty())
+ server_whitelist_filter_string += ",";
+ server_whitelist_filter_string += "*";
+ server_whitelist_filter_string += server_whitelist_array[i];
+ }
+ filter.SetWhitelist(server_whitelist_filter_string);
+ for (size_t i = 0; i < arraysize(urls); i++) {
+ EXPECT_EQ(urls[i].matches, filter.IsValid(urls[i].url, urls[i].target))
+ << " " << i << ": " << urls[i].url;
+ }
+}
+
+} // namespace net
diff --git a/net/http/http_auth_filter_win.h b/net/http/http_auth_filter_win.h
new file mode 100644
index 0000000..f819523
--- /dev/null
+++ b/net/http/http_auth_filter_win.h
@@ -0,0 +1,39 @@
+// 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.
+
+#ifndef NET_HTTP_HTTP_AUTH_FILTER_WIN_H_
+#define NET_HTTP_HTTP_AUTH_FILTER_WIN_H_
+
+#include <string>
+
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include "base/string_util.h"
+
+namespace net {
+
+enum RegistryHiveType {
+ CURRENT_USER,
+ LOCAL_MACHINE
+};
+
+namespace http_auth {
+
+// The common path to all the registry keys containing domain zone information.
+extern const char16 kRegistryInternetSettings[];
+extern const char16 kSettingsMachineOnly[];
+extern const char16* kRegistryEntries[3]; // L"http", L"https", and L"*"
+
+extern const char16* GetRegistryWhitelistKey();
+// Override the whitelist key. Passing in NULL restores the default value.
+extern void SetRegistryWhitelistKey(const char16* new_whitelist_key);
+extern bool UseOnlyMachineSettings();
+
+} // namespace http_auth
+
+} // namespace net
+#endif // OS_WIN
+
+#endif // NET_HTTP_HTTP_AUTH_FILTER_WIN_H_
diff --git a/net/http/http_auth_gssapi_posix.cc b/net/http/http_auth_gssapi_posix.cc
new file mode 100644
index 0000000..5c88375
--- /dev/null
+++ b/net/http/http_auth_gssapi_posix.cc
@@ -0,0 +1,831 @@
+// 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 "net/http/http_auth_gssapi_posix.h"
+
+#include <limits>
+#include <string>
+
+#include "base/base64.h"
+#include "base/file_path.h"
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/singleton.h"
+#include "base/string_util.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+
+// These are defined for the GSSAPI library:
+// Paraphrasing the comments from gssapi.h:
+// "The implementation must reserve static storage for a
+// gss_OID_desc object for each constant. That constant
+// should be initialized to point to that gss_OID_desc."
+namespace {
+
+static gss_OID_desc GSS_C_NT_USER_NAME_VAL = {
+ 10,
+ const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x01")
+};
+static gss_OID_desc GSS_C_NT_MACHINE_UID_NAME_VAL = {
+ 10,
+ const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02")
+};
+static gss_OID_desc GSS_C_NT_STRING_UID_NAME_VAL = {
+ 10,
+ const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x03")
+};
+static gss_OID_desc GSS_C_NT_HOSTBASED_SERVICE_X_VAL = {
+ 6,
+ const_cast<char*>("\x2b\x06\x01\x05\x06\x02")
+};
+static gss_OID_desc GSS_C_NT_HOSTBASED_SERVICE_VAL = {
+ 10,
+ const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04")
+};
+static gss_OID_desc GSS_C_NT_ANONYMOUS_VAL = {
+ 6,
+ const_cast<char*>("\x2b\x06\01\x05\x06\x03")
+};
+static gss_OID_desc GSS_C_NT_EXPORT_NAME_VAL = {
+ 6,
+ const_cast<char*>("\x2b\x06\x01\x05\x06\x04")
+};
+
+} // namespace
+
+gss_OID GSS_C_NT_USER_NAME = &GSS_C_NT_USER_NAME_VAL;
+gss_OID GSS_C_NT_MACHINE_UID_NAME = &GSS_C_NT_MACHINE_UID_NAME_VAL;
+gss_OID GSS_C_NT_STRING_UID_NAME = &GSS_C_NT_STRING_UID_NAME_VAL;
+gss_OID GSS_C_NT_HOSTBASED_SERVICE_X = &GSS_C_NT_HOSTBASED_SERVICE_X_VAL;
+gss_OID GSS_C_NT_HOSTBASED_SERVICE = &GSS_C_NT_HOSTBASED_SERVICE_VAL;
+gss_OID GSS_C_NT_ANONYMOUS = &GSS_C_NT_ANONYMOUS_VAL;
+gss_OID GSS_C_NT_EXPORT_NAME = &GSS_C_NT_EXPORT_NAME_VAL;
+
+namespace net {
+
+// These are encoded using ASN.1 BER encoding.
+
+// This one is used by Firefox's nsAuthGSSAPI class.
+gss_OID_desc CHROME_GSS_KRB5_MECH_OID_DESC_VAL = {
+ 9,
+ const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x02")
+};
+
+gss_OID_desc CHROME_GSS_C_NT_HOSTBASED_SERVICE_X_VAL = {
+ 6,
+ const_cast<char*>("\x2b\x06\x01\x05\x06\x02")
+};
+
+gss_OID_desc CHROME_GSS_C_NT_HOSTBASED_SERVICE_VAL = {
+ 10,
+ const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04")
+};
+
+gss_OID CHROME_GSS_C_NT_HOSTBASED_SERVICE_X =
+ &CHROME_GSS_C_NT_HOSTBASED_SERVICE_X_VAL;
+gss_OID CHROME_GSS_C_NT_HOSTBASED_SERVICE =
+ &CHROME_GSS_C_NT_HOSTBASED_SERVICE_VAL;
+gss_OID CHROME_GSS_KRB5_MECH_OID_DESC =
+ &CHROME_GSS_KRB5_MECH_OID_DESC_VAL;
+
+// Debugging helpers.
+namespace {
+
+std::string DisplayStatus(OM_uint32 major_status,
+ OM_uint32 minor_status) {
+ if (major_status == GSS_S_COMPLETE)
+ return "OK";
+ return StringPrintf("0x%08X 0x%08X", major_status, minor_status);
+}
+
+std::string DisplayCode(GSSAPILibrary* gssapi_lib,
+ OM_uint32 status,
+ OM_uint32 status_code_type) {
+ const int kMaxDisplayIterations = 8;
+ const size_t kMaxMsgLength = 4096;
+ // msg_ctx needs to be outside the loop because it is invoked multiple times.
+ OM_uint32 msg_ctx = 0;
+ std::string rv = StringPrintf("(0x%08X)", status);
+
+ // This loop should continue iterating until msg_ctx is 0 after the first
+ // iteration. To be cautious and prevent an infinite loop, it stops after
+ // a finite number of iterations as well. As an added sanity check, no
+ // individual message may exceed |kMaxMsgLength|, and the final result
+ // will not exceed |kMaxMsgLength|*2-1.
+ for (int i = 0; i < kMaxDisplayIterations && rv.size() < kMaxMsgLength;
+ ++i) {
+ OM_uint32 min_stat;
+ gss_buffer_desc_struct msg = GSS_C_EMPTY_BUFFER;
+ OM_uint32 maj_stat =
+ gssapi_lib->display_status(&min_stat, status, status_code_type,
+ GSS_C_NULL_OID, &msg_ctx, &msg);
+ if (maj_stat == GSS_S_COMPLETE) {
+ int msg_len = (msg.length > kMaxMsgLength) ?
+ static_cast<int>(kMaxMsgLength) :
+ static_cast<int>(msg.length);
+ if (msg_len > 0 && msg.value != NULL) {
+ rv += StringPrintf(" %.*s", msg_len,
+ static_cast<char*>(msg.value));
+ }
+ }
+ gssapi_lib->release_buffer(&min_stat, &msg);
+ if (!msg_ctx)
+ break;
+ }
+ return rv;
+}
+
+std::string DisplayExtendedStatus(GSSAPILibrary* gssapi_lib,
+ OM_uint32 major_status,
+ OM_uint32 minor_status) {
+ if (major_status == GSS_S_COMPLETE)
+ return "OK";
+ std::string major = DisplayCode(gssapi_lib, major_status, GSS_C_GSS_CODE);
+ std::string minor = DisplayCode(gssapi_lib, minor_status, GSS_C_MECH_CODE);
+ return StringPrintf("Major: %s | Minor: %s", major.c_str(), minor.c_str());
+}
+
+// ScopedName releases a gss_name_t when it goes out of scope.
+class ScopedName {
+ public:
+ ScopedName(gss_name_t name,
+ GSSAPILibrary* gssapi_lib)
+ : name_(name),
+ gssapi_lib_(gssapi_lib) {
+ DCHECK(gssapi_lib_);
+ }
+
+ ~ScopedName() {
+ if (name_ != GSS_C_NO_NAME) {
+ OM_uint32 minor_status = 0;
+ OM_uint32 major_status =
+ gssapi_lib_->release_name(&minor_status, &name_);
+ if (major_status != GSS_S_COMPLETE) {
+ LOG(WARNING) << "Problem releasing name. "
+ << DisplayStatus(major_status, minor_status);
+ }
+ name_ = GSS_C_NO_NAME;
+ }
+ }
+
+ private:
+ gss_name_t name_;
+ GSSAPILibrary* gssapi_lib_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedName);
+};
+
+// ScopedBuffer releases a gss_buffer_t when it goes out of scope.
+class ScopedBuffer {
+ public:
+ ScopedBuffer(gss_buffer_t buffer,
+ GSSAPILibrary* gssapi_lib)
+ : buffer_(buffer),
+ gssapi_lib_(gssapi_lib) {
+ DCHECK(gssapi_lib_);
+ }
+
+ ~ScopedBuffer() {
+ if (buffer_ != GSS_C_NO_BUFFER) {
+ OM_uint32 minor_status = 0;
+ OM_uint32 major_status =
+ gssapi_lib_->release_buffer(&minor_status, buffer_);
+ if (major_status != GSS_S_COMPLETE) {
+ LOG(WARNING) << "Problem releasing buffer. "
+ << DisplayStatus(major_status, minor_status);
+ }
+ buffer_ = GSS_C_NO_BUFFER;
+ }
+ }
+
+ private:
+ gss_buffer_t buffer_;
+ GSSAPILibrary* gssapi_lib_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedBuffer);
+};
+
+namespace {
+
+std::string AppendIfPredefinedValue(gss_OID oid,
+ gss_OID predefined_oid,
+ const char* predefined_oid_name) {
+ DCHECK(oid);
+ DCHECK(predefined_oid);
+ DCHECK(predefined_oid_name);
+ std::string output;
+ if (oid->length != predefined_oid->length)
+ return output;
+ if (0 != memcmp(oid->elements,
+ predefined_oid->elements,
+ predefined_oid->length))
+ return output;
+
+ output += " (";
+ output += predefined_oid_name;
+ output += ")";
+ return output;
+}
+
+} // namespace
+
+std::string DescribeOid(GSSAPILibrary* gssapi_lib, const gss_OID oid) {
+ if (!oid)
+ return "<NULL>";
+ std::string output;
+ const size_t kMaxCharsToPrint = 1024;
+ OM_uint32 byte_length = oid->length;
+ size_t char_length = byte_length / sizeof(char);
+ if (char_length > kMaxCharsToPrint) {
+ // This might be a plain ASCII string.
+ // Check if the first |kMaxCharsToPrint| characters
+ // contain only printable characters and are NULL terminated.
+ const char* str = reinterpret_cast<const char*>(oid);
+ bool is_printable = true;
+ size_t str_length = 0;
+ for ( ; str_length < kMaxCharsToPrint; ++str_length) {
+ if (!str[str_length])
+ break;
+ if (!isprint(str[str_length])) {
+ is_printable = false;
+ break;
+ }
+ }
+ if (!str[str_length]) {
+ output += StringPrintf("\"%s\"", str);
+ return output;
+ }
+ }
+ output = StringPrintf("(%u) \"", byte_length);
+ if (!oid->elements) {
+ output += "<NULL>";
+ return output;
+ }
+ const unsigned char* elements =
+ reinterpret_cast<const unsigned char*>(oid->elements);
+ // Don't print more than |kMaxCharsToPrint| characters.
+ size_t i = 0;
+ for ( ; (i < byte_length) && (i < kMaxCharsToPrint); ++i) {
+ output += StringPrintf("\\x%02X", elements[i]);
+ }
+ if (i >= kMaxCharsToPrint)
+ output += "...";
+ output += "\"";
+
+ // Check if the OID is one of the predefined values.
+ output += AppendIfPredefinedValue(oid,
+ GSS_C_NT_USER_NAME,
+ "GSS_C_NT_USER_NAME");
+ output += AppendIfPredefinedValue(oid,
+ GSS_C_NT_MACHINE_UID_NAME,
+ "GSS_C_NT_MACHINE_UID_NAME");
+ output += AppendIfPredefinedValue(oid,
+ GSS_C_NT_STRING_UID_NAME,
+ "GSS_C_NT_STRING_UID_NAME");
+ output += AppendIfPredefinedValue(oid,
+ GSS_C_NT_HOSTBASED_SERVICE_X,
+ "GSS_C_NT_HOSTBASED_SERVICE_X");
+ output += AppendIfPredefinedValue(oid,
+ GSS_C_NT_HOSTBASED_SERVICE,
+ "GSS_C_NT_HOSTBASED_SERVICE");
+ output += AppendIfPredefinedValue(oid,
+ GSS_C_NT_ANONYMOUS,
+ "GSS_C_NT_ANONYMOUS");
+ output += AppendIfPredefinedValue(oid,
+ GSS_C_NT_EXPORT_NAME,
+ "GSS_C_NT_EXPORT_NAME");
+
+ return output;
+}
+
+std::string DescribeBuffer(const gss_buffer_t buffer) {
+ if (!buffer)
+ return "<NULL>";
+ size_t length = buffer->length;
+ std::string output(StringPrintf("(%" PRIuS ") ", length));
+ if (!buffer->value) {
+ output += "<NULL>";
+ return output;
+ }
+ const char* value =
+ reinterpret_cast<const char*>(buffer->value);
+ bool is_printable = true;
+ for (size_t i = 0; i < length; ++i) {
+ if (!isprint(value[i])) {
+ // Allow the last character to be a '0'.
+ if ((i < (length - 1)) && !value[i])
+ continue;
+ is_printable = false;
+ break;
+ }
+ }
+ if (is_printable) {
+ output += "\"";
+ output += value;
+ output += "\"";
+ } else {
+ output += "[";
+ for (size_t i = 0; i < buffer->length; ++i) {
+ output += StringPrintf("\\x%02X", value[i] & 0x0FF);
+ }
+ output += "]";
+ }
+
+ return output;
+}
+
+std::string DescribeName(GSSAPILibrary* gssapi_lib, const gss_name_t name) {
+ OM_uint32 major_status = 0;
+ OM_uint32 minor_status = 0;
+ gss_buffer_desc_struct output_name_buffer = GSS_C_EMPTY_BUFFER;
+ gss_OID_desc output_name_type_desc = GSS_C_EMPTY_BUFFER;
+ gss_OID output_name_type = &output_name_type_desc;
+ major_status = gssapi_lib->display_name(&minor_status,
+ name,
+ &output_name_buffer,
+ &output_name_type);
+ ScopedBuffer scoped_output_name(&output_name_buffer, gssapi_lib);
+ if (major_status != GSS_S_COMPLETE) {
+ std::string error =
+ StringPrintf("Unable to describe name 0x%p, %s",
+ name,
+ DisplayExtendedStatus(gssapi_lib,
+ major_status,
+ minor_status).c_str());
+ return error;
+ }
+ int len = output_name_buffer.length;
+ std::string description =
+ StringPrintf("%*s (Type %s)",
+ len,
+ reinterpret_cast<const char*>(output_name_buffer.value),
+ DescribeOid(gssapi_lib, output_name_type).c_str());
+ return description;
+}
+
+std::string DescribeContext(GSSAPILibrary* gssapi_lib,
+ const gss_ctx_id_t context_handle) {
+ OM_uint32 major_status = 0;
+ OM_uint32 minor_status = 0;
+ gss_name_t src_name = GSS_C_NO_NAME;
+ gss_name_t targ_name = GSS_C_NO_NAME;
+ OM_uint32 lifetime_rec = 0;
+ gss_OID mech_type = GSS_C_NO_OID;
+ OM_uint32 ctx_flags = 0;
+ int locally_initiated = 0;
+ int open = 0;
+ major_status = gssapi_lib->inquire_context(&minor_status,
+ context_handle,
+ &src_name,
+ &targ_name,
+ &lifetime_rec,
+ &mech_type,
+ &ctx_flags,
+ &locally_initiated,
+ &open);
+ ScopedName(src_name, gssapi_lib);
+ ScopedName(targ_name, gssapi_lib);
+ if (major_status != GSS_S_COMPLETE) {
+ std::string error =
+ StringPrintf("Unable to describe context 0x%p, %s",
+ context_handle,
+ DisplayExtendedStatus(gssapi_lib,
+ major_status,
+ minor_status).c_str());
+ return error;
+ }
+ std::string source(DescribeName(gssapi_lib, src_name));
+ std::string target(DescribeName(gssapi_lib, targ_name));
+ std::string description = StringPrintf("Context 0x%p: "
+ "Source \"%s\", "
+ "Target \"%s\", "
+ "lifetime %d, "
+ "mechanism %s, "
+ "flags 0x%08X, "
+ "local %d, "
+ "open %d",
+ context_handle,
+ source.c_str(),
+ target.c_str(),
+ lifetime_rec,
+ DescribeOid(gssapi_lib,
+ mech_type).c_str(),
+ ctx_flags,
+ locally_initiated,
+ open);
+ return description;
+}
+
+} // namespace
+
+GSSAPISharedLibrary::GSSAPISharedLibrary()
+ : initialized_(false),
+ gssapi_library_(NULL),
+ import_name_(NULL),
+ release_name_(NULL),
+ release_buffer_(NULL),
+ display_name_(NULL),
+ display_status_(NULL),
+ init_sec_context_(NULL),
+ wrap_size_limit_(NULL),
+ delete_sec_context_(NULL),
+ inquire_context_(NULL) {
+}
+
+GSSAPISharedLibrary::~GSSAPISharedLibrary() {
+ if (gssapi_library_) {
+ base::UnloadNativeLibrary(gssapi_library_);
+ gssapi_library_ = NULL;
+ }
+}
+
+bool GSSAPISharedLibrary::Init() {
+ if (!initialized_)
+ InitImpl();
+ return initialized_;
+}
+
+bool GSSAPISharedLibrary::InitImpl() {
+ DCHECK(!initialized_);
+ gssapi_library_ = LoadSharedLibrary();
+ if (gssapi_library_ == NULL)
+ return false;
+ initialized_ = true;
+ return true;
+}
+
+base::NativeLibrary GSSAPISharedLibrary::LoadSharedLibrary() {
+ static const char* kLibraryNames[] = {
+#if defined(OS_MACOSX)
+ "libgssapi_krb5.dylib" // MIT Kerberos
+#else
+ "libgssapi_krb5.so.2", // MIT Kerberos - FC, Suse10, Debian
+ "libgssapi.so.4", // Heimdal - Suse10, MDK
+ "libgssapi.so.1" // Heimdal - Suse9, CITI - FC, MDK, Suse10
+#endif
+ };
+ static size_t num_lib_names = arraysize(kLibraryNames);
+
+ for (size_t i = 0; i < num_lib_names; ++i) {
+ const char* library_name = kLibraryNames[i];
+ FilePath file_path(library_name);
+ base::NativeLibrary lib = base::LoadNativeLibrary(file_path);
+ if (lib) {
+ // Only return this library if we can bind the functions we need.
+ if (BindMethods(lib))
+ return lib;
+ base::UnloadNativeLibrary(lib);
+ }
+ }
+ LOG(WARNING) << "Unable to find a compatible GSSAPI library";
+ return NULL;
+}
+
+#define BIND(lib, x) \
+ gss_##x##_type x = reinterpret_cast<gss_##x##_type>( \
+ base::GetFunctionPointerFromNativeLibrary(lib, "gss_" #x)); \
+ if (x == NULL) { \
+ LOG(WARNING) << "Unable to bind function \"" << "gss_" #x << "\""; \
+ return false; \
+ }
+
+bool GSSAPISharedLibrary::BindMethods(base::NativeLibrary lib) {
+ DCHECK(lib != NULL);
+
+ BIND(lib, import_name)
+ BIND(lib, release_name)
+ BIND(lib, release_buffer)
+ BIND(lib, display_name)
+ BIND(lib, display_status)
+ BIND(lib, init_sec_context)
+ BIND(lib, wrap_size_limit)
+ BIND(lib, delete_sec_context)
+ BIND(lib, inquire_context)
+
+ import_name_ = import_name;
+ release_name_ = release_name;
+ release_buffer_ = release_buffer;
+ display_name_ = display_name;
+ display_status_ = display_status;
+ init_sec_context_ = init_sec_context;
+ wrap_size_limit_ = wrap_size_limit;
+ delete_sec_context_ = delete_sec_context;
+ inquire_context_ = inquire_context;
+
+ return true;
+}
+
+#undef BIND
+
+OM_uint32 GSSAPISharedLibrary::import_name(
+ OM_uint32* minor_status,
+ const gss_buffer_t input_name_buffer,
+ const gss_OID input_name_type,
+ gss_name_t* output_name) {
+ DCHECK(initialized_);
+ return import_name_(minor_status, input_name_buffer, input_name_type,
+ output_name);
+}
+
+OM_uint32 GSSAPISharedLibrary::release_name(
+ OM_uint32* minor_status,
+ gss_name_t* input_name) {
+ DCHECK(initialized_);
+ return release_name_(minor_status, input_name);
+}
+
+OM_uint32 GSSAPISharedLibrary::release_buffer(
+ OM_uint32* minor_status,
+ gss_buffer_t buffer) {
+ DCHECK(initialized_);
+ return release_buffer_(minor_status, buffer);
+}
+
+OM_uint32 GSSAPISharedLibrary::display_name(
+ OM_uint32* minor_status,
+ const gss_name_t input_name,
+ gss_buffer_t output_name_buffer,
+ gss_OID* output_name_type) {
+ DCHECK(initialized_);
+ return display_name_(minor_status,
+ input_name,
+ output_name_buffer,
+ output_name_type);
+}
+
+OM_uint32 GSSAPISharedLibrary::display_status(
+ OM_uint32* minor_status,
+ OM_uint32 status_value,
+ int status_type,
+ const gss_OID mech_type,
+ OM_uint32* message_context,
+ gss_buffer_t status_string) {
+ DCHECK(initialized_);
+ return display_status_(minor_status, status_value, status_type, mech_type,
+ message_context, status_string);
+}
+
+OM_uint32 GSSAPISharedLibrary::init_sec_context(
+ OM_uint32* minor_status,
+ const gss_cred_id_t initiator_cred_handle,
+ gss_ctx_id_t* context_handle,
+ const gss_name_t target_name,
+ const gss_OID mech_type,
+ OM_uint32 req_flags,
+ OM_uint32 time_req,
+ const gss_channel_bindings_t input_chan_bindings,
+ const gss_buffer_t input_token,
+ gss_OID* actual_mech_type,
+ gss_buffer_t output_token,
+ OM_uint32* ret_flags,
+ OM_uint32* time_rec) {
+ DCHECK(initialized_);
+ return init_sec_context_(minor_status,
+ initiator_cred_handle,
+ context_handle,
+ target_name,
+ mech_type,
+ req_flags,
+ time_req,
+ input_chan_bindings,
+ input_token,
+ actual_mech_type,
+ output_token,
+ ret_flags,
+ time_rec);
+}
+
+OM_uint32 GSSAPISharedLibrary::wrap_size_limit(
+ OM_uint32* minor_status,
+ const gss_ctx_id_t context_handle,
+ int conf_req_flag,
+ gss_qop_t qop_req,
+ OM_uint32 req_output_size,
+ OM_uint32* max_input_size) {
+ DCHECK(initialized_);
+ return wrap_size_limit_(minor_status,
+ context_handle,
+ conf_req_flag,
+ qop_req,
+ req_output_size,
+ max_input_size);
+}
+
+OM_uint32 GSSAPISharedLibrary::delete_sec_context(
+ OM_uint32* minor_status,
+ gss_ctx_id_t* context_handle,
+ gss_buffer_t output_token) {
+ // This is called from the owner class' destructor, even if
+ // Init() is not called, so we can't assume |initialized_|
+ // is set.
+ if (!initialized_)
+ return 0;
+ return delete_sec_context_(minor_status,
+ context_handle,
+ output_token);
+}
+
+OM_uint32 GSSAPISharedLibrary::inquire_context(
+ OM_uint32* minor_status,
+ const gss_ctx_id_t context_handle,
+ gss_name_t* src_name,
+ gss_name_t* targ_name,
+ OM_uint32* lifetime_rec,
+ gss_OID* mech_type,
+ OM_uint32* ctx_flags,
+ int* locally_initiated,
+ int* open) {
+ DCHECK(initialized_);
+ return inquire_context_(minor_status,
+ context_handle,
+ src_name,
+ targ_name,
+ lifetime_rec,
+ mech_type,
+ ctx_flags,
+ locally_initiated,
+ open);
+}
+GSSAPILibrary* GSSAPILibrary::GetDefault() {
+ return Singleton<GSSAPISharedLibrary>::get();
+}
+
+ScopedSecurityContext::ScopedSecurityContext(GSSAPILibrary* gssapi_lib)
+ : security_context_(GSS_C_NO_CONTEXT),
+ gssapi_lib_(gssapi_lib) {
+ DCHECK(gssapi_lib_);
+}
+
+ScopedSecurityContext::~ScopedSecurityContext() {
+ if (security_context_ != GSS_C_NO_CONTEXT) {
+ gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
+ OM_uint32 minor_status = 0;
+ OM_uint32 major_status = gssapi_lib_->delete_sec_context(
+ &minor_status, &security_context_, &output_token);
+ if (major_status != GSS_S_COMPLETE) {
+ LOG(WARNING) << "Problem releasing security_context. "
+ << DisplayStatus(major_status, minor_status);
+ }
+ security_context_ = GSS_C_NO_CONTEXT;
+ }
+}
+
+HttpAuthGSSAPI::HttpAuthGSSAPI(GSSAPILibrary* library,
+ const std::string& scheme,
+ gss_OID gss_oid)
+ : scheme_(scheme),
+ gss_oid_(gss_oid),
+ library_(library),
+ scoped_sec_context_(library) {
+ DCHECK(library_);
+}
+
+HttpAuthGSSAPI::~HttpAuthGSSAPI() {
+}
+
+bool HttpAuthGSSAPI::Init() {
+ if (!library_)
+ return false;
+ return library_->Init();
+}
+
+bool HttpAuthGSSAPI::NeedsIdentity() const {
+ return decoded_server_auth_token_.empty();
+}
+
+bool HttpAuthGSSAPI::IsFinalRound() const {
+ return !NeedsIdentity();
+}
+
+bool HttpAuthGSSAPI::ParseChallenge(HttpAuth::ChallengeTokenizer* tok) {
+ // Verify the challenge's auth-scheme.
+ if (!tok->valid() ||
+ !LowerCaseEqualsASCII(tok->scheme(), StringToLowerASCII(scheme_).c_str()))
+ return false;
+
+ tok->set_expect_base64_token(true);
+ if (!tok->GetNext()) {
+ decoded_server_auth_token_.clear();
+ return true;
+ }
+
+ std::string encoded_auth_token = tok->value();
+ std::string decoded_auth_token;
+ bool base64_rv = base::Base64Decode(encoded_auth_token, &decoded_auth_token);
+ if (!base64_rv) {
+ LOG(ERROR) << "Base64 decoding of auth token failed.";
+ return false;
+ }
+ decoded_server_auth_token_ = decoded_auth_token;
+ return true;
+}
+
+int HttpAuthGSSAPI::GenerateAuthToken(const std::wstring* username,
+ const std::wstring* password,
+ const std::wstring& spn,
+ std::string* auth_token) {
+ DCHECK(auth_token);
+ DCHECK((username == NULL) == (password == NULL));
+
+ if (!IsFinalRound()) {
+ int rv = OnFirstRound(username, password);
+ if (rv != OK)
+ return rv;
+ }
+
+ gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
+ input_token.length = decoded_server_auth_token_.length();
+ input_token.value =
+ (input_token.length > 0) ?
+ const_cast<char*>(decoded_server_auth_token_.data()) :
+ NULL;
+ gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
+ ScopedBuffer scoped_output_token(&output_token, library_);
+ int rv = GetNextSecurityToken(spn, &input_token, &output_token);
+ if (rv != OK)
+ return rv;
+
+ // Base64 encode data in output buffer and prepend the scheme.
+ std::string encode_input(static_cast<char*>(output_token.value),
+ output_token.length);
+ std::string encode_output;
+ bool ok = base::Base64Encode(encode_input, &encode_output);
+ if (!ok)
+ return ERR_UNEXPECTED;
+ *auth_token = scheme_ + " " + encode_output;
+ return OK;
+}
+
+int HttpAuthGSSAPI::OnFirstRound(const std::wstring* username,
+ const std::wstring* password) {
+ // TODO(cbentzel): Acquire credentials?
+ DCHECK((username == NULL) == (password == NULL));
+ username_.clear();
+ password_.clear();
+ if (username) {
+ username_ = *username;
+ password_ = *password;
+ }
+ return OK;
+}
+
+int HttpAuthGSSAPI::GetNextSecurityToken(const std::wstring& spn,
+ gss_buffer_t in_token,
+ gss_buffer_t out_token) {
+ // Create a name for the principal
+ // TODO(cbentzel): Just do this on the first pass?
+ std::string spn_principal = WideToASCII(spn);
+ gss_buffer_desc spn_buffer = GSS_C_EMPTY_BUFFER;
+ spn_buffer.value = const_cast<char*>(spn_principal.c_str());
+ spn_buffer.length = spn_principal.size() + 1;
+ OM_uint32 minor_status = 0;
+ gss_name_t principal_name = GSS_C_NO_NAME;
+ OM_uint32 major_status = library_->import_name(
+ &minor_status,
+ &spn_buffer,
+ CHROME_GSS_C_NT_HOSTBASED_SERVICE,
+ &principal_name);
+ if (major_status != GSS_S_COMPLETE) {
+ LOG(ERROR) << "Problem importing name from "
+ << "spn \"" << spn_principal << "\""
+ << std::endl
+ << DisplayExtendedStatus(library_,
+ major_status,
+ minor_status);
+ return ERR_UNEXPECTED;
+ }
+ ScopedName scoped_name(principal_name, library_);
+
+ // Continue creating a security context.
+ OM_uint32 req_flags = 0;
+ major_status = library_->init_sec_context(
+ &minor_status,
+ GSS_C_NO_CREDENTIAL,
+ scoped_sec_context_.receive(),
+ principal_name,
+ gss_oid_,
+ req_flags,
+ GSS_C_INDEFINITE,
+ GSS_C_NO_CHANNEL_BINDINGS,
+ in_token,
+ NULL, // actual_mech_type
+ out_token,
+ NULL, // ret flags
+ NULL);
+ if (major_status != GSS_S_COMPLETE &&
+ major_status != GSS_S_CONTINUE_NEEDED) {
+ LOG(ERROR) << "Problem initializing context. "
+ << std::endl
+ << DisplayExtendedStatus(library_,
+ major_status,
+ minor_status)
+ << std::endl
+ << DescribeContext(library_, scoped_sec_context_.get());
+ return ERR_MISSING_AUTH_CREDENTIALS;
+ }
+
+ return OK;
+}
+
+} // namespace net
diff --git a/net/http/http_auth_gssapi_posix.h b/net/http/http_auth_gssapi_posix.h
new file mode 100644
index 0000000..f0642ea
--- /dev/null
+++ b/net/http/http_auth_gssapi_posix.h
@@ -0,0 +1,263 @@
+// 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.
+
+#ifndef NET_HTTP_HTTP_AUTH_GSSAPI_POSIX_H_
+#define NET_HTTP_HTTP_AUTH_GSSAPI_POSIX_H_
+
+#include <string>
+
+#include "base/gtest_prod_util.h"
+#include "base/native_library.h"
+#include "net/http/http_auth.h"
+
+#define GSS_USE_FUNCTION_POINTERS
+#include "net/third_party/gssapi/gssapi.h"
+
+class GURL;
+
+namespace net {
+
+class HttpRequestInfo;
+
+extern gss_OID CHROME_GSS_C_NT_HOSTBASED_SERVICE_X;
+extern gss_OID CHROME_GSS_C_NT_HOSTBASED_SERVICE;
+extern gss_OID CHROME_GSS_KRB5_MECH_OID_DESC;
+
+// GSSAPILibrary is introduced so unit tests can mock the calls to the GSSAPI
+// library. The default implementation attempts to load one of the standard
+// GSSAPI library implementations, then simply passes the arguments on to
+// that implementation.
+class GSSAPILibrary {
+ public:
+ virtual ~GSSAPILibrary() {}
+
+ // Initializes the library, including any necessary dynamic libraries.
+ // This is done separately from construction (which happens at startup time)
+ // in order to delay work until the class is actually needed.
+ virtual bool Init() = 0;
+
+ // These methods match the ones in the GSSAPI library.
+ virtual OM_uint32 import_name(
+ OM_uint32* minor_status,
+ const gss_buffer_t input_name_buffer,
+ const gss_OID input_name_type,
+ gss_name_t* output_name) = 0;
+ virtual OM_uint32 release_name(
+ OM_uint32* minor_status,
+ gss_name_t* input_name) = 0;
+ virtual OM_uint32 release_buffer(
+ OM_uint32* minor_status,
+ gss_buffer_t buffer) = 0;
+ virtual OM_uint32 display_name(
+ OM_uint32* minor_status,
+ const gss_name_t input_name,
+ gss_buffer_t output_name_buffer,
+ gss_OID* output_name_type) = 0;
+ virtual OM_uint32 display_status(
+ OM_uint32* minor_status,
+ OM_uint32 status_value,
+ int status_type,
+ const gss_OID mech_type,
+ OM_uint32* message_contex,
+ gss_buffer_t status_string) = 0;
+ virtual OM_uint32 init_sec_context(
+ OM_uint32* minor_status,
+ const gss_cred_id_t initiator_cred_handle,
+ gss_ctx_id_t* context_handle,
+ const gss_name_t target_name,
+ const gss_OID mech_type,
+ OM_uint32 req_flags,
+ OM_uint32 time_req,
+ const gss_channel_bindings_t input_chan_bindings,
+ const gss_buffer_t input_token,
+ gss_OID* actual_mech_type,
+ gss_buffer_t output_token,
+ OM_uint32* ret_flags,
+ OM_uint32* time_rec) = 0;
+ virtual OM_uint32 wrap_size_limit(
+ OM_uint32* minor_status,
+ const gss_ctx_id_t context_handle,
+ int conf_req_flag,
+ gss_qop_t qop_req,
+ OM_uint32 req_output_size,
+ OM_uint32* max_input_size) = 0;
+ virtual OM_uint32 delete_sec_context(
+ OM_uint32* minor_status,
+ gss_ctx_id_t* context_handle,
+ gss_buffer_t output_token) = 0;
+ virtual OM_uint32 inquire_context(
+ OM_uint32* minor_status,
+ const gss_ctx_id_t context_handle,
+ gss_name_t* src_name,
+ gss_name_t* targ_name,
+ OM_uint32* lifetime_rec,
+ gss_OID* mech_type,
+ OM_uint32* ctx_flags,
+ int* locally_initiated,
+ int* open) = 0;
+
+ // Get the default GSSPILibrary instance. The object returned is a singleton
+ // instance, and the caller should not delete it.
+ static GSSAPILibrary* GetDefault();
+};
+
+// GSSAPISharedLibrary class is defined here so that unit tests can access it.
+class GSSAPISharedLibrary : public GSSAPILibrary {
+ public:
+ GSSAPISharedLibrary();
+ virtual ~GSSAPISharedLibrary();
+
+ // GSSAPILibrary methods:
+ virtual bool Init();
+ virtual OM_uint32 import_name(
+ OM_uint32* minor_status,
+ const gss_buffer_t input_name_buffer,
+ const gss_OID input_name_type,
+ gss_name_t* output_name);
+ virtual OM_uint32 release_name(
+ OM_uint32* minor_status,
+ gss_name_t* input_name);
+ virtual OM_uint32 release_buffer(
+ OM_uint32* minor_status,
+ gss_buffer_t buffer);
+ virtual OM_uint32 display_name(
+ OM_uint32* minor_status,
+ const gss_name_t input_name,
+ gss_buffer_t output_name_buffer,
+ gss_OID* output_name_type);
+ virtual OM_uint32 display_status(
+ OM_uint32* minor_status,
+ OM_uint32 status_value,
+ int status_type,
+ const gss_OID mech_type,
+ OM_uint32* message_contex,
+ gss_buffer_t status_string);
+ virtual OM_uint32 init_sec_context(
+ OM_uint32* minor_status,
+ const gss_cred_id_t initiator_cred_handle,
+ gss_ctx_id_t* context_handle,
+ const gss_name_t target_name,
+ const gss_OID mech_type,
+ OM_uint32 req_flags,
+ OM_uint32 time_req,
+ const gss_channel_bindings_t input_chan_bindings,
+ const gss_buffer_t input_token,
+ gss_OID* actual_mech_type,
+ gss_buffer_t output_token,
+ OM_uint32* ret_flags,
+ OM_uint32* time_rec);
+ virtual OM_uint32 wrap_size_limit(
+ OM_uint32* minor_status,
+ const gss_ctx_id_t context_handle,
+ int conf_req_flag,
+ gss_qop_t qop_req,
+ OM_uint32 req_output_size,
+ OM_uint32* max_input_size);
+ virtual OM_uint32 delete_sec_context(
+ OM_uint32* minor_status,
+ gss_ctx_id_t* context_handle,
+ gss_buffer_t output_token);
+ virtual OM_uint32 inquire_context(
+ OM_uint32* minor_status,
+ const gss_ctx_id_t context_handle,
+ gss_name_t* src_name,
+ gss_name_t* targ_name,
+ OM_uint32* lifetime_rec,
+ gss_OID* mech_type,
+ OM_uint32* ctx_flags,
+ int* locally_initiated,
+ int* open);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(HttpAuthGSSAPIPOSIXTest, GSSAPIStartup);
+
+ bool InitImpl();
+ // Finds a usable dynamic library for GSSAPI and loads it. The criteria are:
+ // 1. The library must exist.
+ // 2. The library must export the functions we need.
+ base::NativeLibrary LoadSharedLibrary();
+ bool BindMethods(base::NativeLibrary lib);
+
+ bool initialized_;
+
+ // Need some way to invalidate the library.
+ base::NativeLibrary gssapi_library_;
+
+ // Function pointers
+ gss_import_name_type import_name_;
+ gss_release_name_type release_name_;
+ gss_release_buffer_type release_buffer_;
+ gss_display_name_type display_name_;
+ gss_display_status_type display_status_;
+ gss_init_sec_context_type init_sec_context_;
+ gss_wrap_size_limit_type wrap_size_limit_;
+ gss_delete_sec_context_type delete_sec_context_;
+ gss_inquire_context_type inquire_context_;
+};
+
+// ScopedSecurityContext releases a gss_ctx_id_t when it goes out of
+// scope.
+class ScopedSecurityContext {
+ public:
+ ScopedSecurityContext(GSSAPILibrary* gssapi_lib);
+ ~ScopedSecurityContext();
+
+ const gss_ctx_id_t get() const { return security_context_; }
+ gss_ctx_id_t* receive() { return &security_context_; }
+
+ private:
+ gss_ctx_id_t security_context_;
+ GSSAPILibrary* gssapi_lib_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedSecurityContext);
+};
+
+
+// TODO(ahendrickson): Share code with HttpAuthSSPI.
+class HttpAuthGSSAPI {
+ public:
+ HttpAuthGSSAPI(GSSAPILibrary* library,
+ const std::string& scheme,
+ const gss_OID gss_oid);
+ ~HttpAuthGSSAPI();
+
+ bool Init();
+
+ bool NeedsIdentity() const;
+ bool IsFinalRound() const;
+
+ bool ParseChallenge(HttpAuth::ChallengeTokenizer* tok);
+
+ // Generates an authentication token.
+ // The return value is an error code. If it's not |OK|, the value of
+ // |*auth_token| is unspecified.
+ // |spn| is the Service Principal Name of the server that the token is
+ // being generated for.
+ // If this is the first round of a multiple round scheme, credentials are
+ // obtained using |*username| and |*password|. If |username| and |password|
+ // are NULL, the default credentials are used instead.
+ int GenerateAuthToken(const std::wstring* username,
+ const std::wstring* password,
+ const std::wstring& spn,
+ std::string* auth_token);
+
+ private:
+ int OnFirstRound(const std::wstring* username,
+ const std::wstring* password);
+ int GetNextSecurityToken(const std::wstring& spn,
+ gss_buffer_t in_token,
+ gss_buffer_t out_token);
+
+ std::string scheme_;
+ std::wstring username_;
+ std::wstring password_;
+ gss_OID gss_oid_;
+ GSSAPILibrary* library_;
+ std::string decoded_server_auth_token_;
+ ScopedSecurityContext scoped_sec_context_;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_AUTH_GSSAPI_POSIX_H_
diff --git a/net/http/http_auth_gssapi_posix_unittest.cc b/net/http/http_auth_gssapi_posix_unittest.cc
new file mode 100644
index 0000000..e66bf85
--- /dev/null
+++ b/net/http/http_auth_gssapi_posix_unittest.cc
@@ -0,0 +1,147 @@
+// 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 "net/http/http_auth_gssapi_posix.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/native_library.h"
+#include "base/scoped_ptr.h"
+#include "net/http/mock_gssapi_library_posix.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+// gss_buffer_t helpers.
+void ClearBuffer(gss_buffer_t dest) {
+ if (!dest)
+ return;
+ dest->length = 0;
+ delete [] reinterpret_cast<char*>(dest->value);
+ dest->value = NULL;
+}
+
+void SetBuffer(gss_buffer_t dest, const void* src, size_t length) {
+ if (!dest)
+ return;
+ ClearBuffer(dest);
+ if (!src)
+ return;
+ dest->length = length;
+ if (length) {
+ dest->value = new char[length];
+ memcpy(dest->value, src, length);
+ }
+}
+
+void CopyBuffer(gss_buffer_t dest, const gss_buffer_t src) {
+ if (!dest)
+ return;
+ ClearBuffer(dest);
+ if (!src)
+ return;
+ SetBuffer(dest, src->value, src->length);
+}
+
+} // namespace
+
+TEST(HttpAuthGSSAPIPOSIXTest, GSSAPIStartup) {
+ // TODO(ahendrickson): Manipulate the libraries and paths to test each of the
+ // libraries we expect, and also whether or not they have the interface
+ // functions we want.
+ GSSAPILibrary* gssapi = GSSAPILibrary::GetDefault();
+ DCHECK(gssapi);
+ gssapi->Init();
+}
+
+TEST(HttpAuthGSSAPIPOSIXTest, GSSAPICycle) {
+ scoped_ptr<test::MockGSSAPILibrary> mock_library(new test::MockGSSAPILibrary);
+ DCHECK(mock_library.get());
+ mock_library->Init();
+ const char kAuthResponse[] = "Mary had a little lamb";
+ test::GssContextMockImpl context1(
+ "localhost", // Source name
+ "example.com", // Target name
+ 23, // Lifetime
+ *GSS_C_NT_HOSTBASED_SERVICE, // Mechanism
+ 0, // Context flags
+ 1, // Locally initiated
+ 0); // Open
+ test::GssContextMockImpl context2(
+ "localhost", // Source name
+ "example.com", // Target name
+ 23, // Lifetime
+ *GSS_C_NT_HOSTBASED_SERVICE, // Mechanism
+ 0, // Context flags
+ 1, // Locally initiated
+ 1); // Open
+ test::MockGSSAPILibrary::SecurityContextQuery queries[] = {
+ { "Negotiate", // Package name
+ GSS_S_CONTINUE_NEEDED, // Major response code
+ 0, // Minor response code
+ context1, // Context
+ { 0, NULL }, // Expected input token
+ { arraysize(kAuthResponse),
+ const_cast<char*>(kAuthResponse) } // Output token
+ },
+ { "Negotiate", // Package name
+ GSS_S_COMPLETE, // Major response code
+ 0, // Minor response code
+ context2, // Context
+ { arraysize(kAuthResponse),
+ const_cast<char*>(kAuthResponse) }, // Expected input token
+ { arraysize(kAuthResponse),
+ const_cast<char*>(kAuthResponse) } // Output token
+ },
+ };
+
+ for (size_t i = 0; i < arraysize(queries); ++i) {
+ mock_library->ExpectSecurityContext(queries[i].expected_package,
+ queries[i].response_code,
+ queries[i].minor_response_code,
+ queries[i].context_info,
+ queries[i].expected_input_token,
+ queries[i].output_token);
+ }
+
+ OM_uint32 major_status = 0;
+ OM_uint32 minor_status = 0;
+ gss_cred_id_t initiator_cred_handle = NULL;
+ gss_ctx_id_t context_handle = NULL;
+ gss_name_t target_name = NULL;
+ gss_OID mech_type = NULL;
+ OM_uint32 req_flags = 0;
+ OM_uint32 time_req = 25;
+ gss_channel_bindings_t input_chan_bindings = NULL;
+ gss_buffer_desc input_token = { 0, NULL };
+ gss_OID actual_mech_type= NULL;
+ gss_buffer_desc output_token = { 0, NULL };
+ OM_uint32 ret_flags = 0;
+ OM_uint32 time_rec = 0;
+ for (size_t i = 0; i < arraysize(queries); ++i) {
+ major_status = mock_library->init_sec_context(&minor_status,
+ initiator_cred_handle,
+ &context_handle,
+ target_name,
+ mech_type,
+ req_flags,
+ time_req,
+ input_chan_bindings,
+ &input_token,
+ &actual_mech_type,
+ &output_token,
+ &ret_flags,
+ &time_rec);
+ CopyBuffer(&input_token, &output_token);
+ ClearBuffer(&output_token);
+ }
+ ClearBuffer(&input_token);
+ major_status = mock_library->delete_sec_context(&minor_status,
+ &context_handle,
+ GSS_C_NO_BUFFER);
+}
+
+} // namespace net
diff --git a/net/http/http_auth_handler.cc b/net/http/http_auth_handler.cc
index 9abdd9a..0bb017b 100644
--- a/net/http/http_auth_handler.cc
+++ b/net/http/http_auth_handler.cc
@@ -1,22 +1,48 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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/logging.h"
#include "net/http/http_auth_handler.h"
+#include "base/histogram.h"
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "net/base/net_errors.h"
+
namespace net {
-bool HttpAuthHandler::InitFromChallenge(std::string::const_iterator begin,
- std::string::const_iterator end,
- HttpAuth::Target target,
- const GURL& origin) {
+HttpAuthHandler::HttpAuthHandler()
+ : score_(-1),
+ target_(HttpAuth::AUTH_NONE),
+ properties_(-1),
+ original_callback_(NULL),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ wrapper_callback_(
+ this, &HttpAuthHandler::OnGenerateAuthTokenComplete)) {
+}
+
+HttpAuthHandler::~HttpAuthHandler() {
+}
+
+//static
+std::string HttpAuthHandler::GenerateHistogramNameFromScheme(
+ const std::string& scheme) {
+ return StringPrintf("Net.AuthGenerateToken_%s", scheme.c_str());
+}
+
+bool HttpAuthHandler::InitFromChallenge(
+ HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ const BoundNetLog& net_log) {
origin_ = origin;
target_ = target;
score_ = -1;
properties_ = -1;
+ net_log_ = net_log;
- bool ok = Init(begin, end);
+ auth_challenge_ = challenge->challenge_text();
+ bool ok = Init(challenge);
// Init() is expected to set the scheme, realm, score, and properties. The
// realm may be empty.
@@ -24,7 +50,69 @@
DCHECK(!ok || score_ != -1);
DCHECK(!ok || properties_ != -1);
+ if (ok)
+ histogram_ = Histogram::FactoryTimeGet(
+ GenerateHistogramNameFromScheme(scheme()),
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromSeconds(10), 50,
+ Histogram::kUmaTargetedHistogramFlag);
+
return ok;
}
+namespace {
+
+NetLog::EventType EventTypeFromAuthTarget(HttpAuth::Target target) {
+ switch (target) {
+ case HttpAuth::AUTH_PROXY:
+ return NetLog::TYPE_AUTH_PROXY;
+ case HttpAuth::AUTH_SERVER:
+ return NetLog::TYPE_AUTH_SERVER;
+ default:
+ NOTREACHED();
+ return NetLog::TYPE_CANCELLED;
+ }
+}
+
+} // namespace
+
+int HttpAuthHandler::GenerateAuthToken(const std::wstring* username,
+ const std::wstring* password,
+ const HttpRequestInfo* request,
+ CompletionCallback* callback,
+ std::string* auth_token) {
+ // TODO(cbentzel): Enforce non-NULL callback after cleaning up SocketStream.
+ DCHECK(request);
+ DCHECK((username == NULL) == (password == NULL));
+ DCHECK(username != NULL || AllowsDefaultCredentials());
+ DCHECK(auth_token != NULL);
+ DCHECK(original_callback_ == NULL);
+ DCHECK(histogram_.get());
+ original_callback_ = callback;
+ net_log_.BeginEvent(EventTypeFromAuthTarget(target_), NULL);
+ generate_auth_token_start_ = base::TimeTicks::Now();
+ int rv = GenerateAuthTokenImpl(username, password, request,
+ &wrapper_callback_, auth_token);
+ if (rv != ERR_IO_PENDING)
+ FinishGenerateAuthToken();
+ return rv;
+}
+
+void HttpAuthHandler::OnGenerateAuthTokenComplete(int rv) {
+ CompletionCallback* callback = original_callback_;
+ FinishGenerateAuthToken();
+ if (callback)
+ callback->Run(rv);
+}
+
+void HttpAuthHandler::FinishGenerateAuthToken() {
+ // TOOD(cbentzel): Should this be done in OK case only?
+ DCHECK(histogram_.get());
+ base::TimeDelta generate_auth_token_duration =
+ base::TimeTicks::Now() - generate_auth_token_start_;
+ histogram_->AddTime(generate_auth_token_duration);
+ net_log_.EndEvent(EventTypeFromAuthTarget(target_), NULL);
+ original_callback_ = NULL;
+}
+
} // namespace net
diff --git a/net/http/http_auth_handler.h b/net/http/http_auth_handler.h
index 02a410e..ad8c939 100644
--- a/net/http/http_auth_handler.h
+++ b/net/http/http_auth_handler.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -7,25 +7,60 @@
#include <string>
-#include "base/ref_counted.h"
+#include "base/time.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_log.h"
#include "net/http/http_auth.h"
+class Histogram;
+
namespace net {
-class HttpRequestInfo;
+class HostResolver;
class ProxyInfo;
+struct HttpRequestInfo;
// HttpAuthHandler is the interface for the authentication schemes
-// (basic, digest, ...)
-// The registry mapping auth-schemes to implementations is hardcoded in
-// HttpAuth::CreateAuthHandler().
-class HttpAuthHandler : public base::RefCounted<HttpAuthHandler> {
+// (basic, digest, NTLM, Negotiate).
+// HttpAuthHandler objects are typically created by an HttpAuthHandlerFactory.
+class HttpAuthHandler {
public:
- // Initialize the handler by parsing a challenge string.
- bool InitFromChallenge(std::string::const_iterator begin,
- std::string::const_iterator end,
+ HttpAuthHandler();
+ virtual ~HttpAuthHandler();
+
+ // Initializes the handler using a challenge issued by a server.
+ // |challenge| must be non-NULL and have already tokenized the
+ // authentication scheme, but none of the tokens occuring after the
+ // authentication scheme. |target| and |origin| are both stored
+ // for later use, and are not part of the initial challenge.
+ bool InitFromChallenge(HttpAuth::ChallengeTokenizer* challenge,
HttpAuth::Target target,
- const GURL& origin);
+ const GURL& origin,
+ const BoundNetLog& net_log);
+
+ // Generates an authentication token, potentially asynchronously.
+ //
+ // When |username| and |password| are NULL, the default credentials for
+ // the currently logged in user are used. |AllowsDefaultCredentials()| MUST be
+ // true in this case.
+ //
+ // |request|, |callback|, and |auth_token| must be non-NULL.
+ //
+ // The return value is a net error code.
+ // If |OK| is returned, |*auth_token| is filled in with an authentication
+ // token which can be inserted in the HTTP request.
+ // If |ERR_IO_PENDING| is returned, |*auth_token| will be filled in
+ // asynchronously and |callback| will be invoked. The lifetime of
+ // |request|, |callback|, and |auth_token| must last until |callback| is
+ // invoked, but |username| and |password| are only used during the initial
+ // call.
+ // Otherwise, there was a problem generating a token synchronously, and the
+ // value of |*auth_token| is unspecified.
+ int GenerateAuthToken(const std::wstring* username,
+ const std::wstring* password,
+ const HttpRequestInfo* request,
+ CompletionCallback* callback,
+ std::string* auth_token);
// Lowercase name of the auth scheme
const std::string& scheme() const {
@@ -37,6 +72,11 @@
return realm_;
}
+ // The challenge which was issued when creating the handler.
+ const std::string challenge() const {
+ return auth_challenge_;
+ }
+
// Numeric rank based on the challenge's security level. Higher
// numbers are better. Used by HttpAuth::ChooseBestChallenge().
int score() const {
@@ -72,11 +112,12 @@
// single-round schemes.
virtual bool IsFinalRound() { return true; }
- // Generate the Authorization header value.
- virtual std::string GenerateCredentials(const std::wstring& username,
- const std::wstring& password,
- const HttpRequestInfo* request,
- const ProxyInfo* proxy) = 0;
+ // Returns whether the default credentials may be used for the |origin| passed
+ // into |InitFromChallenge|. If true, the user does not need to be prompted
+ // for username and password to establish credentials.
+ // NOTE: SSO is a potential security risk.
+ // TODO(cbentzel): Add a pointer to Firefox documentation about risk.
+ virtual bool AllowsDefaultCredentials() { return false; }
protected:
enum Property {
@@ -84,24 +125,34 @@
IS_CONNECTION_BASED = 1 << 1,
};
- friend class base::RefCounted<HttpAuthHandler>;
-
- virtual ~HttpAuthHandler() { }
-
- // Initialize the handler by parsing a challenge string.
+ // Initializes the handler using a challenge issued by a server.
+ // |challenge| must be non-NULL and have already tokenized the
+ // authentication scheme, but none of the tokens occuring after the
+ // authentication scheme.
// Implementations are expcted to initialize the following members:
// scheme_, realm_, score_, properties_
- virtual bool Init(std::string::const_iterator challenge_begin,
- std::string::const_iterator challenge_end) = 0;
+ virtual bool Init(HttpAuth::ChallengeTokenizer* challenge) = 0;
- // The lowercase auth-scheme {"basic", "digest", "ntlm", ...}
+ // |GenerateAuthTokenImpl()} is the auth-scheme specific implementation
+ // of generating the next auth token. Callers sohuld use |GenerateAuthToken()|
+ // which will in turn call |GenerateAuthTokenImpl()|
+ virtual int GenerateAuthTokenImpl(const std::wstring* username,
+ const std::wstring* password,
+ const HttpRequestInfo* request,
+ CompletionCallback* callback,
+ std::string* auth_token) = 0;
+
+ // The lowercase auth-scheme {"basic", "digest", "ntlm", "negotiate"}
std::string scheme_;
// The realm. Used by "basic" and "digest".
std::string realm_;
+ // The auth challenge.
+ std::string auth_challenge_;
+
// The {scheme, host, port} for the authentication target. Used by "ntlm"
- // to construct the service principal name.
+ // and "negotiate" to construct the service principal name.
GURL origin_;
// The score for this challenge. Higher numbers are better.
@@ -113,6 +164,19 @@
// A bitmask of the properties of the authentication scheme.
int properties_;
+
+ BoundNetLog net_log_;
+
+ private:
+ void OnGenerateAuthTokenComplete(int rv);
+ void FinishGenerateAuthToken();
+ static std::string GenerateHistogramNameFromScheme(const std::string& scheme);
+
+ CompletionCallback* original_callback_;
+ CompletionCallbackImpl<HttpAuthHandler> wrapper_callback_;
+ // When GenerateAuthToken was called.
+ base::TimeTicks generate_auth_token_start_;
+ scoped_refptr<Histogram> histogram_;
};
} // namespace net
diff --git a/net/http/http_auth_handler_basic.cc b/net/http/http_auth_handler_basic.cc
index 71c310c..efd8c5e 100644
--- a/net/http/http_auth_handler_basic.cc
+++ b/net/http/http_auth_handler_basic.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -8,6 +8,8 @@
#include "base/base64.h"
#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "net/base/net_errors.h"
#include "net/http/http_auth.h"
namespace net {
@@ -20,38 +22,63 @@
//
// We allow it to be compatibility with certain embedded webservers that don't
// include a realm (see http://crbug.com/20984.)
-bool HttpAuthHandlerBasic::Init(std::string::const_iterator challenge_begin,
- std::string::const_iterator challenge_end) {
+bool HttpAuthHandlerBasic::Init(HttpAuth::ChallengeTokenizer* challenge) {
scheme_ = "basic";
score_ = 1;
properties_ = 0;
// Verify the challenge's auth-scheme.
- HttpAuth::ChallengeTokenizer challenge_tok(challenge_begin, challenge_end);
- if (!challenge_tok.valid() ||
- !LowerCaseEqualsASCII(challenge_tok.scheme(), "basic"))
+ if (!challenge->valid() ||
+ !LowerCaseEqualsASCII(challenge->scheme(), "basic"))
return false;
// Extract the realm (may be missing).
- while (challenge_tok.GetNext()) {
- if (LowerCaseEqualsASCII(challenge_tok.name(), "realm"))
- realm_ = challenge_tok.unquoted_value();
+ while (challenge->GetNext()) {
+ if (LowerCaseEqualsASCII(challenge->name(), "realm"))
+ realm_ = challenge->unquoted_value();
}
- return challenge_tok.valid();
+ return challenge->valid();
}
-std::string HttpAuthHandlerBasic::GenerateCredentials(
- const std::wstring& username,
- const std::wstring& password,
+int HttpAuthHandlerBasic::GenerateAuthTokenImpl(
+ const std::wstring* username,
+ const std::wstring* password,
const HttpRequestInfo*,
- const ProxyInfo*) {
+ CompletionCallback*,
+ std::string* auth_token) {
// TODO(eroman): is this the right encoding of username/password?
std::string base64_username_password;
- if (!base::Base64Encode(WideToUTF8(username) + ":" + WideToUTF8(password),
- &base64_username_password))
- return std::string(); // FAIL
- return std::string("Basic ") + base64_username_password;
+ if (!base::Base64Encode(WideToUTF8(*username) + ":" + WideToUTF8(*password),
+ &base64_username_password)) {
+ LOG(ERROR) << "Unexpected problem Base64 encoding.";
+ return ERR_UNEXPECTED;
+ }
+ *auth_token = "Basic " + base64_username_password;
+ return OK;
+}
+
+HttpAuthHandlerBasic::Factory::Factory() {
+}
+
+HttpAuthHandlerBasic::Factory::~Factory() {
+}
+
+int HttpAuthHandlerBasic::Factory::CreateAuthHandler(
+ HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason reason,
+ int digest_nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) {
+ // TODO(cbentzel): Move towards model of parsing in the factory
+ // method and only constructing when valid.
+ scoped_ptr<HttpAuthHandler> tmp_handler(new HttpAuthHandlerBasic());
+ if (!tmp_handler->InitFromChallenge(challenge, target, origin, net_log))
+ return ERR_INVALID_RESPONSE;
+ handler->swap(tmp_handler);
+ return OK;
}
} // namespace net
diff --git a/net/http/http_auth_handler_basic.h b/net/http/http_auth_handler_basic.h
index 679c91a..6ecb80a 100644
--- a/net/http/http_auth_handler_basic.h
+++ b/net/http/http_auth_handler_basic.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -6,19 +6,35 @@
#define NET_HTTP_HTTP_AUTH_HANDLER_BASIC_H_
#include "net/http/http_auth_handler.h"
+#include "net/http/http_auth_handler_factory.h"
namespace net {
// Code for handling http basic authentication.
class HttpAuthHandlerBasic : public HttpAuthHandler {
public:
- virtual std::string GenerateCredentials(const std::wstring& username,
- const std::wstring& password,
- const HttpRequestInfo*,
- const ProxyInfo*);
+ class Factory : public HttpAuthHandlerFactory {
+ public:
+ Factory();
+ virtual ~Factory();
+
+ virtual int CreateAuthHandler(HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason reason,
+ int digest_nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler);
+ };
+
protected:
- virtual bool Init(std::string::const_iterator challenge_begin,
- std::string::const_iterator challenge_end);
+ virtual bool Init(HttpAuth::ChallengeTokenizer* challenge);
+
+ virtual int GenerateAuthTokenImpl(const std::wstring* username,
+ const std::wstring* password,
+ const HttpRequestInfo* request,
+ CompletionCallback* callback,
+ std::string* auth_token);
private:
~HttpAuthHandlerBasic() {}
diff --git a/net/http/http_auth_handler_basic_unittest.cc b/net/http/http_auth_handler_basic_unittest.cc
index d7a1437..12e8830 100644
--- a/net/http/http_auth_handler_basic_unittest.cc
+++ b/net/http/http_auth_handler_basic_unittest.cc
@@ -1,15 +1,17 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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 "testing/gtest/include/gtest/gtest.h"
#include "base/basictypes.h"
+#include "net/base/net_errors.h"
#include "net/http/http_auth_handler_basic.h"
+#include "net/http/http_request_info.h"
namespace net {
-TEST(HttpAuthHandlerBasicTest, GenerateCredentials) {
+TEST(HttpAuthHandlerBasicTest, GenerateAuthToken) {
static const struct {
const wchar_t* username;
const wchar_t* password;
@@ -24,48 +26,53 @@
{ L"", L"", "Basic Og==" },
};
GURL origin("http://www.example.com");
+ HttpAuthHandlerBasic::Factory factory;
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
std::string challenge = "Basic realm=\"Atlantis\"";
- scoped_refptr<HttpAuthHandlerBasic> basic = new HttpAuthHandlerBasic;
- bool ok = basic->InitFromChallenge(challenge.begin(), challenge.end(),
- HttpAuth::AUTH_SERVER, origin);
- EXPECT_TRUE(ok);
- std::string credentials = basic->GenerateCredentials(tests[i].username,
- tests[i].password,
- NULL, NULL);
- EXPECT_STREQ(tests[i].expected_credentials, credentials.c_str());
+ scoped_ptr<HttpAuthHandler> basic;
+ EXPECT_EQ(OK, factory.CreateAuthHandlerFromString(
+ challenge, HttpAuth::AUTH_SERVER, origin, BoundNetLog(), &basic));
+ std::wstring username(tests[i].username);
+ std::wstring password(tests[i].password);
+ HttpRequestInfo request_info;
+ std::string auth_token;
+ int rv = basic->GenerateAuthToken(&username, &password, &request_info,
+ NULL, &auth_token);
+ EXPECT_EQ(OK, rv);
+ EXPECT_STREQ(tests[i].expected_credentials, auth_token.c_str());
}
}
TEST(HttpAuthHandlerBasicTest, InitFromChallenge) {
static const struct {
const char* challenge;
- bool expected_success;
+ int expected_rv;
const char* expected_realm;
} tests[] = {
// No realm (we allow this even though realm is supposed to be required
// according to RFC 2617.)
{
"Basic",
- true,
+ OK,
"",
},
// Realm is empty string.
{
"Basic realm=\"\"",
- true,
+ OK,
"",
},
};
+ HttpAuthHandlerBasic::Factory factory;
GURL origin("http://www.example.com");
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
std::string challenge = tests[i].challenge;
- scoped_refptr<HttpAuthHandlerBasic> basic = new HttpAuthHandlerBasic;
- bool ok = basic->InitFromChallenge(challenge.begin(), challenge.end(),
- HttpAuth::AUTH_SERVER, origin);
- EXPECT_EQ(tests[i].expected_success, ok);
- if (ok)
+ scoped_ptr<HttpAuthHandler> basic;
+ int rv = factory.CreateAuthHandlerFromString(
+ challenge, HttpAuth::AUTH_SERVER, origin, BoundNetLog(), &basic);
+ EXPECT_EQ(tests[i].expected_rv, rv);
+ if (rv == OK)
EXPECT_EQ(tests[i].expected_realm, basic->realm());
}
}
diff --git a/net/http/http_auth_handler_digest.cc b/net/http/http_auth_handler_digest.cc
index a0c443c..90090a6 100644
--- a/net/http/http_auth_handler_digest.cc
+++ b/net/http/http_auth_handler_digest.cc
@@ -1,12 +1,15 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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 "net/http/http_auth_handler_digest.h"
+#include "base/logging.h"
#include "base/md5.h"
#include "base/rand_util.h"
#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "net/base/net_errors.h"
#include "net/base/net_util.h"
#include "net/http/http_auth.h"
#include "net/http/http_request_info.h"
@@ -42,10 +45,15 @@
//=====================+==========================================+
+//static
+bool HttpAuthHandlerDigest::fixed_cnonce_ = false;
+
// static
std::string HttpAuthHandlerDigest::GenerateNonce() {
// This is how mozilla generates their cnonce -- a 16 digit hex string.
static const char domain[] = "0123456789abcdef";
+ if (fixed_cnonce_)
+ return std::string(domain);
std::string cnonce;
cnonce.reserve(16);
for (int i = 0; i < 16; ++i)
@@ -77,40 +85,34 @@
}
}
-std::string HttpAuthHandlerDigest::GenerateCredentials(
- const std::wstring& username,
- const std::wstring& password,
+int HttpAuthHandlerDigest::GenerateAuthTokenImpl(
+ const std::wstring* username,
+ const std::wstring* password,
const HttpRequestInfo* request,
- const ProxyInfo* proxy) {
+ CompletionCallback* callback,
+ std::string* auth_token) {
// Generate a random client nonce.
std::string cnonce = GenerateNonce();
- // The nonce-count should be incremented after re-use per the spec.
- // This may not be possible when there are multiple connections to the
- // server though:
- // https://bugzilla.mozilla.org/show_bug.cgi?id=114451
- int nonce_count = ++nonce_count_;
-
// Extract the request method and path -- the meaning of 'path' is overloaded
// in certain cases, to be a hostname.
std::string method;
std::string path;
- GetRequestMethodAndPath(request, proxy, &method, &path);
+ GetRequestMethodAndPath(request, &method, &path);
- return AssembleCredentials(method, path,
- // TODO(eroman): is this the right encoding?
- WideToUTF8(username),
- WideToUTF8(password),
- cnonce, nonce_count);
+ *auth_token = AssembleCredentials(method, path,
+ // TODO(eroman): is this the right encoding?
+ WideToUTF8(*username),
+ WideToUTF8(*password),
+ cnonce, nonce_count_);
+ return OK;
}
void HttpAuthHandlerDigest::GetRequestMethodAndPath(
const HttpRequestInfo* request,
- const ProxyInfo* proxy,
std::string* method,
std::string* path) const {
DCHECK(request);
- DCHECK(proxy);
const GURL& url = request->url;
@@ -158,7 +160,7 @@
std::string nc = StringPrintf("%08x", nonce_count);
std::string authorization = std::string("Digest username=") +
- HttpUtil::Quote(username);
+ HttpUtil::Quote(username);
authorization += ", realm=" + HttpUtil::Quote(realm_);
authorization += ", nonce=" + HttpUtil::Quote(nonce_);
authorization += ", uri=" + HttpUtil::Quote(path);
@@ -204,8 +206,7 @@
// send the realm (See http://crbug.com/20984 for an instance where a
// webserver was not sending the realm with a BASIC challenge).
bool HttpAuthHandlerDigest::ParseChallenge(
- std::string::const_iterator challenge_begin,
- std::string::const_iterator challenge_end) {
+ HttpAuth::ChallengeTokenizer* challenge) {
scheme_ = "digest";
score_ = 2;
properties_ = ENCRYPTS_IDENTITY;
@@ -216,24 +217,23 @@
qop_ = QOP_UNSPECIFIED;
realm_ = nonce_ = domain_ = opaque_ = std::string();
- HttpAuth::ChallengeTokenizer props(challenge_begin, challenge_end);
-
- if (!props.valid() || !LowerCaseEqualsASCII(props.scheme(), "digest"))
+ if (!challenge->valid() ||
+ !LowerCaseEqualsASCII(challenge->scheme(), "digest"))
return false; // FAIL -- Couldn't match auth-scheme.
// Loop through all the properties.
- while (props.GetNext()) {
- if (props.value().empty()) {
+ while (challenge->GetNext()) {
+ if (challenge->value().empty()) {
DLOG(INFO) << "Invalid digest property";
return false;
}
- if (!ParseChallengeProperty(props.name(), props.unquoted_value()))
+ if (!ParseChallengeProperty(challenge->name(), challenge->unquoted_value()))
return false; // FAIL -- couldn't parse a property.
}
// Check if tokenizer failed.
- if (!props.valid())
+ if (!challenge->valid())
return false; // FAIL
// Check that a minimum set of properties were provided.
@@ -283,4 +283,28 @@
return true;
}
+HttpAuthHandlerDigest::Factory::Factory() {
+}
+
+HttpAuthHandlerDigest::Factory::~Factory() {
+}
+
+int HttpAuthHandlerDigest::Factory::CreateAuthHandler(
+ HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason reason,
+ int digest_nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) {
+ // TODO(cbentzel): Move towards model of parsing in the factory
+ // method and only constructing when valid.
+ scoped_ptr<HttpAuthHandler> tmp_handler(
+ new HttpAuthHandlerDigest(digest_nonce_count));
+ if (!tmp_handler->InitFromChallenge(challenge, target, origin, net_log))
+ return ERR_INVALID_RESPONSE;
+ handler->swap(tmp_handler);
+ return OK;
+}
+
} // namespace net
diff --git a/net/http/http_auth_handler_digest.h b/net/http/http_auth_handler_digest.h
index b4c8687..2aa9028 100644
--- a/net/http/http_auth_handler_digest.h
+++ b/net/http/http_auth_handler_digest.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -6,6 +6,7 @@
#define NET_HTTP_HTTP_AUTH_HANDLER_DIGEST_H_
#include "net/http/http_auth_handler.h"
+#include "net/http/http_auth_handler_factory.h"
// This is needed for the FRIEND_TEST() macro.
#include "testing/gtest/include/gtest/gtest_prod.h"
@@ -15,21 +16,35 @@
// Code for handling http digest authentication.
class HttpAuthHandlerDigest : public HttpAuthHandler {
public:
- virtual std::string GenerateCredentials(const std::wstring& username,
- const std::wstring& password,
- const HttpRequestInfo* request,
- const ProxyInfo* proxy);
+ class Factory : public HttpAuthHandlerFactory {
+ public:
+ Factory();
+ virtual ~Factory();
+
+ virtual int CreateAuthHandler(HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason reason,
+ int digest_nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler);
+ };
protected:
- virtual bool Init(std::string::const_iterator challenge_begin,
- std::string::const_iterator challenge_end) {
- nonce_count_ = 0;
- return ParseChallenge(challenge_begin, challenge_end);
+ virtual bool Init(HttpAuth::ChallengeTokenizer* challenge) {
+ return ParseChallenge(challenge);
}
+ virtual int GenerateAuthTokenImpl(const std::wstring* username,
+ const std::wstring* password,
+ const HttpRequestInfo* request,
+ CompletionCallback* callback,
+ std::string* auth_token);
+
private:
FRIEND_TEST(HttpAuthHandlerDigestTest, ParseChallenge);
FRIEND_TEST(HttpAuthHandlerDigestTest, AssembleCredentials);
+ FRIEND_TEST(HttpNetworkTransactionTest, DigestPreAuthNonceCount);
// Possible values for the "algorithm" property.
enum DigestAlgorithm {
@@ -53,12 +68,13 @@
QOP_AUTH_INT = 1 << 1,
};
+ explicit HttpAuthHandlerDigest(int nonce_count) : nonce_count_(nonce_count) {}
+
~HttpAuthHandlerDigest() {}
// Parse the challenge, saving the results into this instance.
// Returns true on success.
- bool ParseChallenge(std::string::const_iterator challenge_begin,
- std::string::const_iterator challenge_end);
+ bool ParseChallenge(HttpAuth::ChallengeTokenizer* challenge);
// Parse an individual property. Returns true on success.
bool ParseChallengeProperty(const std::string& name,
@@ -74,7 +90,6 @@
// Extract the method and path of the request, as needed by
// the 'A2' production. (path may be a hostname for proxy).
void GetRequestMethodAndPath(const HttpRequestInfo* request,
- const ProxyInfo* proxy,
std::string* method,
std::string* path) const;
@@ -94,6 +109,11 @@
const std::string& cnonce,
int nonce_count) const;
+ // Forces cnonce to be the same each time. This is used for unit tests.
+ static void SetFixedCnonce(bool fixed_cnonce) {
+ fixed_cnonce_ = fixed_cnonce;
+ }
+
// Information parsed from the challenge.
std::string nonce_;
std::string domain_;
@@ -103,6 +123,9 @@
int qop_; // Bitfield of QualityOfProtection
int nonce_count_;
+
+ // Forces the cnonce to be the same each time, for unit tests.
+ static bool fixed_cnonce_;
};
} // namespace net
diff --git a/net/http/http_auth_handler_digest_unittest.cc b/net/http/http_auth_handler_digest_unittest.cc
index eb5649b..b1782f6 100644
--- a/net/http/http_auth_handler_digest_unittest.cc
+++ b/net/http/http_auth_handler_digest_unittest.cc
@@ -1,10 +1,11 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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 "testing/gtest/include/gtest/gtest.h"
#include "base/basictypes.h"
+#include "net/base/net_errors.h"
#include "net/http/http_auth_handler_digest.h"
namespace net {
@@ -63,7 +64,7 @@
{ // Check that md5-sess is recognized, as is single QOP
"Digest nonce=\"xyz\", algorithm=\"md5-sess\", "
- "realm=\"Oblivion\", qop=\"auth\"",
+ "realm=\"Oblivion\", qop=\"auth\"",
true,
"Oblivion",
"xyz",
@@ -100,13 +101,25 @@
}
};
+ GURL origin("http://www.example.com");
+ scoped_ptr<HttpAuthHandlerDigest::Factory> factory(
+ new HttpAuthHandlerDigest::Factory());
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
- std::string challenge(tests[i].challenge);
-
- scoped_refptr<HttpAuthHandlerDigest> digest = new HttpAuthHandlerDigest;
- bool ok = digest->ParseChallenge(challenge.begin(), challenge.end());
-
- EXPECT_EQ(tests[i].parsed_success, ok);
+ scoped_ptr<HttpAuthHandler> handler;
+ int rv = factory->CreateAuthHandlerFromString(tests[i].challenge,
+ HttpAuth::AUTH_SERVER,
+ origin,
+ BoundNetLog(),
+ &handler);
+ if (tests[i].parsed_success) {
+ EXPECT_EQ(OK, rv);
+ } else {
+ EXPECT_NE(OK, rv);
+ continue;
+ }
+ ASSERT_TRUE(handler != NULL);
+ HttpAuthHandlerDigest* digest =
+ static_cast<HttpAuthHandlerDigest*>(handler.get());
EXPECT_STREQ(tests[i].parsed_realm, digest->realm_.c_str());
EXPECT_STREQ(tests[i].parsed_nonce, digest->nonce_.c_str());
EXPECT_STREQ(tests[i].parsed_domain, digest->domain_.c_str());
@@ -249,15 +262,26 @@
}
};
GURL origin("http://www.example.com");
+ scoped_ptr<HttpAuthHandlerDigest::Factory> factory(
+ new HttpAuthHandlerDigest::Factory());
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
- scoped_refptr<HttpAuthHandlerDigest> digest = new HttpAuthHandlerDigest;
- std::string challenge = tests[i].challenge;
- EXPECT_TRUE(digest->InitFromChallenge(
- challenge.begin(), challenge.end(), HttpAuth::AUTH_SERVER, origin));
+ scoped_ptr<HttpAuthHandler> handler;
+ int rv = factory->CreateAuthHandlerFromString(tests[i].challenge,
+ HttpAuth::AUTH_SERVER,
+ origin,
+ BoundNetLog(),
+ &handler);
+ EXPECT_EQ(OK, rv);
+ ASSERT_TRUE(handler != NULL);
+ HttpAuthHandlerDigest* digest =
+ static_cast<HttpAuthHandlerDigest*>(handler.get());
std::string creds = digest->AssembleCredentials(tests[i].req_method,
- tests[i].req_path, tests[i].username, tests[i].password,
- tests[i].cnonce, tests[i].nonce_count);
+ tests[i].req_path,
+ tests[i].username,
+ tests[i].password,
+ tests[i].cnonce,
+ tests[i].nonce_count);
EXPECT_STREQ(tests[i].expected_creds, creds.c_str());
}
diff --git a/net/http/http_auth_handler_factory.cc b/net/http/http_auth_handler_factory.cc
new file mode 100644
index 0000000..c2d011b
--- /dev/null
+++ b/net/http/http_auth_handler_factory.cc
@@ -0,0 +1,119 @@
+// 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 "net/http/http_auth_handler_factory.h"
+
+#include "base/stl_util-inl.h"
+#include "base/string_util.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_auth_filter.h"
+#include "net/http/http_auth_handler_basic.h"
+#include "net/http/http_auth_handler_digest.h"
+#include "net/http/http_auth_handler_negotiate.h"
+#include "net/http/http_auth_handler_ntlm.h"
+
+namespace net {
+
+int HttpAuthHandlerFactory::CreateAuthHandlerFromString(
+ const std::string& challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) {
+ HttpAuth::ChallengeTokenizer props(challenge.begin(), challenge.end());
+ return CreateAuthHandler(&props, target, origin, CREATE_CHALLENGE, 1,
+ net_log, handler);
+}
+
+int HttpAuthHandlerFactory::CreatePreemptiveAuthHandlerFromString(
+ const std::string& challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ int digest_nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) {
+ HttpAuth::ChallengeTokenizer props(challenge.begin(), challenge.end());
+ return CreateAuthHandler(&props, target, origin, CREATE_PREEMPTIVE,
+ digest_nonce_count, net_log, handler);
+}
+
+// static
+HttpAuthHandlerRegistryFactory* HttpAuthHandlerFactory::CreateDefault() {
+ HttpAuthHandlerRegistryFactory* registry_factory =
+ new HttpAuthHandlerRegistryFactory();
+ registry_factory->RegisterSchemeFactory(
+ "basic", new HttpAuthHandlerBasic::Factory());
+ registry_factory->RegisterSchemeFactory(
+ "digest", new HttpAuthHandlerDigest::Factory());
+ registry_factory->RegisterSchemeFactory(
+ "negotiate", new HttpAuthHandlerNegotiate::Factory());
+ registry_factory->RegisterSchemeFactory(
+ "ntlm", new HttpAuthHandlerNTLM::Factory());
+ return registry_factory;
+}
+
+HttpAuthHandlerRegistryFactory::HttpAuthHandlerRegistryFactory() {
+}
+
+HttpAuthHandlerRegistryFactory::~HttpAuthHandlerRegistryFactory() {
+ STLDeleteContainerPairSecondPointers(factory_map_.begin(),
+ factory_map_.end());
+}
+
+void HttpAuthHandlerRegistryFactory::SetURLSecurityManager(
+ const std::string& scheme,
+ URLSecurityManager* security_manager) {
+ HttpAuthHandlerFactory* factory = GetSchemeFactory(scheme);
+ if (factory)
+ factory->set_url_security_manager(security_manager);
+}
+
+void HttpAuthHandlerRegistryFactory::RegisterSchemeFactory(
+ const std::string& scheme,
+ HttpAuthHandlerFactory* factory) {
+ std::string lower_scheme = StringToLowerASCII(scheme);
+ FactoryMap::iterator it = factory_map_.find(lower_scheme);
+ if (it != factory_map_.end()) {
+ delete it->second;
+ }
+ if (factory)
+ factory_map_[lower_scheme] = factory;
+ else
+ factory_map_.erase(it);
+}
+
+int HttpAuthHandlerRegistryFactory::CreateAuthHandler(
+ HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason reason,
+ int digest_nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) {
+ if (!challenge->valid()) {
+ handler->reset();
+ return ERR_INVALID_RESPONSE;
+ }
+ std::string lower_scheme = StringToLowerASCII(challenge->scheme());
+ FactoryMap::iterator it = factory_map_.find(lower_scheme);
+ if (it == factory_map_.end()) {
+ handler->reset();
+ return ERR_UNSUPPORTED_AUTH_SCHEME;
+ }
+ DCHECK(it->second);
+ return it->second->CreateAuthHandler(challenge, target, origin, reason,
+ digest_nonce_count, net_log, handler);
+}
+
+HttpAuthHandlerFactory* HttpAuthHandlerRegistryFactory::GetSchemeFactory(
+ const std::string& scheme) const {
+ std::string lower_scheme = StringToLowerASCII(scheme);
+ FactoryMap::const_iterator it = factory_map_.find(lower_scheme);
+ if (it == factory_map_.end()) {
+ return NULL; // |scheme| is not registered.
+ }
+ return it->second;
+}
+
+} // namespace net
diff --git a/net/http/http_auth_handler_factory.h b/net/http/http_auth_handler_factory.h
new file mode 100644
index 0000000..9e55350
--- /dev/null
+++ b/net/http/http_auth_handler_factory.h
@@ -0,0 +1,163 @@
+// 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.
+
+#ifndef NET_HTTP_HTTP_AUTH_HANDLER_FACTORY_H_
+#define NET_HTTP_HTTP_AUTH_HANDLER_FACTORY_H_
+
+#include <map>
+#include <string>
+
+#include "base/scoped_ptr.h"
+#include "net/http/http_auth.h"
+#include "net/http/url_security_manager.h"
+
+class GURL;
+
+namespace net {
+
+class BoundNetLog;
+class HttpAuthHandler;
+class HttpAuthHandlerRegistryFactory;
+
+// An HttpAuthHandlerFactory is used to create HttpAuthHandler objects.
+class HttpAuthHandlerFactory {
+ public:
+ HttpAuthHandlerFactory() : url_security_manager_(NULL) {}
+ virtual ~HttpAuthHandlerFactory() {}
+
+ // Sets an URL security manager. HttpAuthHandlerFactory doesn't own the URL
+ // security manager, and the URL security manager should outlive this object.
+ void set_url_security_manager(URLSecurityManager* url_security_manager) {
+ url_security_manager_ = url_security_manager;
+ }
+
+ // Retrieves the associated URL security manager.
+ URLSecurityManager* url_security_manager() {
+ return url_security_manager_;
+ }
+
+ enum CreateReason {
+ CREATE_CHALLENGE, // Create a handler in response to a challenge.
+ CREATE_PREEMPTIVE, // Create a handler preemptively.
+ };
+
+ // Creates an HttpAuthHandler object based on the authentication
+ // challenge specified by |*challenge|. |challenge| must point to a valid
+ // non-NULL tokenizer.
+ //
+ // If an HttpAuthHandler object is successfully created it is passed back to
+ // the caller through |*handler| and OK is returned.
+ //
+ // If |*challenge| specifies an unsupported authentication scheme, |*handler|
+ // is set to NULL and ERR_UNSUPPORTED_AUTH_SCHEME is returned.
+ //
+ // If |*challenge| is improperly formed, |*handler| is set to NULL and
+ // ERR_INVALID_RESPONSE is returned.
+ //
+ // |create_reason| indicates why the handler is being created. This is used
+ // since NTLM and Negotiate schemes do not support preemptive creation.
+ //
+ // |digest_nonce_count| is specifically intended for the Digest authentication
+ // scheme, and indicates the number of handlers generated for a particular
+ // server nonce challenge.
+ //
+ // For the NTLM and Negotiate handlers:
+ // If |origin| does not match the authentication method's filters for
+ // the specified |target|, ERR_INVALID_AUTH_CREDENTIALS is returned.
+ // NOTE: This will apply to ALL |origin| values if the filters are empty.
+ //
+ // |*challenge| should not be reused after a call to |CreateAuthHandler()|,
+ virtual int CreateAuthHandler(HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason create_reason,
+ int digest_nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) = 0;
+
+ // Creates an HTTP authentication handler based on the authentication
+ // challenge string |challenge|.
+ // This is a convenience function which creates a ChallengeTokenizer for
+ // |challenge| and calls |CreateAuthHandler|. See |CreateAuthHandler| for
+ // more details on return values.
+ int CreateAuthHandlerFromString(const std::string& challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler);
+
+ // Creates an HTTP authentication handler based on the authentication
+ // challenge string |challenge|.
+ // This is a convenience function which creates a ChallengeTokenizer for
+ // |challenge| and calls |CreateAuthHandler|. See |CreateAuthHandler| for
+ // more details on return values.
+ int CreatePreemptiveAuthHandlerFromString(
+ const std::string& challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ int digest_nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler);
+
+ // Creates a standard HttpAuthHandlerRegistryFactory. The caller is
+ // responsible for deleting the factory.
+ // The default factory supports Basic, Digest, NTLM, and Negotiate schemes.
+ static HttpAuthHandlerRegistryFactory* CreateDefault();
+
+ private:
+ // The URL security manager
+ URLSecurityManager* url_security_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpAuthHandlerFactory);
+};
+
+// The HttpAuthHandlerRegistryFactory dispatches create requests out
+// to other factories based on the auth scheme.
+class HttpAuthHandlerRegistryFactory : public HttpAuthHandlerFactory {
+ public:
+ HttpAuthHandlerRegistryFactory();
+ virtual ~HttpAuthHandlerRegistryFactory();
+
+ // Sets an URL security manager into the factory associated with |scheme|.
+ void SetURLSecurityManager(const std::string& scheme,
+ URLSecurityManager* url_security_manager);
+
+ // Registers a |factory| that will be used for a particular HTTP
+ // authentication scheme such as Basic, Digest, or Negotiate.
+ // The |*factory| object is assumed to be new-allocated, and its lifetime
+ // will be managed by this HttpAuthHandlerRegistryFactory object (including
+ // deleting it when it is no longer used.
+ // A NULL |factory| value means that HttpAuthHandlers's will not be created
+ // for |scheme|. If a factory object used to exist for |scheme|, it will be
+ // deleted.
+ void RegisterSchemeFactory(const std::string& scheme,
+ HttpAuthHandlerFactory* factory);
+
+ // Retrieve the factory for the specified |scheme|. If no factory exists
+ // for the |scheme|, NULL is returned. The returned factory must not be
+ // deleted by the caller, and it is guaranteed to be valid until either
+ // a new factory is registered for the same scheme, or until this
+ // registry factory is destroyed.
+ HttpAuthHandlerFactory* GetSchemeFactory(const std::string& scheme) const;
+
+ // Creates an auth handler by dispatching out to the registered factories
+ // based on the first token in |challenge|.
+ virtual int CreateAuthHandler(HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason reason,
+ int digest_nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler);
+
+ private:
+ typedef std::map<std::string, HttpAuthHandlerFactory*> FactoryMap;
+
+ FactoryMap factory_map_;
+ DISALLOW_COPY_AND_ASSIGN(HttpAuthHandlerRegistryFactory);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_AUTH_HANDLER_FACTORY_H_
diff --git a/net/http/http_auth_handler_factory_unittest.cc b/net/http/http_auth_handler_factory_unittest.cc
new file mode 100644
index 0000000..8dd37f0
--- /dev/null
+++ b/net/http/http_auth_handler_factory_unittest.cc
@@ -0,0 +1,182 @@
+// 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/scoped_ptr.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_auth_handler.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/url_security_manager.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+class MockHttpAuthHandlerFactory : public HttpAuthHandlerFactory {
+ public:
+ explicit MockHttpAuthHandlerFactory(int return_code) :
+ return_code_(return_code) {}
+ virtual ~MockHttpAuthHandlerFactory() {}
+
+ virtual int CreateAuthHandler(HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason reason,
+ int nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) {
+ handler->reset();
+ return return_code_;
+ }
+
+ private:
+ int return_code_;
+};
+
+} // namespace
+
+TEST(HttpAuthHandlerFactoryTest, RegistryFactory) {
+ HttpAuthHandlerRegistryFactory registry_factory;
+ GURL gurl("www.google.com");
+ const int kBasicReturnCode = ERR_INVALID_SPDY_STREAM;
+ MockHttpAuthHandlerFactory* mock_factory_basic =
+ new MockHttpAuthHandlerFactory(kBasicReturnCode);
+
+ const int kDigestReturnCode = ERR_PAC_SCRIPT_FAILED;
+ MockHttpAuthHandlerFactory* mock_factory_digest =
+ new MockHttpAuthHandlerFactory(kDigestReturnCode);
+
+ const int kDigestReturnCodeReplace = ERR_SYN_REPLY_NOT_RECEIVED;
+ MockHttpAuthHandlerFactory* mock_factory_digest_replace =
+ new MockHttpAuthHandlerFactory(kDigestReturnCodeReplace);
+
+ scoped_ptr<HttpAuthHandler> handler;
+
+ // No schemes should be supported in the beginning.
+ EXPECT_EQ(ERR_UNSUPPORTED_AUTH_SCHEME,
+ registry_factory.CreateAuthHandlerFromString(
+ "Basic", HttpAuth::AUTH_SERVER, gurl, BoundNetLog(), &handler));
+
+ // Test what happens with a single scheme.
+ registry_factory.RegisterSchemeFactory("Basic", mock_factory_basic);
+ EXPECT_EQ(kBasicReturnCode,
+ registry_factory.CreateAuthHandlerFromString(
+ "Basic", HttpAuth::AUTH_SERVER, gurl, BoundNetLog(), &handler));
+ EXPECT_EQ(ERR_UNSUPPORTED_AUTH_SCHEME,
+ registry_factory.CreateAuthHandlerFromString(
+ "Digest", HttpAuth::AUTH_SERVER, gurl, BoundNetLog(),
+ &handler));
+
+ // Test multiple schemes
+ registry_factory.RegisterSchemeFactory("Digest", mock_factory_digest);
+ EXPECT_EQ(kBasicReturnCode,
+ registry_factory.CreateAuthHandlerFromString(
+ "Basic", HttpAuth::AUTH_SERVER, gurl, BoundNetLog(), &handler));
+ EXPECT_EQ(kDigestReturnCode,
+ registry_factory.CreateAuthHandlerFromString(
+ "Digest", HttpAuth::AUTH_SERVER, gurl, BoundNetLog(),
+ &handler));
+
+ // Test case-insensitivity
+ EXPECT_EQ(kBasicReturnCode,
+ registry_factory.CreateAuthHandlerFromString(
+ "basic", HttpAuth::AUTH_SERVER, gurl, BoundNetLog(), &handler));
+
+ // Test replacement of existing auth scheme
+ registry_factory.RegisterSchemeFactory("Digest", mock_factory_digest_replace);
+ EXPECT_EQ(kBasicReturnCode,
+ registry_factory.CreateAuthHandlerFromString(
+ "Basic", HttpAuth::AUTH_SERVER, gurl, BoundNetLog(), &handler));
+ EXPECT_EQ(kDigestReturnCodeReplace,
+ registry_factory.CreateAuthHandlerFromString(
+ "Digest", HttpAuth::AUTH_SERVER, gurl, BoundNetLog(),
+ &handler));
+}
+
+TEST(HttpAuthHandlerFactoryTest, DefaultFactory) {
+ URLSecurityManagerAllow url_security_manager;
+ scoped_ptr<HttpAuthHandlerRegistryFactory> http_auth_handler_factory(
+ HttpAuthHandlerFactory::CreateDefault());
+ http_auth_handler_factory->SetURLSecurityManager(
+ "negotiate", &url_security_manager);
+ GURL server_origin("http://www.example.com");
+ GURL proxy_origin("http://cache.example.com:3128");
+ {
+ scoped_ptr<HttpAuthHandler> handler;
+ int rv = http_auth_handler_factory->CreateAuthHandlerFromString(
+ "Basic realm=\"FooBar\"",
+ HttpAuth::AUTH_SERVER,
+ server_origin,
+ BoundNetLog(),
+ &handler);
+ EXPECT_EQ(OK, rv);
+ EXPECT_FALSE(handler.get() == NULL);
+ EXPECT_STREQ("basic", handler->scheme().c_str());
+ EXPECT_STREQ("FooBar", handler->realm().c_str());
+ EXPECT_EQ(HttpAuth::AUTH_SERVER, handler->target());
+ EXPECT_FALSE(handler->encrypts_identity());
+ EXPECT_FALSE(handler->is_connection_based());
+ }
+ {
+ scoped_ptr<HttpAuthHandler> handler;
+ int rv = http_auth_handler_factory->CreateAuthHandlerFromString(
+ "UNSUPPORTED realm=\"FooBar\"",
+ HttpAuth::AUTH_SERVER,
+ server_origin,
+ BoundNetLog(),
+ &handler);
+ EXPECT_EQ(ERR_UNSUPPORTED_AUTH_SCHEME, rv);
+ EXPECT_TRUE(handler.get() == NULL);
+ }
+ {
+ scoped_ptr<HttpAuthHandler> handler;
+ int rv = http_auth_handler_factory->CreateAuthHandlerFromString(
+ "Digest realm=\"FooBar\", nonce=\"xyz\"",
+ HttpAuth::AUTH_PROXY,
+ proxy_origin,
+ BoundNetLog(),
+ &handler);
+ EXPECT_EQ(OK, rv);
+ EXPECT_FALSE(handler.get() == NULL);
+ EXPECT_STREQ("digest", handler->scheme().c_str());
+ EXPECT_STREQ("FooBar", handler->realm().c_str());
+ EXPECT_EQ(HttpAuth::AUTH_PROXY, handler->target());
+ EXPECT_TRUE(handler->encrypts_identity());
+ EXPECT_FALSE(handler->is_connection_based());
+ }
+ {
+ scoped_ptr<HttpAuthHandler> handler;
+ int rv = http_auth_handler_factory->CreateAuthHandlerFromString(
+ "NTLM",
+ HttpAuth::AUTH_SERVER,
+ server_origin,
+ BoundNetLog(),
+ &handler);
+ EXPECT_EQ(OK, rv);
+ ASSERT_FALSE(handler.get() == NULL);
+ EXPECT_STREQ("ntlm", handler->scheme().c_str());
+ EXPECT_STREQ("", handler->realm().c_str());
+ EXPECT_EQ(HttpAuth::AUTH_SERVER, handler->target());
+ EXPECT_TRUE(handler->encrypts_identity());
+ EXPECT_TRUE(handler->is_connection_based());
+ }
+ {
+ scoped_ptr<HttpAuthHandler> handler;
+ int rv = http_auth_handler_factory->CreateAuthHandlerFromString(
+ "Negotiate",
+ HttpAuth::AUTH_SERVER,
+ server_origin,
+ BoundNetLog(),
+ &handler);
+ EXPECT_EQ(OK, rv);
+ EXPECT_FALSE(handler.get() == NULL);
+ EXPECT_STREQ("negotiate", handler->scheme().c_str());
+ EXPECT_STREQ("", handler->realm().c_str());
+ EXPECT_EQ(HttpAuth::AUTH_SERVER, handler->target());
+ EXPECT_TRUE(handler->encrypts_identity());
+ EXPECT_TRUE(handler->is_connection_based());
+ }
+}
+
+} // namespace net
diff --git a/net/http/http_auth_handler_mock.cc b/net/http/http_auth_handler_mock.cc
new file mode 100644
index 0000000..09a0356
--- /dev/null
+++ b/net/http/http_auth_handler_mock.cc
@@ -0,0 +1,141 @@
+// 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 "net/http/http_auth_handler_mock.h"
+
+#include "base/message_loop.h"
+#include "net/base/net_errors.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+HttpAuthHandlerMock::HttpAuthHandlerMock()
+ : resolve_(RESOLVE_INIT), user_callback_(NULL),
+ ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)),
+ generate_async_(false), generate_rv_(OK),
+ auth_token_(NULL),
+ first_round_(true),
+ connection_based_(false) {
+}
+
+HttpAuthHandlerMock::~HttpAuthHandlerMock() {
+}
+
+void HttpAuthHandlerMock::SetResolveExpectation(Resolve resolve) {
+ EXPECT_EQ(RESOLVE_INIT, resolve_);
+ resolve_ = resolve;
+}
+
+bool HttpAuthHandlerMock::NeedsCanonicalName() {
+ switch (resolve_) {
+ case RESOLVE_SYNC:
+ case RESOLVE_ASYNC:
+ return true;
+ case RESOLVE_SKIP:
+ resolve_ = RESOLVE_TESTED;
+ return false;
+ default:
+ NOTREACHED();
+ return false;
+ }
+}
+
+int HttpAuthHandlerMock::ResolveCanonicalName(HostResolver* host_resolver,
+ CompletionCallback* callback) {
+ EXPECT_NE(RESOLVE_TESTED, resolve_);
+ int rv = OK;
+ switch (resolve_) {
+ case RESOLVE_SYNC:
+ resolve_ = RESOLVE_TESTED;
+ break;
+ case RESOLVE_ASYNC:
+ EXPECT_TRUE(user_callback_ == NULL);
+ rv = ERR_IO_PENDING;
+ user_callback_ = callback;
+ MessageLoop::current()->PostTask(
+ FROM_HERE, method_factory_.NewRunnableMethod(
+ &HttpAuthHandlerMock::OnResolveCanonicalName));
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ return rv;
+}
+
+void HttpAuthHandlerMock::SetGenerateExpectation(bool async, int rv) {
+ generate_async_ = async;
+ generate_rv_ = rv;
+}
+
+bool HttpAuthHandlerMock::Init(HttpAuth::ChallengeTokenizer* challenge) {
+ scheme_ = "mock";
+ score_ = 1;
+ properties_ = connection_based_ ? IS_CONNECTION_BASED : 0;
+ return true;
+}
+
+int HttpAuthHandlerMock::GenerateAuthTokenImpl(const std::wstring* username,
+ const std::wstring* password,
+ const HttpRequestInfo* request,
+ CompletionCallback* callback,
+ std::string* auth_token) {
+ first_round_ = false;
+ if (generate_async_) {
+ EXPECT_TRUE(user_callback_ == NULL);
+ EXPECT_TRUE(auth_token_ == NULL);
+ user_callback_ = callback;
+ auth_token_ = auth_token;
+ MessageLoop::current()->PostTask(
+ FROM_HERE, method_factory_.NewRunnableMethod(
+ &HttpAuthHandlerMock::OnGenerateAuthToken));
+ return ERR_IO_PENDING;
+ } else {
+ if (generate_rv_ == OK)
+ *auth_token = "auth_token";
+ return generate_rv_;
+ }
+}
+
+void HttpAuthHandlerMock::OnResolveCanonicalName() {
+ EXPECT_EQ(RESOLVE_ASYNC, resolve_);
+ EXPECT_TRUE(user_callback_ != NULL);
+ resolve_ = RESOLVE_TESTED;
+ CompletionCallback* callback = user_callback_;
+ user_callback_ = NULL;
+ callback->Run(OK);
+}
+
+void HttpAuthHandlerMock::OnGenerateAuthToken() {
+ EXPECT_TRUE(generate_async_);
+ EXPECT_TRUE(user_callback_ != NULL);
+ if (generate_rv_ == OK)
+ *auth_token_ = "auth_token";
+ auth_token_ = NULL;
+ CompletionCallback* callback = user_callback_;
+ user_callback_ = NULL;
+ callback->Run(generate_rv_);
+}
+
+void HttpAuthHandlerMock::Factory::set_mock_handler(
+ HttpAuthHandler* handler, HttpAuth::Target target) {
+ EXPECT_TRUE(handlers_[target].get() == NULL);
+ handlers_[target].reset(handler);
+}
+
+int HttpAuthHandlerMock::Factory::CreateAuthHandler(
+ HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason reason,
+ int nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) {
+ if (!handlers_[target].get())
+ return ERR_UNEXPECTED;
+ handler->swap(handlers_[target]);
+ return OK;
+}
+
+} // namespace net
diff --git a/net/http/http_auth_handler_mock.h b/net/http/http_auth_handler_mock.h
new file mode 100644
index 0000000..a0ef4f0
--- /dev/null
+++ b/net/http/http_auth_handler_mock.h
@@ -0,0 +1,94 @@
+// 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.
+
+#ifndef NET_HTTP_HTTP_AUTH_HANDLER_MOCK_H_
+#define NET_HTTP_HTTP_AUTH_HANDLER_MOCK_H_
+
+#include <string>
+
+#include "base/task.h"
+#include "net/http/http_auth_handler.h"
+#include "net/http/http_auth_handler_factory.h"
+
+namespace net {
+
+// MockAuthHandler is used in tests to reliably trigger edge cases.
+class HttpAuthHandlerMock : public HttpAuthHandler {
+ public:
+ enum Resolve {
+ RESOLVE_INIT,
+ RESOLVE_SKIP,
+ RESOLVE_SYNC,
+ RESOLVE_ASYNC,
+ RESOLVE_TESTED,
+ };
+
+ HttpAuthHandlerMock();
+
+ virtual ~HttpAuthHandlerMock();
+
+ void SetResolveExpectation(Resolve resolve);
+
+ virtual bool NeedsCanonicalName();
+
+ virtual int ResolveCanonicalName(HostResolver* host_resolver,
+ CompletionCallback* callback);
+
+ virtual bool NeedsIdentity() { return first_round_; }
+ virtual bool IsFinalRound() { return false; }
+
+ void SetGenerateExpectation(bool async, int rv);
+
+ void set_connection_based(bool connection_based) {
+ connection_based_ = connection_based;
+ }
+
+ // The Factory class simply returns the same handler each time
+ // CreateAuthHandler is called.
+ class Factory : public HttpAuthHandlerFactory {
+ public:
+ Factory() {}
+ virtual ~Factory() {}
+
+ void set_mock_handler(HttpAuthHandler* handler, HttpAuth::Target target);
+
+ virtual int CreateAuthHandler(HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason reason,
+ int nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler);
+
+ private:
+ scoped_ptr<HttpAuthHandler> handlers_[HttpAuth::AUTH_NUM_TARGETS];
+ };
+
+ protected:
+ virtual bool Init(HttpAuth::ChallengeTokenizer* challenge);
+
+ virtual int GenerateAuthTokenImpl(const std::wstring* username,
+ const std::wstring* password,
+ const HttpRequestInfo* request,
+ CompletionCallback* callback,
+ std::string* auth_token);
+
+ private:
+ void OnResolveCanonicalName();
+
+ void OnGenerateAuthToken();
+
+ Resolve resolve_;
+ CompletionCallback* user_callback_;
+ ScopedRunnableMethodFactory<HttpAuthHandlerMock> method_factory_;
+ bool generate_async_;
+ int generate_rv_;
+ std::string* auth_token_;
+ bool first_round_;
+ bool connection_based_;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_AUTH_HANDLER_MOCK_H_
diff --git a/net/http/http_auth_handler_negotiate.cc b/net/http/http_auth_handler_negotiate.cc
new file mode 100644
index 0000000..d7a9c50
--- /dev/null
+++ b/net/http/http_auth_handler_negotiate.cc
@@ -0,0 +1,316 @@
+// 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 "net/http/http_auth_handler_negotiate.h"
+
+#include "base/logging.h"
+#include "net/base/address_family.h"
+#include "net/base/host_resolver.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_auth_filter.h"
+#include "net/http/url_security_manager.h"
+
+namespace net {
+
+HttpAuthHandlerNegotiate::HttpAuthHandlerNegotiate(
+ AuthLibrary* auth_library,
+#if defined(OS_WIN)
+ ULONG max_token_length,
+#endif
+ URLSecurityManager* url_security_manager,
+ HostResolver* resolver,
+ bool disable_cname_lookup,
+ bool use_port)
+#if defined(OS_WIN)
+ : auth_system_(auth_library, "Negotiate", NEGOSSP_NAME, max_token_length),
+#elif defined(OS_POSIX)
+ : auth_system_(auth_library, "Negotiate", CHROME_GSS_KRB5_MECH_OID_DESC),
+#endif
+ disable_cname_lookup_(disable_cname_lookup),
+ use_port_(use_port),
+ ALLOW_THIS_IN_INITIALIZER_LIST(io_callback_(
+ this, &HttpAuthHandlerNegotiate::OnIOComplete)),
+ resolver_(resolver),
+ already_called_(false),
+ has_username_and_password_(false),
+ user_callback_(NULL),
+ auth_token_(NULL),
+ next_state_(STATE_NONE),
+ url_security_manager_(url_security_manager) {
+}
+
+HttpAuthHandlerNegotiate::~HttpAuthHandlerNegotiate() {
+}
+
+int HttpAuthHandlerNegotiate::GenerateAuthTokenImpl(
+ const std::wstring* username,
+ const std::wstring* password,
+ const HttpRequestInfo* request,
+ CompletionCallback* callback,
+ std::string* auth_token) {
+ DCHECK(user_callback_ == NULL);
+ DCHECK((username == NULL) == (password == NULL));
+ DCHECK(auth_token_ == NULL);
+ auth_token_ = auth_token;
+ if (already_called_) {
+ DCHECK((!has_username_and_password_ && username == NULL) ||
+ (has_username_and_password_ && *username == username_ &&
+ *password == password_));
+ next_state_ = STATE_GENERATE_AUTH_TOKEN;
+ } else {
+ already_called_ = true;
+ if (username) {
+ has_username_and_password_ = true;
+ username_ = *username;
+ password_ = *password;
+ }
+ next_state_ = STATE_RESOLVE_CANONICAL_NAME;
+ }
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ user_callback_ = callback;
+ return rv;
+}
+
+// The Negotiate challenge header looks like:
+// WWW-Authenticate: NEGOTIATE auth-data
+bool HttpAuthHandlerNegotiate::Init(HttpAuth::ChallengeTokenizer* challenge) {
+#if defined(OS_POSIX)
+ if (!auth_system_.Init()) {
+ LOG(INFO) << "can't initialize GSSAPI library";
+ return false;
+ }
+ // GSSAPI does not provide a way to enter username/password to
+ // obtain a TGT. If the default credentials are not allowed for
+ // a particular site (based on whitelist), fall back to a
+ // different scheme.
+ if (!AllowsDefaultCredentials())
+ return false;
+#endif
+ scheme_ = "negotiate";
+ score_ = 4;
+ properties_ = ENCRYPTS_IDENTITY | IS_CONNECTION_BASED;
+ return auth_system_.ParseChallenge(challenge);
+}
+
+// Require identity on first pass instead of second.
+bool HttpAuthHandlerNegotiate::NeedsIdentity() {
+ return auth_system_.NeedsIdentity();
+}
+
+bool HttpAuthHandlerNegotiate::IsFinalRound() {
+ return auth_system_.IsFinalRound();
+}
+
+bool HttpAuthHandlerNegotiate::AllowsDefaultCredentials() {
+ if (target_ == HttpAuth::AUTH_PROXY)
+ return true;
+ if (!url_security_manager_)
+ return false;
+ return url_security_manager_->CanUseDefaultCredentials(origin_);
+}
+
+std::wstring HttpAuthHandlerNegotiate::CreateSPN(
+ const AddressList& address_list, const GURL& origin) {
+ // Kerberos Web Server SPNs are in the form HTTP/<host>:<port> through SSPI,
+ // and in the form HTTP@<host>:<port> through GSSAPI
+ // http://msdn.microsoft.com/en-us/library/ms677601%28VS.85%29.aspx
+ //
+ // However, reality differs from the specification. A good description of
+ // the problems can be found here:
+ // http://blog.michelbarneveld.nl/michel/archive/2009/11/14/the-reason-why-kb911149-and-kb908209-are-not-the-soluton.aspx
+ //
+ // Typically the <host> portion should be the canonical FQDN for the service.
+ // If this could not be resolved, the original hostname in the URL will be
+ // attempted instead. However, some intranets register SPNs using aliases
+ // for the same canonical DNS name to allow multiple web services to reside
+ // on the same host machine without requiring different ports. IE6 and IE7
+ // have hotpatches that allow the default behavior to be overridden.
+ // http://support.microsoft.com/kb/911149
+ // http://support.microsoft.com/kb/938305
+ //
+ // According to the spec, the <port> option should be included if it is a
+ // non-standard port (i.e. not 80 or 443 in the HTTP case). However,
+ // historically browsers have not included the port, even on non-standard
+ // ports. IE6 required a hotpatch and a registry setting to enable
+ // including non-standard ports, and IE7 and IE8 also require the same
+ // registry setting, but no hotpatch. Firefox does not appear to have an
+ // option to include non-standard ports as of 3.6.
+ // http://support.microsoft.com/kb/908209
+ //
+ // Without any command-line flags, Chrome matches the behavior of Firefox
+ // and IE. Users can override the behavior so aliases are allowed and
+ // non-standard ports are included.
+ int port = origin.EffectiveIntPort();
+ std::string server;
+ if (!address_list.GetCanonicalName(&server))
+ server = origin.host();
+#if defined(OS_WIN)
+ static const char kSpnSeparator = '/';
+#elif defined(OS_POSIX)
+ static const char kSpnSeparator = '@';
+#endif
+ if (port != 80 && port != 443 && use_port_) {
+ return ASCIIToWide(StringPrintf("HTTP%c%s:%d", kSpnSeparator,
+ server.c_str(), port));
+ } else {
+ return ASCIIToWide(StringPrintf("HTTP%c%s", kSpnSeparator, server.c_str()));
+ }
+}
+
+int HttpAuthHandlerNegotiate::DoLoop(int result) {
+ DCHECK(next_state_ != STATE_NONE);
+
+ int rv = result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_RESOLVE_CANONICAL_NAME:
+ DCHECK_EQ(OK, rv);
+ rv = DoResolveCanonicalName();
+ break;
+ case STATE_RESOLVE_CANONICAL_NAME_COMPLETE:
+ rv = DoResolveCanonicalNameComplete(rv);
+ break;
+ case STATE_GENERATE_AUTH_TOKEN:
+ DCHECK_EQ(OK, rv);
+ rv = DoGenerateAuthToken();
+ break;
+ case STATE_GENERATE_AUTH_TOKEN_COMPLETE:
+ rv = DoGenerateAuthTokenComplete(rv);
+ break;
+ default:
+ NOTREACHED() << "bad state";
+ rv = ERR_FAILED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+
+ return rv;
+}
+
+int HttpAuthHandlerNegotiate::DoResolveCanonicalName() {
+ next_state_ = STATE_RESOLVE_CANONICAL_NAME_COMPLETE;
+ if (disable_cname_lookup_)
+ return OK;
+
+ // TODO(cbentzel): Add reverse DNS lookup for numeric addresses.
+ DCHECK(!single_resolve_.get());
+ HostResolver::RequestInfo info(origin_.host(), 0);
+ info.set_host_resolver_flags(HOST_RESOLVER_CANONNAME);
+ single_resolve_.reset(new SingleRequestHostResolver(resolver_));
+ return single_resolve_->Resolve(info, &address_list_, &io_callback_,
+ net_log_);
+}
+
+int HttpAuthHandlerNegotiate::DoResolveCanonicalNameComplete(int rv) {
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ if (rv != OK) {
+ // Even in the error case, try to use origin_.host instead of
+ // passing the failure on to the caller.
+ LOG(INFO) << "Problem finding canonical name for SPN for host "
+ << origin_.host() << ": " << ErrorToString(rv);
+ rv = OK;
+ }
+
+ next_state_ = STATE_GENERATE_AUTH_TOKEN;
+ spn_ = CreateSPN(address_list_, origin_);
+ address_list_.Reset();
+ return rv;
+}
+
+int HttpAuthHandlerNegotiate::DoGenerateAuthToken() {
+ next_state_ = STATE_GENERATE_AUTH_TOKEN_COMPLETE;
+ std::wstring* username = has_username_and_password_ ? &username_ : NULL;
+ std::wstring* password = has_username_and_password_ ? &password_ : NULL;
+ // TODO(cbentzel): This should possibly be done async.
+ return auth_system_.GenerateAuthToken(username, password, spn_, auth_token_);
+}
+
+int HttpAuthHandlerNegotiate::DoGenerateAuthTokenComplete(int rv) {
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ auth_token_ = NULL;
+ return rv;
+}
+
+void HttpAuthHandlerNegotiate::OnIOComplete(int result) {
+ int rv = DoLoop(result);
+ if (rv != ERR_IO_PENDING)
+ DoCallback(rv);
+}
+
+void HttpAuthHandlerNegotiate::DoCallback(int rv) {
+ DCHECK(rv != ERR_IO_PENDING);
+ DCHECK(user_callback_);
+ CompletionCallback* callback = user_callback_;
+ user_callback_ = NULL;
+ callback->Run(rv);
+}
+
+HttpAuthHandlerNegotiate::Factory::Factory()
+ : disable_cname_lookup_(false),
+ use_port_(false),
+#if defined(OS_WIN)
+ max_token_length_(0),
+ first_creation_(true),
+ is_unsupported_(false),
+ auth_library_(SSPILibrary::GetDefault()) {
+#elif defined(OS_POSIX)
+ auth_library_(GSSAPILibrary::GetDefault()) {
+#endif
+}
+
+HttpAuthHandlerNegotiate::Factory::~Factory() {
+}
+
+void HttpAuthHandlerNegotiate::Factory::set_host_resolver(
+ HostResolver* resolver) {
+ resolver_ = resolver;
+}
+
+int HttpAuthHandlerNegotiate::Factory::CreateAuthHandler(
+ HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason reason,
+ int digest_nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) {
+#if defined(OS_WIN)
+ if (is_unsupported_ || reason == CREATE_PREEMPTIVE)
+ return ERR_UNSUPPORTED_AUTH_SCHEME;
+ if (max_token_length_ == 0) {
+ int rv = DetermineMaxTokenLength(auth_library_, NEGOSSP_NAME,
+ &max_token_length_);
+ if (rv == ERR_UNSUPPORTED_AUTH_SCHEME)
+ is_unsupported_ = true;
+ if (rv != OK)
+ return rv;
+ }
+ // TODO(cbentzel): Move towards model of parsing in the factory
+ // method and only constructing when valid.
+ scoped_ptr<HttpAuthHandler> tmp_handler(
+ new HttpAuthHandlerNegotiate(auth_library_, max_token_length_,
+ url_security_manager(), resolver_,
+ disable_cname_lookup_, use_port_));
+ if (!tmp_handler->InitFromChallenge(challenge, target, origin, net_log))
+ return ERR_INVALID_RESPONSE;
+ handler->swap(tmp_handler);
+ return OK;
+#elif defined(OS_POSIX)
+ // TODO(ahendrickson): Move towards model of parsing in the factory
+ // method and only constructing when valid.
+ scoped_ptr<HttpAuthHandler> tmp_handler(
+ new HttpAuthHandlerNegotiate(auth_library_, url_security_manager(),
+ resolver_, disable_cname_lookup_,
+ use_port_));
+ if (!tmp_handler->InitFromChallenge(challenge, target, origin, net_log))
+ return ERR_INVALID_RESPONSE;
+ handler->swap(tmp_handler);
+ return OK;
+#endif
+}
+
+} // namespace net
diff --git a/net/http/http_auth_handler_negotiate.h b/net/http/http_auth_handler_negotiate.h
index eb4d11f..ec1a194 100644
--- a/net/http/http_auth_handler_negotiate.h
+++ b/net/http/http_auth_handler_negotiate.h
@@ -5,18 +5,27 @@
#ifndef NET_HTTP_HTTP_AUTH_HANDLER_NEGOTIATE_H_
#define NET_HTTP_HTTP_AUTH_HANDLER_NEGOTIATE_H_
-#include "build/build_config.h"
-
#include <string>
+#include "build/build_config.h"
+
+#include "build/build_config.h"
+#include "net/base/address_list.h"
#include "net/http/http_auth_handler.h"
+#include "net/http/http_auth_handler_factory.h"
#if defined(OS_WIN)
#include "net/http/http_auth_sspi_win.h"
+#elif defined(OS_POSIX)
+#include "net/http/http_auth_gssapi_posix.h"
#endif
namespace net {
+class HostResolver;
+class SingleRequestHostResolver;
+class URLSecurityManager;
+
// Handler for WWW-Authenticate: Negotiate protocol.
//
// See http://tools.ietf.org/html/rfc4178 and http://tools.ietf.org/html/rfc4559
@@ -24,28 +33,139 @@
class HttpAuthHandlerNegotiate : public HttpAuthHandler {
public:
- HttpAuthHandlerNegotiate();
+#if defined(OS_WIN)
+ typedef SSPILibrary AuthLibrary;
+ typedef HttpAuthSSPI AuthSystem;
+#elif defined(OS_POSIX)
+ typedef GSSAPILibrary AuthLibrary;
+ typedef HttpAuthGSSAPI AuthSystem;
+#endif
+
+ class Factory : public HttpAuthHandlerFactory {
+ public:
+ Factory();
+ virtual ~Factory();
+
+ // |disable_cname_lookup()| and |set_disable_cname_lookup()| get/set whether
+ // the auth handlers generated by this factory should skip looking up the
+ // canonical DNS name of the the host that they are authenticating to when
+ // generating the SPN. The default value is false.
+ bool disable_cname_lookup() const { return disable_cname_lookup_; }
+ void set_disable_cname_lookup(bool disable_cname_lookup) {
+ disable_cname_lookup_ = disable_cname_lookup;
+ }
+
+ // |use_port()| and |set_use_port()| get/set whether the auth handlers
+ // generated by this factory should include the port number of the server
+ // they are authenticating to when constructing a Kerberos SPN. The default
+ // value is false.
+ bool use_port() const { return use_port_; }
+ void set_use_port(bool use_port) { use_port_ = use_port; }
+
+ void set_host_resolver(HostResolver* host_resolver);
+
+ virtual int CreateAuthHandler(HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason reason,
+ int digest_nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler);
+
+ // Set the system library to use. Typically the only callers which need to
+ // use this are unit tests which pass in a mocked-out version of the
+ // system library.
+ // The caller is responsible for managing the lifetime of |*auth_library|,
+ // and the lifetime must exceed that of this Factory object and all
+ // HttpAuthHandler's that this Factory object creates.
+ void set_library(AuthLibrary* auth_library) {
+ auth_library_ = auth_library;
+ }
+
+ private:
+ bool disable_cname_lookup_;
+ bool use_port_;
+ scoped_refptr<HostResolver> resolver_;
+#if defined(OS_WIN)
+ ULONG max_token_length_;
+ bool first_creation_;
+ bool is_unsupported_;
+#endif
+ AuthLibrary* auth_library_;
+ };
+
+ HttpAuthHandlerNegotiate(AuthLibrary* sspi_library,
+#if defined(OS_WIN)
+ ULONG max_token_length,
+#endif
+ URLSecurityManager* url_security_manager,
+ HostResolver* host_resolver,
+ bool disable_cname_lookup,
+ bool use_port);
+
+ virtual ~HttpAuthHandlerNegotiate();
virtual bool NeedsIdentity();
virtual bool IsFinalRound();
- virtual std::string GenerateCredentials(const std::wstring& username,
- const std::wstring& password,
- const HttpRequestInfo* request,
- const ProxyInfo* proxy);
+ virtual bool AllowsDefaultCredentials();
+ // These are public for unit tests
+ std::wstring CreateSPN(const AddressList& address_list, const GURL& orign);
+ const std::wstring& spn() const { return spn_; }
protected:
- virtual bool Init(std::string::const_iterator challenge_begin,
- std::string::const_iterator challenge_end);
+ virtual bool Init(HttpAuth::ChallengeTokenizer* challenge);
+
+ virtual int GenerateAuthTokenImpl(const std::wstring* username,
+ const std::wstring* password,
+ const HttpRequestInfo* request,
+ CompletionCallback* callback,
+ std::string* auth_token);
private:
- ~HttpAuthHandlerNegotiate();
+ enum State {
+ STATE_RESOLVE_CANONICAL_NAME,
+ STATE_RESOLVE_CANONICAL_NAME_COMPLETE,
+ STATE_GENERATE_AUTH_TOKEN,
+ STATE_GENERATE_AUTH_TOKEN_COMPLETE,
+ STATE_NONE,
+ };
-#if defined(OS_WIN)
- HttpAuthSSPI auth_sspi_;
-#endif
+ void OnIOComplete(int result);
+ void DoCallback(int result);
+ int DoLoop(int result);
+
+ int DoResolveCanonicalName();
+ int DoResolveCanonicalNameComplete(int rv);
+ int DoGenerateAuthToken();
+ int DoGenerateAuthTokenComplete(int rv);
+
+ AuthSystem auth_system_;
+ bool disable_cname_lookup_;
+ bool use_port_;
+ CompletionCallbackImpl<HttpAuthHandlerNegotiate> io_callback_;
+ scoped_refptr<HostResolver> resolver_;
+
+ // Members which are needed for DNS lookup + SPN.
+ AddressList address_list_;
+ scoped_ptr<SingleRequestHostResolver> single_resolve_;
+
+ // Things which should be consistent after first call to GenerateAuthToken.
+ bool already_called_;
+ bool has_username_and_password_;
+ std::wstring username_;
+ std::wstring password_;
+ std::wstring spn_;
+
+ // Things which vary each round.
+ CompletionCallback* user_callback_;
+ std::string* auth_token_;
+
+ State next_state_;
+
+ URLSecurityManager* url_security_manager_;
};
} // namespace net
diff --git a/net/http/http_auth_handler_negotiate_unittest.cc b/net/http/http_auth_handler_negotiate_unittest.cc
new file mode 100644
index 0000000..4fcdad1
--- /dev/null
+++ b/net/http/http_auth_handler_negotiate_unittest.cc
@@ -0,0 +1,359 @@
+// 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 "net/http/http_auth_handler_negotiate.h"
+
+#include "net/base/mock_host_resolver.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/http/http_request_info.h"
+#if defined(OS_WIN)
+#include "net/http/mock_sspi_library_win.h"
+#elif defined(OS_POSIX)
+#include "net/http/mock_gssapi_library_posix.h"
+#include "net/third_party/gssapi/gssapi.h"
+#endif
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+#if defined(OS_WIN)
+typedef net::MockSSPILibrary MockAuthLibrary;
+#elif defined(OS_POSIX)
+typedef net::test::MockGSSAPILibrary MockAuthLibrary;
+#endif
+
+
+namespace net {
+
+class HttpAuthHandlerNegotiateTest : public PlatformTest {
+ public:
+ virtual void SetUp() {
+ auth_library_.reset(new MockAuthLibrary());
+ resolver_ = new MockHostResolver();
+ resolver_->rules()->AddIPLiteralRule("alias", "10.0.0.2",
+ "canonical.example.com");
+
+ url_security_manager_.reset(new URLSecurityManagerAllow());
+ factory_.reset(new HttpAuthHandlerNegotiate::Factory());
+ factory_->set_url_security_manager(url_security_manager_.get());
+ factory_->set_library(auth_library_.get());
+ factory_->set_host_resolver(resolver_);
+ }
+
+ void SetupMocks(MockAuthLibrary* mock_library) {
+#if defined(OS_WIN)
+ security_package_.reset(new SecPkgInfoW);
+ memset(security_package_.get(), 0x0, sizeof(SecPkgInfoW));
+ security_package_->cbMaxToken = 1337;
+ mock_library->ExpectQuerySecurityPackageInfo(
+ L"Negotiate", SEC_E_OK, security_package_.get());
+#elif defined(OS_POSIX)
+ // Copied from an actual transaction!
+ const char kAuthResponse[] =
+ "\x60\x82\x02\xCA\x06\x09\x2A\x86\x48\x86\xF7\x12\x01\x02\x02\x01"
+ "\x00\x6E\x82\x02\xB9\x30\x82\x02\xB5\xA0\x03\x02\x01\x05\xA1\x03"
+ "\x02\x01\x0E\xA2\x07\x03\x05\x00\x00\x00\x00\x00\xA3\x82\x01\xC1"
+ "\x61\x82\x01\xBD\x30\x82\x01\xB9\xA0\x03\x02\x01\x05\xA1\x16\x1B"
+ "\x14\x55\x4E\x49\x58\x2E\x43\x4F\x52\x50\x2E\x47\x4F\x4F\x47\x4C"
+ "\x45\x2E\x43\x4F\x4D\xA2\x2C\x30\x2A\xA0\x03\x02\x01\x01\xA1\x23"
+ "\x30\x21\x1B\x04\x68\x6F\x73\x74\x1B\x19\x6E\x69\x6E\x6A\x61\x2E"
+ "\x63\x61\x6D\x2E\x63\x6F\x72\x70\x2E\x67\x6F\x6F\x67\x6C\x65\x2E"
+ "\x63\x6F\x6D\xA3\x82\x01\x6A\x30\x82\x01\x66\xA0\x03\x02\x01\x10"
+ "\xA1\x03\x02\x01\x01\xA2\x82\x01\x58\x04\x82\x01\x54\x2C\xB1\x2B"
+ "\x0A\xA5\xFF\x6F\xEC\xDE\xB0\x19\x6E\x15\x20\x18\x0C\x42\xB3\x2C"
+ "\x4B\xB0\x37\x02\xDE\xD3\x2F\xB4\xBF\xCA\xEC\x0E\xF9\xF3\x45\x6A"
+ "\x43\xF3\x8D\x79\xBD\xCB\xCD\xB2\x2B\xB8\xFC\xD6\xB4\x7F\x09\x48"
+ "\x14\xA7\x4F\xD2\xEE\xBC\x1B\x2F\x18\x3B\x81\x97\x7B\x28\xA4\xAF"
+ "\xA8\xA3\x7A\x31\x1B\xFC\x97\xB6\xBA\x8A\x50\x50\xD7\x44\xB8\x30"
+ "\xA4\x51\x4C\x3A\x95\x6C\xA1\xED\xE2\xEF\x17\xFE\xAB\xD2\xE4\x70"
+ "\xDE\xEB\x7E\x86\x48\xC5\x3E\x19\x5B\x83\x17\xBB\x52\x26\xC0\xF3"
+ "\x38\x0F\xB0\x8C\x72\xC9\xB0\x8B\x99\x96\x18\xE1\x9E\x67\x9D\xDC"
+ "\xF5\x39\x80\x70\x35\x3F\x98\x72\x16\x44\xA2\xC0\x10\xAA\x70\xBD"
+ "\x06\x6F\x83\xB1\xF4\x67\xA4\xBD\xDA\xF7\x79\x1D\x96\xB5\x7E\xF8"
+ "\xC6\xCF\xB4\xD9\x51\xC9\xBB\xB4\x20\x3C\xDD\xB9\x2C\x38\xEA\x40"
+ "\xFB\x02\x6C\xCB\x48\x71\xE8\xF4\x34\x5B\x63\x5D\x13\x57\xBD\xD1"
+ "\x3D\xDE\xE8\x4A\x51\x6E\xBE\x4C\xF5\xA3\x84\xF7\x4C\x4E\x58\x04"
+ "\xBE\xD1\xCC\x22\xA0\x43\xB0\x65\x99\x6A\xE0\x78\x0D\xFC\xE1\x42"
+ "\xA9\x18\xCF\x55\x4D\x23\xBD\x5C\x0D\xB5\x48\x25\x47\xCC\x01\x54"
+ "\x36\x4D\x0C\x6F\xAC\xCD\x33\x21\xC5\x63\x18\x91\x68\x96\xE9\xD1"
+ "\xD8\x23\x1F\x21\xAE\x96\xA3\xBD\x27\xF7\x4B\xEF\x4C\x43\xFF\xF8"
+ "\x22\x57\xCF\x68\x6C\x35\xD5\x21\x48\x5B\x5F\x8F\xA5\xB9\x6F\x99"
+ "\xA6\xE0\x6E\xF0\xC5\x7C\x91\xC8\x0B\x8A\x4B\x4E\x80\x59\x02\xE9"
+ "\xE8\x3F\x87\x04\xA6\xD1\xCA\x26\x3C\xF0\xDA\x57\xFA\xE6\xAF\x25"
+ "\x43\x34\xE1\xA4\x06\x1A\x1C\xF4\xF5\x21\x9C\x00\x98\xDD\xF0\xB4"
+ "\x8E\xA4\x81\xDA\x30\x81\xD7\xA0\x03\x02\x01\x10\xA2\x81\xCF\x04"
+ "\x81\xCC\x20\x39\x34\x60\x19\xF9\x4C\x26\x36\x46\x99\x7A\xFD\x2B"
+ "\x50\x8B\x2D\x47\x72\x38\x20\x43\x0E\x6E\x28\xB3\xA7\x4F\x26\xF1"
+ "\xF1\x7B\x02\x63\x58\x5A\x7F\xC8\xD0\x6E\xF5\xD1\xDA\x28\x43\x1B"
+ "\x6D\x9F\x59\x64\xDE\x90\xEA\x6C\x8C\xA9\x1B\x1E\x92\x29\x24\x23"
+ "\x2C\xE3\xEA\x64\xEF\x91\xA5\x4E\x94\xE1\xDC\x56\x3A\xAF\xD5\xBC"
+ "\xC9\xD3\x9B\x6B\x1F\xBE\x40\xE5\x40\xFF\x5E\x21\xEA\xCE\xFC\xD5"
+ "\xB0\xE5\xBA\x10\x94\xAE\x16\x54\xFC\xEB\xAB\xF1\xD4\x20\x31\xCC"
+ "\x26\xFE\xBE\xFE\x22\xB6\x9B\x1A\xE5\x55\x2C\x93\xB7\x3B\xD6\x4C"
+ "\x35\x35\xC1\x59\x61\xD4\x1F\x2E\x4C\xE1\x72\x8F\x71\x4B\x0C\x39"
+ "\x80\x79\xFA\xCD\xEA\x71\x1B\xAE\x35\x41\xED\xF9\x65\x0C\x59\xF8"
+ "\xE1\x27\xDA\xD6\xD1\x20\x32\xCD\xBF\xD1\xEF\xE2\xED\xAD\x5D\xA7"
+ "\x69\xE3\x55\xF9\x30\xD3\xD4\x08\xC8\xCA\x62\xF8\x64\xEC\x9B\x92"
+ "\x1A\xF1\x03\x2E\xCC\xDC\xEB\x17\xDE\x09\xAC\xA9\x58\x86";
+ test::GssContextMockImpl context1(
+ "localhost", // Source name
+ "example.com", // Target name
+ 23, // Lifetime
+ *GSS_C_NT_HOSTBASED_SERVICE, // Mechanism
+ 0, // Context flags
+ 1, // Locally initiated
+ 0); // Open
+ test::GssContextMockImpl context2(
+ "localhost", // Source name
+ "example.com", // Target name
+ 23, // Lifetime
+ *GSS_C_NT_HOSTBASED_SERVICE, // Mechanism
+ 0, // Context flags
+ 1, // Locally initiated
+ 1); // Open
+ test::MockGSSAPILibrary::SecurityContextQuery queries[] = {
+ { "Negotiate", // Package name
+ GSS_S_CONTINUE_NEEDED, // Major response code
+ 0, // Minor response code
+ context1, // Context
+ { 0, NULL }, // Expected input token
+ { arraysize(kAuthResponse),
+ const_cast<char*>(kAuthResponse) } // Output token
+ },
+ { "Negotiate", // Package name
+ GSS_S_COMPLETE, // Major response code
+ 0, // Minor response code
+ context2, // Context
+ { arraysize(kAuthResponse),
+ const_cast<char*>(kAuthResponse) }, // Expected input token
+ { arraysize(kAuthResponse),
+ const_cast<char*>(kAuthResponse) } // Output token
+ },
+ };
+
+ for (size_t i = 0; i < arraysize(queries); ++i) {
+ mock_library->ExpectSecurityContext(queries[i].expected_package,
+ queries[i].response_code,
+ queries[i].minor_response_code,
+ queries[i].context_info,
+ queries[i].expected_input_token,
+ queries[i].output_token);
+ }
+#endif // defined(OS_POSIX)
+ }
+
+#if defined(OS_POSIX)
+ void SetupErrorMocks(MockAuthLibrary* mock_library,
+ int major_status,
+ int minor_status) {
+ const gss_OID_desc kDefaultMech = { 0, NULL };
+ test::GssContextMockImpl context(
+ "localhost", // Source name
+ "example.com", // Target name
+ 0, // Lifetime
+ kDefaultMech, // Mechanism
+ 0, // Context flags
+ 1, // Locally initiated
+ 0); // Open
+ test::MockGSSAPILibrary::SecurityContextQuery query = {
+ "Negotiate", // Package name
+ major_status, // Major response code
+ minor_status, // Minor response code
+ context, // Context
+ { 0, NULL }, // Expected input token
+ { 0, NULL } // Output token
+ };
+
+ mock_library->ExpectSecurityContext(query.expected_package,
+ query.response_code,
+ query.minor_response_code,
+ query.context_info,
+ query.expected_input_token,
+ query.output_token);
+ }
+
+#endif // defined(OS_POSIX)
+
+ int CreateHandler(bool disable_cname_lookup, bool use_port,
+ bool synchronous_resolve_mode,
+ const std::string& url_string,
+ scoped_ptr<HttpAuthHandlerNegotiate>* handler) {
+ factory_->set_disable_cname_lookup(disable_cname_lookup);
+ factory_->set_use_port(use_port);
+ resolver_->set_synchronous_mode(synchronous_resolve_mode);
+ GURL gurl(url_string);
+
+ // Note: This is a little tricky because CreateAuthHandlerFromString
+ // expects a scoped_ptr<HttpAuthHandler>* rather than a
+ // scoped_ptr<HttpAuthHandlerNegotiate>*. This needs to do the cast
+ // after creating the handler, and make sure that generic_handler
+ // no longer holds on to the HttpAuthHandlerNegotiate object.
+ scoped_ptr<HttpAuthHandler> generic_handler;
+ int rv = factory_->CreateAuthHandlerFromString("Negotiate",
+ HttpAuth::AUTH_SERVER,
+ gurl,
+ BoundNetLog(),
+ &generic_handler);
+ if (rv != OK)
+ return rv;
+ HttpAuthHandlerNegotiate* negotiate_handler =
+ static_cast<HttpAuthHandlerNegotiate*>(generic_handler.release());
+ handler->reset(negotiate_handler);
+ return rv;
+ }
+
+ MockAuthLibrary* AuthLibrary() { return auth_library_.get(); }
+
+ private:
+#if defined(OS_WIN)
+ scoped_ptr<SecPkgInfoW> security_package_;
+#endif
+ scoped_ptr<MockAuthLibrary> auth_library_;
+ scoped_refptr<MockHostResolver> resolver_;
+ scoped_ptr<URLSecurityManager> url_security_manager_;
+ scoped_ptr<HttpAuthHandlerNegotiate::Factory> factory_;
+};
+
+TEST_F(HttpAuthHandlerNegotiateTest, DisableCname) {
+ SetupMocks(AuthLibrary());
+ scoped_ptr<HttpAuthHandlerNegotiate> auth_handler;
+ EXPECT_EQ(OK, CreateHandler(
+ true, false, true, "http://alias:500", &auth_handler));
+
+ ASSERT_TRUE(auth_handler.get() != NULL);
+ TestCompletionCallback callback;
+ HttpRequestInfo request_info;
+ std::string token;
+ std::wstring username = L"foo";
+ std::wstring password = L"bar";
+ EXPECT_EQ(OK, auth_handler->GenerateAuthToken(&username, &password,
+ &request_info,
+ &callback, &token));
+#if defined(OS_WIN)
+ EXPECT_EQ(L"HTTP/alias", auth_handler->spn());
+#elif defined(OS_POSIX)
+ EXPECT_EQ(L"HTTP@alias", auth_handler->spn());
+#endif
+}
+
+TEST_F(HttpAuthHandlerNegotiateTest, DisableCnameStandardPort) {
+ SetupMocks(AuthLibrary());
+ scoped_ptr<HttpAuthHandlerNegotiate> auth_handler;
+ EXPECT_EQ(OK, CreateHandler(
+ true, true, true, "http://alias:80", &auth_handler));
+ ASSERT_TRUE(auth_handler.get() != NULL);
+ TestCompletionCallback callback;
+ HttpRequestInfo request_info;
+ std::string token;
+ std::wstring username = L"foo";
+ std::wstring password = L"bar";
+ EXPECT_EQ(OK, auth_handler->GenerateAuthToken(&username, &password,
+ &request_info,
+ &callback, &token));
+#if defined(OS_WIN)
+ EXPECT_EQ(L"HTTP/alias", auth_handler->spn());
+#elif defined(OS_POSIX)
+ EXPECT_EQ(L"HTTP@alias", auth_handler->spn());
+#endif
+}
+
+TEST_F(HttpAuthHandlerNegotiateTest, DisableCnameNonstandardPort) {
+ SetupMocks(AuthLibrary());
+ scoped_ptr<HttpAuthHandlerNegotiate> auth_handler;
+ EXPECT_EQ(OK, CreateHandler(
+ true, true, true, "http://alias:500", &auth_handler));
+ ASSERT_TRUE(auth_handler.get() != NULL);
+ TestCompletionCallback callback;
+ HttpRequestInfo request_info;
+ std::string token;
+ std::wstring username = L"foo";
+ std::wstring password = L"bar";
+ EXPECT_EQ(OK, auth_handler->GenerateAuthToken(&username, &password,
+ &request_info,
+ &callback, &token));
+#if defined(OS_WIN)
+ EXPECT_EQ(L"HTTP/alias:500", auth_handler->spn());
+#elif defined(OS_POSIX)
+ EXPECT_EQ(L"HTTP@alias:500", auth_handler->spn());
+#endif
+}
+
+TEST_F(HttpAuthHandlerNegotiateTest, CnameSync) {
+ SetupMocks(AuthLibrary());
+ scoped_ptr<HttpAuthHandlerNegotiate> auth_handler;
+ EXPECT_EQ(OK, CreateHandler(
+ false, false, true, "http://alias:500", &auth_handler));
+ ASSERT_TRUE(auth_handler.get() != NULL);
+ TestCompletionCallback callback;
+ HttpRequestInfo request_info;
+ std::string token;
+ std::wstring username = L"foo";
+ std::wstring password = L"bar";
+ EXPECT_EQ(OK, auth_handler->GenerateAuthToken(&username, &password,
+ &request_info,
+ &callback, &token));
+#if defined(OS_WIN)
+ EXPECT_EQ(L"HTTP/canonical.example.com", auth_handler->spn());
+#elif defined(OS_POSIX)
+ EXPECT_EQ(L"HTTP@canonical.example.com", auth_handler->spn());
+#endif
+}
+
+TEST_F(HttpAuthHandlerNegotiateTest, CnameAsync) {
+ SetupMocks(AuthLibrary());
+ scoped_ptr<HttpAuthHandlerNegotiate> auth_handler;
+ EXPECT_EQ(OK, CreateHandler(
+ false, false, false, "http://alias:500", &auth_handler));
+ ASSERT_TRUE(auth_handler.get() != NULL);
+ TestCompletionCallback callback;
+ HttpRequestInfo request_info;
+ std::string token;
+ std::wstring username = L"foo";
+ std::wstring password = L"bar";
+ EXPECT_EQ(ERR_IO_PENDING, auth_handler->GenerateAuthToken(
+ &username, &password, &request_info, &callback, &token));
+ EXPECT_EQ(OK, callback.WaitForResult());
+#if defined(OS_WIN)
+ EXPECT_EQ(L"HTTP/canonical.example.com", auth_handler->spn());
+#elif defined(OS_POSIX)
+ EXPECT_EQ(L"HTTP@canonical.example.com", auth_handler->spn());
+#endif
+}
+
+#if defined(OS_POSIX)
+
+// These tests are only for GSSAPI, as we can't use explicit credentials with
+// that library.
+
+TEST_F(HttpAuthHandlerNegotiateTest, ServerNotInKerberosDatabase) {
+ SetupErrorMocks(AuthLibrary(), GSS_S_FAILURE, 0x96C73A07); // No server
+ scoped_ptr<HttpAuthHandlerNegotiate> auth_handler;
+ EXPECT_EQ(OK, CreateHandler(
+ false, false, false, "http://alias:500", &auth_handler));
+ ASSERT_TRUE(auth_handler.get() != NULL);
+ TestCompletionCallback callback;
+ HttpRequestInfo request_info;
+ std::string token;
+ EXPECT_EQ(ERR_IO_PENDING, auth_handler->GenerateAuthToken(
+ NULL, NULL, &request_info, &callback, &token));
+ EXPECT_EQ(ERR_MISSING_AUTH_CREDENTIALS, callback.WaitForResult());
+}
+
+TEST_F(HttpAuthHandlerNegotiateTest, NoKerberosCredentials) {
+ SetupErrorMocks(AuthLibrary(), GSS_S_FAILURE, 0x96C73AC3); // No credentials
+ scoped_ptr<HttpAuthHandlerNegotiate> auth_handler;
+ EXPECT_EQ(OK, CreateHandler(
+ false, false, false, "http://alias:500", &auth_handler));
+ ASSERT_TRUE(auth_handler.get() != NULL);
+ TestCompletionCallback callback;
+ HttpRequestInfo request_info;
+ std::string token;
+ EXPECT_EQ(ERR_IO_PENDING, auth_handler->GenerateAuthToken(
+ NULL, NULL, &request_info, &callback, &token));
+ EXPECT_EQ(ERR_MISSING_AUTH_CREDENTIALS, callback.WaitForResult());
+}
+
+#endif // defined(OS_POSIX)
+
+} // namespace net
diff --git a/net/http/http_auth_handler_ntlm.cc b/net/http/http_auth_handler_ntlm.cc
old mode 100755
new mode 100644
index 6e10e79..d8e8a75
--- a/net/http/http_auth_handler_ntlm.cc
+++ b/net/http/http_auth_handler_ntlm.cc
@@ -1,33 +1,30 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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 "net/http/http_auth_handler_ntlm.h"
#include "base/base64.h"
+#include "base/logging.h"
#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
namespace net {
-std::string HttpAuthHandlerNTLM::GenerateCredentials(
- const std::wstring& username,
- const std::wstring& password,
+int HttpAuthHandlerNTLM::GenerateAuthTokenImpl(
+ const std::wstring* username,
+ const std::wstring* password,
const HttpRequestInfo* request,
- const ProxyInfo* proxy) {
+ CompletionCallback* callback,
+ std::string* auth_token) {
#if defined(NTLM_SSPI)
- std::string auth_credentials;
-
- int rv = auth_sspi_.GenerateCredentials(
+ return auth_sspi_.GenerateAuthToken(
username,
password,
- origin_,
- request,
- proxy,
- &auth_credentials);
- if (rv == OK)
- return auth_credentials;
- return std::string();
+ CreateSPN(origin_),
+ auth_token);
#else // !defined(NTLM_SSPI)
// TODO(wtc): See if we can use char* instead of void* for in_buf and
// out_buf. This change will need to propagate to GetNextToken,
@@ -41,16 +38,16 @@
// components.
std::wstring domain;
std::wstring user;
- size_t backslash_idx = username.find(L'\\');
+ size_t backslash_idx = username->find(L'\\');
if (backslash_idx == std::wstring::npos) {
- user = username;
+ user = *username;
} else {
- domain = username.substr(0, backslash_idx);
- user = username.substr(backslash_idx + 1);
+ domain = username->substr(0, backslash_idx);
+ user = username->substr(backslash_idx + 1);
}
domain_ = WideToUTF16(domain);
username_ = WideToUTF16(user);
- password_ = WideToUTF16(password);
+ password_ = WideToUTF16(*password);
// Initial challenge.
if (auth_data_.empty()) {
@@ -58,71 +55,66 @@
in_buf = NULL;
int rv = InitializeBeforeFirstChallenge();
if (rv != OK)
- return std::string();
+ return rv;
} else {
- // Decode |auth_data_| into the input buffer.
- int len = auth_data_.length();
-
- // Strip off any padding.
- // (See https://bugzilla.mozilla.org/show_bug.cgi?id=230351.)
- //
- // Our base64 decoder requires that the length be a multiple of 4.
- while (len > 0 && len % 4 != 0 && auth_data_[len - 1] == '=')
- len--;
- auth_data_.erase(len);
-
- if (!base::Base64Decode(auth_data_, &decoded_auth_data))
- return std::string(); // Improper base64 encoding
+ if (!base::Base64Decode(auth_data_, &decoded_auth_data)) {
+ LOG(ERROR) << "Unexpected problem Base64 decoding.";
+ return ERR_UNEXPECTED;
+ }
in_buf_len = decoded_auth_data.length();
in_buf = decoded_auth_data.data();
}
int rv = GetNextToken(in_buf, in_buf_len, &out_buf, &out_buf_len);
if (rv != OK)
- return std::string();
+ return rv;
// Base64 encode data in output buffer and prepend "NTLM ".
std::string encode_input(static_cast<char*>(out_buf), out_buf_len);
std::string encode_output;
- bool ok = base::Base64Encode(encode_input, &encode_output);
+ bool base64_rv = base::Base64Encode(encode_input, &encode_output);
// OK, we are done with |out_buf|
free(out_buf);
- if (!ok)
- return std::string();
- return std::string("NTLM ") + encode_output;
+ if (!base64_rv) {
+ LOG(ERROR) << "Unexpected problem Base64 encoding.";
+ return ERR_UNEXPECTED;
+ }
+ *auth_token = std::string("NTLM ") + encode_output;
+ return OK;
#endif
}
// The NTLM challenge header looks like:
// WWW-Authenticate: NTLM auth-data
bool HttpAuthHandlerNTLM::ParseChallenge(
- std::string::const_iterator challenge_begin,
- std::string::const_iterator challenge_end) {
+ HttpAuth::ChallengeTokenizer* tok) {
scheme_ = "ntlm";
score_ = 3;
properties_ = ENCRYPTS_IDENTITY | IS_CONNECTION_BASED;
#if defined(NTLM_SSPI)
- return auth_sspi_.ParseChallenge(challenge_begin, challenge_end);
+ return auth_sspi_.ParseChallenge(tok);
#else
auth_data_.clear();
// Verify the challenge's auth-scheme.
- HttpAuth::ChallengeTokenizer challenge_tok(challenge_begin, challenge_end);
- if (!challenge_tok.valid() ||
- !LowerCaseEqualsASCII(challenge_tok.scheme(), "ntlm"))
+ if (!tok->valid() || !LowerCaseEqualsASCII(tok->scheme(), "ntlm"))
return false;
- // Extract the auth-data. We can't use challenge_tok.GetNext() because
- // auth-data is base64-encoded and may contain '=' padding at the end,
- // which would be mistaken for a name=value pair.
- challenge_begin += 4; // Skip over "NTLM".
- HttpUtil::TrimLWS(&challenge_begin, &challenge_end);
-
- auth_data_.assign(challenge_begin, challenge_end);
-
+ tok->set_expect_base64_token(true);
+ if (tok->GetNext())
+ auth_data_.assign(tok->value_begin(), tok->value_end());
return true;
-#endif
+#endif // defined(NTLM_SSPI)
+}
+
+// static
+std::wstring HttpAuthHandlerNTLM::CreateSPN(const GURL& origin) {
+ // The service principal name of the destination server. See
+ // http://msdn.microsoft.com/en-us/library/ms677949%28VS.85%29.aspx
+ std::wstring target(L"HTTP/");
+ target.append(ASCIIToWide(GetHostAndPort(origin)));
+ return target;
}
} // namespace net
diff --git a/net/http/http_auth_handler_ntlm.h b/net/http/http_auth_handler_ntlm.h
index c3bda62..f22a2b5 100644
--- a/net/http/http_auth_handler_ntlm.h
+++ b/net/http/http_auth_handler_ntlm.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -27,12 +27,47 @@
#include "base/basictypes.h"
#include "base/string16.h"
#include "net/http/http_auth_handler.h"
+#include "net/http/http_auth_handler_factory.h"
namespace net {
+class URLSecurityManager;
+
// Code for handling HTTP NTLM authentication.
class HttpAuthHandlerNTLM : public HttpAuthHandler {
public:
+ class Factory : public HttpAuthHandlerFactory {
+ public:
+ Factory();
+ virtual ~Factory();
+
+ virtual int CreateAuthHandler(HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason reason,
+ int digest_nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler);
+#if defined(NTLM_SSPI)
+ // Set the SSPILibrary to use. Typically the only callers which need to
+ // use this are unit tests which pass in a mocked-out version of the
+ // SSPI library.
+ // The caller is responsible for managing the lifetime of |*sspi_library|,
+ // and the lifetime must exceed that of this Factory object and all
+ // HttpAuthHandler's that this Factory object creates.
+ void set_sspi_library(SSPILibrary* sspi_library) {
+ sspi_library_ = sspi_library;
+ }
+#endif // defined(NTLM_SSPI)
+ private:
+#if defined(NTLM_SSPI)
+ ULONG max_token_length_;
+ bool first_creation_;
+ bool is_unsupported_;
+ SSPILibrary* sspi_library_;
+#endif // defined(NTLM_SSPI)
+ };
+
#if defined(NTLM_PORTABLE)
// A function that generates n random bytes in the output buffer.
typedef void (*GenerateRandomProc)(uint8* output, size_t n);
@@ -62,23 +97,31 @@
};
#endif
+#if defined(NTLM_PORTABLE)
HttpAuthHandlerNTLM();
+#endif
+#if defined(NTLM_SSPI)
+ HttpAuthHandlerNTLM(SSPILibrary* sspi_library, ULONG max_token_length,
+ URLSecurityManager* url_security_manager);
+#endif
virtual bool NeedsIdentity();
virtual bool IsFinalRound();
- virtual std::string GenerateCredentials(const std::wstring& username,
- const std::wstring& password,
- const HttpRequestInfo* request,
- const ProxyInfo* proxy);
+ virtual bool AllowsDefaultCredentials();
protected:
- virtual bool Init(std::string::const_iterator challenge_begin,
- std::string::const_iterator challenge_end) {
- return ParseChallenge(challenge_begin, challenge_end);
+ virtual bool Init(HttpAuth::ChallengeTokenizer* tok) {
+ return ParseChallenge(tok);
}
+ virtual int GenerateAuthTokenImpl(const std::wstring* username,
+ const std::wstring* password,
+ const HttpRequestInfo* request,
+ CompletionCallback* callback,
+ std::string* auth_token);
+
// This function acquires a credentials handle in the SSPI implementation.
// It does nothing in the portable implementation.
int InitializeBeforeFirstChallenge();
@@ -95,8 +138,7 @@
// Parse the challenge, saving the results into this instance.
// Returns true on success.
- bool ParseChallenge(std::string::const_iterator challenge_begin,
- std::string::const_iterator challenge_end);
+ bool ParseChallenge(HttpAuth::ChallengeTokenizer* tok);
// Given an input token received from the server, generate the next output
// token to be sent to the server.
@@ -105,6 +147,9 @@
void** out_token,
uint32* out_token_len);
+ // Create an NTLM SPN to identify the |origin| server.
+ static std::wstring CreateSPN(const GURL& origin);
+
#if defined(NTLM_SSPI)
HttpAuthSSPI auth_sspi_;
#endif
@@ -121,6 +166,10 @@
// The base64-encoded string following "NTLM" in the "WWW-Authenticate" or
// "Proxy-Authenticate" response header.
std::string auth_data_;
+
+#if defined(NTLM_SSPI)
+ URLSecurityManager* url_security_manager_;
+#endif
};
} // namespace net
diff --git a/net/http/http_auth_handler_ntlm_portable.cc b/net/http/http_auth_handler_ntlm_portable.cc
index 7976878..e16d32c 100644
--- a/net/http/http_auth_handler_ntlm_portable.cc
+++ b/net/http/http_auth_handler_ntlm_portable.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -16,6 +16,7 @@
#include "base/rand_util.h"
#include "base/string_util.h"
#include "base/sys_string_conversions.h"
+#include "base/utf_string_conversions.h"
#include "net/base/net_errors.h"
#include "net/base/net_util.h"
#include "net/http/des.h"
@@ -120,13 +121,12 @@
// We send these flags with our type 1 message.
enum {
- NTLM_TYPE1_FLAGS =
- NTLM_NegotiateUnicode |
- NTLM_NegotiateOEM |
- NTLM_RequestTarget |
- NTLM_NegotiateNTLMKey |
- NTLM_NegotiateAlwaysSign |
- NTLM_NegotiateNTLM2Key
+ NTLM_TYPE1_FLAGS = (NTLM_NegotiateUnicode |
+ NTLM_NegotiateOEM |
+ NTLM_RequestTarget |
+ NTLM_NegotiateNTLMKey |
+ NTLM_NegotiateAlwaysSign |
+ NTLM_NegotiateNTLM2Key)
};
static const char NTLM_SIGNATURE[] = "NTLMSSP";
@@ -657,6 +657,12 @@
return !auth_data_.empty();
}
+bool HttpAuthHandlerNTLM::AllowsDefaultCredentials() {
+ // Default credentials are not supported in the portable implementation of
+ // NTLM, but are supported in the SSPI implementation.
+ return false;
+}
+
// static
HttpAuthHandlerNTLM::GenerateRandomProc
HttpAuthHandlerNTLM::SetGenerateRandomProc(
@@ -704,4 +710,31 @@
return OK;
}
+HttpAuthHandlerNTLM::Factory::Factory() {
+}
+
+HttpAuthHandlerNTLM::Factory::~Factory() {
+}
+
+int HttpAuthHandlerNTLM::Factory::CreateAuthHandler(
+ HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason reason,
+ int digest_nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) {
+ if (reason == CREATE_PREEMPTIVE)
+ return ERR_UNSUPPORTED_AUTH_SCHEME;
+ // TODO(cbentzel): Move towards model of parsing in the factory
+ // method and only constructing when valid.
+ // NOTE: Default credentials are not supported for the portable implementation
+ // of NTLM.
+ scoped_ptr<HttpAuthHandler> tmp_handler(new HttpAuthHandlerNTLM);
+ if (!tmp_handler->InitFromChallenge(challenge, target, origin, net_log))
+ return ERR_INVALID_RESPONSE;
+ handler->swap(tmp_handler);
+ return OK;
+}
+
} // namespace net
diff --git a/net/http/http_auth_handler_ntlm_win.cc b/net/http/http_auth_handler_ntlm_win.cc
index fba9c1b..0f67c68 100644
--- a/net/http/http_auth_handler_ntlm_win.cc
+++ b/net/http/http_auth_handler_ntlm_win.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -14,12 +14,17 @@
#include "net/base/net_errors.h"
#include "net/base/net_util.h"
#include "net/http/http_auth_sspi_win.h"
+#include "net/http/url_security_manager.h"
#pragma comment(lib, "secur32.lib")
namespace net {
-HttpAuthHandlerNTLM::HttpAuthHandlerNTLM() : auth_sspi_("NTLM", NTLMSP_NAME) {
+HttpAuthHandlerNTLM::HttpAuthHandlerNTLM(
+ SSPILibrary* sspi_library, ULONG max_token_length,
+ URLSecurityManager* url_security_manager)
+ : auth_sspi_(sspi_library, "NTLM", NTLMSP_NAME, max_token_length),
+ url_security_manager_(url_security_manager) {
}
HttpAuthHandlerNTLM::~HttpAuthHandlerNTLM() {
@@ -34,6 +39,51 @@
return auth_sspi_.IsFinalRound();
}
+bool HttpAuthHandlerNTLM::AllowsDefaultCredentials() {
+ if (target_ == HttpAuth::AUTH_PROXY)
+ return true;
+ if (!url_security_manager_)
+ return false;
+ return url_security_manager_->CanUseDefaultCredentials(origin_);
+}
+
+HttpAuthHandlerNTLM::Factory::Factory()
+ : max_token_length_(0),
+ first_creation_(true),
+ is_unsupported_(false),
+ sspi_library_(SSPILibrary::GetDefault()) {
+}
+
+HttpAuthHandlerNTLM::Factory::~Factory() {
+}
+
+int HttpAuthHandlerNTLM::Factory::CreateAuthHandler(
+ HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason reason,
+ int digest_nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) {
+ if (is_unsupported_ || reason == CREATE_PREEMPTIVE)
+ return ERR_UNSUPPORTED_AUTH_SCHEME;
+ if (max_token_length_ == 0) {
+ int rv = DetermineMaxTokenLength(sspi_library_, NTLMSP_NAME,
+ &max_token_length_);
+ if (rv == ERR_UNSUPPORTED_AUTH_SCHEME)
+ is_unsupported_ = true;
+ if (rv != OK)
+ return rv;
+ }
+ // TODO(cbentzel): Move towards model of parsing in the factory
+ // method and only constructing when valid.
+ scoped_ptr<HttpAuthHandler> tmp_handler(
+ new HttpAuthHandlerNTLM(sspi_library_, max_token_length_,
+ url_security_manager()));
+ if (!tmp_handler->InitFromChallenge(challenge, target, origin, net_log))
+ return ERR_INVALID_RESPONSE;
+ handler->swap(tmp_handler);
+ return OK;
+}
} // namespace net
-
diff --git a/net/http/http_auth_handler_unittest.cc b/net/http/http_auth_handler_unittest.cc
new file mode 100644
index 0000000..2516745
--- /dev/null
+++ b/net/http/http_auth_handler_unittest.cc
@@ -0,0 +1,60 @@
+// 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 "net/http/http_auth_handler.h"
+
+#include "net/base/capturing_net_log.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/test_completion_callback.h"
+#include "net/http/http_auth_handler_mock.h"
+#include "net/http/http_request_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+TEST(HttpAuthHandlerTest, NetLog) {
+ NetLog::Source source;
+ GURL origin("http://www.example.com");
+ std::string challenge = "Mock asdf";
+ std::wstring username = L"user";
+ std::wstring password = L"pass";
+ std::string auth_token;
+ HttpRequestInfo request;
+
+ for (int i = 0; i < 2; ++i) {
+ bool async = (i == 0);
+ for (int j = 0; j < 2; ++j) {
+ int rv = (j == 0) ? OK : ERR_UNEXPECTED;
+ for (int k = 0; k < 2; ++k) {
+ TestCompletionCallback test_callback;
+ HttpAuth::Target target =
+ (k == 0) ? HttpAuth::AUTH_PROXY : HttpAuth::AUTH_SERVER;
+ NetLog::EventType event_type =
+ (k == 0) ? NetLog::TYPE_AUTH_PROXY : NetLog::TYPE_AUTH_SERVER;
+ HttpAuth::ChallengeTokenizer tokenizer(
+ challenge.begin(), challenge.end());
+ HttpAuthHandlerMock mock_handler;
+ CapturingNetLog capturing_net_log(CapturingNetLog::kUnbounded);
+ BoundNetLog bound_net_log(source, &capturing_net_log);
+
+ mock_handler.InitFromChallenge(&tokenizer, target,
+ origin, bound_net_log);
+ mock_handler.SetGenerateExpectation(async, rv);
+ mock_handler.GenerateAuthToken(&username, &password, &request,
+ &test_callback, &auth_token);
+ if (async)
+ test_callback.WaitForResult();
+
+ EXPECT_EQ(2u, capturing_net_log.entries().size());
+ EXPECT_TRUE(LogContainsBeginEvent(capturing_net_log.entries(),
+ 0, event_type));
+ EXPECT_TRUE(LogContainsEndEvent(capturing_net_log.entries(),
+ 1, event_type));
+ }
+ }
+ }
+}
+
+} // namespace net
diff --git a/net/http/http_auth_sspi_win.cc b/net/http/http_auth_sspi_win.cc
index 00861e5..221acf1 100644
--- a/net/http/http_auth_sspi_win.cc
+++ b/net/http/http_auth_sspi_win.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -9,18 +9,106 @@
#include "base/base64.h"
#include "base/logging.h"
+#include "base/singleton.h"
#include "base/string_util.h"
#include "net/base/net_errors.h"
-#include "net/base/net_util.h"
#include "net/http/http_auth.h"
namespace net {
-HttpAuthSSPI::HttpAuthSSPI(const std::string& scheme,
- SEC_WCHAR* security_package)
- : scheme_(scheme),
+namespace {
+
+int MapAcquireCredentialsStatusToError(SECURITY_STATUS status,
+ const SEC_WCHAR* package) {
+ switch (status) {
+ case SEC_E_OK:
+ return OK;
+ case SEC_E_INSUFFICIENT_MEMORY:
+ return ERR_OUT_OF_MEMORY;
+ case SEC_E_INTERNAL_ERROR:
+ return ERR_UNEXPECTED;
+ case SEC_E_NO_CREDENTIALS:
+ case SEC_E_NOT_OWNER:
+ case SEC_E_UNKNOWN_CREDENTIALS:
+ return ERR_INVALID_AUTH_CREDENTIALS;
+ case SEC_E_SECPKG_NOT_FOUND:
+ // This indicates that the SSPI configuration does not match expectations
+ LOG(ERROR) << "Received SEC_E_SECPKG_NOT_FOUND for " << package;
+ return ERR_UNSUPPORTED_AUTH_SCHEME;
+ default:
+ LOG(ERROR) << "Unexpected SECURITY_STATUS " << status;
+ return ERR_UNEXPECTED;
+ }
+}
+
+int AcquireExplicitCredentials(SSPILibrary* library,
+ const SEC_WCHAR* package,
+ const std::wstring& domain,
+ const std::wstring& user,
+ const std::wstring& password,
+ CredHandle* cred) {
+ SEC_WINNT_AUTH_IDENTITY identity;
+ identity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
+ identity.User =
+ reinterpret_cast<unsigned short*>(const_cast<wchar_t*>(user.c_str()));
+ identity.UserLength = user.size();
+ identity.Domain =
+ reinterpret_cast<unsigned short*>(const_cast<wchar_t*>(domain.c_str()));
+ identity.DomainLength = domain.size();
+ identity.Password =
+ reinterpret_cast<unsigned short*>(const_cast<wchar_t*>(password.c_str()));
+ identity.PasswordLength = password.size();
+
+ TimeStamp expiry;
+
+ // Pass the username/password to get the credentials handle.
+ SECURITY_STATUS status = library->AcquireCredentialsHandle(
+ NULL, // pszPrincipal
+ const_cast<SEC_WCHAR*>(package), // pszPackage
+ SECPKG_CRED_OUTBOUND, // fCredentialUse
+ NULL, // pvLogonID
+ &identity, // pAuthData
+ NULL, // pGetKeyFn (not used)
+ NULL, // pvGetKeyArgument (not used)
+ cred, // phCredential
+ &expiry); // ptsExpiry
+
+ return MapAcquireCredentialsStatusToError(status, package);
+}
+
+int AcquireDefaultCredentials(SSPILibrary* library, const SEC_WCHAR* package,
+ CredHandle* cred) {
+ TimeStamp expiry;
+
+ // Pass the username/password to get the credentials handle.
+ // Note: Since the 5th argument is NULL, it uses the default
+ // cached credentials for the logged in user, which can be used
+ // for a single sign-on.
+ SECURITY_STATUS status = library->AcquireCredentialsHandle(
+ NULL, // pszPrincipal
+ const_cast<SEC_WCHAR*>(package), // pszPackage
+ SECPKG_CRED_OUTBOUND, // fCredentialUse
+ NULL, // pvLogonID
+ NULL, // pAuthData
+ NULL, // pGetKeyFn (not used)
+ NULL, // pvGetKeyArgument (not used)
+ cred, // phCredential
+ &expiry); // ptsExpiry
+
+ return MapAcquireCredentialsStatusToError(status, package);
+}
+
+} // anonymous namespace
+
+HttpAuthSSPI::HttpAuthSSPI(SSPILibrary* library,
+ const std::string& scheme,
+ SEC_WCHAR* security_package,
+ ULONG max_token_length)
+ : library_(library),
+ scheme_(scheme),
security_package_(security_package),
- max_token_length_(0) {
+ max_token_length_(max_token_length) {
+ DCHECK(library_);
SecInvalidateHandle(&cred_);
SecInvalidateHandle(&ctxt_);
}
@@ -28,7 +116,7 @@
HttpAuthSSPI::~HttpAuthSSPI() {
ResetSecurityContext();
if (SecIsValidHandle(&cred_)) {
- FreeCredentialsHandle(&cred_);
+ library_->FreeCredentialsHandle(&cred_);
SecInvalidateHandle(&cred_);
}
}
@@ -43,66 +131,52 @@
void HttpAuthSSPI::ResetSecurityContext() {
if (SecIsValidHandle(&ctxt_)) {
- DeleteSecurityContext(&ctxt_);
+ library_->DeleteSecurityContext(&ctxt_);
SecInvalidateHandle(&ctxt_);
}
}
-bool HttpAuthSSPI::ParseChallenge(std::string::const_iterator challenge_begin,
- std::string::const_iterator challenge_end) {
+bool HttpAuthSSPI::ParseChallenge(HttpAuth::ChallengeTokenizer* tok) {
// Verify the challenge's auth-scheme.
- HttpAuth::ChallengeTokenizer challenge_tok(challenge_begin, challenge_end);
- if (!challenge_tok.valid() ||
- !LowerCaseEqualsASCII(challenge_tok.scheme(),
- StringToLowerASCII(scheme_).c_str()))
+ if (!tok->valid() ||
+ !LowerCaseEqualsASCII(tok->scheme(), StringToLowerASCII(scheme_).c_str()))
return false;
- // Extract the auth-data. We can't use challenge_tok.GetNext() because
- // auth-data is base64-encoded and may contain '=' padding at the end,
- // which would be mistaken for a name=value pair.
- challenge_begin += scheme_.length(); // Skip over scheme name.
- HttpUtil::TrimLWS(&challenge_begin, &challenge_end);
- std::string encoded_auth_token(challenge_begin, challenge_end);
- int encoded_length = encoded_auth_token.length();
- // Strip off any padding.
- // (See https://bugzilla.mozilla.org/show_bug.cgi?id=230351.)
- //
- // Our base64 decoder requires that the length be a multiple of 4.
- while (encoded_length > 0 && encoded_length % 4 != 0 &&
- encoded_auth_token[encoded_length - 1] == '=')
- encoded_length--;
- encoded_auth_token.erase(encoded_length);
- std::string decoded_auth_token;
- bool rv = base::Base64Decode(encoded_auth_token, &decoded_auth_token);
- if (rv) {
- decoded_server_auth_token_ = decoded_auth_token;
+ tok->set_expect_base64_token(true);
+ if (!tok->GetNext()) {
+ decoded_server_auth_token_.clear();
+ return true;
}
- return rv;
+
+ std::string encoded_auth_token = tok->value();
+ std::string decoded_auth_token;
+ bool base64_rv = base::Base64Decode(encoded_auth_token, &decoded_auth_token);
+ if (!base64_rv) {
+ LOG(ERROR) << "Base64 decoding of auth token failed.";
+ return false;
+ }
+ decoded_server_auth_token_ = decoded_auth_token;
+ return true;
}
-int HttpAuthSSPI::GenerateCredentials(const std::wstring& username,
- const std::wstring& password,
- const GURL& origin,
- const HttpRequestInfo* request,
- const ProxyInfo* proxy,
- std::string* out_credentials) {
- // |username| may be in the form "DOMAIN\user". Parse it into the two
- // components.
- std::wstring domain;
- std::wstring user;
- SplitDomainAndUser(username, &domain, &user);
+int HttpAuthSSPI::GenerateAuthToken(const std::wstring* username,
+ const std::wstring* password,
+ const std::wstring& spn,
+ std::string* auth_token) {
+ DCHECK((username == NULL) == (password == NULL));
// Initial challenge.
if (!IsFinalRound()) {
- int rv = OnFirstRound(domain, user, password);
+ int rv = OnFirstRound(username, password);
if (rv != OK)
return rv;
}
+ DCHECK(SecIsValidHandle(&cred_));
void* out_buf;
int out_buf_len;
int rv = GetNextSecurityToken(
- origin,
+ spn,
static_cast<void *>(const_cast<char *>(
decoded_server_auth_token_.c_str())),
decoded_server_auth_token_.length(),
@@ -114,28 +188,41 @@
// Base64 encode data in output buffer and prepend the scheme.
std::string encode_input(static_cast<char*>(out_buf), out_buf_len);
std::string encode_output;
- bool ok = base::Base64Encode(encode_input, &encode_output);
+ bool base64_rv = base::Base64Encode(encode_input, &encode_output);
// OK, we are done with |out_buf|
free(out_buf);
- if (!ok)
- return rv;
- *out_credentials = scheme_ + " " + encode_output;
+ if (!base64_rv) {
+ LOG(ERROR) << "Base64 encoding of auth token failed.";
+ return ERR_UNEXPECTED;
+ }
+ *auth_token = scheme_ + " " + encode_output;
return OK;
}
-int HttpAuthSSPI::OnFirstRound(const std::wstring& domain,
- const std::wstring& user,
- const std::wstring& password) {
- int rv = DetermineMaxTokenLength(security_package_, &max_token_length_);
- if (rv != OK) {
- return rv;
+int HttpAuthSSPI::OnFirstRound(const std::wstring* username,
+ const std::wstring* password) {
+ DCHECK((username == NULL) == (password == NULL));
+ DCHECK(!SecIsValidHandle(&cred_));
+ int rv = OK;
+ if (username) {
+ std::wstring domain;
+ std::wstring user;
+ SplitDomainAndUser(*username, &domain, &user);
+ rv = AcquireExplicitCredentials(library_, security_package_, domain,
+ user, *password, &cred_);
+ if (rv != OK)
+ return rv;
+ } else {
+ rv = AcquireDefaultCredentials(library_, security_package_, &cred_);
+ if (rv != OK)
+ return rv;
}
- rv = AcquireCredentials(security_package_, domain, user, password, &cred_);
+
return rv;
}
int HttpAuthSSPI::GetNextSecurityToken(
- const GURL& origin,
+ const std::wstring& spn,
const void * in_token,
int in_token_len,
void** out_token,
@@ -181,30 +268,25 @@
if (!out_buffer.pvBuffer)
return ERR_OUT_OF_MEMORY;
- // The service principal name of the destination server. See
- // http://msdn.microsoft.com/en-us/library/ms677949%28VS.85%29.aspx
- std::wstring target(L"HTTP/");
- target.append(ASCIIToWide(GetHostAndPort(origin)));
- wchar_t* target_name = const_cast<wchar_t*>(target.c_str());
-
// This returns a token that is passed to the remote server.
- status = InitializeSecurityContext(&cred_, // phCredential
- ctxt_ptr, // phContext
- target_name, // pszTargetName
- 0, // fContextReq
- 0, // Reserved1 (must be 0)
- SECURITY_NATIVE_DREP, // TargetDataRep
- in_buffer_desc_ptr, // pInput
- 0, // Reserved2 (must be 0)
- &ctxt_, // phNewContext
- &out_buffer_desc, // pOutput
- &ctxt_attr, // pfContextAttr
- &expiry); // ptsExpiry
+ status = library_->InitializeSecurityContext(
+ &cred_, // phCredential
+ ctxt_ptr, // phContext
+ const_cast<wchar_t *>(spn.c_str()), // pszTargetName
+ 0, // fContextReq
+ 0, // Reserved1 (must be 0)
+ SECURITY_NATIVE_DREP, // TargetDataRep
+ in_buffer_desc_ptr, // pInput
+ 0, // Reserved2 (must be 0)
+ &ctxt_, // phNewContext
+ &out_buffer_desc, // pOutput
+ &ctxt_attr, // pfContextAttr
+ &expiry); // ptsExpiry
// On success, the function returns SEC_I_CONTINUE_NEEDED on the first call
// and SEC_E_OK on the second call. On failure, the function returns an
// error code.
if (status != SEC_I_CONTINUE_NEEDED && status != SEC_E_OK) {
- LOG(ERROR) << "InitializeSecurityContext failed: " << status;
+ LOG(ERROR) << "InitializeSecurityContext failed " << status;
ResetSecurityContext();
free(out_buffer.pvBuffer);
return ERR_UNEXPECTED; // TODO(wtc): map error code.
@@ -221,6 +303,9 @@
void SplitDomainAndUser(const std::wstring& combined,
std::wstring* domain,
std::wstring* user) {
+ // |combined| may be in the form "user" or "DOMAIN\user".
+ // Separatethe two parts if they exist.
+ // TODO(cbentzel): I believe user@domain is also a valid form.
size_t backslash_idx = combined.find(L'\\');
if (backslash_idx == std::wstring::npos) {
domain->clear();
@@ -231,56 +316,105 @@
}
}
-int DetermineMaxTokenLength(const std::wstring& package,
+int DetermineMaxTokenLength(SSPILibrary* library,
+ const std::wstring& package,
ULONG* max_token_length) {
- PSecPkgInfo pkg_info;
- SECURITY_STATUS status = QuerySecurityPackageInfo(
+ DCHECK(library);
+ DCHECK(max_token_length);
+ PSecPkgInfo pkg_info = NULL;
+ SECURITY_STATUS status = library->QuerySecurityPackageInfo(
const_cast<wchar_t *>(package.c_str()), &pkg_info);
if (status != SEC_E_OK) {
- LOG(ERROR) << "Security package " << package << " not found";
+ // The documentation at
+ // http://msdn.microsoft.com/en-us/library/aa379359(VS.85).aspx
+ // only mentions that a non-zero (or non-SEC_E_OK) value is returned
+ // if the function fails. In practice, it appears to return
+ // SEC_E_SECPKG_NOT_FOUND for invalid/unknown packages.
+ LOG(ERROR) << "Security package " << package << " not found."
+ << " Status code: " << status;
+ if (status == SEC_E_SECPKG_NOT_FOUND)
+ return ERR_UNSUPPORTED_AUTH_SCHEME;
+ else
+ return ERR_UNEXPECTED;
+ }
+ int token_length = pkg_info->cbMaxToken;
+ status = library->FreeContextBuffer(pkg_info);
+ if (status != SEC_E_OK) {
+ // The documentation at
+ // http://msdn.microsoft.com/en-us/library/aa375416(VS.85).aspx
+ // only mentions that a non-zero (or non-SEC_E_OK) value is returned
+ // if the function fails, and does not indicate what the failure conditions
+ // are.
+ LOG(ERROR) << "Unexpected problem freeing context buffer. Status code: "
+ << status;
return ERR_UNEXPECTED;
}
- *max_token_length = pkg_info->cbMaxToken;
- FreeContextBuffer(pkg_info);
+ *max_token_length = token_length;
return OK;
}
-int AcquireCredentials(const SEC_WCHAR* package,
- const std::wstring& domain,
- const std::wstring& user,
- const std::wstring& password,
- CredHandle* cred) {
- SEC_WINNT_AUTH_IDENTITY identity;
- identity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
- identity.User =
- reinterpret_cast<unsigned short*>(const_cast<wchar_t*>(user.c_str()));
- identity.UserLength = user.size();
- identity.Domain =
- reinterpret_cast<unsigned short*>(const_cast<wchar_t*>(domain.c_str()));
- identity.DomainLength = domain.size();
- identity.Password =
- reinterpret_cast<unsigned short*>(const_cast<wchar_t*>(password.c_str()));
- identity.PasswordLength = password.size();
+class SSPILibraryDefault : public SSPILibrary {
+ public:
+ SSPILibraryDefault() {}
+ virtual ~SSPILibraryDefault() {}
- TimeStamp expiry;
+ virtual SECURITY_STATUS AcquireCredentialsHandle(LPWSTR pszPrincipal,
+ LPWSTR pszPackage,
+ unsigned long fCredentialUse,
+ void* pvLogonId,
+ void* pvAuthData,
+ SEC_GET_KEY_FN pGetKeyFn,
+ void* pvGetKeyArgument,
+ PCredHandle phCredential,
+ PTimeStamp ptsExpiry) {
+ return ::AcquireCredentialsHandle(pszPrincipal, pszPackage, fCredentialUse,
+ pvLogonId, pvAuthData, pGetKeyFn,
+ pvGetKeyArgument, phCredential,
+ ptsExpiry);
+ }
- // Pass the username/password to get the credentials handle.
- // Note: If the 5th argument is NULL, it uses the default cached credentials
- // for the logged in user, which can be used for single sign-on.
- SECURITY_STATUS status = AcquireCredentialsHandle(
- NULL, // pszPrincipal
- const_cast<SEC_WCHAR*>(package), // pszPackage
- SECPKG_CRED_OUTBOUND, // fCredentialUse
- NULL, // pvLogonID
- &identity, // pAuthData
- NULL, // pGetKeyFn (not used)
- NULL, // pvGetKeyArgument (not used)
- cred, // phCredential
- &expiry); // ptsExpiry
+ virtual SECURITY_STATUS InitializeSecurityContext(PCredHandle phCredential,
+ PCtxtHandle phContext,
+ SEC_WCHAR* pszTargetName,
+ unsigned long fContextReq,
+ unsigned long Reserved1,
+ unsigned long TargetDataRep,
+ PSecBufferDesc pInput,
+ unsigned long Reserved2,
+ PCtxtHandle phNewContext,
+ PSecBufferDesc pOutput,
+ unsigned long* contextAttr,
+ PTimeStamp ptsExpiry) {
+ return ::InitializeSecurityContext(phCredential, phContext, pszTargetName,
+ fContextReq, Reserved1, TargetDataRep,
+ pInput, Reserved2, phNewContext, pOutput,
+ contextAttr, ptsExpiry);
+ }
- if (status != SEC_E_OK)
- return ERR_UNEXPECTED;
- return OK;
+ virtual SECURITY_STATUS QuerySecurityPackageInfo(LPWSTR pszPackageName,
+ PSecPkgInfoW *pkgInfo) {
+ return ::QuerySecurityPackageInfo(pszPackageName, pkgInfo);
+ }
+
+ virtual SECURITY_STATUS FreeCredentialsHandle(PCredHandle phCredential) {
+ return ::FreeCredentialsHandle(phCredential);
+ }
+
+ virtual SECURITY_STATUS DeleteSecurityContext(PCtxtHandle phContext) {
+ return ::DeleteSecurityContext(phContext);
+ }
+
+ virtual SECURITY_STATUS FreeContextBuffer(PVOID pvContextBuffer) {
+ return ::FreeContextBuffer(pvContextBuffer);
+ }
+
+ private:
+ friend struct DefaultSingletonTraits<SSPILibraryDefault>;
+};
+
+// static
+SSPILibrary* SSPILibrary::GetDefault() {
+ return Singleton<SSPILibraryDefault>::get();
}
} // namespace net
diff --git a/net/http/http_auth_sspi_win.h b/net/http/http_auth_sspi_win.h
old mode 100755
new mode 100644
index 4d340aa..14d158a
--- a/net/http/http_auth_sspi_win.h
+++ b/net/http/http_auth_sspi_win.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -16,45 +16,101 @@
#include <string>
-class GURL;
+#include "net/http/http_auth.h"
namespace net {
-class HttpRequestInfo;
+struct HttpRequestInfo;
class ProxyInfo;
+// SSPILibrary is introduced so unit tests can mock the calls to Windows' SSPI
+// implementation. The default implementation simply passes the arguments on to
+// the SSPI implementation provided by Secur32.dll.
+// NOTE(cbentzel): I considered replacing the Secur32.dll with a mock DLL, but
+// decided that it wasn't worth the effort as this is unlikely to be performance
+// sensitive code.
+class SSPILibrary {
+ public:
+ virtual ~SSPILibrary() {}
+
+ virtual SECURITY_STATUS AcquireCredentialsHandle(LPWSTR pszPrincipal,
+ LPWSTR pszPackage,
+ unsigned long fCredentialUse,
+ void* pvLogonId,
+ void* pvAuthData,
+ SEC_GET_KEY_FN pGetKeyFn,
+ void* pvGetKeyArgument,
+ PCredHandle phCredential,
+ PTimeStamp ptsExpiry) = 0;
+
+ virtual SECURITY_STATUS InitializeSecurityContext(PCredHandle phCredential,
+ PCtxtHandle phContext,
+ SEC_WCHAR* pszTargetName,
+ unsigned long fContextReq,
+ unsigned long Reserved1,
+ unsigned long TargetDataRep,
+ PSecBufferDesc pInput,
+ unsigned long Reserved2,
+ PCtxtHandle phNewContext,
+ PSecBufferDesc pOutput,
+ unsigned long* contextAttr,
+ PTimeStamp ptsExpiry) = 0;
+
+ virtual SECURITY_STATUS QuerySecurityPackageInfo(LPWSTR pszPackageName,
+ PSecPkgInfoW *pkgInfo) = 0;
+
+ virtual SECURITY_STATUS FreeCredentialsHandle(PCredHandle phCredential) = 0;
+
+ virtual SECURITY_STATUS DeleteSecurityContext(PCtxtHandle phContext) = 0;
+
+ virtual SECURITY_STATUS FreeContextBuffer(PVOID pvContextBuffer) = 0;
+
+ // Get the default SSPILibrary instance, which simply acts as a passthrough
+ // to the Windows SSPI implementation. The object returned is a singleton
+ // instance, and the caller should not delete it.
+ static SSPILibrary* GetDefault();
+};
+
class HttpAuthSSPI {
public:
- HttpAuthSSPI(const std::string& scheme,
- SEC_WCHAR* security_package);
+ HttpAuthSSPI(SSPILibrary* sspi_library,
+ const std::string& scheme,
+ SEC_WCHAR* security_package,
+ ULONG max_token_length);
~HttpAuthSSPI();
bool NeedsIdentity() const;
bool IsFinalRound() const;
- bool ParseChallenge(std::string::const_iterator challenge_begin,
- std::string::const_iterator challenge_end);
+ bool ParseChallenge(HttpAuth::ChallengeTokenizer* tok);
- int GenerateCredentials(const std::wstring& username,
- const std::wstring& password,
- const GURL& origin,
- const HttpRequestInfo* request,
- const ProxyInfo* proxy,
- std::string* out_credentials);
+ // Generates an authentication token for the service specified by the
+ // Service Principal Name |spn| and stores the value in |*auth_token|.
+ // If the return value is not |OK|, then the value of |*auth_token| is
+ // unspecified. ERR_IO_PENDING is not a valid return code.
+ // If this is the first round of a multiple round scheme, credentials are
+ // obtained using |*username| and |*password|. If |username| and |password|
+ // are both NULL, the credentials for the currently logged in user are used
+ // instead.
+ int GenerateAuthToken(const std::wstring* username,
+ const std::wstring* password,
+ const std::wstring& spn,
+ std::string* auth_token);
private:
- int OnFirstRound(const std::wstring& domain,
- const std::wstring& user,
- const std::wstring& password);
+ int OnFirstRound(const std::wstring* username,
+ const std::wstring* password);
int GetNextSecurityToken(
- const GURL& origin,
+ const std::wstring& spn,
const void* in_token,
int in_token_len,
void** out_token,
int* out_token_len);
void ResetSecurityContext();
+
+ SSPILibrary* library_;
std::string scheme_;
SEC_WCHAR* security_package_;
std::string decoded_server_auth_token_;
@@ -73,20 +129,24 @@
std::wstring* domain,
std::wstring* user);
-// Determines the max token length for a particular SSPI package.
-// If the return value is not OK, than the value of max_token_length
-// is undefined.
-// |max_token_length| must be non-NULL.
-int DetermineMaxTokenLength(const std::wstring& package,
+// Determines the maximum token length in bytes for a particular SSPI package.
+//
+// |library| and |max_token_length| must be non-NULL pointers to valid objects.
+//
+// If the return value is OK, |*max_token_length| contains the maximum token
+// length in bytes.
+//
+// If the return value is ERR_UNSUPPORTED_AUTH_SCHEME, |package| is not an
+// known SSPI authentication scheme on this system. |*max_token_length| is not
+// changed.
+//
+// If the return value is ERR_UNEXPECTED, there was an unanticipated problem
+// in the underlying SSPI call. The details are logged, and |*max_token_length|
+// is not changed.
+int DetermineMaxTokenLength(SSPILibrary* library,
+ const std::wstring& package,
ULONG* max_token_length);
-// Acquire credentials for a user.
-int AcquireCredentials(const SEC_WCHAR* package,
- const std::wstring& domain,
- const std::wstring& user,
- const std::wstring& password,
- CredHandle* cred);
-
} // namespace net
-#endif // NET_HTTP_HTTP_AUTH_SSPI_WIN_H_
+#endif // NET_HTTP_HTTP_AUTH_SSPI_WIN_H_
diff --git a/net/http/http_auth_sspi_win_unittest.cc b/net/http/http_auth_sspi_win_unittest.cc
index b421ca9..fdef793 100644
--- a/net/http/http_auth_sspi_win_unittest.cc
+++ b/net/http/http_auth_sspi_win_unittest.cc
@@ -1,13 +1,17 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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/base/net_errors.h"
#include "net/http/http_auth_sspi_win.h"
+#include "net/http/mock_sspi_library_win.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace net {
+namespace {
+
void MatchDomainUserAfterSplit(const std::wstring& combined,
const std::wstring& expected_domain,
const std::wstring& expected_user) {
@@ -18,9 +22,36 @@
EXPECT_EQ(expected_user, actual_user);
}
-TEST(HttpAuthHandlerSspiWinTest, SplitUserAndDomain) {
+} // namespace
+
+TEST(HttpAuthSSPITest, SplitUserAndDomain) {
MatchDomainUserAfterSplit(L"foobar", L"", L"foobar");
MatchDomainUserAfterSplit(L"FOO\\bar", L"FOO", L"bar");
}
+TEST(HttpAuthSSPITest, DetermineMaxTokenLength_Normal) {
+ SecPkgInfoW package_info;
+ memset(&package_info, 0x0, sizeof(package_info));
+ package_info.cbMaxToken = 1337;
+
+ MockSSPILibrary mock_library;
+ mock_library.ExpectQuerySecurityPackageInfo(L"NTLM", SEC_E_OK, &package_info);
+ ULONG max_token_length = 100;
+ int rv = DetermineMaxTokenLength(&mock_library, L"NTLM", &max_token_length);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ(1337, max_token_length);
+}
+
+TEST(HttpAuthSSPITest, DetermineMaxTokenLength_InvalidPackage) {
+ MockSSPILibrary mock_library;
+ mock_library.ExpectQuerySecurityPackageInfo(L"Foo", SEC_E_SECPKG_NOT_FOUND,
+ NULL);
+ ULONG max_token_length = 100;
+ int rv = DetermineMaxTokenLength(&mock_library, L"Foo", &max_token_length);
+ EXPECT_EQ(ERR_UNSUPPORTED_AUTH_SCHEME, rv);
+ // |DetermineMaxTokenLength()| interface states that |max_token_length| should
+ // not change on failure.
+ EXPECT_EQ(100, max_token_length);
+}
+
} // namespace net
diff --git a/net/http/http_auth_unittest.cc b/net/http/http_auth_unittest.cc
old mode 100755
new mode 100644
index a960d70..cdc7e16
--- a/net/http/http_auth_unittest.cc
+++ b/net/http/http_auth_unittest.cc
@@ -1,14 +1,21 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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 "testing/gtest/include/gtest/gtest.h"
+#include <set>
+#include <string>
#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "base/string_util.h"
+#include "net/base/net_errors.h"
#include "net/http/http_auth.h"
+#include "net/http/http_auth_filter.h"
#include "net/http/http_auth_handler.h"
+#include "net/http/http_auth_handler_factory.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
namespace net {
@@ -56,18 +63,20 @@
"WWW-Authenticate: Negotiate\n"
"WWW-Authenticate: NTLM\n",
- // Negotiate is not currently support on non-Windows platforms, so
- // the choice varies depending on platform.
-#if defined(OS_WIN)
+ // TODO(ahendrickson): This may be flaky on Linux and OSX as it
+ // relies on being able to load one of the known .so files
+ // for gssapi.
"negotiate",
"",
-#else
- "ntlm",
- "",
-#endif
}
};
GURL origin("http://www.example.com");
+ std::set<std::string> disabled_schemes;
+ URLSecurityManagerAllow url_security_manager;
+ scoped_ptr<HttpAuthHandlerRegistryFactory> http_auth_handler_factory(
+ HttpAuthHandlerFactory::CreateDefault());
+ http_auth_handler_factory->SetURLSecurityManager(
+ "negotiate", &url_security_manager);
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
// Make a HttpResponseHeaders object.
@@ -79,13 +88,16 @@
headers_with_status_line.c_str(),
headers_with_status_line.length())));
- scoped_refptr<HttpAuthHandler> handler;
- HttpAuth::ChooseBestChallenge(headers.get(),
+ scoped_ptr<HttpAuthHandler> handler;
+ HttpAuth::ChooseBestChallenge(http_auth_handler_factory.get(),
+ headers.get(),
HttpAuth::AUTH_SERVER,
origin,
+ disabled_schemes,
+ BoundNetLog(),
&handler);
- if (handler) {
+ if (handler.get()) {
EXPECT_STREQ(tests[i].challenge_scheme, handler->scheme().c_str());
EXPECT_STREQ(tests[i].challenge_realm, handler->realm().c_str());
} else {
@@ -95,7 +107,7 @@
}
}
-TEST(HttpAuthTest, ChooseBestChallengeConnectionBased) {
+TEST(HttpAuthTest, ChooseBestChallengeConnectionBasedNTLM) {
static const struct {
const char* headers;
const char* challenge_realm;
@@ -120,8 +132,12 @@
}
};
GURL origin("http://www.example.com");
+ std::set<std::string> disabled_schemes;
- scoped_refptr<HttpAuthHandler> handler;
+ scoped_ptr<HttpAuthHandlerFactory> http_auth_handler_factory(
+ HttpAuthHandlerFactory::CreateDefault());
+ scoped_ptr<HttpAuthHandler> handler;
+
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
// Make a HttpResponseHeaders object.
std::string headers_with_status_line("HTTP/1.1 401 Unauthorized\n");
@@ -132,17 +148,86 @@
headers_with_status_line.c_str(),
headers_with_status_line.length())));
- scoped_refptr<HttpAuthHandler> old_handler = handler;
- HttpAuth::ChooseBestChallenge(headers.get(),
+ // possibly_deleted_old_handler may point to deleted memory
+ // after ChooseBestChallenge has been called, and should not
+ // be dereferenced.
+ HttpAuthHandler* possibly_deleted_old_handler = handler.get();
+ HttpAuth::ChooseBestChallenge(http_auth_handler_factory.get(),
+ headers.get(),
HttpAuth::AUTH_SERVER,
origin,
+ disabled_schemes,
+ BoundNetLog(),
&handler);
-
EXPECT_TRUE(handler != NULL);
// Since NTLM is connection-based, we should continue to use the existing
// handler rather than creating a new one.
if (i != 0)
- EXPECT_EQ(old_handler, handler);
+ EXPECT_EQ(possibly_deleted_old_handler, handler.get());
+ ASSERT_NE(reinterpret_cast<net::HttpAuthHandler *>(NULL), handler.get());
+ EXPECT_STREQ(tests[i].challenge_realm, handler->realm().c_str());
+ }
+}
+
+TEST(HttpAuthTest, ChooseBestChallengeConnectionBasedNegotiate) {
+ static const struct {
+ const char* headers;
+ const char* challenge_realm;
+ } tests[] = {
+ {
+ "WWW-Authenticate: Negotiate\r\n",
+
+ "",
+ },
+ {
+ "WWW-Authenticate: Negotiate "
+ "TlRMTVNTUAACAAAADAAMADgAAAAFgokCTroKF1e/DRcAAAAAAAAAALo"
+ "AugBEAAAABQEoCgAAAA9HAE8ATwBHAEwARQACAAwARwBPAE8ARwBMAE"
+ "UAAQAaAEEASwBFAEUAUwBBAFIAQQAtAEMATwBSAFAABAAeAGMAbwByA"
+ "HAALgBnAG8AbwBnAGwAZQAuAGMAbwBtAAMAQABhAGsAZQBlAHMAYQBy"
+ "AGEALQBjAG8AcgBwAC4AYQBkAC4AYwBvAHIAcAAuAGcAbwBvAGcAbAB"
+ "lAC4AYwBvAG0ABQAeAGMAbwByAHAALgBnAG8AbwBnAGwAZQAuAGMAbw"
+ "BtAAAAAAA=\r\n",
+
+ // Realm is empty.
+ "",
+ }
+ };
+ GURL origin("http://www.example.com");
+ std::set<std::string> disabled_schemes;
+ URLSecurityManagerAllow url_security_manager;
+ scoped_ptr<HttpAuthHandlerRegistryFactory> http_auth_handler_factory(
+ HttpAuthHandlerFactory::CreateDefault());
+ http_auth_handler_factory->SetURLSecurityManager(
+ "negotiate", &url_security_manager);
+
+ scoped_ptr<HttpAuthHandler> handler;
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ // Make a HttpResponseHeaders object.
+ std::string headers_with_status_line("HTTP/1.1 401 Unauthorized\n");
+ headers_with_status_line += tests[i].headers;
+ scoped_refptr<net::HttpResponseHeaders> headers(
+ new net::HttpResponseHeaders(
+ net::HttpUtil::AssembleRawHeaders(
+ headers_with_status_line.c_str(),
+ headers_with_status_line.length())));
+
+ HttpAuthHandler* old_handler = handler.get();
+ HttpAuth::ChooseBestChallenge(http_auth_handler_factory.get(),
+ headers.get(),
+ HttpAuth::AUTH_SERVER,
+ origin,
+ disabled_schemes,
+ BoundNetLog(),
+ &handler);
+
+ EXPECT_TRUE(handler != NULL);
+ // Since Negotiate is connection-based, we should continue to use the
+ // existing handler rather than creating a new one.
+ if (i != 0)
+ EXPECT_EQ(old_handler, handler.get());
+
+ ASSERT_NE(reinterpret_cast<net::HttpAuthHandler *>(NULL), handler.get());
EXPECT_STREQ(tests[i].challenge_realm, handler->realm().c_str());
}
@@ -179,6 +264,82 @@
EXPECT_FALSE(challenge.GetNext());
}
+// Use a name=value property with mismatching quote marks.
+TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotes) {
+ std::string challenge_str = "Basic realm=\"foobar@baz.com";
+ HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
+ challenge_str.end());
+ EXPECT_TRUE(challenge.valid());
+ EXPECT_EQ(std::string("Basic"), challenge.scheme());
+ EXPECT_TRUE(challenge.GetNext());
+ EXPECT_TRUE(challenge.valid());
+ EXPECT_EQ(std::string("realm"), challenge.name());
+ EXPECT_EQ(std::string("foobar@baz.com"), challenge.value());
+ EXPECT_EQ(std::string("foobar@baz.com"), challenge.unquoted_value());
+ EXPECT_FALSE(challenge.value_is_quoted());
+ EXPECT_FALSE(challenge.GetNext());
+}
+
+// Use a name= property without a value and with mismatching quote marks.
+TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotesNoValue) {
+ std::string challenge_str = "Basic realm=\"";
+ HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
+ challenge_str.end());
+ EXPECT_TRUE(challenge.valid());
+ EXPECT_EQ(std::string("Basic"), challenge.scheme());
+ EXPECT_TRUE(challenge.GetNext());
+ EXPECT_TRUE(challenge.valid());
+ EXPECT_EQ(std::string("realm"), challenge.name());
+ EXPECT_EQ(std::string(""), challenge.value());
+ EXPECT_FALSE(challenge.value_is_quoted());
+ EXPECT_FALSE(challenge.GetNext());
+}
+
+// Use a name=value property with mismatching quote marks and spaces in the
+// value.
+TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotesSpaces) {
+ std::string challenge_str = "Basic realm=\"foo bar";
+ HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
+ challenge_str.end());
+ EXPECT_TRUE(challenge.valid());
+ EXPECT_EQ(std::string("Basic"), challenge.scheme());
+ EXPECT_TRUE(challenge.GetNext());
+ EXPECT_TRUE(challenge.valid());
+ EXPECT_EQ(std::string("realm"), challenge.name());
+ EXPECT_EQ(std::string("foo bar"), challenge.value());
+ EXPECT_EQ(std::string("foo bar"), challenge.unquoted_value());
+ EXPECT_FALSE(challenge.value_is_quoted());
+ EXPECT_FALSE(challenge.GetNext());
+}
+
+// Use multiple name=value properties with mismatching quote marks in the last
+// value.
+TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotesMultiple) {
+ std::string challenge_str = "Digest qop=, algorithm=md5, realm=\"foo";
+ HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
+ challenge_str.end());
+ EXPECT_TRUE(challenge.valid());
+ EXPECT_EQ(std::string("Digest"), challenge.scheme());
+ EXPECT_TRUE(challenge.GetNext());
+ EXPECT_TRUE(challenge.valid());
+ EXPECT_EQ(std::string("qop"), challenge.name());
+ EXPECT_EQ(std::string(""), challenge.value());
+ EXPECT_FALSE(challenge.value_is_quoted());
+ EXPECT_TRUE(challenge.GetNext());
+ EXPECT_TRUE(challenge.valid());
+ EXPECT_EQ(std::string("algorithm"), challenge.name());
+ EXPECT_EQ(std::string("md5"), challenge.value());
+ EXPECT_EQ(std::string("md5"), challenge.unquoted_value());
+ EXPECT_FALSE(challenge.value_is_quoted());
+ EXPECT_TRUE(challenge.GetNext());
+ EXPECT_TRUE(challenge.valid());
+ EXPECT_EQ(std::string("realm"), challenge.name());
+ EXPECT_EQ(std::string("foo"), challenge.value());
+ EXPECT_EQ(std::string("foo"), challenge.unquoted_value());
+ EXPECT_FALSE(challenge.value_is_quoted());
+ EXPECT_FALSE(challenge.GetNext());
+}
+
// Use a name= property which has no value.
TEST(HttpAuthTest, ChallengeTokenizerNoValue) {
std::string challenge_str = "Digest qop=";
@@ -230,6 +391,20 @@
EXPECT_FALSE(challenge.GetNext());
}
+// Use a challenge with Base64 encoded token.
+TEST(HttpAuthTest, ChallengeTokenizerBase64) {
+ std::string challenge_str = "NTLM SGVsbG8sIFdvcmxkCg===";
+ HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
+ challenge_str.end());
+ EXPECT_TRUE(challenge.valid());
+ EXPECT_EQ(std::string("NTLM"), challenge.scheme());
+ challenge.set_expect_base64_token(true);
+ EXPECT_TRUE(challenge.GetNext());
+ // Notice the two equal statements below due to padding removal.
+ EXPECT_EQ(std::string("SGVsbG8sIFdvcmxkCg=="), challenge.value());
+ EXPECT_FALSE(challenge.GetNext());
+}
+
TEST(HttpAuthTest, GetChallengeHeaderName) {
std::string name;
@@ -250,56 +425,4 @@
EXPECT_STREQ("Proxy-Authorization", name.c_str());
}
-TEST(HttpAuthTest, CreateAuthHandler) {
- GURL server_origin("http://www.example.com");
- GURL proxy_origin("http://cache.example.com:3128");
- {
- scoped_refptr<HttpAuthHandler> handler;
- HttpAuth::CreateAuthHandler("Basic realm=\"FooBar\"",
- HttpAuth::AUTH_SERVER,
- server_origin,
- &handler);
- EXPECT_FALSE(handler.get() == NULL);
- EXPECT_STREQ("basic", handler->scheme().c_str());
- EXPECT_STREQ("FooBar", handler->realm().c_str());
- EXPECT_EQ(HttpAuth::AUTH_SERVER, handler->target());
- EXPECT_FALSE(handler->encrypts_identity());
- EXPECT_FALSE(handler->is_connection_based());
- }
- {
- scoped_refptr<HttpAuthHandler> handler;
- HttpAuth::CreateAuthHandler("UNSUPPORTED realm=\"FooBar\"",
- HttpAuth::AUTH_SERVER,
- server_origin,
- &handler);
- EXPECT_TRUE(handler.get() == NULL);
- }
- {
- scoped_refptr<HttpAuthHandler> handler;
- HttpAuth::CreateAuthHandler("Digest realm=\"FooBar\", nonce=\"xyz\"",
- HttpAuth::AUTH_PROXY,
- proxy_origin,
- &handler);
- EXPECT_FALSE(handler.get() == NULL);
- EXPECT_STREQ("digest", handler->scheme().c_str());
- EXPECT_STREQ("FooBar", handler->realm().c_str());
- EXPECT_EQ(HttpAuth::AUTH_PROXY, handler->target());
- EXPECT_TRUE(handler->encrypts_identity());
- EXPECT_FALSE(handler->is_connection_based());
- }
- {
- scoped_refptr<HttpAuthHandler> handler;
- HttpAuth::CreateAuthHandler("NTLM",
- HttpAuth::AUTH_SERVER,
- server_origin,
- &handler);
- EXPECT_FALSE(handler.get() == NULL);
- EXPECT_STREQ("ntlm", handler->scheme().c_str());
- EXPECT_STREQ("", handler->realm().c_str());
- EXPECT_EQ(HttpAuth::AUTH_SERVER, handler->target());
- EXPECT_TRUE(handler->encrypts_identity());
- EXPECT_TRUE(handler->is_connection_based());
- }
-}
-
} // namespace net
diff --git a/net/http/http_basic_stream.cc b/net/http/http_basic_stream.cc
index f587ab4..8303357 100644
--- a/net/http/http_basic_stream.cc
+++ b/net/http/http_basic_stream.cc
@@ -6,9 +6,10 @@
namespace net {
-HttpBasicStream::HttpBasicStream(ClientSocketHandle* handle, LoadLog* load_log)
+HttpBasicStream::HttpBasicStream(ClientSocketHandle* handle,
+ const BoundNetLog& net_log)
: read_buf_(new GrowableIOBuffer()),
- parser_(new HttpStreamParser(handle, read_buf_, load_log)) {
+ parser_(new HttpStreamParser(handle, read_buf_, net_log)) {
}
int HttpBasicStream::SendRequest(const HttpRequestInfo* request,
@@ -20,6 +21,8 @@
request, headers, request_body, response, callback);
}
+HttpBasicStream::~HttpBasicStream() {}
+
uint64 HttpBasicStream::GetUploadProgress() const {
return parser_->GetUploadProgress();
}
diff --git a/net/http/http_basic_stream.h b/net/http/http_basic_stream.h
index 0a12baf..ff3bcf2 100644
--- a/net/http/http_basic_stream.h
+++ b/net/http/http_basic_stream.h
@@ -18,16 +18,16 @@
namespace net {
+class BoundNetLog;
class ClientSocketHandle;
-class HttpRequestInfo;
+struct HttpRequestInfo;
class HttpResponseInfo;
-class LoadLog;
class UploadDataStream;
class HttpBasicStream : public HttpStream {
public:
- HttpBasicStream(ClientSocketHandle* handle, LoadLog* load_log);
- virtual ~HttpBasicStream() {}
+ HttpBasicStream(ClientSocketHandle* handle, const BoundNetLog& net_log);
+ virtual ~HttpBasicStream();
// HttpStream methods:
virtual int SendRequest(const HttpRequestInfo* request,
diff --git a/net/http/http_byte_range.cc b/net/http/http_byte_range.cc
index 8978d9a..60683c5 100644
--- a/net/http/http_byte_range.cc
+++ b/net/http/http_byte_range.cc
@@ -36,9 +36,9 @@
bool HttpByteRange::IsValid() const {
if (suffix_length_ > 0)
return true;
- return first_byte_position_ >= 0 &&
- (last_byte_position_ == kPositionNotSpecified ||
- last_byte_position_ >= first_byte_position_);
+ return (first_byte_position_ >= 0 &&
+ (last_byte_position_ == kPositionNotSpecified ||
+ last_byte_position_ >= first_byte_position_));
}
bool HttpByteRange::ComputeBounds(int64 size) {
@@ -52,9 +52,9 @@
if (!HasFirstBytePosition() &&
!HasLastBytePosition() &&
!IsSuffixByteRange()) {
- first_byte_position_ = 0;
- last_byte_position_ = size - 1;
- return true;
+ first_byte_position_ = 0;
+ last_byte_position_ = size - 1;
+ return true;
}
if (!IsValid())
return false;
diff --git a/net/http/http_cache.cc b/net/http/http_cache.cc
index b7989d1..dbd6bf1 100644
--- a/net/http/http_cache.cc
+++ b/net/http/http_cache.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -12,29 +12,34 @@
#include <unistd.h>
#endif
+#include "base/callback.h"
#include "base/format_macros.h"
#include "base/message_loop.h"
#include "base/pickle.h"
#include "base/ref_counted.h"
+#include "base/stl_util-inl.h"
#include "base/string_util.h"
#include "net/base/io_buffer.h"
+#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/disk_cache/disk_cache.h"
-#include "net/flip/flip_session_pool.h"
#include "net/http/http_cache_transaction.h"
#include "net/http/http_network_layer.h"
#include "net/http/http_network_session.h"
#include "net/http/http_request_info.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_response_info.h"
+#include "net/http/http_util.h"
+#include "net/spdy/spdy_session_pool.h"
namespace net {
-// disk cache entry data indices.
-enum {
- kResponseInfoIndex,
- kResponseContentIndex
-};
+int HttpCache::DefaultBackend::CreateBackend(disk_cache::Backend** backend,
+ CompletionCallback* callback) {
+ DCHECK_GE(max_bytes_, 0);
+ return disk_cache::CreateCacheBackend(type_, path_, max_bytes_, true,
+ thread_, backend, callback);
+}
//-----------------------------------------------------------------------------
@@ -46,20 +51,23 @@
}
HttpCache::ActiveEntry::~ActiveEntry() {
- if (disk_entry)
+ if (disk_entry) {
disk_entry->Close();
+ disk_entry = NULL;
+ }
}
//-----------------------------------------------------------------------------
// This structure keeps track of work items that are attempting to create or
-// open cache entries.
-struct HttpCache::NewEntry {
- NewEntry() : disk_entry(NULL), writer(NULL) {}
- ~NewEntry() {}
+// open cache entries or the backend itself.
+struct HttpCache::PendingOp {
+ PendingOp() : disk_entry(NULL), writer(NULL), callback(NULL) {}
+ ~PendingOp() {}
disk_cache::Entry* disk_entry;
WorkItem* writer;
+ CompletionCallback* callback; // BackendCallback.
WorkItemList pending_queue;
};
@@ -67,38 +75,56 @@
// The type of operation represented by a work item.
enum WorkItemOperation {
+ WI_CREATE_BACKEND,
WI_OPEN_ENTRY,
WI_CREATE_ENTRY,
WI_DOOM_ENTRY
};
-// A work item encapsulates a single request for cache entry with all the
+// A work item encapsulates a single request to the backend with all the
// information needed to complete that request.
class HttpCache::WorkItem {
public:
- WorkItem(ActiveEntry** entry, CompletionCallback* callback,
- WorkItemOperation operation)
- : entry_(entry), callback_(callback), operation_(operation) {}
+ WorkItem(WorkItemOperation operation, Transaction* trans, ActiveEntry** entry)
+ : operation_(operation), trans_(trans), entry_(entry), callback_(NULL),
+ backend_(NULL) {}
+ WorkItem(WorkItemOperation operation, Transaction* trans,
+ CompletionCallback* cb, disk_cache::Backend** backend)
+ : operation_(operation), trans_(trans), entry_(NULL), callback_(cb),
+ backend_(backend) {}
~WorkItem() {}
- WorkItemOperation operation() { return operation_; }
// Calls back the transaction with the result of the operation.
void NotifyTransaction(int result, ActiveEntry* entry) {
+ // TODO(rvargas): convert to DCHECK after fixing bug 47895.
+ CHECK(!entry || entry->disk_entry);
if (entry_)
*entry_ = entry;
+ if (trans_)
+ trans_->io_callback()->Run(result);
+ }
+
+ // Notifies the caller about the operation completion.
+ void DoCallback(int result, disk_cache::Backend* backend) {
+ if (backend_)
+ *backend_ = backend;
if (callback_)
callback_->Run(result);
}
- void ClearCallback() { callback_ = NULL; }
+ WorkItemOperation operation() { return operation_; }
+ void ClearTransaction() { trans_ = NULL; }
void ClearEntry() { entry_ = NULL; }
- bool Matches(CompletionCallback* cb) const { return cb == callback_; }
- bool IsValid() const { return callback_ || entry_; }
+ void ClearCallback() { callback_ = NULL; }
+ bool Matches(Transaction* trans) const { return trans == trans_; }
+ bool IsValid() const { return trans_ || entry_ || callback_; }
private:
- ActiveEntry** entry_;
- CompletionCallback* callback_;
WorkItemOperation operation_;
+ Transaction* trans_;
+ ActiveEntry** entry_;
+ CompletionCallback* callback_; // User callback.
+ disk_cache::Backend** backend_;
};
//-----------------------------------------------------------------------------
@@ -107,76 +133,141 @@
// pass multiple arguments to the completion routine.
class HttpCache::BackendCallback : public CallbackRunner<Tuple1<int> > {
public:
- BackendCallback(HttpCache* cache, NewEntry* entry)
- : cache_(cache), entry_(entry) {}
+ BackendCallback(HttpCache* cache, PendingOp* pending_op)
+ : cache_(cache), pending_op_(pending_op) {}
~BackendCallback() {}
virtual void RunWithParams(const Tuple1<int>& params) {
- cache_->OnIOComplete(params.a, entry_);
+ if (cache_)
+ cache_->OnIOComplete(params.a, pending_op_);
delete this;
}
+ void Cancel() {
+ cache_ = NULL;
+ }
+
private:
HttpCache* cache_;
- NewEntry* entry_;
+ PendingOp* pending_op_;
DISALLOW_COPY_AND_ASSIGN(BackendCallback);
};
//-----------------------------------------------------------------------------
-HttpCache::HttpCache(NetworkChangeNotifier* network_change_notifier,
- HostResolver* host_resolver,
- ProxyService* proxy_service,
+// This class encapsulates a transaction whose only purpose is to write metadata
+// to a given entry.
+class HttpCache::MetadataWriter {
+ public:
+ explicit MetadataWriter(HttpCache::Transaction* trans)
+ : transaction_(trans),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ callback_(this, &MetadataWriter::OnIOComplete)) {}
+ ~MetadataWriter() {}
+
+ // Implementes the bulk of HttpCache::WriteMetadata.
+ void Write(const GURL& url, base::Time expected_response_time, IOBuffer* buf,
+ int buf_len);
+
+ private:
+ void VerifyResponse(int result);
+ void SelfDestroy();
+ void OnIOComplete(int result);
+
+ scoped_ptr<HttpCache::Transaction> transaction_;
+ bool verified_;
+ scoped_refptr<IOBuffer> buf_;
+ int buf_len_;
+ base::Time expected_response_time_;
+ CompletionCallbackImpl<MetadataWriter> callback_;
+ HttpRequestInfo request_info_;
+ DISALLOW_COPY_AND_ASSIGN(MetadataWriter);
+};
+
+void HttpCache::MetadataWriter::Write(const GURL& url,
+ base::Time expected_response_time,
+ IOBuffer* buf, int buf_len) {
+ DCHECK_GT(buf_len, 0);
+ DCHECK(buf);
+ DCHECK(buf->data());
+ request_info_.url = url;
+ request_info_.method = "GET";
+ request_info_.load_flags = LOAD_ONLY_FROM_CACHE;
+
+ expected_response_time_ = expected_response_time;
+ buf_ = buf;
+ buf_len_ = buf_len;
+ verified_ = false;
+
+ int rv = transaction_->Start(&request_info_, &callback_, BoundNetLog());
+ if (rv != ERR_IO_PENDING)
+ VerifyResponse(rv);
+}
+
+void HttpCache::MetadataWriter::VerifyResponse(int result) {
+ verified_ = true;
+ if (result != OK)
+ return SelfDestroy();
+
+ const HttpResponseInfo* response_info = transaction_->GetResponseInfo();
+ DCHECK(response_info->was_cached);
+ if (response_info->response_time != expected_response_time_)
+ return SelfDestroy();
+
+ result = transaction_->WriteMetadata(buf_, buf_len_, &callback_);
+ if (result != ERR_IO_PENDING)
+ SelfDestroy();
+}
+
+void HttpCache::MetadataWriter::SelfDestroy() {
+ delete this;
+}
+
+void HttpCache::MetadataWriter::OnIOComplete(int result) {
+ if (!verified_)
+ return VerifyResponse(result);
+ SelfDestroy();
+}
+
+//-----------------------------------------------------------------------------
+
+HttpCache::HttpCache(HostResolver* host_resolver, ProxyService* proxy_service,
SSLConfigService* ssl_config_service,
- const FilePath& cache_dir,
- int cache_size)
- : disk_cache_dir_(cache_dir),
+ HttpAuthHandlerFactory* http_auth_handler_factory,
+ HttpNetworkDelegate* network_delegate,
+ NetLog* net_log,
+ BackendFactory* backend_factory)
+ : backend_factory_(backend_factory),
+ temp_backend_(NULL),
+ building_backend_(false),
mode_(NORMAL),
- type_(DISK_CACHE),
- network_layer_(HttpNetworkLayer::CreateFactory(
- network_change_notifier, host_resolver, proxy_service,
- ssl_config_service)),
+ network_layer_(HttpNetworkLayer::CreateFactory(host_resolver,
+ proxy_service, ssl_config_service, http_auth_handler_factory,
+ network_delegate, net_log)),
ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)),
- enable_range_support_(true),
- cache_size_(cache_size) {
+ enable_range_support_(true) {
}
HttpCache::HttpCache(HttpNetworkSession* session,
- const FilePath& cache_dir,
- int cache_size)
- : disk_cache_dir_(cache_dir),
+ BackendFactory* backend_factory)
+ : backend_factory_(backend_factory),
+ temp_backend_(NULL),
+ building_backend_(false),
mode_(NORMAL),
- type_(DISK_CACHE),
network_layer_(HttpNetworkLayer::CreateFactory(session)),
ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)),
- enable_range_support_(true),
- cache_size_(cache_size) {
-}
-
-HttpCache::HttpCache(NetworkChangeNotifier* network_change_notifier,
- HostResolver* host_resolver,
- ProxyService* proxy_service,
- SSLConfigService* ssl_config_service,
- int cache_size)
- : mode_(NORMAL),
- type_(MEMORY_CACHE),
- network_layer_(HttpNetworkLayer::CreateFactory(
- network_change_notifier, host_resolver, proxy_service,
- ssl_config_service)),
- ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)),
- enable_range_support_(true),
- cache_size_(cache_size) {
+ enable_range_support_(true) {
}
HttpCache::HttpCache(HttpTransactionFactory* network_layer,
- disk_cache::Backend* disk_cache)
- : mode_(NORMAL),
- type_(DISK_CACHE),
+ BackendFactory* backend_factory)
+ : backend_factory_(backend_factory),
+ temp_backend_(NULL),
+ building_backend_(false),
+ mode_(NORMAL),
network_layer_(network_layer),
- disk_cache_(disk_cache),
ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)),
- enable_range_support_(true),
- cache_size_(0) {
+ enable_range_support_(true) {
}
HttpCache::~HttpCache() {
@@ -193,32 +284,50 @@
DeactivateEntry(entry);
}
- ActiveEntriesSet::iterator it = doomed_entries_.begin();
- for (; it != doomed_entries_.end(); ++it)
- delete *it;
+ STLDeleteElements(&doomed_entries_);
+
+ PendingOpsMap::iterator pending_it = pending_ops_.begin();
+ for (; pending_it != pending_ops_.end(); ++pending_it) {
+ // We are not notifying the transactions about the cache going away, even
+ // though they are waiting for a callback that will never fire.
+ PendingOp* pending_op = pending_it->second;
+ delete pending_op->writer;
+ if (building_backend_) {
+ // If we don't have a backend, when its construction finishes it will
+ // deliver the callbacks.
+ BackendCallback* callback =
+ static_cast<BackendCallback*>(pending_op->callback);
+ callback->Cancel();
+ } else {
+ delete pending_op->callback;
+ }
+
+ STLDeleteElements(&pending_op->pending_queue);
+ delete pending_op;
+ }
}
-disk_cache::Backend* HttpCache::GetBackend() {
- if (disk_cache_.get())
- return disk_cache_.get();
+int HttpCache::GetBackend(disk_cache::Backend** backend,
+ CompletionCallback* callback) {
+ DCHECK(callback != NULL);
- DCHECK_GE(cache_size_, 0);
- if (type_ == MEMORY_CACHE) {
- // We may end up with no folder name and no cache if the initialization
- // of the disk cache fails. We want to be sure that what we wanted to have
- // was an in-memory cache.
- disk_cache_.reset(disk_cache::CreateInMemoryCacheBackend(cache_size_));
- } else if (!disk_cache_dir_.empty()) {
- disk_cache_.reset(disk_cache::CreateCacheBackend(disk_cache_dir_, true,
- cache_size_, type_));
- disk_cache_dir_ = FilePath(); // Reclaim memory.
+ if (disk_cache_.get()) {
+ *backend = disk_cache_.get();
+ return OK;
}
+
+ return CreateBackend(backend, callback);
+}
+
+disk_cache::Backend* HttpCache::GetCurrentBackend() {
return disk_cache_.get();
}
int HttpCache::CreateTransaction(scoped_ptr<HttpTransaction>* trans) {
// Do lazy initialization of disk cache if needed.
- GetBackend();
+ if (!disk_cache_.get())
+ CreateBackend(NULL, NULL); // We don't care about the result.
+
trans->reset(new HttpCache::Transaction(this, enable_range_support_));
return OK;
}
@@ -245,38 +354,83 @@
return response_info->InitFromPickle(pickle, response_truncated);
}
-// static
-bool HttpCache::ReadResponseInfo(disk_cache::Entry* disk_entry,
- HttpResponseInfo* response_info,
- bool* response_truncated) {
- int size = disk_entry->GetDataSize(kResponseInfoIndex);
+void HttpCache::WriteMetadata(const GURL& url,
+ base::Time expected_response_time, IOBuffer* buf,
+ int buf_len) {
+ if (!buf_len)
+ return;
- scoped_refptr<IOBuffer> buffer = new IOBuffer(size);
- int rv = disk_entry->ReadData(kResponseInfoIndex, 0, buffer, size, NULL);
- if (rv != size) {
- DLOG(ERROR) << "ReadData failed: " << rv;
- return false;
- }
+ // Do lazy initialization of disk cache if needed.
+ if (!disk_cache_.get())
+ CreateBackend(NULL, NULL); // We don't care about the result.
- return ParseResponseInfo(buffer->data(), size, response_info,
- response_truncated);
+ HttpCache::Transaction* trans =
+ new HttpCache::Transaction(this, enable_range_support_);
+ MetadataWriter* writer = new MetadataWriter(trans);
+
+ // The writer will self destruct when done.
+ writer->Write(url, expected_response_time, buf, buf_len);
}
-// static
-bool HttpCache::WriteResponseInfo(disk_cache::Entry* disk_entry,
- const HttpResponseInfo* response_info,
- bool skip_transient_headers,
- bool response_truncated) {
- Pickle pickle;
- response_info->Persist(
- &pickle, skip_transient_headers, response_truncated);
+void HttpCache::CloseCurrentConnections() {
+ net::HttpNetworkLayer* network =
+ static_cast<net::HttpNetworkLayer*>(network_layer_.get());
+ HttpNetworkSession* session = network->GetSession();
+ if (session) {
+ session->tcp_socket_pool()->Flush();
+ if (session->spdy_session_pool())
+ session->spdy_session_pool()->CloseAllSessions();
+ }
+}
- scoped_refptr<WrappedIOBuffer> data = new WrappedIOBuffer(
- reinterpret_cast<const char*>(pickle.data()));
- int len = static_cast<int>(pickle.size());
+//-----------------------------------------------------------------------------
- return disk_entry->WriteData(kResponseInfoIndex, 0, data, len, NULL,
- true) == len;
+int HttpCache::CreateBackend(disk_cache::Backend** backend,
+ CompletionCallback* callback) {
+ if (!backend_factory_.get())
+ return ERR_FAILED;
+
+ building_backend_ = true;
+
+ scoped_ptr<WorkItem> item(new WorkItem(WI_CREATE_BACKEND, NULL, callback,
+ backend));
+
+ // This is the only operation that we can do that is not related to any given
+ // entry, so we use an empty key for it.
+ PendingOp* pending_op = GetPendingOp("");
+ if (pending_op->writer) {
+ if (callback)
+ pending_op->pending_queue.push_back(item.release());
+ return ERR_IO_PENDING;
+ }
+
+ DCHECK(pending_op->pending_queue.empty());
+
+ pending_op->writer = item.release();
+ BackendCallback* my_callback = new BackendCallback(this, pending_op);
+ pending_op->callback = my_callback;
+
+ int rv = backend_factory_->CreateBackend(&temp_backend_, my_callback);
+ if (rv != ERR_IO_PENDING) {
+ pending_op->writer->ClearCallback();
+ my_callback->Run(rv);
+ }
+
+ return rv;
+}
+
+int HttpCache::GetBackendForTransaction(Transaction* trans) {
+ if (disk_cache_.get())
+ return OK;
+
+ if (!building_backend_)
+ return ERR_FAILED;
+
+ WorkItem* item = new WorkItem(WI_CREATE_BACKEND, trans, NULL, NULL);
+ PendingOp* pending_op = GetPendingOp("");
+ DCHECK(pending_op->writer);
+ pending_op->pending_queue.push_back(item);
+ return ERR_IO_PENDING;
}
// Generate a key that can be used inside the cache.
@@ -318,14 +472,14 @@
return result;
}
-int HttpCache::DoomEntry(const std::string& key, CompletionCallback* callback) {
+int HttpCache::DoomEntry(const std::string& key, Transaction* trans) {
// Need to abandon the ActiveEntry, but any transaction attached to the entry
// should not be impacted. Dooming an entry only means that it will no
// longer be returned by FindActiveEntry (and it will also be destroyed once
// all consumers are finished with the entry).
ActiveEntriesMap::iterator it = active_entries_.find(key);
if (it == active_entries_.end()) {
- return AsyncDoomEntry(key, callback);
+ return AsyncDoomEntry(key, trans);
}
ActiveEntry* entry = it->second;
@@ -342,24 +496,24 @@
return OK;
}
-int HttpCache::AsyncDoomEntry(const std::string& key,
- CompletionCallback* callback) {
- DCHECK(callback);
- WorkItem* item = new WorkItem(NULL, callback, WI_DOOM_ENTRY);
- NewEntry* new_entry = GetNewEntry(key);
- if (new_entry->writer) {
- new_entry->pending_queue.push_back(item);
+int HttpCache::AsyncDoomEntry(const std::string& key, Transaction* trans) {
+ DCHECK(trans);
+ WorkItem* item = new WorkItem(WI_DOOM_ENTRY, trans, NULL);
+ PendingOp* pending_op = GetPendingOp(key);
+ if (pending_op->writer) {
+ pending_op->pending_queue.push_back(item);
return ERR_IO_PENDING;
}
- DCHECK(new_entry->pending_queue.empty());
+ DCHECK(pending_op->pending_queue.empty());
- new_entry->writer = item;
- BackendCallback* my_callback = new BackendCallback(this, new_entry);
+ pending_op->writer = item;
+ BackendCallback* my_callback = new BackendCallback(this, pending_op);
+ pending_op->callback = my_callback;
int rv = disk_cache_->DoomEntry(key, my_callback);
if (rv != ERR_IO_PENDING) {
- item->ClearCallback();
+ item->ClearTransaction();
my_callback->Run(rv);
}
@@ -384,102 +538,6 @@
return it != active_entries_.end() ? it->second : NULL;
}
-HttpCache::NewEntry* HttpCache::GetNewEntry(const std::string& key) {
- DCHECK(!FindActiveEntry(key));
-
- NewEntriesMap::const_iterator it = new_entries_.find(key);
- if (it != new_entries_.end())
- return it->second;
-
- NewEntry* entry = new NewEntry();
- new_entries_[key] = entry;
- return entry;
-}
-
-void HttpCache::DeleteNewEntry(NewEntry* entry) {
- std::string key;
- if (entry->disk_entry)
- key = entry->disk_entry->GetKey();
-
- if (!key.empty()) {
- NewEntriesMap::iterator it = new_entries_.find(key);
- DCHECK(it != new_entries_.end());
- new_entries_.erase(it);
- } else {
- for (NewEntriesMap::iterator it = new_entries_.begin();
- it != new_entries_.end(); ++it) {
- if (it->second == entry) {
- new_entries_.erase(it);
- break;
- }
- }
- }
-
- delete entry;
-}
-
-int HttpCache::OpenEntry(const std::string& key, ActiveEntry** entry,
- CompletionCallback* callback) {
- ActiveEntry* active_entry = FindActiveEntry(key);
- if (active_entry) {
- *entry = active_entry;
- return OK;
- }
-
- WorkItem* item = new WorkItem(entry, callback, WI_OPEN_ENTRY);
- NewEntry* new_entry = GetNewEntry(key);
- if (new_entry->writer) {
- new_entry->pending_queue.push_back(item);
- return ERR_IO_PENDING;
- }
-
- DCHECK(new_entry->pending_queue.empty());
-
- new_entry->writer = item;
- BackendCallback* my_callback = new BackendCallback(this, new_entry);
-
- int rv = disk_cache_->OpenEntry(key, &(new_entry->disk_entry), my_callback);
- if (rv != ERR_IO_PENDING) {
- item->ClearCallback();
- my_callback->Run(rv);
- }
-
- return rv;
-}
-
-int HttpCache::CreateEntry(const std::string& key, ActiveEntry** entry,
- CompletionCallback* callback) {
- DCHECK(!FindActiveEntry(key));
-
- WorkItem* item = new WorkItem(entry, callback, WI_CREATE_ENTRY);
- NewEntry* new_entry = GetNewEntry(key);
- if (new_entry->writer) {
- new_entry->pending_queue.push_back(item);
- return ERR_IO_PENDING;
- }
-
- DCHECK(new_entry->pending_queue.empty());
-
- new_entry->writer = item;
- BackendCallback* my_callback = new BackendCallback(this, new_entry);
-
- int rv = disk_cache_->CreateEntry(key, &(new_entry->disk_entry), my_callback);
- if (rv != ERR_IO_PENDING) {
- item->ClearCallback();
- my_callback->Run(rv);
- }
-
- return rv;
-}
-
-void HttpCache::DestroyEntry(ActiveEntry* entry) {
- if (entry->doomed) {
- FinalizeDoomedEntry(entry);
- } else {
- DeactivateEntry(entry);
- }
-}
-
HttpCache::ActiveEntry* HttpCache::ActivateEntry(
const std::string& key,
disk_cache::Entry* disk_entry) {
@@ -490,19 +548,21 @@
}
void HttpCache::DeactivateEntry(ActiveEntry* entry) {
- DCHECK(!entry->will_process_pending_queue);
- DCHECK(!entry->doomed);
- DCHECK(!entry->writer);
- DCHECK(entry->readers.empty());
- DCHECK(entry->pending_queue.empty());
+ // TODO(rvargas): convert to DCHECKs after fixing bug 47895.
+ CHECK(!entry->will_process_pending_queue);
+ CHECK(!entry->doomed);
+ CHECK(!entry->writer);
+ CHECK(entry->disk_entry);
+ CHECK(entry->readers.empty());
+ CHECK(entry->pending_queue.empty());
std::string key = entry->disk_entry->GetKey();
if (key.empty())
return SlowDeactivateEntry(entry);
ActiveEntriesMap::iterator it = active_entries_.find(key);
- DCHECK(it != active_entries_.end());
- DCHECK(it->second == entry);
+ CHECK(it != active_entries_.end());
+ CHECK(it->second == entry);
active_entries_.erase(it);
delete entry;
@@ -520,8 +580,110 @@
}
}
+HttpCache::PendingOp* HttpCache::GetPendingOp(const std::string& key) {
+ DCHECK(!FindActiveEntry(key));
+
+ PendingOpsMap::const_iterator it = pending_ops_.find(key);
+ if (it != pending_ops_.end())
+ return it->second;
+
+ PendingOp* operation = new PendingOp();
+ pending_ops_[key] = operation;
+ return operation;
+}
+
+void HttpCache::DeletePendingOp(PendingOp* pending_op) {
+ std::string key;
+ if (pending_op->disk_entry)
+ key = pending_op->disk_entry->GetKey();
+
+ if (!key.empty()) {
+ PendingOpsMap::iterator it = pending_ops_.find(key);
+ DCHECK(it != pending_ops_.end());
+ pending_ops_.erase(it);
+ } else {
+ for (PendingOpsMap::iterator it = pending_ops_.begin();
+ it != pending_ops_.end(); ++it) {
+ if (it->second == pending_op) {
+ pending_ops_.erase(it);
+ break;
+ }
+ }
+ }
+ DCHECK(pending_op->pending_queue.empty());
+
+ delete pending_op;
+}
+
+int HttpCache::OpenEntry(const std::string& key, ActiveEntry** entry,
+ Transaction* trans) {
+ ActiveEntry* active_entry = FindActiveEntry(key);
+ if (active_entry) {
+ *entry = active_entry;
+ return OK;
+ }
+
+ WorkItem* item = new WorkItem(WI_OPEN_ENTRY, trans, entry);
+ PendingOp* pending_op = GetPendingOp(key);
+ if (pending_op->writer) {
+ pending_op->pending_queue.push_back(item);
+ return ERR_IO_PENDING;
+ }
+
+ DCHECK(pending_op->pending_queue.empty());
+
+ pending_op->writer = item;
+ BackendCallback* my_callback = new BackendCallback(this, pending_op);
+ pending_op->callback = my_callback;
+
+ int rv = disk_cache_->OpenEntry(key, &(pending_op->disk_entry), my_callback);
+ if (rv != ERR_IO_PENDING) {
+ item->ClearTransaction();
+ my_callback->Run(rv);
+ }
+
+ return rv;
+}
+
+int HttpCache::CreateEntry(const std::string& key, ActiveEntry** entry,
+ Transaction* trans) {
+ DCHECK(!FindActiveEntry(key));
+
+ WorkItem* item = new WorkItem(WI_CREATE_ENTRY, trans, entry);
+ PendingOp* pending_op = GetPendingOp(key);
+ if (pending_op->writer) {
+ pending_op->pending_queue.push_back(item);
+ return ERR_IO_PENDING;
+ }
+
+ DCHECK(pending_op->pending_queue.empty());
+
+ pending_op->writer = item;
+ BackendCallback* my_callback = new BackendCallback(this, pending_op);
+ pending_op->callback = my_callback;
+
+ int rv = disk_cache_->CreateEntry(key, &(pending_op->disk_entry),
+ my_callback);
+ if (rv != ERR_IO_PENDING) {
+ item->ClearTransaction();
+ my_callback->Run(rv);
+ }
+
+ return rv;
+}
+
+void HttpCache::DestroyEntry(ActiveEntry* entry) {
+ if (entry->doomed) {
+ FinalizeDoomedEntry(entry);
+ } else {
+ DeactivateEntry(entry);
+ }
+}
+
int HttpCache::AddTransactionToEntry(ActiveEntry* entry, Transaction* trans) {
- DCHECK(entry);
+ // TODO(rvargas): convert to DCHECKs after fixing bug 47895.
+ CHECK(entry);
+ CHECK(entry->disk_entry);
// We implement a basic reader/writer lock for the disk cache entry. If
// there is already a writer, then everyone has to wait for the writer to
@@ -555,7 +717,7 @@
if (!entry->writer && !entry->pending_queue.empty())
ProcessPendingQueue(entry);
- return trans->EntryAvailable(entry);
+ return OK;
}
void HttpCache::DoneWithEntry(ActiveEntry* entry, Transaction* trans,
@@ -602,7 +764,8 @@
// We need to do something about these pending entries, which now need to
// be added to a new entry.
while (!pending_queue.empty()) {
- pending_queue.front()->AddToEntry();
+ // ERR_CACHE_RACE causes the transaction to restart the whole process.
+ pending_queue.front()->io_callback()->Run(ERR_CACHE_RACE);
pending_queue.pop_front();
}
}
@@ -633,8 +796,20 @@
ProcessPendingQueue(entry);
}
-void HttpCache::RemovePendingTransaction(Transaction* trans,
- CompletionCallback* cb) {
+LoadState HttpCache::GetLoadStateForPendingTransaction(
+ const Transaction* trans) {
+ ActiveEntriesMap::const_iterator i = active_entries_.find(trans->key());
+ if (i == active_entries_.end()) {
+ // If this is really a pending transaction, and it is not part of
+ // active_entries_, we should be creating the backend or the entry.
+ return LOAD_STATE_WAITING_FOR_CACHE;
+ }
+
+ Transaction* writer = i->second->writer;
+ return writer ? writer->GetWriterLoadState() : LOAD_STATE_WAITING_FOR_CACHE;
+}
+
+void HttpCache::RemovePendingTransaction(Transaction* trans) {
ActiveEntriesMap::const_iterator i = active_entries_.find(trans->key());
bool found = false;
if (i != active_entries_.end())
@@ -643,9 +818,21 @@
if (found)
return;
- NewEntriesMap::const_iterator j = new_entries_.find(trans->key());
- if (j != new_entries_.end())
- found = RemovePendingCallbackFromNewEntry(j->second, cb);
+ if (building_backend_) {
+ PendingOpsMap::const_iterator j = pending_ops_.find("");
+ if (j != pending_ops_.end())
+ found = RemovePendingTransactionFromPendingOp(j->second, trans);
+
+ if (found)
+ return;
+ }
+
+ PendingOpsMap::const_iterator j = pending_ops_.find(trans->key());
+ if (j != pending_ops_.end())
+ found = RemovePendingTransactionFromPendingOp(j->second, trans);
+
+ if (found)
+ return;
ActiveEntriesSet::iterator k = doomed_entries_.begin();
for (; k != doomed_entries_.end() && !found; ++k)
@@ -667,18 +854,18 @@
return true;
}
-bool HttpCache::RemovePendingCallbackFromNewEntry(NewEntry* entry,
- CompletionCallback* cb) {
- if (entry->writer->Matches(cb)) {
- entry->writer->ClearCallback();
- entry->writer->ClearEntry();
+bool HttpCache::RemovePendingTransactionFromPendingOp(PendingOp* pending_op,
+ Transaction* trans) {
+ if (pending_op->writer->Matches(trans)) {
+ pending_op->writer->ClearTransaction();
+ pending_op->writer->ClearEntry();
return true;
}
- WorkItemList& pending_queue = entry->pending_queue;
+ WorkItemList& pending_queue = pending_op->pending_queue;
WorkItemList::iterator it = pending_queue.begin();
for (; it != pending_queue.end(); ++it) {
- if ((*it)->Matches(cb)) {
+ if ((*it)->Matches(trans)) {
delete *it;
pending_queue.erase(it);
return true;
@@ -695,14 +882,16 @@
return;
entry->will_process_pending_queue = true;
- MessageLoop::current()->PostTask(FROM_HERE,
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
task_factory_.NewRunnableMethod(&HttpCache::OnProcessPendingQueue,
entry));
}
void HttpCache::OnProcessPendingQueue(ActiveEntry* entry) {
entry->will_process_pending_queue = false;
- DCHECK(!entry->writer);
+ // TODO(rvargas): convert to DCHECK after fixing bug 47895.
+ CHECK(!entry->writer);
// If no one is interested in this entry, then we can de-activate it.
if (entry->pending_queue.empty()) {
@@ -718,12 +907,20 @@
entry->pending_queue.erase(entry->pending_queue.begin());
- AddTransactionToEntry(entry, next);
+ int rv = AddTransactionToEntry(entry, next);
+ if (rv != ERR_IO_PENDING) {
+ next->io_callback()->Run(rv);
+ }
}
-void HttpCache::OnIOComplete(int result, NewEntry* new_entry) {
- scoped_ptr<WorkItem> item(new_entry->writer);
- WorkItemOperation op = item->operation();
+void HttpCache::OnIOComplete(int result, PendingOp* pending_op) {
+ WorkItemOperation op = pending_op->writer->operation();
+
+ // Completing the creation of the backend is simpler than the other cases.
+ if (op == WI_CREATE_BACKEND)
+ return OnBackendCreated(result, pending_op);
+
+ scoped_ptr<WorkItem> item(pending_op->writer);
bool fail_requests = false;
ActiveEntry* entry = NULL;
@@ -733,31 +930,32 @@
// Anything after a Doom has to be restarted.
fail_requests = true;
} else if (item->IsValid()) {
- key = new_entry->disk_entry->GetKey();
- entry = ActivateEntry(key, new_entry->disk_entry);
+ key = pending_op->disk_entry->GetKey();
+ entry = ActivateEntry(key, pending_op->disk_entry);
} else {
// The writer transaction is gone.
if (op == WI_CREATE_ENTRY)
- new_entry->disk_entry->Doom();
- new_entry->disk_entry->Close();
+ pending_op->disk_entry->Doom();
+ pending_op->disk_entry->Close();
+ pending_op->disk_entry = NULL;
fail_requests = true;
}
}
// We are about to notify a bunch of transactions, and they may decide to
- // re-issue a request (or send a different one). If we don't delete new_entry,
- // the new request will be appended to the end of the list, and we'll see it
- // again from this point before it has a chance to complete (and we'll be
- // messing out the request order). The down side is that if for some reason
- // notifying request A ends up cancelling request B (for the same key), we
- // won't find request B anywhere (because it would be in a local variable
+ // re-issue a request (or send a different one). If we don't delete
+ // pending_op, the new request will be appended to the end of the list, and
+ // we'll see it again from this point before it has a chance to complete (and
+ // we'll be messing out the request order). The down side is that if for some
+ // reason notifying request A ends up cancelling request B (for the same key),
+ // we won't find request B anywhere (because it would be in a local variable
// here) and that's bad. If there is a chance for that to happen, we'll have
// to move the callback used to be a CancelableCallback. By the way, for this
// to happen the action (to cancel B) has to be synchronous to the
// notification for request A.
WorkItemList pending_items;
- pending_items.swap(new_entry->pending_queue);
- DeleteNewEntry(new_entry);
+ pending_items.swap(pending_op->pending_queue);
+ DeletePendingOp(pending_op);
item->NotifyTransaction(result, entry);
@@ -793,7 +991,7 @@
}
}
} else {
- if (op == WI_CREATE_ENTRY && result != OK) {
+ if (op == WI_CREATE_ENTRY && result != OK) {
// Failed Create followed by an Open.
item->NotifyTransaction(ERR_CACHE_RACE, NULL);
fail_requests = true;
@@ -804,18 +1002,31 @@
}
}
-void HttpCache::CloseCurrentConnections() {
- net::HttpNetworkLayer* network =
- static_cast<net::HttpNetworkLayer*>(network_layer_.get());
- HttpNetworkSession* session = network->GetSession();
- if (session) {
- session->tcp_socket_pool()->CloseIdleSockets();
- if (session->flip_session_pool())
- session->flip_session_pool()->CloseAllSessions();
- session->ReplaceTCPSocketPool();
- }
-}
+void HttpCache::OnBackendCreated(int result, PendingOp* pending_op) {
+ scoped_ptr<WorkItem> item(pending_op->writer);
+ WorkItemOperation op = item->operation();
+ DCHECK_EQ(WI_CREATE_BACKEND, op);
-//-----------------------------------------------------------------------------
+ backend_factory_.reset(); // Reclaim memory.
+
+ if (result == OK)
+ disk_cache_.reset(temp_backend_);
+
+ item->DoCallback(result, temp_backend_);
+
+ // Notify all callers and delete all pending work items.
+ while (!pending_op->pending_queue.empty()) {
+ scoped_ptr<WorkItem> pending_item(pending_op->pending_queue.front());
+ pending_op->pending_queue.pop_front();
+ DCHECK_EQ(WI_CREATE_BACKEND, pending_item->operation());
+
+ // This could be an external caller or a transaction waiting on Start().
+ pending_item->DoCallback(result, temp_backend_);
+ pending_item->NotifyTransaction(result, NULL);
+ }
+
+ DeletePendingOp(pending_op);
+ building_backend_ = false;
+}
} // namespace net
diff --git a/net/http/http_cache.h b/net/http/http_cache.h
index 1985f9b..daa6d28 100644
--- a/net/http/http_cache.h
+++ b/net/http/http_cache.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -16,17 +16,23 @@
#include <list>
#include <set>
+#include <string>
#include "base/basictypes.h"
#include "base/file_path.h"
#include "base/hash_tables.h"
+#include "base/message_loop_proxy.h"
+#include "base/non_thread_safe.h"
#include "base/scoped_ptr.h"
#include "base/task.h"
#include "base/weak_ptr.h"
#include "net/base/cache_type.h"
#include "net/base/completion_callback.h"
+#include "net/base/load_states.h"
#include "net/http/http_transaction_factory.h"
+class GURL;
+
namespace disk_cache {
class Backend;
class Entry;
@@ -35,15 +41,20 @@
namespace net {
class HostResolver;
+class HttpAuthHandlerFactory;
+class HttpNetworkDelegate;
class HttpNetworkSession;
-class HttpRequestInfo;
+struct HttpRequestInfo;
class HttpResponseInfo;
-class NetworkChangeNotifier;
+class IOBuffer;
+class NetLog;
class ProxyService;
class SSLConfigService;
+class ViewCacheHelper;
class HttpCache : public HttpTransactionFactory,
- public base::SupportsWeakPtr<HttpCache> {
+ public base::SupportsWeakPtr<HttpCache>,
+ public NonThreadSafe {
public:
~HttpCache();
@@ -61,47 +72,82 @@
DISABLE
};
- // Initialize the cache from the directory where its data is stored. The
- // disk cache is initialized lazily (by CreateTransaction) in this case. If
- // |cache_size| is zero, a default value will be calculated automatically.
- HttpCache(NetworkChangeNotifier* network_change_notifier,
- HostResolver* host_resolver,
+ // A BackendFactory creates a backend object to be used by the HttpCache.
+ class BackendFactory {
+ public:
+ virtual ~BackendFactory() {}
+
+ // The actual method to build the backend. Returns a net error code. If
+ // ERR_IO_PENDING is returned, the |callback| will be notified when the
+ // operation completes, and |backend| must remain valid until the
+ // notification arrives.
+ // The implementation must not access the factory object after invoking the
+ // |callback| because the object can be deleted from within the callback.
+ virtual int CreateBackend(disk_cache::Backend** backend,
+ CompletionCallback* callback) = 0;
+ };
+
+ // A default backend factory for the common use cases.
+ class DefaultBackend : public BackendFactory {
+ public:
+ // |path| is the destination for any files used by the backend, and
+ // |cache_thread| is the thread where disk operations should take place. If
+ // |max_bytes| is zero, a default value will be calculated automatically.
+ DefaultBackend(CacheType type, const FilePath& path, int max_bytes,
+ base::MessageLoopProxy* thread)
+ : type_(type), path_(path), max_bytes_(max_bytes), thread_(thread) {}
+
+ // Returns a factory for an in-memory cache.
+ static BackendFactory* InMemory(int max_bytes) {
+ return new DefaultBackend(MEMORY_CACHE, FilePath(), max_bytes, NULL);
+ }
+
+ // BackendFactory implementation.
+ virtual int CreateBackend(disk_cache::Backend** backend,
+ CompletionCallback* callback);
+
+ private:
+ CacheType type_;
+ const FilePath path_;
+ int max_bytes_;
+ scoped_refptr<base::MessageLoopProxy> thread_;
+ };
+
+ // The disk cache is initialized lazily (by CreateTransaction) in this case.
+ // The HttpCache takes ownership of the |backend_factory|.
+ HttpCache(HostResolver* host_resolver,
ProxyService* proxy_service,
SSLConfigService* ssl_config_service,
- const FilePath& cache_dir,
- int cache_size);
+ HttpAuthHandlerFactory* http_auth_handler_factory,
+ HttpNetworkDelegate* network_delegate,
+ NetLog* net_log,
+ BackendFactory* backend_factory);
- // Initialize the cache from the directory where its data is stored. The
- // disk cache is initialized lazily (by CreateTransaction) in this case. If
- // |cache_size| is zero, a default value will be calculated automatically.
+ // The disk cache is initialized lazily (by CreateTransaction) in this case.
// Provide an existing HttpNetworkSession, the cache can construct a
// network layer with a shared HttpNetworkSession in order for multiple
- // network layers to share information (e.g. authenication data).
- HttpCache(HttpNetworkSession* session, const FilePath& cache_dir,
- int cache_size);
-
- // Initialize using an in-memory cache. The cache is initialized lazily
- // (by CreateTransaction) in this case. If |cache_size| is zero, a default
- // value will be calculated automatically.
- HttpCache(NetworkChangeNotifier* network_change_notifier,
- HostResolver* host_resolver,
- ProxyService* proxy_service,
- SSLConfigService* ssl_config_service,
- int cache_size);
+ // network layers to share information (e.g. authenication data). The
+ // HttpCache takes ownership of the |backend_factory|.
+ HttpCache(HttpNetworkSession* session, BackendFactory* backend_factory);
// Initialize the cache from its component parts, which is useful for
- // testing. The lifetime of the network_layer and disk_cache are managed by
- // the HttpCache and will be destroyed using |delete| when the HttpCache is
+ // testing. The lifetime of the network_layer and backend_factory are managed
+ // by the HttpCache and will be destroyed using |delete| when the HttpCache is
// destroyed.
HttpCache(HttpTransactionFactory* network_layer,
- disk_cache::Backend* disk_cache);
+ BackendFactory* backend_factory);
HttpTransactionFactory* network_layer() { return network_layer_.get(); }
- // Returns the cache backend for this HttpCache instance. If the backend
- // is not initialized yet, this method will initialize it. If the return
- // value is NULL then the backend cannot be initialized.
- disk_cache::Backend* GetBackend();
+ // Retrieves the cache backend for this HttpCache instance. If the backend
+ // is not initialized yet, this method will initialize it. The return value is
+ // a network error code, and it could be ERR_IO_PENDING, in which case the
+ // |callback| will be notified when the operation completes. The pointer that
+ // receives the |backend| must remain valid until the operation completes.
+ int GetBackend(disk_cache::Backend** backend, CompletionCallback* callback);
+
+ // Returns the current backend (can be NULL).
+ disk_cache::Backend* GetCurrentBackend();
// HttpTransactionFactory implementation:
virtual int CreateTransaction(scoped_ptr<HttpTransaction>* trans);
@@ -109,33 +155,22 @@
virtual HttpNetworkSession* GetSession();
virtual void Suspend(bool suspend);
- // Helper function for reading response info from the disk cache. If the
- // cache doesn't have the whole resource *|request_truncated| is set to true.
- // Avoid this function for performance critical paths as it uses blocking IO.
- static bool ReadResponseInfo(disk_cache::Entry* disk_entry,
- HttpResponseInfo* response_info,
- bool* response_truncated);
-
- // Helper function for writing response info into the disk cache. If the
- // cache doesn't have the whole resource |request_truncated| should be true.
- // Avoid this function for performance critical paths as it uses blocking IO.
- static bool WriteResponseInfo(disk_cache::Entry* disk_entry,
- const HttpResponseInfo* response_info,
- bool skip_transient_headers,
- bool response_truncated);
-
// Given a header data blob, convert it to a response info object.
static bool ParseResponseInfo(const char* data, int len,
HttpResponseInfo* response_info,
bool* response_truncated);
+ // Writes |buf_len| bytes of metadata stored in |buf| to the cache entry
+ // referenced by |url|, as long as the entry's |expected_response_time| has
+ // not changed. This method returns without blocking, and the operation will
+ // be performed asynchronously without any completion notification.
+ void WriteMetadata(const GURL& url, base::Time expected_response_time,
+ IOBuffer* buf, int buf_len);
+
// Get/Set the cache's mode.
void set_mode(Mode value) { mode_ = value; }
Mode mode() { return mode_; }
- void set_type(CacheType type) { type_ = type; }
- CacheType type() { return type_; }
-
// Close currently active sockets so that fresh page loads will not use any
// recycled connections. For sockets currently in use, they may not close
// immediately, but they will not be reusable. This is for debugging.
@@ -145,15 +180,27 @@
enable_range_support_ = value;
}
- private:
+ protected:
+ // Disk cache entry data indices.
+ enum {
+ kResponseInfoIndex = 0,
+ kResponseContentIndex,
+ kMetadataIndex,
+ // Must remain at the end of the enum.
+ kNumCacheEntryDataIndices
+ };
+ friend class ViewCacheHelper;
+
+ private:
// Types --------------------------------------------------------------------
class BackendCallback;
+ class MetadataWriter;
class Transaction;
class WorkItem;
friend class Transaction;
- struct NewEntry; // Info for an entry under construction.
+ struct PendingOp; // Info for an entry under construction.
typedef std::list<Transaction*> TransactionList;
typedef std::list<WorkItem*> WorkItemList;
@@ -171,24 +218,35 @@
};
typedef base::hash_map<std::string, ActiveEntry*> ActiveEntriesMap;
- typedef base::hash_map<std::string, NewEntry*> NewEntriesMap;
+ typedef base::hash_map<std::string, PendingOp*> PendingOpsMap;
typedef std::set<ActiveEntry*> ActiveEntriesSet;
-
// Methods ------------------------------------------------------------------
+ // Creates the |backend| object and notifies the |callback| when the operation
+ // completes. Returns an error code.
+ int CreateBackend(disk_cache::Backend** backend,
+ CompletionCallback* callback);
+
+ // Makes sure that the backend creation is complete before allowing the
+ // provided transaction to use the object. Returns an error code. |trans|
+ // will be notified via its IO callback if this method returns ERR_IO_PENDING.
+ // The transaction is free to use the backend directly at any time after
+ // receiving the notification.
+ int GetBackendForTransaction(Transaction* trans);
+
// Generates the cache key for this request.
std::string GenerateCacheKey(const HttpRequestInfo*);
- // Dooms the entry selected by |key|. |callback| is used for completion
- // notification if this function returns ERR_IO_PENDING. The entry can be
+ // Dooms the entry selected by |key|. |trans| will be notified via its IO
+ // callback if this method returns ERR_IO_PENDING. The entry can be
// currently in use or not.
- int DoomEntry(const std::string& key, CompletionCallback* callback);
+ int DoomEntry(const std::string& key, Transaction* trans);
- // Dooms the entry selected by |key|. |callback| is used for completion
- // notification if this function returns ERR_IO_PENDING. The entry should not
+ // Dooms the entry selected by |key|. |trans| will be notified via its IO
+ // callback if this method returns ERR_IO_PENDING. The entry should not
// be currently in use.
- int AsyncDoomEntry(const std::string& key, CompletionCallback* callback);
+ int AsyncDoomEntry(const std::string& key, Transaction* trans);
// Closes a previously doomed entry.
void FinalizeDoomedEntry(ActiveEntry* entry);
@@ -208,29 +266,33 @@
// Deletes an ActiveEntry using an exhaustive search.
void SlowDeactivateEntry(ActiveEntry* entry);
- // Returns the NewEntry for the desired |key|. If an entry is not under
- // construction already, a new NewEntry structure is created.
- NewEntry* GetNewEntry(const std::string& key);
+ // Returns the PendingOp for the desired |key|. If an entry is not under
+ // construction already, a new PendingOp structure is created.
+ PendingOp* GetPendingOp(const std::string& key);
- // Deletes a NewEntry.
- void DeleteNewEntry(NewEntry* entry);
+ // Deletes a PendingOp.
+ void DeletePendingOp(PendingOp* pending_op);
// Opens the disk cache entry associated with |key|, returning an ActiveEntry
- // in |*entry|. |callback| is used for completion notification if this
- // function returns ERR_IO_PENDING.
+ // in |*entry|. |trans| will be notified via its IO callback if this method
+ // returns ERR_IO_PENDING.
int OpenEntry(const std::string& key, ActiveEntry** entry,
- CompletionCallback* callback);
+ Transaction* trans);
// Creates the disk cache entry associated with |key|, returning an
- // ActiveEntry in |*entry|. |callback| is used for completion notification if
- // this function returns ERR_IO_PENDING.
+ // ActiveEntry in |*entry|. |trans| will be notified via its IO callback if
+ // this method returns ERR_IO_PENDING.
int CreateEntry(const std::string& key, ActiveEntry** entry,
- CompletionCallback* callback);
+ Transaction* trans);
// Destroys an ActiveEntry (active or doomed).
void DestroyEntry(ActiveEntry* entry);
- // Adds a transaction to an ActiveEntry.
+ // Adds a transaction to an ActiveEntry. If this method returns ERR_IO_PENDING
+ // the transaction will be notified about completion via its IO callback. This
+ // method returns ERR_CACHE_RACE to signal the transaction that it cannot be
+ // added to the provided entry, and it should retry the process with another
+ // one (in this case, the entry is no longer valid).
int AddTransactionToEntry(ActiveEntry* entry, Transaction* trans);
// Called when the transaction has finished working with this entry. |cancel|
@@ -249,17 +311,20 @@
// transactions can start reading from this entry.
void ConvertWriterToReader(ActiveEntry* entry);
- // Removes the transaction |trans|, waiting for |callback|, from the pending
- // list of an entry (NewEntry, active or doomed entry).
- void RemovePendingTransaction(Transaction* trans, CompletionCallback* cb);
+ // Returns the LoadState of the provided pending transaction.
+ LoadState GetLoadStateForPendingTransaction(const Transaction* trans);
+
+ // Removes the transaction |trans|, from the pending list of an entry
+ // (PendingOp, active or doomed entry).
+ void RemovePendingTransaction(Transaction* trans);
// Removes the transaction |trans|, from the pending list of |entry|.
bool RemovePendingTransactionFromEntry(ActiveEntry* entry,
Transaction* trans);
- // Removes the callback |cb|, from the pending list of |entry|.
- bool RemovePendingCallbackFromNewEntry(NewEntry* entry,
- CompletionCallback* cb);
+ // Removes the transaction |trans|, from the pending list of |pending_op|.
+ bool RemovePendingTransactionFromPendingOp(PendingOp* pending_op,
+ Transaction* trans);
// Resumes processing the pending list of |entry|.
void ProcessPendingQueue(ActiveEntry* entry);
@@ -271,16 +336,20 @@
// Callbacks ----------------------------------------------------------------
// Processes BackendCallback notifications.
- void OnIOComplete(int result, NewEntry* entry);
+ void OnIOComplete(int result, PendingOp* entry);
+
+ // Processes the backend creation notification.
+ void OnBackendCreated(int result, PendingOp* pending_op);
// Variables ----------------------------------------------------------------
// Used when lazily constructing the disk_cache_.
- FilePath disk_cache_dir_;
+ scoped_ptr<BackendFactory> backend_factory_;
+ disk_cache::Backend* temp_backend_;
+ bool building_backend_;
Mode mode_;
- CacheType type_;
scoped_ptr<HttpTransactionFactory> network_layer_;
scoped_ptr<disk_cache::Backend> disk_cache_;
@@ -292,12 +361,11 @@
ActiveEntriesSet doomed_entries_;
// The set of entries "under construction".
- NewEntriesMap new_entries_;
+ PendingOpsMap pending_ops_;
ScopedRunnableMethodFactory<HttpCache> task_factory_;
bool enable_range_support_;
- int cache_size_;
typedef base::hash_map<std::string, int> PlaybackCacheMap;
scoped_ptr<PlaybackCacheMap> playback_cache_map_;
diff --git a/net/http/http_cache_transaction.cc b/net/http/http_cache_transaction.cc
index 3be313f..2ea08b8 100644
--- a/net/http/http_cache_transaction.cc
+++ b/net/http/http_cache_transaction.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -16,8 +16,8 @@
#include "base/time.h"
#include "net/base/io_buffer.h"
#include "net/base/load_flags.h"
-#include "net/base/load_log.h"
#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
#include "net/base/ssl_cert_request_info.h"
#include "net/disk_cache/disk_cache.h"
#include "net/http/http_request_info.h"
@@ -30,14 +30,6 @@
namespace net {
-// disk cache entry data indices.
-enum {
- kResponseInfoIndex,
- kResponseContentIndex
-};
-
-//-----------------------------------------------------------------------------
-
struct HeaderNameAndValue {
const char* name;
const char* value;
@@ -77,16 +69,17 @@
{ NULL, NULL }
};
-static bool HeaderMatches(const HttpUtil::HeadersIterator& h,
+static bool HeaderMatches(const HttpRequestHeaders& headers,
const HeaderNameAndValue* search) {
for (; search->name; ++search) {
- if (!LowerCaseEqualsASCII(h.name_begin(), h.name_end(), search->name))
+ std::string header_value;
+ if (!headers.GetHeader(search->name, &header_value))
continue;
if (!search->value)
return true;
- HttpUtil::ValuesIterator v(h.values_begin(), h.values_end(), ',');
+ HttpUtil::ValuesIterator v(header_value.begin(), header_value.end(), ',');
while (v.GetNext()) {
if (LowerCaseEqualsASCII(v.value_begin(), v.value_end(), search->value))
return true;
@@ -116,28 +109,26 @@
cache_pending_(false),
read_offset_(0),
effective_load_flags_(0),
+ write_len_(0),
final_upload_progress_(0),
ALLOW_THIS_IN_INITIALIZER_LIST(
io_callback_(this, &Transaction::OnIOComplete)),
ALLOW_THIS_IN_INITIALIZER_LIST(
cache_callback_(new CancelableCompletionCallback<Transaction>(
- this, &Transaction::OnIOComplete))),
+ this, &Transaction::OnIOComplete))),
ALLOW_THIS_IN_INITIALIZER_LIST(
write_headers_callback_(new CancelableCompletionCallback<Transaction>(
- this, &Transaction::OnIOComplete))) {
+ this, &Transaction::OnIOComplete))) {
COMPILE_ASSERT(HttpCache::Transaction::kNumValidationHeaders ==
- ARRAYSIZE_UNSAFE(kValidationHeaders),
+ arraysize(kValidationHeaders),
Invalid_number_of_validation_headers);
}
-#if defined(OS_WIN)
-#pragma optimize("", off)
-#pragma warning(disable:4748)
-#endif
HttpCache::Transaction::~Transaction() {
- // TODO(rvargas): remove this after finding the cause for bug 31723.
- char local_obj[sizeof(*this)];
- memcpy(local_obj, this, sizeof(local_obj));
+ // We may have to issue another IO, but we should never invoke the callback_
+ // after this point.
+ callback_ = NULL;
+
if (cache_) {
if (entry_) {
bool cancel_request = reading_ && enable_range_support_;
@@ -151,7 +142,7 @@
cache_->DoneWithEntry(entry_, this, cancel_request);
} else if (cache_pending_) {
- cache_->RemovePendingTransaction(this, &io_callback_);
+ cache_->RemovePendingTransaction(this);
}
}
@@ -164,14 +155,10 @@
// cache_ pointer to signal that we are dead. See DoCacheReadCompleted.
cache_.reset();
}
-#if defined(OS_WIN)
-#pragma warning(default:4748)
-#pragma optimize("", on)
-#endif
int HttpCache::Transaction::Start(const HttpRequestInfo* request,
CompletionCallback* callback,
- LoadLog* load_log) {
+ const BoundNetLog& net_log) {
DCHECK(request);
DCHECK(callback);
@@ -184,48 +171,13 @@
if (!cache_)
return ERR_UNEXPECTED;
- SetRequest(load_log, request);
+ SetRequest(net_log, request);
- int rv;
+ // We have to wait until the backend is initialized so we start the SM.
+ next_state_ = STATE_GET_BACKEND;
+ int rv = DoLoop(OK);
- if (!ShouldPassThrough()) {
- cache_key_ = cache_->GenerateCacheKey(request);
-
- // Requested cache access mode.
- if (effective_load_flags_ & LOAD_ONLY_FROM_CACHE) {
- mode_ = READ;
- } else if (effective_load_flags_ & LOAD_BYPASS_CACHE) {
- mode_ = WRITE;
- } else {
- mode_ = READ_WRITE;
- }
-
- // Downgrade to UPDATE if the request has been externally conditionalized.
- if (external_validation_.initialized) {
- if (mode_ & WRITE) {
- // Strip off the READ_DATA bit (and maybe add back a READ_META bit
- // in case READ was off).
- mode_ = UPDATE;
- } else {
- mode_ = NONE;
- }
- }
- }
-
- // If must use cache, then we must fail. This can happen for back/forward
- // navigations to a page generated via a form post.
- if (!(mode_ & READ) && effective_load_flags_ & LOAD_ONLY_FROM_CACHE)
- return ERR_CACHE_MISS;
-
- if (mode_ == NONE) {
- if (partial_.get())
- partial_->RestoreHeaders(&custom_request_->extra_headers);
- rv = BeginNetworkRequest();
- } else {
- rv = AddToEntry();
- }
-
- // Setting this here allows us to check for the existance of a callback_ to
+ // Setting this here allows us to check for the existence of a callback_ to
// determine if we are still inside Start.
if (rv == ERR_IO_PENDING)
callback_ = callback;
@@ -351,6 +303,9 @@
return rv;
}
+void HttpCache::Transaction::StopCaching() {
+}
+
const HttpResponseInfo* HttpCache::Transaction::GetResponseInfo() const {
// Null headers means we encountered an error or haven't a response yet
if (auth_response_.headers)
@@ -360,11 +315,14 @@
}
LoadState HttpCache::Transaction::GetLoadState() const {
- if (network_trans_.get())
- return network_trans_->GetLoadState();
- if (entry_ || !request_)
- return LOAD_STATE_IDLE;
- return LOAD_STATE_WAITING_FOR_CACHE;
+ LoadState state = GetWriterLoadState();
+ if (state != LOAD_STATE_WAITING_FOR_CACHE)
+ return state;
+
+ if (cache_)
+ return cache_->GetLoadStateForPendingTransaction(this);
+
+ return LOAD_STATE_IDLE;
}
uint64 HttpCache::Transaction::GetUploadProgress() const {
@@ -373,197 +331,20 @@
return final_upload_progress_;
}
-int HttpCache::Transaction::AddToEntry() {
- next_state_ = STATE_INIT_ENTRY;
- cache_pending_ = false;
- return DoLoop(OK);
-}
-
-int HttpCache::Transaction::DoInitEntry() {
- DCHECK(!new_entry_);
-
- if (!cache_)
+int HttpCache::Transaction::WriteMetadata(IOBuffer* buf, int buf_len,
+ CompletionCallback* callback) {
+ DCHECK(buf);
+ DCHECK_GT(buf_len, 0);
+ DCHECK(callback);
+ if (!cache_ || !entry_)
return ERR_UNEXPECTED;
- if (mode_ == WRITE) {
- next_state_ = STATE_DOOM_ENTRY;
- return OK;
- }
-
- next_state_ = STATE_OPEN_ENTRY;
- return OK;
-}
-
-int HttpCache::Transaction::DoDoomEntry() {
- next_state_ = STATE_DOOM_ENTRY_COMPLETE;
- cache_pending_ = true;
- // TODO(rvargas): Add a LoadLog event.
- return cache_->DoomEntry(cache_key_, &io_callback_);
-}
-
-int HttpCache::Transaction::DoDoomEntryComplete(int result) {
- next_state_ = STATE_CREATE_ENTRY;
- cache_pending_ = false;
- if (result == ERR_CACHE_RACE)
- next_state_ = STATE_INIT_ENTRY;
-
- return OK;
-}
-
-int HttpCache::Transaction::DoOpenEntry() {
- DCHECK(!new_entry_);
- next_state_ = STATE_OPEN_ENTRY_COMPLETE;
- cache_pending_ = true;
- LoadLog::BeginEvent(load_log_, LoadLog::TYPE_HTTP_CACHE_OPEN_ENTRY);
- return cache_->OpenEntry(cache_key_, &new_entry_, &io_callback_);
-}
-
-int HttpCache::Transaction::DoOpenEntryComplete(int result) {
- // It is important that we go to STATE_ADD_TO_ENTRY whenever the result is
- // OK, otherwise the cache will end up with an active entry without any
- // transaction attached.
- LoadLog::EndEvent(load_log_, LoadLog::TYPE_HTTP_CACHE_OPEN_ENTRY);
- cache_pending_ = false;
- if (result == OK) {
- next_state_ = STATE_ADD_TO_ENTRY;
- return OK;
- }
-
- if (result == ERR_CACHE_RACE) {
- next_state_ = STATE_INIT_ENTRY;
- return OK;
- }
-
- if (mode_ == READ_WRITE) {
- mode_ = WRITE;
- next_state_ = STATE_CREATE_ENTRY;
- return OK;
- }
- if (mode_ == UPDATE) {
- // There is no cache entry to update; proceed without caching.
- mode_ = NONE;
- next_state_ = STATE_SEND_REQUEST;
- return OK;
- }
- if (cache_->mode() == PLAYBACK)
- DLOG(INFO) << "Playback Cache Miss: " << request_->url;
-
- // The entry does not exist, and we are not permitted to create a new entry,
- // so we must fail.
- return ERR_CACHE_MISS;
-}
-
-int HttpCache::Transaction::DoCreateEntry() {
- DCHECK(!new_entry_);
- next_state_ = STATE_CREATE_ENTRY_COMPLETE;
- cache_pending_ = true;
- LoadLog::BeginEvent(load_log_, LoadLog::TYPE_HTTP_CACHE_CREATE_ENTRY);
- return cache_->CreateEntry(cache_key_, &new_entry_, &io_callback_);
-}
-
-int HttpCache::Transaction::DoCreateEntryComplete(int result) {
- // It is important that we go to STATE_ADD_TO_ENTRY whenever the result is
- // OK, otherwise the cache will end up with an active entry without any
- // transaction attached.
- LoadLog::EndEvent(load_log_, LoadLog::TYPE_HTTP_CACHE_CREATE_ENTRY);
- cache_pending_ = false;
- next_state_ = STATE_ADD_TO_ENTRY;
-
- if (result == ERR_CACHE_RACE) {
- next_state_ = STATE_INIT_ENTRY;
- return OK;
- }
-
- if (result != OK) {
- // We have a race here: Maybe we failed to open the entry and decided to
- // create one, but by the time we called create, another transaction already
- // created the entry. If we want to eliminate this issue, we need an atomic
- // OpenOrCreate() method exposed by the disk cache.
- DLOG(WARNING) << "Unable to create cache entry";
- mode_ = NONE;
- if (partial_.get())
- partial_->RestoreHeaders(&custom_request_->extra_headers);
- next_state_ = STATE_SEND_REQUEST;
- }
- return OK;
-}
-
-int HttpCache::Transaction::DoAddToEntry() {
- DCHECK(new_entry_);
- cache_pending_ = true;
- LoadLog::BeginEvent(load_log_, LoadLog::TYPE_HTTP_CACHE_WAITING);
- int rv = cache_->AddTransactionToEntry(new_entry_, this);
- new_entry_ = NULL;
- return rv;
-}
-
-int HttpCache::Transaction::EntryAvailable(ActiveEntry* entry) {
- LoadLog::EndEvent(load_log_, LoadLog::TYPE_HTTP_CACHE_WAITING);
- cache_pending_ = false;
- entry_ = entry;
-
- next_state_ = STATE_ENTRY_AVAILABLE;
- if (new_entry_) {
- // We are inside AddTransactionToEntry() so avoid reentering DoLoop().
- DCHECK_EQ(new_entry_, entry);
- return OK;
- }
- return DoLoop(OK);
-}
-
-int HttpCache::Transaction::DoEntryAvailable() {
- DCHECK(!new_entry_);
- if (mode_ == WRITE) {
- if (partial_.get())
- partial_->RestoreHeaders(&custom_request_->extra_headers);
- next_state_ = STATE_SEND_REQUEST;
- } else {
- // We have to read the headers from the cached entry.
- DCHECK(mode_ & READ_META);
- next_state_ = STATE_CACHE_READ_RESPONSE;
- }
- return OK;
-}
-
-int HttpCache::Transaction::DoCacheReadResponseComplete(int result) {
- cache_callback_->Release(); // Balance the AddRef from DoCacheReadResponse.
- LoadLog::EndEvent(load_log_, LoadLog::TYPE_HTTP_CACHE_READ_INFO);
- if (result != io_buf_len_ ||
- !HttpCache::ParseResponseInfo(read_buf_->data(), io_buf_len_,
- &response_, &truncated_)) {
- DLOG(ERROR) << "ReadData failed: " << result;
- return ERR_CACHE_READ_FAILURE;
- }
-
- // We now have access to the cache entry.
- //
- // o if we are a reader for the transaction, then we can start reading the
- // cache entry.
- //
- // o if we can read or write, then we should check if the cache entry needs
- // to be validated and then issue a network request if needed or just read
- // from the cache if the cache entry is already valid.
- //
- // o if we are set to UPDATE, then we are handling an externally
- // conditionalized request (if-modified-since / if-none-match). We check
- // if the request headers define a validation request.
- //
- switch (mode_) {
- case READ:
- result = BeginCacheRead();
- break;
- case READ_WRITE:
- result = BeginPartialCacheValidation();
- break;
- case UPDATE:
- result = BeginExternallyConditionalizedRequest();
- break;
- case WRITE:
- default:
- NOTREACHED();
- result = ERR_FAILED;
- }
- return result;
+ // We don't need to track this operation for anything.
+ // It could be possible to check if there is something already written and
+ // avoid writing again (it should be the same, right?), but let's allow the
+ // caller to "update" the contents with something new.
+ return entry_->disk_entry->WriteData(kMetadataIndex, 0, buf, buf_len,
+ callback, true);
}
bool HttpCache::Transaction::AddTruncatedFlag() {
@@ -589,6 +370,16 @@
return true;
}
+LoadState HttpCache::Transaction::GetWriterLoadState() const {
+ if (network_trans_.get())
+ return network_trans_->GetLoadState();
+ if (entry_ || !request_)
+ return LOAD_STATE_IDLE;
+ return LOAD_STATE_WAITING_FOR_CACHE;
+}
+
+//-----------------------------------------------------------------------------
+
void HttpCache::Transaction::DoCallback(int rv) {
DCHECK(rv != ERR_IO_PENDING);
DCHECK(callback_);
@@ -614,6 +405,13 @@
State state = next_state_;
next_state_ = STATE_NONE;
switch (state) {
+ case STATE_GET_BACKEND:
+ DCHECK_EQ(OK, rv);
+ rv = DoGetBackend();
+ break;
+ case STATE_GET_BACKEND_COMPLETE:
+ rv = DoGetBackendComplete(rv);
+ break;
case STATE_SEND_REQUEST:
DCHECK_EQ(OK, rv);
rv = DoSendRequest();
@@ -661,13 +459,15 @@
DCHECK_EQ(OK, rv);
rv = DoAddToEntry();
break;
- case STATE_ENTRY_AVAILABLE:
- DCHECK_EQ(OK, rv);
- rv = DoEntryAvailable();
+ case STATE_ADD_TO_ENTRY_COMPLETE:
+ rv = DoAddToEntryComplete(rv);
break;
- case STATE_PARTIAL_CACHE_VALIDATION:
+ case STATE_START_PARTIAL_CACHE_VALIDATION:
DCHECK_EQ(OK, rv);
- rv = DoPartialCacheValidation();
+ rv = DoStartPartialCacheValidation();
+ break;
+ case STATE_COMPLETE_PARTIAL_CACHE_VALIDATION:
+ rv = DoCompletePartialCacheValidation(rv);
break;
case STATE_UPDATE_CACHED_RESPONSE:
DCHECK_EQ(OK, rv);
@@ -687,6 +487,13 @@
case STATE_TRUNCATE_CACHED_DATA_COMPLETE:
rv = DoTruncateCachedDataComplete(rv);
break;
+ case STATE_TRUNCATE_CACHED_METADATA:
+ DCHECK_EQ(OK, rv);
+ rv = DoTruncateCachedMetadata();
+ break;
+ case STATE_TRUNCATE_CACHED_METADATA_COMPLETE:
+ rv = DoTruncateCachedMetadataComplete(rv);
+ break;
case STATE_PARTIAL_HEADERS_RECEIVED:
DCHECK_EQ(OK, rv);
rv = DoPartialHeadersReceived();
@@ -709,6 +516,13 @@
case STATE_CACHE_WRITE_RESPONSE_COMPLETE:
rv = DoCacheWriteResponseComplete(rv);
break;
+ case STATE_CACHE_READ_METADATA:
+ DCHECK_EQ(OK, rv);
+ rv = DoCacheReadMetadata();
+ break;
+ case STATE_CACHE_READ_METADATA_COMPLETE:
+ rv = DoCacheReadMetadataComplete(rv);
+ break;
case STATE_CACHE_QUERY_DATA:
DCHECK_EQ(OK, rv);
rv = DoCacheQueryData();
@@ -742,9 +556,664 @@
return rv;
}
-void HttpCache::Transaction::SetRequest(LoadLog* load_log,
+int HttpCache::Transaction::DoGetBackend() {
+ cache_pending_ = true;
+ next_state_ = STATE_GET_BACKEND_COMPLETE;
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_CACHE_WAITING, NULL);
+ return cache_->GetBackendForTransaction(this);
+}
+
+int HttpCache::Transaction::DoGetBackendComplete(int result) {
+ DCHECK(result == OK || result == ERR_FAILED);
+ net_log_.EndEvent(NetLog::TYPE_HTTP_CACHE_WAITING, NULL);
+ cache_pending_ = false;
+
+ if (!ShouldPassThrough()) {
+ cache_key_ = cache_->GenerateCacheKey(request_);
+
+ // Requested cache access mode.
+ if (effective_load_flags_ & LOAD_ONLY_FROM_CACHE) {
+ mode_ = READ;
+ } else if (effective_load_flags_ & LOAD_BYPASS_CACHE) {
+ mode_ = WRITE;
+ } else {
+ mode_ = READ_WRITE;
+ }
+
+ // Downgrade to UPDATE if the request has been externally conditionalized.
+ if (external_validation_.initialized) {
+ if (mode_ & WRITE) {
+ // Strip off the READ_DATA bit (and maybe add back a READ_META bit
+ // in case READ was off).
+ mode_ = UPDATE;
+ } else {
+ mode_ = NONE;
+ }
+ }
+ }
+
+ // If must use cache, then we must fail. This can happen for back/forward
+ // navigations to a page generated via a form post.
+ if (!(mode_ & READ) && effective_load_flags_ & LOAD_ONLY_FROM_CACHE)
+ return ERR_CACHE_MISS;
+
+ if (mode_ == NONE) {
+ if (partial_.get())
+ partial_->RestoreHeaders(&custom_request_->extra_headers);
+ next_state_ = STATE_SEND_REQUEST;
+ } else {
+ next_state_ = STATE_INIT_ENTRY;
+ }
+
+ return OK;
+}
+
+int HttpCache::Transaction::DoSendRequest() {
+ DCHECK(mode_ & WRITE || mode_ == NONE);
+ DCHECK(!network_trans_.get());
+
+ // Create a network transaction.
+ int rv = cache_->network_layer_->CreateTransaction(&network_trans_);
+ if (rv != OK)
+ return rv;
+
+ next_state_ = STATE_SEND_REQUEST_COMPLETE;
+ rv = network_trans_->Start(request_, &io_callback_, net_log_);
+ return rv;
+}
+
+int HttpCache::Transaction::DoSendRequestComplete(int result) {
+ if (!cache_)
+ return ERR_UNEXPECTED;
+
+ if (result == OK) {
+ next_state_ = STATE_SUCCESSFUL_SEND_REQUEST;
+ return OK;
+ }
+
+ if (IsCertificateError(result)) {
+ const HttpResponseInfo* response = network_trans_->GetResponseInfo();
+ // If we get a certificate error, then there is a certificate in ssl_info,
+ // so GetResponseInfo() should never returns NULL here.
+ DCHECK(response);
+ response_.ssl_info = response->ssl_info;
+ } else if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
+ const HttpResponseInfo* response = network_trans_->GetResponseInfo();
+ DCHECK(response);
+ response_.cert_request_info = response->cert_request_info;
+ }
+ return result;
+}
+
+// We received the response headers and there is no error.
+int HttpCache::Transaction::DoSuccessfulSendRequest() {
+ DCHECK(!new_response_);
+ const HttpResponseInfo* new_response = network_trans_->GetResponseInfo();
+ if (new_response->headers->response_code() == 401 ||
+ new_response->headers->response_code() == 407) {
+ auth_response_ = *new_response;
+ return OK;
+ }
+
+ if (!ValidatePartialResponse(new_response->headers, &server_responded_206_) &&
+ !auth_response_.headers) {
+ // Something went wrong with this request and we have to restart it.
+ // If we have an authentication response, we are exposed to weird things
+ // hapenning if the user cancels the authentication before we receive
+ // the new response.
+ response_ = HttpResponseInfo();
+ network_trans_.reset();
+ next_state_ = STATE_SEND_REQUEST;
+ return OK;
+ }
+ if (server_responded_206_ && mode_ == READ_WRITE && !truncated_ &&
+ response_.headers->response_code() == 200) {
+ // We have stored the full entry, but it changed and the server is
+ // sending a range. We have to delete the old entry.
+ DoneWritingToEntry(false);
+ }
+
+ HistogramHeaders(new_response->headers);
+
+ new_response_ = new_response;
+ // Are we expecting a response to a conditional query?
+ if (mode_ == READ_WRITE || mode_ == UPDATE) {
+ if (new_response->headers->response_code() == 304 ||
+ server_responded_206_) {
+ next_state_ = STATE_UPDATE_CACHED_RESPONSE;
+ return OK;
+ }
+ mode_ = WRITE;
+ }
+
+ next_state_ = STATE_OVERWRITE_CACHED_RESPONSE;
+ return OK;
+}
+
+int HttpCache::Transaction::DoNetworkRead() {
+ next_state_ = STATE_NETWORK_READ_COMPLETE;
+ return network_trans_->Read(read_buf_, io_buf_len_, &io_callback_);
+}
+
+int HttpCache::Transaction::DoNetworkReadComplete(int result) {
+ DCHECK(mode_ & WRITE || mode_ == NONE);
+
+ if (!cache_)
+ return ERR_UNEXPECTED;
+
+ next_state_ = STATE_CACHE_WRITE_DATA;
+ return result;
+}
+
+int HttpCache::Transaction::DoInitEntry() {
+ DCHECK(!new_entry_);
+
+ if (!cache_)
+ return ERR_UNEXPECTED;
+
+ if (mode_ == WRITE) {
+ next_state_ = STATE_DOOM_ENTRY;
+ return OK;
+ }
+
+ next_state_ = STATE_OPEN_ENTRY;
+ return OK;
+}
+
+int HttpCache::Transaction::DoOpenEntry() {
+ DCHECK(!new_entry_);
+ next_state_ = STATE_OPEN_ENTRY_COMPLETE;
+ cache_pending_ = true;
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_CACHE_OPEN_ENTRY, NULL);
+ return cache_->OpenEntry(cache_key_, &new_entry_, this);
+}
+
+int HttpCache::Transaction::DoOpenEntryComplete(int result) {
+ // It is important that we go to STATE_ADD_TO_ENTRY whenever the result is
+ // OK, otherwise the cache will end up with an active entry without any
+ // transaction attached.
+ net_log_.EndEvent(NetLog::TYPE_HTTP_CACHE_OPEN_ENTRY, NULL);
+ cache_pending_ = false;
+ if (result == OK) {
+ next_state_ = STATE_ADD_TO_ENTRY;
+ return OK;
+ }
+
+ if (result == ERR_CACHE_RACE) {
+ next_state_ = STATE_INIT_ENTRY;
+ return OK;
+ }
+
+ if (mode_ == READ_WRITE) {
+ mode_ = WRITE;
+ next_state_ = STATE_CREATE_ENTRY;
+ return OK;
+ }
+ if (mode_ == UPDATE) {
+ // There is no cache entry to update; proceed without caching.
+ mode_ = NONE;
+ next_state_ = STATE_SEND_REQUEST;
+ return OK;
+ }
+ if (cache_->mode() == PLAYBACK)
+ DLOG(INFO) << "Playback Cache Miss: " << request_->url;
+
+ // The entry does not exist, and we are not permitted to create a new entry,
+ // so we must fail.
+ return ERR_CACHE_MISS;
+}
+
+int HttpCache::Transaction::DoCreateEntry() {
+ DCHECK(!new_entry_);
+ next_state_ = STATE_CREATE_ENTRY_COMPLETE;
+ cache_pending_ = true;
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_CACHE_CREATE_ENTRY, NULL);
+ return cache_->CreateEntry(cache_key_, &new_entry_, this);
+}
+
+int HttpCache::Transaction::DoCreateEntryComplete(int result) {
+ // It is important that we go to STATE_ADD_TO_ENTRY whenever the result is
+ // OK, otherwise the cache will end up with an active entry without any
+ // transaction attached.
+ net_log_.EndEvent(NetLog::TYPE_HTTP_CACHE_CREATE_ENTRY, NULL);
+ cache_pending_ = false;
+ next_state_ = STATE_ADD_TO_ENTRY;
+
+ if (result == ERR_CACHE_RACE) {
+ next_state_ = STATE_INIT_ENTRY;
+ return OK;
+ }
+
+ if (result != OK) {
+ // We have a race here: Maybe we failed to open the entry and decided to
+ // create one, but by the time we called create, another transaction already
+ // created the entry. If we want to eliminate this issue, we need an atomic
+ // OpenOrCreate() method exposed by the disk cache.
+ DLOG(WARNING) << "Unable to create cache entry";
+ mode_ = NONE;
+ if (partial_.get())
+ partial_->RestoreHeaders(&custom_request_->extra_headers);
+ next_state_ = STATE_SEND_REQUEST;
+ }
+ return OK;
+}
+
+int HttpCache::Transaction::DoDoomEntry() {
+ next_state_ = STATE_DOOM_ENTRY_COMPLETE;
+ cache_pending_ = true;
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_CACHE_DOOM_ENTRY, NULL);
+ return cache_->DoomEntry(cache_key_, this);
+}
+
+int HttpCache::Transaction::DoDoomEntryComplete(int result) {
+ net_log_.EndEvent(NetLog::TYPE_HTTP_CACHE_DOOM_ENTRY, NULL);
+ next_state_ = STATE_CREATE_ENTRY;
+ cache_pending_ = false;
+ if (result == ERR_CACHE_RACE)
+ next_state_ = STATE_INIT_ENTRY;
+
+ return OK;
+}
+
+int HttpCache::Transaction::DoAddToEntry() {
+ DCHECK(new_entry_);
+ cache_pending_ = true;
+ next_state_ = STATE_ADD_TO_ENTRY_COMPLETE;
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_CACHE_WAITING, NULL);
+ DCHECK(entry_lock_waiting_since_.is_null());
+ entry_lock_waiting_since_ = base::TimeTicks::Now();
+ return cache_->AddTransactionToEntry(new_entry_, this);
+}
+
+int HttpCache::Transaction::DoAddToEntryComplete(int result) {
+ net_log_.EndEvent(NetLog::TYPE_HTTP_CACHE_WAITING, NULL);
+ UMA_HISTOGRAM_TIMES("HttpCache.EntryLockWait",
+ base::TimeTicks::Now() - entry_lock_waiting_since_);
+ entry_lock_waiting_since_ = base::TimeTicks();
+ DCHECK(new_entry_);
+ cache_pending_ = false;
+
+ if (result == ERR_CACHE_RACE) {
+ new_entry_ = NULL;
+ next_state_ = STATE_INIT_ENTRY;
+ return OK;
+ }
+
+ if (result != OK) {
+ // If there is a failure, the cache should have taken care of new_entry_.
+ NOTREACHED();
+ new_entry_ = NULL;
+ return result;
+ }
+
+ entry_ = new_entry_;
+ new_entry_ = NULL;
+
+ if (mode_ == WRITE) {
+ if (partial_.get())
+ partial_->RestoreHeaders(&custom_request_->extra_headers);
+ next_state_ = STATE_SEND_REQUEST;
+ } else {
+ // We have to read the headers from the cached entry.
+ DCHECK(mode_ & READ_META);
+ next_state_ = STATE_CACHE_READ_RESPONSE;
+ }
+ return OK;
+}
+
+int HttpCache::Transaction::DoStartPartialCacheValidation() {
+ if (mode_ == NONE)
+ return OK;
+
+ next_state_ = STATE_COMPLETE_PARTIAL_CACHE_VALIDATION;
+ return partial_->ShouldValidateCache(entry_->disk_entry, &io_callback_);
+}
+
+int HttpCache::Transaction::DoCompletePartialCacheValidation(int result) {
+ if (!result) {
+ // This is the end of the request.
+ if (mode_ & WRITE) {
+ DoneWritingToEntry(true);
+ } else {
+ cache_->DoneReadingFromEntry(entry_, this);
+ entry_ = NULL;
+ }
+ return result;
+ }
+
+ if (result < 0)
+ return result;
+
+ partial_->PrepareCacheValidation(entry_->disk_entry,
+ &custom_request_->extra_headers);
+
+ if (reading_ && partial_->IsCurrentRangeCached()) {
+ next_state_ = STATE_CACHE_READ_DATA;
+ return OK;
+ }
+
+ return BeginCacheValidation();
+}
+
+// We received 304 or 206 and we want to update the cached response headers.
+int HttpCache::Transaction::DoUpdateCachedResponse() {
+ next_state_ = STATE_UPDATE_CACHED_RESPONSE_COMPLETE;
+ int rv = OK;
+ // Update cached response based on headers in new_response.
+ // TODO(wtc): should we update cached certificate (response_.ssl_info), too?
+ response_.headers->Update(*new_response_->headers);
+ response_.response_time = new_response_->response_time;
+ response_.request_time = new_response_->request_time;
+
+ if (response_.headers->HasHeaderValue("cache-control", "no-store")) {
+ int ret = cache_->DoomEntry(cache_key_, NULL);
+ DCHECK_EQ(OK, ret);
+ } else {
+ // If we are already reading, we already updated the headers for this
+ // request; doing it again will change Content-Length.
+ if (!reading_) {
+ target_state_ = STATE_UPDATE_CACHED_RESPONSE_COMPLETE;
+ next_state_ = STATE_CACHE_WRITE_RESPONSE;
+ rv = OK;
+ }
+ }
+ return rv;
+}
+
+int HttpCache::Transaction::DoUpdateCachedResponseComplete(int result) {
+ if (mode_ == UPDATE) {
+ DCHECK(!server_responded_206_);
+ // We got a "not modified" response and already updated the corresponding
+ // cache entry above.
+ //
+ // By closing the cached entry now, we make sure that the 304 rather than
+ // the cached 200 response, is what will be returned to the user.
+ DoneWritingToEntry(true);
+ } else if (entry_ && !server_responded_206_) {
+ DCHECK_EQ(READ_WRITE, mode_);
+ if (!partial_.get() || partial_->IsLastRange()) {
+ cache_->ConvertWriterToReader(entry_);
+ mode_ = READ;
+ }
+ // We no longer need the network transaction, so destroy it.
+ final_upload_progress_ = network_trans_->GetUploadProgress();
+ network_trans_.reset();
+ }
+ next_state_ = STATE_OVERWRITE_CACHED_RESPONSE;
+ return OK;
+}
+
+int HttpCache::Transaction::DoOverwriteCachedResponse() {
+ if (mode_ & READ) {
+ next_state_ = STATE_PARTIAL_HEADERS_RECEIVED;
+ return OK;
+ }
+
+ // We change the value of Content-Length for partial content.
+ if (server_responded_206_ && partial_.get())
+ partial_->FixContentLength(new_response_->headers);
+
+ response_ = *new_response_;
+ target_state_ = STATE_TRUNCATE_CACHED_DATA;
+ next_state_ = truncated_ ? STATE_CACHE_WRITE_TRUNCATED_RESPONSE :
+ STATE_CACHE_WRITE_RESPONSE;
+ return OK;
+}
+
+int HttpCache::Transaction::DoTruncateCachedData() {
+ next_state_ = STATE_TRUNCATE_CACHED_DATA_COMPLETE;
+ cache_callback_->AddRef(); // Balanced in DoTruncateCachedDataComplete.
+ if (!entry_)
+ return OK;
+
+ // Truncate the stream.
+ return WriteToEntry(kResponseContentIndex, 0, NULL, 0, cache_callback_);
+}
+
+int HttpCache::Transaction::DoTruncateCachedDataComplete(int result) {
+ // Balance the AddRef from DoTruncateCachedData.
+ cache_callback_->Release();
+ next_state_ = STATE_TRUNCATE_CACHED_METADATA;
+ return OK;
+}
+
+int HttpCache::Transaction::DoTruncateCachedMetadata() {
+ next_state_ = STATE_TRUNCATE_CACHED_METADATA_COMPLETE;
+ cache_callback_->AddRef(); // Balanced in DoTruncateCachedMetadataComplete.
+ if (!entry_)
+ return OK;
+
+ return WriteToEntry(kMetadataIndex, 0, NULL, 0, cache_callback_);
+}
+
+int HttpCache::Transaction::DoTruncateCachedMetadataComplete(int result) {
+ // Balance the AddRef from DoTruncateCachedMetadata.
+ cache_callback_->Release();
+
+ // If this response is a redirect, then we can stop writing now. (We don't
+ // need to cache the response body of a redirect.)
+ if (response_.headers->IsRedirect(NULL))
+ DoneWritingToEntry(true);
+ next_state_ = STATE_PARTIAL_HEADERS_RECEIVED;
+ return OK;
+}
+
+int HttpCache::Transaction::DoPartialHeadersReceived() {
+ new_response_ = NULL;
+ if (entry_ && !partial_.get() &&
+ entry_->disk_entry->GetDataSize(kMetadataIndex))
+ next_state_ = STATE_CACHE_READ_METADATA;
+
+ if (!partial_.get())
+ return OK;
+
+ if (reading_) {
+ if (network_trans_.get()) {
+ next_state_ = STATE_NETWORK_READ;
+ } else {
+ next_state_ = STATE_CACHE_READ_DATA;
+ }
+ } else if (mode_ != NONE) {
+ // We are about to return the headers for a byte-range request to the user,
+ // so let's fix them.
+ partial_->FixResponseHeaders(response_.headers);
+ }
+ return OK;
+}
+
+int HttpCache::Transaction::DoCacheReadResponse() {
+ DCHECK(entry_);
+ next_state_ = STATE_CACHE_READ_RESPONSE_COMPLETE;
+
+ io_buf_len_ = entry_->disk_entry->GetDataSize(kResponseInfoIndex);
+ read_buf_ = new IOBuffer(io_buf_len_);
+
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_CACHE_READ_INFO, NULL);
+ cache_callback_->AddRef(); // Balanced in DoCacheReadResponseComplete.
+ return entry_->disk_entry->ReadData(kResponseInfoIndex, 0, read_buf_,
+ io_buf_len_, cache_callback_);
+}
+
+int HttpCache::Transaction::DoCacheReadResponseComplete(int result) {
+ cache_callback_->Release(); // Balance the AddRef from DoCacheReadResponse.
+ net_log_.EndEvent(NetLog::TYPE_HTTP_CACHE_READ_INFO, NULL);
+ if (result != io_buf_len_ ||
+ !HttpCache::ParseResponseInfo(read_buf_->data(), io_buf_len_,
+ &response_, &truncated_)) {
+ DLOG(ERROR) << "ReadData failed: " << result;
+ return ERR_CACHE_READ_FAILURE;
+ }
+
+ // We now have access to the cache entry.
+ //
+ // o if we are a reader for the transaction, then we can start reading the
+ // cache entry.
+ //
+ // o if we can read or write, then we should check if the cache entry needs
+ // to be validated and then issue a network request if needed or just read
+ // from the cache if the cache entry is already valid.
+ //
+ // o if we are set to UPDATE, then we are handling an externally
+ // conditionalized request (if-modified-since / if-none-match). We check
+ // if the request headers define a validation request.
+ //
+ switch (mode_) {
+ case READ:
+ result = BeginCacheRead();
+ break;
+ case READ_WRITE:
+ result = BeginPartialCacheValidation();
+ break;
+ case UPDATE:
+ result = BeginExternallyConditionalizedRequest();
+ break;
+ case WRITE:
+ default:
+ NOTREACHED();
+ result = ERR_FAILED;
+ }
+ return result;
+}
+
+int HttpCache::Transaction::DoCacheWriteResponse() {
+ return WriteResponseInfoToEntry(false);
+}
+
+int HttpCache::Transaction::DoCacheWriteTruncatedResponse() {
+ return WriteResponseInfoToEntry(true);
+}
+
+int HttpCache::Transaction::DoCacheWriteResponseComplete(int result) {
+ next_state_ = target_state_;
+ target_state_ = STATE_NONE;
+ if (!entry_)
+ return OK;
+
+ // Balance the AddRef from WriteResponseInfoToEntry.
+ write_headers_callback_->Release();
+ if (result != io_buf_len_) {
+ DLOG(ERROR) << "failed to write response info to cache";
+ DoneWritingToEntry(false);
+ }
+ return OK;
+}
+
+int HttpCache::Transaction::DoCacheReadMetadata() {
+ DCHECK(entry_);
+ DCHECK(!response_.metadata);
+ next_state_ = STATE_CACHE_READ_METADATA_COMPLETE;
+
+ response_.metadata =
+ new IOBufferWithSize(entry_->disk_entry->GetDataSize(kMetadataIndex));
+
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_CACHE_READ_INFO, NULL);
+ cache_callback_->AddRef(); // Balanced in DoCacheReadMetadataComplete.
+ return entry_->disk_entry->ReadData(kMetadataIndex, 0, response_.metadata,
+ response_.metadata->size(),
+ cache_callback_);
+}
+
+int HttpCache::Transaction::DoCacheReadMetadataComplete(int result) {
+ cache_callback_->Release(); // Balance the AddRef from DoCacheReadMetadata.
+ net_log_.EndEvent(NetLog::TYPE_HTTP_CACHE_READ_INFO, NULL);
+ if (result != response_.metadata->size()) {
+ DLOG(ERROR) << "ReadData failed: " << result;
+ return ERR_CACHE_READ_FAILURE;
+ }
+
+ return OK;
+}
+
+int HttpCache::Transaction::DoCacheQueryData() {
+ next_state_ = STATE_CACHE_QUERY_DATA_COMPLETE;
+
+ // Balanced in ValidateEntryHeadersAndContinue.
+ cache_callback_->AddRef();
+ return entry_->disk_entry->ReadyForSparseIO(cache_callback_);
+}
+
+int HttpCache::Transaction::DoCacheQueryDataComplete(int result) {
+ DCHECK_EQ(OK, result);
+ // Balance the AddRef from BeginPartialCacheValidation.
+ cache_callback_->Release();
+ if (!cache_)
+ return ERR_UNEXPECTED;
+
+ return ValidateEntryHeadersAndContinue(true);
+}
+
+int HttpCache::Transaction::DoCacheReadData() {
+ DCHECK(entry_);
+ next_state_ = STATE_CACHE_READ_DATA_COMPLETE;
+ cache_callback_->AddRef(); // Balanced in DoCacheReadDataComplete.
+ if (partial_.get()) {
+ return partial_->CacheRead(entry_->disk_entry, read_buf_, io_buf_len_,
+ cache_callback_);
+ }
+
+ return entry_->disk_entry->ReadData(kResponseContentIndex, read_offset_,
+ read_buf_, io_buf_len_, cache_callback_);
+}
+
+int HttpCache::Transaction::DoCacheReadDataComplete(int result) {
+ cache_callback_->Release(); // Balance the AddRef from DoCacheReadData.
+
+ if (!cache_)
+ return ERR_UNEXPECTED;
+
+ if (partial_.get())
+ return DoPartialCacheReadCompleted(result);
+
+ if (result > 0) {
+ read_offset_ += result;
+ } else if (result == 0) { // End of file.
+ cache_->DoneReadingFromEntry(entry_, this);
+ entry_ = NULL;
+ }
+ return result;
+}
+
+int HttpCache::Transaction::DoCacheWriteData(int num_bytes) {
+ next_state_ = STATE_CACHE_WRITE_DATA_COMPLETE;
+ write_len_ = num_bytes;
+ cache_callback_->AddRef(); // Balanced in DoCacheWriteDataComplete.
+
+ return AppendResponseDataToEntry(read_buf_, num_bytes, cache_callback_);
+}
+
+int HttpCache::Transaction::DoCacheWriteDataComplete(int result) {
+ // Balance the AddRef from DoCacheWriteData.
+ cache_callback_->Release();
+ if (!cache_)
+ return ERR_UNEXPECTED;
+
+ if (result != write_len_) {
+ DLOG(ERROR) << "failed to write response data to cache";
+ DoneWritingToEntry(false);
+
+ // We want to ignore errors writing to disk and just keep reading from
+ // the network.
+ result = write_len_;
+ }
+
+ if (partial_.get()) {
+ // This may be the last request.
+ if (!(result == 0 && !truncated_ &&
+ (partial_->IsLastRange() || mode_ == WRITE)))
+ return DoPartialNetworkReadCompleted(result);
+ }
+
+ if (result == 0) // End of file.
+ DoneWritingToEntry(true);
+
+ return result;
+}
+
+//-----------------------------------------------------------------------------
+
+void HttpCache::Transaction::SetRequest(const BoundNetLog& net_log,
const HttpRequestInfo* request) {
- load_log_ = load_log;
+ net_log_ = net_log;
request_ = request;
effective_load_flags_ = request_->load_flags;
@@ -785,45 +1254,37 @@
{ kForceValidateHeaders, LOAD_VALIDATE_CACHE },
};
- std::string new_extra_headers;
bool range_found = false;
bool external_validation_error = false;
- // scan request headers to see if we have any that would impact our load flags
- HttpUtil::HeadersIterator it(request_->extra_headers.begin(),
- request_->extra_headers.end(),
- "\r\n");
- while (it.GetNext()) {
- if (!LowerCaseEqualsASCII(it.name(), "range")) {
- new_extra_headers.append(it.name_begin(), it.values_end());
- new_extra_headers.append("\r\n");
+ if (request_->extra_headers.HasHeader(HttpRequestHeaders::kRange)) {
+ if (enable_range_support_) {
+ range_found = true;
} else {
- if (enable_range_support_) {
- range_found = true;
- } else {
- effective_load_flags_ |= LOAD_DISABLE_CACHE;
- continue;
- }
+ effective_load_flags_ |= LOAD_DISABLE_CACHE;
}
- for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kSpecialHeaders); ++i) {
- if (HeaderMatches(it, kSpecialHeaders[i].search)) {
- effective_load_flags_ |= kSpecialHeaders[i].load_flag;
- break;
- }
- }
+ }
- // Check for conditionalization headers which may correspond with a
- // cache validation request.
- for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kValidationHeaders); ++i) {
- const ValidationHeaderInfo& info = kValidationHeaders[i];
- if (LowerCaseEqualsASCII(it.name_begin(), it.name_end(),
- info.request_header_name)) {
- if (!external_validation_.values[i].empty() || it.values().empty())
- external_validation_error = true;
- external_validation_.values[i] = it.values();
- external_validation_.initialized = true;
- break;
- }
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kSpecialHeaders); ++i) {
+ if (HeaderMatches(request_->extra_headers, kSpecialHeaders[i].search)) {
+ effective_load_flags_ |= kSpecialHeaders[i].load_flag;
+ break;
+ }
+ }
+
+ // Check for conditionalization headers which may correspond with a
+ // cache validation request.
+ for (size_t i = 0; i < arraysize(kValidationHeaders); ++i) {
+ const ValidationHeaderInfo& info = kValidationHeaders[i];
+ std::string validation_value;
+ if (request_->extra_headers.GetHeader(
+ info.request_header_name, &validation_value)) {
+ if (!external_validation_.values[i].empty() ||
+ validation_value.empty())
+ external_validation_error = true;
+ external_validation_.values[i] = validation_value;
+ external_validation_.initialized = true;
+ break;
}
}
@@ -847,9 +1308,9 @@
// We will be modifying the actual range requested to the server, so
// let's remove the header here.
custom_request_.reset(new HttpRequestInfo(*request_));
+ custom_request_->extra_headers.RemoveHeader(HttpRequestHeaders::kRange);
request_ = custom_request_.get();
- custom_request_->extra_headers = new_extra_headers;
- partial_->SetHeaders(new_extra_headers);
+ partial_->SetHeaders(custom_request_->extra_headers);
} else {
// The range is invalid or we cannot handle it properly.
LOG(INFO) << "Invalid byte range found.";
@@ -895,16 +1356,33 @@
if (truncated_)
return ERR_CACHE_MISS;
+ if (entry_->disk_entry->GetDataSize(kMetadataIndex))
+ next_state_ = STATE_CACHE_READ_METADATA;
+
return OK;
}
int HttpCache::Transaction::BeginCacheValidation() {
DCHECK(mode_ == READ_WRITE);
- if ((effective_load_flags_ & LOAD_PREFERRING_CACHE ||
- !RequiresValidation()) && !partial_.get()) {
+ bool skip_validation = effective_load_flags_ & LOAD_PREFERRING_CACHE ||
+ !RequiresValidation();
+
+ if (partial_.get() && !partial_->IsCurrentRangeCached())
+ skip_validation = false;
+
+ if (skip_validation) {
+ if (partial_.get()) {
+ // We are going to return the saved response headers to the caller, so
+ // we may need to adjust them first.
+ next_state_ = STATE_PARTIAL_HEADERS_RECEIVED;
+ return OK;
+ }
cache_->ConvertWriterToReader(entry_);
mode_ = READ;
+
+ if (entry_ && entry_->disk_entry->GetDataSize(kMetadataIndex))
+ next_state_ = STATE_CACHE_READ_METADATA;
} else {
// Make the network request conditional, to see if we may reuse our cached
// response. If we cannot do so, then we just resort to a normal fetch.
@@ -943,24 +1421,6 @@
return ValidateEntryHeadersAndContinue(false);
}
-int HttpCache::Transaction::DoCacheQueryData() {
- next_state_ = STATE_CACHE_QUERY_DATA_COMPLETE;
-
- // Balanced in ValidateEntryHeadersAndContinue.
- cache_callback_->AddRef();
- return entry_->disk_entry->ReadyForSparseIO(cache_callback_);
-}
-
-int HttpCache::Transaction::DoCacheQueryDataComplete(int result) {
- DCHECK_EQ(OK, result);
- // Balance the AddRef from BeginPartialCacheValidation.
- cache_callback_->Release();
- if (!cache_)
- return ERR_UNEXPECTED;
-
- return ValidateEntryHeadersAndContinue(true);
-}
-
int HttpCache::Transaction::ValidateEntryHeadersAndContinue(
bool byte_range_requested) {
DCHECK(mode_ == READ_WRITE);
@@ -981,46 +1441,15 @@
invalid_range_ = true;
}
- next_state_ = STATE_PARTIAL_CACHE_VALIDATION;
+ next_state_ = STATE_START_PARTIAL_CACHE_VALIDATION;
return OK;
}
-int HttpCache::Transaction::DoPartialCacheValidation() {
- if (mode_ == NONE)
- return OK;
-
- int rv = partial_->PrepareCacheValidation(entry_->disk_entry,
- &custom_request_->extra_headers);
-
- if (!rv) {
- // This is the end of the request.
- if (mode_ & WRITE) {
- DoneWritingToEntry(true);
- } else {
- cache_->DoneReadingFromEntry(entry_, this);
- entry_ = NULL;
- }
- return rv;
- }
-
- if (rv < 0) {
- DCHECK(rv != ERR_IO_PENDING);
- return rv;
- }
-
- if (reading_ && partial_->IsCurrentRangeCached()) {
- next_state_ = STATE_CACHE_READ_DATA;
- return OK;
- }
-
- return BeginCacheValidation();
-}
-
int HttpCache::Transaction::BeginExternallyConditionalizedRequest() {
DCHECK_EQ(UPDATE, mode_);
DCHECK(external_validation_.initialized);
- for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kValidationHeaders); i++) {
+ for (size_t i = 0; i < arraysize(kValidationHeaders); i++) {
if (external_validation_.values[i].empty())
continue;
// Retrieve either the cached response's "etag" or "last-modified" header.
@@ -1042,25 +1471,6 @@
return OK;
}
-int HttpCache::Transaction::BeginNetworkRequest() {
- next_state_ = STATE_SEND_REQUEST;
- return DoLoop(OK);
-}
-
-int HttpCache::Transaction::DoSendRequest() {
- DCHECK(mode_ & WRITE || mode_ == NONE);
- DCHECK(!network_trans_.get());
-
- // Create a network transaction.
- int rv = cache_->network_layer_->CreateTransaction(&network_trans_);
- if (rv != OK)
- return rv;
-
- next_state_ = STATE_SEND_REQUEST_COMPLETE;
- rv = network_trans_->Start(request_, &io_callback_, load_log_);
- return rv;
-}
-
int HttpCache::Transaction::RestartNetworkRequest() {
DCHECK(mode_ & WRITE || mode_ == NONE);
DCHECK(network_trans_.get());
@@ -1120,7 +1530,7 @@
// Since Vary header computation is fairly expensive, we save it for last.
if (response_.vary_data.is_valid() &&
- !response_.vary_data.MatchesRequest(*request_, *response_.headers))
+ !response_.vary_data.MatchesRequest(*request_, *response_.headers))
return true;
return false;
@@ -1163,12 +1573,12 @@
if (partial_.get() && !partial_->IsCurrentRangeCached()) {
// We don't want to switch to WRITE mode if we don't have this block of a
// byte-range request because we may have other parts cached.
- custom_request_->extra_headers.append("If-Range: ");
+ custom_request_->extra_headers.SetHeader(
+ HttpRequestHeaders::kIfRange, etag_value);
} else {
- custom_request_->extra_headers.append("If-None-Match: ");
+ custom_request_->extra_headers.SetHeader(
+ HttpRequestHeaders::kIfNoneMatch, etag_value);
}
- custom_request_->extra_headers.append(etag_value);
- custom_request_->extra_headers.append("\r\n");
// For byte-range requests, make sure that we use only one way to validate
// the request.
if (partial_.get() && !partial_->IsCurrentRangeCached())
@@ -1177,12 +1587,12 @@
if (!last_modified_value.empty()) {
if (partial_.get() && !partial_->IsCurrentRangeCached()) {
- custom_request_->extra_headers.append("If-Range: ");
+ custom_request_->extra_headers.SetHeader(
+ HttpRequestHeaders::kIfRange, last_modified_value);
} else {
- custom_request_->extra_headers.append("If-Modified-Since: ");
+ custom_request_->extra_headers.SetHeader(
+ HttpRequestHeaders::kIfModifiedSince, last_modified_value);
}
- custom_request_->extra_headers.append(last_modified_value);
- custom_request_->extra_headers.append("\r\n");
}
return true;
@@ -1303,11 +1713,6 @@
return DoLoop(OK);
}
-int HttpCache::Transaction::DoNetworkRead() {
- next_state_ = STATE_NETWORK_READ_COMPLETE;
- return network_trans_->Read(read_buf_, io_buf_len_, &io_callback_);
-}
-
int HttpCache::Transaction::ReadFromEntry(IOBuffer* data, int data_len) {
read_buf_ = data;
io_buf_len_ = data_len;
@@ -1315,32 +1720,6 @@
return DoLoop(OK);
}
-int HttpCache::Transaction::DoCacheReadData() {
- DCHECK(entry_);
- next_state_ = STATE_CACHE_READ_DATA_COMPLETE;
- cache_callback_->AddRef(); // Balanced in DoCacheReadDataComplete.
- if (partial_.get()) {
- return partial_->CacheRead(entry_->disk_entry, read_buf_, io_buf_len_,
- cache_callback_);
- }
-
- return entry_->disk_entry->ReadData(kResponseContentIndex, read_offset_,
- read_buf_, io_buf_len_, cache_callback_);
-}
-
-int HttpCache::Transaction::DoCacheReadResponse() {
- DCHECK(entry_);
- next_state_ = STATE_CACHE_READ_RESPONSE_COMPLETE;
-
- io_buf_len_ = entry_->disk_entry->GetDataSize(kResponseInfoIndex);
- read_buf_ = new IOBuffer(io_buf_len_);
-
- LoadLog::BeginEvent(load_log_, LoadLog::TYPE_HTTP_CACHE_READ_INFO);
- cache_callback_->AddRef(); // Balanced in DoCacheReadResponseComplete.
- return entry_->disk_entry->ReadData(kResponseInfoIndex, 0, read_buf_,
- io_buf_len_, cache_callback_);
-}
-
int HttpCache::Transaction::WriteToEntry(int index, int offset,
IOBuffer* data, int data_len,
CompletionCallback* callback) {
@@ -1354,26 +1733,9 @@
} else {
rv = partial_->CacheWrite(entry_->disk_entry, data, data_len, callback);
}
-
- if (rv != ERR_IO_PENDING && rv != data_len) {
- DLOG(ERROR) << "failed to write response data to cache";
- DoneWritingToEntry(false);
-
- // We want to ignore errors writing to disk and just keep reading from
- // the network.
- rv = data_len;
- }
return rv;
}
-int HttpCache::Transaction::DoCacheWriteResponse() {
- return WriteResponseInfoToEntry(false);
-}
-
-int HttpCache::Transaction::DoCacheWriteTruncatedResponse() {
- return WriteResponseInfoToEntry(true);
-}
-
int HttpCache::Transaction::WriteResponseInfoToEntry(bool truncated) {
next_state_ = STATE_CACHE_WRITE_RESPONSE_COMPLETE;
if (!entry_)
@@ -1389,7 +1751,7 @@
// errors) and no SSL blocking page is shown. An alternative would be to
// reverse-map the cert status to a net error and replay the net error.
if ((cache_->mode() != RECORD &&
- response_.headers->HasHeaderValue("cache-control", "no-store")) ||
+ response_.headers->HasHeaderValue("cache-control", "no-store")) ||
net::IsCertStatusError(response_.ssl_info.cert_status)) {
DoneWritingToEntry(false);
return OK;
@@ -1415,21 +1777,6 @@
write_headers_callback_, true);
}
-int HttpCache::Transaction::DoCacheWriteResponseComplete(int result) {
- next_state_ = target_state_;
- target_state_ = STATE_NONE;
- if (!entry_)
- return OK;
-
- // Balance the AddRef from WriteResponseInfoToEntry.
- write_headers_callback_->Release();
- if (result != io_buf_len_) {
- DLOG(ERROR) << "failed to write response info to cache";
- DoneWritingToEntry(false);
- }
- return OK;
-}
-
int HttpCache::Transaction::AppendResponseDataToEntry(
IOBuffer* data, int data_len, CompletionCallback* callback) {
if (!entry_ || !data_len)
@@ -1440,17 +1787,6 @@
callback);
}
-int HttpCache::Transaction::DoTruncateCachedData() {
- next_state_ = STATE_TRUNCATE_CACHED_DATA_COMPLETE;
- if (!entry_)
- return OK;
-
- // Truncate the stream.
- int rv = WriteToEntry(kResponseContentIndex, 0, NULL, 0, NULL);
- DCHECK(rv != ERR_IO_PENDING);
- return OK;
-}
-
void HttpCache::Transaction::DoneWritingToEntry(bool success) {
if (!entry_)
return;
@@ -1473,48 +1809,13 @@
partial_.reset(NULL);
}
-int HttpCache::Transaction::DoNetworkReadComplete(int result) {
- DCHECK(mode_ & WRITE || mode_ == NONE);
-
- if (!cache_)
- return ERR_UNEXPECTED;
-
- next_state_ = STATE_CACHE_WRITE_DATA;
- return result;
-}
-
-int HttpCache::Transaction::DoCacheWriteData(int num_bytes) {
- next_state_ = STATE_CACHE_WRITE_DATA_COMPLETE;
- cache_callback_->AddRef(); // Balanced in DoCacheWriteDataComplete.
-
- return AppendResponseDataToEntry(read_buf_, num_bytes, cache_callback_);
-}
-
int HttpCache::Transaction::DoPartialNetworkReadCompleted(int result) {
partial_->OnNetworkReadCompleted(result);
if (result == 0) {
// We need to move on to the next range.
network_trans_.reset();
- next_state_ = STATE_PARTIAL_CACHE_VALIDATION;
- }
- return result;
-}
-
-int HttpCache::Transaction::DoCacheReadDataComplete(int result) {
- cache_callback_->Release(); // Balance the AddRef from DoCacheReadData.
-
- if (!cache_)
- return ERR_UNEXPECTED;
-
- if (partial_.get())
- return DoPartialCacheReadCompleted(result);
-
- if (result > 0) {
- read_offset_ += result;
- } else if (result == 0) { // End of file.
- cache_->DoneReadingFromEntry(entry_, this);
- entry_ = NULL;
+ next_state_ = STATE_START_PARTIAL_CACHE_VALIDATION;
}
return result;
}
@@ -1524,190 +1825,7 @@
if (result == 0 && mode_ == READ_WRITE) {
// We need to move on to the next range.
- next_state_ = STATE_PARTIAL_CACHE_VALIDATION;
- }
- return result;
-}
-
-int HttpCache::Transaction::DoCacheWriteDataComplete(int result) {
- // Balance the AddRef from DoCacheWriteData.
- cache_callback_->Release();
- if (!cache_)
- return ERR_UNEXPECTED;
-
- if (result < 0)
- return result;
-
- if (partial_.get()) {
- // This may be the last request.
- if (!(result == 0 && !truncated_ &&
- (partial_->IsLastRange() || mode_ == WRITE)))
- return DoPartialNetworkReadCompleted(result);
- }
-
- if (result == 0) // End of file.
- DoneWritingToEntry(true);
-
- return result;
-}
-
-// We received the response headers and there is no error.
-int HttpCache::Transaction::DoSuccessfulSendRequest() {
- DCHECK(!new_response_);
- const HttpResponseInfo* new_response = network_trans_->GetResponseInfo();
- if (new_response->headers->response_code() == 401 ||
- new_response->headers->response_code() == 407) {
- auth_response_ = *new_response;
- return OK;
- }
-
- if (!ValidatePartialResponse(new_response->headers, &server_responded_206_) &&
- !auth_response_.headers) {
- // Something went wrong with this request and we have to restart it.
- // If we have an authentication response, we are exposed to weird things
- // hapenning if the user cancels the authentication before we receive
- // the new response.
- response_ = HttpResponseInfo();
- network_trans_.reset();
- next_state_ = STATE_SEND_REQUEST;
- return OK;
- }
- if (server_responded_206_ && mode_ == READ_WRITE && !truncated_ &&
- response_.headers->response_code() == 200) {
- // We have stored the full entry, but it changed and the server is
- // sending a range. We have to delete the old entry.
- DoneWritingToEntry(false);
- }
-
- HistogramHeaders(new_response->headers);
-
- new_response_ = new_response;
- // Are we expecting a response to a conditional query?
- if (mode_ == READ_WRITE || mode_ == UPDATE) {
- if (new_response->headers->response_code() == 304 ||
- server_responded_206_) {
- next_state_ = STATE_UPDATE_CACHED_RESPONSE;
- return OK;
- }
- mode_ = WRITE;
- }
-
- next_state_ = STATE_OVERWRITE_CACHED_RESPONSE;
- return OK;
-}
-
-// We received 304 or 206 and we want to update the cached response headers.
-int HttpCache::Transaction::DoUpdateCachedResponse() {
- next_state_ = STATE_UPDATE_CACHED_RESPONSE_COMPLETE;
- int rv = OK;
- // Update cached response based on headers in new_response.
- // TODO(wtc): should we update cached certificate (response_.ssl_info), too?
- response_.headers->Update(*new_response_->headers);
- response_.response_time = new_response_->response_time;
- response_.request_time = new_response_->request_time;
-
- if (response_.headers->HasHeaderValue("cache-control", "no-store")) {
- int ret = cache_->DoomEntry(cache_key_, NULL);
- DCHECK_EQ(OK, ret);
- } else {
- // If we are already reading, we already updated the headers for this
- // request; doing it again will change Content-Length.
- if (!reading_) {
- target_state_ = STATE_UPDATE_CACHED_RESPONSE_COMPLETE;
- next_state_ = STATE_CACHE_WRITE_RESPONSE;
- rv = OK;
- }
- }
- return rv;
-}
-
-int HttpCache::Transaction::DoUpdateCachedResponseComplete(int result) {
- if (mode_ == UPDATE) {
- DCHECK(!server_responded_206_);
- // We got a "not modified" response and already updated the corresponding
- // cache entry above.
- //
- // By closing the cached entry now, we make sure that the 304 rather than
- // the cached 200 response, is what will be returned to the user.
- DoneWritingToEntry(true);
- } else if (entry_ && !server_responded_206_) {
- DCHECK_EQ(READ_WRITE, mode_);
- if (!partial_.get() || partial_->IsLastRange()) {
- cache_->ConvertWriterToReader(entry_);
- mode_ = READ;
- }
- // We no longer need the network transaction, so destroy it.
- final_upload_progress_ = network_trans_->GetUploadProgress();
- network_trans_.reset();
- }
- next_state_ = STATE_OVERWRITE_CACHED_RESPONSE;
- return OK;
-}
-
-int HttpCache::Transaction::DoOverwriteCachedResponse() {
- if (mode_ & READ) {
- next_state_ = STATE_PARTIAL_HEADERS_RECEIVED;
- return OK;
- }
-
- // We change the value of Content-Length for partial content.
- if (server_responded_206_ && partial_.get())
- partial_->FixContentLength(new_response_->headers);
-
- response_ = *new_response_;
- target_state_ = STATE_TRUNCATE_CACHED_DATA;
- next_state_ = truncated_ ? STATE_CACHE_WRITE_TRUNCATED_RESPONSE :
- STATE_CACHE_WRITE_RESPONSE;
- return OK;
-}
-
-int HttpCache::Transaction::DoTruncateCachedDataComplete(int result) {
- // If this response is a redirect, then we can stop writing now. (We don't
- // need to cache the response body of a redirect.)
- if (response_.headers->IsRedirect(NULL))
- DoneWritingToEntry(true);
- next_state_ = STATE_PARTIAL_HEADERS_RECEIVED;
- return OK;
-}
-
-int HttpCache::Transaction::DoPartialHeadersReceived() {
- new_response_ = NULL;
- if (!partial_.get())
- return OK;
-
- if (reading_) {
- if (network_trans_.get()) {
- next_state_ = STATE_NETWORK_READ;
- } else {
- next_state_ = STATE_CACHE_READ_DATA;
- }
- } else if (mode_ != NONE) {
- // We are about to return the headers for a byte-range request to the user,
- // so let's fix them.
- partial_->FixResponseHeaders(response_.headers);
- }
- return OK;
-}
-
-int HttpCache::Transaction::DoSendRequestComplete(int result) {
- if (!cache_)
- return ERR_UNEXPECTED;
-
- if (result == OK) {
- next_state_ = STATE_SUCCESSFUL_SEND_REQUEST;
- return OK;
- }
-
- if (IsCertificateError(result)) {
- const HttpResponseInfo* response = network_trans_->GetResponseInfo();
- // If we get a certificate error, then there is a certificate in ssl_info,
- // so GetResponseInfo() should never returns NULL here.
- DCHECK(response);
- response_.ssl_info = response->ssl_info;
- } else if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
- const HttpResponseInfo* response = network_trans_->GetResponseInfo();
- DCHECK(response);
- response_.cert_request_info = response->cert_request_info;
+ next_state_ = STATE_START_PARTIAL_CACHE_VALIDATION;
}
return result;
}
diff --git a/net/http/http_cache_transaction.h b/net/http/http_cache_transaction.h
index f417378..ae143e9 100644
--- a/net/http/http_cache_transaction.h
+++ b/net/http/http_cache_transaction.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2006-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.
@@ -8,6 +8,8 @@
#ifndef NET_HTTP_HTTP_CACHE_TRANSACTION_H_
#define NET_HTTP_HTTP_CACHE_TRANSACTION_H_
+#include "net/base/net_log.h"
+#include "base/time.h"
#include "net/http/http_cache.h"
#include "net/http/http_response_info.h"
#include "net/http/http_transaction.h"
@@ -25,7 +27,8 @@
virtual ~Transaction();
// HttpTransaction methods:
- virtual int Start(const HttpRequestInfo*, CompletionCallback*, LoadLog*);
+ virtual int Start(const HttpRequestInfo*, CompletionCallback*,
+ const BoundNetLog&);
virtual int RestartIgnoringLastError(CompletionCallback* callback);
virtual int RestartWithCertificate(X509Certificate* client_cert,
CompletionCallback* callback);
@@ -34,6 +37,7 @@
CompletionCallback* callback);
virtual bool IsReadyToRestartForAuth();
virtual int Read(IOBuffer* buf, int buf_len, CompletionCallback* callback);
+ virtual void StopCaching();
virtual const HttpResponseInfo* GetResponseInfo() const;
virtual LoadState GetLoadState() const;
virtual uint64 GetUploadProgress(void) const;
@@ -71,18 +75,36 @@
const std::string& key() const { return cache_key_; }
- // Associates this transaction with a cache entry.
- int AddToEntry();
-
- // Called by the HttpCache when the given disk cache entry becomes accessible
- // to the transaction. Returns network error code.
- int EntryAvailable(ActiveEntry* entry);
+ // Writes |buf_len| bytes of meta-data from the provided buffer |buf|. to the
+ // HTTP cache entry that backs this transaction (if any).
+ // Returns the number of bytes actually written, or a net error code. If the
+ // operation cannot complete immediately, returns ERR_IO_PENDING, grabs a
+ // reference to the buffer (until completion), and notifies the caller using
+ // the provided |callback| when the operatiopn finishes.
+ //
+ // The first time this method is called for a given transaction, previous
+ // meta-data will be overwritten with the provided data, and subsequent
+ // invocations will keep appending to the cached entry.
+ //
+ // In order to guarantee that the metadata is set to the correct entry, the
+ // response (or response info) must be evaluated by the caller, for instance
+ // to make sure that the response_time is as expected, before calling this
+ // method.
+ int WriteMetadata(IOBuffer* buf, int buf_len, CompletionCallback* callback);
// This transaction is being deleted and we are not done writing to the cache.
// We need to indicate that the response data was truncated. Returns true on
// success.
bool AddTruncatedFlag();
+ // Returns the LoadState of the writer transaction of a given ActiveEntry. In
+ // other words, returns the LoadState of this transaction without asking the
+ // http cache, because this transaction should be the one currently writing
+ // to the cache entry.
+ LoadState GetWriterLoadState() const;
+
+ CompletionCallback* io_callback() { return &io_callback_; }
+
private:
static const size_t kNumValidationHeaders = 2;
// Helper struct to pair a header name with its value, for
@@ -96,6 +118,8 @@
enum State {
STATE_NONE,
+ STATE_GET_BACKEND,
+ STATE_GET_BACKEND_COMPLETE,
STATE_SEND_REQUEST,
STATE_SEND_REQUEST_COMPLETE,
STATE_SUCCESSFUL_SEND_REQUEST,
@@ -109,19 +133,24 @@
STATE_DOOM_ENTRY,
STATE_DOOM_ENTRY_COMPLETE,
STATE_ADD_TO_ENTRY,
- STATE_ENTRY_AVAILABLE,
- STATE_PARTIAL_CACHE_VALIDATION,
+ STATE_ADD_TO_ENTRY_COMPLETE,
+ STATE_START_PARTIAL_CACHE_VALIDATION,
+ STATE_COMPLETE_PARTIAL_CACHE_VALIDATION,
STATE_UPDATE_CACHED_RESPONSE,
STATE_UPDATE_CACHED_RESPONSE_COMPLETE,
STATE_OVERWRITE_CACHED_RESPONSE,
STATE_TRUNCATE_CACHED_DATA,
STATE_TRUNCATE_CACHED_DATA_COMPLETE,
+ STATE_TRUNCATE_CACHED_METADATA,
+ STATE_TRUNCATE_CACHED_METADATA_COMPLETE,
STATE_PARTIAL_HEADERS_RECEIVED,
STATE_CACHE_READ_RESPONSE,
STATE_CACHE_READ_RESPONSE_COMPLETE,
STATE_CACHE_WRITE_RESPONSE,
STATE_CACHE_WRITE_TRUNCATED_RESPONSE,
STATE_CACHE_WRITE_RESPONSE_COMPLETE,
+ STATE_CACHE_READ_METADATA,
+ STATE_CACHE_READ_METADATA_COMPLETE,
STATE_CACHE_QUERY_DATA,
STATE_CACHE_QUERY_DATA_COMPLETE,
STATE_CACHE_READ_DATA,
@@ -143,6 +172,8 @@
// Each of these methods corresponds to a State value. If there is an
// argument, the value corresponds to the return of the previous state or
// corresponding callback.
+ int DoGetBackend();
+ int DoGetBackendComplete(int result);
int DoSendRequest();
int DoSendRequestComplete(int result);
int DoSuccessfulSendRequest();
@@ -156,19 +187,24 @@
int DoDoomEntry();
int DoDoomEntryComplete(int result);
int DoAddToEntry();
- int DoEntryAvailable();
- int DoPartialCacheValidation();
+ int DoAddToEntryComplete(int result);
+ int DoStartPartialCacheValidation();
+ int DoCompletePartialCacheValidation(int result);
int DoUpdateCachedResponse();
int DoUpdateCachedResponseComplete(int result);
int DoOverwriteCachedResponse();
int DoTruncateCachedData();
int DoTruncateCachedDataComplete(int result);
+ int DoTruncateCachedMetadata();
+ int DoTruncateCachedMetadataComplete(int result);
int DoPartialHeadersReceived();
int DoCacheReadResponse();
int DoCacheReadResponseComplete(int result);
int DoCacheWriteResponse();
int DoCacheWriteTruncatedResponse();
int DoCacheWriteResponseComplete(int result);
+ int DoCacheReadMetadata();
+ int DoCacheReadMetadataComplete(int result);
int DoCacheQueryData();
int DoCacheQueryDataComplete(int result);
int DoCacheReadData();
@@ -177,7 +213,7 @@
int DoCacheWriteDataComplete(int result);
// Sets request_ and fields derived from it.
- void SetRequest(LoadLog* load_log, const HttpRequestInfo* request);
+ void SetRequest(const BoundNetLog& net_log, const HttpRequestInfo* request);
// Returns true if the request should be handled exclusively by the network
// layer (skipping the cache entirely).
@@ -203,9 +239,6 @@
// Returns a network error code.
int BeginExternallyConditionalizedRequest();
- // Called to begin a network transaction. Returns network error code.
- int BeginNetworkRequest();
-
// Called to restart a network transaction after an error. Returns network
// error code.
int RestartNetworkRequest();
@@ -271,9 +304,6 @@
// working with range requests.
int DoPartialCacheReadCompleted(int result);
- // Performs the needed work after writing data to the cache.
- int DoCacheWriteCompleted(int result);
-
// Sends a histogram with info about the response headers.
void HistogramHeaders(const HttpResponseHeaders* headers);
@@ -282,13 +312,14 @@
State next_state_;
const HttpRequestInfo* request_;
- scoped_refptr<LoadLog> load_log_;
+ BoundNetLog net_log_;
scoped_ptr<HttpRequestInfo> custom_request_;
// If extra_headers specified a "if-modified-since" or "if-none-match",
// |external_validation_| contains the value of those headers.
ValidationHeaders external_validation_;
base::WeakPtr<HttpCache> cache_;
HttpCache::ActiveEntry* entry_;
+ base::TimeTicks entry_lock_waiting_since_;
HttpCache::ActiveEntry* new_entry_;
scoped_ptr<HttpTransaction> network_trans_;
CompletionCallback* callback_; // Consumer's callback.
@@ -308,6 +339,7 @@
int io_buf_len_;
int read_offset_;
int effective_load_flags_;
+ int write_len_;
scoped_ptr<PartialData> partial_; // We are dealing with range requests.
uint64 final_upload_progress_;
CompletionCallbackImpl<Transaction> io_callback_;
diff --git a/net/http/http_cache_unittest.cc b/net/http/http_cache_unittest.cc
index 15ac1aa..8b2f7cc 100644
--- a/net/http/http_cache_unittest.cc
+++ b/net/http/http_cache_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2006-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.
@@ -11,10 +11,11 @@
#include "net/base/cache_type.h"
#include "net/base/net_errors.h"
#include "net/base/load_flags.h"
-#include "net/base/load_log_unittest.h"
+#include "net/base/net_log_unittest.h"
#include "net/base/ssl_cert_request_info.h"
#include "net/disk_cache/disk_cache.h"
#include "net/http/http_byte_range.h"
+#include "net/http/http_request_headers.h"
#include "net/http/http_request_info.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_response_info.h"
@@ -47,9 +48,23 @@
return t->test_mode;
}
+// We can override the test mode for a given operation by setting this global
+// variable. Just remember to reset it after the test!.
+int g_test_mode = 0;
+
+// Returns the test mode after considering the global override.
+int GetEffectiveTestMode(int test_mode) {
+ if (!g_test_mode)
+ return test_mode;
+
+ return g_test_mode;
+}
+
//-----------------------------------------------------------------------------
// mock disk cache (a very basic memory cache implementation)
+static const int kNumCacheEntryDataIndices = 3;
+
class MockDiskEntry : public disk_cache::Entry,
public base::RefCounted<MockDiskEntry> {
public:
@@ -89,13 +104,14 @@
}
virtual int32 GetDataSize(int index) const {
- DCHECK(index >= 0 && index < 2);
+ DCHECK(index >= 0 && index < kNumCacheEntryDataIndices);
return static_cast<int32>(data_[index].size());
}
virtual int ReadData(int index, int offset, net::IOBuffer* buf, int buf_len,
net::CompletionCallback* callback) {
- DCHECK(index >= 0 && index < 2);
+ DCHECK(index >= 0 && index < kNumCacheEntryDataIndices);
+ DCHECK(callback);
if (fail_requests_)
return net::ERR_CACHE_READ_FAILURE;
@@ -108,7 +124,7 @@
int num = std::min(buf_len, static_cast<int>(data_[index].size()) - offset);
memcpy(buf->data(), &data_[index][offset], num);
- if (!callback || (test_mode_ & TEST_MODE_SYNC_CACHE_READ))
+ if (GetEffectiveTestMode(test_mode_) & TEST_MODE_SYNC_CACHE_READ)
return num;
CallbackLater(callback, num);
@@ -117,11 +133,14 @@
virtual int WriteData(int index, int offset, net::IOBuffer* buf, int buf_len,
net::CompletionCallback* callback, bool truncate) {
- DCHECK(index >= 0 && index < 2);
+ DCHECK(index >= 0 && index < kNumCacheEntryDataIndices);
+ DCHECK(callback);
DCHECK(truncate);
- if (fail_requests_)
- return net::ERR_CACHE_READ_FAILURE;
+ if (fail_requests_) {
+ CallbackLater(callback, net::ERR_CACHE_READ_FAILURE);
+ return net::ERR_IO_PENDING;
+ }
if (offset < 0 || offset > static_cast<int>(data_[index].size()))
return net::ERR_FAILED;
@@ -130,7 +149,7 @@
if (buf_len)
memcpy(&data_[index][offset], buf->data(), buf_len);
- if (!callback || (test_mode_ & TEST_MODE_SYNC_CACHE_WRITE))
+ if (GetEffectiveTestMode(test_mode_) & TEST_MODE_SYNC_CACHE_WRITE)
return buf_len;
CallbackLater(callback, buf_len);
@@ -138,7 +157,8 @@
}
virtual int ReadSparseData(int64 offset, net::IOBuffer* buf, int buf_len,
- net::CompletionCallback* completion_callback) {
+ net::CompletionCallback* callback) {
+ DCHECK(callback);
if (!sparse_ || busy_)
return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
if (offset < 0)
@@ -156,17 +176,18 @@
buf_len);
memcpy(buf->data(), &data_[1][real_offset], num);
- if (!completion_callback || (test_mode_ & TEST_MODE_SYNC_CACHE_READ))
+ if (GetEffectiveTestMode(test_mode_) & TEST_MODE_SYNC_CACHE_READ)
return num;
- CallbackLater(completion_callback, num);
+ CallbackLater(callback, num);
busy_ = true;
delayed_ = false;
return net::ERR_IO_PENDING;
}
virtual int WriteSparseData(int64 offset, net::IOBuffer* buf, int buf_len,
- net::CompletionCallback* completion_callback) {
+ net::CompletionCallback* callback) {
+ DCHECK(callback);
if (busy_)
return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
if (!sparse_) {
@@ -189,14 +210,16 @@
data_[1].resize(real_offset + buf_len);
memcpy(&data_[1][real_offset], buf->data(), buf_len);
- if (!completion_callback || (test_mode_ & TEST_MODE_SYNC_CACHE_WRITE))
+ if (GetEffectiveTestMode(test_mode_) & TEST_MODE_SYNC_CACHE_WRITE)
return buf_len;
- CallbackLater(completion_callback, buf_len);
+ CallbackLater(callback, buf_len);
return net::ERR_IO_PENDING;
}
- virtual int GetAvailableRange(int64 offset, int len, int64* start) {
+ virtual int GetAvailableRange(int64 offset, int len, int64* start,
+ net::CompletionCallback* callback) {
+ DCHECK(callback);
if (!sparse_ || busy_)
return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
if (offset < 0)
@@ -225,12 +248,15 @@
count++;
}
}
- return count;
+ if (GetEffectiveTestMode(test_mode_) & TEST_MODE_SYNC_CACHE_WRITE)
+ return count;
+
+ CallbackLater(callback, count);
+ return net::ERR_IO_PENDING;
}
- virtual int GetAvailableRange(int64 offset, int len, int64* start,
- net::CompletionCallback* callback) {
- return net::ERR_NOT_IMPLEMENTED;
+ virtual bool CouldBeSparse() const {
+ return sparse_;
}
virtual void CancelSparseIO() { cancel_ = true; }
@@ -241,7 +267,7 @@
cancel_ = false;
DCHECK(completion_callback);
- if (test_mode_ & TEST_MODE_SYNC_CACHE_READ)
+ if (GetEffectiveTestMode(test_mode_) & TEST_MODE_SYNC_CACHE_READ)
return net::OK;
// The pending operation is already in the message loop (and hopefuly
@@ -281,8 +307,8 @@
void CallbackLater(net::CompletionCallback* callback, int result) {
if (ignore_callbacks_)
return StoreAndDeliverCallbacks(true, this, callback, result);
- MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(this,
- &MockDiskEntry::RunCallback, callback, result));
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &MockDiskEntry::RunCallback, callback, result));
}
void RunCallback(net::CompletionCallback* callback, int result) {
if (busy_) {
@@ -322,7 +348,7 @@
}
std::string key_;
- std::vector<char> data_[2];
+ std::vector<char> data_[kNumCacheEntryDataIndices];
int test_mode_;
bool doomed_;
bool sparse_;
@@ -345,22 +371,16 @@
}
~MockDiskCache() {
- EntryMap::iterator it = entries_.begin();
- for (; it != entries_.end(); ++it)
- it->second->Release();
+ ReleaseAll();
}
virtual int32 GetEntryCount() const {
return static_cast<int32>(entries_.size());
}
- virtual bool OpenEntry(const std::string& key, disk_cache::Entry** entry) {
- NOTREACHED();
- return false;
- }
-
virtual int OpenEntry(const std::string& key, disk_cache::Entry** entry,
net::CompletionCallback* callback) {
+ DCHECK(callback);
if (fail_requests_)
return net::ERR_CACHE_OPEN_FAILURE;
@@ -382,20 +402,16 @@
if (soft_failures_)
it->second->set_fail_requests();
- if (!callback || (GetTestModeForEntry(key) & TEST_MODE_SYNC_CACHE_START))
+ if (GetTestModeForEntry(key) & TEST_MODE_SYNC_CACHE_START)
return net::OK;
CallbackLater(callback, net::OK);
return net::ERR_IO_PENDING;
}
- virtual bool CreateEntry(const std::string& key, disk_cache::Entry** entry) {
- NOTREACHED();
- return false;
- }
-
virtual int CreateEntry(const std::string& key, disk_cache::Entry** entry,
net::CompletionCallback* callback) {
+ DCHECK(callback);
if (fail_requests_)
return net::ERR_CACHE_CREATE_FAILURE;
@@ -419,19 +435,16 @@
if (soft_failures_)
new_entry->set_fail_requests();
- if (!callback || (GetTestModeForEntry(key) & TEST_MODE_SYNC_CACHE_START))
+ if (GetTestModeForEntry(key) & TEST_MODE_SYNC_CACHE_START)
return net::OK;
CallbackLater(callback, net::OK);
return net::ERR_IO_PENDING;
}
- virtual bool DoomEntry(const std::string& key) {
- return false;
- }
-
virtual int DoomEntry(const std::string& key,
net::CompletionCallback* callback) {
+ DCHECK(callback);
EntryMap::iterator it = entries_.find(key);
if (it != entries_.end()) {
it->second->Release();
@@ -445,38 +458,21 @@
return net::ERR_IO_PENDING;
}
- virtual bool DoomAllEntries() {
- return false;
- }
-
virtual int DoomAllEntries(net::CompletionCallback* callback) {
return net::ERR_NOT_IMPLEMENTED;
}
- virtual bool DoomEntriesBetween(const Time initial_time,
- const Time end_time) {
- return false;
- }
-
virtual int DoomEntriesBetween(const base::Time initial_time,
const base::Time end_time,
net::CompletionCallback* callback) {
return net::ERR_NOT_IMPLEMENTED;
}
- virtual bool DoomEntriesSince(const Time initial_time) {
- return false;
- }
-
virtual int DoomEntriesSince(const base::Time initial_time,
net::CompletionCallback* callback) {
return net::ERR_NOT_IMPLEMENTED;
}
- virtual bool OpenNextEntry(void** iter, disk_cache::Entry** next_entry) {
- return false;
- }
-
virtual int OpenNextEntry(void** iter, disk_cache::Entry** next_entry,
net::CompletionCallback* callback) {
return net::ERR_NOT_IMPLEMENTED;
@@ -500,6 +496,13 @@
// Return entries that fail some of their requests.
void set_soft_failures(bool value) { soft_failures_ = value; }
+ void ReleaseAll() {
+ EntryMap::iterator it = entries_.begin();
+ for (; it != entries_.end(); ++it)
+ it->second->Release();
+ entries_.clear();
+ }
+
private:
typedef base::hash_map<std::string, MockDiskEntry*> EntryMap;
@@ -512,7 +515,7 @@
}
private:
- net::CompletionCallback* callback_;
+ net::CompletionCallback* callback_;
int result_;
DISALLOW_COPY_AND_ASSIGN(CallbackRunner);
};
@@ -529,13 +532,23 @@
bool soft_failures_;
};
+class MockBackendFactory : public net::HttpCache::BackendFactory {
+ public:
+ virtual int CreateBackend(disk_cache::Backend** backend,
+ net::CompletionCallback* callback) {
+ *backend = new MockDiskCache();
+ return net::OK;
+ }
+};
+
class MockHttpCache {
public:
- MockHttpCache() : http_cache_(new MockNetworkLayer(), new MockDiskCache()) {
+ MockHttpCache()
+ : http_cache_(new MockNetworkLayer(), new MockBackendFactory()) {
}
- explicit MockHttpCache(disk_cache::Backend* disk_cache)
- : http_cache_(new MockNetworkLayer(), disk_cache) {
+ explicit MockHttpCache(net::HttpCache::BackendFactory* disk_cache_factory)
+ : http_cache_(new MockNetworkLayer(), disk_cache_factory) {
}
net::HttpCache* http_cache() { return &http_cache_; }
@@ -544,13 +557,128 @@
return static_cast<MockNetworkLayer*>(http_cache_.network_layer());
}
MockDiskCache* disk_cache() {
- return static_cast<MockDiskCache*>(http_cache_.GetBackend());
+ TestCompletionCallback cb;
+ disk_cache::Backend* backend;
+ int rv = http_cache_.GetBackend(&backend, &cb);
+ rv = cb.GetResult(rv);
+ return (rv == net::OK) ? static_cast<MockDiskCache*>(backend) : NULL;
+ }
+
+ // Helper function for reading response info from the disk cache.
+ static bool ReadResponseInfo(disk_cache::Entry* disk_entry,
+ net::HttpResponseInfo* response_info,
+ bool* response_truncated) {
+ int size = disk_entry->GetDataSize(0);
+
+ TestCompletionCallback cb;
+ scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer(size);
+ int rv = disk_entry->ReadData(0, 0, buffer, size, &cb);
+ rv = cb.GetResult(rv);
+ EXPECT_EQ(size, rv);
+
+ return net::HttpCache::ParseResponseInfo(buffer->data(), size,
+ response_info,
+ response_truncated);
+ }
+
+ // Helper function for writing response info into the disk cache.
+ static bool WriteResponseInfo(disk_cache::Entry* disk_entry,
+ const net::HttpResponseInfo* response_info,
+ bool skip_transient_headers,
+ bool response_truncated) {
+ Pickle pickle;
+ response_info->Persist(
+ &pickle, skip_transient_headers, response_truncated);
+
+ TestCompletionCallback cb;
+ scoped_refptr<net::WrappedIOBuffer> data = new net::WrappedIOBuffer(
+ reinterpret_cast<const char*>(pickle.data()));
+ int len = static_cast<int>(pickle.size());
+
+ int rv = disk_entry->WriteData(0, 0, data, len, &cb, true);
+ rv = cb.GetResult(rv);
+ return (rv == len);
+ }
+
+ // Helper function to synchronously open a backend entry.
+ bool OpenBackendEntry(const std::string& key, disk_cache::Entry** entry) {
+ TestCompletionCallback cb;
+ int rv = disk_cache()->OpenEntry(key, entry, &cb);
+ return (cb.GetResult(rv) == net::OK);
+ }
+
+ // Helper function to synchronously create a backend entry.
+ bool CreateBackendEntry(const std::string& key, disk_cache::Entry** entry) {
+ TestCompletionCallback cb;
+ int rv = disk_cache()->CreateEntry(key, entry, &cb);
+ return (cb.GetResult(rv) == net::OK);
}
private:
net::HttpCache http_cache_;
};
+// This version of the disk cache doesn't invoke CreateEntry callbacks.
+class MockDiskCacheNoCB : public MockDiskCache {
+ virtual int CreateEntry(const std::string& key, disk_cache::Entry** entry,
+ net::CompletionCallback* callback) {
+ return net::ERR_IO_PENDING;
+ }
+};
+
+class MockBackendNoCbFactory : public net::HttpCache::BackendFactory {
+ public:
+ virtual int CreateBackend(disk_cache::Backend** backend,
+ net::CompletionCallback* callback) {
+ *backend = new MockDiskCacheNoCB();
+ return net::OK;
+ }
+};
+
+// This backend factory allows us to control the backend instantiation.
+class MockBlockingBackendFactory : public net::HttpCache::BackendFactory {
+ public:
+ MockBlockingBackendFactory()
+ : backend_(NULL), callback_(NULL), block_(true), fail_(false) {}
+
+ virtual int CreateBackend(disk_cache::Backend** backend,
+ net::CompletionCallback* callback) {
+ if (!block_) {
+ if (!fail_)
+ *backend = new MockDiskCache();
+ return Result();
+ }
+
+ backend_ = backend;
+ callback_ = callback;
+ return net::ERR_IO_PENDING;
+ }
+
+ // Completes the backend creation. Any blocked call will be notified via the
+ // provided callback.
+ void FinishCreation() {
+ block_ = false;
+ if (callback_) {
+ if (!fail_)
+ *backend_ = new MockDiskCache();
+ net::CompletionCallback* cb = callback_;
+ callback_ = NULL;
+ cb->Run(Result()); // This object can be deleted here.
+ }
+ }
+
+ void set_fail(bool fail) { fail_ = fail; }
+
+ net::CompletionCallback* callback() { return callback_; }
+
+ private:
+ int Result() { return fail_ ? net::ERR_FAILED : net::OK; }
+
+ disk_cache::Backend** backend_;
+ net::CompletionCallback* callback_;
+ bool block_;
+ bool fail_;
+};
//-----------------------------------------------------------------------------
// helpers
@@ -569,7 +697,7 @@
const MockTransaction& trans_info,
const MockHttpRequest& request,
net::HttpResponseInfo* response_info,
- net::LoadLog* load_log) {
+ const net::BoundNetLog& net_log) {
TestCompletionCallback callback;
// write to the cache
@@ -579,7 +707,7 @@
EXPECT_EQ(net::OK, rv);
ASSERT_TRUE(trans.get());
- rv = trans->Start(&request, &callback, load_log);
+ rv = trans->Start(&request, &callback, net_log);
if (rv == net::ERR_IO_PENDING)
rv = callback.WaitForResult();
ASSERT_EQ(net::OK, rv);
@@ -598,19 +726,19 @@
const MockHttpRequest& request,
net::HttpResponseInfo* response_info) {
RunTransactionTestWithRequestAndLog(cache, trans_info, request,
- response_info, NULL);
+ response_info, net::BoundNetLog());
}
void RunTransactionTestWithLog(net::HttpCache* cache,
const MockTransaction& trans_info,
- net::LoadLog* log) {
+ const net::BoundNetLog& log) {
RunTransactionTestWithRequestAndLog(
cache, trans_info, MockHttpRequest(trans_info), NULL, log);
}
void RunTransactionTest(net::HttpCache* cache,
const MockTransaction& trans_info) {
- RunTransactionTestWithLog(cache, trans_info, NULL);
+ RunTransactionTestWithLog(cache, trans_info, net::BoundNetLog());
}
void RunTransactionTestWithResponseInfo(net::HttpCache* cache,
@@ -708,20 +836,21 @@
bool RangeTransactionServer::bad_200_ = false;
// A dummy extra header that must be preserved on a given request.
-#define EXTRA_HEADER "Extra: header\r\n"
+#define EXTRA_HEADER "Extra: header"
+static const char kExtraHeaderKey[] = "Extra";
// Static.
void RangeTransactionServer::RangeHandler(const net::HttpRequestInfo* request,
std::string* response_status,
std::string* response_headers,
std::string* response_data) {
- if (request->extra_headers.empty()) {
+ if (request->extra_headers.IsEmpty()) {
response_status->assign("HTTP/1.1 416 Requested Range Not Satisfiable");
return;
}
// We want to make sure we don't delete extra headers.
- EXPECT_TRUE(request->extra_headers.find(EXTRA_HEADER) != std::string::npos);
+ EXPECT_TRUE(request->extra_headers.HasHeader(kExtraHeaderKey));
if (not_modified_) {
response_status->assign("HTTP/1.1 304 Not Modified");
@@ -729,7 +858,10 @@
}
std::vector<net::HttpByteRange> ranges;
- if (!net::HttpUtil::ParseRanges(request->extra_headers, &ranges) ||
+ std::string range_header;
+ if (!request->extra_headers.GetHeader(
+ net::HttpRequestHeaders::kRange, &range_header) ||
+ !net::HttpUtil::ParseRangeHeader(range_header, &ranges) ||
ranges.size() != 1)
return;
// We can handle this range request.
@@ -745,19 +877,11 @@
EXPECT_LT(end, 80);
- size_t if_range_header = request->extra_headers.find("If-Range");
- if (std::string::npos != if_range_header) {
- // Check that If-Range isn't specified twice.
- EXPECT_EQ(std::string::npos,
- request->extra_headers.find("If-Range", if_range_header + 1));
- }
-
std::string content_range = StringPrintf("Content-Range: bytes %d-%d/80\n",
start, end);
response_headers->append(content_range);
- if (request->extra_headers.find("If-None-Match") == std::string::npos ||
- modified_) {
+ if (!request->extra_headers.HasHeader("If-None-Match") || modified_) {
EXPECT_EQ(9, (end - start) % 10);
std::string data;
for (int block_start = start; block_start < end; block_start += 10)
@@ -801,33 +925,25 @@
0
};
-// Returns true if the response headers (|response|) match a partial content
+// Verifies the response headers (|response|) match a partial content
// response for the range starting at |start| and ending at |end|.
-bool Verify206Response(std::string response, int start, int end) {
+void Verify206Response(std::string response, int start, int end) {
std::string raw_headers(net::HttpUtil::AssembleRawHeaders(response.data(),
response.size()));
scoped_refptr<net::HttpResponseHeaders> headers =
new net::HttpResponseHeaders(raw_headers);
- if (206 != headers->response_code())
- return false;
+ ASSERT_EQ(206, headers->response_code());
int64 range_start, range_end, object_size;
- if (!headers->GetContentRange(&range_start, &range_end, &object_size))
- return false;
+ ASSERT_TRUE(
+ headers->GetContentRange(&range_start, &range_end, &object_size));
int64 content_length = headers->GetContentLength();
int length = end - start + 1;
- if (content_length != length)
- return false;
-
- if (range_start != start)
- return false;
-
- if (range_end != end)
- return false;
-
- return true;
+ ASSERT_EQ(length, content_length);
+ ASSERT_EQ(start, range_start);
+ ASSERT_EQ(end, range_end);
}
// Helper to represent a network HTTP response.
@@ -862,7 +978,6 @@
//-----------------------------------------------------------------------------
// tests
-
TEST(HttpCache, CreateThenDestroy) {
MockHttpCache cache;
@@ -873,12 +988,13 @@
}
TEST(HttpCache, GetBackend) {
- // This will initialize a cache object with NULL backend.
- MockHttpCache cache(NULL);
+ MockHttpCache cache(net::HttpCache::DefaultBackend::InMemory(0));
+ disk_cache::Backend* backend;
+ TestCompletionCallback cb;
// This will lazily initialize the backend.
- cache.http_cache()->set_type(net::MEMORY_CACHE);
- EXPECT_TRUE(cache.http_cache()->GetBackend());
+ int rv = cache.http_cache()->GetBackend(&backend, &cb);
+ EXPECT_EQ(net::OK, cb.GetResult(rv));
}
TEST(HttpCache, SimpleGET) {
@@ -897,28 +1013,47 @@
cache.disk_cache()->set_fail_requests();
- scoped_refptr<net::LoadLog> log(new net::LoadLog(net::LoadLog::kUnbounded));
+ net::CapturingBoundNetLog log(net::CapturingNetLog::kUnbounded);
// Read from the network, and don't use the cache.
- RunTransactionTestWithLog(cache.http_cache(), kSimpleGET_Transaction, log);
+ RunTransactionTestWithLog(cache.http_cache(), kSimpleGET_Transaction,
+ log.bound());
- // Check that the LoadLog was filled as expected.
+ // Check that the NetLog was filled as expected.
// (We attempted to both Open and Create entries, but both failed).
- EXPECT_EQ(4u, log->entries().size());
+ EXPECT_EQ(6u, log.entries().size());
EXPECT_TRUE(net::LogContainsBeginEvent(
- *log, 0, net::LoadLog::TYPE_HTTP_CACHE_OPEN_ENTRY));
+ log.entries(), 0, net::NetLog::TYPE_HTTP_CACHE_WAITING));
EXPECT_TRUE(net::LogContainsEndEvent(
- *log, 1, net::LoadLog::TYPE_HTTP_CACHE_OPEN_ENTRY));
+ log.entries(), 1, net::NetLog::TYPE_HTTP_CACHE_WAITING));
EXPECT_TRUE(net::LogContainsBeginEvent(
- *log, 2, net::LoadLog::TYPE_HTTP_CACHE_CREATE_ENTRY));
+ log.entries(), 2, net::NetLog::TYPE_HTTP_CACHE_OPEN_ENTRY));
EXPECT_TRUE(net::LogContainsEndEvent(
- *log, 3, net::LoadLog::TYPE_HTTP_CACHE_CREATE_ENTRY));
+ log.entries(), 3, net::NetLog::TYPE_HTTP_CACHE_OPEN_ENTRY));
+ EXPECT_TRUE(net::LogContainsBeginEvent(
+ log.entries(), 4, net::NetLog::TYPE_HTTP_CACHE_CREATE_ENTRY));
+ EXPECT_TRUE(net::LogContainsEndEvent(
+ log.entries(), 5, net::NetLog::TYPE_HTTP_CACHE_CREATE_ENTRY));
EXPECT_EQ(1, cache.network_layer()->transaction_count());
EXPECT_EQ(0, cache.disk_cache()->open_count());
EXPECT_EQ(0, cache.disk_cache()->create_count());
}
+TEST(HttpCache, SimpleGETNoDiskCache2) {
+ // This will initialize a cache object with NULL backend.
+ MockBlockingBackendFactory* factory = new MockBlockingBackendFactory();
+ factory->set_fail(true);
+ factory->FinishCreation(); // We'll complete synchronously.
+ MockHttpCache cache(factory);
+
+ // Read from the network, and don't use the cache.
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_FALSE(cache.http_cache()->GetCurrentBackend());
+}
+
TEST(HttpCache, SimpleGETWithDiskFailures) {
MockHttpCache cache;
@@ -950,7 +1085,7 @@
int rv = cache.http_cache()->CreateTransaction(&c->trans);
EXPECT_EQ(net::OK, rv);
- rv = c->trans->Start(&request, &c->callback, NULL);
+ rv = c->trans->Start(&request, &c->callback, net::BoundNetLog());
EXPECT_EQ(net::ERR_IO_PENDING, rv);
rv = c->callback.WaitForResult();
@@ -959,8 +1094,7 @@
// We have to open the entry again to propagate the failure flag.
disk_cache::Entry* en;
- ASSERT_EQ(net::OK, cache.disk_cache()->OpenEntry(kSimpleGET_Transaction.url,
- &en, NULL));
+ ASSERT_TRUE(cache.OpenBackendEntry(kSimpleGET_Transaction.url, &en));
en->Close();
ReadAndVerifyTransaction(c->trans.get(), kSimpleGET_Transaction);
@@ -981,48 +1115,57 @@
TEST(HttpCache, SimpleGET_LoadOnlyFromCache_Hit) {
MockHttpCache cache;
- scoped_refptr<net::LoadLog> log(new net::LoadLog(net::LoadLog::kUnbounded));
+ net::CapturingBoundNetLog log(net::CapturingNetLog::kUnbounded);
// write to the cache
- RunTransactionTestWithLog(cache.http_cache(), kSimpleGET_Transaction, log);
+ RunTransactionTestWithLog(cache.http_cache(), kSimpleGET_Transaction,
+ log.bound());
- // Check that the LoadLog was filled as expected.
- EXPECT_EQ(6u, log->entries().size());
+ // Check that the NetLog was filled as expected.
+ EXPECT_EQ(8u, log.entries().size());
EXPECT_TRUE(net::LogContainsBeginEvent(
- *log, 0, net::LoadLog::TYPE_HTTP_CACHE_OPEN_ENTRY));
+ log.entries(), 0, net::NetLog::TYPE_HTTP_CACHE_WAITING));
EXPECT_TRUE(net::LogContainsEndEvent(
- *log, 1, net::LoadLog::TYPE_HTTP_CACHE_OPEN_ENTRY));
+ log.entries(), 1, net::NetLog::TYPE_HTTP_CACHE_WAITING));
EXPECT_TRUE(net::LogContainsBeginEvent(
- *log, 2, net::LoadLog::TYPE_HTTP_CACHE_CREATE_ENTRY));
+ log.entries(), 2, net::NetLog::TYPE_HTTP_CACHE_OPEN_ENTRY));
EXPECT_TRUE(net::LogContainsEndEvent(
- *log, 3, net::LoadLog::TYPE_HTTP_CACHE_CREATE_ENTRY));
+ log.entries(), 3, net::NetLog::TYPE_HTTP_CACHE_OPEN_ENTRY));
EXPECT_TRUE(net::LogContainsBeginEvent(
- *log, 4, net::LoadLog::TYPE_HTTP_CACHE_WAITING));
+ log.entries(), 4, net::NetLog::TYPE_HTTP_CACHE_CREATE_ENTRY));
EXPECT_TRUE(net::LogContainsEndEvent(
- *log, 5, net::LoadLog::TYPE_HTTP_CACHE_WAITING));
+ log.entries(), 5, net::NetLog::TYPE_HTTP_CACHE_CREATE_ENTRY));
+ EXPECT_TRUE(net::LogContainsBeginEvent(
+ log.entries(), 6, net::NetLog::TYPE_HTTP_CACHE_WAITING));
+ EXPECT_TRUE(net::LogContainsEndEvent(
+ log.entries(), 7, net::NetLog::TYPE_HTTP_CACHE_WAITING));
// force this transaction to read from the cache
MockTransaction transaction(kSimpleGET_Transaction);
transaction.load_flags |= net::LOAD_ONLY_FROM_CACHE;
- log = new net::LoadLog(net::LoadLog::kUnbounded);
+ log.Clear();
- RunTransactionTestWithLog(cache.http_cache(), transaction, log);
+ RunTransactionTestWithLog(cache.http_cache(), transaction, log.bound());
- // Check that the LoadLog was filled as expected.
- EXPECT_EQ(6u, log->entries().size());
+ // Check that the NetLog was filled as expected.
+ EXPECT_EQ(8u, log.entries().size());
EXPECT_TRUE(net::LogContainsBeginEvent(
- *log, 0, net::LoadLog::TYPE_HTTP_CACHE_OPEN_ENTRY));
+ log.entries(), 0, net::NetLog::TYPE_HTTP_CACHE_WAITING));
EXPECT_TRUE(net::LogContainsEndEvent(
- *log, 1, net::LoadLog::TYPE_HTTP_CACHE_OPEN_ENTRY));
+ log.entries(), 1, net::NetLog::TYPE_HTTP_CACHE_WAITING));
EXPECT_TRUE(net::LogContainsBeginEvent(
- *log, 2, net::LoadLog::TYPE_HTTP_CACHE_WAITING));
+ log.entries(), 2, net::NetLog::TYPE_HTTP_CACHE_OPEN_ENTRY));
EXPECT_TRUE(net::LogContainsEndEvent(
- *log, 3, net::LoadLog::TYPE_HTTP_CACHE_WAITING));
+ log.entries(), 3, net::NetLog::TYPE_HTTP_CACHE_OPEN_ENTRY));
EXPECT_TRUE(net::LogContainsBeginEvent(
- *log, 4, net::LoadLog::TYPE_HTTP_CACHE_READ_INFO));
+ log.entries(), 4, net::NetLog::TYPE_HTTP_CACHE_WAITING));
EXPECT_TRUE(net::LogContainsEndEvent(
- *log, 5, net::LoadLog::TYPE_HTTP_CACHE_READ_INFO));
+ log.entries(), 5, net::NetLog::TYPE_HTTP_CACHE_WAITING));
+ EXPECT_TRUE(net::LogContainsBeginEvent(
+ log.entries(), 6, net::NetLog::TYPE_HTTP_CACHE_READ_INFO));
+ EXPECT_TRUE(net::LogContainsEndEvent(
+ log.entries(), 7, net::NetLog::TYPE_HTTP_CACHE_READ_INFO));
EXPECT_EQ(1, cache.network_layer()->transaction_count());
EXPECT_EQ(1, cache.disk_cache()->open_count());
@@ -1044,7 +1187,7 @@
EXPECT_EQ(net::OK, rv);
ASSERT_TRUE(trans.get());
- rv = trans->Start(&request, &callback, NULL);
+ rv = trans->Start(&request, &callback, net::BoundNetLog());
if (rv == net::ERR_IO_PENDING)
rv = callback.WaitForResult();
ASSERT_EQ(net::ERR_CACHE_MISS, rv);
@@ -1090,14 +1233,35 @@
TEST(HttpCache, SimpleGET_LoadBypassCache) {
MockHttpCache cache;
- // write to the cache
+ // Write to the cache.
RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
- // force this transaction to write to the cache again
+ // Force this transaction to write to the cache again.
MockTransaction transaction(kSimpleGET_Transaction);
transaction.load_flags |= net::LOAD_BYPASS_CACHE;
- RunTransactionTest(cache.http_cache(), transaction);
+ net::CapturingBoundNetLog log(net::CapturingNetLog::kUnbounded);
+
+ RunTransactionTestWithLog(cache.http_cache(), transaction, log.bound());
+
+ // Check that the NetLog was filled as expected.
+ EXPECT_EQ(8u, log.entries().size());
+ EXPECT_TRUE(net::LogContainsBeginEvent(
+ log.entries(), 0, net::NetLog::TYPE_HTTP_CACHE_WAITING));
+ EXPECT_TRUE(net::LogContainsEndEvent(
+ log.entries(), 1, net::NetLog::TYPE_HTTP_CACHE_WAITING));
+ EXPECT_TRUE(net::LogContainsBeginEvent(
+ log.entries(), 2, net::NetLog::TYPE_HTTP_CACHE_DOOM_ENTRY));
+ EXPECT_TRUE(net::LogContainsEndEvent(
+ log.entries(), 3, net::NetLog::TYPE_HTTP_CACHE_DOOM_ENTRY));
+ EXPECT_TRUE(net::LogContainsBeginEvent(
+ log.entries(), 4, net::NetLog::TYPE_HTTP_CACHE_CREATE_ENTRY));
+ EXPECT_TRUE(net::LogContainsEndEvent(
+ log.entries(), 5, net::NetLog::TYPE_HTTP_CACHE_CREATE_ENTRY));
+ EXPECT_TRUE(net::LogContainsBeginEvent(
+ log.entries(), 6, net::NetLog::TYPE_HTTP_CACHE_WAITING));
+ EXPECT_TRUE(net::LogContainsEndEvent(
+ log.entries(), 7, net::NetLog::TYPE_HTTP_CACHE_WAITING));
EXPECT_EQ(2, cache.network_layer()->transaction_count());
EXPECT_EQ(0, cache.disk_cache()->open_count());
@@ -1183,7 +1347,7 @@
std::string* response_status,
std::string* response_headers,
std::string* response_data) {
- EXPECT_TRUE(request->extra_headers.find(EXTRA_HEADER) != std::string::npos);
+ EXPECT_TRUE(request->extra_headers.HasHeader(kExtraHeaderKey));
}
// Tests that we don't remove extra headers for simple requests.
@@ -1215,7 +1379,7 @@
MockTransaction transaction(kETagGET_Transaction);
transaction.handler = PreserveRequestHeaders_Handler;
- transaction.request_headers = "If-None-Match: \"foopy\"\n"
+ transaction.request_headers = "If-None-Match: \"foopy\"\r\n"
EXTRA_HEADER;
AddMockTransaction(&transaction);
@@ -1241,8 +1405,15 @@
c->result = cache.http_cache()->CreateTransaction(&c->trans);
EXPECT_EQ(net::OK, c->result);
+ EXPECT_EQ(net::LOAD_STATE_IDLE, c->trans->GetLoadState());
- c->result = c->trans->Start(&request, &c->callback, NULL);
+ c->result = c->trans->Start(&request, &c->callback, net::BoundNetLog());
+ }
+
+ // All requests are waiting for the active entry.
+ for (int i = 0; i < kNumTransactions; ++i) {
+ Context* c = context_list[i];
+ EXPECT_EQ(net::LOAD_STATE_WAITING_FOR_CACHE, c->trans->GetLoadState());
}
// Allow all requests to move from the Create queue to the active entry.
@@ -1255,6 +1426,13 @@
EXPECT_EQ(0, cache.disk_cache()->open_count());
EXPECT_EQ(1, cache.disk_cache()->create_count());
+ // All requests depend on the writer, and the writer is between Start and
+ // Read, i.e. idle.
+ for (int i = 0; i < kNumTransactions; ++i) {
+ Context* c = context_list[i];
+ EXPECT_EQ(net::LOAD_STATE_IDLE, c->trans->GetLoadState());
+ }
+
for (int i = 0; i < kNumTransactions; ++i) {
Context* c = context_list[i];
if (c->result == net::ERR_IO_PENDING)
@@ -1299,7 +1477,7 @@
if (i == 1 || i == 2)
this_request = &reader_request;
- c->result = c->trans->Start(this_request, &c->callback, NULL);
+ c->result = c->trans->Start(this_request, &c->callback, net::BoundNetLog());
}
// Allow all requests to move from the Create queue to the active entry.
@@ -1319,6 +1497,11 @@
// Now we have 2 active readers and two queued transactions.
+ EXPECT_EQ(net::LOAD_STATE_IDLE,
+ context_list[2]->trans->GetLoadState());
+ EXPECT_EQ(net::LOAD_STATE_WAITING_FOR_CACHE,
+ context_list[3]->trans->GetLoadState());
+
c = context_list[1];
ASSERT_EQ(net::ERR_IO_PENDING, c->result);
c->result = c->callback.WaitForResult();
@@ -1358,9 +1541,7 @@
// See http://code.google.com/p/chromium/issues/detail?id=25588
TEST(HttpCache, SimpleGET_DoomWithPending) {
// We need simultaneous doomed / not_doomed entries so let's use a real cache.
- disk_cache::Backend* disk_cache =
- disk_cache::CreateInMemoryCacheBackend(1024 * 1024);
- MockHttpCache cache(disk_cache);
+ MockHttpCache cache(net::HttpCache::DefaultBackend::InMemory(1024 * 1024));
MockHttpRequest request(kSimpleGET_Transaction);
MockHttpRequest writer_request(kSimpleGET_Transaction);
@@ -1380,7 +1561,7 @@
if (i == 3)
this_request = &writer_request;
- c->result = c->trans->Start(this_request, &c->callback, NULL);
+ c->result = c->trans->Start(this_request, &c->callback, net::BoundNetLog());
}
// The first request should be a writer at this point, and the two subsequent
@@ -1423,7 +1604,7 @@
c->result = cache.http_cache()->CreateTransaction(&c->trans);
EXPECT_EQ(net::OK, c->result);
- c->result = c->trans->Start(&request, &c->callback, NULL);
+ c->result = c->trans->Start(&request, &c->callback, net::BoundNetLog());
}
// Allow all requests to move from the Create queue to the active entry.
@@ -1469,7 +1650,7 @@
c->result = cache.http_cache()->CreateTransaction(&c->trans);
EXPECT_EQ(net::OK, c->result);
- c->result = c->trans->Start(&request, &c->callback, NULL);
+ c->result = c->trans->Start(&request, &c->callback, net::BoundNetLog());
}
// Allow all requests to move from the Create queue to the active entry.
@@ -1528,7 +1709,7 @@
c->result = cache.http_cache()->CreateTransaction(&c->trans);
EXPECT_EQ(net::OK, c->result);
- c->result = c->trans->Start(&request, &c->callback, NULL);
+ c->result = c->trans->Start(&request, &c->callback, net::BoundNetLog());
}
// The first request should be creating the disk cache entry and the others
@@ -1567,6 +1748,29 @@
}
}
+// Tests that we can cancel a single request to open a disk cache entry.
+TEST(HttpCache, SimpleGET_CancelCreate) {
+ MockHttpCache cache;
+
+ MockHttpRequest request(kSimpleGET_Transaction);
+
+ Context* c = new Context();
+
+ c->result = cache.http_cache()->CreateTransaction(&c->trans);
+ EXPECT_EQ(net::OK, c->result);
+
+ c->result = c->trans->Start(&request, &c->callback, net::BoundNetLog());
+ EXPECT_EQ(net::ERR_IO_PENDING, c->result);
+
+ // Release the reference that the mock disk cache keeps for this entry, so
+ // that we test that the http cache handles the cancelation correctly.
+ cache.disk_cache()->ReleaseAll();
+ delete c;
+
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+}
+
// Tests that we delete/create entries even if multiple requests are queued.
TEST(HttpCache, SimpleGET_ManyWriters_BypassCache) {
MockHttpCache cache;
@@ -1584,7 +1788,7 @@
c->result = cache.http_cache()->CreateTransaction(&c->trans);
EXPECT_EQ(net::OK, c->result);
- c->result = c->trans->Start(&request, &c->callback, NULL);
+ c->result = c->trans->Start(&request, &c->callback, net::BoundNetLog());
}
// The first request should be deleting the disk cache entry and the others
@@ -1624,7 +1828,7 @@
scoped_ptr<net::HttpTransaction> trans;
int rv = cache.http_cache()->CreateTransaction(&trans);
EXPECT_EQ(net::OK, rv);
- rv = trans->Start(&request, &callback, NULL);
+ rv = trans->Start(&request, &callback, net::BoundNetLog());
if (rv == net::ERR_IO_PENDING)
rv = callback.WaitForResult();
ASSERT_EQ(net::OK, rv);
@@ -1642,6 +1846,172 @@
MessageLoop::current()->RunAllPending();
}
+// Tests that we can delete the HttpCache and deal with queued transactions
+// ("waiting for the backend" as opposed to Active or Doomed entries).
+TEST(HttpCache, SimpleGET_ManyWriters_DeleteCache) {
+ scoped_ptr<MockHttpCache> cache(new MockHttpCache(
+ new MockBackendNoCbFactory()));
+
+ MockHttpRequest request(kSimpleGET_Transaction);
+
+ std::vector<Context*> context_list;
+ const int kNumTransactions = 5;
+
+ for (int i = 0; i < kNumTransactions; i++) {
+ context_list.push_back(new Context());
+ Context* c = context_list[i];
+
+ c->result = cache->http_cache()->CreateTransaction(&c->trans);
+ EXPECT_EQ(net::OK, c->result);
+
+ c->result = c->trans->Start(&request, &c->callback, net::BoundNetLog());
+ }
+
+ // The first request should be creating the disk cache entry and the others
+ // should be pending.
+
+ EXPECT_EQ(0, cache->network_layer()->transaction_count());
+ EXPECT_EQ(0, cache->disk_cache()->open_count());
+ EXPECT_EQ(0, cache->disk_cache()->create_count());
+
+ cache.reset();
+
+ // There is not much to do with the transactions at this point... they are
+ // waiting for a callback that will not fire.
+ for (int i = 0; i < kNumTransactions; ++i) {
+ delete context_list[i];
+ }
+}
+
+// Tests that we queue requests when initializing the backend.
+TEST(HttpCache, SimpleGET_WaitForBackend) {
+ MockBlockingBackendFactory* factory = new MockBlockingBackendFactory();
+ MockHttpCache cache(factory);
+
+ MockHttpRequest request0(kSimpleGET_Transaction);
+ MockHttpRequest request1(kTypicalGET_Transaction);
+ MockHttpRequest request2(kETagGET_Transaction);
+
+ std::vector<Context*> context_list;
+ const int kNumTransactions = 3;
+
+ for (int i = 0; i < kNumTransactions; i++) {
+ context_list.push_back(new Context());
+ Context* c = context_list[i];
+
+ c->result = cache.http_cache()->CreateTransaction(&c->trans);
+ EXPECT_EQ(net::OK, c->result);
+ }
+
+ context_list[0]->result = context_list[0]->trans->Start(
+ &request0, &context_list[0]->callback, net::BoundNetLog());
+ context_list[1]->result = context_list[1]->trans->Start(
+ &request1, &context_list[1]->callback, net::BoundNetLog());
+ context_list[2]->result = context_list[2]->trans->Start(
+ &request2, &context_list[2]->callback, net::BoundNetLog());
+
+ // Just to make sure that everything is still pending.
+ MessageLoop::current()->RunAllPending();
+
+ // The first request should be creating the disk cache.
+ EXPECT_FALSE(context_list[0]->callback.have_result());
+
+ factory->FinishCreation();
+
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(3, cache.network_layer()->transaction_count());
+ EXPECT_EQ(3, cache.disk_cache()->create_count());
+
+ for (int i = 0; i < kNumTransactions; ++i) {
+ EXPECT_TRUE(context_list[i]->callback.have_result());
+ delete context_list[i];
+ }
+}
+
+// Tests that we can cancel requests that are queued waiting for the backend
+// to be initialized.
+TEST(HttpCache, SimpleGET_WaitForBackend_CancelCreate) {
+ MockBlockingBackendFactory* factory = new MockBlockingBackendFactory();
+ MockHttpCache cache(factory);
+
+ MockHttpRequest request0(kSimpleGET_Transaction);
+ MockHttpRequest request1(kTypicalGET_Transaction);
+ MockHttpRequest request2(kETagGET_Transaction);
+
+ std::vector<Context*> context_list;
+ const int kNumTransactions = 3;
+
+ for (int i = 0; i < kNumTransactions; i++) {
+ context_list.push_back(new Context());
+ Context* c = context_list[i];
+
+ c->result = cache.http_cache()->CreateTransaction(&c->trans);
+ EXPECT_EQ(net::OK, c->result);
+ }
+
+ context_list[0]->result = context_list[0]->trans->Start(
+ &request0, &context_list[0]->callback, net::BoundNetLog());
+ context_list[1]->result = context_list[1]->trans->Start(
+ &request1, &context_list[1]->callback, net::BoundNetLog());
+ context_list[2]->result = context_list[2]->trans->Start(
+ &request2, &context_list[2]->callback, net::BoundNetLog());
+
+ // Just to make sure that everything is still pending.
+ MessageLoop::current()->RunAllPending();
+
+ // The first request should be creating the disk cache.
+ EXPECT_FALSE(context_list[0]->callback.have_result());
+
+ // Cancel a request from the pending queue.
+ delete context_list[1];
+ context_list[1] = NULL;
+
+ // Cancel the request that is creating the entry.
+ delete context_list[0];
+ context_list[0] = NULL;
+
+ // Complete the last transaction.
+ factory->FinishCreation();
+
+ context_list[2]->result =
+ context_list[2]->callback.GetResult(context_list[2]->result);
+ ReadAndVerifyTransaction(context_list[2]->trans.get(), kETagGET_Transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ delete context_list[2];
+}
+
+// Tests that we can delete the cache while creating the backend.
+TEST(HttpCache, DeleteCacheWaitingForBackend) {
+ MockBlockingBackendFactory* factory = new MockBlockingBackendFactory();
+ scoped_ptr<MockHttpCache> cache(new MockHttpCache(factory));
+
+ MockHttpRequest request(kSimpleGET_Transaction);
+
+ scoped_ptr<Context> c(new Context());
+ c->result = cache->http_cache()->CreateTransaction(&c->trans);
+ EXPECT_EQ(net::OK, c->result);
+
+ c->trans->Start(&request, &c->callback, net::BoundNetLog());
+
+ // Just to make sure that everything is still pending.
+ MessageLoop::current()->RunAllPending();
+
+ // The request should be creating the disk cache.
+ EXPECT_FALSE(c->callback.have_result());
+
+ // We cannot call FinishCreation because the factory itself will go away with
+ // the cache, so grab the callback and attempt to use it.
+ net::CompletionCallback* callback = factory->callback();
+
+ cache.reset();
+ MessageLoop::current()->RunAllPending();
+
+ callback->Run(net::ERR_ABORTED);
+}
+
TEST(HttpCache, TypicalGET_ConditionalRequest) {
MockHttpCache cache;
@@ -1666,8 +2036,8 @@
std::string* response_status,
std::string* response_headers,
std::string* response_data) {
- EXPECT_TRUE(request->extra_headers.find("If-None-Match") !=
- std::string::npos);
+ EXPECT_TRUE(
+ request->extra_headers.HasHeader(net::HttpRequestHeaders::kIfNoneMatch));
response_status->assign("HTTP/1.1 304 Not Modified");
response_headers->assign(kETagGET_Transaction.response_headers);
response_data->clear();
@@ -1701,8 +2071,8 @@
std::string* response_status,
std::string* response_headers,
std::string* response_data) {
- EXPECT_TRUE(request->extra_headers.find("If-None-Match") !=
- std::string::npos);
+ EXPECT_TRUE(
+ request->extra_headers.HasHeader(net::HttpRequestHeaders::kIfNoneMatch));
response_status->assign("HTTP/1.1 304 Not Modified");
response_headers->assign("Cache-Control: no-store\n");
response_data->clear();
@@ -1967,7 +2337,7 @@
};
const char* kExtraRequestHeaders =
- "If-Modified-Since: Wed, 06 Feb 2008 22:38:21 GMT\n";
+ "If-Modified-Since: Wed, 06 Feb 2008 22:38:21 GMT";
// We will control the network layer's responses for |kUrl| using
// |mock_network_response|.
@@ -2011,7 +2381,7 @@
};
const char* kExtraRequestHeaders =
- "If-Modified-Since: Wed, 06 Feb 2008 22:38:21 GMT\n";
+ "If-Modified-Since: Wed, 06 Feb 2008 22:38:21 GMT";
// We will control the network layer's responses for |kUrl| using
// |mock_network_response|.
@@ -2119,8 +2489,8 @@
};
const char* kExtraRequestHeaders =
- "If-Modified-Since: Wed, 06 Feb 2008 22:38:21 GMT\n"
- "If-None-Match: \"Foo1\"\n";
+ "If-Modified-Since: Wed, 06 Feb 2008 22:38:21 GMT\r\n"
+ "If-None-Match: \"Foo1\"\r\n";
ConditionalizedRequestUpdatesCacheHelper(
kNetResponse1, kNetResponse2, kNetResponse2, kExtraRequestHeaders);
@@ -2148,8 +2518,8 @@
// The etag doesn't match what we have stored.
const char* kExtraRequestHeaders =
- "If-Modified-Since: Wed, 06 Feb 2008 22:38:21 GMT\n"
- "If-None-Match: \"Foo2\"\n";
+ "If-Modified-Since: Wed, 06 Feb 2008 22:38:21 GMT\n"
+ "If-None-Match: \"Foo2\"\n";
ConditionalizedRequestUpdatesCacheHelper(
kNetResponse1, kNetResponse2, kNetResponse1, kExtraRequestHeaders);
@@ -2177,8 +2547,8 @@
// The modification date doesn't match what we have stored.
const char* kExtraRequestHeaders =
- "If-Modified-Since: Fri, 08 Feb 2008 22:38:21 GMT\n"
- "If-None-Match: \"Foo1\"\n";
+ "If-Modified-Since: Fri, 08 Feb 2008 22:38:21 GMT\n"
+ "If-None-Match: \"Foo1\"\n";
ConditionalizedRequestUpdatesCacheHelper(
kNetResponse1, kNetResponse2, kNetResponse1, kExtraRequestHeaders);
@@ -2206,8 +2576,8 @@
// Two dates, the second matches what we have stored.
const char* kExtraRequestHeaders =
- "If-Modified-Since: Mon, 04 Feb 2008 22:38:21 GMT\n"
- "If-Modified-Since: Wed, 06 Feb 2008 22:38:21 GMT\n";
+ "If-Modified-Since: Mon, 04 Feb 2008 22:38:21 GMT\n"
+ "If-Modified-Since: Wed, 06 Feb 2008 22:38:21 GMT\n";
ConditionalizedRequestUpdatesCacheHelper(
kNetResponse1, kNetResponse2, kNetResponse1, kExtraRequestHeaders);
@@ -2255,7 +2625,7 @@
EXPECT_EQ(net::OK, rv);
ASSERT_TRUE(trans.get());
- rv = trans->Start(&request, &callback, NULL);
+ rv = trans->Start(&request, &callback, net::BoundNetLog());
if (rv == net::ERR_IO_PENDING)
rv = callback.WaitForResult();
ASSERT_EQ(net::ERR_CACHE_MISS, rv);
@@ -2334,9 +2704,9 @@
cache.http_cache()->set_enable_range_support(true);
MockTransaction transaction(kRangeGET_Transaction);
- transaction.request_headers = "If-None-Match: foo\n"
+ transaction.request_headers = "If-None-Match: foo\r\n"
EXTRA_HEADER
- "Range: bytes = 40-49\n";
+ "\r\nRange: bytes = 40-49";
RunTransactionTest(cache.http_cache(), transaction);
EXPECT_EQ(1, cache.network_layer()->transaction_count());
@@ -2344,18 +2714,18 @@
EXPECT_EQ(0, cache.disk_cache()->create_count());
transaction.request_headers =
- "If-Modified-Since: Wed, 28 Nov 2007 00:45:20 GMT\n"
+ "If-Modified-Since: Wed, 28 Nov 2007 00:45:20 GMT\r\n"
EXTRA_HEADER
- "Range: bytes = 40-49\n";
+ "\r\nRange: bytes = 40-49";
RunTransactionTest(cache.http_cache(), transaction);
EXPECT_EQ(2, cache.network_layer()->transaction_count());
EXPECT_EQ(0, cache.disk_cache()->open_count());
EXPECT_EQ(0, cache.disk_cache()->create_count());
- transaction.request_headers = "If-Range: bla\n"
+ transaction.request_headers = "If-Range: bla\r\n"
EXTRA_HEADER
- "Range: bytes = 40-49\n";
+ "\r\nRange: bytes = 40-49\n";
RunTransactionTest(cache.http_cache(), transaction);
EXPECT_EQ(3, cache.network_layer()->transaction_count());
@@ -2400,7 +2770,7 @@
RunTransactionTestWithResponse(cache.http_cache(), kRangeGET_TransactionOK,
&headers);
- EXPECT_TRUE(Verify206Response(headers, 40, 49));
+ Verify206Response(headers, 40, 49);
EXPECT_EQ(1, cache.network_layer()->transaction_count());
EXPECT_EQ(0, cache.disk_cache()->open_count());
EXPECT_EQ(1, cache.disk_cache()->create_count());
@@ -2409,8 +2779,8 @@
RunTransactionTestWithResponse(cache.http_cache(), kRangeGET_TransactionOK,
&headers);
- EXPECT_TRUE(Verify206Response(headers, 40, 49));
- EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ Verify206Response(headers, 40, 49);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
EXPECT_EQ(1, cache.disk_cache()->open_count());
EXPECT_EQ(1, cache.disk_cache()->create_count());
@@ -2423,8 +2793,8 @@
transaction.data = "rg: 30-39 ";
RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
- EXPECT_TRUE(Verify206Response(headers, 30, 39));
- EXPECT_EQ(3, cache.network_layer()->transaction_count());
+ Verify206Response(headers, 30, 39);
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
EXPECT_EQ(2, cache.disk_cache()->open_count());
EXPECT_EQ(1, cache.disk_cache()->create_count());
@@ -2436,8 +2806,8 @@
transaction.data = "rg: 20-29 rg: 30-39 rg: 40-49 rg: 50-59 ";
RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
- EXPECT_TRUE(Verify206Response(headers, 20, 59));
- EXPECT_EQ(5, cache.network_layer()->transaction_count());
+ Verify206Response(headers, 20, 59);
+ EXPECT_EQ(4, cache.network_layer()->transaction_count());
EXPECT_EQ(3, cache.disk_cache()->open_count());
EXPECT_EQ(1, cache.disk_cache()->create_count());
@@ -2458,7 +2828,7 @@
std::string headers;
RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
- EXPECT_TRUE(Verify206Response(headers, 40, 49));
+ Verify206Response(headers, 40, 49);
EXPECT_EQ(1, cache.network_layer()->transaction_count());
EXPECT_EQ(0, cache.disk_cache()->open_count());
EXPECT_EQ(1, cache.disk_cache()->create_count());
@@ -2466,8 +2836,8 @@
// Read from the cache (40-49).
RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
- EXPECT_TRUE(Verify206Response(headers, 40, 49));
- EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ Verify206Response(headers, 40, 49);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
EXPECT_EQ(0, cache.disk_cache()->open_count());
EXPECT_EQ(1, cache.disk_cache()->create_count());
@@ -2479,8 +2849,8 @@
transaction.data = "rg: 30-39 ";
RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
- EXPECT_TRUE(Verify206Response(headers, 30, 39));
- EXPECT_EQ(3, cache.network_layer()->transaction_count());
+ Verify206Response(headers, 30, 39);
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
EXPECT_EQ(1, cache.disk_cache()->open_count());
EXPECT_EQ(1, cache.disk_cache()->create_count());
@@ -2492,14 +2862,89 @@
transaction.data = "rg: 20-29 rg: 30-39 rg: 40-49 rg: 50-59 ";
RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
- EXPECT_TRUE(Verify206Response(headers, 20, 59));
- EXPECT_EQ(5, cache.network_layer()->transaction_count());
+ Verify206Response(headers, 20, 59);
+ EXPECT_EQ(4, cache.network_layer()->transaction_count());
EXPECT_EQ(2, cache.disk_cache()->open_count());
EXPECT_EQ(1, cache.disk_cache()->create_count());
RemoveMockTransaction(&transaction);
}
+// Tests that we don't revalidate an entry unless we are required to do so.
+TEST(HttpCache, RangeGET_Revalidate1) {
+ MockHttpCache cache;
+ cache.http_cache()->set_enable_range_support(true);
+ std::string headers;
+
+ // Write to the cache (40-49).
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.response_headers =
+ "Last-Modified: Sat, 18 Apr 2009 01:10:43 GMT\n"
+ "Expires: Wed, 7 Sep 2033 21:46:42 GMT\n" // Should never expire.
+ "ETag: \"foo\"\n"
+ "Accept-Ranges: bytes\n"
+ "Content-Length: 10\n";
+ AddMockTransaction(&transaction);
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+
+ Verify206Response(headers, 40, 49);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Read from the cache (40-49).
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+ Verify206Response(headers, 40, 49);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Read again forcing the revalidation.
+ transaction.load_flags |= net::LOAD_VALIDATE_CACHE;
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+
+ Verify206Response(headers, 40, 49);
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ RemoveMockTransaction(&transaction);
+}
+
+// Checks that we revalidate an entry when the headers say so.
+TEST(HttpCache, RangeGET_Revalidate2) {
+ MockHttpCache cache;
+ cache.http_cache()->set_enable_range_support(true);
+ std::string headers;
+
+ // Write to the cache (40-49).
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.response_headers =
+ "Last-Modified: Sat, 18 Apr 2009 01:10:43 GMT\n"
+ "Expires: Sat, 18 Apr 2009 01:10:43 GMT\n" // Expired.
+ "ETag: \"foo\"\n"
+ "Accept-Ranges: bytes\n"
+ "Content-Length: 10\n";
+ AddMockTransaction(&transaction);
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+
+ Verify206Response(headers, 40, 49);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Read from the cache (40-49).
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+ Verify206Response(headers, 40, 49);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ RemoveMockTransaction(&transaction);
+}
+
// Tests that we deal with 304s for range requests.
TEST(HttpCache, RangeGET_304) {
MockHttpCache cache;
@@ -2511,7 +2956,7 @@
RunTransactionTestWithResponse(cache.http_cache(), kRangeGET_TransactionOK,
&headers);
- EXPECT_TRUE(Verify206Response(headers, 40, 49));
+ Verify206Response(headers, 40, 49);
EXPECT_EQ(1, cache.network_layer()->transaction_count());
EXPECT_EQ(0, cache.disk_cache()->open_count());
EXPECT_EQ(1, cache.disk_cache()->create_count());
@@ -2519,10 +2964,11 @@
// Read from the cache (40-49).
RangeTransactionServer handler;
handler.set_not_modified(true);
- RunTransactionTestWithResponse(cache.http_cache(), kRangeGET_TransactionOK,
- &headers);
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.load_flags |= net::LOAD_VALIDATE_CACHE;
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
- EXPECT_TRUE(Verify206Response(headers, 40, 49));
+ Verify206Response(headers, 40, 49);
EXPECT_EQ(2, cache.network_layer()->transaction_count());
EXPECT_EQ(1, cache.disk_cache()->open_count());
EXPECT_EQ(1, cache.disk_cache()->create_count());
@@ -2541,7 +2987,7 @@
RunTransactionTestWithResponse(cache.http_cache(), kRangeGET_TransactionOK,
&headers);
- EXPECT_TRUE(Verify206Response(headers, 40, 49));
+ Verify206Response(headers, 40, 49);
EXPECT_EQ(1, cache.network_layer()->transaction_count());
EXPECT_EQ(0, cache.disk_cache()->open_count());
EXPECT_EQ(1, cache.disk_cache()->create_count());
@@ -2549,10 +2995,11 @@
// Attempt to read from the cache (40-49).
RangeTransactionServer handler;
handler.set_modified(true);
- RunTransactionTestWithResponse(cache.http_cache(), kRangeGET_TransactionOK,
- &headers);
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.load_flags |= net::LOAD_VALIDATE_CACHE;
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
- EXPECT_TRUE(Verify206Response(headers, 40, 49));
+ Verify206Response(headers, 40, 49);
EXPECT_EQ(2, cache.network_layer()->transaction_count());
EXPECT_EQ(1, cache.disk_cache()->open_count());
EXPECT_EQ(1, cache.disk_cache()->create_count());
@@ -2580,7 +3027,7 @@
transaction.data = "rg: 70-79 ";
RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
- EXPECT_TRUE(Verify206Response(headers, 70, 79));
+ Verify206Response(headers, 70, 79);
EXPECT_EQ(1, cache.network_layer()->transaction_count());
EXPECT_EQ(0, cache.disk_cache()->open_count());
EXPECT_EQ(1, cache.disk_cache()->create_count());
@@ -2593,7 +3040,7 @@
transaction.data = "rg: 60-69 rg: 70-79 ";
RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
- EXPECT_TRUE(Verify206Response(headers, 60, 79));
+ Verify206Response(headers, 60, 79);
EXPECT_EQ(2, cache.network_layer()->transaction_count());
EXPECT_EQ(1, cache.disk_cache()->open_count());
EXPECT_EQ(1, cache.disk_cache()->create_count());
@@ -2620,7 +3067,7 @@
transaction.data = "rg: 70-79 ";
RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
- EXPECT_TRUE(Verify206Response(headers, 70, 79));
+ Verify206Response(headers, 70, 79);
EXPECT_EQ(1, cache.network_layer()->transaction_count());
EXPECT_EQ(0, cache.disk_cache()->open_count());
EXPECT_EQ(1, cache.disk_cache()->create_count());
@@ -2633,7 +3080,7 @@
transaction.data = "rg: 60-69 rg: 70-79 ";
RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
- EXPECT_TRUE(Verify206Response(headers, 60, 79));
+ Verify206Response(headers, 60, 79);
EXPECT_EQ(2, cache.network_layer()->transaction_count());
EXPECT_EQ(1, cache.disk_cache()->open_count());
EXPECT_EQ(1, cache.disk_cache()->create_count());
@@ -2682,7 +3129,7 @@
RunTransactionTestWithResponse(cache.http_cache(), kRangeGET_TransactionOK,
&headers);
- EXPECT_TRUE(Verify206Response(headers, 40, 49));
+ Verify206Response(headers, 40, 49);
EXPECT_EQ(1, cache.network_layer()->transaction_count());
EXPECT_EQ(0, cache.disk_cache()->open_count());
EXPECT_EQ(1, cache.disk_cache()->create_count());
@@ -2717,13 +3164,14 @@
// Write to the cache (0-9).
RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
- EXPECT_TRUE(Verify206Response(headers, 0, 9));
+ Verify206Response(headers, 0, 9);
EXPECT_EQ(1, cache.network_layer()->transaction_count());
EXPECT_EQ(0, cache.disk_cache()->open_count());
EXPECT_EQ(1, cache.disk_cache()->create_count());
// Read from the cache (0-9), write and read from cache (10 - 79),
MockTransaction transaction2(kRangeGET_TransactionOK);
+ transaction2.load_flags |= net::LOAD_VALIDATE_CACHE;
transaction2.request_headers = "Foo: bar\r\n" EXTRA_HEADER;
transaction2.data = "rg: 00-09 rg: 10-19 rg: 20-29 rg: 30-39 rg: 40-49 "
"rg: 50-59 rg: 60-69 rg: 70-79 ";
@@ -2751,7 +3199,7 @@
transaction.data = "rg: 00-09 ";
RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
- EXPECT_TRUE(Verify206Response(headers, 0, 9));
+ Verify206Response(headers, 0, 9);
EXPECT_EQ(1, cache.network_layer()->transaction_count());
EXPECT_EQ(0, cache.disk_cache()->open_count());
EXPECT_EQ(1, cache.disk_cache()->create_count());
@@ -2762,6 +3210,7 @@
// real server will answer with 200.
MockTransaction transaction2(kRangeGET_TransactionOK);
transaction2.request_headers = EXTRA_HEADER;
+ transaction2.load_flags |= net::LOAD_VALIDATE_CACHE;
transaction2.data = "rg: 40-49 ";
RangeTransactionServer handler;
handler.set_modified(true);
@@ -2786,8 +3235,7 @@
// Create a disk cache entry that stores 206 headers while not being sparse.
disk_cache::Entry* entry;
- ASSERT_EQ(net::OK, cache.disk_cache()->CreateEntry(kSimpleGET_Transaction.url,
- &entry, NULL));
+ ASSERT_TRUE(cache.CreateBackendEntry(kSimpleGET_Transaction.url, &entry));
std::string raw_headers(kRangeGET_TransactionOK.status);
raw_headers.append("\n");
@@ -2797,12 +3245,14 @@
net::HttpResponseInfo response;
response.headers = new net::HttpResponseHeaders(raw_headers);
- EXPECT_TRUE(net::HttpCache::WriteResponseInfo(entry, &response, true, false));
+ EXPECT_TRUE(MockHttpCache::WriteResponseInfo(entry, &response, true, false));
scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(500));
int len = static_cast<int>(base::strlcpy(buf->data(),
kRangeGET_TransactionOK.data, 500));
- EXPECT_EQ(len, entry->WriteData(1, 0, buf, len, NULL, true));
+ TestCompletionCallback cb;
+ int rv = entry->WriteData(1, 0, buf, len, &cb, true);
+ EXPECT_EQ(len, cb.GetResult(rv));
entry->Close();
// Now see that we don't use the stored entry.
@@ -2829,9 +3279,7 @@
// Create a disk cache entry that stores 206 headers while not being sparse.
disk_cache::Entry* entry;
- ASSERT_EQ(net::OK,
- cache.disk_cache()->CreateEntry(kRangeGET_TransactionOK.url,
- &entry, NULL));
+ ASSERT_TRUE(cache.CreateBackendEntry(kRangeGET_TransactionOK.url, &entry));
std::string raw_headers(kRangeGET_TransactionOK.status);
raw_headers.append("\n");
@@ -2841,12 +3289,14 @@
net::HttpResponseInfo response;
response.headers = new net::HttpResponseHeaders(raw_headers);
- EXPECT_TRUE(net::HttpCache::WriteResponseInfo(entry, &response, true, false));
+ EXPECT_TRUE(MockHttpCache::WriteResponseInfo(entry, &response, true, false));
scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(500));
int len = static_cast<int>(base::strlcpy(buf->data(),
kRangeGET_TransactionOK.data, 500));
- EXPECT_EQ(len, entry->WriteData(1, 0, buf, len, NULL, true));
+ TestCompletionCallback cb;
+ int rv = entry->WriteData(1, 0, buf, len, &cb, true);
+ EXPECT_EQ(len, cb.GetResult(rv));
entry->Close();
// Now see that we don't use the stored entry.
@@ -2855,7 +3305,7 @@
&headers);
// We are expecting a 206.
- EXPECT_TRUE(Verify206Response(headers, 40, 49));
+ Verify206Response(headers, 40, 49);
EXPECT_EQ(1, cache.network_layer()->transaction_count());
EXPECT_EQ(1, cache.disk_cache()->open_count());
EXPECT_EQ(2, cache.disk_cache()->create_count());
@@ -2890,7 +3340,7 @@
RunTransactionTestWithResponse(cache.http_cache(), transaction2, &headers);
// We are expecting a 206.
- EXPECT_TRUE(Verify206Response(headers, 40, 49));
+ Verify206Response(headers, 40, 49);
EXPECT_EQ(2, cache.network_layer()->transaction_count());
EXPECT_EQ(1, cache.disk_cache()->open_count());
EXPECT_EQ(1, cache.disk_cache()->create_count());
@@ -2902,7 +3352,7 @@
handler.set_not_modified(false);
transaction2.request_headers = kRangeGET_TransactionOK.request_headers;
RunTransactionTestWithResponse(cache.http_cache(), transaction2, &headers);
- EXPECT_TRUE(Verify206Response(headers, 40, 49));
+ Verify206Response(headers, 40, 49);
EXPECT_EQ(3, cache.network_layer()->transaction_count());
EXPECT_EQ(2, cache.disk_cache()->open_count());
EXPECT_EQ(1, cache.disk_cache()->create_count());
@@ -2926,7 +3376,7 @@
transaction.data = "rg: 70-79 ";
RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
- EXPECT_TRUE(Verify206Response(headers, 70, 79));
+ Verify206Response(headers, 70, 79);
EXPECT_EQ(1, cache.network_layer()->transaction_count());
EXPECT_EQ(0, cache.disk_cache()->open_count());
EXPECT_EQ(1, cache.disk_cache()->create_count());
@@ -2965,7 +3415,7 @@
RunTransactionTestWithResponse(cache.http_cache(), kRangeGET_TransactionOK,
&headers);
- EXPECT_TRUE(Verify206Response(headers, 40, 49));
+ Verify206Response(headers, 40, 49);
EXPECT_EQ(1, cache.network_layer()->transaction_count());
EXPECT_EQ(0, cache.disk_cache()->open_count());
EXPECT_EQ(1, cache.disk_cache()->create_count());
@@ -2999,7 +3449,7 @@
int rv = cache.http_cache()->CreateTransaction(&c->trans);
EXPECT_EQ(net::OK, rv);
- rv = c->trans->Start(&request, &c->callback, NULL);
+ rv = c->trans->Start(&request, &c->callback, net::BoundNetLog());
if (rv == net::ERR_IO_PENDING)
rv = c->callback.WaitForResult();
@@ -3019,8 +3469,7 @@
// Verify that the entry has not been deleted.
disk_cache::Entry* entry;
- ASSERT_EQ(net::OK, cache.disk_cache()->OpenEntry(kRangeGET_TransactionOK.url,
- &entry, NULL));
+ ASSERT_TRUE(cache.OpenBackendEntry(kRangeGET_TransactionOK.url, &entry));
entry->Close();
RemoveMockTransaction(&kRangeGET_TransactionOK);
}
@@ -3034,12 +3483,13 @@
RunTransactionTest(cache.http_cache(), kRangeGET_TransactionOK);
MockHttpRequest request(kRangeGET_TransactionOK);
+ request.load_flags |= net::LOAD_VALIDATE_CACHE;
Context* c = new Context();
int rv = cache.http_cache()->CreateTransaction(&c->trans);
EXPECT_EQ(net::OK, rv);
- rv = c->trans->Start(&request, &c->callback, NULL);
+ rv = c->trans->Start(&request, &c->callback, net::BoundNetLog());
if (rv == net::ERR_IO_PENDING)
rv = c->callback.WaitForResult();
@@ -3064,7 +3514,7 @@
RunTransactionTest(cache.http_cache(), kRangeGET_TransactionOK);
- EXPECT_EQ(3, cache.network_layer()->transaction_count());
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
EXPECT_EQ(1, cache.disk_cache()->open_count());
EXPECT_EQ(1, cache.disk_cache()->create_count());
RemoveMockTransaction(&kRangeGET_TransactionOK);
@@ -3079,12 +3529,13 @@
RunTransactionTest(cache.http_cache(), kRangeGET_TransactionOK);
MockHttpRequest request(kRangeGET_TransactionOK);
+ request.load_flags |= net::LOAD_VALIDATE_CACHE;
Context* c = new Context();
int rv = cache.http_cache()->CreateTransaction(&c->trans);
EXPECT_EQ(net::OK, rv);
- rv = c->trans->Start(&request, &c->callback, NULL);
+ rv = c->trans->Start(&request, &c->callback, net::BoundNetLog());
EXPECT_EQ(net::ERR_IO_PENDING, rv);
rv = c->callback.WaitForResult();
@@ -3111,7 +3562,7 @@
rv = cache.http_cache()->CreateTransaction(&c->trans);
EXPECT_EQ(net::OK, rv);
- rv = c->trans->Start(&request, &c->callback, NULL);
+ rv = c->trans->Start(&request, &c->callback, net::BoundNetLog());
EXPECT_EQ(net::ERR_IO_PENDING, rv);
MockDiskEntry::IgnoreCallbacks(true);
@@ -3154,8 +3605,7 @@
// Verify that we don't have a cached entry.
disk_cache::Entry* entry;
- EXPECT_NE(net::OK, cache.disk_cache()->OpenEntry(kRangeGET_TransactionOK.url,
- &entry, NULL));
+ EXPECT_FALSE(cache.OpenBackendEntry(kRangeGET_TransactionOK.url, &entry));
RemoveMockTransaction(&kRangeGET_TransactionOK);
}
@@ -3184,8 +3634,7 @@
// Verify that we don't have a cached entry.
disk_cache::Entry* entry;
- EXPECT_NE(net::OK, cache.disk_cache()->OpenEntry(kRangeGET_TransactionOK.url,
- &entry, NULL));
+ EXPECT_FALSE(cache.OpenBackendEntry(kRangeGET_TransactionOK.url, &entry));
RemoveMockTransaction(&kRangeGET_TransactionOK);
}
@@ -3206,7 +3655,7 @@
AddMockTransaction(&transaction);
RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
- EXPECT_TRUE(Verify206Response(headers, 50, 59));
+ Verify206Response(headers, 50, 59);
EXPECT_EQ(1, cache.network_layer()->transaction_count());
EXPECT_EQ(0, cache.disk_cache()->open_count());
EXPECT_EQ(1, cache.disk_cache()->create_count());
@@ -3219,17 +3668,19 @@
RunTransactionTestWithResponse(cache.http_cache(), kRangeGET_TransactionOK,
&headers);
- EXPECT_TRUE(Verify206Response(headers, 40, 49));
+ Verify206Response(headers, 40, 49);
EXPECT_EQ(2, cache.network_layer()->transaction_count());
EXPECT_EQ(1, cache.disk_cache()->open_count());
EXPECT_EQ(1, cache.disk_cache()->create_count());
// Verify that we cached the first response but not the second one.
disk_cache::Entry* en;
- ASSERT_EQ(net::OK, cache.disk_cache()->OpenEntry(kRangeGET_TransactionOK.url,
- &en, NULL));
+ ASSERT_TRUE(cache.OpenBackendEntry(kRangeGET_TransactionOK.url, &en));
+
int64 cached_start = 0;
- EXPECT_EQ(10, en->GetAvailableRange(40, 20, &cached_start));
+ TestCompletionCallback cb;
+ int rv = en->GetAvailableRange(40, 20, &cached_start, &cb);
+ EXPECT_EQ(10, cb.GetResult(rv));
EXPECT_EQ(50, cached_start);
en->Close();
@@ -3239,9 +3690,7 @@
// Tests that we handle large range values properly.
TEST(HttpCache, RangeGET_LargeValues) {
// We need a real sparse cache for this test.
- disk_cache::Backend* disk_cache =
- disk_cache::CreateInMemoryCacheBackend(1024 * 1024);
- MockHttpCache cache(disk_cache);
+ MockHttpCache cache(net::HttpCache::DefaultBackend::InMemory(1024 * 1024));
cache.http_cache()->set_enable_range_support(true);
std::string headers;
@@ -3264,7 +3713,7 @@
// Verify that we have a cached entry.
disk_cache::Entry* en;
- ASSERT_TRUE(cache.disk_cache()->OpenEntry(kRangeGET_TransactionOK.url, &en));
+ ASSERT_TRUE(cache.OpenBackendEntry(kRangeGET_TransactionOK.url, &en));
en->Close();
RemoveMockTransaction(&kRangeGET_TransactionOK);
@@ -3273,7 +3722,11 @@
// Tests that we don't crash with a range request if the disk cache was not
// initialized properly.
TEST(HttpCache, RangeGET_NoDiskCache) {
- MockHttpCache cache(NULL);
+ MockBlockingBackendFactory* factory = new MockBlockingBackendFactory();
+ factory->set_fail(true);
+ factory->FinishCreation(); // We'll complete synchronously.
+ MockHttpCache cache(factory);
+
cache.http_cache()->set_enable_range_support(true);
AddMockTransaction(&kRangeGET_TransactionOK);
@@ -3297,7 +3750,7 @@
std::string headers;
RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
- EXPECT_TRUE(Verify206Response(headers, 70, 79));
+ Verify206Response(headers, 70, 79);
EXPECT_EQ(1, cache.network_layer()->transaction_count());
EXPECT_EQ(0, cache.disk_cache()->open_count());
EXPECT_EQ(0, cache.disk_cache()->create_count());
@@ -3314,6 +3767,7 @@
MockTransaction transaction(kRangeGET_TransactionOK);
transaction.request_headers = "Range: bytes = 40-\r\n" EXTRA_HEADER;
transaction.test_mode = TEST_MODE_SYNC_NET_START;
+ transaction.load_flags |= net::LOAD_VALIDATE_CACHE;
AddMockTransaction(&transaction);
// Write to the cache.
@@ -3397,7 +3851,7 @@
EXPECT_EQ(net::OK, rv);
ASSERT_TRUE(trans.get());
- rv = trans->Start(&request, &callback, NULL);
+ rv = trans->Start(&request, &callback, net::BoundNetLog());
if (rv == net::ERR_IO_PENDING)
rv = callback.WaitForResult();
ASSERT_EQ(net::ERR_CACHE_MISS, rv);
@@ -3416,8 +3870,7 @@
TEST(HttpCache, WriteResponseInfo_Truncated) {
MockHttpCache cache;
disk_cache::Entry* entry;
- ASSERT_EQ(net::OK, cache.disk_cache()->CreateEntry("http://www.google.com",
- &entry, NULL));
+ ASSERT_TRUE(cache.CreateBackendEntry("http://www.google.com", &entry));
std::string headers("HTTP/1.1 200 OK");
headers = net::HttpUtil::AssembleRawHeaders(headers.data(), headers.size());
@@ -3425,15 +3878,15 @@
response.headers = new net::HttpResponseHeaders(headers);
// Set the last argument for this to be an incomplete request.
- EXPECT_TRUE(net::HttpCache::WriteResponseInfo(entry, &response, true, true));
+ EXPECT_TRUE(MockHttpCache::WriteResponseInfo(entry, &response, true, true));
bool truncated = false;
- EXPECT_TRUE(net::HttpCache::ReadResponseInfo(entry, &response, &truncated));
+ EXPECT_TRUE(MockHttpCache::ReadResponseInfo(entry, &response, &truncated));
EXPECT_TRUE(truncated);
// And now test the opposite case.
- EXPECT_TRUE(net::HttpCache::WriteResponseInfo(entry, &response, true, false));
+ EXPECT_TRUE(MockHttpCache::WriteResponseInfo(entry, &response, true, false));
truncated = true;
- EXPECT_TRUE(net::HttpCache::ReadResponseInfo(entry, &response, &truncated));
+ EXPECT_TRUE(MockHttpCache::ReadResponseInfo(entry, &response, &truncated));
EXPECT_FALSE(truncated);
entry->Close();
}
@@ -3450,7 +3903,7 @@
int rv = cache.http_cache()->CreateTransaction(&c->trans);
EXPECT_EQ(net::OK, rv);
- rv = c->trans->Start(&request, &c->callback, NULL);
+ rv = c->trans->Start(&request, &c->callback, net::BoundNetLog());
if (rv == net::ERR_IO_PENDING)
c->result = c->callback.WaitForResult();
@@ -3481,7 +3934,7 @@
int rv = cache.http_cache()->CreateTransaction(&c->trans);
EXPECT_EQ(net::OK, rv);
- rv = c->trans->Start(&request, &c->callback, NULL);
+ rv = c->trans->Start(&request, &c->callback, net::BoundNetLog());
if (rv == net::ERR_IO_PENDING)
rv = c->callback.WaitForResult();
@@ -3525,7 +3978,7 @@
int rv = cache.http_cache()->CreateTransaction(&c->trans);
EXPECT_EQ(net::OK, rv);
- rv = c->trans->Start(&request, &c->callback, NULL);
+ rv = c->trans->Start(&request, &c->callback, net::BoundNetLog());
if (rv == net::ERR_IO_PENDING)
rv = c->callback.WaitForResult();
@@ -3565,11 +4018,11 @@
AddMockTransaction(&transaction);
MockHttpRequest request(transaction);
- Context* c = new Context();
+ scoped_ptr<Context> c(new Context());
int rv = cache.http_cache()->CreateTransaction(&c->trans);
EXPECT_EQ(net::OK, rv);
- rv = c->trans->Start(&request, &c->callback, NULL);
+ rv = c->trans->Start(&request, &c->callback, net::BoundNetLog());
if (rv == net::ERR_IO_PENDING)
rv = c->callback.WaitForResult();
@@ -3584,16 +4037,29 @@
rv = c->callback.WaitForResult();
EXPECT_EQ(buf->size(), rv);
+ // We want to cancel the request when the transaction is busy.
+ rv = c->trans->Read(buf, buf->size(), &c->callback);
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+ EXPECT_FALSE(c->callback.have_result());
+
+ g_test_mode = TEST_MODE_SYNC_ALL;
+
// Destroy the transaction.
- delete c;
+ c->trans.reset();
+ g_test_mode = 0;
+
+ // Make sure that we don't invoke the callback. We may have an issue if the
+ // UrlRequestJob is killed directly (without cancelling the UrlRequest) so we
+ // could end up with the transaction being deleted twice if we send any
+ // notification from the transaction destructor (see http://crbug.com/31723).
+ EXPECT_FALSE(c->callback.have_result());
// Verify that the entry is marked as incomplete.
disk_cache::Entry* entry;
- ASSERT_EQ(net::OK, cache.disk_cache()->OpenEntry(kSimpleGET_Transaction.url,
- &entry, NULL));
+ ASSERT_TRUE(cache.OpenBackendEntry(kSimpleGET_Transaction.url, &entry));
net::HttpResponseInfo response;
bool truncated = false;
- EXPECT_TRUE(net::HttpCache::ReadResponseInfo(entry, &response, &truncated));
+ EXPECT_TRUE(MockHttpCache::ReadResponseInfo(entry, &response, &truncated));
EXPECT_TRUE(truncated);
entry->Close();
@@ -3608,9 +4074,7 @@
// Create a disk cache entry that stores an incomplete resource.
disk_cache::Entry* entry;
- ASSERT_EQ(net::OK,
- cache.disk_cache()->CreateEntry(kRangeGET_TransactionOK.url, &entry,
- NULL));
+ ASSERT_TRUE(cache.CreateBackendEntry(kRangeGET_TransactionOK.url, &entry));
std::string raw_headers("HTTP/1.1 200 OK\n"
"Last-Modified: Sat, 18 Apr 2009 01:10:43 GMT\n"
@@ -3623,12 +4087,14 @@
net::HttpResponseInfo response;
response.headers = new net::HttpResponseHeaders(raw_headers);
// Set the last argument for this to be an incomplete request.
- EXPECT_TRUE(net::HttpCache::WriteResponseInfo(entry, &response, true, true));
+ EXPECT_TRUE(MockHttpCache::WriteResponseInfo(entry, &response, true, true));
scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(100));
int len = static_cast<int>(base::strlcpy(buf->data(),
"rg: 00-09 rg: 10-19 ", 100));
- EXPECT_EQ(len, entry->WriteData(1, 0, buf, len, NULL, true));
+ TestCompletionCallback cb;
+ int rv = entry->WriteData(1, 0, buf, len, &cb, true);
+ EXPECT_EQ(len, cb.GetResult(rv));
// Now make a regular request.
std::string headers;
@@ -3656,7 +4122,7 @@
// Verify that the disk entry was updated.
EXPECT_EQ(80, entry->GetDataSize(1));
bool truncated = true;
- EXPECT_TRUE(net::HttpCache::ReadResponseInfo(entry, &response, &truncated));
+ EXPECT_TRUE(MockHttpCache::ReadResponseInfo(entry, &response, &truncated));
EXPECT_FALSE(truncated);
entry->Close();
}
@@ -3669,9 +4135,7 @@
// Create a disk cache entry that stores an incomplete resource.
disk_cache::Entry* entry;
- ASSERT_EQ(net::OK,
- cache.disk_cache()->CreateEntry(kRangeGET_TransactionOK.url, &entry,
- NULL));
+ ASSERT_TRUE(cache.CreateBackendEntry(kRangeGET_TransactionOK.url, &entry));
// Content-length will be intentionally bad.
@@ -3686,12 +4150,14 @@
net::HttpResponseInfo response;
response.headers = new net::HttpResponseHeaders(raw_headers);
// Set the last argument for this to be an incomplete request.
- EXPECT_TRUE(net::HttpCache::WriteResponseInfo(entry, &response, true, true));
+ EXPECT_TRUE(MockHttpCache::WriteResponseInfo(entry, &response, true, true));
scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(100));
int len = static_cast<int>(base::strlcpy(buf->data(),
"rg: 00-09 rg: 10-19 ", 100));
- EXPECT_EQ(len, entry->WriteData(1, 0, buf, len, NULL, true));
+ TestCompletionCallback cb;
+ int rv = entry->WriteData(1, 0, buf, len, &cb, true);
+ EXPECT_EQ(len, cb.GetResult(rv));
entry->Close();
// Now make a regular request.
@@ -3717,8 +4183,7 @@
RemoveMockTransaction(&kRangeGET_TransactionOK);
// Verify that the disk entry was deleted.
- ASSERT_NE(net::OK, cache.disk_cache()->OpenEntry(kRangeGET_TransactionOK.url,
- &entry, NULL));
+ ASSERT_FALSE(cache.OpenBackendEntry(kRangeGET_TransactionOK.url, &entry));
}
// Tests that when we cancel a request that was interrupted, we mark it again
@@ -3730,9 +4195,7 @@
// Create a disk cache entry that stores an incomplete resource.
disk_cache::Entry* entry;
- ASSERT_EQ(net::OK,
- cache.disk_cache()->CreateEntry(kRangeGET_TransactionOK.url, &entry,
- NULL));
+ ASSERT_TRUE(cache.CreateBackendEntry(kRangeGET_TransactionOK.url, &entry));
std::string raw_headers("HTTP/1.1 200 OK\n"
"Last-Modified: Sat, 18 Apr 2009 01:10:43 GMT\n"
@@ -3746,12 +4209,14 @@
response.headers = new net::HttpResponseHeaders(raw_headers);
// Set the last argument for this to be an incomplete request.
- EXPECT_TRUE(net::HttpCache::WriteResponseInfo(entry, &response, true, true));
+ EXPECT_TRUE(MockHttpCache::WriteResponseInfo(entry, &response, true, true));
scoped_refptr<net::IOBufferWithSize> buf(new net::IOBufferWithSize(100));
int len = static_cast<int>(base::strlcpy(buf->data(), "rg: 00-09 rg: 10-19 ",
buf->size()));
- EXPECT_EQ(len, entry->WriteData(1, 0, buf, len, NULL, true));
+ TestCompletionCallback cb;
+ int rv = entry->WriteData(1, 0, buf, len, &cb, true);
+ EXPECT_EQ(len, cb.GetResult(rv));
// Now make a regular request.
MockTransaction transaction(kRangeGET_TransactionOK);
@@ -3761,7 +4226,7 @@
Context* c = new Context();
EXPECT_EQ(net::OK, cache.http_cache()->CreateTransaction(&c->trans));
- int rv = c->trans->Start(&request, &c->callback, NULL);
+ rv = c->trans->Start(&request, &c->callback, net::BoundNetLog());
EXPECT_EQ(net::OK, c->callback.GetResult(rv));
// Read 20 bytes from the cache, and 10 from the net.
@@ -3783,7 +4248,7 @@
// Verify that the disk entry was updated: now we have 30 bytes.
EXPECT_EQ(30, entry->GetDataSize(1));
bool truncated = false;
- EXPECT_TRUE(net::HttpCache::ReadResponseInfo(entry, &response, &truncated));
+ EXPECT_TRUE(MockHttpCache::ReadResponseInfo(entry, &response, &truncated));
EXPECT_TRUE(truncated);
entry->Close();
}
@@ -3796,9 +4261,7 @@
// Create a disk cache entry that stores an incomplete resource.
disk_cache::Entry* entry;
- ASSERT_EQ(net::OK,
- cache.disk_cache()->CreateEntry(kRangeGET_TransactionOK.url, &entry,
- NULL));
+ ASSERT_TRUE(cache.CreateBackendEntry(kRangeGET_TransactionOK.url, &entry));
// Content-length will be intentionally bogus.
std::string raw_headers("HTTP/1.1 200 OK\n"
@@ -3812,12 +4275,14 @@
net::HttpResponseInfo response;
response.headers = new net::HttpResponseHeaders(raw_headers);
// Set the last argument for this to be an incomplete request.
- EXPECT_TRUE(net::HttpCache::WriteResponseInfo(entry, &response, true, true));
+ EXPECT_TRUE(MockHttpCache::WriteResponseInfo(entry, &response, true, true));
scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(100));
int len = static_cast<int>(base::strlcpy(buf->data(),
"rg: 00-09 rg: 10-19 ", 100));
- EXPECT_EQ(len, entry->WriteData(1, 0, buf, len, NULL, true));
+ TestCompletionCallback cb;
+ int rv = entry->WriteData(1, 0, buf, len, &cb, true);
+ EXPECT_EQ(len, cb.GetResult(rv));
entry->Close();
// Now make a range request.
@@ -3825,7 +4290,7 @@
RunTransactionTestWithResponse(cache.http_cache(), kRangeGET_TransactionOK,
&headers);
- EXPECT_TRUE(Verify206Response(headers, 40, 49));
+ Verify206Response(headers, 40, 49);
EXPECT_EQ(1, cache.network_layer()->transaction_count());
EXPECT_EQ(1, cache.disk_cache()->open_count());
EXPECT_EQ(2, cache.disk_cache()->create_count());
@@ -3852,13 +4317,13 @@
c2(cache.http_cache()),
c3(cache.http_cache());
- c1.Start(&r1, NULL);
+ c1.Start(&r1, net::BoundNetLog());
r2.load_flags |= net::LOAD_ONLY_FROM_CACHE;
- c2.Start(&r2, NULL);
+ c2.Start(&r2, net::BoundNetLog());
r3.load_flags |= net::LOAD_ONLY_FROM_CACHE;
- c3.Start(&r3, NULL);
+ c3.Start(&r3, net::BoundNetLog());
MessageLoop::current()->Run();
@@ -3906,7 +4371,7 @@
EXPECT_EQ(net::OK, rv);
ASSERT_TRUE(trans.get());
- rv = trans->Start(&request, &callback, NULL);
+ rv = trans->Start(&request, &callback, net::BoundNetLog());
if (rv == net::ERR_IO_PENDING)
rv = callback.WaitForResult();
ASSERT_EQ(net::OK, rv);
@@ -3934,7 +4399,7 @@
EXPECT_EQ(net::OK, rv);
ASSERT_TRUE(trans.get());
- rv = trans->Start(&request, &callback, NULL);
+ rv = trans->Start(&request, &callback, net::BoundNetLog());
if (rv == net::ERR_IO_PENDING)
rv = callback.WaitForResult();
ASSERT_EQ(net::OK, rv);
@@ -3977,8 +4442,7 @@
EXPECT_EQ(2, cache.disk_cache()->create_count());
disk_cache::Entry* entry;
- EXPECT_NE(net::OK,
- cache.disk_cache()->OpenEntry(transaction.url, &entry, NULL));
+ EXPECT_FALSE(cache.OpenBackendEntry(transaction.url, &entry));
}
TEST(HttpCache, CacheControlNoStore2) {
@@ -4006,8 +4470,7 @@
EXPECT_EQ(1, cache.disk_cache()->create_count());
disk_cache::Entry* entry;
- EXPECT_NE(net::OK,
- cache.disk_cache()->OpenEntry(transaction.url, &entry, NULL));
+ EXPECT_FALSE(cache.OpenBackendEntry(transaction.url, &entry));
}
TEST(HttpCache, CacheControlNoStore3) {
@@ -4036,8 +4499,7 @@
EXPECT_EQ(1, cache.disk_cache()->create_count());
disk_cache::Entry* entry;
- EXPECT_NE(net::OK,
- cache.disk_cache()->OpenEntry(transaction.url, &entry, NULL));
+ EXPECT_FALSE(cache.OpenBackendEntry(transaction.url, &entry));
}
// Ensure that we don't cache requests served over bad HTTPS.
@@ -4062,7 +4524,7 @@
EXPECT_EQ(net::OK, rv);
ASSERT_TRUE(trans.get());
- rv = trans->Start(&request, &callback, NULL);
+ rv = trans->Start(&request, &callback, net::BoundNetLog());
if (rv == net::ERR_IO_PENDING)
rv = callback.WaitForResult();
ASSERT_EQ(net::ERR_CACHE_MISS, rv);
@@ -4173,3 +4635,132 @@
RemoveMockTransaction(&mock_network_response);
}
+
+// Tests that we can write metadata to an entry.
+TEST(HttpCache, WriteMetadata_OK) {
+ MockHttpCache cache;
+
+ // Write to the cache
+ net::HttpResponseInfo response;
+ RunTransactionTestWithResponseInfo(cache.http_cache(), kSimpleGET_Transaction,
+ &response);
+ EXPECT_TRUE(response.metadata.get() == NULL);
+
+ // Trivial call.
+ cache.http_cache()->WriteMetadata(GURL("foo"), Time::Now(), NULL, 0);
+
+ // Write meta data to the same entry.
+ scoped_refptr<net::IOBufferWithSize> buf(new net::IOBufferWithSize(50));
+ memset(buf->data(), 0, buf->size());
+ base::strlcpy(buf->data(), "Hi there", buf->size());
+ cache.http_cache()->WriteMetadata(GURL(kSimpleGET_Transaction.url),
+ response.response_time, buf, buf->size());
+
+ // Release the buffer before the operation takes place.
+ buf = NULL;
+
+ // Makes sure we finish pending operations.
+ MessageLoop::current()->RunAllPending();
+
+ RunTransactionTestWithResponseInfo(cache.http_cache(), kSimpleGET_Transaction,
+ &response);
+ ASSERT_TRUE(response.metadata.get() != NULL);
+ EXPECT_EQ(50, response.metadata->size());
+ EXPECT_EQ(0, strcmp(response.metadata->data(), "Hi there"));
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(2, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+}
+
+// Tests that we only write metadata to an entry if the time stamp matches.
+TEST(HttpCache, WriteMetadata_Fail) {
+ MockHttpCache cache;
+
+ // Write to the cache
+ net::HttpResponseInfo response;
+ RunTransactionTestWithResponseInfo(cache.http_cache(), kSimpleGET_Transaction,
+ &response);
+ EXPECT_TRUE(response.metadata.get() == NULL);
+
+ // Attempt to write meta data to the same entry.
+ scoped_refptr<net::IOBufferWithSize> buf(new net::IOBufferWithSize(50));
+ memset(buf->data(), 0, buf->size());
+ base::strlcpy(buf->data(), "Hi there", buf->size());
+ base::Time expected_time = response.response_time -
+ base::TimeDelta::FromMilliseconds(20);
+ cache.http_cache()->WriteMetadata(GURL(kSimpleGET_Transaction.url),
+ expected_time, buf, buf->size());
+
+ // Makes sure we finish pending operations.
+ MessageLoop::current()->RunAllPending();
+
+ RunTransactionTestWithResponseInfo(cache.http_cache(), kSimpleGET_Transaction,
+ &response);
+ EXPECT_TRUE(response.metadata.get() == NULL);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(2, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+}
+
+// Tests that we can read metadata after validating the entry and with READ mode
+// transactions.
+TEST(HttpCache, ReadMetadata) {
+ MockHttpCache cache;
+
+ // Write to the cache
+ net::HttpResponseInfo response;
+ RunTransactionTestWithResponseInfo(cache.http_cache(),
+ kTypicalGET_Transaction, &response);
+ EXPECT_TRUE(response.metadata.get() == NULL);
+
+ // Write meta data to the same entry.
+ scoped_refptr<net::IOBufferWithSize> buf(new net::IOBufferWithSize(50));
+ memset(buf->data(), 0, buf->size());
+ base::strlcpy(buf->data(), "Hi there", buf->size());
+ cache.http_cache()->WriteMetadata(GURL(kTypicalGET_Transaction.url),
+ response.response_time, buf, buf->size());
+
+ // Makes sure we finish pending operations.
+ MessageLoop::current()->RunAllPending();
+
+ // Start with a READ mode transaction.
+ MockTransaction trans1(kTypicalGET_Transaction);
+ trans1.load_flags = net::LOAD_ONLY_FROM_CACHE;
+
+ RunTransactionTestWithResponseInfo(cache.http_cache(), trans1, &response);
+ ASSERT_TRUE(response.metadata.get() != NULL);
+ EXPECT_EQ(50, response.metadata->size());
+ EXPECT_EQ(0, strcmp(response.metadata->data(), "Hi there"));
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(2, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ MessageLoop::current()->RunAllPending();
+
+ // Now make sure that the entry is re-validated with the server.
+ trans1.load_flags = net::LOAD_VALIDATE_CACHE;
+ trans1.status = "HTTP/1.1 304 Not Modified";
+ AddMockTransaction(&trans1);
+
+ response.metadata = NULL;
+ RunTransactionTestWithResponseInfo(cache.http_cache(), trans1, &response);
+ EXPECT_TRUE(response.metadata.get() != NULL);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(3, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ MessageLoop::current()->RunAllPending();
+ RemoveMockTransaction(&trans1);
+
+ // Now return 200 when validating the entry so the metadata will be lost.
+ MockTransaction trans2(kTypicalGET_Transaction);
+ trans2.load_flags = net::LOAD_VALIDATE_CACHE;
+ RunTransactionTestWithResponseInfo(cache.http_cache(), trans2, &response);
+ EXPECT_TRUE(response.metadata.get() == NULL);
+
+ EXPECT_EQ(3, cache.network_layer()->transaction_count());
+ EXPECT_EQ(4, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+}
diff --git a/net/http/http_chunked_decoder.cc b/net/http/http_chunked_decoder.cc
index 619c9a1..1f6ef0d 100644
--- a/net/http/http_chunked_decoder.cc
+++ b/net/http/http_chunked_decoder.cc
@@ -116,11 +116,11 @@
reached_eof_ = true;
}
} else if (chunk_terminator_remaining_) {
- if (buf_len) {
- DLOG(ERROR) << "chunk data not terminated properly";
- return ERR_INVALID_CHUNKED_ENCODING;
- }
- chunk_terminator_remaining_ = false;
+ if (buf_len) {
+ DLOG(ERROR) << "chunk data not terminated properly";
+ return ERR_INVALID_CHUNKED_ENCODING;
+ }
+ chunk_terminator_remaining_ = false;
} else if (buf_len) {
// Ignore any chunk-extensions.
size_t index_of_semicolon = base::StringPiece(buf, buf_len).find(';');
diff --git a/net/http/http_net_log_params.h b/net/http/http_net_log_params.h
new file mode 100644
index 0000000..563c799
--- /dev/null
+++ b/net/http/http_net_log_params.h
@@ -0,0 +1,84 @@
+// 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.
+
+#ifndef NET_HTTP_HTTP_NET_LOG_PARAMS_H_
+#define NET_HTTP_HTTP_NET_LOG_PARAMS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/ref_counted.h"
+#include "base/values.h"
+#include "net/base/net_log.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_response_headers.h"
+
+namespace net {
+
+class NetLogHttpRequestParameter : public NetLog::EventParameters {
+ public:
+ NetLogHttpRequestParameter(const std::string& line,
+ const HttpRequestHeaders& headers)
+ : line_(line) {
+ headers_.CopyFrom(headers);
+ }
+
+ Value* ToValue() const {
+ DictionaryValue* dict = new DictionaryValue();
+ dict->SetString(L"line", line_);
+ ListValue* headers = new ListValue();
+ HttpRequestHeaders::Iterator iterator(headers_);
+ while (iterator.GetNext()) {
+ headers->Append(
+ new StringValue(StringPrintf("%s: %s",
+ iterator.name().c_str(),
+ iterator.value().c_str())));
+ }
+ dict->Set(L"headers", headers);
+ return dict;
+ }
+
+ private:
+ ~NetLogHttpRequestParameter() {}
+
+ const std::string line_;
+ HttpRequestHeaders headers_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetLogHttpRequestParameter);
+};
+
+class NetLogHttpResponseParameter : public NetLog::EventParameters {
+ public:
+ explicit NetLogHttpResponseParameter(
+ const scoped_refptr<HttpResponseHeaders>& headers)
+ : headers_(headers) {}
+
+ Value* ToValue() const {
+ DictionaryValue* dict = new DictionaryValue();
+ ListValue* headers = new ListValue();
+ headers->Append(new StringValue(headers_->GetStatusLine()));
+ void* iterator = NULL;
+ std::string name;
+ std::string value;
+ while (headers_->EnumerateHeaderLines(&iterator, &name, &value)) {
+ headers->Append(
+ new StringValue(StringPrintf("%s: %s", name.c_str(), value.c_str())));
+ }
+ dict->Set(L"headers", headers);
+ return dict;
+ }
+
+ private:
+ ~NetLogHttpResponseParameter() {}
+
+ const scoped_refptr<HttpResponseHeaders> headers_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetLogHttpResponseParameter);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_NET_LOG_PARAMS_H_
+
diff --git a/net/http/http_network_delegate.h b/net/http/http_network_delegate.h
new file mode 100644
index 0000000..51c6766
--- /dev/null
+++ b/net/http/http_network_delegate.h
@@ -0,0 +1,24 @@
+// 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.
+
+#ifndef NET_HTTP_HTTP_NETWORK_DELEGATE_H_
+#define NET_HTTP_HTTP_NETWORK_DELEGATE_H_
+
+namespace net {
+
+class HttpRequestHeaders;
+
+class HttpNetworkDelegate {
+ public:
+ // Called right before the HTTP headers are sent. Allows the delegate to
+ // read/write |headers| before they get sent out.
+ virtual void OnSendHttpRequest(HttpRequestHeaders* headers) = 0;
+
+ protected:
+ virtual ~HttpNetworkDelegate() {}
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_NETWORK_DELEGATE_H_
diff --git a/net/http/http_network_layer.cc b/net/http/http_network_layer.cc
index c8022b7..7df286f 100644
--- a/net/http/http_network_layer.cc
+++ b/net/http/http_network_layer.cc
@@ -1,18 +1,19 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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 "net/http/http_network_layer.h"
+#include "base/field_trial.h"
#include "base/logging.h"
#include "base/string_util.h"
-#include "net/flip/flip_framer.h"
-#include "net/flip/flip_network_transaction.h"
-#include "net/flip/flip_session.h"
-#include "net/flip/flip_session_pool.h"
#include "net/http/http_network_session.h"
#include "net/http/http_network_transaction.h"
#include "net/socket/client_socket_factory.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_network_transaction.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_session_pool.h"
namespace net {
@@ -20,15 +21,19 @@
// static
HttpTransactionFactory* HttpNetworkLayer::CreateFactory(
- NetworkChangeNotifier* network_change_notifier,
HostResolver* host_resolver,
ProxyService* proxy_service,
- SSLConfigService* ssl_config_service) {
+ SSLConfigService* ssl_config_service,
+ HttpAuthHandlerFactory* http_auth_handler_factory,
+ HttpNetworkDelegate* network_delegate,
+ NetLog* net_log) {
DCHECK(proxy_service);
return new HttpNetworkLayer(ClientSocketFactory::GetDefaultFactory(),
- network_change_notifier,
- host_resolver, proxy_service, ssl_config_service);
+ host_resolver, proxy_service, ssl_config_service,
+ http_auth_handler_factory,
+ network_delegate,
+ net_log);
}
// static
@@ -40,21 +45,25 @@
}
//-----------------------------------------------------------------------------
-bool HttpNetworkLayer::force_flip_ = false;
+bool HttpNetworkLayer::force_spdy_ = false;
HttpNetworkLayer::HttpNetworkLayer(
ClientSocketFactory* socket_factory,
- NetworkChangeNotifier* network_change_notifier,
HostResolver* host_resolver,
ProxyService* proxy_service,
- SSLConfigService* ssl_config_service)
+ SSLConfigService* ssl_config_service,
+ HttpAuthHandlerFactory* http_auth_handler_factory,
+ HttpNetworkDelegate* network_delegate,
+ NetLog* net_log)
: socket_factory_(socket_factory),
- network_change_notifier_(network_change_notifier),
host_resolver_(host_resolver),
proxy_service_(proxy_service),
ssl_config_service_(ssl_config_service),
session_(NULL),
- flip_session_pool_(NULL),
+ spdy_session_pool_(NULL),
+ http_auth_handler_factory_(http_auth_handler_factory),
+ network_delegate_(network_delegate),
+ net_log_(net_log),
suspended_(false) {
DCHECK(proxy_service_);
DCHECK(ssl_config_service_.get());
@@ -62,10 +71,12 @@
HttpNetworkLayer::HttpNetworkLayer(HttpNetworkSession* session)
: socket_factory_(ClientSocketFactory::GetDefaultFactory()),
- network_change_notifier_(NULL),
ssl_config_service_(NULL),
session_(session),
- flip_session_pool_(session->flip_session_pool()),
+ spdy_session_pool_(session->spdy_session_pool()),
+ http_auth_handler_factory_(NULL),
+ network_delegate_(NULL),
+ net_log_(NULL),
suspended_(false) {
DCHECK(session_.get());
}
@@ -77,8 +88,8 @@
if (suspended_)
return ERR_NETWORK_IO_SUSPENDED;
- if (force_flip_)
- trans->reset(new FlipNetworkTransaction(GetSession()));
+ if (force_spdy_)
+ trans->reset(new SpdyNetworkTransaction(GetSession()));
else
trans->reset(new HttpNetworkTransaction(GetSession()));
return OK;
@@ -98,47 +109,80 @@
HttpNetworkSession* HttpNetworkLayer::GetSession() {
if (!session_) {
DCHECK(proxy_service_);
- FlipSessionPool* flip_pool = new FlipSessionPool;
- session_ = new HttpNetworkSession(
- network_change_notifier_, host_resolver_, proxy_service_,
- socket_factory_, ssl_config_service_, flip_pool);
+ SpdySessionPool* spdy_pool = new SpdySessionPool();
+ session_ = new HttpNetworkSession(host_resolver_, proxy_service_,
+ socket_factory_, ssl_config_service_, spdy_pool,
+ http_auth_handler_factory_, network_delegate_, net_log_);
// These were just temps for lazy-initializing HttpNetworkSession.
- network_change_notifier_ = NULL;
host_resolver_ = NULL;
proxy_service_ = NULL;
socket_factory_ = NULL;
+ http_auth_handler_factory_ = NULL;
+ net_log_ = NULL;
+ network_delegate_ = NULL;
}
return session_;
}
// static
-void HttpNetworkLayer::EnableFlip(const std::string& mode) {
+void HttpNetworkLayer::EnableSpdy(const std::string& mode) {
static const char kDisableSSL[] = "no-ssl";
static const char kDisableCompression[] = "no-compress";
+ static const char kDisableAltProtocols[] = "no-alt-protocols";
+
+ // We want an A/B experiment between SPDY enabled and SPDY disabled,
+ // but only for pages where SPDY *could have been* negotiated. To do
+ // this, we use NPN, but prevent it from negotiating SPDY. If the
+ // server negotiates HTTP, rather than SPDY, today that will only happen
+ // on servers that installed NPN (and could have done SPDY). But this is
+ // a bit of a hack, as this correlation between NPN and SPDY is not
+ // really guaranteed.
static const char kEnableNPN[] = "npn";
+ static const char kEnableNpnHttpOnly[] = "npn-http";
- std::vector<std::string> flip_options;
- SplitString(mode, ',', &flip_options);
+ // Except for the first element, the order is irrelevant. First element
+ // specifies the fallback in case nothing matches
+ // (SSLClientSocket::kNextProtoNoOverlap). Otherwise, the SSL library
+ // will choose the first overlapping protocol in the server's list, since
+ // it presumedly has a better understanding of which protocol we should
+ // use, therefore the rest of the ordering here is not important.
+ static const char kNpnProtosFull[] =
+ "\x08http/1.1\x07http1.1\x06spdy/1\x04spdy";
+ // No spdy specified.
+ static const char kNpnProtosHttpOnly[] = "\x08http/1.1\x07http1.1";
- // Force flip mode (use FlipNetworkTransaction for all http requests).
- force_flip_ = true;
+ std::vector<std::string> spdy_options;
+ SplitString(mode, ',', &spdy_options);
- for (std::vector<std::string>::iterator it = flip_options.begin();
- it != flip_options.end(); ++it) {
+ // Force spdy mode (use SpdyNetworkTransaction for all http requests).
+ force_spdy_ = true;
+
+ bool use_alt_protocols = true;
+
+ for (std::vector<std::string>::iterator it = spdy_options.begin();
+ it != spdy_options.end(); ++it) {
const std::string& option = *it;
-
- // Disable SSL
if (option == kDisableSSL) {
- FlipSession::SetSSLMode(false);
+ SpdySession::SetSSLMode(false); // Disable SSL
} else if (option == kDisableCompression) {
- flip::FlipFramer::set_enable_compression_default(false);
+ spdy::SpdyFramer::set_enable_compression_default(false);
} else if (option == kEnableNPN) {
- HttpNetworkTransaction::SetNextProtos("\007http1.1\004spdy");
- force_flip_ = false;
- } else if (option.empty() && it == flip_options.begin()) {
+ HttpNetworkTransaction::SetUseAlternateProtocols(use_alt_protocols);
+ HttpNetworkTransaction::SetNextProtos(kNpnProtosFull);
+ force_spdy_ = false;
+ } else if (option == kEnableNpnHttpOnly) {
+ // Avoid alternate protocol in this case. Otherwise, browser will try SSL
+ // and then fallback to http. This introduces extra load.
+ HttpNetworkTransaction::SetUseAlternateProtocols(false);
+ HttpNetworkTransaction::SetNextProtos(kNpnProtosHttpOnly);
+ force_spdy_ = false;
+ } else if (option == kDisableAltProtocols) {
+ use_alt_protocols = false;
+ HttpNetworkTransaction::SetUseAlternateProtocols(false);
+ } else if (option.empty() && it == spdy_options.begin()) {
continue;
} else {
- LOG(DFATAL) << "Unrecognized flip option: " << option;
+ LOG(DFATAL) << "Unrecognized spdy option: " << option;
}
}
}
diff --git a/net/http/http_network_layer.h b/net/http/http_network_layer.h
index 38c10c7..8cc62ab 100644
--- a/net/http/http_network_layer.h
+++ b/net/http/http_network_layer.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -7,6 +7,7 @@
#include <string>
+#include "base/non_thread_safe.h"
#include "base/ref_counted.h"
#include "base/scoped_ptr.h"
#include "net/http/http_transaction_factory.h"
@@ -14,22 +15,27 @@
namespace net {
class ClientSocketFactory;
-class FlipSessionPool;
class HostResolver;
+class HttpAuthHandlerFactory;
+class HttpNetworkDelegate;
class HttpNetworkSession;
-class NetworkChangeNotifier;
+class NetLog;
class ProxyInfo;
class ProxyService;
+class SpdySessionPool;
class SSLConfigService;
-class HttpNetworkLayer : public HttpTransactionFactory {
+class HttpNetworkLayer : public HttpTransactionFactory, public NonThreadSafe {
public:
- // |socket_factory|, |network_change_notifier|, |proxy_service| and
- // |host_resolver| must remain valid for the lifetime of HttpNetworkLayer.
+ // |socket_factory|, |proxy_service| and |host_resolver| must remain valid for
+ // the lifetime of HttpNetworkLayer.
HttpNetworkLayer(ClientSocketFactory* socket_factory,
- NetworkChangeNotifier* network_change_notifier,
- HostResolver* host_resolver, ProxyService* proxy_service,
- SSLConfigService* ssl_config_service);
+ HostResolver* host_resolver,
+ ProxyService* proxy_service,
+ SSLConfigService* ssl_config_service,
+ HttpAuthHandlerFactory* http_auth_handler_factory,
+ HttpNetworkDelegate* network_delegate,
+ NetLog* net_log);
// Construct a HttpNetworkLayer with an existing HttpNetworkSession which
// contains a valid ProxyService.
explicit HttpNetworkLayer(HttpNetworkSession* session);
@@ -38,10 +44,12 @@
// This function hides the details of how a network layer gets instantiated
// and allows other implementations to be substituted.
static HttpTransactionFactory* CreateFactory(
- NetworkChangeNotifier* network_change_notifier,
HostResolver* host_resolver,
ProxyService* proxy_service,
- SSLConfigService* ssl_config_service);
+ SSLConfigService* ssl_config_service,
+ HttpAuthHandlerFactory* http_auth_handler_factory,
+ HttpNetworkDelegate* network_delegate,
+ NetLog* net_log);
// Create a transaction factory that instantiate a network layer over an
// existing network session. Network session contains some valuable
// information (e.g. authentication data) that we want to share across
@@ -56,20 +64,18 @@
virtual HttpNetworkSession* GetSession();
virtual void Suspend(bool suspend);
- // Enable the flip protocol.
- // Without calling this function, FLIP is disabled. The mode can be:
+ // Enable the spdy protocol.
+ // Without calling this function, SPDY is disabled. The mode can be:
// "" : (default) SSL and compression are enabled.
// "no-ssl" : disables SSL.
// "no-compress" : disables compression.
// "none" : disables both SSL and compression.
- static void EnableFlip(const std::string& mode);
+ static void EnableSpdy(const std::string& mode);
private:
// The factory we will use to create network sockets.
ClientSocketFactory* socket_factory_;
- NetworkChangeNotifier* network_change_notifier_;
-
// The host resolver and proxy service that will be used when lazily
// creating |session_|.
scoped_refptr<HostResolver> host_resolver_;
@@ -79,10 +85,14 @@
scoped_refptr<SSLConfigService> ssl_config_service_;
scoped_refptr<HttpNetworkSession> session_;
- scoped_refptr<FlipSessionPool> flip_session_pool_;
+ scoped_refptr<SpdySessionPool> spdy_session_pool_;
+
+ HttpAuthHandlerFactory* http_auth_handler_factory_;
+ HttpNetworkDelegate* network_delegate_;
+ NetLog* net_log_;
bool suspended_;
- static bool force_flip_;
+ static bool force_spdy_;
};
} // namespace net
diff --git a/net/http/http_network_layer_unittest.cc b/net/http/http_network_layer_unittest.cc
index 1e75ece..274d2dc 100644
--- a/net/http/http_network_layer_unittest.cc
+++ b/net/http/http_network_layer_unittest.cc
@@ -1,8 +1,9 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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 "net/base/mock_host_resolver.h"
+#include "net/base/net_log.h"
#include "net/base/ssl_config_service_defaults.h"
#include "net/http/http_network_layer.h"
#include "net/http/http_transaction_unittest.h"
@@ -15,9 +16,9 @@
};
TEST_F(HttpNetworkLayerTest, CreateAndDestroy) {
- net::HttpNetworkLayer factory(
- NULL, NULL, new net::MockHostResolver, net::ProxyService::CreateNull(),
- new net::SSLConfigServiceDefaults);
+ net::HttpNetworkLayer factory(NULL, new net::MockHostResolver,
+ net::ProxyService::CreateNull(), new net::SSLConfigServiceDefaults, NULL,
+ NULL, NULL);
scoped_ptr<net::HttpTransaction> trans;
int rv = factory.CreateTransaction(&trans);
@@ -26,9 +27,9 @@
}
TEST_F(HttpNetworkLayerTest, Suspend) {
- net::HttpNetworkLayer factory(
- NULL, NULL, new net::MockHostResolver, net::ProxyService::CreateNull(),
- new net::SSLConfigServiceDefaults);
+ net::HttpNetworkLayer factory(NULL, new net::MockHostResolver,
+ net::ProxyService::CreateNull(), new net::SSLConfigServiceDefaults, NULL,
+ NULL, NULL);
scoped_ptr<net::HttpTransaction> trans;
int rv = factory.CreateTransaction(&trans);
@@ -62,13 +63,13 @@
"Connection: keep-alive\r\n"
"User-Agent: Foo/1.0\r\n\r\n"),
};
- net::StaticSocketDataProvider data(data_reads, data_writes);
+ net::StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_reads));
mock_socket_factory.AddSocketDataProvider(&data);
- net::HttpNetworkLayer factory(&mock_socket_factory, NULL,
- new net::MockHostResolver,
- net::ProxyService::CreateNull(),
- new net::SSLConfigServiceDefaults);
+ net::HttpNetworkLayer factory(&mock_socket_factory, new net::MockHostResolver,
+ net::ProxyService::CreateNull(), new net::SSLConfigServiceDefaults, NULL,
+ NULL, NULL);
TestCompletionCallback callback;
@@ -79,10 +80,11 @@
net::HttpRequestInfo request_info;
request_info.url = GURL("http://www.google.com/");
request_info.method = "GET";
- request_info.user_agent = "Foo/1.0";
+ request_info.extra_headers.SetHeader(net::HttpRequestHeaders::kUserAgent,
+ "Foo/1.0");
request_info.load_flags = net::LOAD_NORMAL;
- rv = trans->Start(&request_info, &callback, NULL);
+ rv = trans->Start(&request_info, &callback, net::BoundNetLog());
if (rv == net::ERR_IO_PENDING)
rv = callback.WaitForResult();
ASSERT_EQ(net::OK, rv);
diff --git a/net/http/http_network_session.cc b/net/http/http_network_session.cc
index 9f4cba7..971786a 100644
--- a/net/http/http_network_session.cc
+++ b/net/http/http_network_session.cc
@@ -1,40 +1,72 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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 "net/http/http_network_session.h"
+#include <utility>
+
#include "base/logging.h"
-#include "net/flip/flip_session_pool.h"
+#include "base/stl_util-inl.h"
+#include "base/string_util.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/url_security_manager.h"
+#include "net/spdy/spdy_session_pool.h"
namespace net {
-// static
-int HttpNetworkSession::max_sockets_ = 100;
+namespace {
-// static
-int HttpNetworkSession::max_sockets_per_group_ = 6;
+// Total limit of sockets.
+int g_max_sockets = 256;
-// static
-uint16 HttpNetworkSession::g_fixed_http_port = 0;
-uint16 HttpNetworkSession::g_fixed_https_port = 0;
+// Default to allow up to 6 connections per host. Experiment and tuning may
+// try other values (greater than 0). Too large may cause many problems, such
+// as home routers blocking the connections!?!? See http://crbug.com/12066.
+int g_max_sockets_per_group = 6;
+
+// The max number of sockets to allow per proxy server. This applies both to
+// http and SOCKS proxies. See http://crbug.com/12066 and
+// http://crbug.com/44501 for details about proxy server connection limits.
+int g_max_sockets_per_proxy_server = 32;
+
+uint16 g_fixed_http_port = 0;
+uint16 g_fixed_https_port = 0;
+
+} // namespace
HttpNetworkSession::HttpNetworkSession(
- NetworkChangeNotifier* network_change_notifier,
HostResolver* host_resolver,
ProxyService* proxy_service,
ClientSocketFactory* client_socket_factory,
SSLConfigService* ssl_config_service,
- FlipSessionPool* flip_session_pool)
- : network_change_notifier_(network_change_notifier),
+ SpdySessionPool* spdy_session_pool,
+ HttpAuthHandlerFactory* http_auth_handler_factory,
+ HttpNetworkDelegate* network_delegate,
+ NetLog* net_log)
+ : tcp_pool_histograms_(new ClientSocketPoolHistograms("TCP")),
+ tcp_for_http_proxy_pool_histograms_(
+ new ClientSocketPoolHistograms("TCPforHTTPProxy")),
+ http_proxy_pool_histograms_(new ClientSocketPoolHistograms("HTTPProxy")),
+ tcp_for_socks_pool_histograms_(
+ new ClientSocketPoolHistograms("TCPforSOCKS")),
+ socks_pool_histograms_(new ClientSocketPoolHistograms("SOCK")),
+ ssl_pool_histograms_(new ClientSocketPoolHistograms("SSL")),
tcp_socket_pool_(new TCPClientSocketPool(
- max_sockets_, max_sockets_per_group_,
- host_resolver, client_socket_factory, network_change_notifier_)),
+ g_max_sockets, g_max_sockets_per_group, tcp_pool_histograms_,
+ host_resolver, client_socket_factory, net_log)),
+ ssl_socket_pool_(new SSLClientSocketPool(
+ g_max_sockets, g_max_sockets_per_group, ssl_pool_histograms_,
+ host_resolver, client_socket_factory, tcp_socket_pool_, NULL,
+ NULL, net_log)),
socket_factory_(client_socket_factory),
host_resolver_(host_resolver),
proxy_service_(proxy_service),
ssl_config_service_(ssl_config_service),
- flip_session_pool_(flip_session_pool) {
+ spdy_session_pool_(spdy_session_pool),
+ http_auth_handler_factory_(http_auth_handler_factory),
+ network_delegate_(network_delegate),
+ net_log_(net_log) {
DCHECK(proxy_service);
DCHECK(ssl_config_service);
}
@@ -42,20 +74,98 @@
HttpNetworkSession::~HttpNetworkSession() {
}
+const scoped_refptr<HttpProxyClientSocketPool>&
+HttpNetworkSession::GetSocketPoolForHTTPProxy(const HostPortPair& http_proxy) {
+ HTTPProxySocketPoolMap::const_iterator it =
+ http_proxy_socket_pools_.find(http_proxy);
+ if (it != http_proxy_socket_pools_.end())
+ return it->second;
+
+ std::pair<HTTPProxySocketPoolMap::iterator, bool> ret =
+ http_proxy_socket_pools_.insert(
+ std::make_pair(
+ http_proxy,
+ new HttpProxyClientSocketPool(
+ g_max_sockets_per_proxy_server, g_max_sockets_per_group,
+ http_proxy_pool_histograms_, host_resolver_,
+ new TCPClientSocketPool(
+ g_max_sockets_per_proxy_server, g_max_sockets_per_group,
+ tcp_for_http_proxy_pool_histograms_, host_resolver_,
+ socket_factory_, net_log_),
+ net_log_)));
+
+ return ret.first->second;
+}
+
+const scoped_refptr<SOCKSClientSocketPool>&
+HttpNetworkSession::GetSocketPoolForSOCKSProxy(
+ const HostPortPair& socks_proxy) {
+ SOCKSSocketPoolMap::const_iterator it = socks_socket_pools_.find(socks_proxy);
+ if (it != socks_socket_pools_.end())
+ return it->second;
+
+ std::pair<SOCKSSocketPoolMap::iterator, bool> ret =
+ socks_socket_pools_.insert(
+ std::make_pair(socks_proxy, new SOCKSClientSocketPool(
+ g_max_sockets_per_proxy_server, g_max_sockets_per_group,
+ socks_pool_histograms_, host_resolver_,
+ new TCPClientSocketPool(g_max_sockets_per_proxy_server,
+ g_max_sockets_per_group, tcp_for_socks_pool_histograms_,
+ host_resolver_, socket_factory_, net_log_),
+ net_log_)));
+
+ return ret.first->second;
+}
+
+const scoped_refptr<SSLClientSocketPool>&
+HttpNetworkSession::GetSocketPoolForSSLWithProxy(
+ const HostPortPair& proxy_server) {
+ SSLSocketPoolMap::const_iterator it =
+ ssl_socket_pools_for_proxies_.find(proxy_server);
+ if (it != ssl_socket_pools_for_proxies_.end())
+ return it->second;
+
+ SSLClientSocketPool* new_pool = new SSLClientSocketPool(
+ g_max_sockets_per_proxy_server, g_max_sockets_per_group,
+ ssl_pool_histograms_, host_resolver_, socket_factory_,
+ NULL,
+ GetSocketPoolForHTTPProxy(proxy_server),
+ GetSocketPoolForSOCKSProxy(proxy_server),
+ net_log_);
+
+ std::pair<SSLSocketPoolMap::iterator, bool> ret =
+ ssl_socket_pools_for_proxies_.insert(std::make_pair(proxy_server,
+ new_pool));
+
+ return ret.first->second;
+}
+
// static
void HttpNetworkSession::set_max_sockets_per_group(int socket_count) {
- DCHECK(0 < socket_count);
+ DCHECK_LT(0, socket_count);
// The following is a sanity check... but we should NEVER be near this value.
- DCHECK(100 > socket_count);
- max_sockets_per_group_ = socket_count;
+ DCHECK_GT(100, socket_count);
+ g_max_sockets_per_group = socket_count;
}
-void HttpNetworkSession::ReplaceTCPSocketPool() {
- tcp_socket_pool_ = new TCPClientSocketPool(max_sockets_,
- max_sockets_per_group_,
- host_resolver_,
- socket_factory_,
- network_change_notifier_);
+// static
+uint16 HttpNetworkSession::fixed_http_port() {
+ return g_fixed_http_port;
}
-} // namespace net
+// static
+void HttpNetworkSession::set_fixed_http_port(uint16 port) {
+ g_fixed_http_port = port;
+}
+
+// static
+uint16 HttpNetworkSession::fixed_https_port() {
+ return g_fixed_https_port;
+}
+
+// static
+void HttpNetworkSession::set_fixed_https_port(uint16 port) {
+ g_fixed_https_port = port;
+}
+
+} // namespace net
diff --git a/net/http/http_network_session.h b/net/http/http_network_session.h
index 32bcdee..de57eba 100644
--- a/net/http/http_network_session.h
+++ b/net/http/http_network_session.h
@@ -1,89 +1,176 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// 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.
#ifndef NET_HTTP_HTTP_NETWORK_SESSION_H_
#define NET_HTTP_HTTP_NETWORK_SESSION_H_
+#include <map>
+#include "base/non_thread_safe.h"
#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "net/base/host_port_pair.h"
#include "net/base/host_resolver.h"
#include "net/base/ssl_client_auth_cache.h"
#include "net/base/ssl_config_service.h"
+#include "net/http/http_alternate_protocols.h"
#include "net/http/http_auth_cache.h"
+#include "net/http/http_network_delegate.h"
+#include "net/http/http_network_transaction.h"
+#include "net/http/http_proxy_client_socket_pool.h"
#include "net/proxy/proxy_service.h"
+#include "net/socket/client_socket_pool_histograms.h"
+#include "net/socket/socks_client_socket_pool.h"
+#include "net/socket/ssl_client_socket_pool.h"
#include "net/socket/tcp_client_socket_pool.h"
+#include "net/spdy/spdy_settings_storage.h"
namespace net {
class ClientSocketFactory;
-class FlipSessionPool;
-class NetworkChangeNotifier;
+class HttpAuthHandlerFactory;
+class HttpNetworkDelegate;
+class HttpNetworkSessionPeer;
+class SpdySessionPool;
// This class holds session objects used by HttpNetworkTransaction objects.
-class HttpNetworkSession : public base::RefCounted<HttpNetworkSession> {
+class HttpNetworkSession : public base::RefCounted<HttpNetworkSession>,
+ public NonThreadSafe {
public:
HttpNetworkSession(
- NetworkChangeNotifier* network_change_notifier,
HostResolver* host_resolver,
ProxyService* proxy_service,
ClientSocketFactory* client_socket_factory,
SSLConfigService* ssl_config_service,
- FlipSessionPool* flip_session_pool);
+ SpdySessionPool* spdy_session_pool,
+ HttpAuthHandlerFactory* http_auth_handler_factory,
+ HttpNetworkDelegate* network_delegate,
+ NetLog* net_log);
HttpAuthCache* auth_cache() { return &auth_cache_; }
SSLClientAuthCache* ssl_client_auth_cache() {
return &ssl_client_auth_cache_;
}
+ const HttpAlternateProtocols& alternate_protocols() const {
+ return alternate_protocols_;
+ }
+ HttpAlternateProtocols* mutable_alternate_protocols() {
+ return &alternate_protocols_;
+ }
+
+ // Access to the SpdySettingsStorage
+ const SpdySettingsStorage& spdy_settings() const {
+ return spdy_settings_;
+ }
+ SpdySettingsStorage* mutable_spdy_settings() {
+ return &spdy_settings_;
+ }
+
// TCP sockets come from the tcp_socket_pool().
- TCPClientSocketPool* tcp_socket_pool() { return tcp_socket_pool_; }
- // SSL sockets come frmo the socket_factory().
+ const scoped_refptr<TCPClientSocketPool>& tcp_socket_pool() {
+ return tcp_socket_pool_;
+ }
+
+ const scoped_refptr<SSLClientSocketPool>& ssl_socket_pool() {
+ return ssl_socket_pool_;
+ }
+
+ const scoped_refptr<SOCKSClientSocketPool>& GetSocketPoolForSOCKSProxy(
+ const HostPortPair& socks_proxy);
+
+ const scoped_refptr<HttpProxyClientSocketPool>& GetSocketPoolForHTTPProxy(
+ const HostPortPair& http_proxy);
+
+ const scoped_refptr<SSLClientSocketPool>& GetSocketPoolForSSLWithProxy(
+ const HostPortPair& proxy_server);
+
+ // SSL sockets come from the socket_factory().
ClientSocketFactory* socket_factory() { return socket_factory_; }
HostResolver* host_resolver() { return host_resolver_; }
ProxyService* proxy_service() { return proxy_service_; }
SSLConfigService* ssl_config_service() { return ssl_config_service_; }
- const scoped_refptr<FlipSessionPool>& flip_session_pool() {
- return flip_session_pool_;
+ const scoped_refptr<SpdySessionPool>& spdy_session_pool() {
+ return spdy_session_pool_;
}
-
- // Replace the current socket pool with a new one. This effectively
- // abandons the current pool. This is only used for debugging.
- void ReplaceTCPSocketPool();
+ HttpAuthHandlerFactory* http_auth_handler_factory() {
+ return http_auth_handler_factory_;
+ }
+ HttpNetworkDelegate* network_delegate() {
+ return network_delegate_;
+ }
static void set_max_sockets_per_group(int socket_count);
- static uint16 fixed_http_port() { return g_fixed_http_port; }
- static void set_fixed_http_port(uint16 port) { g_fixed_http_port = port; }
+ static uint16 fixed_http_port();
+ static void set_fixed_http_port(uint16 port);
- static uint16 fixed_https_port() { return g_fixed_https_port; }
- static void set_fixed_https_port(uint16 port) { g_fixed_https_port = port; }
+ static uint16 fixed_https_port();
+ static void set_fixed_https_port(uint16 port);
+
+#ifdef UNIT_TEST
+ void FlushSocketPools() {
+ if (ssl_socket_pool_.get())
+ ssl_socket_pool_->Flush();
+ if (tcp_socket_pool_.get())
+ tcp_socket_pool_->Flush();
+
+ for (SSLSocketPoolMap::const_iterator it =
+ ssl_socket_pools_for_proxies_.begin();
+ it != ssl_socket_pools_for_proxies_.end();
+ it++)
+ it->second->Flush();
+
+ for (SOCKSSocketPoolMap::const_iterator it =
+ socks_socket_pools_.begin();
+ it != socks_socket_pools_.end();
+ it++)
+ it->second->Flush();
+
+ for (HTTPProxySocketPoolMap::const_iterator it =
+ http_proxy_socket_pools_.begin();
+ it != http_proxy_socket_pools_.end();
+ it++)
+ it->second->Flush();
+ }
+#endif
private:
+ typedef std::map<HostPortPair, scoped_refptr<HttpProxyClientSocketPool> >
+ HTTPProxySocketPoolMap;
+ typedef std::map<HostPortPair, scoped_refptr<SOCKSClientSocketPool> >
+ SOCKSSocketPoolMap;
+ typedef std::map<HostPortPair, scoped_refptr<SSLClientSocketPool> >
+ SSLSocketPoolMap;
+
friend class base::RefCounted<HttpNetworkSession>;
- FRIEND_TEST(HttpNetworkTransactionTest, GroupNameForProxyConnections);
+ friend class HttpNetworkSessionPeer;
~HttpNetworkSession();
- // Total limit of sockets. Not a constant to allow experiments.
- static int max_sockets_;
-
- // Default to allow up to 6 connections per host. Experiment and tuning may
- // try other values (greater than 0). Too large may cause many problems, such
- // as home routers blocking the connections!?!?
- static int max_sockets_per_group_;
-
- static uint16 g_fixed_http_port;
- static uint16 g_fixed_https_port;
-
HttpAuthCache auth_cache_;
SSLClientAuthCache ssl_client_auth_cache_;
- NetworkChangeNotifier* const network_change_notifier_;
+ HttpAlternateProtocols alternate_protocols_;
+ scoped_refptr<ClientSocketPoolHistograms> tcp_pool_histograms_;
+ scoped_refptr<ClientSocketPoolHistograms> tcp_for_http_proxy_pool_histograms_;
+ scoped_refptr<ClientSocketPoolHistograms> http_proxy_pool_histograms_;
+ scoped_refptr<ClientSocketPoolHistograms> tcp_for_socks_pool_histograms_;
+ scoped_refptr<ClientSocketPoolHistograms> socks_pool_histograms_;
+ scoped_refptr<ClientSocketPoolHistograms> ssl_pool_histograms_;
scoped_refptr<TCPClientSocketPool> tcp_socket_pool_;
+ scoped_refptr<SSLClientSocketPool> ssl_socket_pool_;
+ HTTPProxySocketPoolMap http_proxy_socket_pools_;
+ SOCKSSocketPoolMap socks_socket_pools_;
+ SSLSocketPoolMap ssl_socket_pools_for_proxies_;
ClientSocketFactory* socket_factory_;
scoped_refptr<HostResolver> host_resolver_;
scoped_refptr<ProxyService> proxy_service_;
scoped_refptr<SSLConfigService> ssl_config_service_;
- scoped_refptr<FlipSessionPool> flip_session_pool_;
+ scoped_refptr<SpdySessionPool> spdy_session_pool_;
+ HttpAuthHandlerFactory* http_auth_handler_factory_;
+ HttpNetworkDelegate* const network_delegate_;
+ NetLog* net_log_;
+ SpdySettingsStorage spdy_settings_;
};
} // namespace net
diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc
index 4be555f..a85a1ca 100644
--- a/net/http/http_network_transaction.cc
+++ b/net/http/http_network_transaction.cc
@@ -1,41 +1,51 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// 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 "net/http/http_network_transaction.h"
-#include "base/format_macros.h"
-#include "base/scoped_ptr.h"
#include "base/compiler_specific.h"
#include "base/field_trial.h"
+#include "base/format_macros.h"
#include "base/histogram.h"
+#include "base/scoped_ptr.h"
#include "base/stats_counters.h"
+#include "base/stl_util-inl.h"
#include "base/string_util.h"
-#include "base/trace_event.h"
#include "build/build_config.h"
+#include "googleurl/src/gurl.h"
#include "net/base/connection_type_histograms.h"
+#include "net/base/host_mapping_rules.h"
#include "net/base/io_buffer.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/base/net_util.h"
#include "net/base/ssl_cert_request_info.h"
+#include "net/base/ssl_connection_status_flags.h"
#include "net/base/upload_data_stream.h"
-#include "net/flip/flip_session.h"
-#include "net/flip/flip_session_pool.h"
-#include "net/flip/flip_stream.h"
#include "net/http/http_auth.h"
#include "net/http/http_auth_handler.h"
+#include "net/http/http_auth_handler_factory.h"
#include "net/http/http_basic_stream.h"
#include "net/http/http_chunked_decoder.h"
+#include "net/http/http_net_log_params.h"
#include "net/http/http_network_session.h"
+#include "net/http/http_proxy_client_socket.h"
+#include "net/http/http_proxy_client_socket_pool.h"
+#include "net/http/http_request_headers.h"
#include "net/http/http_request_info.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_response_info.h"
#include "net/http/http_util.h"
+#include "net/http/url_security_manager.h"
#include "net/socket/client_socket_factory.h"
-#include "net/socket/socks5_client_socket.h"
-#include "net/socket/socks_client_socket.h"
+#include "net/socket/socks_client_socket_pool.h"
#include "net/socket/ssl_client_socket.h"
+#include "net/socket/ssl_client_socket_pool.h"
+#include "net/socket/tcp_client_socket_pool.h"
+#include "net/spdy/spdy_http_stream.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_session_pool.h"
using base::Time;
@@ -43,95 +53,143 @@
namespace {
+const HostMappingRules* g_host_mapping_rules = NULL;
+const std::string* g_next_protos = NULL;
+bool g_use_alternate_protocols = false;
+
+// A set of host:port strings. These are servers which we have needed to back
+// off to SSLv3 for.
+std::set<std::string>* g_tls_intolerant_servers = NULL;
+
void BuildRequestHeaders(const HttpRequestInfo* request_info,
- const std::string& authorization_headers,
+ const HttpRequestHeaders& authorization_headers,
const UploadDataStream* upload_data_stream,
bool using_proxy,
- std::string* request_headers) {
+ std::string* request_line,
+ HttpRequestHeaders* request_headers) {
const std::string path = using_proxy ?
- HttpUtil::SpecForRequest(request_info->url) :
- HttpUtil::PathForRequest(request_info->url);
- *request_headers =
- StringPrintf("%s %s HTTP/1.1\r\nHost: %s\r\n",
- request_info->method.c_str(), path.c_str(),
- GetHostAndOptionalPort(request_info->url).c_str());
+ HttpUtil::SpecForRequest(request_info->url) :
+ HttpUtil::PathForRequest(request_info->url);
+ *request_line = StringPrintf(
+ "%s %s HTTP/1.1\r\n", request_info->method.c_str(), path.c_str());
+ request_headers->SetHeader(HttpRequestHeaders::kHost,
+ GetHostAndOptionalPort(request_info->url));
// For compat with HTTP/1.0 servers and proxies:
- if (using_proxy)
- *request_headers += "Proxy-";
- *request_headers += "Connection: keep-alive\r\n";
-
- if (!request_info->user_agent.empty()) {
- StringAppendF(request_headers, "User-Agent: %s\r\n",
- request_info->user_agent.c_str());
+ if (using_proxy) {
+ request_headers->SetHeader(HttpRequestHeaders::kProxyConnection,
+ "keep-alive");
+ } else {
+ request_headers->SetHeader(HttpRequestHeaders::kConnection, "keep-alive");
}
// Our consumer should have made sure that this is a safe referrer. See for
// instance WebCore::FrameLoader::HideReferrer.
- if (request_info->referrer.is_valid())
- StringAppendF(request_headers, "Referer: %s\r\n",
- request_info->referrer.spec().c_str());
+ if (request_info->referrer.is_valid()) {
+ request_headers->SetHeader(HttpRequestHeaders::kReferer,
+ request_info->referrer.spec());
+ }
// Add a content length header?
if (upload_data_stream) {
- StringAppendF(request_headers, "Content-Length: %" PRIu64 "\r\n",
- upload_data_stream->size());
+ request_headers->SetHeader(
+ HttpRequestHeaders::kContentLength,
+ Uint64ToString(upload_data_stream->size()));
} else if (request_info->method == "POST" || request_info->method == "PUT" ||
request_info->method == "HEAD") {
// An empty POST/PUT request still needs a content length. As for HEAD,
// IE and Safari also add a content length header. Presumably it is to
// support sending a HEAD request to an URL that only expects to be sent a
// POST or some other method that normally would have a message body.
- *request_headers += "Content-Length: 0\r\n";
+ request_headers->SetHeader(HttpRequestHeaders::kContentLength, "0");
}
// Honor load flags that impact proxy caches.
if (request_info->load_flags & LOAD_BYPASS_CACHE) {
- *request_headers += "Pragma: no-cache\r\nCache-Control: no-cache\r\n";
+ request_headers->SetHeader(HttpRequestHeaders::kPragma, "no-cache");
+ request_headers->SetHeader(HttpRequestHeaders::kCacheControl, "no-cache");
} else if (request_info->load_flags & LOAD_VALIDATE_CACHE) {
- *request_headers += "Cache-Control: max-age=0\r\n";
+ request_headers->SetHeader(HttpRequestHeaders::kCacheControl, "max-age=0");
}
- if (!authorization_headers.empty()) {
- *request_headers += authorization_headers;
- }
+ request_headers->MergeFrom(authorization_headers);
- // TODO(darin): Need to prune out duplicate headers.
+ // Headers that will be stripped from request_info->extra_headers to prevent,
+ // e.g., plugins from overriding headers that are controlled using other
+ // means. Otherwise a plugin could set a referrer although sending the
+ // referrer is inhibited.
+ // TODO(jochen): check whether also other headers should be stripped.
+ static const char* const kExtraHeadersToBeStripped[] = {
+ "Referer"
+ };
- *request_headers += request_info->extra_headers;
- *request_headers += "\r\n";
+ HttpRequestHeaders stripped_extra_headers;
+ stripped_extra_headers.CopyFrom(request_info->extra_headers);
+ for (size_t i = 0; i < arraysize(kExtraHeadersToBeStripped); ++i)
+ stripped_extra_headers.RemoveHeader(kExtraHeadersToBeStripped[i]);
+ request_headers->MergeFrom(stripped_extra_headers);
}
-// The HTTP CONNECT method for establishing a tunnel connection is documented
-// in draft-luotonen-web-proxy-tunneling-01.txt and RFC 2817, Sections 5.2 and
-// 5.3.
-void BuildTunnelRequest(const HttpRequestInfo* request_info,
- const std::string& authorization_headers,
- std::string* request_headers) {
- // RFC 2616 Section 9 says the Host request-header field MUST accompany all
- // HTTP/1.1 requests. Add "Proxy-Connection: keep-alive" for compat with
- // HTTP/1.0 proxies such as Squid (required for NTLM authentication).
- *request_headers = StringPrintf(
- "CONNECT %s HTTP/1.1\r\nHost: %s\r\nProxy-Connection: keep-alive\r\n",
- GetHostAndPort(request_info->url).c_str(),
- GetHostAndOptionalPort(request_info->url).c_str());
+void ProcessAlternateProtocol(const HttpResponseHeaders& headers,
+ const HostPortPair& http_host_port_pair,
+ HttpAlternateProtocols* alternate_protocols) {
- if (!request_info->user_agent.empty())
- StringAppendF(request_headers, "User-Agent: %s\r\n",
- request_info->user_agent.c_str());
-
- if (!authorization_headers.empty()) {
- *request_headers += authorization_headers;
+ std::string alternate_protocol_str;
+ if (!headers.EnumerateHeader(NULL, HttpAlternateProtocols::kHeader,
+ &alternate_protocol_str)) {
+ // Header is not present.
+ return;
}
- *request_headers += "\r\n";
+ std::vector<std::string> port_protocol_vector;
+ SplitString(alternate_protocol_str, ':', &port_protocol_vector);
+ if (port_protocol_vector.size() != 2) {
+ DLOG(WARNING) << HttpAlternateProtocols::kHeader
+ << " header has too many tokens: "
+ << alternate_protocol_str;
+ return;
+ }
+
+ int port;
+ if (!StringToInt(port_protocol_vector[0], &port) ||
+ port <= 0 || port >= 1 << 16) {
+ DLOG(WARNING) << HttpAlternateProtocols::kHeader
+ << " header has unrecognizable port: "
+ << port_protocol_vector[0];
+ return;
+ }
+
+ if (port_protocol_vector[1] !=
+ HttpAlternateProtocols::kProtocolStrings[
+ HttpAlternateProtocols::NPN_SPDY_1]) {
+ // Currently, we only recognize the npn-spdy protocol.
+ DLOG(WARNING) << HttpAlternateProtocols::kHeader
+ << " header has unrecognized protocol: "
+ << port_protocol_vector[1];
+ return;
+ }
+
+ HostPortPair host_port(http_host_port_pair);
+ if (g_host_mapping_rules)
+ g_host_mapping_rules->RewriteHost(&host_port);
+
+ if (alternate_protocols->HasAlternateProtocolFor(host_port)) {
+ const HttpAlternateProtocols::PortProtocolPair existing_alternate =
+ alternate_protocols->GetAlternateProtocolFor(host_port);
+ // If we think the alternate protocol is broken, don't change it.
+ if (existing_alternate.protocol == HttpAlternateProtocols::BROKEN)
+ return;
+ }
+
+ alternate_protocols->SetAlternateProtocolFor(
+ host_port, port, HttpAlternateProtocols::NPN_SPDY_1);
}
} // namespace
//-----------------------------------------------------------------------------
-std::string* HttpNetworkTransaction::g_next_protos = NULL;
+bool HttpNetworkTransaction::g_ignore_certificate_errors = false;
HttpNetworkTransaction::HttpNetworkTransaction(HttpNetworkSession* session)
: pending_auth_target_(HttpAuth::AUTH_NONE),
@@ -144,16 +202,34 @@
connection_(new ClientSocketHandle),
reused_socket_(false),
headers_valid_(false),
- logged_response_time(false),
+ logged_response_time_(false),
using_ssl_(false),
- proxy_mode_(kDirectConnection),
- establishing_tunnel_(false),
- embedded_identity_used_(false),
+ using_spdy_(false),
+ spdy_certificate_error_(OK),
+ alternate_protocol_mode_(
+ g_use_alternate_protocols ? kUnspecified :
+ kDoNotUseAlternateProtocol),
read_buf_len_(0),
- next_state_(STATE_NONE) {
+ next_state_(STATE_NONE),
+ establishing_tunnel_(false) {
session->ssl_config_service()->GetSSLConfig(&ssl_config_);
if (g_next_protos)
ssl_config_.next_protos = *g_next_protos;
+ if (!g_tls_intolerant_servers)
+ g_tls_intolerant_servers = new std::set<std::string>;
+}
+
+// static
+void HttpNetworkTransaction::SetHostMappingRules(const std::string& rules) {
+ HostMappingRules* host_mapping_rules = new HostMappingRules();
+ host_mapping_rules->SetRulesFromString(rules);
+ delete g_host_mapping_rules;
+ g_host_mapping_rules = host_mapping_rules;
+}
+
+// static
+void HttpNetworkTransaction::SetUseAlternateProtocols(bool value) {
+ g_use_alternate_protocols = value;
}
// static
@@ -162,12 +238,17 @@
g_next_protos = new std::string(next_protos);
}
+// static
+void HttpNetworkTransaction::IgnoreCertificateErrors(bool enabled) {
+ g_ignore_certificate_errors = enabled;
+}
+
int HttpNetworkTransaction::Start(const HttpRequestInfo* request_info,
CompletionCallback* callback,
- LoadLog* load_log) {
+ const BoundNetLog& net_log) {
SIMPLE_STATS_COUNTER("HttpNetworkTransaction.Count");
- load_log_ = load_log;
+ net_log_ = net_log;
request_ = request_info;
start_time_ = base::Time::Now();
@@ -180,10 +261,18 @@
int HttpNetworkTransaction::RestartIgnoringLastError(
CompletionCallback* callback) {
- if (connection_->socket()->IsConnectedAndIdle()) {
- next_state_ = STATE_SEND_REQUEST;
+ if (connection_->socket() && connection_->socket()->IsConnectedAndIdle()) {
+ // TODO(wtc): Should we update any of the connection histograms that we
+ // update in DoSSLConnectComplete if |result| is OK?
+ if (using_spdy_) {
+ // TODO(cbentzel): Add auth support to spdy. See http://crbug.com/46620
+ next_state_ = STATE_SPDY_GET_STREAM;
+ } else {
+ next_state_ = STATE_GENERATE_PROXY_AUTH_TOKEN;
+ }
} else {
- connection_->socket()->Disconnect();
+ if (connection_->socket())
+ connection_->socket()->Disconnect();
connection_->Reset();
next_state_ = STATE_INIT_CONNECTION;
}
@@ -221,22 +310,18 @@
NOTREACHED();
return ERR_UNEXPECTED;
}
-
pending_auth_target_ = HttpAuth::AUTH_NONE;
- DCHECK(auth_identity_[target].invalid ||
- (username.empty() && password.empty()));
+ auth_controllers_[target]->ResetAuth(username, password);
- if (auth_identity_[target].invalid) {
- // Update the username/password.
- auth_identity_[target].source = HttpAuth::IDENT_SRC_EXTERNAL;
- auth_identity_[target].invalid = false;
- auth_identity_[target].username = username;
- auth_identity_[target].password = password;
+ if (target == HttpAuth::AUTH_PROXY && using_ssl_ && proxy_info_.is_http()) {
+ DCHECK(establishing_tunnel_);
+ next_state_ = STATE_INIT_CONNECTION;
+ ResetStateForRestart();
+ } else {
+ PrepareForAuthRestart(target);
}
- PrepareForAuthRestart(target);
-
DCHECK(user_callback_ == NULL);
int rv = DoLoop(OK);
if (rv == ERR_IO_PENDING)
@@ -247,29 +332,7 @@
void HttpNetworkTransaction::PrepareForAuthRestart(HttpAuth::Target target) {
DCHECK(HaveAuth(target));
- DCHECK(auth_identity_[target].source != HttpAuth::IDENT_SRC_PATH_LOOKUP);
-
- // Add the auth entry to the cache before restarting. We don't know whether
- // the identity is valid yet, but if it is valid we want other transactions
- // to know about it. If an entry for (origin, handler->realm()) already
- // exists, we update it.
- //
- // If auth_identity_[target].source is HttpAuth::IDENT_SRC_NONE,
- // auth_identity_[target] contains no identity because identity is not
- // required yet.
- //
- // TODO(wtc): For NTLM_SSPI, we add the same auth entry to the cache in
- // round 1 and round 2, which is redundant but correct. It would be nice
- // to add an auth entry to the cache only once, preferrably in round 1.
- // See http://crbug.com/21015.
- bool has_auth_identity =
- auth_identity_[target].source != HttpAuth::IDENT_SRC_NONE;
- if (has_auth_identity) {
- session_->auth_cache()->Add(AuthOrigin(target), auth_handler_[target],
- auth_identity_[target].username, auth_identity_[target].password,
- AuthPath(target));
- }
-
+ DCHECK(!establishing_tunnel_);
bool keep_alive = false;
// Even if the server says the connection is keep-alive, we have to be
// able to find the end of each response in order to reuse the connection.
@@ -292,10 +355,11 @@
}
void HttpNetworkTransaction::DidDrainBodyForAuthRestart(bool keep_alive) {
+ DCHECK(!establishing_tunnel_);
if (keep_alive && connection_->socket()->IsConnectedAndIdle()) {
// We should call connection_->set_idle_time(), but this doesn't occur
// often enough to be worth the trouble.
- next_state_ = STATE_SEND_REQUEST;
+ next_state_ = STATE_GENERATE_PROXY_AUTH_TOKEN;
connection_->set_is_reused(true);
reused_socket_ = true;
} else {
@@ -315,31 +379,34 @@
State next_state = STATE_NONE;
+ scoped_refptr<HttpResponseHeaders> headers = GetResponseHeaders();
+ if (headers_valid_ && headers.get() && establishing_tunnel_) {
+ // We're trying to read the body of the response but we're still trying
+ // to establish an SSL tunnel through the proxy. We can't read these
+ // bytes when establishing a tunnel because they might be controlled by
+ // an active network attacker. We don't worry about this for HTTP
+ // because an active network attacker can already control HTTP sessions.
+ // We reach this case when the user cancels a 407 proxy auth prompt.
+ // See http://crbug.com/8473.
+ DCHECK(proxy_info_.is_http());
+ DCHECK_EQ(headers->response_code(), 407);
+ LOG(WARNING) << "Blocked proxy response with status "
+ << headers->response_code() << " to CONNECT request for "
+ << GetHostAndPort(request_->url) << ".";
+ return ERR_TUNNEL_CONNECTION_FAILED;
+ }
+
// Are we using SPDY or HTTP?
- if (spdy_stream_.get()) {
+ if (using_spdy_) {
DCHECK(!http_stream_.get());
- DCHECK(spdy_stream_->GetResponseInfo()->headers);
+ DCHECK(spdy_http_stream_->GetResponseInfo()->headers);
next_state = STATE_SPDY_READ_BODY;
} else {
- scoped_refptr<HttpResponseHeaders> headers = GetResponseHeaders();
- DCHECK(headers.get());
+ DCHECK(!spdy_http_stream_.get());
next_state = STATE_READ_BODY;
if (!connection_->is_initialized())
- return 0; // connection_->has been reset. Treat like EOF.
-
- if (establishing_tunnel_) {
- // We're trying to read the body of the response but we're still trying
- // to establish an SSL tunnel through the proxy. We can't read these
- // bytes when establishing a tunnel because they might be controlled by
- // an active network attacker. We don't worry about this for HTTP
- // because an active network attacker can already control HTTP sessions.
- // We reach this case when the user cancels a 407 proxy auth prompt.
- // See http://crbug.com/8473.
- DCHECK_EQ(407, headers->response_code());
- LogBlockedTunnelResponse(headers->response_code());
- return ERR_TUNNEL_CONNECTION_FAILED;
- }
+ return 0; // |*connection_| has been reset. Treat like EOF.
}
read_buf_ = buf;
@@ -365,11 +432,17 @@
return LOAD_STATE_RESOLVING_PROXY_FOR_URL;
case STATE_INIT_CONNECTION_COMPLETE:
return connection_->GetLoadState();
+ case STATE_GENERATE_PROXY_AUTH_TOKEN_COMPLETE:
+ case STATE_GENERATE_SERVER_AUTH_TOKEN_COMPLETE:
case STATE_SEND_REQUEST_COMPLETE:
+ case STATE_SPDY_GET_STREAM:
+ case STATE_SPDY_SEND_REQUEST_COMPLETE:
return LOAD_STATE_SENDING_REQUEST;
case STATE_READ_HEADERS_COMPLETE:
+ case STATE_SPDY_READ_HEADERS_COMPLETE:
return LOAD_STATE_WAITING_FOR_RESPONSE;
case STATE_READ_BODY_COMPLETE:
+ case STATE_SPDY_READ_BODY_COMPLETE:
return LOAD_STATE_READING_RESPONSE;
default:
return LOAD_STATE_IDLE;
@@ -385,15 +458,25 @@
HttpNetworkTransaction::~HttpNetworkTransaction() {
// If we still have an open socket, then make sure to disconnect it so it
- // won't call us back and we don't try to reuse it later on.
- if (connection_.get() && connection_->is_initialized())
- connection_->socket()->Disconnect();
+ // won't call us back and we don't try to reuse it later on. However,
+ // don't close the socket if we should keep the connection alive.
+ if (connection_.get() && connection_->is_initialized()) {
+ // The STATE_NONE check guarantees there are no pending socket IOs that
+ // could try to call this object back after it is deleted.
+ bool keep_alive = next_state_ == STATE_NONE &&
+ http_stream_.get() &&
+ http_stream_->IsResponseBodyComplete() &&
+ http_stream_->CanFindEndOfResponse() &&
+ GetResponseHeaders()->IsKeepAlive();
+ if (!keep_alive)
+ connection_->socket()->Disconnect();
+ }
if (pac_request_)
session_->proxy_service()->CancelPacRequest(pac_request_);
- if (spdy_stream_.get())
- spdy_stream_->Cancel();
+ if (spdy_http_stream_.get())
+ spdy_http_stream_->Cancel();
}
void HttpNetworkTransaction::DoCallback(int rv) {
@@ -422,134 +505,103 @@
switch (state) {
case STATE_RESOLVE_PROXY:
DCHECK_EQ(OK, rv);
- TRACE_EVENT_BEGIN("http.resolve_proxy", request_, request_->url.spec());
rv = DoResolveProxy();
break;
case STATE_RESOLVE_PROXY_COMPLETE:
rv = DoResolveProxyComplete(rv);
- TRACE_EVENT_END("http.resolve_proxy", request_, request_->url.spec());
break;
case STATE_INIT_CONNECTION:
DCHECK_EQ(OK, rv);
- TRACE_EVENT_BEGIN("http.init_conn", request_, request_->url.spec());
rv = DoInitConnection();
break;
case STATE_INIT_CONNECTION_COMPLETE:
rv = DoInitConnectionComplete(rv);
- TRACE_EVENT_END("http.init_conn", request_, request_->url.spec());
break;
- case STATE_SOCKS_CONNECT:
+ case STATE_GENERATE_PROXY_AUTH_TOKEN:
DCHECK_EQ(OK, rv);
- TRACE_EVENT_BEGIN("http.socks_connect", request_, request_->url.spec());
- rv = DoSOCKSConnect();
+ rv = DoGenerateProxyAuthToken();
break;
- case STATE_SOCKS_CONNECT_COMPLETE:
- rv = DoSOCKSConnectComplete(rv);
- TRACE_EVENT_END("http.socks_connect", request_, request_->url.spec());
+ case STATE_GENERATE_PROXY_AUTH_TOKEN_COMPLETE:
+ rv = DoGenerateProxyAuthTokenComplete(rv);
break;
- case STATE_SSL_CONNECT:
+ case STATE_GENERATE_SERVER_AUTH_TOKEN:
DCHECK_EQ(OK, rv);
- TRACE_EVENT_BEGIN("http.ssl_connect", request_, request_->url.spec());
- rv = DoSSLConnect();
+ rv = DoGenerateServerAuthToken();
break;
- case STATE_SSL_CONNECT_COMPLETE:
- rv = DoSSLConnectComplete(rv);
- TRACE_EVENT_END("http.ssl_connect", request_, request_->url.spec());
+ case STATE_GENERATE_SERVER_AUTH_TOKEN_COMPLETE:
+ rv = DoGenerateServerAuthTokenComplete(rv);
break;
case STATE_SEND_REQUEST:
DCHECK_EQ(OK, rv);
- TRACE_EVENT_BEGIN("http.send_request", request_, request_->url.spec());
- LoadLog::BeginEvent(load_log_,
- LoadLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST);
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST, NULL);
rv = DoSendRequest();
break;
case STATE_SEND_REQUEST_COMPLETE:
rv = DoSendRequestComplete(rv);
- TRACE_EVENT_END("http.send_request", request_, request_->url.spec());
- LoadLog::EndEvent(load_log_,
- LoadLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST);
+ net_log_.EndEvent(NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST, NULL);
break;
case STATE_READ_HEADERS:
DCHECK_EQ(OK, rv);
- TRACE_EVENT_BEGIN("http.read_headers", request_, request_->url.spec());
- LoadLog::BeginEvent(load_log_,
- LoadLog::TYPE_HTTP_TRANSACTION_READ_HEADERS);
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_TRANSACTION_READ_HEADERS, NULL);
rv = DoReadHeaders();
break;
case STATE_READ_HEADERS_COMPLETE:
rv = DoReadHeadersComplete(rv);
- TRACE_EVENT_END("http.read_headers", request_, request_->url.spec());
- LoadLog::EndEvent(load_log_,
- LoadLog::TYPE_HTTP_TRANSACTION_READ_HEADERS);
+ net_log_.EndEvent(NetLog::TYPE_HTTP_TRANSACTION_READ_HEADERS, NULL);
break;
case STATE_READ_BODY:
DCHECK_EQ(OK, rv);
- TRACE_EVENT_BEGIN("http.read_body", request_, request_->url.spec());
- LoadLog::BeginEvent(load_log_,
- LoadLog::TYPE_HTTP_TRANSACTION_READ_BODY);
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_TRANSACTION_READ_BODY, NULL);
rv = DoReadBody();
break;
case STATE_READ_BODY_COMPLETE:
rv = DoReadBodyComplete(rv);
- TRACE_EVENT_END("http.read_body", request_, request_->url.spec());
- LoadLog::EndEvent(load_log_,
- LoadLog::TYPE_HTTP_TRANSACTION_READ_BODY);
+ net_log_.EndEvent(NetLog::TYPE_HTTP_TRANSACTION_READ_BODY, NULL);
break;
case STATE_DRAIN_BODY_FOR_AUTH_RESTART:
DCHECK_EQ(OK, rv);
- TRACE_EVENT_BEGIN("http.drain_body_for_auth_restart",
- request_, request_->url.spec());
- LoadLog::BeginEvent(
- load_log_,
- LoadLog::TYPE_HTTP_TRANSACTION_DRAIN_BODY_FOR_AUTH_RESTART);
+ net_log_.BeginEvent(
+ NetLog::TYPE_HTTP_TRANSACTION_DRAIN_BODY_FOR_AUTH_RESTART, NULL);
rv = DoDrainBodyForAuthRestart();
break;
case STATE_DRAIN_BODY_FOR_AUTH_RESTART_COMPLETE:
rv = DoDrainBodyForAuthRestartComplete(rv);
- TRACE_EVENT_END("http.drain_body_for_auth_restart",
- request_, request_->url.spec());
- LoadLog::EndEvent(
- load_log_,
- LoadLog::TYPE_HTTP_TRANSACTION_DRAIN_BODY_FOR_AUTH_RESTART);
+ net_log_.EndEvent(
+ NetLog::TYPE_HTTP_TRANSACTION_DRAIN_BODY_FOR_AUTH_RESTART, NULL);
+ break;
+ case STATE_SPDY_GET_STREAM:
+ DCHECK_EQ(OK, rv);
+ rv = DoSpdyGetStream();
+ break;
+ case STATE_SPDY_GET_STREAM_COMPLETE:
+ rv = DoSpdyGetStreamComplete(rv);
break;
case STATE_SPDY_SEND_REQUEST:
DCHECK_EQ(OK, rv);
- TRACE_EVENT_BEGIN("http.send_request", request_, request_->url.spec());
- LoadLog::BeginEvent(load_log_,
- LoadLog::TYPE_FLIP_TRANSACTION_SEND_REQUEST);
+ net_log_.BeginEvent(NetLog::TYPE_SPDY_TRANSACTION_SEND_REQUEST, NULL);
rv = DoSpdySendRequest();
break;
case STATE_SPDY_SEND_REQUEST_COMPLETE:
rv = DoSpdySendRequestComplete(rv);
- TRACE_EVENT_END("http.send_request", request_, request_->url.spec());
- LoadLog::EndEvent(load_log_,
- LoadLog::TYPE_FLIP_TRANSACTION_SEND_REQUEST);
+ net_log_.EndEvent(NetLog::TYPE_SPDY_TRANSACTION_SEND_REQUEST, NULL);
break;
case STATE_SPDY_READ_HEADERS:
DCHECK_EQ(OK, rv);
- TRACE_EVENT_BEGIN("http.read_headers", request_, request_->url.spec());
- LoadLog::BeginEvent(load_log_,
- LoadLog::TYPE_FLIP_TRANSACTION_READ_HEADERS);
+ net_log_.BeginEvent(NetLog::TYPE_SPDY_TRANSACTION_READ_HEADERS, NULL);
rv = DoSpdyReadHeaders();
break;
case STATE_SPDY_READ_HEADERS_COMPLETE:
rv = DoSpdyReadHeadersComplete(rv);
- TRACE_EVENT_END("http.read_headers", request_, request_->url.spec());
- LoadLog::EndEvent(load_log_,
- LoadLog::TYPE_FLIP_TRANSACTION_READ_HEADERS);
+ net_log_.EndEvent(NetLog::TYPE_SPDY_TRANSACTION_READ_HEADERS, NULL);
break;
case STATE_SPDY_READ_BODY:
DCHECK_EQ(OK, rv);
- TRACE_EVENT_BEGIN("http.read_body", request_, request_->url.spec());
- LoadLog::BeginEvent(load_log_,
- LoadLog::TYPE_FLIP_TRANSACTION_READ_BODY);
+ net_log_.BeginEvent(NetLog::TYPE_SPDY_TRANSACTION_READ_BODY, NULL);
rv = DoSpdyReadBody();
break;
case STATE_SPDY_READ_BODY_COMPLETE:
rv = DoSpdyReadBodyComplete(rv);
- TRACE_EVENT_END("http.read_body", request_, request_->url.spec());
- LoadLog::EndEvent(load_log_,
- LoadLog::TYPE_FLIP_TRANSACTION_READ_BODY);
+ net_log_.EndEvent(NetLog::TYPE_SPDY_TRANSACTION_READ_BODY, NULL);
break;
default:
NOTREACHED() << "bad state";
@@ -566,57 +618,73 @@
next_state_ = STATE_RESOLVE_PROXY_COMPLETE;
+ // |endpoint_| indicates the final destination endpoint.
+ endpoint_ = HostPortPair(request_->url.HostNoBrackets(),
+ request_->url.EffectiveIntPort());
+
+ // Extra URL we might be attempting to resolve to.
+ GURL alternate_endpoint_url;
+
+ // Tracks whether we are using |request_->url| or |alternate_endpoint_url|.
+ const GURL *curr_endpoint_url = &request_->url;
+
+ if (g_host_mapping_rules && g_host_mapping_rules->RewriteHost(&endpoint_)) {
+ url_canon::Replacements<char> replacements;
+ const std::string port_str = IntToString(endpoint_.port);
+ replacements.SetPort(port_str.c_str(),
+ url_parse::Component(0, port_str.size()));
+ replacements.SetHost(endpoint_.host.c_str(),
+ url_parse::Component(0, endpoint_.host.size()));
+ alternate_endpoint_url = curr_endpoint_url->ReplaceComponents(replacements);
+ curr_endpoint_url = &alternate_endpoint_url;
+ }
+
+ const HttpAlternateProtocols& alternate_protocols =
+ session_->alternate_protocols();
+ if (alternate_protocols.HasAlternateProtocolFor(endpoint_)) {
+ response_.was_alternate_protocol_available = true;
+ if (alternate_protocol_mode_ == kUnspecified) {
+ HttpAlternateProtocols::PortProtocolPair alternate =
+ alternate_protocols.GetAlternateProtocolFor(endpoint_);
+ if (alternate.protocol != HttpAlternateProtocols::BROKEN) {
+ DCHECK_EQ(HttpAlternateProtocols::NPN_SPDY_1, alternate.protocol);
+ endpoint_.port = alternate.port;
+ alternate_protocol_ = HttpAlternateProtocols::NPN_SPDY_1;
+ alternate_protocol_mode_ = kUsingAlternateProtocol;
+
+ url_canon::Replacements<char> replacements;
+ replacements.SetScheme("https",
+ url_parse::Component(0, strlen("https")));
+ const std::string port_str = IntToString(endpoint_.port);
+ replacements.SetPort(port_str.c_str(),
+ url_parse::Component(0, port_str.size()));
+ alternate_endpoint_url =
+ curr_endpoint_url->ReplaceComponents(replacements);
+ curr_endpoint_url = &alternate_endpoint_url;
+ }
+ }
+ }
+
if (request_->load_flags & LOAD_BYPASS_PROXY) {
proxy_info_.UseDirect();
return OK;
}
return session_->proxy_service()->ResolveProxy(
- request_->url, &proxy_info_, &io_callback_, &pac_request_, load_log_);
+ *curr_endpoint_url, &proxy_info_, &io_callback_, &pac_request_, net_log_);
}
int HttpNetworkTransaction::DoResolveProxyComplete(int result) {
-
pac_request_ = NULL;
- if (result != OK) {
- DLOG(ERROR) << "Failed to resolve proxy: " << result;
- // Fall-back to direct when there were runtime errors in the PAC script,
- // or some other failure with the settings.
- proxy_info_.UseDirect();
- }
+ if (result != OK)
+ return result;
// Remove unsupported proxies from the list.
proxy_info_.RemoveProxiesWithoutScheme(
ProxyServer::SCHEME_DIRECT | ProxyServer::SCHEME_HTTP |
ProxyServer::SCHEME_SOCKS4 | ProxyServer::SCHEME_SOCKS5);
- // There are four possible outcomes of having run the ProxyService:
- // (1) The ProxyService decided we should connect through a proxy.
- // (2) The ProxyService decided we should direct-connect.
- // (3) The ProxyService decided we should give up, as there are no more
- // proxies to try (this is more likely to happen during
- // ReconsiderProxyAfterError()).
- // (4) The ProxyService failed (which can happen if the PAC script
- // we were configured with threw a runtime exception).
- //
- // It is important that we fail the connection in case (3) rather than
- // falling-back to a direct connection, since sending traffic through
- // a proxy may be integral to the user's privacy/security model.
- //
- // For example if a user had configured traffic to go through the TOR
- // anonymizing proxy to protect their privacy, it would be bad if we
- // silently fell-back to direct connect if the proxy server were to
- // become unreachable.
- //
- // In case (4) it is less obvious what the right thing to do is. On the
- // one hand, for consistency it would be natural to hard-fail as well.
- // However, both Firefox 3.5 and Internet Explorer 8 will silently fall-back
- // to DIRECT in this case, so we will do the same for compatibility.
- //
- // For more information, see:
- // http://www.chromium.org/developers/design-documents/proxy-settings-fallback
-
if (proxy_info_.is_empty()) {
// No proxies/direct to choose from. This happens when we don't support any
// of the proxies in the returned list.
@@ -630,269 +698,333 @@
int HttpNetworkTransaction::DoInitConnection() {
DCHECK(!connection_->is_initialized());
DCHECK(proxy_info_.proxy_server().is_valid());
-
next_state_ = STATE_INIT_CONNECTION_COMPLETE;
- using_ssl_ = request_->url.SchemeIs("https");
-
- if (proxy_info_.is_direct())
- proxy_mode_ = kDirectConnection;
- else if (proxy_info_.proxy_server().is_socks())
- proxy_mode_ = kSOCKSProxy;
- else if (using_ssl_)
- proxy_mode_ = kHTTPProxyUsingTunnel;
- else
- proxy_mode_ = kHTTPProxy;
-
- // Build the string used to uniquely identify connections of this type.
- // Determine the host and port to connect to.
- std::string connection_group;
- std::string host;
- int port;
- if (proxy_mode_ != kDirectConnection) {
- ProxyServer proxy_server = proxy_info_.proxy_server();
- connection_group = "proxy/" + proxy_server.ToURI() + "/";
- host = proxy_server.HostNoBrackets();
- port = proxy_server.port();
- } else {
- host = request_->url.HostNoBrackets();
- port = request_->url.EffectiveIntPort();
+ // Now that the proxy server has been resolved, create the auth_controllers_.
+ for (int i = 0; i < HttpAuth::AUTH_NUM_TARGETS; i++) {
+ HttpAuth::Target target = static_cast<HttpAuth::Target>(i);
+ if (!auth_controllers_[target].get())
+ auth_controllers_[target] = new HttpAuthController(target,
+ AuthURL(target),
+ session_);
}
+ bool want_spdy = alternate_protocol_mode_ == kUsingAlternateProtocol
+ && alternate_protocol_ == HttpAlternateProtocols::NPN_SPDY_1;
+ using_ssl_ = request_->url.SchemeIs("https") || want_spdy;
+ using_spdy_ = false;
+ response_.was_fetched_via_proxy = !proxy_info_.is_direct();
+
// Use the fixed testing ports if they've been provided.
if (using_ssl_) {
if (session_->fixed_https_port() != 0)
- port = session_->fixed_https_port();
+ endpoint_.port = session_->fixed_https_port();
} else if (session_->fixed_http_port() != 0) {
- port = session_->fixed_http_port();
+ endpoint_.port = session_->fixed_http_port();
}
- // For a connection via HTTP proxy not using CONNECT, the connection
- // is to the proxy server only. For all other cases
- // (direct, HTTP proxy CONNECT, SOCKS), the connection is upto the
- // url endpoint. Hence we append the url data into the connection_group.
- if (proxy_mode_ != kHTTPProxy)
- connection_group.append(request_->url.GetOrigin().spec());
+ // Check first if we have a spdy session for this group. If so, then go
+ // straight to using that.
+ if (session_->spdy_session_pool()->HasSession(endpoint_)) {
+ using_spdy_ = true;
+ reused_socket_ = true;
+ next_state_ = STATE_SPDY_GET_STREAM;
+ return OK;
+ }
+ // Build the string used to uniquely identify connections of this type.
+ // Determine the host and port to connect to.
+ std::string connection_group = endpoint_.ToString();
DCHECK(!connection_group.empty());
- HostResolver::RequestInfo resolve_info(host, port);
- resolve_info.set_priority(request_->priority);
-
- // The referrer is used by the DNS prefetch system to correlate resolutions
- // with the page that triggered them. It doesn't impact the actual addresses
- // that we resolve to.
- resolve_info.set_referrer(request_->referrer);
+ if (using_ssl_)
+ connection_group = StringPrintf("ssl/%s", connection_group.c_str());
// If the user is refreshing the page, bypass the host cache.
- if (request_->load_flags & LOAD_BYPASS_CACHE ||
- request_->load_flags & LOAD_DISABLE_CACHE) {
- resolve_info.set_allow_cached_response(false);
+ bool disable_resolver_cache = request_->load_flags & LOAD_BYPASS_CACHE ||
+ request_->load_flags & LOAD_VALIDATE_CACHE ||
+ request_->load_flags & LOAD_DISABLE_CACHE;
+
+ // Build up the connection parameters.
+ scoped_refptr<TCPSocketParams> tcp_params;
+ scoped_refptr<HttpProxySocketParams> http_proxy_params;
+ scoped_refptr<SOCKSSocketParams> socks_params;
+ scoped_ptr<HostPortPair> proxy_host_port;
+
+ if (proxy_info_.is_direct()) {
+ tcp_params = new TCPSocketParams(endpoint_, request_->priority,
+ request_->referrer,
+ disable_resolver_cache);
+ } else {
+ ProxyServer proxy_server = proxy_info_.proxy_server();
+ proxy_host_port.reset(new HostPortPair(proxy_server.HostNoBrackets(),
+ proxy_server.port()));
+ scoped_refptr<TCPSocketParams> proxy_tcp_params =
+ new TCPSocketParams(*proxy_host_port, request_->priority,
+ request_->referrer, disable_resolver_cache);
+
+ if (proxy_info_.is_http()) {
+ scoped_refptr<HttpAuthController> http_proxy_auth;
+ if (using_ssl_) {
+ http_proxy_auth = auth_controllers_[HttpAuth::AUTH_PROXY];
+ establishing_tunnel_ = true;
+ }
+ http_proxy_params = new HttpProxySocketParams(proxy_tcp_params,
+ request_->url, endpoint_,
+ http_proxy_auth,
+ using_ssl_);
+ } else {
+ DCHECK(proxy_info_.is_socks());
+ char socks_version;
+ if (proxy_server.scheme() == ProxyServer::SCHEME_SOCKS5)
+ socks_version = '5';
+ else
+ socks_version = '4';
+ connection_group =
+ StringPrintf("socks%c/%s", socks_version, connection_group.c_str());
+
+ socks_params = new SOCKSSocketParams(proxy_tcp_params,
+ socks_version == '5',
+ endpoint_,
+ request_->priority,
+ request_->referrer);
+ }
}
- // Check first if we have a flip session for this group. If so, then go
- // straight to using that.
- if (session_->flip_session_pool()->HasSession(resolve_info))
- return OK;
+ // Deal with SSL - which layers on top of any given proxy.
+ if (using_ssl_) {
+ if (ContainsKey(*g_tls_intolerant_servers, GetHostAndPort(request_->url))) {
+ LOG(WARNING) << "Falling back to SSLv3 because host is TLS intolerant: "
+ << GetHostAndPort(request_->url);
+ ssl_config_.ssl3_fallback = true;
+ ssl_config_.tls1_enabled = false;
+ }
- int rv = connection_->Init(connection_group, resolve_info, request_->priority,
- &io_callback_, session_->tcp_socket_pool(),
- load_log_);
- return rv;
+ UMA_HISTOGRAM_ENUMERATION("Net.ConnectionUsedSSLv3Fallback",
+ (int) ssl_config_.ssl3_fallback, 2);
+
+ int load_flags = request_->load_flags;
+ if (g_ignore_certificate_errors)
+ load_flags |= LOAD_IGNORE_ALL_CERT_ERRORS;
+ if (request_->load_flags & LOAD_VERIFY_EV_CERT)
+ ssl_config_.verify_ev_cert = true;
+
+ scoped_refptr<SSLSocketParams> ssl_params =
+ new SSLSocketParams(tcp_params, http_proxy_params, socks_params,
+ proxy_info_.proxy_server().scheme(),
+ request_->url.HostNoBrackets(), ssl_config_,
+ load_flags, want_spdy);
+
+ scoped_refptr<SSLClientSocketPool> ssl_pool;
+ if (proxy_info_.is_direct())
+ ssl_pool = session_->ssl_socket_pool();
+ else
+ ssl_pool = session_->GetSocketPoolForSSLWithProxy(*proxy_host_port);
+
+ return connection_->Init(connection_group, ssl_params, request_->priority,
+ &io_callback_, ssl_pool, net_log_);
+ }
+
+ // Finally, get the connection started.
+ if (proxy_info_.is_http()) {
+ return connection_->Init(
+ connection_group, http_proxy_params, request_->priority, &io_callback_,
+ session_->GetSocketPoolForHTTPProxy(*proxy_host_port), net_log_);
+ }
+
+ if (proxy_info_.is_socks()) {
+ return connection_->Init(
+ connection_group, socks_params, request_->priority, &io_callback_,
+ session_->GetSocketPoolForSOCKSProxy(*proxy_host_port), net_log_);
+ }
+
+ DCHECK(proxy_info_.is_direct());
+ return connection_->Init(connection_group, tcp_params, request_->priority,
+ &io_callback_, session_->tcp_socket_pool(),
+ net_log_);
}
int HttpNetworkTransaction::DoInitConnectionComplete(int result) {
- if (result < 0) {
- UpdateConnectionTypeHistograms(CONNECTION_HTTP, false);
- return ReconsiderProxyAfterError(result);
+ // |result| may be the result of any of the stacked pools. The following
+ // logic is used when determining how to interpret an error.
+ // If |result| < 0:
+ // and connection_->socket() != NULL, then the SSL handshake ran and it
+ // is a potentially recoverable error.
+ // and connection_->socket == NULL and connection_->is_ssl_error() is true,
+ // then the SSL handshake ran with an unrecoverable error.
+ // otherwise, the error came from one of the other pools.
+ bool ssl_started = using_ssl_ && (result == OK || connection_->socket() ||
+ connection_->is_ssl_error());
+
+ if (ssl_started && (result == OK || IsCertificateError(result))) {
+ SSLClientSocket* ssl_socket =
+ static_cast<SSLClientSocket*>(connection_->socket());
+ if (ssl_socket->wasNpnNegotiated()) {
+ response_.was_npn_negotiated = true;
+ std::string proto;
+ ssl_socket->GetNextProto(&proto);
+ if (SSLClientSocket::NextProtoFromString(proto) ==
+ SSLClientSocket::kProtoSPDY1)
+ using_spdy_ = true;
+ }
}
- DCHECK_EQ(OK, result);
+ if (result == ERR_PROXY_AUTH_REQUESTED) {
+ DCHECK(!ssl_started);
+ const HttpResponseInfo& tunnel_auth_response =
+ connection_->ssl_error_response_info();
- // If we don't have an initialized connection, that means we have a flip
- // connection waiting for us.
- if (!connection_->is_initialized()) {
- next_state_ = STATE_SPDY_SEND_REQUEST;
+ response_.headers = tunnel_auth_response.headers;
+ response_.auth_challenge = tunnel_auth_response.auth_challenge;
+ headers_valid_ = true;
+ pending_auth_target_ = HttpAuth::AUTH_PROXY;
return OK;
}
- LogTCPConnectedMetrics(*connection_);
+ if ((!ssl_started && result < 0 &&
+ alternate_protocol_mode_ == kUsingAlternateProtocol) ||
+ result == ERR_NPN_NEGOTIATION_FAILED) {
+ // Mark the alternate protocol as broken and fallback.
+ MarkBrokenAlternateProtocolAndFallback();
+ return OK;
+ }
- // Set the reused_socket_ flag to indicate that we are using a keep-alive
- // connection. This flag is used to handle errors that occur while we are
- // trying to reuse a keep-alive connection.
- reused_socket_ = connection_->is_reused();
- if (reused_socket_) {
- next_state_ = STATE_SEND_REQUEST;
- } else {
- // Now we have a TCP connected socket. Perform other connection setup as
- // needed.
- UpdateConnectionTypeHistograms(CONNECTION_HTTP, true);
- if (proxy_mode_ == kSOCKSProxy)
- next_state_ = STATE_SOCKS_CONNECT;
- else if (using_ssl_ && proxy_mode_ == kDirectConnection) {
- next_state_ = STATE_SSL_CONNECT;
- } else {
- next_state_ = STATE_SEND_REQUEST;
- if (proxy_mode_ == kHTTPProxyUsingTunnel)
- establishing_tunnel_ = true;
+ if (result < 0 && !ssl_started)
+ return ReconsiderProxyAfterError(result);
+ establishing_tunnel_ = false;
+
+ if (connection_->socket()) {
+ LogHttpConnectedMetrics(*connection_);
+
+ // Set the reused_socket_ flag to indicate that we are using a keep-alive
+ // connection. This flag is used to handle errors that occur while we are
+ // trying to reuse a keep-alive connection.
+ reused_socket_ = connection_->is_reused();
+ // TODO(vandebo) should we exclude SPDY in the following if?
+ if (!reused_socket_)
+ UpdateConnectionTypeHistograms(CONNECTION_HTTP);
+
+ if (!using_ssl_) {
+ DCHECK_EQ(OK, result);
+ next_state_ = STATE_GENERATE_PROXY_AUTH_TOKEN;
+ return result;
}
}
- return OK;
-}
-
-int HttpNetworkTransaction::DoSOCKSConnect() {
- DCHECK_EQ(kSOCKSProxy, proxy_mode_);
-
- next_state_ = STATE_SOCKS_CONNECT_COMPLETE;
-
- // Add a SOCKS connection on top of our existing transport socket.
- ClientSocket* s = connection_->release_socket();
- HostResolver::RequestInfo req_info(request_->url.HostNoBrackets(),
- request_->url.EffectiveIntPort());
- req_info.set_referrer(request_->referrer);
- req_info.set_priority(request_->priority);
-
- if (proxy_info_.proxy_server().scheme() == ProxyServer::SCHEME_SOCKS5)
- s = new SOCKS5ClientSocket(s, req_info);
- else
- s = new SOCKSClientSocket(s, req_info, session_->host_resolver());
- connection_->set_socket(s);
- return connection_->socket()->Connect(&io_callback_, load_log_);
-}
-
-int HttpNetworkTransaction::DoSOCKSConnectComplete(int result) {
- DCHECK_EQ(kSOCKSProxy, proxy_mode_);
-
- if (result == OK) {
- if (using_ssl_) {
- next_state_ = STATE_SSL_CONNECT;
- } else {
- next_state_ = STATE_SEND_REQUEST;
- }
- } else {
- result = ReconsiderProxyAfterError(result);
- }
- return result;
-}
-
-int HttpNetworkTransaction::DoSSLConnect() {
- next_state_ = STATE_SSL_CONNECT_COMPLETE;
-
- if (request_->load_flags & LOAD_VERIFY_EV_CERT)
- ssl_config_.verify_ev_cert = true;
-
- ssl_connect_start_time_ = base::TimeTicks::Now();
-
- // Add a SSL socket on top of our existing transport socket.
- ClientSocket* s = connection_->release_socket();
- s = session_->socket_factory()->CreateSSLClientSocket(
- s, request_->url.HostNoBrackets(), ssl_config_);
- connection_->set_socket(s);
- return connection_->socket()->Connect(&io_callback_, load_log_);
-}
-
-int HttpNetworkTransaction::DoSSLConnectComplete(int result) {
- SSLClientSocket* ssl_socket =
- reinterpret_cast<SSLClientSocket*>(connection_->socket());
-
- SSLClientSocket::NextProtoStatus status =
- SSLClientSocket::kNextProtoUnsupported;
- std::string proto;
- // GetNextProto will fail and and trigger a NOTREACHED if we pass in a socket
- // that hasn't had SSL_ImportFD called on it. If we get a certificate error
- // here, then we know that we called SSL_ImportFD.
- if (result == OK || IsCertificateError(result))
- status = ssl_socket->GetNextProto(&proto);
- static const char kSpdyProto[] = "spdy";
- const bool use_spdy = (status == SSLClientSocket::kNextProtoNegotiated &&
- proto == kSpdyProto);
-
+ // Handle SSL errors below.
+ DCHECK(using_ssl_);
+ DCHECK(ssl_started);
if (IsCertificateError(result)) {
- if (use_spdy) {
- // TODO(agl/willchan/wtc): We currently ignore certificate errors for
- // spdy but we shouldn't. http://crbug.com/32020
+ if (using_spdy_ && request_->url.SchemeIs("http")) {
+ // We ignore certificate errors for http over spdy.
+ spdy_certificate_error_ = result;
result = OK;
} else {
result = HandleCertificateError(result);
+ if (result == OK && !connection_->socket()->IsConnectedAndIdle()) {
+ connection_->socket()->Disconnect();
+ connection_->Reset();
+ next_state_ = STATE_INIT_CONNECTION;
+ return result;
+ }
}
}
- if (result == OK) {
- DCHECK(ssl_connect_start_time_ != base::TimeTicks());
- base::TimeDelta connect_duration =
- base::TimeTicks::Now() - ssl_connect_start_time_;
+ if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
+ response_.cert_request_info =
+ connection_->ssl_error_response_info().cert_request_info;
+ return HandleCertificateRequest(result);
+ }
+ if (result < 0)
+ return HandleSSLHandshakeError(result);
- if (use_spdy) {
- UMA_HISTOGRAM_CUSTOM_TIMES("Net.SpdyConnectionLatency",
- connect_duration,
- base::TimeDelta::FromMilliseconds(1),
- base::TimeDelta::FromMinutes(10),
- 100);
-
- UpdateConnectionTypeHistograms(CONNECTION_SPDY, true);
- next_state_ = STATE_SPDY_SEND_REQUEST;
- } else {
- UMA_HISTOGRAM_CUSTOM_TIMES("Net.SSL_Connection_Latency",
- connect_duration,
- base::TimeDelta::FromMilliseconds(1),
- base::TimeDelta::FromMinutes(10),
- 100);
-
- next_state_ = STATE_SEND_REQUEST;
- }
- } else if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
- result = HandleCertificateRequest(result);
+ if (using_spdy_) {
+ UpdateConnectionTypeHistograms(CONNECTION_SPDY);
+ // TODO(cbentzel): Add auth support to spdy. See http://crbug.com/46620
+ next_state_ = STATE_SPDY_GET_STREAM;
} else {
- result = HandleSSLHandshakeError(result);
+ next_state_ = STATE_GENERATE_PROXY_AUTH_TOKEN;
}
- return result;
+ return OK;
+}
+
+int HttpNetworkTransaction::DoGenerateProxyAuthToken() {
+ next_state_ = STATE_GENERATE_PROXY_AUTH_TOKEN_COMPLETE;
+ if (!ShouldApplyProxyAuth())
+ return OK;
+ return auth_controllers_[HttpAuth::AUTH_PROXY]->MaybeGenerateAuthToken(
+ request_, &io_callback_, net_log_);
+}
+
+int HttpNetworkTransaction::DoGenerateProxyAuthTokenComplete(int rv) {
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ if (rv == OK)
+ next_state_ = STATE_GENERATE_SERVER_AUTH_TOKEN;
+ return rv;
+}
+
+int HttpNetworkTransaction::DoGenerateServerAuthToken() {
+ next_state_ = STATE_GENERATE_SERVER_AUTH_TOKEN_COMPLETE;
+ if (!ShouldApplyServerAuth())
+ return OK;
+ return auth_controllers_[HttpAuth::AUTH_SERVER]->MaybeGenerateAuthToken(
+ request_, &io_callback_, net_log_);
+}
+
+int HttpNetworkTransaction::DoGenerateServerAuthTokenComplete(int rv) {
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ if (rv == OK)
+ next_state_ = STATE_SEND_REQUEST;
+ return rv;
}
int HttpNetworkTransaction::DoSendRequest() {
next_state_ = STATE_SEND_REQUEST_COMPLETE;
UploadDataStream* request_body = NULL;
- if (!establishing_tunnel_ && request_->upload_data)
- request_body = new UploadDataStream(request_->upload_data);
+ if (request_->upload_data) {
+ int error_code;
+ request_body = UploadDataStream::Create(request_->upload_data, &error_code);
+ if (!request_body)
+ return error_code;
+ }
// This is constructed lazily (instead of within our Start method), so that
// we have proxy info available.
if (request_headers_.empty()) {
// Figure out if we can/should add Proxy-Authentication & Authentication
// headers.
- bool have_proxy_auth =
- ShouldApplyProxyAuth() &&
- (HaveAuth(HttpAuth::AUTH_PROXY) ||
- SelectPreemptiveAuth(HttpAuth::AUTH_PROXY));
- bool have_server_auth =
- ShouldApplyServerAuth() &&
- (HaveAuth(HttpAuth::AUTH_SERVER) ||
- SelectPreemptiveAuth(HttpAuth::AUTH_SERVER));
-
- std::string authorization_headers;
-
- // TODO(wtc): If BuildAuthorizationHeader fails (returns an authorization
- // header with no credentials), we should return an error to prevent
- // entering an infinite auth restart loop. See http://crbug.com/21050.
+ HttpRequestHeaders authorization_headers;
+ bool have_proxy_auth = (ShouldApplyProxyAuth() &&
+ HaveAuth(HttpAuth::AUTH_PROXY));
+ bool have_server_auth = (ShouldApplyServerAuth() &&
+ HaveAuth(HttpAuth::AUTH_SERVER));
if (have_proxy_auth)
- authorization_headers.append(
- BuildAuthorizationHeader(HttpAuth::AUTH_PROXY));
+ auth_controllers_[HttpAuth::AUTH_PROXY]->AddAuthorizationHeader(
+ &authorization_headers);
if (have_server_auth)
- authorization_headers.append(
- BuildAuthorizationHeader(HttpAuth::AUTH_SERVER));
+ auth_controllers_[HttpAuth::AUTH_SERVER]->AddAuthorizationHeader(
+ &authorization_headers);
+ std::string request_line;
+ HttpRequestHeaders request_headers;
+ BuildRequestHeaders(request_, authorization_headers, request_body,
+ !using_ssl_ && proxy_info_.is_http(), &request_line,
+ &request_headers);
- if (establishing_tunnel_) {
- BuildTunnelRequest(request_, authorization_headers, &request_headers_);
- } else {
- BuildRequestHeaders(request_, authorization_headers, request_body,
- proxy_mode_ == kHTTPProxy, &request_headers_);
+ if (session_->network_delegate())
+ session_->network_delegate()->OnSendHttpRequest(&request_headers);
+
+ if (net_log_.HasListener()) {
+ net_log_.AddEvent(
+ NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST_HEADERS,
+ new NetLogHttpRequestParameter(request_line, request_headers));
}
+
+ request_headers_ = request_line + request_headers.ToString();
}
headers_valid_ = false;
- http_stream_.reset(new HttpBasicStream(connection_.get(), load_log_));
+ http_stream_.reset(new HttpBasicStream(connection_.get(), net_log_));
return http_stream_->SendRequest(request_, request_headers_,
request_body, &response_, &io_callback_);
@@ -901,24 +1033,16 @@
int HttpNetworkTransaction::DoSendRequestComplete(int result) {
if (result < 0)
return HandleIOError(result);
-
next_state_ = STATE_READ_HEADERS;
-
return OK;
}
int HttpNetworkTransaction::DoReadHeaders() {
next_state_ = STATE_READ_HEADERS_COMPLETE;
-
return http_stream_->ReadResponseHeaders(&io_callback_);
}
int HttpNetworkTransaction::HandleConnectionClosedBeforeEndOfHeaders() {
- if (establishing_tunnel_) {
- // The connection was closed before the tunnel could be established.
- return ERR_TUNNEL_CONNECTION_FAILED;
- }
-
if (!response_.headers) {
// The connection was closed before any data was sent. Likely an error
// rather than empty HTTP/0.9 response.
@@ -940,9 +1064,30 @@
<< " during SSL renegotiation";
result = ERR_CERT_ERROR_IN_SSL_RENEGOTIATION;
} else if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
+ response_.cert_request_info = new SSLCertRequestInfo;
+ SSLClientSocket* ssl_socket =
+ static_cast<SSLClientSocket*>(connection_->socket());
+ ssl_socket->GetSSLCertRequestInfo(response_.cert_request_info);
result = HandleCertificateRequest(result);
if (result == OK)
return result;
+ } else if ((result == ERR_SSL_DECOMPRESSION_FAILURE_ALERT ||
+ result == ERR_SSL_BAD_RECORD_MAC_ALERT) &&
+ ssl_config_.tls1_enabled &&
+ !SSLConfigService::IsKnownStrictTLSServer(request_->url.host())){
+ // Some buggy servers select DEFLATE compression when offered and then
+ // fail to ever decompress anything. They will send a fatal alert telling
+ // us this. Normally we would pick this up during the handshake because
+ // our Finished message is compressed and we'll never get the server's
+ // Finished if it fails to process ours.
+ //
+ // However, with False Start, we'll believe that the handshake is
+ // complete as soon as we've /sent/ our Finished message. In this case,
+ // we only find out that the server is buggy here, when we try to read
+ // the initial reply.
+ g_tls_intolerant_servers->insert(GetHostAndPort(request_->url));
+ ResetConnectionAndRequestForResend();
+ return OK;
}
}
@@ -957,26 +1102,23 @@
// After we call RestartWithAuth a new response_time will be recorded, and
// we need to be cautious about incorrectly logging the duration across the
// authentication activity.
- if (!logged_response_time) {
- LogTransactionConnectedMetrics();
- logged_response_time = true;
- }
+ LogTransactionConnectedMetrics();
if (result == ERR_CONNECTION_CLOSED) {
+ // For now, if we get at least some data, we do the best we can to make
+ // sense of it and send it back up the stack.
int rv = HandleConnectionClosedBeforeEndOfHeaders();
if (rv != OK)
return rv;
- // TODO(wtc): Traditionally this code has returned 0 when reading a closed
- // socket. That is partially corrected in classes that we call, but
- // callers need to be updated.
- result = 0;
+ }
+
+ if (net_log_.HasListener()) {
+ net_log_.AddEvent(
+ NetLog::TYPE_HTTP_TRANSACTION_READ_RESPONSE_HEADERS,
+ new NetLogHttpResponseParameter(response_.headers));
}
if (response_.headers->GetParsedHttpVersion() < HttpVersion(1, 0)) {
- // Require the "HTTP/1.x" status line for SSL CONNECT.
- if (establishing_tunnel_)
- return ERR_TUNNEL_CONNECTION_FAILED;
-
// HTTP/0.9 doesn't support the PUT method, so lack of response headers
// indicates a buggy server. See:
// https://bugzilla.mozilla.org/show_bug.cgi?id=193921
@@ -984,44 +1126,6 @@
return ERR_METHOD_NOT_SUPPORTED;
}
- if (establishing_tunnel_) {
- switch (response_.headers->response_code()) {
- case 200: // OK
- if (http_stream_->IsMoreDataBuffered()) {
- // The proxy sent extraneous data after the headers.
- return ERR_TUNNEL_CONNECTION_FAILED;
- }
- next_state_ = STATE_SSL_CONNECT;
- // Reset for the real request and response headers.
- request_headers_.clear();
- http_stream_.reset(new HttpBasicStream(connection_.get(), load_log_));
- headers_valid_ = false;
- establishing_tunnel_ = false;
- return OK;
-
- // We aren't able to CONNECT to the remote host through the proxy. We
- // need to be very suspicious about the response because an active network
- // attacker can force us into this state by masquerading as the proxy.
- // The only safe thing to do here is to fail the connection because our
- // client is expecting an SSL protected response.
- // See http://crbug.com/7338.
- case 407: // Proxy Authentication Required
- // We need this status code to allow proxy authentication. Our
- // authentication code is smart enough to avoid being tricked by an
- // active network attacker.
- break;
- default:
- // For all other status codes, we conservatively fail the CONNECT
- // request.
- // We lose something by doing this. We have seen proxy 403, 404, and
- // 501 response bodies that contain a useful error message. For
- // example, Squid uses a 404 response to report the DNS error: "The
- // domain name does not exist."
- LogBlockedTunnelResponse(response_.headers->response_code());
- return ERR_TUNNEL_CONNECTION_FAILED;
- }
- }
-
// Check for an intermediate 100 Continue response. An origin server is
// allowed to send this response even if we didn't ask for it, so we just
// need to skip over it.
@@ -1033,13 +1137,17 @@
return OK;
}
+ ProcessAlternateProtocol(*response_.headers,
+ endpoint_,
+ session_->mutable_alternate_protocols());
+
int rv = HandleAuthChallenge();
if (rv != OK)
return rv;
- if (using_ssl_ && !establishing_tunnel_) {
+ if (using_ssl_) {
SSLClientSocket* ssl_socket =
- reinterpret_cast<SSLClientSocket*>(connection_->socket());
+ static_cast<SSLClientSocket*>(connection_->socket());
ssl_socket->GetSSLInfo(&response_.ssl_info);
}
@@ -1059,21 +1167,14 @@
int HttpNetworkTransaction::DoReadBodyComplete(int result) {
// We are done with the Read call.
- DCHECK(!establishing_tunnel_) <<
- "We should never read a response body of a tunnel.";
-
bool done = false, keep_alive = false;
- if (result < 0) {
- // Error or closed connection while reading the socket.
+ if (result <= 0)
done = true;
- // TODO(wtc): Traditionally this code has returned 0 when reading a closed
- // socket. That is partially corrected in classes that we call, but
- // callers need to be updated.
- if (result == ERR_CONNECTION_CLOSED)
- result = 0;
- } else if (http_stream_->IsResponseBodyComplete()) {
+
+ if (http_stream_->IsResponseBodyComplete()) {
done = true;
- keep_alive = GetResponseHeaders()->IsKeepAlive();
+ if (http_stream_->CanFindEndOfResponse())
+ keep_alive = GetResponseHeaders()->IsKeepAlive();
}
// Clean up connection_->if we are done.
@@ -1126,36 +1227,65 @@
return OK;
}
-int HttpNetworkTransaction::DoSpdySendRequest() {
- next_state_ = STATE_SPDY_SEND_REQUEST_COMPLETE;
- CHECK(!spdy_stream_.get());
+int HttpNetworkTransaction::DoSpdyGetStream() {
+ next_state_ = STATE_SPDY_GET_STREAM_COMPLETE;
+ CHECK(!spdy_http_stream_.get());
// First we get a SPDY session. Theoretically, we've just negotiated one, but
// if one already exists, then screw it, use the existing one! Otherwise,
// use the existing TCP socket.
- HostResolver::RequestInfo req_info(request_->url.HostNoBrackets(),
- request_->url.EffectiveIntPort());
- req_info.set_priority(request_->priority);
- const scoped_refptr<FlipSessionPool> spdy_pool =
- session_->flip_session_pool();
- scoped_refptr<FlipSession> spdy_session;
+ const scoped_refptr<SpdySessionPool> spdy_pool =
+ session_->spdy_session_pool();
+ scoped_refptr<SpdySession> spdy_session;
- if (spdy_pool->HasSession(req_info)) {
- spdy_session = spdy_pool->Get(req_info, session_);
+ if (spdy_pool->HasSession(endpoint_)) {
+ spdy_session = spdy_pool->Get(endpoint_, session_, net_log_);
} else {
- spdy_session = spdy_pool->GetFlipSessionFromSocket(
- req_info, session_, connection_.release());
+ // SPDY is negotiated using the TLS next protocol negotiation (NPN)
+ // extension, so |connection_| must contain an SSLClientSocket.
+ DCHECK(using_ssl_);
+ CHECK(connection_->socket());
+ int error = spdy_pool->GetSpdySessionFromSSLSocket(
+ endpoint_, session_, connection_.release(), net_log_,
+ spdy_certificate_error_, &spdy_session);
+ if (error != OK)
+ return error;
}
CHECK(spdy_session.get());
+ if(spdy_session->IsClosed())
+ return ERR_CONNECTION_CLOSED;
- UploadDataStream* upload_data = request_->upload_data ?
- new UploadDataStream(request_->upload_data) : NULL;
headers_valid_ = false;
- spdy_stream_ = spdy_session->GetOrCreateStream(
- *request_, upload_data, load_log_);
- return spdy_stream_->SendRequest(upload_data, &response_, &io_callback_);
+
+ spdy_http_stream_.reset(new SpdyHttpStream());
+ return spdy_http_stream_->InitializeStream(spdy_session, *request_,
+ net_log_, &io_callback_);
+}
+
+int HttpNetworkTransaction::DoSpdyGetStreamComplete(int result) {
+ if (result < 0)
+ return result;
+
+ next_state_ = STATE_SPDY_SEND_REQUEST;
+ return OK;
+}
+
+int HttpNetworkTransaction::DoSpdySendRequest() {
+ next_state_ = STATE_SPDY_SEND_REQUEST_COMPLETE;
+
+ UploadDataStream* upload_data_stream = NULL;
+ if (request_->upload_data) {
+ int error_code = OK;
+ upload_data_stream = UploadDataStream::Create(request_->upload_data,
+ &error_code);
+ if (!upload_data_stream)
+ return error_code;
+ }
+ spdy_http_stream_->InitializeRequest(base::Time::Now(), upload_data_stream);
+
+ return spdy_http_stream_->SendRequest(&response_, &io_callback_);
}
int HttpNetworkTransaction::DoSpdySendRequestComplete(int result) {
@@ -1168,20 +1298,23 @@
int HttpNetworkTransaction::DoSpdyReadHeaders() {
next_state_ = STATE_SPDY_READ_HEADERS_COMPLETE;
- return spdy_stream_->ReadResponseHeaders(&io_callback_);
+ return spdy_http_stream_->ReadResponseHeaders(&io_callback_);
}
int HttpNetworkTransaction::DoSpdyReadHeadersComplete(int result) {
// TODO(willchan): Flesh out the support for HTTP authentication here.
if (result == OK)
headers_valid_ = true;
+
+ LogTransactionConnectedMetrics();
+
return result;
}
int HttpNetworkTransaction::DoSpdyReadBody() {
next_state_ = STATE_SPDY_READ_BODY_COMPLETE;
- return spdy_stream_->ReadResponseBody(
+ return spdy_http_stream_->ReadResponseBody(
read_buf_, read_buf_len_, &io_callback_);
}
@@ -1190,79 +1323,37 @@
read_buf_len_ = 0;
if (result <= 0)
- spdy_stream_ = NULL;
+ spdy_http_stream_.reset();
return result;
}
-void HttpNetworkTransaction::LogTCPConnectedMetrics(
+void HttpNetworkTransaction::LogHttpConnectedMetrics(
const ClientSocketHandle& handle) {
- const base::TimeDelta time_to_obtain_connected_socket =
- base::TimeTicks::Now() - handle.init_time();
-
- static const bool use_late_binding_histogram =
- !FieldTrial::MakeName("", "SocketLateBinding").empty();
-
- if (handle.reuse_type() == ClientSocketHandle::UNUSED) {
- UMA_HISTOGRAM_CUSTOM_TIMES(
- "Net.HttpConnectionLatency",
- time_to_obtain_connected_socket,
- base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(10),
- 100);
- }
-
- UMA_HISTOGRAM_ENUMERATION("Net.TCPSocketType", handle.reuse_type(),
- ClientSocketHandle::NUM_TYPES);
-
- if (use_late_binding_histogram) {
- UMA_HISTOGRAM_ENUMERATION(
- FieldTrial::MakeName("Net.TCPSocketType", "SocketLateBinding"),
- handle.reuse_type(), ClientSocketHandle::NUM_TYPES);
- }
-
- UMA_HISTOGRAM_CLIPPED_TIMES(
- "Net.TransportSocketRequestTime",
- time_to_obtain_connected_socket,
- base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(10),
- 100);
-
- if (use_late_binding_histogram) {
- UMA_HISTOGRAM_CUSTOM_TIMES(
- FieldTrial::MakeName("Net.TransportSocketRequestTime",
- "SocketLateBinding").data(),
- time_to_obtain_connected_socket,
- base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(10),
- 100);
- }
+ UMA_HISTOGRAM_ENUMERATION("Net.HttpSocketType", handle.reuse_type(),
+ ClientSocketHandle::NUM_TYPES);
switch (handle.reuse_type()) {
case ClientSocketHandle::UNUSED:
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.HttpConnectionLatency",
+ handle.setup_time(),
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10),
+ 100);
break;
case ClientSocketHandle::UNUSED_IDLE:
- UMA_HISTOGRAM_CUSTOM_TIMES(
- "Net.SocketIdleTimeBeforeNextUse_UnusedSocket",
- handle.idle_time(), base::TimeDelta::FromMilliseconds(1),
- base::TimeDelta::FromMinutes(6), 100);
- if (use_late_binding_histogram) {
- UMA_HISTOGRAM_CUSTOM_TIMES(
- FieldTrial::MakeName("Net.SocketIdleTimeBeforeNextUse_UnusedSocket",
- "SocketLateBinding").data(),
- handle.idle_time(), base::TimeDelta::FromMilliseconds(1),
- base::TimeDelta::FromMinutes(6), 100);
- }
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.SocketIdleTimeBeforeNextUse_UnusedSocket",
+ handle.idle_time(),
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(6),
+ 100);
break;
case ClientSocketHandle::REUSED_IDLE:
- UMA_HISTOGRAM_CUSTOM_TIMES(
- "Net.SocketIdleTimeBeforeNextUse_ReusedSocket",
- handle.idle_time(), base::TimeDelta::FromMilliseconds(1),
- base::TimeDelta::FromMinutes(6), 100);
- if (use_late_binding_histogram) {
- UMA_HISTOGRAM_CUSTOM_TIMES(
- FieldTrial::MakeName("Net.SocketIdleTimeBeforeNextUse_ReusedSocket",
- "SocketLateBinding").data(),
- handle.idle_time(), base::TimeDelta::FromMilliseconds(1),
- base::TimeDelta::FromMinutes(6), 100);
- }
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.SocketIdleTimeBeforeNextUse_ReusedSocket",
+ handle.idle_time(),
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(6),
+ 100);
break;
default:
NOTREACHED();
@@ -1272,18 +1363,8 @@
void HttpNetworkTransaction::LogIOErrorMetrics(
const ClientSocketHandle& handle) {
- static const bool use_late_binding_histogram =
- !FieldTrial::MakeName("", "SocketLateBinding").empty();
-
UMA_HISTOGRAM_ENUMERATION("Net.IOError_SocketReuseType",
- handle.reuse_type(), ClientSocketHandle::NUM_TYPES);
-
- if (use_late_binding_histogram) {
- UMA_HISTOGRAM_ENUMERATION(
- FieldTrial::MakeName("Net.IOError_SocketReuseType",
- "SocketLateBinding"),
- handle.reuse_type(), ClientSocketHandle::NUM_TYPES);
- }
+ handle.reuse_type(), ClientSocketHandle::NUM_TYPES);
switch (handle.reuse_type()) {
case ClientSocketHandle::UNUSED:
@@ -1293,26 +1374,12 @@
"Net.SocketIdleTimeOnIOError2_UnusedSocket",
handle.idle_time(), base::TimeDelta::FromMilliseconds(1),
base::TimeDelta::FromMinutes(6), 100);
- if (use_late_binding_histogram) {
- UMA_HISTOGRAM_CUSTOM_TIMES(
- FieldTrial::MakeName("Net.SocketIdleTimeOnIOError2_UnusedSocket",
- "SocketLateBinding").data(),
- handle.idle_time(), base::TimeDelta::FromMilliseconds(1),
- base::TimeDelta::FromMinutes(6), 100);
- }
break;
case ClientSocketHandle::REUSED_IDLE:
UMA_HISTOGRAM_CUSTOM_TIMES(
"Net.SocketIdleTimeOnIOError2_ReusedSocket",
handle.idle_time(), base::TimeDelta::FromMilliseconds(1),
base::TimeDelta::FromMinutes(6), 100);
- if (use_late_binding_histogram) {
- UMA_HISTOGRAM_CUSTOM_TIMES(
- FieldTrial::MakeName("Net.SocketIdleTimeOnIOError2_ReusedSocket",
- "SocketLateBinding").data(),
- handle.idle_time(), base::TimeDelta::FromMilliseconds(1),
- base::TimeDelta::FromMinutes(6), 100);
- }
break;
default:
NOTREACHED();
@@ -1320,7 +1387,12 @@
}
}
-void HttpNetworkTransaction::LogTransactionConnectedMetrics() const {
+void HttpNetworkTransaction::LogTransactionConnectedMetrics() {
+ if (logged_response_time_)
+ return;
+
+ logged_response_time_ = true;
+
base::TimeDelta total_duration = response_.response_time - start_time_;
UMA_HISTOGRAM_CLIPPED_TIMES(
@@ -1329,24 +1401,40 @@
base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(10),
100);
- static const bool use_late_binding_histogram =
- !FieldTrial::MakeName("", "SocketLateBinding").empty();
-
- if (use_late_binding_histogram) {
- UMA_HISTOGRAM_CUSTOM_TIMES(
- FieldTrial::MakeName("Net.Transaction_Connected_Under_10",
- "SocketLateBinding").data(),
- total_duration,
- base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(10),
- 100);
- }
-
if (!reused_socket_) {
UMA_HISTOGRAM_CLIPPED_TIMES(
"Net.Transaction_Connected_New",
total_duration,
base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(10),
100);
+
+ static bool use_conn_impact_histogram(
+ FieldTrialList::Find("ConnCountImpact") &&
+ !FieldTrialList::Find("ConnCountImpact")->group_name().empty());
+ if (use_conn_impact_histogram) {
+ UMA_HISTOGRAM_CLIPPED_TIMES(
+ FieldTrial::MakeName("Net.Transaction_Connected_New",
+ "ConnCountImpact"),
+ total_duration,
+ base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(10),
+ 100);
+ }
+ }
+
+ static bool use_spdy_histogram(FieldTrialList::Find("SpdyImpact") &&
+ !FieldTrialList::Find("SpdyImpact")->group_name().empty());
+ if (use_spdy_histogram && response_.was_npn_negotiated) {
+ UMA_HISTOGRAM_CLIPPED_TIMES(
+ FieldTrial::MakeName("Net.Transaction_Connected_Under_10", "SpdyImpact"),
+ total_duration, base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10), 100);
+
+ if (!reused_socket_) {
+ UMA_HISTOGRAM_CLIPPED_TIMES(
+ FieldTrial::MakeName("Net.Transaction_Connected_New", "SpdyImpact"),
+ total_duration, base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10), 100);
+ }
}
// Currently, non-zero priority requests are frame or sub-frame resource
@@ -1369,7 +1457,7 @@
void HttpNetworkTransaction::LogTransactionMetrics() const {
base::TimeDelta duration = base::Time::Now() -
- response_.request_time;
+ response_.request_time;
if (60 < duration.InMinutes())
return;
@@ -1377,11 +1465,13 @@
UMA_HISTOGRAM_LONG_TIMES("Net.Transaction_Latency", duration);
UMA_HISTOGRAM_CLIPPED_TIMES("Net.Transaction_Latency_Under_10", duration,
- base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(10),
- 100);
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10),
+ 100);
UMA_HISTOGRAM_CLIPPED_TIMES("Net.Transaction_Latency_Total_Under_10",
- total_duration, base::TimeDelta::FromMilliseconds(1),
- base::TimeDelta::FromMinutes(10), 100);
+ total_duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10), 100);
if (!reused_socket_) {
UMA_HISTOGRAM_CLIPPED_TIMES(
"Net.Transaction_Latency_Total_New_Connection_Under_10",
@@ -1390,79 +1480,42 @@
}
}
-void HttpNetworkTransaction::LogBlockedTunnelResponse(
- int response_code) const {
- LOG(WARNING) << "Blocked proxy response with status " << response_code
- << " to CONNECT request for "
- << GetHostAndPort(request_->url) << ".";
-}
-
int HttpNetworkTransaction::HandleCertificateError(int error) {
DCHECK(using_ssl_);
+ DCHECK(IsCertificateError(error));
- const int kCertFlags = LOAD_IGNORE_CERT_COMMON_NAME_INVALID |
- LOAD_IGNORE_CERT_DATE_INVALID |
- LOAD_IGNORE_CERT_AUTHORITY_INVALID |
- LOAD_IGNORE_CERT_WRONG_USAGE;
- if (request_->load_flags & kCertFlags) {
- switch (error) {
- case ERR_CERT_COMMON_NAME_INVALID:
- if (request_->load_flags & LOAD_IGNORE_CERT_COMMON_NAME_INVALID)
- error = OK;
- break;
- case ERR_CERT_DATE_INVALID:
- if (request_->load_flags & LOAD_IGNORE_CERT_DATE_INVALID)
- error = OK;
- break;
- case ERR_CERT_AUTHORITY_INVALID:
- if (request_->load_flags & LOAD_IGNORE_CERT_AUTHORITY_INVALID)
- error = OK;
- break;
- }
- }
+ SSLClientSocket* ssl_socket =
+ static_cast<SSLClientSocket*>(connection_->socket());
+ ssl_socket->GetSSLInfo(&response_.ssl_info);
- if (error != OK) {
- SSLClientSocket* ssl_socket =
- reinterpret_cast<SSLClientSocket*>(connection_->socket());
- ssl_socket->GetSSLInfo(&response_.ssl_info);
+ // Add the bad certificate to the set of allowed certificates in the
+ // SSL info object. This data structure will be consulted after calling
+ // RestartIgnoringLastError(). And the user will be asked interactively
+ // before RestartIgnoringLastError() is ever called.
+ SSLConfig::CertAndStatus bad_cert;
+ bad_cert.cert = response_.ssl_info.cert;
+ bad_cert.cert_status = response_.ssl_info.cert_status;
+ ssl_config_.allowed_bad_certs.push_back(bad_cert);
- // Add the bad certificate to the set of allowed certificates in the
- // SSL info object. This data structure will be consulted after calling
- // RestartIgnoringLastError(). And the user will be asked interactively
- // before RestartIgnoringLastError() is ever called.
- SSLConfig::CertAndStatus bad_cert;
- bad_cert.cert = response_.ssl_info.cert;
- bad_cert.cert_status = response_.ssl_info.cert_status;
- ssl_config_.allowed_bad_certs.push_back(bad_cert);
- }
+ int load_flags = request_->load_flags;
+ if (g_ignore_certificate_errors)
+ load_flags |= LOAD_IGNORE_ALL_CERT_ERRORS;
+ if (ssl_socket->IgnoreCertError(error, load_flags))
+ return OK;
return error;
}
int HttpNetworkTransaction::HandleCertificateRequest(int error) {
- // Assert that the socket did not send a client certificate.
- // Note: If we got a reused socket, it was created with some other
- // transaction's ssl_config_, so we need to disable this assertion. We can
- // get a certificate request on a reused socket when the server requested
- // renegotiation (rehandshake).
- // TODO(wtc): add a GetSSLParams method to SSLClientSocket so we can query
- // the SSL parameters it was created with and get rid of the reused_socket_
- // test.
- DCHECK(reused_socket_ || !ssl_config_.send_client_cert);
-
- response_.cert_request_info = new SSLCertRequestInfo;
- SSLClientSocket* ssl_socket =
- reinterpret_cast<SSLClientSocket*>(connection_->socket());
- ssl_socket->GetSSLCertRequestInfo(response_.cert_request_info);
-
// Close the connection while the user is selecting a certificate to send
// to the server.
- connection_->socket()->Disconnect();
+ if (connection_->socket())
+ connection_->socket()->Disconnect();
connection_->Reset();
// If the user selected one of the certificate in client_certs for this
// server before, use it automatically.
X509Certificate* client_cert = session_->ssl_client_auth_cache()->
- Lookup(GetHostAndPort(request_->url));
+ Lookup(GetHostAndPort(request_->url));
if (client_cert) {
const std::vector<scoped_refptr<X509Certificate> >& client_certs =
response_.cert_request_info->client_certs;
@@ -1483,21 +1536,23 @@
int HttpNetworkTransaction::HandleSSLHandshakeError(int error) {
if (ssl_config_.send_client_cert &&
- (error == ERR_SSL_PROTOCOL_ERROR ||
- error == ERR_BAD_SSL_CLIENT_AUTH_CERT)) {
+ (error == ERR_SSL_PROTOCOL_ERROR ||
+ error == ERR_BAD_SSL_CLIENT_AUTH_CERT)) {
session_->ssl_client_auth_cache()->Remove(GetHostAndPort(request_->url));
}
switch (error) {
case ERR_SSL_PROTOCOL_ERROR:
case ERR_SSL_VERSION_OR_CIPHER_MISMATCH:
- if (ssl_config_.tls1_enabled) {
- // This could be a TLS-intolerant server or an SSL 3.0 server that
- // chose a TLS-only cipher suite. Turn off TLS 1.0 and retry.
- ssl_config_.tls1_enabled = false;
- connection_->socket()->Disconnect();
- connection_->Reset();
- next_state_ = STATE_INIT_CONNECTION;
+ case ERR_SSL_DECOMPRESSION_FAILURE_ALERT:
+ case ERR_SSL_BAD_RECORD_MAC_ALERT:
+ if (ssl_config_.tls1_enabled &&
+ !SSLConfigService::IsKnownStrictTLSServer(request_->url.host())) {
+ // This could be a TLS-intolerant server, an SSL 3.0 server that
+ // chose a TLS-only cipher suite or a server with buggy DEFLATE
+ // support. Turn off TLS 1.0, DEFLATE support and retry.
+ g_tls_intolerant_servers->insert(GetHostAndPort(request_->url));
+ ResetConnectionAndRequestForResend();
error = OK;
}
break;
@@ -1546,8 +1601,7 @@
// NOTE: we resend a request only if we reused a keep-alive connection.
// This automatically prevents an infinite resend loop because we'll run
// out of the cached keep-alive connections eventually.
- if (establishing_tunnel_ ||
- !connection_->ShouldResendFailedRequest(error) ||
+ if (!connection_->ShouldResendFailedRequest(error) ||
GetResponseHeaders()) { // We have received some response headers.
return false;
}
@@ -1555,11 +1609,13 @@
}
void HttpNetworkTransaction::ResetConnectionAndRequestForResend() {
- connection_->socket()->Disconnect();
+ if (connection_->socket())
+ connection_->socket()->Disconnect();
connection_->Reset();
// We need to clear request_headers_ because it contains the real request
// headers, but we may need to resend the CONNECT request first to recreate
// the SSL tunnel.
+
request_headers_.clear();
next_state_ = STATE_INIT_CONNECTION; // Resend the request.
}
@@ -1585,7 +1641,18 @@
case ERR_CONNECTION_ABORTED:
case ERR_TIMED_OUT:
case ERR_TUNNEL_CONNECTION_FAILED:
+ case ERR_SOCKS_CONNECTION_FAILED:
break;
+ case ERR_SOCKS_CONNECTION_HOST_UNREACHABLE:
+ // Remap the SOCKS-specific "host unreachable" error to a more
+ // generic error code (this way consumers like the link doctor
+ // know to substitute their error page).
+ //
+ // Note that if the host resolving was done by the SOCSK5 proxy, we can't
+ // differentiate between a proxy-side "host not found" versus a proxy-side
+ // "address unreachable" error, and will report both of these failures as
+ // ERR_ADDRESS_UNREACHABLE.
+ return ERR_ADDRESS_UNREACHABLE;
default:
return error;
}
@@ -1595,7 +1662,7 @@
}
int rv = session_->proxy_service()->ReconsiderProxyAfterError(
- request_->url, &proxy_info_, &io_callback_, &pac_request_, load_log_);
+ request_->url, &proxy_info_, &io_callback_, &pac_request_, net_log_);
if (rv == OK || rv == ERR_IO_PENDING) {
// If the error was during connection setup, there is no socket to
// disconnect.
@@ -1615,178 +1682,11 @@
}
bool HttpNetworkTransaction::ShouldApplyProxyAuth() const {
- return (proxy_mode_ == kHTTPProxy) || establishing_tunnel_;
+ return !using_ssl_ && proxy_info_.is_http();
}
bool HttpNetworkTransaction::ShouldApplyServerAuth() const {
- return !establishing_tunnel_ &&
- !(request_->load_flags & LOAD_DO_NOT_SEND_AUTH_DATA);
-}
-
-std::string HttpNetworkTransaction::BuildAuthorizationHeader(
- HttpAuth::Target target) const {
- DCHECK(HaveAuth(target));
-
- // Add a Authorization/Proxy-Authorization header line.
- std::string credentials = auth_handler_[target]->GenerateCredentials(
- auth_identity_[target].username,
- auth_identity_[target].password,
- request_,
- &proxy_info_);
-
- return HttpAuth::GetAuthorizationHeaderName(target) +
- ": " + credentials + "\r\n";
-}
-
-GURL HttpNetworkTransaction::AuthOrigin(HttpAuth::Target target) const {
- return target == HttpAuth::AUTH_PROXY ?
- GURL("http://" + proxy_info_.proxy_server().host_and_port()) :
- request_->url.GetOrigin();
-}
-
-std::string HttpNetworkTransaction::AuthPath(HttpAuth::Target target)
- const {
- // Proxy authentication realms apply to all paths. So we will use
- // empty string in place of an absolute path.
- return target == HttpAuth::AUTH_PROXY ?
- std::string() : request_->url.path();
-}
-
-// static
-std::string HttpNetworkTransaction::AuthTargetString(
- HttpAuth::Target target) {
- return target == HttpAuth::AUTH_PROXY ? "proxy" : "server";
-}
-
-void HttpNetworkTransaction::InvalidateRejectedAuthFromCache(
- HttpAuth::Target target,
- const GURL& auth_origin) {
- DCHECK(HaveAuth(target));
-
- // TODO(eroman): this short-circuit can be relaxed. If the realm of
- // the preemptively used auth entry matches the realm of the subsequent
- // challenge, then we can invalidate the preemptively used entry.
- // Otherwise as-is we may send the failed credentials one extra time.
- if (auth_identity_[target].source == HttpAuth::IDENT_SRC_PATH_LOOKUP)
- return;
-
- // Clear the cache entry for the identity we just failed on.
- // Note: we require the username/password to match before invalidating
- // since the entry in the cache may be newer than what we used last time.
- session_->auth_cache()->Remove(auth_origin,
- auth_handler_[target]->realm(),
- auth_identity_[target].username,
- auth_identity_[target].password);
-}
-
-bool HttpNetworkTransaction::SelectPreemptiveAuth(HttpAuth::Target target) {
- DCHECK(!HaveAuth(target));
-
- // Don't do preemptive authorization if the URL contains a username/password,
- // since we must first be challenged in order to use the URL's identity.
- if (request_->url.has_username())
- return false;
-
- // SelectPreemptiveAuth() is on the critical path for each request, so it
- // is expected to be fast. LookupByPath() is fast in the common case, since
- // the number of http auth cache entries is expected to be very small.
- // (For most users in fact, it will be 0.)
-
- HttpAuthCache::Entry* entry = session_->auth_cache()->LookupByPath(
- AuthOrigin(target), AuthPath(target));
-
- // We don't support preemptive authentication for connection-based
- // authentication schemes because they can't reuse entry->handler().
- // Hopefully we can remove this limitation in the future.
- if (entry && !entry->handler()->is_connection_based()) {
- auth_identity_[target].source = HttpAuth::IDENT_SRC_PATH_LOOKUP;
- auth_identity_[target].invalid = false;
- auth_identity_[target].username = entry->username();
- auth_identity_[target].password = entry->password();
- auth_handler_[target] = entry->handler();
- return true;
- }
- return false;
-}
-
-bool HttpNetworkTransaction::SelectNextAuthIdentityToTry(
- HttpAuth::Target target,
- const GURL& auth_origin) {
- DCHECK(auth_handler_[target]);
- DCHECK(auth_identity_[target].invalid);
-
- // Try to use the username/password encoded into the URL first.
- if (target == HttpAuth::AUTH_SERVER && request_->url.has_username() &&
- !embedded_identity_used_) {
- auth_identity_[target].source = HttpAuth::IDENT_SRC_URL;
- auth_identity_[target].invalid = false;
- // Extract the username:password from the URL.
- GetIdentityFromURL(request_->url,
- &auth_identity_[target].username,
- &auth_identity_[target].password);
- embedded_identity_used_ = true;
- // TODO(eroman): If the password is blank, should we also try combining
- // with a password from the cache?
- return true;
- }
-
- // Check the auth cache for a realm entry.
- HttpAuthCache::Entry* entry = session_->auth_cache()->LookupByRealm(
- auth_origin, auth_handler_[target]->realm());
-
- if (entry) {
- // Disallow re-using of identity if the scheme of the originating challenge
- // does not match. This protects against the following situation:
- // 1. Browser prompts user to sign into DIGEST realm="Foo".
- // 2. Since the auth-scheme is not BASIC, the user is reasured that it
- // will not be sent over the wire in clear text. So they use their
- // most trusted password.
- // 3. Next, the browser receives a challenge for BASIC realm="Foo". This
- // is the same realm that we have a cached identity for. However if
- // we use that identity, it would get sent over the wire in
- // clear text (which isn't what the user agreed to when entering it).
- if (entry->handler()->scheme() != auth_handler_[target]->scheme()) {
- LOG(WARNING) << "The scheme of realm " << auth_handler_[target]->realm()
- << " has changed from " << entry->handler()->scheme()
- << " to " << auth_handler_[target]->scheme();
- return false;
- }
-
- auth_identity_[target].source = HttpAuth::IDENT_SRC_REALM_LOOKUP;
- auth_identity_[target].invalid = false;
- auth_identity_[target].username = entry->username();
- auth_identity_[target].password = entry->password();
- return true;
- }
- return false;
-}
-
-std::string HttpNetworkTransaction::AuthChallengeLogMessage() const {
- std::string msg;
- std::string header_val;
- void* iter = NULL;
- scoped_refptr<HttpResponseHeaders> headers = GetResponseHeaders();
- while (headers->EnumerateHeader(&iter, "proxy-authenticate", &header_val)) {
- msg.append("\n Has header Proxy-Authenticate: ");
- msg.append(header_val);
- }
-
- iter = NULL;
- while (headers->EnumerateHeader(&iter, "www-authenticate", &header_val)) {
- msg.append("\n Has header WWW-Authenticate: ");
- msg.append(header_val);
- }
-
- // RFC 4559 requires that a proxy indicate its support of NTLM/Negotiate
- // authentication with a "Proxy-Support: Session-Based-Authentication"
- // response header.
- iter = NULL;
- while (headers->EnumerateHeader(&iter, "proxy-support", &header_val)) {
- msg.append("\n Has header Proxy-Support: ");
- msg.append(header_val);
- }
-
- return msg;
+ return !(request_->load_flags & LOAD_DO_NOT_SEND_AUTH_DATA);
}
int HttpNetworkTransaction::HandleAuthChallenge() {
@@ -1797,91 +1697,99 @@
if (status != 401 && status != 407)
return OK;
HttpAuth::Target target = status == 407 ?
- HttpAuth::AUTH_PROXY : HttpAuth::AUTH_SERVER;
- GURL auth_origin = AuthOrigin(target);
-
- LOG(INFO) << "The " << AuthTargetString(target) << " "
- << auth_origin << " requested auth"
- << AuthChallengeLogMessage();
-
+ HttpAuth::AUTH_PROXY : HttpAuth::AUTH_SERVER;
if (target == HttpAuth::AUTH_PROXY && proxy_info_.is_direct())
return ERR_UNEXPECTED_PROXY_AUTH;
- // The auth we tried just failed, hence it can't be valid. Remove it from
- // the cache so it won't be used again.
- // TODO(wtc): IsFinalRound is not the right condition. In a multi-round
- // auth sequence, the server may fail the auth in round 1 if our first
- // authorization header is broken. We should inspect response_.headers to
- // determine if the server already failed the auth or wants us to continue.
- // See http://crbug.com/21015.
- if (HaveAuth(target) && auth_handler_[target]->IsFinalRound()) {
- InvalidateRejectedAuthFromCache(target, auth_origin);
- auth_handler_[target] = NULL;
- auth_identity_[target] = HttpAuth::Identity();
- }
+ int rv = auth_controllers_[target]->HandleAuthChallenge(
+ headers, (request_->load_flags & LOAD_DO_NOT_SEND_AUTH_DATA) != 0, false,
+ net_log_);
+ if (auth_controllers_[target]->HaveAuthHandler())
+ pending_auth_target_ = target;
- auth_identity_[target].invalid = true;
+ scoped_refptr<AuthChallengeInfo> auth_info =
+ auth_controllers_[target]->auth_info();
+ if (auth_info.get())
+ response_.auth_challenge = auth_info;
- if (target != HttpAuth::AUTH_SERVER ||
- !(request_->load_flags & LOAD_DO_NOT_SEND_AUTH_DATA)) {
- // Find the best authentication challenge that we support.
- HttpAuth::ChooseBestChallenge(headers, target, auth_origin,
- &auth_handler_[target]);
- }
-
- if (!auth_handler_[target]) {
- if (establishing_tunnel_) {
- LOG(ERROR) << "Can't perform auth to the " << AuthTargetString(target)
- << " " << auth_origin << " when establishing a tunnel"
- << AuthChallengeLogMessage();
-
- // We are establishing a tunnel, we can't show the error page because an
- // active network attacker could control its contents. Instead, we just
- // fail to establish the tunnel.
- DCHECK(target == HttpAuth::AUTH_PROXY);
- return ERR_PROXY_AUTH_REQUESTED;
- }
- // We found no supported challenge -- let the transaction continue
- // so we end up displaying the error page.
- return OK;
- }
-
- if (auth_handler_[target]->NeedsIdentity()) {
- // Pick a new auth identity to try, by looking to the URL and auth cache.
- // If an identity to try is found, it is saved to auth_identity_[target].
- SelectNextAuthIdentityToTry(target, auth_origin);
- } else {
- // Proceed with the existing identity or a null identity.
- //
- // TODO(wtc): Add a safeguard against infinite transaction restarts, if
- // the server keeps returning "NTLM".
- auth_identity_[target].invalid = false;
- }
-
- // Make a note that we are waiting for auth. This variable is inspected
- // when the client calls RestartWithAuth() to pick up where we left off.
- pending_auth_target_ = target;
-
- if (auth_identity_[target].invalid) {
- // We have exhausted all identity possibilities, all we can do now is
- // pass the challenge information back to the client.
- PopulateAuthChallenge(target, auth_origin);
- }
- return OK;
+ return rv;
}
-void HttpNetworkTransaction::PopulateAuthChallenge(HttpAuth::Target target,
- const GURL& auth_origin) {
- // Populates response_.auth_challenge with the authentication challenge info.
- // This info is consumed by URLRequestHttpJob::GetAuthChallengeInfo().
-
- AuthChallengeInfo* auth_info = new AuthChallengeInfo;
- auth_info->is_proxy = target == HttpAuth::AUTH_PROXY;
- auth_info->host_and_port = ASCIIToWide(GetHostAndPort(auth_origin));
- auth_info->scheme = ASCIIToWide(auth_handler_[target]->scheme());
- // TODO(eroman): decode realm according to RFC 2047.
- auth_info->realm = ASCIIToWide(auth_handler_[target]->realm());
- response_.auth_challenge = auth_info;
+GURL HttpNetworkTransaction::AuthURL(HttpAuth::Target target) const {
+ switch (target) {
+ case HttpAuth::AUTH_PROXY:
+ if (!proxy_info_.proxy_server().is_valid() ||
+ proxy_info_.proxy_server().is_direct()) {
+ return GURL(); // There is no proxy server.
+ }
+ return GURL("http://" + proxy_info_.proxy_server().host_and_port());
+ case HttpAuth::AUTH_SERVER:
+ return request_->url;
+ default:
+ return GURL();
+ }
}
+void HttpNetworkTransaction::MarkBrokenAlternateProtocolAndFallback() {
+ // We have to:
+ // * Reset the endpoint to be the unmodified URL specified destination.
+ // * Mark the endpoint as broken so we don't try again.
+ // * Set the alternate protocol mode to kDoNotUseAlternateProtocol so we
+ // ignore future Alternate-Protocol headers from the HostPortPair.
+ // * Reset the connection and go back to STATE_INIT_CONNECTION.
+
+ endpoint_ = HostPortPair(request_->url.HostNoBrackets(),
+ request_->url.EffectiveIntPort());
+
+ session_->mutable_alternate_protocols()->MarkBrokenAlternateProtocolFor(
+ endpoint_);
+
+ alternate_protocol_mode_ = kDoNotUseAlternateProtocol;
+ if (connection_->socket())
+ connection_->socket()->Disconnect();
+ connection_->Reset();
+ next_state_ = STATE_INIT_CONNECTION;
+}
+
+#define STATE_CASE(s) case s: \
+ description = StringPrintf("%s (0x%08X)", #s, s); \
+ break
+
+std::string HttpNetworkTransaction::DescribeState(State state) {
+ std::string description;
+ switch (state) {
+ STATE_CASE(STATE_RESOLVE_PROXY);
+ STATE_CASE(STATE_RESOLVE_PROXY_COMPLETE);
+ STATE_CASE(STATE_INIT_CONNECTION);
+ STATE_CASE(STATE_INIT_CONNECTION_COMPLETE);
+ STATE_CASE(STATE_GENERATE_PROXY_AUTH_TOKEN);
+ STATE_CASE(STATE_GENERATE_PROXY_AUTH_TOKEN_COMPLETE);
+ STATE_CASE(STATE_GENERATE_SERVER_AUTH_TOKEN);
+ STATE_CASE(STATE_GENERATE_SERVER_AUTH_TOKEN_COMPLETE);
+ STATE_CASE(STATE_SEND_REQUEST);
+ STATE_CASE(STATE_SEND_REQUEST_COMPLETE);
+ STATE_CASE(STATE_READ_HEADERS);
+ STATE_CASE(STATE_READ_HEADERS_COMPLETE);
+ STATE_CASE(STATE_READ_BODY);
+ STATE_CASE(STATE_READ_BODY_COMPLETE);
+ STATE_CASE(STATE_DRAIN_BODY_FOR_AUTH_RESTART);
+ STATE_CASE(STATE_DRAIN_BODY_FOR_AUTH_RESTART_COMPLETE);
+ STATE_CASE(STATE_SPDY_GET_STREAM);
+ STATE_CASE(STATE_SPDY_GET_STREAM_COMPLETE);
+ STATE_CASE(STATE_SPDY_SEND_REQUEST);
+ STATE_CASE(STATE_SPDY_SEND_REQUEST_COMPLETE);
+ STATE_CASE(STATE_SPDY_READ_HEADERS);
+ STATE_CASE(STATE_SPDY_READ_HEADERS_COMPLETE);
+ STATE_CASE(STATE_SPDY_READ_BODY);
+ STATE_CASE(STATE_SPDY_READ_BODY_COMPLETE);
+ STATE_CASE(STATE_NONE);
+ default:
+ description = StringPrintf("Unknown state 0x%08X (%u)", state, state);
+ break;
+ }
+ return description;
+}
+
+#undef STATE_CASE
+
} // namespace net
diff --git a/net/http/http_network_transaction.h b/net/http/http_network_transaction.h
index 8a6d460..7c32bd3 100644
--- a/net/http/http_network_transaction.h
+++ b/net/http/http_network_transaction.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -12,12 +12,14 @@
#include "base/scoped_ptr.h"
#include "base/time.h"
#include "net/base/address_list.h"
-#include "net/base/host_resolver.h"
#include "net/base/io_buffer.h"
#include "net/base/load_flags.h"
#include "net/base/load_states.h"
+#include "net/base/net_log.h"
#include "net/base/ssl_config_service.h"
+#include "net/http/http_alternate_protocols.h"
#include "net/http/http_auth.h"
+#include "net/http/http_auth_controller.h"
#include "net/http/http_auth_handler.h"
#include "net/http/http_response_info.h"
#include "net/http/http_transaction.h"
@@ -29,9 +31,10 @@
class ClientSocketFactory;
class ClientSocketHandle;
-class FlipStream;
class HttpNetworkSession;
+class HttpRequestHeaders;
class HttpStream;
+class SpdyHttpStream;
class HttpNetworkTransaction : public HttpTransaction {
public:
@@ -39,13 +42,22 @@
virtual ~HttpNetworkTransaction();
+ static void SetHostMappingRules(const std::string& rules);
+
+ // Controls whether or not we use the Alternate-Protocol header.
+ static void SetUseAlternateProtocols(bool value);
+
// Sets the next protocol negotiation value used during the SSL handshake.
static void SetNextProtos(const std::string& next_protos);
+ // Sets the HttpNetworkTransaction into a mode where it can ignore
+ // certificate errors. This is for testing.
+ static void IgnoreCertificateErrors(bool enabled);
+
// HttpTransaction methods:
virtual int Start(const HttpRequestInfo* request_info,
CompletionCallback* callback,
- LoadLog* load_log);
+ const BoundNetLog& net_log);
virtual int RestartIgnoringLastError(CompletionCallback* callback);
virtual int RestartWithCertificate(X509Certificate* client_cert,
CompletionCallback* callback);
@@ -58,6 +70,7 @@
}
virtual int Read(IOBuffer* buf, int buf_len, CompletionCallback* callback);
+ virtual void StopCaching() {}
virtual const HttpResponseInfo* GetResponseInfo() const;
virtual LoadState GetLoadState() const;
virtual uint64 GetUploadProgress() const;
@@ -70,10 +83,10 @@
STATE_RESOLVE_PROXY_COMPLETE,
STATE_INIT_CONNECTION,
STATE_INIT_CONNECTION_COMPLETE,
- STATE_SOCKS_CONNECT,
- STATE_SOCKS_CONNECT_COMPLETE,
- STATE_SSL_CONNECT,
- STATE_SSL_CONNECT_COMPLETE,
+ STATE_GENERATE_PROXY_AUTH_TOKEN,
+ STATE_GENERATE_PROXY_AUTH_TOKEN_COMPLETE,
+ STATE_GENERATE_SERVER_AUTH_TOKEN,
+ STATE_GENERATE_SERVER_AUTH_TOKEN_COMPLETE,
STATE_SEND_REQUEST,
STATE_SEND_REQUEST_COMPLETE,
STATE_READ_HEADERS,
@@ -82,6 +95,8 @@
STATE_READ_BODY_COMPLETE,
STATE_DRAIN_BODY_FOR_AUTH_RESTART,
STATE_DRAIN_BODY_FOR_AUTH_RESTART_COMPLETE,
+ STATE_SPDY_GET_STREAM,
+ STATE_SPDY_GET_STREAM_COMPLETE,
STATE_SPDY_SEND_REQUEST,
STATE_SPDY_SEND_REQUEST_COMPLETE,
STATE_SPDY_READ_HEADERS,
@@ -91,11 +106,10 @@
STATE_NONE
};
- enum ProxyMode {
- kDirectConnection, // If using a direct connection
- kHTTPProxy, // If using a proxy for HTTP (not HTTPS)
- kHTTPProxyUsingTunnel, // If using a tunnel for HTTPS
- kSOCKSProxy, // If using a SOCKS proxy
+ enum AlternateProtocolMode {
+ kUnspecified, // Unspecified, check HttpAlternateProtocols
+ kUsingAlternateProtocol, // Using an alternate protocol
+ kDoNotUseAlternateProtocol, // Failed to connect once, do not try again.
};
void DoCallback(int result);
@@ -112,10 +126,10 @@
int DoResolveProxyComplete(int result);
int DoInitConnection();
int DoInitConnectionComplete(int result);
- int DoSOCKSConnect();
- int DoSOCKSConnectComplete(int result);
- int DoSSLConnect();
- int DoSSLConnectComplete(int result);
+ int DoGenerateProxyAuthToken();
+ int DoGenerateProxyAuthTokenComplete(int result);
+ int DoGenerateServerAuthToken();
+ int DoGenerateServerAuthTokenComplete(int result);
int DoSendRequest();
int DoSendRequestComplete(int result);
int DoReadHeaders();
@@ -124,6 +138,8 @@
int DoReadBodyComplete(int result);
int DoDrainBodyForAuthRestart();
int DoDrainBodyForAuthRestartComplete(int result);
+ int DoSpdyGetStream();
+ int DoSpdyGetStreamComplete(int result);
int DoSpdySendRequest();
int DoSpdySendRequestComplete(int result);
int DoSpdyReadHeaders();
@@ -132,10 +148,10 @@
int DoSpdyReadBodyComplete(int result);
// Record histograms of latency until Connect() completes.
- static void LogTCPConnectedMetrics(const ClientSocketHandle& handle);
+ static void LogHttpConnectedMetrics(const ClientSocketHandle& handle);
// Record histogram of time until first byte of header is received.
- void LogTransactionConnectedMetrics() const;
+ void LogTransactionConnectedMetrics();
// Record histogram of latency (durations until last byte received).
void LogTransactionMetrics() const;
@@ -205,71 +221,28 @@
// Returns true if we should try to add an Authorization header.
bool ShouldApplyServerAuth() const;
- // Builds either the proxy auth header, or the origin server auth header,
- // as specified by |target|.
- std::string BuildAuthorizationHeader(HttpAuth::Target target) const;
-
- // Returns a log message for all the response headers related to the auth
- // challenge.
- std::string AuthChallengeLogMessage() const;
-
// Handles HTTP status code 401 or 407.
// HandleAuthChallenge() returns a network error code, or OK on success.
// May update |pending_auth_target_| or |response_.auth_challenge|.
int HandleAuthChallenge();
- // Populates response_.auth_challenge with the challenge information, so that
- // URLRequestHttpJob can prompt for a username/password.
- void PopulateAuthChallenge(HttpAuth::Target target,
- const GURL& auth_origin);
-
- // Invalidates any auth cache entries after authentication has failed.
- // The identity that was rejected is auth_identity_[target].
- void InvalidateRejectedAuthFromCache(HttpAuth::Target target,
- const GURL& auth_origin);
-
- // Sets auth_identity_[target] to the next identity that the transaction
- // should try. It chooses candidates by searching the auth cache
- // and the URL for a username:password. Returns true if an identity
- // was found.
- bool SelectNextAuthIdentityToTry(HttpAuth::Target target,
- const GURL& auth_origin);
-
- // Searches the auth cache for an entry that encompasses the request's path.
- // If such an entry is found, updates auth_identity_[target] and
- // auth_handler_[target] with the cache entry's data and returns true.
- bool SelectPreemptiveAuth(HttpAuth::Target target);
-
bool HaveAuth(HttpAuth::Target target) const {
- return auth_handler_[target].get() && !auth_identity_[target].invalid;
+ return auth_controllers_[target].get() &&
+ auth_controllers_[target]->HaveAuth();
}
- // Get the {scheme, host, port} for the authentication target
- GURL AuthOrigin(HttpAuth::Target target) const;
+ // Get the {scheme, host, path, port} for the authentication target
+ GURL AuthURL(HttpAuth::Target target) const;
- // Get the absolute path of the resource needing authentication.
- // For proxy authentication the path is always empty string.
- std::string AuthPath(HttpAuth::Target target) const;
+ void MarkBrokenAlternateProtocolAndFallback();
- // Returns a string representation of a HttpAuth::Target value that can be
- // used in log messages.
- static std::string AuthTargetString(HttpAuth::Target target);
+ // Debug helper.
+ static std::string DescribeState(State state);
- static std::string* g_next_protos;
+ static bool g_ignore_certificate_errors;
- // The following three auth members are arrays of size two -- index 0 is
- // for the proxy server, and index 1 is for the origin server.
- // Use the enum HttpAuth::Target to index into them.
-
- // auth_handler encapsulates the logic for the particular auth-scheme.
- // This includes the challenge's parameters. If NULL, then there is no
- // associated auth handler.
- scoped_refptr<HttpAuthHandler> auth_handler_[2];
-
- // auth_identity_ holds the (username/password) that should be used by
- // the auth_handler_ to generate credentials. This identity can come from
- // a number of places (url, cache, prompt).
- HttpAuth::Identity auth_identity_[2];
+ scoped_refptr<HttpAuthController>
+ auth_controllers_[HttpAuth::AUTH_NUM_TARGETS];
// Whether this transaction is waiting for proxy auth, server auth, or is
// not waiting for any auth at all. |pending_auth_target_| is read and
@@ -281,7 +254,7 @@
scoped_refptr<HttpNetworkSession> session_;
- scoped_refptr<LoadLog> load_log_;
+ BoundNetLog net_log_;
const HttpRequestInfo* request_;
HttpResponseInfo response_;
@@ -290,7 +263,7 @@
scoped_ptr<ClientSocketHandle> connection_;
scoped_ptr<HttpStream> http_stream_;
- scoped_refptr<FlipStream> spdy_stream_;
+ scoped_ptr<SpdyHttpStream> spdy_http_stream_;
bool reused_socket_;
// True if we've validated the headers that the stream parser has returned.
@@ -299,22 +272,20 @@
// True if we've logged the time of the first response byte. Used to
// prevent logging across authentication activity where we see multiple
// responses.
- bool logged_response_time;
+ bool logged_response_time_;
bool using_ssl_; // True if handling a HTTPS request
- ProxyMode proxy_mode_;
- // True while establishing a tunnel. This allows the HTTP CONNECT
- // request/response to reuse the STATE_SEND_REQUEST,
- // STATE_SEND_REQUEST_COMPLETE, STATE_READ_HEADERS, and
- // STATE_READ_HEADERS_COMPLETE states and allows us to tell them apart from
- // the real request/response of the transaction.
- bool establishing_tunnel_;
+ // True if this network transaction is using SPDY instead of HTTP.
+ bool using_spdy_;
- // True if we've used the username/password embedded in the URL. This
- // makes sure we use the embedded identity only once for the transaction,
- // preventing an infinite auth restart loop.
- bool embedded_identity_used_;
+ // The certificate error while using SPDY over SSL for insecure URLs.
+ int spdy_certificate_error_;
+
+ AlternateProtocolMode alternate_protocol_mode_;
+
+ // Only valid if |alternate_protocol_mode_| == kUsingAlternateProtocol.
+ HttpAlternateProtocols::Protocol alternate_protocol_;
SSLConfig ssl_config_;
@@ -332,11 +303,18 @@
// The time the Start method was called.
base::Time start_time_;
- // The time the DoSSLConnect() method was called (if it got called).
- base::TimeTicks ssl_connect_start_time_;
-
// The next state in the state machine.
State next_state_;
+
+ // The hostname and port of the endpoint. This is not necessarily the one
+ // specified by the URL, due to Alternate-Protocol or fixed testing ports.
+ HostPortPair endpoint_;
+
+ // True when the tunnel is in the process of being established - we can't
+ // read from the socket until the tunnel is done.
+ bool establishing_tunnel_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpNetworkTransaction);
};
} // namespace net
diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc
index 9639457..03e4b31 100644
--- a/net/http/http_network_transaction_unittest.cc
+++ b/net/http/http_network_transaction_unittest.cc
@@ -1,88 +1,144 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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 <math.h> // ceil
+#include "net/http/http_network_transaction.h"
+#include <math.h> // ceil
+#include <vector>
+
+#include "base/basictypes.h"
#include "base/compiler_specific.h"
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/scoped_ptr.h"
+#include "net/base/capturing_net_log.h"
#include "net/base/completion_callback.h"
#include "net/base/mock_host_resolver.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
#include "net/base/request_priority.h"
#include "net/base/ssl_config_service_defaults.h"
#include "net/base/ssl_info.h"
#include "net/base/test_completion_callback.h"
#include "net/base/upload_data.h"
-#include "net/flip/flip_session_pool.h"
+#include "net/http/http_auth_handler_digest.h"
+#include "net/http/http_auth_handler_mock.h"
#include "net/http/http_auth_handler_ntlm.h"
#include "net/http/http_basic_stream.h"
#include "net/http/http_network_session.h"
-#include "net/http/http_network_transaction.h"
#include "net/http/http_stream.h"
#include "net/http/http_transaction_unittest.h"
#include "net/proxy/proxy_config_service_fixed.h"
+#include "net/proxy/proxy_resolver.h"
+#include "net/proxy/proxy_service.h"
#include "net/socket/client_socket_factory.h"
#include "net/socket/socket_test_util.h"
#include "net/socket/ssl_client_socket.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_session_pool.h"
+#include "net/spdy/spdy_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
//-----------------------------------------------------------------------------
-// TODO(eroman): Add a regression test for http://crbug.com/32316 -- when the
-// proxy service returns an error, we should fallback to DIRECT instead of
-// failing with ERR_NO_SUPPORTED_PROXIES.
-
namespace net {
-// Create a proxy service which fails on all requests (falls back to direct).
-ProxyService* CreateNullProxyService() {
- return ProxyService::CreateNull();
-}
+class HttpNetworkSessionPeer {
+ public:
+ explicit HttpNetworkSessionPeer(
+ const scoped_refptr<HttpNetworkSession>& session)
+ : session_(session) {}
+
+ void SetTCPSocketPool(const scoped_refptr<TCPClientSocketPool>& pool) {
+ session_->tcp_socket_pool_ = pool;
+ }
+
+ void SetSocketPoolForSOCKSProxy(
+ const HostPortPair& socks_proxy,
+ const scoped_refptr<SOCKSClientSocketPool>& pool) {
+ session_->socks_socket_pools_[socks_proxy] = pool;
+ }
+
+ void SetSocketPoolForHTTPProxy(
+ const HostPortPair& http_proxy,
+ const scoped_refptr<HttpProxyClientSocketPool>& pool) {
+ session_->http_proxy_socket_pools_[http_proxy] = pool;
+ }
+
+ void SetSSLSocketPool(const scoped_refptr<SSLClientSocketPool>& pool) {
+ session_->ssl_socket_pool_ = pool;
+ }
+
+ void SetSocketPoolForSSLWithProxy(
+ const HostPortPair& proxy_host,
+ const scoped_refptr<SSLClientSocketPool>& pool) {
+ session_->ssl_socket_pools_for_proxies_[proxy_host] = pool;
+ }
+
+ private:
+ const scoped_refptr<HttpNetworkSession> session_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpNetworkSessionPeer);
+};
// Helper to manage the lifetimes of the dependencies for a
// HttpNetworkTransaction.
-class SessionDependencies {
- public:
+struct SessionDependencies {
// Default set of dependencies -- "null" proxy service.
SessionDependencies()
: host_resolver(new MockHostResolver),
- proxy_service(CreateNullProxyService()),
+ proxy_service(ProxyService::CreateNull()),
ssl_config_service(new SSLConfigServiceDefaults),
- flip_session_pool(new FlipSessionPool) {}
+ http_auth_handler_factory(HttpAuthHandlerFactory::CreateDefault()),
+ spdy_session_pool(new SpdySessionPool()),
+ net_log(NULL) {}
// Custom proxy service dependency.
explicit SessionDependencies(ProxyService* proxy_service)
: host_resolver(new MockHostResolver),
proxy_service(proxy_service),
ssl_config_service(new SSLConfigServiceDefaults),
- flip_session_pool(new FlipSessionPool) {}
+ http_auth_handler_factory(HttpAuthHandlerFactory::CreateDefault()),
+ spdy_session_pool(new SpdySessionPool()),
+ net_log(NULL) {}
scoped_refptr<MockHostResolverBase> host_resolver;
scoped_refptr<ProxyService> proxy_service;
scoped_refptr<SSLConfigService> ssl_config_service;
MockClientSocketFactory socket_factory;
- scoped_refptr<FlipSessionPool> flip_session_pool;
+ scoped_ptr<HttpAuthHandlerFactory> http_auth_handler_factory;
+ scoped_refptr<SpdySessionPool> spdy_session_pool;
+ NetLog* net_log;
};
ProxyService* CreateFixedProxyService(const std::string& proxy) {
net::ProxyConfig proxy_config;
- proxy_config.proxy_rules.ParseFromString(proxy);
+ proxy_config.proxy_rules().ParseFromString(proxy);
return ProxyService::CreateFixed(proxy_config);
}
-
HttpNetworkSession* CreateSession(SessionDependencies* session_deps) {
- return new HttpNetworkSession(NULL,
- session_deps->host_resolver,
+ return new HttpNetworkSession(session_deps->host_resolver,
session_deps->proxy_service,
&session_deps->socket_factory,
session_deps->ssl_config_service,
- session_deps->flip_session_pool);
+ session_deps->spdy_session_pool,
+ session_deps->http_auth_handler_factory.get(),
+ NULL,
+ session_deps->net_log);
}
class HttpNetworkTransactionTest : public PlatformTest {
public:
+ virtual void SetUp() {
+ spdy::SpdyFramer::set_enable_compression_default(false);
+ }
+
virtual void TearDown() {
+ spdy::SpdyFramer::set_enable_compression_default(true);
// Empty the current queue.
MessageLoop::current()->RunAllPending();
PlatformTest::TearDown();
@@ -97,7 +153,8 @@
std::string response_data;
};
- SimpleGetHelperResult SimpleGetHelper(MockRead data_reads[]) {
+ SimpleGetHelperResult SimpleGetHelper(MockRead data_reads[],
+ size_t reads_count) {
SimpleGetHelperResult out;
SessionDependencies session_deps;
@@ -109,12 +166,13 @@
request.url = GURL("http://www.google.com/");
request.load_flags = 0;
- StaticSocketDataProvider data(data_reads, NULL);
+ StaticSocketDataProvider data(data_reads, reads_count, NULL, 0);
session_deps.socket_factory.AddSocketDataProvider(&data);
TestCompletionCallback callback;
- int rv = trans->Start(&request, &callback, NULL);
+ CapturingBoundNetLog log(CapturingNetLog::kUnbounded);
+ int rv = trans->Start(&request, &callback, log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
out.rv = callback.WaitForResult();
@@ -129,6 +187,13 @@
rv = ReadTransaction(trans.get(), &out.response_data);
EXPECT_EQ(OK, rv);
+ size_t pos = ExpectLogContainsSomewhere(
+ log.entries(), 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST_HEADERS,
+ NetLog::PHASE_NONE);
+ ExpectLogContainsSomewhere(
+ log.entries(), pos,
+ NetLog::TYPE_HTTP_TRANSACTION_READ_RESPONSE_HEADERS,
+ NetLog::PHASE_NONE);
return out;
}
@@ -183,9 +248,11 @@
return "WTC-WIN7";
}
-class CaptureGroupNameSocketPool : public TCPClientSocketPool {
+template<typename ParentPool>
+class CaptureGroupNameSocketPool : public ParentPool {
public:
- CaptureGroupNameSocketPool() : TCPClientSocketPool(0, 0, NULL, NULL, NULL) {}
+ explicit CaptureGroupNameSocketPool(HttpNetworkSession* session);
+
const std::string last_group_name_received() const {
return last_group_name_;
}
@@ -195,12 +262,12 @@
RequestPriority priority,
ClientSocketHandle* handle,
CompletionCallback* callback,
- LoadLog* load_log) {
+ const BoundNetLog& net_log) {
last_group_name_ = group_name;
return ERR_IO_PENDING;
}
virtual void CancelRequest(const std::string& group_name,
- const ClientSocketHandle* handle) { }
+ ClientSocketHandle* handle) {}
virtual void ReleaseSocket(const std::string& group_name,
ClientSocket* socket) {}
virtual void CloseIdleSockets() {}
@@ -217,11 +284,34 @@
const ClientSocketHandle* handle) const {
return LOAD_STATE_IDLE;
}
+ virtual base::TimeDelta ConnectionTimeout() const {
+ return base::TimeDelta();
+ }
private:
std::string last_group_name_;
};
+typedef CaptureGroupNameSocketPool<TCPClientSocketPool>
+CaptureGroupNameTCPSocketPool;
+typedef CaptureGroupNameSocketPool<HttpProxyClientSocketPool>
+CaptureGroupNameHttpProxySocketPool;
+typedef CaptureGroupNameSocketPool<SOCKSClientSocketPool>
+CaptureGroupNameSOCKSSocketPool;
+typedef CaptureGroupNameSocketPool<SSLClientSocketPool>
+CaptureGroupNameSSLSocketPool;
+
+template<typename ParentPool>
+CaptureGroupNameSocketPool<ParentPool>::CaptureGroupNameSocketPool(
+ HttpNetworkSession* session)
+ : ParentPool(0, 0, NULL, session->host_resolver(), NULL, NULL) {}
+
+template<>
+CaptureGroupNameSSLSocketPool::CaptureGroupNameSocketPool(
+ HttpNetworkSession* session)
+ : SSLClientSocketPool(0, 0, NULL, session->host_resolver(), NULL, NULL,
+ NULL, NULL, NULL) {}
+
//-----------------------------------------------------------------------------
TEST_F(HttpNetworkTransactionTest, Basic) {
@@ -236,7 +326,8 @@
MockRead("hello world"),
MockRead(false, OK),
};
- SimpleGetHelperResult out = SimpleGetHelper(data_reads);
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
EXPECT_EQ(OK, out.rv);
EXPECT_EQ("HTTP/1.0 200 OK", out.status_line);
EXPECT_EQ("hello world", out.response_data);
@@ -248,7 +339,8 @@
MockRead("hello world"),
MockRead(false, OK),
};
- SimpleGetHelperResult out = SimpleGetHelper(data_reads);
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
EXPECT_EQ(OK, out.rv);
EXPECT_EQ("HTTP/0.9 200 OK", out.status_line);
EXPECT_EQ("hello world", out.response_data);
@@ -260,7 +352,8 @@
MockRead("xxxHTTP/1.0 404 Not Found\nServer: blah\n\nDATA"),
MockRead(false, OK),
};
- SimpleGetHelperResult out = SimpleGetHelper(data_reads);
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
EXPECT_EQ(OK, out.rv);
EXPECT_EQ("HTTP/1.0 404 Not Found", out.status_line);
EXPECT_EQ("DATA", out.response_data);
@@ -272,7 +365,8 @@
MockRead("\n\nQJHTTP/1.0 404 Not Found\nServer: blah\n\nDATA"),
MockRead(false, OK),
};
- SimpleGetHelperResult out = SimpleGetHelper(data_reads);
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
EXPECT_EQ(OK, out.rv);
EXPECT_EQ("HTTP/1.0 404 Not Found", out.status_line);
EXPECT_EQ("DATA", out.response_data);
@@ -284,7 +378,8 @@
MockRead("xxxxxHTTP/1.1 404 Not Found\nServer: blah"),
MockRead(false, OK),
};
- SimpleGetHelperResult out = SimpleGetHelper(data_reads);
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
EXPECT_EQ(OK, out.rv);
EXPECT_EQ("HTTP/0.9 200 OK", out.status_line);
EXPECT_EQ("xxxxxHTTP/1.1 404 Not Found\nServer: blah", out.response_data);
@@ -300,7 +395,8 @@
MockRead("HTTP/1.0 404 Not Found\nServer: blah\n\nDATA"),
MockRead(false, OK),
};
- SimpleGetHelperResult out = SimpleGetHelper(data_reads);
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
EXPECT_EQ(OK, out.rv);
EXPECT_EQ("HTTP/1.0 404 Not Found", out.status_line);
EXPECT_EQ("DATA", out.response_data);
@@ -312,7 +408,8 @@
MockRead("HTT"),
MockRead(false, OK),
};
- SimpleGetHelperResult out = SimpleGetHelper(data_reads);
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
EXPECT_EQ(OK, out.rv);
EXPECT_EQ("HTTP/0.9 200 OK", out.status_line);
EXPECT_EQ("HTT", out.response_data);
@@ -327,7 +424,8 @@
MockRead("junk"), // Should not be read!!
MockRead(false, OK),
};
- SimpleGetHelperResult out = SimpleGetHelper(data_reads);
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
EXPECT_EQ(OK, out.rv);
EXPECT_EQ("HTTP/1.1 204 No Content", out.status_line);
EXPECT_EQ("", out.response_data);
@@ -345,7 +443,8 @@
MockRead("0\r\n\r\nHTTP/1.1 200 OK\r\n"),
MockRead(false, OK),
};
- SimpleGetHelperResult out = SimpleGetHelper(data_reads);
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
EXPECT_EQ(OK, out.rv);
EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
EXPECT_EQ("Hello world", out.response_data);
@@ -378,12 +477,13 @@
MockRead(false, ERR_UNEXPECTED), // Should not be reached.
};
- StaticSocketDataProvider data1(data_reads1, data_writes1);
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
session_deps.socket_factory.AddSocketDataProvider(&data1);
TestCompletionCallback callback1;
- int rv = trans->Start(&request, &callback1, NULL);
+ int rv = trans->Start(&request, &callback1, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
@@ -423,7 +523,7 @@
MockRead("world"),
MockRead(false, OK),
};
- StaticSocketDataProvider data(data_reads, NULL);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
session_deps.socket_factory.AddSocketDataProvider(&data);
const char* kExpectedResponseData[] = {
@@ -440,7 +540,7 @@
TestCompletionCallback callback;
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -477,12 +577,12 @@
MockRead("hello world"),
MockRead(false, OK),
};
- StaticSocketDataProvider data(data_reads, NULL);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
session_deps.socket_factory.AddSocketDataProvider(&data);
TestCompletionCallback callback;
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -519,12 +619,12 @@
MockRead("hello world"),
MockRead(false, OK),
};
- StaticSocketDataProvider data(data_reads, NULL);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
session_deps.socket_factory.AddSocketDataProvider(&data);
TestCompletionCallback callback;
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -556,12 +656,12 @@
MockRead(false, "HTTP/1.0 100 Continue\r\n"),
MockRead(true, 0),
};
- StaticSocketDataProvider data(data_reads, NULL);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
session_deps.socket_factory.AddSocketDataProvider(&data);
TestCompletionCallback callback;
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -586,12 +686,12 @@
MockRead data_reads[] = {
MockRead(true, 0),
};
- StaticSocketDataProvider data(data_reads, NULL);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
session_deps.socket_factory.AddSocketDataProvider(&data);
TestCompletionCallback callback;
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -615,7 +715,7 @@
MockRead("hello"),
read_failure, // Now, we reuse the connection and fail the first read.
};
- StaticSocketDataProvider data1(data1_reads, NULL);
+ StaticSocketDataProvider data1(data1_reads, arraysize(data1_reads), NULL, 0);
session_deps.socket_factory.AddSocketDataProvider(&data1);
MockRead data2_reads[] = {
@@ -623,7 +723,7 @@
MockRead("world"),
MockRead(true, OK),
};
- StaticSocketDataProvider data2(data2_reads, NULL);
+ StaticSocketDataProvider data2(data2_reads, arraysize(data2_reads), NULL, 0);
session_deps.socket_factory.AddSocketDataProvider(&data2);
const char* kExpectedResponseData[] = {
@@ -635,7 +735,7 @@
scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -680,12 +780,12 @@
MockRead("hello world"),
MockRead(false, OK),
};
- StaticSocketDataProvider data(data_reads, NULL);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
session_deps.socket_factory.AddSocketDataProvider(&data);
TestCompletionCallback callback;
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -711,10 +811,65 @@
MockRead("hello world"),
MockRead(false, OK),
};
- SimpleGetHelperResult out = SimpleGetHelper(data_reads);
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
EXPECT_EQ(ERR_EMPTY_RESPONSE, out.rv);
}
+// Test that we correctly reuse a keep-alive connection after receiving a 304.
+TEST_F(HttpNetworkTransactionTest, KeepAliveAfter304) {
+ SessionDependencies session_deps;
+ scoped_refptr<HttpNetworkSession> session = CreateSession(&session_deps);
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.foo.com/");
+ request.load_flags = 0;
+
+ MockRead data1_reads[] = {
+ MockRead("HTTP/1.1 304 Not Modified\r\n\r\n"),
+ MockRead("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n"),
+ MockRead("hello"),
+ };
+ StaticSocketDataProvider data1(data1_reads, arraysize(data1_reads), NULL, 0);
+ session_deps.socket_factory.AddSocketDataProvider(&data1);
+
+ MockRead data2_reads[] = {
+ MockRead(false, ERR_UNEXPECTED), // Should not be reached.
+ };
+ StaticSocketDataProvider data2(data2_reads, arraysize(data2_reads), NULL, 0);
+ session_deps.socket_factory.AddSocketDataProvider(&data2);
+
+ for (int i = 0; i < 2; ++i) {
+ TestCompletionCallback callback;
+
+ scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
+
+ int rv = trans->Start(&request, &callback, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_TRUE(response != NULL);
+
+ EXPECT_TRUE(response->headers != NULL);
+ if (i == 0) {
+ EXPECT_EQ("HTTP/1.1 304 Not Modified",
+ response->headers->GetStatusLine());
+ // We intentionally don't read the response in this case, to reflect how
+ // HttpCache::Transaction uses HttpNetworkTransaction.
+ } else {
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ std::string response_data;
+ rv = ReadTransaction(trans.get(), &response_data);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("hello", response_data);
+ }
+ }
+}
+
// Test the request-challenge-retry sequence for basic auth.
// (basic auth is the easiest to mock, because it has no randomness).
TEST_F(HttpNetworkTransactionTest, BasicAuth) {
@@ -763,14 +918,16 @@
MockRead(false, OK),
};
- StaticSocketDataProvider data1(data_reads1, data_writes1);
- StaticSocketDataProvider data2(data_reads2, data_writes2);
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
session_deps.socket_factory.AddSocketDataProvider(&data1);
session_deps.socket_factory.AddSocketDataProvider(&data2);
TestCompletionCallback callback1;
- int rv = trans->Start(&request, &callback1, NULL);
+ int rv = trans->Start(&request, &callback1, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
@@ -825,11 +982,12 @@
MockRead(false, ERR_FAILED),
};
- StaticSocketDataProvider data(data_reads, data_writes);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
session_deps.socket_factory.AddSocketDataProvider(&data);
TestCompletionCallback callback;
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -879,12 +1037,13 @@
MockRead(false, OK),
};
- StaticSocketDataProvider data1(data_reads1, data_writes1);
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
session_deps.socket_factory.AddSocketDataProvider(&data1);
TestCompletionCallback callback1;
- int rv = trans->Start(&request, &callback1, NULL);
+ int rv = trans->Start(&request, &callback1, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
@@ -951,12 +1110,13 @@
MockRead(false, OK),
};
- StaticSocketDataProvider data1(data_reads1, data_writes1);
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
session_deps.socket_factory.AddSocketDataProvider(&data1);
TestCompletionCallback callback1;
- int rv = trans->Start(&request, &callback1, NULL);
+ int rv = trans->Start(&request, &callback1, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
@@ -1031,12 +1191,13 @@
MockRead(false, OK),
};
- StaticSocketDataProvider data1(data_reads1, data_writes1);
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
session_deps.socket_factory.AddSocketDataProvider(&data1);
TestCompletionCallback callback1;
- int rv = trans->Start(&request, &callback1, NULL);
+ int rv = trans->Start(&request, &callback1, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
@@ -1118,14 +1279,16 @@
MockRead(false, OK),
};
- StaticSocketDataProvider data1(data_reads1, data_writes1);
- StaticSocketDataProvider data2(data_reads2, data_writes2);
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
session_deps.socket_factory.AddSocketDataProvider(&data1);
session_deps.socket_factory.AddSocketDataProvider(&data2);
TestCompletionCallback callback1;
- int rv = trans->Start(&request, &callback1, NULL);
+ int rv = trans->Start(&request, &callback1, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
@@ -1160,6 +1323,8 @@
TEST_F(HttpNetworkTransactionTest, BasicAuthProxyKeepAlive) {
// Configure against proxy server "myproxy:70".
SessionDependencies session_deps(CreateFixedProxyService("myproxy:70"));
+ CapturingBoundNetLog log(CapturingNetLog::kUnbounded);
+ session_deps.net_log = log.bound().net_log();
scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
@@ -1202,16 +1367,24 @@
MockRead(false, ERR_UNEXPECTED), // Should not be reached.
};
- StaticSocketDataProvider data1(data_reads1, data_writes1);
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
session_deps.socket_factory.AddSocketDataProvider(&data1);
TestCompletionCallback callback1;
- int rv = trans->Start(&request, &callback1, NULL);
+ int rv = trans->Start(&request, &callback1, log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
EXPECT_EQ(OK, rv);
+ size_t pos = ExpectLogContainsSomewhere(
+ log.entries(), 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS,
+ NetLog::PHASE_NONE);
+ ExpectLogContainsSomewhere(
+ log.entries(), pos,
+ NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS,
+ NetLog::PHASE_NONE);
const HttpResponseInfo* response = trans->GetResponseInfo();
EXPECT_FALSE(response == NULL);
@@ -1251,6 +1424,10 @@
EXPECT_EQ(L"myproxy:70", response->auth_challenge->host_and_port);
EXPECT_EQ(L"MyRealm1", response->auth_challenge->realm);
EXPECT_EQ(L"basic", response->auth_challenge->scheme);
+
+ // Flush the idle socket before the NetLog and HttpNetworkTransaction go
+ // out of scope.
+ session->FlushSocketPools();
}
// Test that we don't read the response body when we fail to establish a tunnel,
@@ -1283,12 +1460,13 @@
MockRead(false, ERR_UNEXPECTED), // Should not be reached.
};
- StaticSocketDataProvider data(data_reads, data_writes);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
session_deps.socket_factory.AddSocketDataProvider(&data);
TestCompletionCallback callback;
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -1305,6 +1483,49 @@
std::string response_data;
rv = ReadTransaction(trans.get(), &response_data);
EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv);
+
+ // Flush the idle socket before the HttpNetworkTransaction goes out of scope.
+ session->FlushSocketPools();
+}
+
+// Test when a server (non-proxy) returns a 407 (proxy-authenticate).
+// The request should fail with ERR_UNEXPECTED_PROXY_AUTH.
+TEST_F(HttpNetworkTransactionTest, UnexpectedProxyAuth) {
+ // We are using a DIRECT connection (i.e. no proxy) for this session.
+ SessionDependencies session_deps;
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(CreateSession(&session_deps)));
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ MockWrite data_writes1[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.0 407 Proxy Auth required\r\n"),
+ MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ // Large content-length -- won't matter, as connection will be reset.
+ MockRead("Content-Length: 10000\r\n\r\n"),
+ MockRead(false, ERR_FAILED),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ session_deps.socket_factory.AddSocketDataProvider(&data1);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, &callback, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(ERR_UNEXPECTED_PROXY_AUTH, rv);
}
void HttpNetworkTransactionTest::ConnectStatusHelperWithExpectedStatus(
@@ -1335,12 +1556,13 @@
MockRead(false, ERR_UNEXPECTED), // Should not be reached.
};
- StaticSocketDataProvider data(data_reads, data_writes);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
session_deps.socket_factory.AddSocketDataProvider(&data);
TestCompletionCallback callback;
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -1448,7 +1670,7 @@
TEST_F(HttpNetworkTransactionTest, ConnectStatus407) {
ConnectStatusHelperWithExpectedStatus(
MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"),
- ERR_PROXY_AUTH_REQUESTED);
+ ERR_PROXY_AUTH_UNSUPPORTED);
}
TEST_F(HttpNetworkTransactionTest, ConnectStatus408) {
@@ -1590,16 +1812,19 @@
MockRead(false, OK),
};
- StaticSocketDataProvider data1(data_reads1, data_writes1);
- StaticSocketDataProvider data2(data_reads2, data_writes2);
- StaticSocketDataProvider data3(data_reads3, data_writes3);
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
+ StaticSocketDataProvider data3(data_reads3, arraysize(data_reads3),
+ data_writes3, arraysize(data_writes3));
session_deps.socket_factory.AddSocketDataProvider(&data1);
session_deps.socket_factory.AddSocketDataProvider(&data2);
session_deps.socket_factory.AddSocketDataProvider(&data3);
TestCompletionCallback callback1;
- int rv = trans->Start(&request, &callback1, NULL);
+ int rv = trans->Start(&request, &callback1, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
@@ -1655,7 +1880,7 @@
// Enter the correct password and authenticate successfully.
TEST_F(HttpNetworkTransactionTest, NTLMAuth1) {
HttpAuthHandlerNTLM::ScopedProcSetter proc_setter(MockGenerateRandom1,
- MockGetHostName);
+ MockGetHostName);
SessionDependencies session_deps;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(CreateSession(&session_deps)));
@@ -1673,8 +1898,9 @@
MockRead data_reads1[] = {
MockRead("HTTP/1.1 401 Access Denied\r\n"),
- // Negotiate and NTLM are often requested together. We only support NTLM.
- MockRead("WWW-Authenticate: Negotiate\r\n"),
+ // Negotiate and NTLM are often requested together. However, we only want
+ // to test NTLM. Since Negotiate is preferred over NTLM, we have to skip
+ // the header that requests Negotiate for this test.
MockRead("WWW-Authenticate: NTLM\r\n"),
MockRead("Connection: close\r\n"),
MockRead("Content-Length: 42\r\n"),
@@ -1729,14 +1955,16 @@
MockRead(false, OK),
};
- StaticSocketDataProvider data1(data_reads1, data_writes1);
- StaticSocketDataProvider data2(data_reads2, data_writes2);
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
session_deps.socket_factory.AddSocketDataProvider(&data1);
session_deps.socket_factory.AddSocketDataProvider(&data2);
TestCompletionCallback callback1;
- int rv = trans->Start(&request, &callback1, NULL);
+ int rv = trans->Start(&request, &callback1, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
@@ -1751,9 +1979,10 @@
EXPECT_FALSE(trans->IsReadyToRestartForAuth());
const HttpResponseInfo* response = trans->GetResponseInfo();
- EXPECT_FALSE(response == NULL);
+ ASSERT_FALSE(response == NULL);
- // The password prompt info should have been set in response->auth_challenge.
+ // The password prompt info should have been set in
+ // response->auth_challenge.
EXPECT_FALSE(response->auth_challenge.get() == NULL);
EXPECT_EQ(L"172.22.68.17:80", response->auth_challenge->host_and_port);
@@ -1769,6 +1998,8 @@
EXPECT_EQ(OK, rv);
response = trans->GetResponseInfo();
+ ASSERT_FALSE(response == NULL);
+
EXPECT_TRUE(response->auth_challenge.get() == NULL);
EXPECT_EQ(13, response->headers->GetContentLength());
}
@@ -1776,7 +2007,7 @@
// Enter a wrong password, and then the correct one.
TEST_F(HttpNetworkTransactionTest, NTLMAuth2) {
HttpAuthHandlerNTLM::ScopedProcSetter proc_setter(MockGenerateRandom2,
- MockGetHostName);
+ MockGetHostName);
SessionDependencies session_deps;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(CreateSession(&session_deps)));
@@ -1794,8 +2025,9 @@
MockRead data_reads1[] = {
MockRead("HTTP/1.1 401 Access Denied\r\n"),
- // Negotiate and NTLM are often requested together. We only support NTLM.
- MockRead("WWW-Authenticate: Negotiate\r\n"),
+ // Negotiate and NTLM are often requested together. However, we only want
+ // to test NTLM. Since Negotiate is preferred over NTLM, we have to skip
+ // the header that requests Negotiate for this test.
MockRead("WWW-Authenticate: NTLM\r\n"),
MockRead("Connection: close\r\n"),
MockRead("Content-Length: 42\r\n"),
@@ -1844,7 +2076,6 @@
// Wrong password.
MockRead("HTTP/1.1 401 Access Denied\r\n"),
- MockRead("WWW-Authenticate: Negotiate\r\n"),
MockRead("WWW-Authenticate: NTLM\r\n"),
MockRead("Connection: close\r\n"),
MockRead("Content-Length: 42\r\n"),
@@ -1899,16 +2130,19 @@
MockRead(false, OK),
};
- StaticSocketDataProvider data1(data_reads1, data_writes1);
- StaticSocketDataProvider data2(data_reads2, data_writes2);
- StaticSocketDataProvider data3(data_reads3, data_writes3);
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
+ StaticSocketDataProvider data3(data_reads3, arraysize(data_reads3),
+ data_writes3, arraysize(data_writes3));
session_deps.socket_factory.AddSocketDataProvider(&data1);
session_deps.socket_factory.AddSocketDataProvider(&data2);
session_deps.socket_factory.AddSocketDataProvider(&data3);
TestCompletionCallback callback1;
- int rv = trans->Start(&request, &callback1, NULL);
+ int rv = trans->Start(&request, &callback1, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
@@ -1997,12 +2231,12 @@
MockRead("\r\nBODY"),
MockRead(false, OK),
};
- StaticSocketDataProvider data(data_reads, NULL);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
session_deps.socket_factory.AddSocketDataProvider(&data);
TestCompletionCallback callback;
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -2044,12 +2278,13 @@
MockRead(false, ERR_UNEXPECTED), // Should not be reached.
};
- StaticSocketDataProvider data1(data_reads1, data_writes1);
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
session_deps.socket_factory.AddSocketDataProvider(&data1);
TestCompletionCallback callback1;
- int rv = trans->Start(&request, &callback1, NULL);
+ int rv = trans->Start(&request, &callback1, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
@@ -2093,12 +2328,12 @@
MockRead(false, OK),
};
- StaticSocketDataProvider data(data_reads, NULL);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
session_deps.socket_factory.AddSocketDataProvider(&data);
TestCompletionCallback callback;
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -2150,12 +2385,12 @@
MockRead(false, OK),
};
- StaticSocketDataProvider data(data_reads, NULL);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
session_deps.socket_factory.AddSocketDataProvider(&data);
TestCompletionCallback callback;
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -2219,7 +2454,8 @@
MockWrite(false, 93), // POST
MockWrite(false, ERR_CONNECTION_ABORTED), // POST data
};
- StaticSocketDataProvider data1(data_reads1, data_writes1);
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
// The second socket is used for the second attempt of transaction 2.
@@ -2234,7 +2470,8 @@
MockWrite(false, 93), // POST
MockWrite(false, 3), // POST data
};
- StaticSocketDataProvider data2(data_reads2, data_writes2);
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
session_deps.socket_factory.AddSocketDataProvider(&data1);
session_deps.socket_factory.AddSocketDataProvider(&data2);
@@ -2249,7 +2486,7 @@
TestCompletionCallback callback;
- int rv = trans->Start(&request[i], &callback, NULL);
+ int rv = trans->Start(&request[i], &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -2315,14 +2552,16 @@
MockRead(false, OK),
};
- StaticSocketDataProvider data1(data_reads1, data_writes1);
- StaticSocketDataProvider data2(data_reads2, data_writes2);
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
session_deps.socket_factory.AddSocketDataProvider(&data1);
session_deps.socket_factory.AddSocketDataProvider(&data2);
TestCompletionCallback callback1;
- int rv = trans->Start(&request, &callback1, NULL);
+ int rv = trans->Start(&request, &callback1, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
@@ -2408,16 +2647,19 @@
MockRead(false, OK),
};
- StaticSocketDataProvider data1(data_reads1, data_writes1);
- StaticSocketDataProvider data2(data_reads2, data_writes2);
- StaticSocketDataProvider data3(data_reads3, data_writes3);
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
+ StaticSocketDataProvider data3(data_reads3, arraysize(data_reads3),
+ data_writes3, arraysize(data_writes3));
session_deps.socket_factory.AddSocketDataProvider(&data1);
session_deps.socket_factory.AddSocketDataProvider(&data2);
session_deps.socket_factory.AddSocketDataProvider(&data3);
TestCompletionCallback callback1;
- int rv = trans->Start(&request, &callback1, NULL);
+ int rv = trans->Start(&request, &callback1, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
@@ -2501,14 +2743,16 @@
MockRead(false, OK),
};
- StaticSocketDataProvider data1(data_reads1, data_writes1);
- StaticSocketDataProvider data2(data_reads2, data_writes2);
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
session_deps.socket_factory.AddSocketDataProvider(&data1);
session_deps.socket_factory.AddSocketDataProvider(&data2);
TestCompletionCallback callback1;
- int rv = trans->Start(&request, &callback1, NULL);
+ int rv = trans->Start(&request, &callback1, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
@@ -2584,14 +2828,16 @@
MockRead(false, OK),
};
- StaticSocketDataProvider data1(data_reads1, data_writes1);
- StaticSocketDataProvider data2(data_reads2, data_writes2);
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
session_deps.socket_factory.AddSocketDataProvider(&data1);
session_deps.socket_factory.AddSocketDataProvider(&data2);
TestCompletionCallback callback1;
- int rv = trans->Start(&request, &callback1, NULL);
+ int rv = trans->Start(&request, &callback1, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
@@ -2650,12 +2896,13 @@
MockRead(false, OK),
};
- StaticSocketDataProvider data1(data_reads1, data_writes1);
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
session_deps.socket_factory.AddSocketDataProvider(&data1);
TestCompletionCallback callback1;
- int rv = trans->Start(&request, &callback1, NULL);
+ int rv = trans->Start(&request, &callback1, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
@@ -2708,14 +2955,16 @@
MockRead(false, OK),
};
- StaticSocketDataProvider data1(data_reads1, data_writes1);
- StaticSocketDataProvider data2(data_reads2, data_writes2);
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
session_deps.socket_factory.AddSocketDataProvider(&data1);
session_deps.socket_factory.AddSocketDataProvider(&data2);
TestCompletionCallback callback1;
- int rv = trans->Start(&request, &callback1, NULL);
+ int rv = trans->Start(&request, &callback1, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
@@ -2792,16 +3041,19 @@
MockRead(false, OK),
};
- StaticSocketDataProvider data1(data_reads1, data_writes1);
- StaticSocketDataProvider data2(data_reads2, data_writes2);
- StaticSocketDataProvider data3(data_reads3, data_writes3);
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
+ StaticSocketDataProvider data3(data_reads3, arraysize(data_reads3),
+ data_writes3, arraysize(data_writes3));
session_deps.socket_factory.AddSocketDataProvider(&data1);
session_deps.socket_factory.AddSocketDataProvider(&data2);
session_deps.socket_factory.AddSocketDataProvider(&data3);
TestCompletionCallback callback1;
- int rv = trans->Start(&request, &callback1, NULL);
+ int rv = trans->Start(&request, &callback1, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
@@ -2841,6 +3093,141 @@
}
}
+// Tests that nonce count increments when multiple auth attempts
+// are started with the same nonce.
+TEST_F(HttpNetworkTransactionTest, DigestPreAuthNonceCount) {
+ SessionDependencies session_deps;
+ scoped_refptr<HttpNetworkSession> session = CreateSession(&session_deps);
+ HttpAuthHandlerDigest::SetFixedCnonce(true);
+
+ // Transaction 1: authenticate (foo, bar) on MyRealm1
+ {
+ scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/x/y/z");
+ request.load_flags = 0;
+
+ MockWrite data_writes1[] = {
+ MockWrite("GET /x/y/z HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.0 401 Unauthorized\r\n"),
+ MockRead("WWW-Authenticate: Digest realm=\"digestive\", nonce=\"OU812\", "
+ "algorithm=MD5, qop=\"auth\"\r\n\r\n"),
+ MockRead(false, OK),
+ };
+
+ // Resend with authorization (username=foo, password=bar)
+ MockWrite data_writes2[] = {
+ MockWrite("GET /x/y/z HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: Digest username=\"foo\", realm=\"digestive\", "
+ "nonce=\"OU812\", uri=\"/x/y/z\", algorithm=MD5, "
+ "response=\"03ffbcd30add722589c1de345d7a927f\", qop=auth, "
+ "nc=00000001, cnonce=\"0123456789abcdef\"\r\n\r\n"),
+ };
+
+ // Sever accepts the authorization.
+ MockRead data_reads2[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead(false, OK),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
+ session_deps.socket_factory.AddSocketDataProvider(&data1);
+ session_deps.socket_factory.AddSocketDataProvider(&data2);
+
+ TestCompletionCallback callback1;
+
+ int rv = trans->Start(&request, &callback1, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_FALSE(response == NULL);
+
+ // The password prompt info should have been set in
+ // response->auth_challenge.
+ ASSERT_FALSE(response->auth_challenge.get() == NULL);
+
+ EXPECT_EQ(L"www.google.com:80", response->auth_challenge->host_and_port);
+ EXPECT_EQ(L"digestive", response->auth_challenge->realm);
+ EXPECT_EQ(L"digest", response->auth_challenge->scheme);
+
+ TestCompletionCallback callback2;
+
+ rv = trans->RestartWithAuth(L"foo", L"bar", &callback2);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ response = trans->GetResponseInfo();
+ ASSERT_FALSE(response == NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ }
+
+ // ------------------------------------------------------------------------
+
+ // Transaction 2: Request another resource in digestive's protection space.
+ // This will preemptively add an Authorization header which should have an
+ // "nc" value of 2 (as compared to 1 in the first use.
+ {
+ scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ // Note that Transaction 1 was at /x/y/z, so this is in the same
+ // protection space as digest.
+ request.url = GURL("http://www.google.com/x/y/a/b");
+ request.load_flags = 0;
+
+ MockWrite data_writes1[] = {
+ MockWrite("GET /x/y/a/b HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: Digest username=\"foo\", realm=\"digestive\", "
+ "nonce=\"OU812\", uri=\"/x/y/a/b\", algorithm=MD5, "
+ "response=\"d6f9a2c07d1c5df7b89379dca1269b35\", qop=auth, "
+ "nc=00000002, cnonce=\"0123456789abcdef\"\r\n\r\n"),
+ };
+
+ // Sever accepts the authorization.
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(false, OK),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ session_deps.socket_factory.AddSocketDataProvider(&data1);
+
+ TestCompletionCallback callback1;
+
+ int rv = trans->Start(&request, &callback1, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_FALSE(response == NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ }
+}
+
// Test the ResetStateForRestart() private method.
TEST_F(HttpNetworkTransactionTest, ResetStateForRestart) {
// Create a transaction (the dependencies aren't important).
@@ -2865,7 +3252,8 @@
std::string temp("HTTP/1.1 200 OK\nVary: foo, bar\n\n");
std::replace(temp.begin(), temp.end(), '\n', '\0');
scoped_refptr<HttpResponseHeaders> headers = new HttpResponseHeaders(temp);
- request.extra_headers = "Foo: 1\nbar: 23";
+ request.extra_headers.SetHeader("Foo", "1");
+ request.extra_headers.SetHeader("bar", "23");
EXPECT_TRUE(response->vary_data.Init(request, *headers));
}
@@ -2908,7 +3296,8 @@
};
StaticSocketDataProvider ssl_bad_certificate;
- StaticSocketDataProvider data(data_reads, data_writes);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
SSLSocketDataProvider ssl_bad(true, ERR_CERT_AUTHORITY_INVALID);
SSLSocketDataProvider ssl(true, OK);
@@ -2919,7 +3308,7 @@
TestCompletionCallback callback;
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -2975,8 +3364,11 @@
MockRead(false, OK),
};
- StaticSocketDataProvider ssl_bad_certificate(proxy_reads, proxy_writes);
- StaticSocketDataProvider data(data_reads, data_writes);
+ StaticSocketDataProvider ssl_bad_certificate(
+ proxy_reads, arraysize(proxy_reads),
+ proxy_writes, arraysize(proxy_writes));
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
SSLSocketDataProvider ssl_bad(true, ERR_CERT_AUTHORITY_INVALID);
SSLSocketDataProvider ssl(true, OK);
@@ -2993,7 +3385,7 @@
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(CreateSession(&session_deps)));
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -3020,7 +3412,8 @@
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.google.com/");
- request.user_agent = "Chromium Ultra Awesome X Edition";
+ request.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent,
+ "Chromium Ultra Awesome X Edition");
MockWrite data_writes[] = {
MockWrite("GET / HTTP/1.1\r\n"
@@ -3037,12 +3430,13 @@
MockRead(false, OK),
};
- StaticSocketDataProvider data(data_reads, data_writes);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
session_deps.socket_factory.AddSocketDataProvider(&data);
TestCompletionCallback callback;
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -3075,12 +3469,13 @@
MockRead(false, OK),
};
- StaticSocketDataProvider data(data_reads, data_writes);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
session_deps.socket_factory.AddSocketDataProvider(&data);
TestCompletionCallback callback;
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -3111,12 +3506,13 @@
MockRead(false, OK),
};
- StaticSocketDataProvider data(data_reads, data_writes);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
session_deps.socket_factory.AddSocketDataProvider(&data);
TestCompletionCallback callback;
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -3147,12 +3543,13 @@
MockRead(false, OK),
};
- StaticSocketDataProvider data(data_reads, data_writes);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
session_deps.socket_factory.AddSocketDataProvider(&data);
TestCompletionCallback callback;
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -3183,12 +3580,13 @@
MockRead(false, OK),
};
- StaticSocketDataProvider data(data_reads, data_writes);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
session_deps.socket_factory.AddSocketDataProvider(&data);
TestCompletionCallback callback;
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -3221,12 +3619,13 @@
MockRead(false, OK),
};
- StaticSocketDataProvider data(data_reads, data_writes);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
session_deps.socket_factory.AddSocketDataProvider(&data);
TestCompletionCallback callback;
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -3259,12 +3658,13 @@
MockRead(false, OK),
};
- StaticSocketDataProvider data(data_reads, data_writes);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
session_deps.socket_factory.AddSocketDataProvider(&data);
TestCompletionCallback callback;
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -3279,7 +3679,7 @@
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.google.com/");
- request.extra_headers = "FooHeader: Bar\r\n";
+ request.extra_headers.SetHeader("FooHeader", "Bar");
MockWrite data_writes[] = {
MockWrite("GET / HTTP/1.1\r\n"
@@ -3296,12 +3696,54 @@
MockRead(false, OK),
};
- StaticSocketDataProvider data(data_reads, data_writes);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
session_deps.socket_factory.AddSocketDataProvider(&data);
TestCompletionCallback callback;
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+}
+
+TEST_F(HttpNetworkTransactionTest, BuildRequest_ExtraHeadersStripped) {
+ SessionDependencies session_deps;
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(CreateSession(&session_deps)));
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.extra_headers.SetHeader("referer", "www.foo.com");
+ request.extra_headers.SetHeader("hEllo", "Kitty");
+ request.extra_headers.SetHeader("FoO", "bar");
+
+ MockWrite data_writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "hEllo: Kitty\r\n"
+ "FoO: bar\r\n\r\n"),
+ };
+
+ // Lastly, the server responds with the actual content.
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(false, OK),
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps.socket_factory.AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -3338,12 +3780,13 @@
MockRead(false, OK)
};
- StaticSocketDataProvider data(data_reads, data_writes);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
session_deps.socket_factory.AddSocketDataProvider(&data);
TestCompletionCallback callback;
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -3390,7 +3833,8 @@
MockRead(false, OK)
};
- StaticSocketDataProvider data(data_reads, data_writes);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
session_deps.socket_factory.AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(true, OK);
@@ -3398,7 +3842,7 @@
TestCompletionCallback callback;
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -3457,12 +3901,13 @@
MockRead(false, OK)
};
- StaticSocketDataProvider data(data_reads, data_writes);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
session_deps.socket_factory.AddSocketDataProvider(&data);
TestCompletionCallback callback;
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -3523,7 +3968,8 @@
MockRead(false, OK)
};
- StaticSocketDataProvider data(data_reads, data_writes);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
session_deps.socket_factory.AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(true, OK);
@@ -3531,7 +3977,7 @@
TestCompletionCallback callback;
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -3547,70 +3993,226 @@
}
// Tests that for connection endpoints the group names are correctly set.
-TEST_F(HttpNetworkTransactionTest, GroupNameForProxyConnections) {
- const struct {
- const std::string proxy_server;
- const std::string url;
- const std::string expected_group_name;
- } tests[] = {
+
+struct GroupNameTest {
+ std::string proxy_server;
+ std::string url;
+ std::string expected_group_name;
+ bool ssl;
+};
+
+scoped_refptr<HttpNetworkSession> SetupSessionForGroupNameTests(
+ const std::string& proxy_server) {
+ SessionDependencies session_deps(CreateFixedProxyService(proxy_server));
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
+
+ HttpAlternateProtocols* alternate_protocols =
+ session->mutable_alternate_protocols();
+ alternate_protocols->SetAlternateProtocolFor(
+ HostPortPair("host.with.alternate", 80), 443,
+ HttpAlternateProtocols::NPN_SPDY_1);
+
+ return session;
+}
+
+int GroupNameTransactionHelper(
+ const std::string& url,
+ const scoped_refptr<HttpNetworkSession>& session) {
+ scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL(url);
+ request.load_flags = 0;
+
+ TestCompletionCallback callback;
+
+ // We do not complete this request, the dtor will clean the transaction up.
+ return trans->Start(&request, &callback, BoundNetLog());
+}
+
+TEST_F(HttpNetworkTransactionTest, GroupNameForDirectConnections) {
+ const GroupNameTest tests[] = {
{
- "", // no proxy (direct)
+ "", // unused
"http://www.google.com/direct",
- "http://www.google.com/",
+ "www.google.com:80",
+ false,
},
{
- "http_proxy",
- "http://www.google.com/http_proxy_normal",
- "proxy/http_proxy:80/",
- },
- {
- "socks4://socks_proxy:1080",
- "http://www.google.com/socks4_direct",
- "proxy/socks4://socks_proxy:1080/http://www.google.com/",
+ "", // unused
+ "http://[2001:1418:13:1::25]/direct",
+ "[2001:1418:13:1::25]:80",
+ false,
},
// SSL Tests
{
- "",
+ "", // unused
"https://www.google.com/direct_ssl",
- "https://www.google.com/",
+ "ssl/www.google.com:443",
+ true,
},
{
- "http_proxy",
- "https://www.google.com/http_connect_ssl",
- "proxy/http_proxy:80/https://www.google.com/",
+ "", // unused
+ "https://[2001:1418:13:1::25]/direct",
+ "ssl/[2001:1418:13:1::25]:443",
+ true,
},
{
- "socks4://socks_proxy:1080",
- "https://www.google.com/socks4_ssl",
- "proxy/socks4://socks_proxy:1080/https://www.google.com/",
+ "", // unused
+ "http://host.with.alternate/direct",
+ "ssl/host.with.alternate:443",
+ true,
},
};
+ HttpNetworkTransaction::SetUseAlternateProtocols(true);
+
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
- SessionDependencies session_deps(
- CreateFixedProxyService(tests[i].proxy_server));
+ scoped_refptr<HttpNetworkSession> session(
+ SetupSessionForGroupNameTests(tests[i].proxy_server));
- scoped_refptr<CaptureGroupNameSocketPool> conn_pool(
- new CaptureGroupNameSocketPool());
+ HttpNetworkSessionPeer peer(session);
+ scoped_refptr<CaptureGroupNameTCPSocketPool> tcp_conn_pool(
+ new CaptureGroupNameTCPSocketPool(session.get()));
+ peer.SetTCPSocketPool(tcp_conn_pool);
+ scoped_refptr<CaptureGroupNameSSLSocketPool> ssl_conn_pool(
+ new CaptureGroupNameSSLSocketPool(session.get()));
+ peer.SetSSLSocketPool(ssl_conn_pool);
- scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
- session->tcp_socket_pool_ = conn_pool.get();
+ EXPECT_EQ(ERR_IO_PENDING,
+ GroupNameTransactionHelper(tests[i].url, session));
+ if (tests[i].ssl)
+ EXPECT_EQ(tests[i].expected_group_name,
+ ssl_conn_pool->last_group_name_received());
+ else
+ EXPECT_EQ(tests[i].expected_group_name,
+ tcp_conn_pool->last_group_name_received());
+ }
+
+ HttpNetworkTransaction::SetUseAlternateProtocols(false);
+}
+
+TEST_F(HttpNetworkTransactionTest, GroupNameForHTTPProxyConnections) {
+ const GroupNameTest tests[] = {
+ {
+ "http_proxy",
+ "http://www.google.com/http_proxy_normal",
+ "www.google.com:80",
+ false,
+ },
+
+ // SSL Tests
+ {
+ "http_proxy",
+ "https://www.google.com/http_connect_ssl",
+ "ssl/www.google.com:443",
+ true,
+ },
+
+ {
+ "http_proxy",
+ "http://host.with.alternate/direct",
+ "ssl/host.with.alternate:443",
+ true,
+ },
+ };
+
+ HttpNetworkTransaction::SetUseAlternateProtocols(true);
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ scoped_refptr<HttpNetworkSession> session(
+ SetupSessionForGroupNameTests(tests[i].proxy_server));
+
+ HttpNetworkSessionPeer peer(session);
+
+ HostPortPair proxy_host("http_proxy", 80);
+ scoped_refptr<CaptureGroupNameHttpProxySocketPool> http_proxy_pool(
+ new CaptureGroupNameHttpProxySocketPool(session.get()));
+ peer.SetSocketPoolForHTTPProxy(proxy_host, http_proxy_pool);
+ scoped_refptr<CaptureGroupNameSSLSocketPool> ssl_conn_pool(
+ new CaptureGroupNameSSLSocketPool(session.get()));
+ peer.SetSocketPoolForSSLWithProxy(proxy_host, ssl_conn_pool);
+
+ EXPECT_EQ(ERR_IO_PENDING,
+ GroupNameTransactionHelper(tests[i].url, session));
+ if (tests[i].ssl)
+ EXPECT_EQ(tests[i].expected_group_name,
+ ssl_conn_pool->last_group_name_received());
+ else
+ EXPECT_EQ(tests[i].expected_group_name,
+ http_proxy_pool->last_group_name_received());
+ }
+
+ HttpNetworkTransaction::SetUseAlternateProtocols(false);
+}
+
+TEST_F(HttpNetworkTransactionTest, GroupNameForSOCKSConnections) {
+ const GroupNameTest tests[] = {
+ {
+ "socks4://socks_proxy:1080",
+ "http://www.google.com/socks4_direct",
+ "socks4/www.google.com:80",
+ false,
+ },
+ {
+ "socks5://socks_proxy:1080",
+ "http://www.google.com/socks5_direct",
+ "socks5/www.google.com:80",
+ false,
+ },
+
+ // SSL Tests
+ {
+ "socks4://socks_proxy:1080",
+ "https://www.google.com/socks4_ssl",
+ "socks4/ssl/www.google.com:443",
+ true,
+ },
+ {
+ "socks5://socks_proxy:1080",
+ "https://www.google.com/socks5_ssl",
+ "socks5/ssl/www.google.com:443",
+ true,
+ },
+
+ {
+ "socks4://socks_proxy:1080",
+ "http://host.with.alternate/direct",
+ "socks4/ssl/host.with.alternate:443",
+ true,
+ },
+ };
+
+ HttpNetworkTransaction::SetUseAlternateProtocols(true);
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ scoped_refptr<HttpNetworkSession> session(
+ SetupSessionForGroupNameTests(tests[i].proxy_server));
+ HttpNetworkSessionPeer peer(session);
+
+ HostPortPair proxy_host("socks_proxy", 1080);
+ scoped_refptr<CaptureGroupNameSOCKSSocketPool> socks_conn_pool(
+ new CaptureGroupNameSOCKSSocketPool(session.get()));
+ peer.SetSocketPoolForSOCKSProxy(proxy_host, socks_conn_pool);
+ scoped_refptr<CaptureGroupNameSSLSocketPool> ssl_conn_pool(
+ new CaptureGroupNameSSLSocketPool(session.get()));
+ peer.SetSocketPoolForSSLWithProxy(proxy_host, ssl_conn_pool);
scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
- HttpRequestInfo request;
- request.method = "GET";
- request.url = GURL(tests[i].url);
- request.load_flags = 0;
-
- TestCompletionCallback callback;
-
- // We do not complete this request, the dtor will clean the transaction up.
- EXPECT_EQ(ERR_IO_PENDING, trans->Start(&request, &callback, NULL));
- EXPECT_EQ(tests[i].expected_group_name,
- conn_pool->last_group_name_received());
+ EXPECT_EQ(ERR_IO_PENDING,
+ GroupNameTransactionHelper(tests[i].url, session));
+ if (tests[i].ssl)
+ EXPECT_EQ(tests[i].expected_group_name,
+ ssl_conn_pool->last_group_name_received());
+ else
+ EXPECT_EQ(tests[i].expected_group_name,
+ socks_conn_pool->last_group_name_received());
}
+
+ HttpNetworkTransaction::SetUseAlternateProtocols(false);
}
TEST_F(HttpNetworkTransactionTest, ReconsiderProxyAfterFailedConnection) {
@@ -3630,7 +4232,7 @@
TestCompletionCallback callback;
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -3695,7 +4297,7 @@
MockRead data_reads[] = {
MockRead(false, ERR_FAILED),
};
- StaticSocketDataProvider data(data_reads, NULL);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
session_deps.socket_factory.AddSocketDataProvider(&data);
// Issue a request, containing an HTTP referrer.
@@ -3706,7 +4308,7 @@
// Run the request until it fails reading from the socket.
TestCompletionCallback callback;
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(ERR_FAILED, rv);
@@ -3715,9 +4317,9 @@
EXPECT_TRUE(resolution_observer.did_complete_with_expected_referrer());
}
-// Make sure that when the load flags contain LOAD_BYPASS_CACHE, the resolver's
-// host cache is bypassed.
-TEST_F(HttpNetworkTransactionTest, BypassHostCacheOnRefresh) {
+// Base test to make sure that when the load flags for a request specify to
+// bypass the cache, the DNS cache is not used.
+void BypassHostCacheOnRefreshHelper(int load_flags) {
SessionDependencies session_deps;
// Select a host resolver that does caching.
@@ -3731,7 +4333,7 @@
AddressList addrlist;
int rv = session_deps.host_resolver->Resolve(
HostResolver::RequestInfo("www.google.com", 80), &addrlist,
- NULL, NULL, NULL);
+ NULL, NULL, BoundNetLog());
EXPECT_EQ(OK, rv);
// Verify that it was added to host cache, by doing a subsequent async lookup
@@ -3739,7 +4341,7 @@
TestCompletionCallback resolve_callback;
rv = session_deps.host_resolver->Resolve(
HostResolver::RequestInfo("www.google.com", 80), &addrlist,
- &resolve_callback, NULL, NULL);
+ &resolve_callback, NULL, BoundNetLog());
ASSERT_EQ(OK, rv);
// Inject a failure the next time that "www.google.com" is resolved. This way
@@ -3750,18 +4352,18 @@
// Connect up a mock socket which will fail with ERR_UNEXPECTED during the
// first read -- this won't be reached as the host resolution will fail first.
MockRead data_reads[] = { MockRead(false, ERR_UNEXPECTED) };
- StaticSocketDataProvider data(data_reads, NULL);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
session_deps.socket_factory.AddSocketDataProvider(&data);
// Issue a request, asking to bypass the cache(s).
HttpRequestInfo request;
request.method = "GET";
- request.load_flags = LOAD_BYPASS_CACHE;
+ request.load_flags = load_flags;
request.url = GURL("http://www.google.com/");
// Run the request.
TestCompletionCallback callback;
- rv = trans->Start(&request, &callback, NULL);
+ rv = trans->Start(&request, &callback, BoundNetLog());
ASSERT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -3770,6 +4372,20 @@
EXPECT_EQ(ERR_NAME_NOT_RESOLVED, rv);
}
+// There are multiple load flags that should trigger the host cache bypass.
+// Test each in isolation:
+TEST_F(HttpNetworkTransactionTest, BypassHostCacheOnRefresh1) {
+ BypassHostCacheOnRefreshHelper(LOAD_BYPASS_CACHE);
+}
+
+TEST_F(HttpNetworkTransactionTest, BypassHostCacheOnRefresh2) {
+ BypassHostCacheOnRefreshHelper(LOAD_VALIDATE_CACHE);
+}
+
+TEST_F(HttpNetworkTransactionTest, BypassHostCacheOnRefresh3) {
+ BypassHostCacheOnRefreshHelper(LOAD_DISABLE_CACHE);
+}
+
// Make sure we can handle an error when writing the request.
TEST_F(HttpNetworkTransactionTest, RequestWriteError) {
SessionDependencies session_deps;
@@ -3783,7 +4399,8 @@
MockWrite write_failure[] = {
MockWrite(true, ERR_CONNECTION_RESET),
};
- StaticSocketDataProvider data(NULL, write_failure);
+ StaticSocketDataProvider data(NULL, 0,
+ write_failure, arraysize(write_failure));
session_deps.socket_factory.AddSocketDataProvider(&data);
TestCompletionCallback callback;
@@ -3791,7 +4408,7 @@
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(CreateSession(&session_deps)));
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -3813,7 +4430,7 @@
MockRead(false, OK),
};
- StaticSocketDataProvider data(data_reads, NULL);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
session_deps.socket_factory.AddSocketDataProvider(&data);
TestCompletionCallback callback;
@@ -3821,7 +4438,7 @@
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(CreateSession(&session_deps)));
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -3866,7 +4483,8 @@
MockRead(true, ERR_CONNECTION_RESET),
};
- StaticSocketDataProvider data1(data_reads1, data_writes1);
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
session_deps.socket_factory.AddSocketDataProvider(&data1);
// After calling trans->RestartWithAuth(), this is the request we should
@@ -3886,12 +4504,13 @@
MockRead(false, OK),
};
- StaticSocketDataProvider data2(data_reads2, data_writes2);
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
session_deps.socket_factory.AddSocketDataProvider(&data2);
TestCompletionCallback callback1;
- int rv = trans->Start(&request, &callback1, NULL);
+ int rv = trans->Start(&request, &callback1, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
@@ -3935,7 +4554,7 @@
MockRead(false, OK)
};
- StaticSocketDataProvider data(proxy_reads, NULL);
+ StaticSocketDataProvider data(proxy_reads, arraysize(proxy_reads), NULL, 0);
SSLSocketDataProvider ssl(true, OK);
session_deps.socket_factory.AddSocketDataProvider(&data);
@@ -3948,7 +4567,7 @@
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(CreateSession(&session_deps)));
- int rv = trans->Start(&request, &callback, NULL);
+ int rv = trans->Start(&request, &callback, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
@@ -3956,14 +4575,1683 @@
}
TEST_F(HttpNetworkTransactionTest, LargeContentLengthThenClose) {
+ SessionDependencies session_deps;
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(CreateSession(&session_deps)));
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
MockRead data_reads[] = {
MockRead("HTTP/1.0 200 OK\r\nContent-Length:6719476739\r\n\r\n"),
MockRead(false, OK),
};
- SimpleGetHelperResult out = SimpleGetHelper(data_reads);
- EXPECT_EQ(OK, out.rv);
- EXPECT_EQ("HTTP/1.0 200 OK", out.status_line);
- EXPECT_EQ("", out.response_data);
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
+ session_deps.socket_factory.AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, &callback, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_TRUE(response != NULL);
+
+ EXPECT_TRUE(response->headers != NULL);
+ EXPECT_EQ("HTTP/1.0 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ rv = ReadTransaction(trans.get(), &response_data);
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, rv);
+}
+
+TEST_F(HttpNetworkTransactionTest, UploadFileSmallerThanLength) {
+ SessionDependencies session_deps;
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(CreateSession(&session_deps)));
+
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.google.com/upload");
+ request.upload_data = new UploadData;
+ request.load_flags = 0;
+
+ FilePath temp_file_path;
+ ASSERT_TRUE(file_util::CreateTemporaryFile(&temp_file_path));
+ const uint64 kFakeSize = 100000; // file is actually blank
+
+ std::vector<UploadData::Element> elements;
+ UploadData::Element element;
+ element.SetToFilePath(temp_file_path);
+ element.SetContentLength(kFakeSize);
+ elements.push_back(element);
+ request.upload_data->set_elements(elements);
+ EXPECT_EQ(kFakeSize, request.upload_data->GetContentLength());
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 OK\r\n\r\n"),
+ MockRead("hello world"),
+ MockRead(false, OK),
+ };
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
+ session_deps.socket_factory.AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, &callback, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_TRUE(response != NULL);
+
+ EXPECT_TRUE(response->headers != NULL);
+ EXPECT_EQ("HTTP/1.0 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ rv = ReadTransaction(trans.get(), &response_data);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("hello world", response_data);
+
+ file_util::Delete(temp_file_path, false);
+}
+
+TEST_F(HttpNetworkTransactionTest, UploadUnreadableFile) {
+ // If we try to upload an unreadable file, the network stack should report
+ // the file size as zero and upload zero bytes for that file.
+ SessionDependencies session_deps;
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(CreateSession(&session_deps)));
+
+ FilePath temp_file;
+ ASSERT_TRUE(file_util::CreateTemporaryFile(&temp_file));
+ std::string temp_file_content("Unreadable file.");
+ ASSERT_TRUE(file_util::WriteFile(temp_file, temp_file_content.c_str(),
+ temp_file_content.length()));
+ ASSERT_TRUE(file_util::MakeFileUnreadable(temp_file));
+
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.google.com/upload");
+ request.upload_data = new UploadData;
+ request.load_flags = 0;
+
+ std::vector<UploadData::Element> elements;
+ UploadData::Element element;
+ element.SetToFilePath(temp_file);
+ elements.push_back(element);
+ request.upload_data->set_elements(elements);
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 OK\r\n\r\n"),
+ MockRead(false, OK),
+ };
+ MockWrite data_writes[] = {
+ MockWrite("POST /upload HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Content-Length: 0\r\n\r\n"),
+ MockWrite(false, OK),
+ };
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), data_writes,
+ arraysize(data_writes));
+ session_deps.socket_factory.AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, &callback, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_TRUE(response != NULL);
+ EXPECT_TRUE(response->headers != NULL);
+ EXPECT_EQ("HTTP/1.0 200 OK", response->headers->GetStatusLine());
+
+ file_util::Delete(temp_file, false);
+}
+
+TEST_F(HttpNetworkTransactionTest, UnreadableUploadFileAfterAuthRestart) {
+ SessionDependencies session_deps;
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(CreateSession(&session_deps)));
+
+ FilePath temp_file;
+ ASSERT_TRUE(file_util::CreateTemporaryFile(&temp_file));
+ std::string temp_file_contents("Unreadable file.");
+ std::string unreadable_contents(temp_file_contents.length(), '\0');
+ ASSERT_TRUE(file_util::WriteFile(temp_file, temp_file_contents.c_str(),
+ temp_file_contents.length()));
+
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.google.com/upload");
+ request.upload_data = new UploadData;
+ request.load_flags = 0;
+
+ std::vector<UploadData::Element> elements;
+ UploadData::Element element;
+ element.SetToFilePath(temp_file);
+ elements.push_back(element);
+ request.upload_data->set_elements(elements);
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 401 Unauthorized\r\n"),
+ MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Content-Length: 0\r\n\r\n"), // No response body.
+
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Length: 0\r\n\r\n"),
+ MockRead(false, OK),
+ };
+ MockWrite data_writes[] = {
+ MockWrite("POST /upload HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Content-Length: 16\r\n\r\n"),
+ MockWrite(false, temp_file_contents.c_str()),
+
+ MockWrite("POST /upload HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Content-Length: 16\r\n"
+ "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ MockWrite(false, unreadable_contents.c_str(), temp_file_contents.length()),
+ MockWrite(false, OK),
+ };
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), data_writes,
+ arraysize(data_writes));
+ session_deps.socket_factory.AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback1;
+
+ int rv = trans->Start(&request, &callback1, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_TRUE(response != NULL);
+ EXPECT_TRUE(response->headers != NULL);
+ EXPECT_EQ("HTTP/1.1 401 Unauthorized", response->headers->GetStatusLine());
+
+ // The password prompt info should have been set in response->auth_challenge.
+ EXPECT_TRUE(response->auth_challenge.get() != NULL);
+ EXPECT_EQ(L"www.google.com:80", response->auth_challenge->host_and_port);
+ EXPECT_EQ(L"MyRealm1", response->auth_challenge->realm);
+ EXPECT_EQ(L"basic", response->auth_challenge->scheme);
+
+ // Now make the file unreadable and try again.
+ ASSERT_TRUE(file_util::MakeFileUnreadable(temp_file));
+
+ TestCompletionCallback callback2;
+
+ rv = trans->RestartWithAuth(L"foo", L"bar", &callback2);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ response = trans->GetResponseInfo();
+ EXPECT_TRUE(response != NULL);
+ EXPECT_TRUE(response->headers != NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ file_util::Delete(temp_file, false);
+}
+
+// Tests that changes to Auth realms are treated like auth rejections.
+TEST_F(HttpNetworkTransactionTest, ChangeAuthRealms) {
+ SessionDependencies session_deps;
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(CreateSession(&session_deps)));
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ // First transaction will request a resource and receive a Basic challenge
+ // with realm="first_realm".
+ MockWrite data_writes1[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "\r\n"),
+ };
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.1 401 Unauthorized\r\n"
+ "WWW-Authenticate: Basic realm=\"first_realm\"\r\n"
+ "\r\n"),
+ };
+
+ // After calling trans->RestartWithAuth(), provide an Authentication header
+ // for first_realm. The server will reject and provide a challenge with
+ // second_realm.
+ MockWrite data_writes2[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: Basic Zmlyc3Q6YmF6\r\n"
+ "\r\n"),
+ };
+ MockRead data_reads2[] = {
+ MockRead("HTTP/1.1 401 Unauthorized\r\n"
+ "WWW-Authenticate: Basic realm=\"second_realm\"\r\n"
+ "\r\n"),
+ };
+
+ // This again fails, and goes back to first_realm. Make sure that the
+ // entry is removed from cache.
+ MockWrite data_writes3[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: Basic c2Vjb25kOmZvdQ==\r\n"
+ "\r\n"),
+ };
+ MockRead data_reads3[] = {
+ MockRead("HTTP/1.1 401 Unauthorized\r\n"
+ "WWW-Authenticate: Basic realm=\"first_realm\"\r\n"
+ "\r\n"),
+ };
+
+ // Try one last time (with the correct password) and get the resource.
+ MockWrite data_writes4[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: Basic Zmlyc3Q6YmFy\r\n"
+ "\r\n"),
+ };
+ MockRead data_reads4[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/html; charset=iso-8859-1\r\n"
+ "Content-Length: 100\r\n"
+ "\r\n"),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
+ StaticSocketDataProvider data3(data_reads3, arraysize(data_reads3),
+ data_writes3, arraysize(data_writes3));
+ StaticSocketDataProvider data4(data_reads4, arraysize(data_reads4),
+ data_writes4, arraysize(data_writes4));
+ session_deps.socket_factory.AddSocketDataProvider(&data1);
+ session_deps.socket_factory.AddSocketDataProvider(&data2);
+ session_deps.socket_factory.AddSocketDataProvider(&data3);
+ session_deps.socket_factory.AddSocketDataProvider(&data4);
+
+ TestCompletionCallback callback1;
+
+ // Issue the first request with Authorize headers. There should be a
+ // password prompt for first_realm waiting to be filled in after the
+ // transaction completes.
+ int rv = trans->Start(&request, &callback1, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_FALSE(response == NULL);
+ ASSERT_FALSE(response->auth_challenge.get() == NULL);
+ EXPECT_EQ(L"www.google.com:80", response->auth_challenge->host_and_port);
+ EXPECT_EQ(L"first_realm", response->auth_challenge->realm);
+ EXPECT_EQ(L"basic", response->auth_challenge->scheme);
+
+ // Issue the second request with an incorrect password. There should be a
+ // password prompt for second_realm waiting to be filled in after the
+ // transaction completes.
+ TestCompletionCallback callback2;
+ rv = trans->RestartWithAuth(L"first", L"baz", &callback2);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ response = trans->GetResponseInfo();
+ ASSERT_FALSE(response == NULL);
+ ASSERT_FALSE(response->auth_challenge.get() == NULL);
+ EXPECT_EQ(L"www.google.com:80", response->auth_challenge->host_and_port);
+ EXPECT_EQ(L"second_realm", response->auth_challenge->realm);
+ EXPECT_EQ(L"basic", response->auth_challenge->scheme);
+
+ // Issue the third request with another incorrect password. There should be
+ // a password prompt for first_realm waiting to be filled in. If the password
+ // prompt is not present, it indicates that the HttpAuthCacheEntry for
+ // first_realm was not correctly removed.
+ TestCompletionCallback callback3;
+ rv = trans->RestartWithAuth(L"second", L"fou", &callback3);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback3.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ response = trans->GetResponseInfo();
+ ASSERT_FALSE(response == NULL);
+ ASSERT_FALSE(response->auth_challenge.get() == NULL);
+ EXPECT_EQ(L"www.google.com:80", response->auth_challenge->host_and_port);
+ EXPECT_EQ(L"first_realm", response->auth_challenge->realm);
+ EXPECT_EQ(L"basic", response->auth_challenge->scheme);
+
+ // Issue the fourth request with the correct password and username.
+ TestCompletionCallback callback4;
+ rv = trans->RestartWithAuth(L"first", L"bar", &callback4);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback4.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ response = trans->GetResponseInfo();
+ ASSERT_FALSE(response == NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+}
+
+TEST_F(HttpNetworkTransactionTest, HonorAlternateProtocolHeader) {
+ HttpNetworkTransaction::SetNextProtos("needs_to_be_set_for_this_test");
+ HttpNetworkTransaction::SetUseAlternateProtocols(true);
+
+ SessionDependencies session_deps;
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Alternate-Protocol: 443:npn-spdy/1\r\n\r\n"),
+ MockRead("hello world"),
+ MockRead(false, OK),
+ };
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
+
+ session_deps.socket_factory.AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
+ scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
+
+ int rv = trans->Start(&request, &callback, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ HostPortPair http_host_port_pair;
+ http_host_port_pair.host = "www.google.com";
+ http_host_port_pair.port = 80;
+ const HttpAlternateProtocols& alternate_protocols =
+ session->alternate_protocols();
+ EXPECT_FALSE(
+ alternate_protocols.HasAlternateProtocolFor(http_host_port_pair));
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ EXPECT_FALSE(response->was_fetched_via_spdy);
+ EXPECT_FALSE(response->was_npn_negotiated);
+ EXPECT_FALSE(response->was_alternate_protocol_available);
+
+ std::string response_data;
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ("hello world", response_data);
+
+ ASSERT_TRUE(alternate_protocols.HasAlternateProtocolFor(http_host_port_pair));
+ const HttpAlternateProtocols::PortProtocolPair alternate =
+ alternate_protocols.GetAlternateProtocolFor(http_host_port_pair);
+ HttpAlternateProtocols::PortProtocolPair expected_alternate;
+ expected_alternate.port = 443;
+ expected_alternate.protocol = HttpAlternateProtocols::NPN_SPDY_1;
+ EXPECT_TRUE(expected_alternate.Equals(alternate));
+
+ HttpNetworkTransaction::SetUseAlternateProtocols(false);
+ HttpNetworkTransaction::SetNextProtos("");
+}
+
+TEST_F(HttpNetworkTransactionTest, MarkBrokenAlternateProtocol) {
+ HttpNetworkTransaction::SetUseAlternateProtocols(true);
+ SessionDependencies session_deps;
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ MockConnect mock_connect(true, ERR_CONNECTION_REFUSED);
+ StaticSocketDataProvider first_data;
+ first_data.set_connect_data(mock_connect);
+ session_deps.socket_factory.AddSocketDataProvider(&first_data);
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n\r\n"),
+ MockRead("hello world"),
+ MockRead(true, OK),
+ };
+ StaticSocketDataProvider second_data(
+ data_reads, arraysize(data_reads), NULL, 0);
+ session_deps.socket_factory.AddSocketDataProvider(&second_data);
+
+ // TODO(willchan): Delete this extra data provider. It's necessary due to a
+ // ClientSocketPoolBaseHelper bug that starts up too many ConnectJobs:
+ // http://crbug.com/37454.
+ session_deps.socket_factory.AddSocketDataProvider(&second_data);
+
+ TestCompletionCallback callback;
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
+
+ HostPortPair http_host_port_pair;
+ http_host_port_pair.host = "www.google.com";
+ http_host_port_pair.port = 80;
+ HttpAlternateProtocols* alternate_protocols =
+ session->mutable_alternate_protocols();
+ alternate_protocols->SetAlternateProtocolFor(
+ http_host_port_pair, 1234 /* port is ignored by MockConnect anyway */,
+ HttpAlternateProtocols::NPN_SPDY_1);
+
+ scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
+
+ int rv = trans->Start(&request, &callback, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ("hello world", response_data);
+
+ ASSERT_TRUE(
+ alternate_protocols->HasAlternateProtocolFor(http_host_port_pair));
+ const HttpAlternateProtocols::PortProtocolPair alternate =
+ alternate_protocols->GetAlternateProtocolFor(http_host_port_pair);
+ EXPECT_EQ(HttpAlternateProtocols::BROKEN, alternate.protocol);
+ HttpNetworkTransaction::SetUseAlternateProtocols(false);
+}
+
+// TODO(willchan): Redo this test to use TLS/NPN=>SPDY. Currently, the code
+// says that it does SPDY, but it just does the TLS handshake, but the NPN
+// response does not indicate SPDY, so we just do standard HTTPS over the port.
+// We should add code such that we don't fallback to HTTPS, but fallback to HTTP
+// on the original port.
+// TEST_F(HttpNetworkTransactionTest, UseAlternateProtocol) {
+// SessionDependencies session_deps;
+//
+// HttpRequestInfo request;
+// request.method = "GET";
+// request.url = GURL("http://www.google.com/");
+// request.load_flags = 0;
+//
+// MockRead data_reads[] = {
+// MockRead("HTTP/1.1 200 OK\r\n\r\n"),
+// MockRead("hello world"),
+// MockRead(true, OK),
+// };
+// StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
+// session_deps.socket_factory.AddSocketDataProvider(&data);
+//
+// SSLSocketDataProvider ssl(true, OK);
+// session_deps.socket_factory.AddSSLSocketDataProvider(&ssl);
+//
+// TestCompletionCallback callback;
+//
+// scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
+//
+// HostPortPair http_host_port_pair;
+// http_host_port_pair.host = "www.google.com";
+// http_host_port_pair.port = 80;
+// HttpAlternateProtocols* alternate_protocols =
+// session->mutable_alternate_protocols();
+// alternate_protocols->SetAlternateProtocolFor(
+// http_host_port_pair, 1234 /* port is ignored */,
+// HttpAlternateProtocols::NPN_SPDY_1);
+//
+// scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
+//
+// int rv = trans->Start(&request, &callback, BoundNetLog());
+// EXPECT_EQ(ERR_IO_PENDING, rv);
+// EXPECT_EQ(OK, callback.WaitForResult());
+//
+// const HttpResponseInfo* response = trans->GetResponseInfo();
+// ASSERT_TRUE(response != NULL);
+// ASSERT_TRUE(response->headers != NULL);
+// EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+//
+// std::string response_data;
+// ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+// EXPECT_EQ("hello world", response_data);
+// }
+
+TEST_F(HttpNetworkTransactionTest, FailNpnSpdyAndFallback) {
+ HttpNetworkTransaction::SetUseAlternateProtocols(true);
+ HttpNetworkTransaction::SetNextProtos(
+ "\x08http/1.1\x07http1.1\x06spdy/1\x04spdy");
+ SessionDependencies session_deps;
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ StaticSocketDataProvider first_tcp_connect;
+ session_deps.socket_factory.AddSocketDataProvider(&first_tcp_connect);
+
+ SSLSocketDataProvider ssl(true, OK);
+ session_deps.socket_factory.AddSSLSocketDataProvider(&ssl);
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n\r\n"),
+ MockRead("hello world"),
+ MockRead(true, OK),
+ };
+ StaticSocketDataProvider fallback_data(
+ data_reads, arraysize(data_reads), NULL, 0);
+ session_deps.socket_factory.AddSocketDataProvider(&fallback_data);
+
+ TestCompletionCallback callback;
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
+
+ HostPortPair http_host_port_pair;
+ http_host_port_pair.host = "www.google.com";
+ http_host_port_pair.port = 80;
+ HttpAlternateProtocols* alternate_protocols =
+ session->mutable_alternate_protocols();
+ alternate_protocols->SetAlternateProtocolFor(
+ http_host_port_pair, 1234 /* port is ignored */,
+ HttpAlternateProtocols::NPN_SPDY_1);
+
+ scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
+
+ int rv = trans->Start(&request, &callback, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ("hello world", response_data);
+ HttpNetworkTransaction::SetNextProtos("");
+ HttpNetworkTransaction::SetUseAlternateProtocols(false);
+}
+
+TEST_F(HttpNetworkTransactionTest, UseAlternateProtocolForNpnSpdy) {
+ HttpNetworkTransaction::SetUseAlternateProtocols(true);
+ HttpNetworkTransaction::SetNextProtos(
+ "\x08http/1.1\x07http1.1\x06spdy/1\x04spdy");
+ SessionDependencies session_deps;
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Alternate-Protocol: 443:npn-spdy/1\r\n\r\n"),
+ MockRead("hello world"),
+ MockRead(true, OK),
+ };
+
+ StaticSocketDataProvider first_transaction(
+ data_reads, arraysize(data_reads), NULL, 0);
+ session_deps.socket_factory.AddSocketDataProvider(&first_transaction);
+
+ SSLSocketDataProvider ssl(true, OK);
+ ssl.next_proto_status = SSLClientSocket::kNextProtoNegotiated;
+ ssl.next_proto = "spdy/1";
+ ssl.was_npn_negotiated = true;
+ session_deps.socket_factory.AddSSLSocketDataProvider(&ssl);
+
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite spdy_writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<spdy::SpdyFrame> data(ConstructSpdyBodyFrame(1, true));
+ MockRead spdy_reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*data),
+ MockRead(true, 0, 0),
+ };
+
+ scoped_refptr<DelayedSocketData> spdy_data(
+ new DelayedSocketData(
+ 1, // wait for one write to finish before reading.
+ spdy_reads, arraysize(spdy_reads),
+ spdy_writes, arraysize(spdy_writes)));
+ session_deps.socket_factory.AddSocketDataProvider(spdy_data);
+
+ TestCompletionCallback callback;
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
+ scoped_ptr<HttpNetworkTransaction> trans(new HttpNetworkTransaction(session));
+
+ int rv = trans->Start(&request, &callback, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ("hello world", response_data);
+
+ trans.reset(new HttpNetworkTransaction(session));
+
+ rv = trans->Start(&request, &callback, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ EXPECT_TRUE(response->was_npn_negotiated);
+ EXPECT_TRUE(response->was_alternate_protocol_available);
+
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ("hello!", response_data);
+
+ HttpNetworkTransaction::SetNextProtos("");
+ HttpNetworkTransaction::SetUseAlternateProtocols(false);
+}
+
+class CapturingProxyResolver : public ProxyResolver {
+ public:
+ CapturingProxyResolver() : ProxyResolver(false /* expects_pac_bytes */) {}
+ virtual ~CapturingProxyResolver() {}
+
+ virtual int GetProxyForURL(const GURL& url,
+ ProxyInfo* results,
+ CompletionCallback* callback,
+ RequestHandle* request,
+ const BoundNetLog& net_log) {
+ ProxyServer proxy_server(
+ ProxyServer::SCHEME_HTTP, "myproxy", 80);
+ results->UseProxyServer(proxy_server);
+ resolved_.push_back(url);
+ return OK;
+ }
+
+ virtual void CancelRequest(RequestHandle request) {
+ NOTREACHED();
+ }
+
+ virtual int SetPacScript(const scoped_refptr<ProxyResolverScriptData>&,
+ CompletionCallback* /*callback*/) {
+ return OK;
+ }
+
+ const std::vector<GURL>& resolved() const { return resolved_; }
+
+ private:
+ std::vector<GURL> resolved_;
+
+ DISALLOW_COPY_AND_ASSIGN(CapturingProxyResolver);
+};
+
+TEST_F(HttpNetworkTransactionTest, UseAlternateProtocolForTunneledNpnSpdy) {
+ HttpNetworkTransaction::SetUseAlternateProtocols(true);
+ HttpNetworkTransaction::SetNextProtos(
+ "\x08http/1.1\x07http1.1\x06spdy/1\x04spdy");
+
+ ProxyConfig proxy_config;
+ proxy_config.set_auto_detect(true);
+ proxy_config.set_pac_url(GURL("http://fooproxyurl"));
+
+ CapturingProxyResolver* capturing_proxy_resolver =
+ new CapturingProxyResolver();
+ SessionDependencies session_deps(new ProxyService(
+ new ProxyConfigServiceFixed(proxy_config), capturing_proxy_resolver,
+ NULL));
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Alternate-Protocol: 443:npn-spdy/1\r\n\r\n"),
+ MockRead("hello world"),
+ MockRead(true, OK),
+ };
+
+ StaticSocketDataProvider first_transaction(
+ data_reads, arraysize(data_reads), NULL, 0);
+ session_deps.socket_factory.AddSocketDataProvider(&first_transaction);
+
+ SSLSocketDataProvider ssl(true, OK);
+ ssl.next_proto_status = SSLClientSocket::kNextProtoNegotiated;
+ ssl.next_proto = "spdy/1";
+ ssl.was_npn_negotiated = true;
+ session_deps.socket_factory.AddSSLSocketDataProvider(&ssl);
+
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite spdy_writes[] = {
+ MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"), // 0
+ CreateMockWrite(*req) // 3
+ };
+
+ const char kCONNECTResponse[] = "HTTP/1.1 200 Connected\r\n\r\n";
+
+ scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<spdy::SpdyFrame> data(ConstructSpdyBodyFrame(1, true));
+ MockRead spdy_reads[] = {
+ MockRead(true, kCONNECTResponse, arraysize(kCONNECTResponse) - 1, 1), // 1
+ CreateMockRead(*resp.get(), 4), // 2, 4
+ CreateMockRead(*data.get(), 4), // 5
+ MockRead(true, 0, 0, 4), // 6
+ };
+
+ scoped_refptr<OrderedSocketData> spdy_data(
+ new OrderedSocketData(
+ spdy_reads, arraysize(spdy_reads),
+ spdy_writes, arraysize(spdy_writes)));
+ session_deps.socket_factory.AddSocketDataProvider(spdy_data);
+
+ TestCompletionCallback callback;
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
+ scoped_ptr<HttpNetworkTransaction> trans(new HttpNetworkTransaction(session));
+
+ int rv = trans->Start(&request, &callback, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ EXPECT_FALSE(response->was_fetched_via_spdy);
+ EXPECT_FALSE(response->was_npn_negotiated);
+
+ std::string response_data;
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ("hello world", response_data);
+
+ trans.reset(new HttpNetworkTransaction(session));
+
+ rv = trans->Start(&request, &callback, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ EXPECT_TRUE(response->was_npn_negotiated);
+
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ("hello!", response_data);
+ ASSERT_EQ(2u, capturing_proxy_resolver->resolved().size());
+ EXPECT_EQ("http://www.google.com/",
+ capturing_proxy_resolver->resolved()[0].spec());
+ EXPECT_EQ("https://www.google.com/",
+ capturing_proxy_resolver->resolved()[1].spec());
+
+ HttpNetworkTransaction::SetNextProtos("");
+ HttpNetworkTransaction::SetUseAlternateProtocols(false);
+}
+
+TEST_F(HttpNetworkTransactionTest,
+ UseAlternateProtocolForNpnSpdyWithExistingSpdySession) {
+ HttpNetworkTransaction::SetUseAlternateProtocols(true);
+ HttpNetworkTransaction::SetNextProtos(
+ "\x08http/1.1\x07http1.1\x06spdy/1\x04spdy");
+ SessionDependencies session_deps;
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Alternate-Protocol: 443:npn-spdy/1\r\n\r\n"),
+ MockRead("hello world"),
+ MockRead(true, OK),
+ };
+
+ StaticSocketDataProvider first_transaction(
+ data_reads, arraysize(data_reads), NULL, 0);
+ session_deps.socket_factory.AddSocketDataProvider(&first_transaction);
+
+ SSLSocketDataProvider ssl(true, OK);
+ ssl.next_proto_status = SSLClientSocket::kNextProtoNegotiated;
+ ssl.next_proto = "spdy/1";
+ ssl.was_npn_negotiated = true;
+ session_deps.socket_factory.AddSSLSocketDataProvider(&ssl);
+ // Make sure we use ssl for spdy here.
+ SpdySession::SetSSLMode(true);
+
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite spdy_writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<spdy::SpdyFrame> data(ConstructSpdyBodyFrame(1, true));
+ MockRead spdy_reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*data),
+ MockRead(true, 0, 0),
+ };
+
+ scoped_refptr<DelayedSocketData> spdy_data(
+ new DelayedSocketData(
+ 1, // wait for one write to finish before reading.
+ spdy_reads, arraysize(spdy_reads),
+ spdy_writes, arraysize(spdy_writes)));
+ session_deps.socket_factory.AddSocketDataProvider(spdy_data);
+
+ TestCompletionCallback callback;
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
+
+ scoped_ptr<HttpNetworkTransaction> trans(new HttpNetworkTransaction(session));
+
+ int rv = trans->Start(&request, &callback, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ("hello world", response_data);
+
+ // Set up an initial SpdySession in the pool to reuse.
+ scoped_refptr<SpdySession> spdy_session =
+ session->spdy_session_pool()->Get(HostPortPair("www.google.com", 443),
+ session, BoundNetLog());
+ scoped_refptr<TCPSocketParams> tcp_params =
+ new TCPSocketParams("www.google.com", 443, MEDIUM, GURL(), false);
+ spdy_session->Connect("www.google.com:443", tcp_params, MEDIUM);
+ trans.reset(new HttpNetworkTransaction(session));
+
+ rv = trans->Start(&request, &callback, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ EXPECT_TRUE(response->was_npn_negotiated);
+ EXPECT_TRUE(response->was_alternate_protocol_available);
+
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ("hello!", response_data);
+
+ HttpNetworkTransaction::SetNextProtos("");
+ HttpNetworkTransaction::SetUseAlternateProtocols(false);
+}
+
+// GenerateAuthToken is a mighty big test.
+// It tests all permutation of GenerateAuthToken behavior:
+// - Synchronous and Asynchronous completion.
+// - OK or error on completion.
+// - Direct connection, non-authenticating proxy, and authenticating proxy.
+// - HTTP or HTTPS backend (to include proxy tunneling).
+// - Non-authenticating and authenticating backend.
+//
+// In all, there are 44 reasonable permuations (for example, if there are
+// problems generating an auth token for an authenticating proxy, we don't
+// need to test all permutations of the backend server).
+//
+// The test proceeds by going over each of the configuration cases, and
+// potentially running up to three rounds in each of the tests. The TestConfig
+// specifies both the configuration for the test as well as the expectations
+// for the results.
+TEST_F(HttpNetworkTransactionTest, GenerateAuthToken) {
+ const char* kServer = "http://www.example.com";
+ const char* kSecureServer = "https://www.example.com";
+ const char* kProxy = "myproxy:70";
+ const int kAuthErr = ERR_INVALID_AUTH_CREDENTIALS;
+
+ enum AuthTiming {
+ AUTH_NONE,
+ AUTH_SYNC,
+ AUTH_ASYNC,
+ };
+
+ const MockWrite kGet(
+ "GET / HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "Connection: keep-alive\r\n\r\n");
+ const MockWrite kGetProxy(
+ "GET http://www.example.com/ HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n");
+ const MockWrite kGetAuth(
+ "GET / HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: auth_token\r\n\r\n");
+ const MockWrite kGetProxyAuth(
+ "GET http://www.example.com/ HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: auth_token\r\n\r\n");
+ const MockWrite kGetAuthThroughProxy(
+ "GET http://www.example.com/ HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Authorization: auth_token\r\n\r\n");
+ const MockWrite kGetAuthWithProxyAuth(
+ "GET http://www.example.com/ HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: auth_token\r\n"
+ "Authorization: auth_token\r\n\r\n");
+ const MockWrite kConnect(
+ "CONNECT www.example.com:443 HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n");
+ const MockWrite kConnectProxyAuth(
+ "CONNECT www.example.com:443 HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: auth_token\r\n\r\n");
+
+ const MockRead kSuccess(
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/html; charset=iso-8859-1\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "Yes");
+ const MockRead kFailure(
+ "Should not be called.");
+ const MockRead kServerChallenge(
+ "HTTP/1.1 401 Unauthorized\r\n"
+ "WWW-Authenticate: Mock realm=server\r\n"
+ "Content-Type: text/html; charset=iso-8859-1\r\n"
+ "Content-Length: 14\r\n\r\n"
+ "Unauthorized\r\n");
+ const MockRead kProxyChallenge(
+ "HTTP/1.1 407 Unauthorized\r\n"
+ "Proxy-Authenticate: Mock realm=proxy\r\n"
+ "Proxy-Connection: close\r\n"
+ "Content-Type: text/html; charset=iso-8859-1\r\n"
+ "Content-Length: 14\r\n\r\n"
+ "Unauthorized\r\n");
+ const MockRead kProxyConnected(
+ "HTTP/1.1 200 Connection Established\r\n\r\n");
+
+ // NOTE(cbentzel): I wanted TestReadWriteRound to be a simple struct with
+ // no constructors, but the C++ compiler on Windows warns about
+ // unspecified data in compound literals. So, moved to using constructors,
+ // and TestRound's created with the default constructor should not be used.
+ struct TestRound {
+ TestRound()
+ : expected_rv(ERR_UNEXPECTED),
+ extra_write(NULL),
+ extra_read(NULL) {
+ }
+ TestRound(const MockWrite& write_arg, const MockRead& read_arg,
+ int expected_rv_arg)
+ : write(write_arg),
+ read(read_arg),
+ expected_rv(expected_rv_arg),
+ extra_write(NULL),
+ extra_read(NULL) {
+ }
+ TestRound(const MockWrite& write_arg, const MockRead& read_arg,
+ int expected_rv_arg, const MockWrite* extra_write_arg,
+ const MockWrite* extra_read_arg)
+ : write(write_arg),
+ read(read_arg),
+ expected_rv(expected_rv_arg),
+ extra_write(extra_write_arg),
+ extra_read(extra_read_arg) {
+ }
+ MockWrite write;
+ MockRead read;
+ int expected_rv;
+ const MockWrite* extra_write;
+ const MockRead* extra_read;
+ };
+
+ static const int kNoSSL = 500;
+
+ struct TestConfig {
+ const char* proxy_url;
+ AuthTiming proxy_auth_timing;
+ int proxy_auth_rv;
+ const char* server_url;
+ AuthTiming server_auth_timing;
+ int server_auth_rv;
+ int num_auth_rounds;
+ int first_ssl_round;
+ TestRound rounds[3];
+ } test_configs[] = {
+ // Non-authenticating HTTP server with a direct connection.
+ { NULL, AUTH_NONE, OK, kServer, AUTH_NONE, OK, 1, kNoSSL,
+ { TestRound(kGet, kSuccess, OK)}},
+ // Authenticating HTTP server with a direct connection.
+ { NULL, AUTH_NONE, OK, kServer, AUTH_SYNC, OK, 2, kNoSSL,
+ { TestRound(kGet, kServerChallenge, OK),
+ TestRound(kGetAuth, kSuccess, OK)}},
+ { NULL, AUTH_NONE, OK, kServer, AUTH_SYNC, kAuthErr, 2, kNoSSL,
+ { TestRound(kGet, kServerChallenge, OK),
+ TestRound(kGetAuth, kFailure, kAuthErr)}},
+ { NULL, AUTH_NONE, OK, kServer, AUTH_ASYNC, OK, 2, kNoSSL,
+ { TestRound(kGet, kServerChallenge, OK),
+ TestRound(kGetAuth, kSuccess, OK)}},
+ { NULL, AUTH_NONE, OK, kServer, AUTH_ASYNC, kAuthErr, 2, kNoSSL,
+ { TestRound(kGet, kServerChallenge, OK),
+ TestRound(kGetAuth, kFailure, kAuthErr)}},
+ // Non-authenticating HTTP server through a non-authenticating proxy.
+ { kProxy, AUTH_NONE, OK, kServer, AUTH_NONE, OK, 1, kNoSSL,
+ { TestRound(kGetProxy, kSuccess, OK)}},
+ // Authenticating HTTP server through a non-authenticating proxy.
+ { kProxy, AUTH_NONE, OK, kServer, AUTH_SYNC, OK, 2, kNoSSL,
+ { TestRound(kGetProxy, kServerChallenge, OK),
+ TestRound(kGetAuthThroughProxy, kSuccess, OK)}},
+ { kProxy, AUTH_NONE, OK, kServer, AUTH_SYNC, kAuthErr, 2, kNoSSL,
+ { TestRound(kGetProxy, kServerChallenge, OK),
+ TestRound(kGetAuthThroughProxy, kFailure, kAuthErr)}},
+ { kProxy, AUTH_NONE, OK, kServer, AUTH_ASYNC, OK, 2, kNoSSL,
+ { TestRound(kGetProxy, kServerChallenge, OK),
+ TestRound(kGetAuthThroughProxy, kSuccess, OK)}},
+ { kProxy, AUTH_NONE, OK, kServer, AUTH_ASYNC, kAuthErr, 2, kNoSSL,
+ { TestRound(kGetProxy, kServerChallenge, OK),
+ TestRound(kGetAuthThroughProxy, kFailure, kAuthErr)}},
+ // Non-authenticating HTTP server through an authenticating proxy.
+ { kProxy, AUTH_SYNC, OK, kServer, AUTH_NONE, OK, 2, kNoSSL,
+ { TestRound(kGetProxy, kProxyChallenge, OK),
+ TestRound(kGetProxyAuth, kSuccess, OK)}},
+ { kProxy, AUTH_SYNC, kAuthErr, kServer, AUTH_NONE, OK, 2, kNoSSL,
+ { TestRound(kGetProxy, kProxyChallenge, OK),
+ TestRound(kGetProxyAuth, kFailure, kAuthErr)}},
+ { kProxy, AUTH_ASYNC, OK, kServer, AUTH_NONE, OK, 2, kNoSSL,
+ { TestRound(kGetProxy, kProxyChallenge, OK),
+ TestRound(kGetProxyAuth, kSuccess, OK)}},
+ { kProxy, AUTH_ASYNC, kAuthErr, kServer, AUTH_NONE, OK, 2, kNoSSL,
+ { TestRound(kGetProxy, kProxyChallenge, OK),
+ TestRound(kGetProxyAuth, kFailure, kAuthErr)}},
+ // Authenticating HTTP server through an authenticating proxy.
+ { kProxy, AUTH_SYNC, OK, kServer, AUTH_SYNC, OK, 3, kNoSSL,
+ { TestRound(kGetProxy, kProxyChallenge, OK),
+ TestRound(kGetProxyAuth, kServerChallenge, OK),
+ TestRound(kGetAuthWithProxyAuth, kSuccess, OK)}},
+ { kProxy, AUTH_SYNC, OK, kServer, AUTH_SYNC, kAuthErr, 3, kNoSSL,
+ { TestRound(kGetProxy, kProxyChallenge, OK),
+ TestRound(kGetProxyAuth, kServerChallenge, OK),
+ TestRound(kGetAuthWithProxyAuth, kFailure, kAuthErr)}},
+ { kProxy, AUTH_ASYNC, OK, kServer, AUTH_SYNC, OK, 3, kNoSSL,
+ { TestRound(kGetProxy, kProxyChallenge, OK),
+ TestRound(kGetProxyAuth, kServerChallenge, OK),
+ TestRound(kGetAuthWithProxyAuth, kSuccess, OK)}},
+ { kProxy, AUTH_ASYNC, OK, kServer, AUTH_SYNC, kAuthErr, 3, kNoSSL,
+ { TestRound(kGetProxy, kProxyChallenge, OK),
+ TestRound(kGetProxyAuth, kServerChallenge, OK),
+ TestRound(kGetAuthWithProxyAuth, kFailure, kAuthErr)}},
+ { kProxy, AUTH_SYNC, OK, kServer, AUTH_ASYNC, OK, 3, kNoSSL,
+ { TestRound(kGetProxy, kProxyChallenge, OK),
+ TestRound(kGetProxyAuth, kServerChallenge, OK),
+ TestRound(kGetAuthWithProxyAuth, kSuccess, OK)}},
+ { kProxy, AUTH_SYNC, OK, kServer, AUTH_ASYNC, kAuthErr, 3, kNoSSL,
+ { TestRound(kGetProxy, kProxyChallenge, OK),
+ TestRound(kGetProxyAuth, kServerChallenge, OK),
+ TestRound(kGetAuthWithProxyAuth, kFailure, kAuthErr)}},
+ { kProxy, AUTH_ASYNC, OK, kServer, AUTH_ASYNC, OK, 3, kNoSSL,
+ { TestRound(kGetProxy, kProxyChallenge, OK),
+ TestRound(kGetProxyAuth, kServerChallenge, OK),
+ TestRound(kGetAuthWithProxyAuth, kSuccess, OK)}},
+ { kProxy, AUTH_ASYNC, OK, kServer, AUTH_ASYNC, kAuthErr, 3, kNoSSL,
+ { TestRound(kGetProxy, kProxyChallenge, OK),
+ TestRound(kGetProxyAuth, kServerChallenge, OK),
+ TestRound(kGetAuthWithProxyAuth, kFailure, kAuthErr)}},
+ // Non-authenticating HTTPS server with a direct connection.
+ { NULL, AUTH_NONE, OK, kSecureServer, AUTH_NONE, OK, 1, 0,
+ { TestRound(kGet, kSuccess, OK)}},
+ // Authenticating HTTPS server with a direct connection.
+ { NULL, AUTH_NONE, OK, kSecureServer, AUTH_SYNC, OK, 2, 0,
+ { TestRound(kGet, kServerChallenge, OK),
+ TestRound(kGetAuth, kSuccess, OK)}},
+ { NULL, AUTH_NONE, OK, kSecureServer, AUTH_SYNC, kAuthErr, 2, 0,
+ { TestRound(kGet, kServerChallenge, OK),
+ TestRound(kGetAuth, kFailure, kAuthErr)}},
+ { NULL, AUTH_NONE, OK, kSecureServer, AUTH_ASYNC, OK, 2, 0,
+ { TestRound(kGet, kServerChallenge, OK),
+ TestRound(kGetAuth, kSuccess, OK)}},
+ { NULL, AUTH_NONE, OK, kSecureServer, AUTH_ASYNC, kAuthErr, 2, 0,
+ { TestRound(kGet, kServerChallenge, OK),
+ TestRound(kGetAuth, kFailure, kAuthErr)}},
+ // Non-authenticating HTTPS server with a non-authenticating proxy.
+ { kProxy, AUTH_NONE, OK, kSecureServer, AUTH_NONE, OK, 1, 0,
+ { TestRound(kConnect, kProxyConnected, OK, &kGet, &kSuccess)}},
+ // Authenticating HTTPS server through a non-authenticating proxy.
+ { kProxy, AUTH_NONE, OK, kSecureServer, AUTH_SYNC, OK, 2, 0,
+ { TestRound(kConnect, kProxyConnected, OK, &kGet, &kServerChallenge),
+ TestRound(kGetAuth, kSuccess, OK)}},
+ { kProxy, AUTH_NONE, OK, kSecureServer, AUTH_SYNC, kAuthErr, 2, 0,
+ { TestRound(kConnect, kProxyConnected, OK, &kGet, &kServerChallenge),
+ TestRound(kGetAuth, kFailure, kAuthErr)}},
+ { kProxy, AUTH_NONE, OK, kSecureServer, AUTH_ASYNC, OK, 2, 0,
+ { TestRound(kConnect, kProxyConnected, OK, &kGet, &kServerChallenge),
+ TestRound(kGetAuth, kSuccess, OK)}},
+ { kProxy, AUTH_NONE, OK, kSecureServer, AUTH_ASYNC, kAuthErr, 2, 0,
+ { TestRound(kConnect, kProxyConnected, OK, &kGet, &kServerChallenge),
+ TestRound(kGetAuth, kFailure, kAuthErr)}},
+ // Non-Authenticating HTTPS server through an authenticating proxy.
+ { kProxy, AUTH_SYNC, OK, kSecureServer, AUTH_NONE, OK, 2, 1,
+ { TestRound(kConnect, kProxyChallenge, OK),
+ TestRound(kConnectProxyAuth, kProxyConnected, OK, &kGet, &kSuccess)}},
+ { kProxy, AUTH_SYNC, kAuthErr, kSecureServer, AUTH_NONE, OK, 2, kNoSSL,
+ { TestRound(kConnect, kProxyChallenge, OK),
+ TestRound(kConnectProxyAuth, kFailure, kAuthErr)}},
+ { kProxy, AUTH_ASYNC, OK, kSecureServer, AUTH_NONE, OK, 2, 1,
+ { TestRound(kConnect, kProxyChallenge, OK),
+ TestRound(kConnectProxyAuth, kProxyConnected, OK, &kGet, &kSuccess)}},
+ { kProxy, AUTH_ASYNC, kAuthErr, kSecureServer, AUTH_NONE, OK, 2, kNoSSL,
+ { TestRound(kConnect, kProxyChallenge, OK),
+ TestRound(kConnectProxyAuth, kFailure, kAuthErr)}},
+ // Authenticating HTTPS server through an authenticating proxy.
+ { kProxy, AUTH_SYNC, OK, kSecureServer, AUTH_SYNC, OK, 3, 1,
+ { TestRound(kConnect, kProxyChallenge, OK),
+ TestRound(kConnectProxyAuth, kProxyConnected, OK,
+ &kGet, &kServerChallenge),
+ TestRound(kGetAuth, kSuccess, OK)}},
+ { kProxy, AUTH_SYNC, OK, kSecureServer, AUTH_SYNC, kAuthErr, 3, 1,
+ { TestRound(kConnect, kProxyChallenge, OK),
+ TestRound(kConnectProxyAuth, kProxyConnected, OK,
+ &kGet, &kServerChallenge),
+ TestRound(kGetAuth, kFailure, kAuthErr)}},
+ { kProxy, AUTH_ASYNC, OK, kSecureServer, AUTH_SYNC, OK, 3, 1,
+ { TestRound(kConnect, kProxyChallenge, OK),
+ TestRound(kConnectProxyAuth, kProxyConnected, OK,
+ &kGet, &kServerChallenge),
+ TestRound(kGetAuth, kSuccess, OK)}},
+ { kProxy, AUTH_ASYNC, OK, kSecureServer, AUTH_SYNC, kAuthErr, 3, 1,
+ { TestRound(kConnect, kProxyChallenge, OK),
+ TestRound(kConnectProxyAuth, kProxyConnected, OK,
+ &kGet, &kServerChallenge),
+ TestRound(kGetAuth, kFailure, kAuthErr)}},
+ { kProxy, AUTH_SYNC, OK, kSecureServer, AUTH_ASYNC, OK, 3, 1,
+ { TestRound(kConnect, kProxyChallenge, OK),
+ TestRound(kConnectProxyAuth, kProxyConnected, OK,
+ &kGet, &kServerChallenge),
+ TestRound(kGetAuth, kSuccess, OK)}},
+ { kProxy, AUTH_SYNC, OK, kSecureServer, AUTH_ASYNC, kAuthErr, 3, 1,
+ { TestRound(kConnect, kProxyChallenge, OK),
+ TestRound(kConnectProxyAuth, kProxyConnected, OK,
+ &kGet, &kServerChallenge),
+ TestRound(kGetAuth, kFailure, kAuthErr)}},
+ { kProxy, AUTH_ASYNC, OK, kSecureServer, AUTH_ASYNC, OK, 3, 1,
+ { TestRound(kConnect, kProxyChallenge, OK),
+ TestRound(kConnectProxyAuth, kProxyConnected, OK,
+ &kGet, &kServerChallenge),
+ TestRound(kGetAuth, kSuccess, OK)}},
+ { kProxy, AUTH_ASYNC, OK, kSecureServer, AUTH_ASYNC, kAuthErr, 3, 1,
+ { TestRound(kConnect, kProxyChallenge, OK),
+ TestRound(kConnectProxyAuth, kProxyConnected, OK,
+ &kGet, &kServerChallenge),
+ TestRound(kGetAuth, kFailure, kAuthErr)}},
+ };
+
+ SessionDependencies session_deps;
+ scoped_refptr<HttpNetworkSession> session = CreateSession(&session_deps);
+ HttpAuthHandlerMock::Factory* auth_factory(
+ new HttpAuthHandlerMock::Factory());
+ session_deps.http_auth_handler_factory.reset(auth_factory);
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_configs); ++i) {
+ const TestConfig& test_config = test_configs[i];
+
+ // Set up authentication handlers as necessary.
+ if (test_config.proxy_auth_timing != AUTH_NONE) {
+ HttpAuthHandlerMock* auth_handler(new HttpAuthHandlerMock());
+ std::string auth_challenge = "Mock realm=proxy";
+ GURL origin(test_config.proxy_url);
+ HttpAuth::ChallengeTokenizer tokenizer(auth_challenge.begin(),
+ auth_challenge.end());
+ auth_handler->InitFromChallenge(&tokenizer, HttpAuth::AUTH_PROXY,
+ origin, BoundNetLog());
+ auth_handler->SetGenerateExpectation(
+ test_config.proxy_auth_timing == AUTH_ASYNC,
+ test_config.proxy_auth_rv);
+ auth_factory->set_mock_handler(auth_handler, HttpAuth::AUTH_PROXY);
+ }
+ if (test_config.server_auth_timing != AUTH_NONE) {
+ HttpAuthHandlerMock* auth_handler(new HttpAuthHandlerMock());
+ std::string auth_challenge = "Mock realm=server";
+ GURL origin(test_config.server_url);
+ HttpAuth::ChallengeTokenizer tokenizer(auth_challenge.begin(),
+ auth_challenge.end());
+ auth_handler->InitFromChallenge(&tokenizer, HttpAuth::AUTH_SERVER,
+ origin, BoundNetLog());
+ auth_handler->SetGenerateExpectation(
+ test_config.server_auth_timing == AUTH_ASYNC,
+ test_config.server_auth_rv);
+ auth_factory->set_mock_handler(auth_handler, HttpAuth::AUTH_SERVER);
+ }
+ if (test_config.proxy_url) {
+ session_deps.proxy_service =
+ CreateFixedProxyService(test_config.proxy_url);
+ } else {
+ session_deps.proxy_service = ProxyService::CreateNull();
+ }
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL(test_config.server_url);
+ request.load_flags = 0;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(CreateSession(&session_deps)));
+
+ for (int round = 0; round < test_config.num_auth_rounds; ++round) {
+ const TestRound& read_write_round = test_config.rounds[round];
+
+ // Set up expected reads and writes.
+ MockRead reads[2];
+ reads[0] = read_write_round.read;
+ size_t length_reads = 1;
+ if (read_write_round.extra_read) {
+ reads[1] = *read_write_round.extra_read;
+ length_reads = 2;
+ }
+
+ MockWrite writes[2];
+ writes[0] = read_write_round.write;
+ size_t length_writes = 1;
+ if (read_write_round.extra_write) {
+ writes[1] = *read_write_round.extra_write;
+ length_writes = 2;
+ }
+ StaticSocketDataProvider data_provider(
+ reads, length_reads, writes, length_writes);
+ session_deps.socket_factory.AddSocketDataProvider(&data_provider);
+
+ // Add an SSL sequence if necessary.
+ SSLSocketDataProvider ssl_socket_data_provider(false, OK);
+ if (round >= test_config.first_ssl_round)
+ session_deps.socket_factory.AddSSLSocketDataProvider(
+ &ssl_socket_data_provider);
+
+ // Start or restart the transaction.
+ TestCompletionCallback callback;
+ int rv;
+ if (round == 0) {
+ rv = trans->Start(&request, &callback, BoundNetLog());
+ } else {
+ rv = trans->RestartWithAuth(L"foo", L"bar", &callback);
+ }
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+
+ // Compare results with expected data.
+ EXPECT_EQ(read_write_round.expected_rv, rv);
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ if (read_write_round.expected_rv == OK) {
+ EXPECT_FALSE(response == NULL);
+ } else {
+ EXPECT_TRUE(response == NULL);
+ EXPECT_EQ(round + 1, test_config.num_auth_rounds);
+ continue;
+ }
+ if (round + 1 < test_config.num_auth_rounds) {
+ EXPECT_FALSE(response->auth_challenge.get() == NULL);
+ } else {
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ }
+ }
+ }
+
+ // Flush the idle socket before the HttpNetworkTransaction goes out of scope.
+ session->FlushSocketPools();
+}
+
+TEST_F(HttpNetworkTransactionTest, MultiRoundAuth) {
+ // Do multi-round authentication and make sure it works correctly.
+ SessionDependencies session_deps;
+ HttpAuthHandlerMock::Factory* auth_factory(
+ new HttpAuthHandlerMock::Factory());
+ session_deps.http_auth_handler_factory.reset(auth_factory);
+ session_deps.proxy_service = ProxyService::CreateNull();
+ session_deps.host_resolver->rules()->AddRule("www.example.com", "10.0.0.1");
+ session_deps.host_resolver->set_synchronous_mode(true);
+
+ HttpAuthHandlerMock* auth_handler(new HttpAuthHandlerMock());
+ auth_handler->set_connection_based(true);
+ std::string auth_challenge = "Mock realm=server";
+ GURL origin("http://www.example.com");
+ HttpAuth::ChallengeTokenizer tokenizer(auth_challenge.begin(),
+ auth_challenge.end());
+ auth_handler->InitFromChallenge(&tokenizer, HttpAuth::AUTH_SERVER,
+ origin, BoundNetLog());
+ auth_factory->set_mock_handler(auth_handler, HttpAuth::AUTH_SERVER);
+
+ scoped_refptr<HttpNetworkSession> session = CreateSession(&session_deps);
+ scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
+
+ int rv = OK;
+ const HttpResponseInfo* response = NULL;
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = origin;
+ request.load_flags = 0;
+ TestCompletionCallback callback;
+
+ const MockWrite kGet(
+ "GET / HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "Connection: keep-alive\r\n\r\n");
+ const MockWrite kGetAuth(
+ "GET / HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: auth_token\r\n\r\n");
+
+ const MockRead kServerChallenge(
+ "HTTP/1.1 401 Unauthorized\r\n"
+ "WWW-Authenticate: Mock realm=server\r\n"
+ "Content-Type: text/html; charset=iso-8859-1\r\n"
+ "Content-Length: 14\r\n\r\n"
+ "Unauthorized\r\n");
+ const MockRead kSuccess(
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/html; charset=iso-8859-1\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "Yes");
+
+ MockWrite writes[] = {
+ // First round
+ kGet,
+ // Second round
+ kGetAuth,
+ // Third round
+ kGetAuth,
+ };
+ MockRead reads[] = {
+ // First round
+ kServerChallenge,
+ // Second round
+ kServerChallenge,
+ // Third round
+ kSuccess,
+ };
+ StaticSocketDataProvider data_provider(reads, arraysize(reads),
+ writes, arraysize(writes));
+ session_deps.socket_factory.AddSocketDataProvider(&data_provider);
+
+ // First round
+ auth_handler->SetGenerateExpectation(false, OK);
+ rv = trans->Start(&request, &callback, BoundNetLog());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ response = trans->GetResponseInfo();
+ ASSERT_FALSE(response == NULL);
+ EXPECT_FALSE(response->auth_challenge.get() == NULL);
+
+ // Second round
+ auth_handler->SetGenerateExpectation(false, OK);
+ rv = trans->RestartWithAuth(L"foo", L"bar", &callback);
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ response = trans->GetResponseInfo();
+ ASSERT_FALSE(response == NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+
+ // Third round
+ auth_handler->SetGenerateExpectation(false, OK);
+ rv = trans->RestartWithAuth(L"", L"", &callback);
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ response = trans->GetResponseInfo();
+ ASSERT_FALSE(response == NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+}
+
+class TLSDecompressionFailureSocketDataProvider : public SocketDataProvider {
+ public:
+ explicit TLSDecompressionFailureSocketDataProvider(bool fail_all)
+ : fail_all_(fail_all) {
+ }
+
+ virtual MockRead GetNextRead() {
+ if (fail_all_)
+ return MockRead(false /* async */, ERR_SSL_DECOMPRESSION_FAILURE_ALERT);
+
+ return MockRead(false /* async */,
+ "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nok.\r\n");
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) {
+ return MockWriteResult(false /* async */, data.size());
+ }
+
+ void Reset() {
+ }
+
+ private:
+ const bool fail_all_;
+};
+
+// Test that we restart a connection when we see a decompression failure from
+// the peer during the handshake. (In the real world we'll restart with SSLv3
+// and we won't offer DEFLATE in that case.)
+TEST_F(HttpNetworkTransactionTest, RestartAfterTLSDecompressionFailure) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://tlsdecompressionfailure.example.com/");
+ request.load_flags = 0;
+
+ SessionDependencies session_deps;
+ TLSDecompressionFailureSocketDataProvider socket_data_provider1(
+ false /* fail all reads */);
+ TLSDecompressionFailureSocketDataProvider socket_data_provider2(false);
+ SSLSocketDataProvider ssl_socket_data_provider1(
+ false, ERR_SSL_DECOMPRESSION_FAILURE_ALERT);
+ SSLSocketDataProvider ssl_socket_data_provider2(false, OK);
+ session_deps.socket_factory.AddSocketDataProvider(&socket_data_provider1);
+ session_deps.socket_factory.AddSocketDataProvider(&socket_data_provider2);
+ session_deps.socket_factory.AddSSLSocketDataProvider(
+ &ssl_socket_data_provider1);
+ session_deps.socket_factory.AddSSLSocketDataProvider(
+ &ssl_socket_data_provider2);
+
+ // Work around http://crbug.com/37454
+ StaticSocketDataProvider bug37454_connection;
+ bug37454_connection.set_connect_data(MockConnect(true, ERR_UNEXPECTED));
+ session_deps.socket_factory.AddSocketDataProvider(&bug37454_connection);
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
+ scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, &callback, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ("ok.", response_data);
+}
+
+// Test that we restart a connection if we get a decompression failure from the
+// peer while reading the first bytes from the connection. This occurs when the
+// peer cannot handle DEFLATE but we're using False Start, so we don't notice
+// in the handshake.
+TEST_F(HttpNetworkTransactionTest,
+ RestartAfterTLSDecompressionFailureWithFalseStart) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://tlsdecompressionfailure2.example.com/");
+ request.load_flags = 0;
+
+ SessionDependencies session_deps;
+ TLSDecompressionFailureSocketDataProvider socket_data_provider1(
+ true /* fail all reads */);
+ TLSDecompressionFailureSocketDataProvider socket_data_provider2(false);
+ SSLSocketDataProvider ssl_socket_data_provider1(false, OK);
+ SSLSocketDataProvider ssl_socket_data_provider2(false, OK);
+ session_deps.socket_factory.AddSocketDataProvider(&socket_data_provider1);
+ session_deps.socket_factory.AddSocketDataProvider(&socket_data_provider2);
+ session_deps.socket_factory.AddSSLSocketDataProvider(
+ &ssl_socket_data_provider1);
+ session_deps.socket_factory.AddSSLSocketDataProvider(
+ &ssl_socket_data_provider2);
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
+ scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, &callback, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ("ok.", response_data);
+}
+
+// This tests the case that a request is issued via http instead of spdy after
+// npn is negotiated.
+TEST_F(HttpNetworkTransactionTest, NpnWithHttpOverSSL) {
+ HttpNetworkTransaction::SetUseAlternateProtocols(true);
+ HttpNetworkTransaction::SetNextProtos("\x08http/1.1\x07http1.1");
+ SessionDependencies session_deps;
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ request.load_flags = 0;
+
+ MockWrite data_writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Alternate-Protocol: 443:npn-spdy/1\r\n\r\n"),
+ MockRead("hello world"),
+ MockRead(false, OK),
+ };
+
+ SSLSocketDataProvider ssl(true, OK);
+ ssl.next_proto_status = SSLClientSocket::kNextProtoNegotiated;
+ ssl.next_proto = "http/1.1";
+
+ session_deps.socket_factory.AddSSLSocketDataProvider(&ssl);
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps.socket_factory.AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
+ scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
+
+ int rv = trans->Start(&request, &callback, BoundNetLog());
+
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ("hello world", response_data);
+
+ EXPECT_FALSE(response->was_fetched_via_spdy);
+ EXPECT_TRUE(response->was_npn_negotiated);
+ EXPECT_FALSE(response->was_alternate_protocol_available);
+
+ HttpNetworkTransaction::SetNextProtos("");
+ HttpNetworkTransaction::SetUseAlternateProtocols(false);
+}
+
+TEST_F(HttpNetworkTransactionTest, SpdyPostNPNServerHangup) {
+ // Simulate the SSL handshake completing with an NPN negotiation
+ // followed by an immediate server closing of the socket.
+ // Fix crash: http://crbug.com/46369
+ HttpNetworkTransaction::SetUseAlternateProtocols(true);
+ HttpNetworkTransaction::SetNextProtos(
+ "\x08http/1.1\x07http1.1\x06spdy/1\x04spdy");
+ SessionDependencies session_deps;
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ request.load_flags = 0;
+
+ SSLSocketDataProvider ssl(true, OK);
+ ssl.next_proto_status = SSLClientSocket::kNextProtoNegotiated;
+ ssl.next_proto = "spdy/1";
+ ssl.was_npn_negotiated = true;
+ session_deps.socket_factory.AddSSLSocketDataProvider(&ssl);
+
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite spdy_writes[] = { CreateMockWrite(*req) };
+
+ MockRead spdy_reads[] = {
+ MockRead(false, 0, 0) // Not async - return 0 immediately.
+ };
+
+ scoped_refptr<DelayedSocketData> spdy_data(
+ new DelayedSocketData(
+ 0, // don't wait in this case, immediate hangup.
+ spdy_reads, arraysize(spdy_reads),
+ spdy_writes, arraysize(spdy_writes)));
+ session_deps.socket_factory.AddSocketDataProvider(spdy_data);
+
+ TestCompletionCallback callback;
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
+ scoped_ptr<HttpNetworkTransaction> trans(new HttpNetworkTransaction(session));
+
+ int rv = trans->Start(&request, &callback, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, callback.WaitForResult());
+
+ HttpNetworkTransaction::SetNextProtos("");
+ HttpNetworkTransaction::SetUseAlternateProtocols(false);
}
} // namespace net
diff --git a/net/http/http_proxy_client_socket.cc b/net/http/http_proxy_client_socket.cc
new file mode 100644
index 0000000..ec4b753
--- /dev/null
+++ b/net/http/http_proxy_client_socket.cc
@@ -0,0 +1,420 @@
+// 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 "net/http/http_proxy_client_socket.h"
+
+#include "base/string_util.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "net/http/http_basic_stream.h"
+#include "net/http/http_net_log_params.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_request_info.h"
+#include "net/socket/client_socket_handle.h"
+
+namespace net {
+
+namespace {
+
+// The HTTP CONNECT method for establishing a tunnel connection is documented
+// in draft-luotonen-web-proxy-tunneling-01.txt and RFC 2817, Sections 5.2 and
+// 5.3.
+void BuildTunnelRequest(const HttpRequestInfo* request_info,
+ const HttpRequestHeaders& authorization_headers,
+ const HostPortPair& endpoint,
+ std::string* request_line,
+ HttpRequestHeaders* request_headers) {
+ // RFC 2616 Section 9 says the Host request-header field MUST accompany all
+ // HTTP/1.1 requests. Add "Proxy-Connection: keep-alive" for compat with
+ // HTTP/1.0 proxies such as Squid (required for NTLM authentication).
+ *request_line = StringPrintf(
+ "CONNECT %s HTTP/1.1\r\n", endpoint.ToString().c_str());
+ request_headers->SetHeader(HttpRequestHeaders::kHost,
+ GetHostAndOptionalPort(request_info->url));
+ request_headers->SetHeader(HttpRequestHeaders::kProxyConnection,
+ "keep-alive");
+
+ std::string user_agent;
+ if (request_info->extra_headers.GetHeader(HttpRequestHeaders::kUserAgent,
+ &user_agent))
+ request_headers->SetHeader(HttpRequestHeaders::kUserAgent, user_agent);
+
+ request_headers->MergeFrom(authorization_headers);
+}
+
+} // namespace
+
+HttpProxyClientSocket::HttpProxyClientSocket(
+ ClientSocketHandle* transport_socket, const GURL& request_url,
+ const HostPortPair& endpoint, const scoped_refptr<HttpAuthController>& auth,
+ bool tunnel)
+ : ALLOW_THIS_IN_INITIALIZER_LIST(
+ io_callback_(this, &HttpProxyClientSocket::OnIOComplete)),
+ next_state_(STATE_NONE),
+ user_callback_(NULL),
+ transport_(transport_socket),
+ endpoint_(endpoint),
+ auth_(auth),
+ tunnel_(tunnel),
+ net_log_(transport_socket->socket()->NetLog()) {
+ DCHECK_EQ(tunnel, auth != NULL);
+ // Synthesize the bits of a request that we actually use.
+ request_.url = request_url;
+ request_.method = "GET";
+}
+
+HttpProxyClientSocket::~HttpProxyClientSocket() {
+ Disconnect();
+}
+
+int HttpProxyClientSocket::Connect(CompletionCallback* callback) {
+ DCHECK(transport_.get());
+ DCHECK(transport_->socket());
+ DCHECK(transport_->socket()->IsConnected());
+ DCHECK(!user_callback_);
+
+ if (!tunnel_)
+ next_state_ = STATE_DONE;
+ if (next_state_ == STATE_DONE)
+ return OK;
+
+ DCHECK_EQ(STATE_NONE, next_state_);
+ next_state_ = STATE_GENERATE_AUTH_TOKEN;
+
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ user_callback_ = callback;
+ return rv;
+}
+
+int HttpProxyClientSocket::RestartWithAuth(CompletionCallback* callback) {
+ DCHECK_EQ(STATE_NONE, next_state_);
+ DCHECK(!user_callback_);
+
+ int rv = PrepareForAuthRestart();
+ if (rv != OK)
+ return rv;
+
+ rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ user_callback_ = callback;
+ return rv;
+}
+
+int HttpProxyClientSocket::PrepareForAuthRestart() {
+ if (!response_.headers.get())
+ return ERR_CONNECTION_RESET;
+
+ bool keep_alive = false;
+ if (response_.headers->IsKeepAlive() &&
+ http_stream_->CanFindEndOfResponse()) {
+ if (!http_stream_->IsResponseBodyComplete()) {
+ next_state_ = STATE_DRAIN_BODY;
+ drain_buf_ = new IOBuffer(kDrainBodyBufferSize);
+ return OK;
+ }
+ keep_alive = true;
+ }
+
+ // We don't need to drain the response body, so we act as if we had drained
+ // the response body.
+ return DidDrainBodyForAuthRestart(keep_alive);
+}
+
+int HttpProxyClientSocket::DidDrainBodyForAuthRestart(bool keep_alive) {
+ int rc = OK;
+ if (keep_alive && transport_->socket()->IsConnectedAndIdle()) {
+ next_state_ = STATE_GENERATE_AUTH_TOKEN;
+ transport_->set_is_reused(true);
+ } else {
+ transport_->socket()->Disconnect();
+ rc = ERR_RETRY_CONNECTION;
+ }
+
+ // Reset the other member variables.
+ drain_buf_ = NULL;
+ http_stream_.reset();
+ request_headers_.clear();
+ response_ = HttpResponseInfo();
+ return rc;
+}
+
+void HttpProxyClientSocket::LogBlockedTunnelResponse(int response_code) const {
+ LOG(WARNING) << "Blocked proxy response with status " << response_code
+ << " to CONNECT request for "
+ << GetHostAndPort(request_.url) << ".";
+}
+
+void HttpProxyClientSocket::Disconnect() {
+ transport_->socket()->Disconnect();
+
+ // Reset other states to make sure they aren't mistakenly used later.
+ // These are the states initialized by Connect().
+ next_state_ = STATE_NONE;
+ user_callback_ = NULL;
+}
+
+bool HttpProxyClientSocket::IsConnected() const {
+ return transport_->socket()->IsConnected();
+}
+
+bool HttpProxyClientSocket::IsConnectedAndIdle() const {
+ return transport_->socket()->IsConnectedAndIdle();
+}
+
+bool HttpProxyClientSocket::NeedsRestartWithAuth() const {
+ return next_state_ != STATE_DONE;
+}
+
+int HttpProxyClientSocket::Read(IOBuffer* buf, int buf_len,
+ CompletionCallback* callback) {
+ DCHECK(!user_callback_);
+ if (next_state_ != STATE_DONE) {
+ // We're trying to read the body of the response but we're still trying
+ // to establish an SSL tunnel through the proxy. We can't read these
+ // bytes when establishing a tunnel because they might be controlled by
+ // an active network attacker. We don't worry about this for HTTP
+ // because an active network attacker can already control HTTP sessions.
+ // We reach this case when the user cancels a 407 proxy auth prompt.
+ // See http://crbug.com/8473.
+ DCHECK_EQ(407, response_.headers->response_code());
+ LogBlockedTunnelResponse(response_.headers->response_code());
+
+ return ERR_TUNNEL_CONNECTION_FAILED;
+ }
+
+ return transport_->socket()->Read(buf, buf_len, callback);
+}
+
+int HttpProxyClientSocket::Write(IOBuffer* buf, int buf_len,
+ CompletionCallback* callback) {
+ DCHECK_EQ(STATE_DONE, next_state_);
+ DCHECK(!user_callback_);
+
+ return transport_->socket()->Write(buf, buf_len, callback);
+}
+
+bool HttpProxyClientSocket::SetReceiveBufferSize(int32 size) {
+ return transport_->socket()->SetReceiveBufferSize(size);
+}
+
+bool HttpProxyClientSocket::SetSendBufferSize(int32 size) {
+ return transport_->socket()->SetSendBufferSize(size);
+}
+
+int HttpProxyClientSocket::GetPeerAddress(AddressList* address) const {
+ return transport_->socket()->GetPeerAddress(address);
+}
+
+void HttpProxyClientSocket::DoCallback(int result) {
+ DCHECK_NE(ERR_IO_PENDING, result);
+ DCHECK(user_callback_);
+
+ // Since Run() may result in Read being called,
+ // clear user_callback_ up front.
+ CompletionCallback* c = user_callback_;
+ user_callback_ = NULL;
+ c->Run(result);
+}
+
+void HttpProxyClientSocket::OnIOComplete(int result) {
+ DCHECK_NE(STATE_NONE, next_state_);
+ DCHECK_NE(STATE_DONE, next_state_);
+ int rv = DoLoop(result);
+ if (rv != ERR_IO_PENDING)
+ DoCallback(rv);
+}
+
+int HttpProxyClientSocket::DoLoop(int last_io_result) {
+ DCHECK_NE(next_state_, STATE_NONE);
+ DCHECK_NE(next_state_, STATE_DONE);
+ int rv = last_io_result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_GENERATE_AUTH_TOKEN:
+ DCHECK_EQ(OK, rv);
+ rv = DoGenerateAuthToken();
+ break;
+ case STATE_GENERATE_AUTH_TOKEN_COMPLETE:
+ rv = DoGenerateAuthTokenComplete(rv);
+ break;
+ case STATE_SEND_REQUEST:
+ DCHECK_EQ(OK, rv);
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_SEND_REQUEST,
+ NULL);
+ rv = DoSendRequest();
+ break;
+ case STATE_SEND_REQUEST_COMPLETE:
+ rv = DoSendRequestComplete(rv);
+ net_log_.EndEvent(NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_SEND_REQUEST,
+ NULL);
+ break;
+ case STATE_READ_HEADERS:
+ DCHECK_EQ(OK, rv);
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_READ_HEADERS,
+ NULL);
+ rv = DoReadHeaders();
+ break;
+ case STATE_READ_HEADERS_COMPLETE:
+ rv = DoReadHeadersComplete(rv);
+ net_log_.EndEvent(NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_READ_HEADERS,
+ NULL);
+ break;
+ case STATE_DRAIN_BODY:
+ DCHECK_EQ(OK, rv);
+ rv = DoDrainBody();
+ break;
+ case STATE_DRAIN_BODY_COMPLETE:
+ rv = DoDrainBodyComplete(rv);
+ break;
+ case STATE_DONE:
+ break;
+ default:
+ NOTREACHED() << "bad state";
+ rv = ERR_UNEXPECTED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE
+ && next_state_ != STATE_DONE);
+ return rv;
+}
+
+int HttpProxyClientSocket::DoGenerateAuthToken() {
+ next_state_ = STATE_GENERATE_AUTH_TOKEN_COMPLETE;
+ return auth_->MaybeGenerateAuthToken(&request_, &io_callback_, net_log_);
+}
+
+int HttpProxyClientSocket::DoGenerateAuthTokenComplete(int result) {
+ DCHECK_NE(ERR_IO_PENDING, result);
+ if (result == OK)
+ next_state_ = STATE_SEND_REQUEST;
+ return result;
+}
+
+int HttpProxyClientSocket::DoSendRequest() {
+ next_state_ = STATE_SEND_REQUEST_COMPLETE;
+
+ // This is constructed lazily (instead of within our Start method), so that
+ // we have proxy info available.
+ if (request_headers_.empty()) {
+ HttpRequestHeaders authorization_headers;
+ if (auth_->HaveAuth())
+ auth_->AddAuthorizationHeader(&authorization_headers);
+ std::string request_line;
+ HttpRequestHeaders request_headers;
+ BuildTunnelRequest(&request_, authorization_headers, endpoint_,
+ &request_line, &request_headers);
+ if (net_log_.HasListener()) {
+ net_log_.AddEvent(
+ NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS,
+ new NetLogHttpRequestParameter(
+ request_line, request_headers));
+ }
+ request_headers_ = request_line + request_headers.ToString();
+ }
+
+ http_stream_.reset(new HttpBasicStream(transport_.get(), net_log_));
+
+ return http_stream_->SendRequest(&request_, request_headers_, NULL,
+ &response_, &io_callback_);
+}
+
+int HttpProxyClientSocket::DoSendRequestComplete(int result) {
+ if (result < 0)
+ return result;
+
+ next_state_ = STATE_READ_HEADERS;
+ return OK;
+}
+
+int HttpProxyClientSocket::DoReadHeaders() {
+ next_state_ = STATE_READ_HEADERS_COMPLETE;
+ return http_stream_->ReadResponseHeaders(&io_callback_);
+}
+
+int HttpProxyClientSocket::DoReadHeadersComplete(int result) {
+ if (result < 0)
+ return result;
+
+ // Require the "HTTP/1.x" status line for SSL CONNECT.
+ if (response_.headers->GetParsedHttpVersion() < HttpVersion(1, 0))
+ return ERR_TUNNEL_CONNECTION_FAILED;
+
+ if (net_log_.HasListener()) {
+ net_log_.AddEvent(
+ NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS,
+ new NetLogHttpResponseParameter(response_.headers));
+ }
+
+ switch (response_.headers->response_code()) {
+ case 200: // OK
+ if (http_stream_->IsMoreDataBuffered())
+ // The proxy sent extraneous data after the headers.
+ return ERR_TUNNEL_CONNECTION_FAILED;
+
+ next_state_ = STATE_DONE;
+ return OK;
+
+ // We aren't able to CONNECT to the remote host through the proxy. We
+ // need to be very suspicious about the response because an active network
+ // attacker can force us into this state by masquerading as the proxy.
+ // The only safe thing to do here is to fail the connection because our
+ // client is expecting an SSL protected response.
+ // See http://crbug.com/7338.
+ case 407: // Proxy Authentication Required
+ // We need this status code to allow proxy authentication. Our
+ // authentication code is smart enough to avoid being tricked by an
+ // active network attacker.
+ // The next state is intentionally not set as it should be STATE_NONE;
+ return HandleAuthChallenge();
+
+ default:
+ // For all other status codes, we conservatively fail the CONNECT
+ // request.
+ // We lose something by doing this. We have seen proxy 403, 404, and
+ // 501 response bodies that contain a useful error message. For
+ // example, Squid uses a 404 response to report the DNS error: "The
+ // domain name does not exist."
+ LogBlockedTunnelResponse(response_.headers->response_code());
+ return ERR_TUNNEL_CONNECTION_FAILED;
+ }
+}
+
+int HttpProxyClientSocket::DoDrainBody() {
+ DCHECK(drain_buf_);
+ DCHECK(transport_->is_initialized());
+ next_state_ = STATE_DRAIN_BODY_COMPLETE;
+ return http_stream_->ReadResponseBody(drain_buf_, kDrainBodyBufferSize,
+ &io_callback_);
+}
+
+int HttpProxyClientSocket::DoDrainBodyComplete(int result) {
+ if (result < 0)
+ return result;
+
+ if (http_stream_->IsResponseBodyComplete())
+ return DidDrainBodyForAuthRestart(true);
+
+ // Keep draining.
+ next_state_ = STATE_DRAIN_BODY;
+ return OK;
+}
+
+int HttpProxyClientSocket::HandleAuthChallenge() {
+ DCHECK(response_.headers);
+
+ int rv = auth_->HandleAuthChallenge(response_.headers, false, true, net_log_);
+ response_.auth_challenge = auth_->auth_info();
+ if (rv == OK)
+ return ERR_PROXY_AUTH_REQUESTED;
+
+ return rv;
+}
+
+} // namespace net
diff --git a/net/http/http_proxy_client_socket.h b/net/http/http_proxy_client_socket.h
new file mode 100644
index 0000000..61e8158
--- /dev/null
+++ b/net/http/http_proxy_client_socket.h
@@ -0,0 +1,145 @@
+// 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.
+
+#ifndef NET_HTTP_HTTP_PROXY_CLIENT_SOCKET_H_
+#define NET_HTTP_HTTP_PROXY_CLIENT_SOCKET_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/ref_counted.h"
+#include "net/base/completion_callback.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_log.h"
+#include "net/http/http_auth_controller.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_info.h"
+#include "net/socket/client_socket.h"
+
+class GURL;
+
+namespace net {
+
+class AddressList;
+class ClientSocketHandle;
+class HttpStream;
+class IOBuffer;;
+
+class HttpProxyClientSocket : public ClientSocket {
+ public:
+ // Takes ownership of |transport_socket|, which should already be connected
+ // by the time Connect() is called. If tunnel is true then on Connect()
+ // this socket will establish an Http tunnel.
+ HttpProxyClientSocket(ClientSocketHandle* transport_socket,
+ const GURL& request_url, const HostPortPair& endpoint,
+ const scoped_refptr<HttpAuthController>& auth,
+ bool tunnel);
+
+ // On destruction Disconnect() is called.
+ virtual ~HttpProxyClientSocket();
+
+ // If Connect (or its callback) returns PROXY_AUTH_REQUESTED, then
+ // credentials should be added to the HttpAuthController before calling
+ // RestartWithAuth.
+ int RestartWithAuth(CompletionCallback* callback);
+
+ // Indicates if RestartWithAuth needs to be called. i.e. if Connect
+ // returned PROXY_AUTH_REQUESTED. Only valid after Connect has been called.
+ bool NeedsRestartWithAuth() const;
+
+ const HttpResponseInfo* GetResponseInfo() const {
+ return response_.headers ? &response_ : NULL;
+ }
+
+ // ClientSocket methods:
+
+ // Authenticates to the Http Proxy and then passes data freely.
+ virtual int Connect(CompletionCallback* callback);
+ virtual void Disconnect();
+ virtual bool IsConnected() const;
+ virtual bool IsConnectedAndIdle() const;
+ virtual const BoundNetLog& NetLog() const { return net_log_; }
+
+ // Socket methods:
+ virtual int Read(IOBuffer* buf, int buf_len, CompletionCallback* callback);
+ virtual int Write(IOBuffer* buf, int buf_len, CompletionCallback* callback);
+
+ virtual bool SetReceiveBufferSize(int32 size);
+ virtual bool SetSendBufferSize(int32 size);
+
+ virtual int GetPeerAddress(AddressList* address) const;
+
+ private:
+ enum State {
+ STATE_NONE,
+ STATE_GENERATE_AUTH_TOKEN,
+ STATE_GENERATE_AUTH_TOKEN_COMPLETE,
+ STATE_SEND_REQUEST,
+ STATE_SEND_REQUEST_COMPLETE,
+ STATE_READ_HEADERS,
+ STATE_READ_HEADERS_COMPLETE,
+ STATE_RESOLVE_CANONICAL_NAME,
+ STATE_RESOLVE_CANONICAL_NAME_COMPLETE,
+ STATE_DRAIN_BODY,
+ STATE_DRAIN_BODY_COMPLETE,
+ STATE_DONE,
+ };
+
+ // The size in bytes of the buffer we use to drain the response body that
+ // we want to throw away. The response body is typically a small error
+ // page just a few hundred bytes long.
+ enum { kDrainBodyBufferSize = 1024 };
+
+ int PrepareForAuthRestart();
+ int DidDrainBodyForAuthRestart(bool keep_alive);
+
+ int HandleAuthChallenge();
+
+ void LogBlockedTunnelResponse(int response_code) const;
+
+ void DoCallback(int result);
+ void OnIOComplete(int result);
+
+ int DoLoop(int last_io_result);
+ int DoGenerateAuthToken();
+ int DoGenerateAuthTokenComplete(int result);
+ int DoSendRequest();
+ int DoSendRequestComplete(int result);
+ int DoReadHeaders();
+ int DoReadHeadersComplete(int result);
+ int DoDrainBody();
+ int DoDrainBodyComplete(int result);
+
+ CompletionCallbackImpl<HttpProxyClientSocket> io_callback_;
+ State next_state_;
+
+ // Stores the callback to the layer above, called on completing Connect().
+ CompletionCallback* user_callback_;
+
+ HttpRequestInfo request_;
+ HttpResponseInfo response_;
+
+ scoped_ptr<HttpStream> http_stream_;
+ scoped_refptr<IOBuffer> drain_buf_;
+
+ // Stores the underlying socket.
+ const scoped_ptr<ClientSocketHandle> transport_;
+
+ // The hostname and port of the endpoint. This is not necessarily the one
+ // specified by the URL, due to Alternate-Protocol or fixed testing ports.
+ const HostPortPair endpoint_;
+ scoped_refptr<HttpAuthController> auth_;
+ const bool tunnel_;
+
+ std::string request_headers_;
+
+ const BoundNetLog net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpProxyClientSocket);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_PROXY_CLIENT_SOCKET_H_
diff --git a/net/http/http_proxy_client_socket_pool.cc b/net/http/http_proxy_client_socket_pool.cc
new file mode 100644
index 0000000..a142576
--- /dev/null
+++ b/net/http/http_proxy_client_socket_pool.cc
@@ -0,0 +1,229 @@
+// 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 "net/http/http_proxy_client_socket_pool.h"
+
+#include "base/time.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_proxy_client_socket.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/client_socket_pool_base.h"
+
+namespace net {
+
+HttpProxySocketParams::HttpProxySocketParams(
+ const scoped_refptr<TCPSocketParams>& proxy_server,
+ const GURL& request_url,
+ HostPortPair endpoint,
+ scoped_refptr<HttpAuthController> auth_controller,
+ bool tunnel)
+ : tcp_params_(proxy_server),
+ request_url_(request_url),
+ endpoint_(endpoint),
+ auth_controller_(auth_controller),
+ tunnel_(tunnel) {
+}
+
+HttpProxySocketParams::~HttpProxySocketParams() {}
+
+// HttpProxyConnectJobs will time out after this many seconds. Note this is on
+// top of the timeout for the transport socket.
+static const int kHttpProxyConnectJobTimeoutInSeconds = 30;
+
+HttpProxyConnectJob::HttpProxyConnectJob(
+ const std::string& group_name,
+ const scoped_refptr<HttpProxySocketParams>& params,
+ const base::TimeDelta& timeout_duration,
+ const scoped_refptr<TCPClientSocketPool>& tcp_pool,
+ const scoped_refptr<HostResolver>& host_resolver,
+ Delegate* delegate,
+ NetLog* net_log)
+ : ConnectJob(group_name, timeout_duration, delegate,
+ BoundNetLog::Make(net_log, NetLog::SOURCE_CONNECT_JOB)),
+ params_(params),
+ tcp_pool_(tcp_pool),
+ resolver_(host_resolver),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ callback_(this, &HttpProxyConnectJob::OnIOComplete)) {
+}
+
+HttpProxyConnectJob::~HttpProxyConnectJob() {}
+
+LoadState HttpProxyConnectJob::GetLoadState() const {
+ switch (next_state_) {
+ case kStateTCPConnect:
+ case kStateTCPConnectComplete:
+ return tcp_socket_handle_->GetLoadState();
+ case kStateHttpProxyConnect:
+ case kStateHttpProxyConnectComplete:
+ return LOAD_STATE_ESTABLISHING_PROXY_TUNNEL;
+ default:
+ NOTREACHED();
+ return LOAD_STATE_IDLE;
+ }
+}
+
+int HttpProxyConnectJob::ConnectInternal() {
+ next_state_ = kStateTCPConnect;
+ return DoLoop(OK);
+}
+
+void HttpProxyConnectJob::OnIOComplete(int result) {
+ int rv = DoLoop(result);
+ if (rv != ERR_IO_PENDING)
+ NotifyDelegateOfCompletion(rv); // Deletes |this|
+}
+
+int HttpProxyConnectJob::DoLoop(int result) {
+ DCHECK_NE(next_state_, kStateNone);
+
+ int rv = result;
+ do {
+ State state = next_state_;
+ next_state_ = kStateNone;
+ switch (state) {
+ case kStateTCPConnect:
+ DCHECK_EQ(OK, rv);
+ rv = DoTCPConnect();
+ break;
+ case kStateTCPConnectComplete:
+ rv = DoTCPConnectComplete(rv);
+ break;
+ case kStateHttpProxyConnect:
+ DCHECK_EQ(OK, rv);
+ rv = DoHttpProxyConnect();
+ break;
+ case kStateHttpProxyConnectComplete:
+ rv = DoHttpProxyConnectComplete(rv);
+ break;
+ default:
+ NOTREACHED() << "bad state";
+ rv = ERR_FAILED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != kStateNone);
+
+ return rv;
+}
+
+int HttpProxyConnectJob::DoTCPConnect() {
+ next_state_ = kStateTCPConnectComplete;
+ tcp_socket_handle_.reset(new ClientSocketHandle());
+ return tcp_socket_handle_->Init(
+ group_name(), params_->tcp_params(),
+ params_->tcp_params()->destination().priority(), &callback_, tcp_pool_,
+ net_log());
+}
+
+int HttpProxyConnectJob::DoTCPConnectComplete(int result) {
+ if (result != OK)
+ return result;
+
+ // Reset the timer to just the length of time allowed for HttpProxy handshake
+ // so that a fast TCP connection plus a slow HttpProxy failure doesn't take
+ // longer to timeout than it should.
+ ResetTimer(base::TimeDelta::FromSeconds(
+ kHttpProxyConnectJobTimeoutInSeconds));
+ next_state_ = kStateHttpProxyConnect;
+ return result;
+}
+
+int HttpProxyConnectJob::DoHttpProxyConnect() {
+ next_state_ = kStateHttpProxyConnectComplete;
+
+ // Add a HttpProxy connection on top of the tcp socket.
+ socket_.reset(new HttpProxyClientSocket(tcp_socket_handle_.release(),
+ params_->request_url(),
+ params_->endpoint(),
+ params_->auth_controller(),
+ params_->tunnel()));
+ return socket_->Connect(&callback_);
+}
+
+int HttpProxyConnectJob::DoHttpProxyConnectComplete(int result) {
+ DCHECK_NE(result, ERR_RETRY_CONNECTION);
+
+ if (result == OK || result == ERR_PROXY_AUTH_REQUESTED)
+ set_socket(socket_.release());
+
+ return result;
+}
+
+ConnectJob*
+HttpProxyClientSocketPool::HttpProxyConnectJobFactory::NewConnectJob(
+ const std::string& group_name,
+ const PoolBase::Request& request,
+ ConnectJob::Delegate* delegate) const {
+ return new HttpProxyConnectJob(group_name, request.params(),
+ ConnectionTimeout(), tcp_pool_, host_resolver_,
+ delegate, net_log_);
+}
+
+base::TimeDelta
+HttpProxyClientSocketPool::HttpProxyConnectJobFactory::ConnectionTimeout()
+const {
+ return tcp_pool_->ConnectionTimeout() +
+ base::TimeDelta::FromSeconds(kHttpProxyConnectJobTimeoutInSeconds);
+}
+
+HttpProxyClientSocketPool::HttpProxyClientSocketPool(
+ int max_sockets,
+ int max_sockets_per_group,
+ const scoped_refptr<ClientSocketPoolHistograms>& histograms,
+ const scoped_refptr<HostResolver>& host_resolver,
+ const scoped_refptr<TCPClientSocketPool>& tcp_pool,
+ NetLog* net_log)
+ : base_(max_sockets, max_sockets_per_group, histograms,
+ base::TimeDelta::FromSeconds(
+ ClientSocketPool::unused_idle_socket_timeout()),
+ base::TimeDelta::FromSeconds(kUsedIdleSocketTimeout),
+ new HttpProxyConnectJobFactory(tcp_pool, host_resolver, net_log)) {}
+
+HttpProxyClientSocketPool::~HttpProxyClientSocketPool() {}
+
+int HttpProxyClientSocketPool::RequestSocket(const std::string& group_name,
+ const void* socket_params,
+ RequestPriority priority,
+ ClientSocketHandle* handle,
+ CompletionCallback* callback,
+ const BoundNetLog& net_log) {
+ const scoped_refptr<HttpProxySocketParams>* casted_socket_params =
+ static_cast<const scoped_refptr<HttpProxySocketParams>*>(socket_params);
+
+ return base_.RequestSocket(group_name, *casted_socket_params, priority,
+ handle, callback, net_log);
+}
+
+void HttpProxyClientSocketPool::CancelRequest(
+ const std::string& group_name,
+ ClientSocketHandle* handle) {
+ base_.CancelRequest(group_name, handle);
+}
+
+void HttpProxyClientSocketPool::ReleaseSocket(const std::string& group_name,
+ ClientSocket* socket, int id) {
+ base_.ReleaseSocket(group_name, socket, id);
+}
+
+void HttpProxyClientSocketPool::Flush() {
+ base_.Flush();
+}
+
+void HttpProxyClientSocketPool::CloseIdleSockets() {
+ base_.CloseIdleSockets();
+}
+
+int HttpProxyClientSocketPool::IdleSocketCountInGroup(
+ const std::string& group_name) const {
+ return base_.IdleSocketCountInGroup(group_name);
+}
+
+LoadState HttpProxyClientSocketPool::GetLoadState(
+ const std::string& group_name, const ClientSocketHandle* handle) const {
+ return base_.GetLoadState(group_name, handle);
+}
+
+} // namespace net
diff --git a/net/http/http_proxy_client_socket_pool.h b/net/http/http_proxy_client_socket_pool.h
new file mode 100644
index 0000000..afe7d19
--- /dev/null
+++ b/net/http/http_proxy_client_socket_pool.h
@@ -0,0 +1,204 @@
+// 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.
+
+#ifndef NET_HTTP_HTTP_PROXY_CLIENT_SOCKET_POOL_H_
+#define NET_HTTP_HTTP_PROXY_CLIENT_SOCKET_POOL_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "base/time.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/host_resolver.h"
+#include "net/http/http_auth.h"
+#include "net/proxy/proxy_server.h"
+#include "net/socket/client_socket_pool_base.h"
+#include "net/socket/client_socket_pool_histograms.h"
+#include "net/socket/client_socket_pool.h"
+#include "net/socket/tcp_client_socket_pool.h"
+
+namespace net {
+
+class ClientSocketFactory;
+class ConnectJobFactory;
+class HttpAuthController;
+
+class HttpProxySocketParams : public base::RefCounted<HttpProxySocketParams> {
+ public:
+ HttpProxySocketParams(const scoped_refptr<TCPSocketParams>& proxy_server,
+ const GURL& request_url, HostPortPair endpoint,
+ scoped_refptr<HttpAuthController> auth_controller,
+ bool tunnel);
+
+ const scoped_refptr<TCPSocketParams>& tcp_params() const {
+ return tcp_params_;
+ }
+ const GURL& request_url() const { return request_url_; }
+ const HostPortPair& endpoint() const { return endpoint_; }
+ const scoped_refptr<HttpAuthController>& auth_controller() {
+ return auth_controller_;
+ }
+ bool tunnel() const { return tunnel_; }
+
+ private:
+ friend class base::RefCounted<HttpProxySocketParams>;
+ ~HttpProxySocketParams();
+
+ const scoped_refptr<TCPSocketParams> tcp_params_;
+ const GURL request_url_;
+ const HostPortPair endpoint_;
+ const scoped_refptr<HttpAuthController> auth_controller_;
+ const bool tunnel_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpProxySocketParams);
+};
+
+// HttpProxyConnectJob optionally establishes a tunnel through the proxy
+// server after connecting the underlying transport socket.
+class HttpProxyConnectJob : public ConnectJob {
+ public:
+ HttpProxyConnectJob(const std::string& group_name,
+ const scoped_refptr<HttpProxySocketParams>& params,
+ const base::TimeDelta& timeout_duration,
+ const scoped_refptr<TCPClientSocketPool>& tcp_pool,
+ const scoped_refptr<HostResolver> &host_resolver,
+ Delegate* delegate,
+ NetLog* net_log);
+ virtual ~HttpProxyConnectJob();
+
+ // ConnectJob methods.
+ virtual LoadState GetLoadState() const;
+
+ private:
+ enum State {
+ kStateTCPConnect,
+ kStateTCPConnectComplete,
+ kStateHttpProxyConnect,
+ kStateHttpProxyConnectComplete,
+ kStateNone,
+ };
+
+ // Begins the tcp connection and the optional Http proxy tunnel. If the
+ // request is not immediately servicable (likely), the request will return
+ // ERR_IO_PENDING. An OK return from this function or the callback means
+ // that the connection is established; ERR_PROXY_AUTH_REQUESTED means
+ // that the tunnel needs authentication credentials, the socket will be
+ // returned in this case, and must be release back to the pool; or
+ // a standard net error code will be returned.
+ virtual int ConnectInternal();
+
+ void OnIOComplete(int result);
+
+ // Runs the state transition loop.
+ int DoLoop(int result);
+
+ int DoTCPConnect();
+ int DoTCPConnectComplete(int result);
+ int DoHttpProxyConnect();
+ int DoHttpProxyConnectComplete(int result);
+
+ scoped_refptr<HttpProxySocketParams> params_;
+ const scoped_refptr<TCPClientSocketPool> tcp_pool_;
+ const scoped_refptr<HostResolver> resolver_;
+
+ State next_state_;
+ CompletionCallbackImpl<HttpProxyConnectJob> callback_;
+ scoped_ptr<ClientSocketHandle> tcp_socket_handle_;
+ scoped_ptr<ClientSocket> socket_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpProxyConnectJob);
+};
+
+class HttpProxyClientSocketPool : public ClientSocketPool {
+ public:
+ HttpProxyClientSocketPool(
+ int max_sockets,
+ int max_sockets_per_group,
+ const scoped_refptr<ClientSocketPoolHistograms>& histograms,
+ const scoped_refptr<HostResolver>& host_resolver,
+ const scoped_refptr<TCPClientSocketPool>& tcp_pool,
+ NetLog* net_log);
+
+ // ClientSocketPool methods:
+ virtual int RequestSocket(const std::string& group_name,
+ const void* connect_params,
+ RequestPriority priority,
+ ClientSocketHandle* handle,
+ CompletionCallback* callback,
+ const BoundNetLog& net_log);
+
+ virtual void CancelRequest(const std::string& group_name,
+ ClientSocketHandle* handle);
+
+ virtual void ReleaseSocket(const std::string& group_name,
+ ClientSocket* socket,
+ int id);
+
+ virtual void Flush();
+
+ virtual void CloseIdleSockets();
+
+ virtual int IdleSocketCount() const {
+ return base_.idle_socket_count();
+ }
+
+ virtual int IdleSocketCountInGroup(const std::string& group_name) const;
+
+ virtual LoadState GetLoadState(const std::string& group_name,
+ const ClientSocketHandle* handle) const;
+
+ virtual base::TimeDelta ConnectionTimeout() const {
+ return base_.ConnectionTimeout();
+ }
+
+ virtual scoped_refptr<ClientSocketPoolHistograms> histograms() const {
+ return base_.histograms();
+ };
+
+ protected:
+ virtual ~HttpProxyClientSocketPool();
+
+ private:
+ typedef ClientSocketPoolBase<HttpProxySocketParams> PoolBase;
+
+ class HttpProxyConnectJobFactory : public PoolBase::ConnectJobFactory {
+ public:
+ HttpProxyConnectJobFactory(
+ const scoped_refptr<TCPClientSocketPool>& tcp_pool,
+ HostResolver* host_resolver,
+ NetLog* net_log)
+ : tcp_pool_(tcp_pool),
+ host_resolver_(host_resolver),
+ net_log_(net_log) {}
+
+ virtual ~HttpProxyConnectJobFactory() {}
+
+ // ClientSocketPoolBase::ConnectJobFactory methods.
+ virtual ConnectJob* NewConnectJob(const std::string& group_name,
+ const PoolBase::Request& request,
+ ConnectJob::Delegate* delegate) const;
+
+ virtual base::TimeDelta ConnectionTimeout() const;
+
+ private:
+ const scoped_refptr<TCPClientSocketPool> tcp_pool_;
+ const scoped_refptr<HostResolver> host_resolver_;
+ NetLog* net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpProxyConnectJobFactory);
+ };
+
+ PoolBase base_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpProxyClientSocketPool);
+};
+
+REGISTER_SOCKET_PARAMS_FOR_POOL(HttpProxyClientSocketPool,
+ HttpProxySocketParams);
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_PROXY_CLIENT_SOCKET_POOL_H_
diff --git a/net/http/http_proxy_client_socket_pool_unittest.cc b/net/http/http_proxy_client_socket_pool_unittest.cc
new file mode 100644
index 0000000..9400dd8
--- /dev/null
+++ b/net/http/http_proxy_client_socket_pool_unittest.cc
@@ -0,0 +1,272 @@
+// 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 "net/http/http_proxy_client_socket_pool.h"
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/http/http_proxy_client_socket.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/client_socket_pool_histograms.h"
+#include "net/socket/socket_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+const int kMaxSockets = 32;
+const int kMaxSocketsPerGroup = 6;
+
+class HttpProxyClientSocketPoolTest : public ClientSocketPoolTest {
+ protected:
+ HttpProxyClientSocketPoolTest()
+ : ignored_tcp_socket_params_(new TCPSocketParams(
+ HostPortPair("proxy", 80), MEDIUM, GURL(), false)),
+ tcp_histograms_(new ClientSocketPoolHistograms("MockTCP")),
+ tcp_socket_pool_(new MockTCPClientSocketPool(kMaxSockets,
+ kMaxSocketsPerGroup, tcp_histograms_, &tcp_client_socket_factory_)),
+ notunnel_socket_params_(new HttpProxySocketParams(
+ ignored_tcp_socket_params_, GURL("http://host"),
+ HostPortPair("host", 80), NULL, false)),
+ auth_controller_(new MockHttpAuthController),
+ tunnel_socket_params_(new HttpProxySocketParams(
+ ignored_tcp_socket_params_, GURL("http://host"),
+ HostPortPair("host", 80), auth_controller_, true)),
+ http_proxy_histograms_(
+ new ClientSocketPoolHistograms("HttpProxyUnitTest")),
+ pool_(new HttpProxyClientSocketPool(kMaxSockets, kMaxSocketsPerGroup,
+ http_proxy_histograms_, NULL, tcp_socket_pool_, NULL)) {
+ }
+
+ int StartRequest(const std::string& group_name, RequestPriority priority) {
+ return StartRequestUsingPool(
+ pool_, group_name, priority, tunnel_socket_params_);
+ }
+
+ scoped_refptr<TCPSocketParams> ignored_tcp_socket_params_;
+ scoped_refptr<ClientSocketPoolHistograms> tcp_histograms_;
+ MockClientSocketFactory tcp_client_socket_factory_;
+ scoped_refptr<MockTCPClientSocketPool> tcp_socket_pool_;
+
+ scoped_refptr<HttpProxySocketParams> notunnel_socket_params_;
+ scoped_refptr<MockHttpAuthController> auth_controller_;
+ scoped_refptr<HttpProxySocketParams> tunnel_socket_params_;
+ scoped_refptr<ClientSocketPoolHistograms> http_proxy_histograms_;
+ scoped_refptr<HttpProxyClientSocketPool> pool_;
+};
+
+TEST_F(HttpProxyClientSocketPoolTest, NoTunnel) {
+ StaticSocketDataProvider data;
+ data.set_connect_data(MockConnect(false, 0));
+ tcp_client_socket_factory_.AddSocketDataProvider(&data);
+
+ ClientSocketHandle handle;
+ int rv = handle.Init("a", notunnel_socket_params_, LOW, NULL, pool_,
+ BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+ HttpProxyClientSocket* tunnel_socket =
+ static_cast<HttpProxyClientSocket*>(handle.socket());
+ EXPECT_FALSE(tunnel_socket->NeedsRestartWithAuth());
+}
+
+TEST_F(HttpProxyClientSocketPoolTest, NeedAuth) {
+ MockWrite writes[] = {
+ MockWrite("CONNECT host:80 HTTP/1.1\r\n"
+ "Host: host\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ // No credentials.
+ MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"),
+ MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Content-Length: 10\r\n\r\n"),
+ MockRead("0123456789"),
+ };
+ StaticSocketDataProvider data(reads, arraysize(reads), writes,
+ arraysize(writes));
+
+ tcp_client_socket_factory_.AddSocketDataProvider(&data);
+ MockHttpAuthControllerData auth_data[] = {
+ MockHttpAuthControllerData(""),
+ };
+ auth_controller_->SetMockAuthControllerData(auth_data, arraysize(auth_data));
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a", tunnel_socket_params_, LOW, &callback, pool_,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(ERR_PROXY_AUTH_REQUESTED, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+ HttpProxyClientSocket* tunnel_socket =
+ static_cast<HttpProxyClientSocket*>(handle.socket());
+ EXPECT_TRUE(tunnel_socket->NeedsRestartWithAuth());
+}
+
+TEST_F(HttpProxyClientSocketPoolTest, HaveAuth) {
+ MockWrite writes[] = {
+ MockWrite(false,
+ "CONNECT host:80 HTTP/1.1\r\n"
+ "Host: host\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: Basic Zm9vOmJheg==\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(false, "HTTP/1.1 200 Connection Established\r\n\r\n"),
+ };
+ StaticSocketDataProvider data(reads, arraysize(reads), writes,
+ arraysize(writes));
+ data.set_connect_data(MockConnect(false, 0));
+
+ tcp_client_socket_factory_.AddSocketDataProvider(&data);
+ MockHttpAuthControllerData auth_data[] = {
+ MockHttpAuthControllerData("Proxy-Authorization: Basic Zm9vOmJheg=="),
+ };
+ auth_controller_->SetMockAuthControllerData(auth_data, arraysize(auth_data));
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a", tunnel_socket_params_, LOW, &callback, pool_,
+ BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+ HttpProxyClientSocket* tunnel_socket =
+ static_cast<HttpProxyClientSocket*>(handle.socket());
+ EXPECT_FALSE(tunnel_socket->NeedsRestartWithAuth());
+}
+
+TEST_F(HttpProxyClientSocketPoolTest, AsyncHaveAuth) {
+ MockWrite writes[] = {
+ MockWrite("CONNECT host:80 HTTP/1.1\r\n"
+ "Host: host\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: Basic Zm9vOmJheg==\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"),
+ };
+ StaticSocketDataProvider data(reads, arraysize(reads), writes,
+ arraysize(writes));
+
+ tcp_client_socket_factory_.AddSocketDataProvider(&data);
+ MockHttpAuthControllerData auth_data[] = {
+ MockHttpAuthControllerData("Proxy-Authorization: Basic Zm9vOmJheg=="),
+ };
+ auth_controller_->SetMockAuthControllerData(auth_data, arraysize(auth_data));
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a", tunnel_socket_params_, LOW, &callback, pool_,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+ HttpProxyClientSocket* tunnel_socket =
+ static_cast<HttpProxyClientSocket*>(handle.socket());
+ EXPECT_FALSE(tunnel_socket->NeedsRestartWithAuth());
+}
+
+TEST_F(HttpProxyClientSocketPoolTest, TCPError) {
+ StaticSocketDataProvider data;
+ data.set_connect_data(MockConnect(true, ERR_CONNECTION_CLOSED));
+
+ tcp_client_socket_factory_.AddSocketDataProvider(&data);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a", tunnel_socket_params_, LOW, &callback, pool_,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, callback.WaitForResult());
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+}
+
+TEST_F(HttpProxyClientSocketPoolTest, TunnelUnexpectedClose) {
+ MockWrite writes[] = {
+ MockWrite("CONNECT host:80 HTTP/1.1\r\n"
+ "Host: host\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: Basic Zm9vOmJheg==\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead("HTTP/1.1 200 Conn"),
+ MockRead(true, ERR_CONNECTION_CLOSED),
+ };
+ StaticSocketDataProvider data(reads, arraysize(reads), writes,
+ arraysize(writes));
+
+ tcp_client_socket_factory_.AddSocketDataProvider(&data);
+ MockHttpAuthControllerData auth_data[] = {
+ MockHttpAuthControllerData("Proxy-Authorization: Basic Zm9vOmJheg=="),
+ };
+ auth_controller_->SetMockAuthControllerData(auth_data, arraysize(auth_data));
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a", tunnel_socket_params_, LOW, &callback, pool_,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, callback.WaitForResult());
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+}
+
+TEST_F(HttpProxyClientSocketPoolTest, TunnelSetupError) {
+ MockWrite writes[] = {
+ MockWrite("CONNECT host:80 HTTP/1.1\r\n"
+ "Host: host\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: Basic Zm9vOmJheg==\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead("HTTP/1.1 304 Not Modified\r\n\r\n"),
+ };
+ StaticSocketDataProvider data(reads, arraysize(reads), writes,
+ arraysize(writes));
+
+ tcp_client_socket_factory_.AddSocketDataProvider(&data);
+ MockHttpAuthControllerData auth_data[] = {
+ MockHttpAuthControllerData("Proxy-Authorization: Basic Zm9vOmJheg=="),
+ };
+ auth_controller_->SetMockAuthControllerData(auth_data, arraysize(auth_data));
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a", tunnel_socket_params_, LOW, &callback, pool_,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, callback.WaitForResult());
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+}
+
+// It would be nice to also test the timeouts in HttpProxyClientSocketPool.
+
+} // namespace
+
+} // namespace net
diff --git a/net/http/http_request_headers.cc b/net/http/http_request_headers.cc
new file mode 100644
index 0000000..29379c1
--- /dev/null
+++ b/net/http/http_request_headers.cc
@@ -0,0 +1,185 @@
+// 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 "net/http/http_request_headers.h"
+
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "net/http/http_util.h"
+
+namespace net {
+
+const char HttpRequestHeaders::kGetMethod[] = "GET";
+const char HttpRequestHeaders::kAcceptCharset[] = "Accept-Charset";
+const char HttpRequestHeaders::kAcceptEncoding[] = "Accept-Encoding";
+const char HttpRequestHeaders::kAcceptLanguage[] = "Accept-Language";
+const char HttpRequestHeaders::kCacheControl[] = "Cache-Control";
+const char HttpRequestHeaders::kConnection[] = "Connection";
+const char HttpRequestHeaders::kContentLength[] = "Content-Length";
+const char HttpRequestHeaders::kContentType[] = "Content-Type";
+const char HttpRequestHeaders::kCookie[] = "Cookie";
+const char HttpRequestHeaders::kHost[] = "Host";
+const char HttpRequestHeaders::kIfModifiedSince[] = "If-Modified-Since";
+const char HttpRequestHeaders::kIfNoneMatch[] = "If-None-Match";
+const char HttpRequestHeaders::kIfRange[] = "If-Range";
+const char HttpRequestHeaders::kOrigin[] = "Origin";
+const char HttpRequestHeaders::kPragma[] = "Pragma";
+const char HttpRequestHeaders::kProxyConnection[] = "Proxy-Connection";
+const char HttpRequestHeaders::kRange[] = "Range";
+const char HttpRequestHeaders::kReferer[] = "Referer";
+const char HttpRequestHeaders::kUserAgent[] = "User-Agent";
+
+HttpRequestHeaders::Iterator::Iterator(const HttpRequestHeaders& headers)
+ : started_(false),
+ curr_(headers.headers_.begin()),
+ end_(headers.headers_.end()) {}
+
+HttpRequestHeaders::Iterator::~Iterator() {}
+
+bool HttpRequestHeaders::Iterator::GetNext() {
+ if (!started_) {
+ started_ = true;
+ return curr_ != end_;
+ }
+
+ if (curr_ == end_)
+ return false;
+
+ ++curr_;
+ return curr_ != end_;
+}
+
+HttpRequestHeaders::HttpRequestHeaders() {}
+HttpRequestHeaders::~HttpRequestHeaders() {}
+
+bool HttpRequestHeaders::GetHeader(const base::StringPiece& key,
+ std::string* out) const {
+ HeaderVector::const_iterator it = FindHeader(key);
+ if (it == headers_.end())
+ return false;
+ out->assign(it->value);
+ return true;
+}
+
+void HttpRequestHeaders::Clear() {
+ headers_.clear();
+}
+
+void HttpRequestHeaders::SetHeader(const base::StringPiece& key,
+ const base::StringPiece& value) {
+ HeaderVector::iterator it = FindHeader(key);
+ if (it != headers_.end())
+ it->value = value.as_string();
+ else
+ headers_.push_back(HeaderKeyValuePair(key.as_string(), value.as_string()));
+}
+
+void HttpRequestHeaders::RemoveHeader(const base::StringPiece& key) {
+ HeaderVector::iterator it = FindHeader(key);
+ if (it != headers_.end())
+ headers_.erase(it);
+}
+
+void HttpRequestHeaders::AddHeaderFromString(
+ const base::StringPiece& header_line) {
+ DCHECK_EQ(std::string::npos, header_line.find("\r\n"))
+ << "\"" << header_line << "\" contains CRLF.";
+
+ const std::string::size_type key_end_index = header_line.find(":");
+ if (key_end_index == std::string::npos) {
+ LOG(DFATAL) << "\"" << header_line << "\" is missing colon delimiter.";
+ return;
+ }
+
+ if (key_end_index == 0) {
+ LOG(DFATAL) << "\"" << header_line << "\" is missing header key.";
+ return;
+ }
+
+ const base::StringPiece header_key(header_line.data(), key_end_index);
+
+ const std::string::size_type value_index = key_end_index + 1;
+
+ if (value_index < header_line.size()) {
+ std::string header_value(header_line.data() + value_index,
+ header_line.size() - value_index);
+ std::string::const_iterator header_value_begin =
+ header_value.begin();
+ std::string::const_iterator header_value_end =
+ header_value.end();
+ HttpUtil::TrimLWS(&header_value_begin, &header_value_end);
+
+ if (header_value_begin == header_value_end) {
+ // Value was all LWS.
+ SetHeader(header_key, "");
+ } else {
+ SetHeader(header_key,
+ base::StringPiece(&*header_value_begin,
+ header_value_end - header_value_begin));
+ }
+ } else if (value_index == header_line.size()) {
+ SetHeader(header_key, "");
+ } else {
+ NOTREACHED();
+ }
+}
+
+void HttpRequestHeaders::AddHeadersFromString(
+ const base::StringPiece& headers) {
+ // TODO(willchan): Consider adding more StringPiece support in string_util.h
+ // to eliminate copies.
+ std::vector<std::string> header_line_vector;
+ SplitStringUsingSubstr(headers.as_string(), "\r\n", &header_line_vector);
+ for (std::vector<std::string>::const_iterator it = header_line_vector.begin();
+ it != header_line_vector.end(); ++it) {
+ if (!it->empty())
+ AddHeaderFromString(*it);
+ }
+}
+
+void HttpRequestHeaders::MergeFrom(const HttpRequestHeaders& other) {
+ for (HeaderVector::const_iterator it = other.headers_.begin();
+ it != other.headers_.end(); ++it ) {
+ SetHeader(it->key, it->value);
+ }
+}
+
+std::string HttpRequestHeaders::ToString() const {
+ std::string output;
+ for (HeaderVector::const_iterator it = headers_.begin();
+ it != headers_.end(); ++it) {
+ if (!it->value.empty())
+ StringAppendF(&output, "%s: %s\r\n", it->key.c_str(), it->value.c_str());
+ else
+ StringAppendF(&output, "%s:\r\n", it->key.c_str());
+ }
+ output.append("\r\n");
+ return output;
+}
+
+HttpRequestHeaders::HeaderVector::iterator
+HttpRequestHeaders::FindHeader(const base::StringPiece& key) {
+ for (HeaderVector::iterator it = headers_.begin();
+ it != headers_.end(); ++it) {
+ if (key.length() == it->key.length() &&
+ !base::strncasecmp(key.data(), it->key.data(), key.length()))
+ return it;
+ }
+
+ return headers_.end();
+}
+
+HttpRequestHeaders::HeaderVector::const_iterator
+HttpRequestHeaders::FindHeader(const base::StringPiece& key) const {
+ for (HeaderVector::const_iterator it = headers_.begin();
+ it != headers_.end(); ++it) {
+ if (key.length() == it->key.length() &&
+ !base::strncasecmp(key.data(), it->key.data(), key.length()))
+ return it;
+ }
+
+ return headers_.end();
+}
+
+} // namespace net
diff --git a/net/http/http_request_headers.h b/net/http/http_request_headers.h
new file mode 100644
index 0000000..c1f98b6
--- /dev/null
+++ b/net/http/http_request_headers.h
@@ -0,0 +1,148 @@
+// 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.
+//
+// HttpRequestHeaders manages the request headers.
+// It maintains these in a vector of header key/value pairs, thereby maintaining
+// the order of the headers. This means that any lookups are linear time
+// operations.
+
+#ifndef NET_HTTP_HTTP_REQUEST_HEADERS_H_
+#define NET_HTTP_HTTP_REQUEST_HEADERS_H_
+
+#include <string>
+#include <vector>
+#include "base/basictypes.h"
+#include "base/string_piece.h"
+
+namespace net {
+
+class HttpRequestHeaders {
+ public:
+ struct HeaderKeyValuePair {
+ HeaderKeyValuePair() {}
+ HeaderKeyValuePair(const base::StringPiece& key,
+ const base::StringPiece& value)
+ : key(key.data(), key.size()), value(value.data(), value.size()) {}
+
+ std::string key;
+ std::string value;
+ };
+
+ typedef std::vector<HeaderKeyValuePair> HeaderVector;
+
+ class Iterator {
+ public:
+ explicit Iterator(const HttpRequestHeaders& headers);
+ ~Iterator();
+
+ // Advances the iterator to the next header, if any. Returns true if there
+ // is a next header. Use name() and value() methods to access the resultant
+ // header name and value.
+ bool GetNext();
+
+ // These two accessors are only valid if GetNext() returned true.
+ const std::string& name() const { return curr_->key; }
+ const std::string& value() const { return curr_->value; }
+
+ private:
+ bool started_;
+ HttpRequestHeaders::HeaderVector::const_iterator curr_;
+ const HttpRequestHeaders::HeaderVector::const_iterator end_;
+
+ DISALLOW_COPY_AND_ASSIGN(Iterator);
+ };
+
+ static const char kGetMethod[];
+
+ static const char kAcceptCharset[];
+ static const char kAcceptEncoding[];
+ static const char kAcceptLanguage[];
+ static const char kCacheControl[];
+ static const char kConnection[];
+ static const char kContentType[];
+ static const char kCookie[];
+ static const char kContentLength[];
+ static const char kHost[];
+ static const char kIfModifiedSince[];
+ static const char kIfNoneMatch[];
+ static const char kIfRange[];
+ static const char kOrigin[];
+ static const char kPragma[];
+ static const char kProxyConnection[];
+ static const char kRange[];
+ static const char kReferer[];
+ static const char kUserAgent[];
+
+ HttpRequestHeaders();
+ ~HttpRequestHeaders();
+
+ bool IsEmpty() const { return headers_.empty(); }
+
+ bool HasHeader(const base::StringPiece& key) const {
+ return FindHeader(key) != headers_.end();
+ }
+
+ // Gets the first header that matches |key|. If found, returns true and
+ // writes the value to |out|.
+ bool GetHeader(const base::StringPiece& key, std::string* out) const;
+
+ // Clears all the headers.
+ void Clear();
+
+ // Sets the header value pair for |key| and |value|. If |key| already exists,
+ // then the header value is modified, but the key is untouched, and the order
+ // in the vector remains the same. When comparing |key|, case is ignored.
+ void SetHeader(const base::StringPiece& key, const base::StringPiece& value);
+
+ // Removes the first header that matches (case insensitive) |key|.
+ void RemoveHeader(const base::StringPiece& key);
+
+ // Parses the header from a string and calls SetHeader() with it. This string
+ // should not contain any CRLF. As per RFC2616, the format is:
+ //
+ // message-header = field-name ":" [ field-value ]
+ // field-name = token
+ // field-value = *( field-content | LWS )
+ // field-content = <the OCTETs making up the field-value
+ // and consisting of either *TEXT or combinations
+ // of token, separators, and quoted-string>
+ //
+ // AddHeaderFromString() will trim any LWS surrounding the
+ // field-content.
+ void AddHeaderFromString(const base::StringPiece& header_line);
+
+ // Same thing as AddHeaderFromString() except that |headers| is a "\r\n"
+ // delimited string of header lines. It will split up the string by "\r\n"
+ // and call AddHeaderFromString() on each.
+ void AddHeadersFromString(const base::StringPiece& headers);
+
+ // Calls SetHeader() on each header from |other|, maintaining order.
+ void MergeFrom(const HttpRequestHeaders& other);
+
+ // Copies from |other| to |this|.
+ void CopyFrom(const HttpRequestHeaders& other) {
+ *this = other;
+ }
+
+ // Serializes HttpRequestHeaders to a string representation. Joins all the
+ // header keys and values with ": ", and inserts "\r\n" between each header
+ // line, and adds the trailing "\r\n".
+ std::string ToString() const;
+
+ private:
+ HeaderVector::iterator FindHeader(const base::StringPiece& key);
+ HeaderVector::const_iterator FindHeader(const base::StringPiece& key) const;
+
+ HeaderVector headers_;
+
+ // Allow the copy construction and operator= to facilitate copying in
+ // HttpRequestInfo.
+ // TODO(willchan): Investigate to see if we can remove the need to copy
+ // HttpRequestInfo.
+ // DISALLOW_COPY_AND_ASSIGN(HttpRequestHeaders);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_REQUEST_HEADERS_H_
diff --git a/net/http/http_request_headers_unittest.cc b/net/http/http_request_headers_unittest.cc
new file mode 100644
index 0000000..f3abfbe
--- /dev/null
+++ b/net/http/http_request_headers_unittest.cc
@@ -0,0 +1,160 @@
+// 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 "net/http/http_request_headers.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+TEST(HttpRequestHeaders, HasHeader) {
+ HttpRequestHeaders headers;
+ headers.SetHeader("Foo", "bar");
+ EXPECT_TRUE(headers.HasHeader("foo"));
+ EXPECT_TRUE(headers.HasHeader("Foo"));
+ EXPECT_FALSE(headers.HasHeader("Fo"));
+
+ const HttpRequestHeaders& headers_ref = headers;
+ EXPECT_TRUE(headers_ref.HasHeader("foo"));
+ EXPECT_TRUE(headers_ref.HasHeader("Foo"));
+ EXPECT_FALSE(headers_ref.HasHeader("Fo"));
+}
+
+TEST(HttpRequestHeaders, SetHeader) {
+ HttpRequestHeaders headers;
+ headers.SetHeader("Foo", "bar");
+ EXPECT_EQ("Foo: bar\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, SetMultipleHeaders) {
+ HttpRequestHeaders headers;
+ headers.SetHeader("Cookie-Monster", "Nom nom nom");
+ headers.SetHeader("Domo-Kun", "Loves Chrome");
+ EXPECT_EQ("Cookie-Monster: Nom nom nom\r\nDomo-Kun: Loves Chrome\r\n\r\n",
+ headers.ToString());
+}
+
+TEST(HttpRequestHeaders, SetHeaderTwice) {
+ HttpRequestHeaders headers;
+ headers.SetHeader("Foo", "bar");
+ headers.SetHeader("Foo", "bar");
+ EXPECT_EQ("Foo: bar\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, SetHeaderTwiceCaseInsensitive) {
+ HttpRequestHeaders headers;
+ headers.SetHeader("Foo", "bar");
+ headers.SetHeader("FoO", "Bar");
+ EXPECT_EQ("Foo: Bar\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, SetHeaderTwiceSamePrefix) {
+ HttpRequestHeaders headers;
+ headers.SetHeader("FooBar", "smokes");
+ headers.SetHeader("Foo", "crack");
+ EXPECT_EQ("FooBar: smokes\r\nFoo: crack\r\n\r\n", headers.ToString());
+ const HttpRequestHeaders& headers_ref = headers;
+ EXPECT_EQ("FooBar: smokes\r\nFoo: crack\r\n\r\n", headers_ref.ToString());
+}
+
+TEST(HttpRequestHeaders, SetEmptyHeader) {
+ HttpRequestHeaders headers;
+ headers.SetHeader("Foo", "Bar");
+ headers.SetHeader("Bar", "");
+ EXPECT_EQ("Foo: Bar\r\nBar:\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, RemoveHeader) {
+ HttpRequestHeaders headers;
+ headers.SetHeader("Foo", "bar");
+ headers.RemoveHeader("Foo");
+ EXPECT_EQ("\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, RemoveHeaderMissingHeader) {
+ HttpRequestHeaders headers;
+ headers.SetHeader("Foo", "bar");
+ headers.RemoveHeader("Bar");
+ EXPECT_EQ("Foo: bar\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, RemoveHeaderCaseInsensitive) {
+ HttpRequestHeaders headers;
+ headers.SetHeader("Foo", "bar");
+ headers.SetHeader("All-Your-Base", "Belongs To Chrome");
+ headers.RemoveHeader("foo");
+ EXPECT_EQ("All-Your-Base: Belongs To Chrome\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, AddHeaderFromString) {
+ HttpRequestHeaders headers;
+ headers.AddHeaderFromString("Foo: bar");
+ EXPECT_EQ("Foo: bar\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, AddHeaderFromStringNoLeadingWhitespace) {
+ HttpRequestHeaders headers;
+ headers.AddHeaderFromString("Foo:bar");
+ EXPECT_EQ("Foo: bar\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, AddHeaderFromStringMoreLeadingWhitespace) {
+ HttpRequestHeaders headers;
+ headers.AddHeaderFromString("Foo: \t \t bar");
+ EXPECT_EQ("Foo: bar\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, AddHeaderFromStringTrailingWhitespace) {
+ HttpRequestHeaders headers;
+ headers.AddHeaderFromString("Foo: bar \t \t ");
+ EXPECT_EQ("Foo: bar\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, AddHeaderFromStringLeadingTrailingWhitespace) {
+ HttpRequestHeaders headers;
+ headers.AddHeaderFromString("Foo: \t bar\t ");
+ EXPECT_EQ("Foo: bar\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, AddHeaderFromStringWithEmptyValue) {
+ HttpRequestHeaders headers;
+ headers.AddHeaderFromString("Foo:");
+ EXPECT_EQ("Foo:\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, AddHeaderFromStringWithWhitespaceValue) {
+ HttpRequestHeaders headers;
+ headers.AddHeaderFromString("Foo: ");
+ EXPECT_EQ("Foo:\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, MergeFrom) {
+ HttpRequestHeaders headers;
+ headers.SetHeader("A", "A");
+ headers.SetHeader("B", "B");
+
+ HttpRequestHeaders headers2;
+ headers2.SetHeader("B", "b");
+ headers2.SetHeader("C", "c");
+ headers.MergeFrom(headers2);
+ EXPECT_EQ("A: A\r\nB: b\r\nC: c\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, CopyFrom) {
+ HttpRequestHeaders headers;
+ headers.SetHeader("A", "A");
+ headers.SetHeader("B", "B");
+
+ HttpRequestHeaders headers2;
+ headers2.SetHeader("B", "b");
+ headers2.SetHeader("C", "c");
+ headers.CopyFrom(headers2);
+ EXPECT_EQ("B: b\r\nC: c\r\n\r\n", headers.ToString());
+}
+
+} // namespace
+
+} // namespace net
diff --git a/net/http/http_request_info.h b/net/http/http_request_info.h
index 7ada539..c36e21a 100644
--- a/net/http/http_request_info.h
+++ b/net/http/http_request_info.h
@@ -10,10 +10,11 @@
#include "googleurl/src/gurl.h"
#include "net/base/request_priority.h"
#include "net/base/upload_data.h"
+#include "net/http/http_request_headers.h"
namespace net {
-class HttpRequestInfo {
+struct HttpRequestInfo {
public:
HttpRequestInfo() : load_flags(0), priority(LOWEST) {
}
@@ -27,12 +28,8 @@
// The method to use (GET, POST, etc.).
std::string method;
- // The user agent string to use. TODO(darin): we should just add this to
- // extra_headers
- std::string user_agent;
-
- // Any extra request headers (\r\n-delimited).
- std::string extra_headers;
+ // Any extra request headers (including User-Agent).
+ HttpRequestHeaders extra_headers;
// Any upload data.
scoped_refptr<UploadData> upload_data;
diff --git a/net/http/http_response_headers.cc b/net/http/http_response_headers.cc
index 82272bb..08f397d 100644
--- a/net/http/http_response_headers.cc
+++ b/net/http/http_response_headers.cc
@@ -137,7 +137,7 @@
// Locate the start of the next header.
size_t k = i;
- while (++k < parsed_.size() && parsed_[k].is_continuation());
+ while (++k < parsed_.size() && parsed_[k].is_continuation()) {}
--k;
std::string header_name(parsed_[i].name_begin, parsed_[i].name_end);
@@ -177,7 +177,7 @@
// Locate the start of the next header.
size_t k = i;
- while (++k < new_parsed.size() && new_parsed[k].is_continuation());
+ while (++k < new_parsed.size() && new_parsed[k].is_continuation()) {}
--k;
const std::string::const_iterator& name_begin = new_parsed[i].name_begin;
@@ -208,7 +208,7 @@
// Locate the start of the next header.
size_t k = i;
- while (++k < parsed_.size() && parsed_[k].is_continuation());
+ while (++k < parsed_.size() && parsed_[k].is_continuation()) {}
--k;
std::string name(parsed_[i].name_begin, parsed_[i].name_end);
@@ -274,8 +274,9 @@
find(line_begin, raw_input.end(), '\0');
// has_headers = true, if there is any data following the status line.
// Used by ParseStatusLine() to decide if a HTTP/0.9 is really a HTTP/1.0.
- bool has_headers = line_end != raw_input.end() &&
- (line_end + 1) != raw_input.end() && *(line_end + 1) != '\0';
+ bool has_headers = (line_end != raw_input.end() &&
+ (line_end + 1) != raw_input.end() &&
+ *(line_end + 1) != '\0');
ParseStatusLine(line_begin, line_end, has_headers);
if (line_end == raw_input.end()) {
@@ -357,6 +358,30 @@
output->push_back('\n');
}
+void HttpResponseHeaders::GetRawHeaders(std::string* output) const {
+ if (!output)
+ return;
+ output->erase();
+ const char* headers_string = raw_headers().c_str();
+ size_t headers_length = raw_headers().length();
+ if (!headers_string)
+ return;
+ // The headers_string is a NULL-terminated status line, followed by NULL-
+ // terminated headers.
+ std::string raw_string = headers_string;
+ size_t current_length = strlen(headers_string) + 1;
+ while (headers_length > current_length) {
+ // Move to the next header, and append it.
+ headers_string += current_length;
+ headers_length -= current_length;
+ raw_string += "\n";
+ raw_string += headers_string;
+ // Get the next header location.
+ current_length = strlen(headers_string) + 1;
+ }
+ *output = raw_string;
+}
+
bool HttpResponseHeaders::GetNormalizedHeader(const std::string& name,
std::string* value) const {
// If you hit this assertion, please use EnumerateHeader instead!
@@ -464,6 +489,10 @@
return false;
}
+bool HttpResponseHeaders::HasHeader(const std::string& name) const {
+ return FindHeader(0, name) != std::string::npos;
+}
+
// Note: this implementation implicitly assumes that line_end points at a valid
// sentinel character (such as '\0').
// static
@@ -1115,9 +1144,9 @@
// Obtain last-byte-pos.
std::string::const_iterator last_byte_pos_begin =
- byte_range_resp_spec.begin() + minus_position + 1;
+ byte_range_resp_spec.begin() + minus_position + 1;
std::string::const_iterator last_byte_pos_end =
- byte_range_resp_spec.end();
+ byte_range_resp_spec.end();
HttpUtil::TrimLWS(&last_byte_pos_begin, &last_byte_pos_end);
ok &= StringToInt64(
@@ -1146,8 +1175,8 @@
if (LowerCaseEqualsASCII(instance_length_begin, instance_length_end, "*")) {
return false;
} else if (!StringToInt64(
- std::string(instance_length_begin, instance_length_end),
- instance_length)) {
+ std::string(instance_length_begin, instance_length_end),
+ instance_length)) {
*instance_length = -1;
return false;
}
diff --git a/net/http/http_response_headers.h b/net/http/http_response_headers.h
index f6656e3..30e43ea 100644
--- a/net/http/http_response_headers.h
+++ b/net/http/http_response_headers.h
@@ -99,6 +99,9 @@
//
void GetNormalizedHeaders(std::string* output) const;
+ // Gets the raw stored headers, in human-readable form.
+ void GetRawHeaders(std::string* output) const;
+
// Fetch the "normalized" value of a single header, where all values for the
// header name are separated by commas. See the GetNormalizedHeaders for
// format details. Returns false if this header wasn't found.
@@ -154,6 +157,10 @@
// Both name and value are compared case insensitively.
bool HasHeaderValue(const std::string& name, const std::string& value) const;
+ // Returns true if the response contains the specified header.
+ // The name is compared case insensitively.
+ bool HasHeader(const std::string& name) const;
+
// Get the mime type and charset values in lower case form from the headers.
// Empty strings are returned if the values are not present.
void GetMimeTypeAndCharset(std::string* mime_type,
diff --git a/net/http/http_response_headers_unittest.cc b/net/http/http_response_headers_unittest.cc
index bea4f36..18cd1c4 100644
--- a/net/http/http_response_headers_unittest.cc
+++ b/net/http/http_response_headers_unittest.cc
@@ -277,9 +277,9 @@
TEST(HttpResponseHeadersTest, GetNormalizedHeader) {
std::string headers =
- "HTTP/1.1 200 OK\n"
- "Cache-control: private\n"
- "cache-Control: no-store\n";
+ "HTTP/1.1 200 OK\n"
+ "Cache-control: private\n"
+ "cache-Control: no-store\n";
HeadersToRaw(&headers);
scoped_refptr<HttpResponseHeaders> parsed = new HttpResponseHeaders(headers);
@@ -468,9 +468,9 @@
// Ensure that commas in quoted strings are not regarded as value separators.
// Ensure that whitespace following a value is trimmed properly
std::string headers =
- "HTTP/1.1 200 OK\n"
- "Cache-control:private , no-cache=\"set-cookie,server\" \n"
- "cache-Control: no-store\n";
+ "HTTP/1.1 200 OK\n"
+ "Cache-control:private , no-cache=\"set-cookie,server\" \n"
+ "cache-Control: no-store\n";
HeadersToRaw(&headers);
scoped_refptr<HttpResponseHeaders> parsed = new HttpResponseHeaders(headers);
@@ -489,9 +489,9 @@
// Even though WWW-Authenticate has commas, it should not be treated as
// coalesced values.
std::string headers =
- "HTTP/1.1 401 OK\n"
- "WWW-Authenticate:Digest realm=foobar, nonce=x, domain=y\n"
- "WWW-Authenticate:Basic realm=quatar\n";
+ "HTTP/1.1 401 OK\n"
+ "WWW-Authenticate:Digest realm=foobar, nonce=x, domain=y\n"
+ "WWW-Authenticate:Basic realm=quatar\n";
HeadersToRaw(&headers);
scoped_refptr<HttpResponseHeaders> parsed = new HttpResponseHeaders(headers);
@@ -508,9 +508,9 @@
// The comma in a date valued header should not be treated as a
// field-value separator
std::string headers =
- "HTTP/1.1 200 OK\n"
- "Date: Tue, 07 Aug 2007 23:10:55 GMT\n"
- "Last-Modified: Wed, 01 Aug 2007 23:23:45 GMT\n";
+ "HTTP/1.1 200 OK\n"
+ "Date: Tue, 07 Aug 2007 23:10:55 GMT\n"
+ "Last-Modified: Wed, 01 Aug 2007 23:23:45 GMT\n";
HeadersToRaw(&headers);
scoped_refptr<HttpResponseHeaders> parsed = new HttpResponseHeaders(headers);
@@ -524,130 +524,130 @@
TEST(HttpResponseHeadersTest, GetMimeType) {
const ContentTypeTestData tests[] = {
{ "HTTP/1.1 200 OK\n"
- "Content-type: text/html\n",
+ "Content-type: text/html\n",
"text/html", true,
"", false,
"text/html" },
// Multiple content-type headers should give us the last one.
{ "HTTP/1.1 200 OK\n"
- "Content-type: text/html\n"
- "Content-type: text/html\n",
+ "Content-type: text/html\n"
+ "Content-type: text/html\n",
"text/html", true,
"", false,
"text/html, text/html" },
{ "HTTP/1.1 200 OK\n"
- "Content-type: text/plain\n"
- "Content-type: text/html\n"
- "Content-type: text/plain\n"
- "Content-type: text/html\n",
+ "Content-type: text/plain\n"
+ "Content-type: text/html\n"
+ "Content-type: text/plain\n"
+ "Content-type: text/html\n",
"text/html", true,
"", false,
"text/plain, text/html, text/plain, text/html" },
// Test charset parsing.
{ "HTTP/1.1 200 OK\n"
- "Content-type: text/html\n"
- "Content-type: text/html; charset=ISO-8859-1\n",
+ "Content-type: text/html\n"
+ "Content-type: text/html; charset=ISO-8859-1\n",
"text/html", true,
"iso-8859-1", true,
"text/html, text/html; charset=ISO-8859-1" },
// Test charset in double quotes.
{ "HTTP/1.1 200 OK\n"
- "Content-type: text/html\n"
- "Content-type: text/html; charset=\"ISO-8859-1\"\n",
+ "Content-type: text/html\n"
+ "Content-type: text/html; charset=\"ISO-8859-1\"\n",
"text/html", true,
"iso-8859-1", true,
"text/html, text/html; charset=\"ISO-8859-1\"" },
// If there are multiple matching content-type headers, we carry
// over the charset value.
{ "HTTP/1.1 200 OK\n"
- "Content-type: text/html;charset=utf-8\n"
- "Content-type: text/html\n",
+ "Content-type: text/html;charset=utf-8\n"
+ "Content-type: text/html\n",
"text/html", true,
"utf-8", true,
"text/html;charset=utf-8, text/html" },
// Test single quotes.
{ "HTTP/1.1 200 OK\n"
- "Content-type: text/html;charset='utf-8'\n"
- "Content-type: text/html\n",
+ "Content-type: text/html;charset='utf-8'\n"
+ "Content-type: text/html\n",
"text/html", true,
"utf-8", true,
"text/html;charset='utf-8', text/html" },
// Last charset wins if matching content-type.
{ "HTTP/1.1 200 OK\n"
- "Content-type: text/html;charset=utf-8\n"
- "Content-type: text/html;charset=iso-8859-1\n",
+ "Content-type: text/html;charset=utf-8\n"
+ "Content-type: text/html;charset=iso-8859-1\n",
"text/html", true,
"iso-8859-1", true,
"text/html;charset=utf-8, text/html;charset=iso-8859-1" },
// Charset is ignored if the content types change.
{ "HTTP/1.1 200 OK\n"
- "Content-type: text/plain;charset=utf-8\n"
- "Content-type: text/html\n",
+ "Content-type: text/plain;charset=utf-8\n"
+ "Content-type: text/html\n",
"text/html", true,
"", false,
"text/plain;charset=utf-8, text/html" },
// Empty content-type
{ "HTTP/1.1 200 OK\n"
- "Content-type: \n",
+ "Content-type: \n",
"", false,
"", false,
"" },
// Emtpy charset
{ "HTTP/1.1 200 OK\n"
- "Content-type: text/html;charset=\n",
+ "Content-type: text/html;charset=\n",
"text/html", true,
"", false,
"text/html;charset=" },
// Multiple charsets, last one wins.
{ "HTTP/1.1 200 OK\n"
- "Content-type: text/html;charset=utf-8; charset=iso-8859-1\n",
+ "Content-type: text/html;charset=utf-8; charset=iso-8859-1\n",
"text/html", true,
"iso-8859-1", true,
"text/html;charset=utf-8; charset=iso-8859-1" },
// Multiple params.
{ "HTTP/1.1 200 OK\n"
- "Content-type: text/html; foo=utf-8; charset=iso-8859-1\n",
+ "Content-type: text/html; foo=utf-8; charset=iso-8859-1\n",
"text/html", true,
"iso-8859-1", true,
"text/html; foo=utf-8; charset=iso-8859-1" },
{ "HTTP/1.1 200 OK\n"
- "Content-type: text/html ; charset=utf-8 ; bar=iso-8859-1\n",
+ "Content-type: text/html ; charset=utf-8 ; bar=iso-8859-1\n",
"text/html", true,
"utf-8", true,
"text/html ; charset=utf-8 ; bar=iso-8859-1" },
// Comma embeded in quotes.
{ "HTTP/1.1 200 OK\n"
- "Content-type: text/html ; charset='utf-8,text/plain' ;\n",
+ "Content-type: text/html ; charset='utf-8,text/plain' ;\n",
"text/html", true,
"utf-8,text/plain", true,
"text/html ; charset='utf-8,text/plain' ;" },
// Charset with leading spaces.
{ "HTTP/1.1 200 OK\n"
- "Content-type: text/html ; charset= 'utf-8' ;\n",
+ "Content-type: text/html ; charset= 'utf-8' ;\n",
"text/html", true,
"utf-8", true,
"text/html ; charset= 'utf-8' ;" },
// Media type comments in mime-type.
{ "HTTP/1.1 200 OK\n"
- "Content-type: text/html (html)\n",
+ "Content-type: text/html (html)\n",
"text/html", true,
"", false,
"text/html (html)" },
// Incomplete charset= param
{ "HTTP/1.1 200 OK\n"
- "Content-type: text/html; char=\n",
+ "Content-type: text/html; char=\n",
"text/html", true,
"", false,
"text/html; char=" },
// Invalid media type: no slash
{ "HTTP/1.1 200 OK\n"
- "Content-type: texthtml\n",
+ "Content-type: texthtml\n",
"", false,
"", false,
"texthtml" },
// Invalid media type: */*
{ "HTTP/1.1 200 OK\n"
- "Content-type: */*\n",
+ "Content-type: */*\n",
"", false,
"", false,
"*/*" },
@@ -1477,7 +1477,7 @@
new HttpResponseHeaders(headers);
EXPECT_EQ(tests[i].expected_result, parsed->HasStrongValidators()) <<
- "Failed test case " << i;
+ "Failed test case " << i;
}
}
diff --git a/net/http/http_response_info.cc b/net/http/http_response_info.cc
index 1a548b1..0159caf 100644
--- a/net/http/http_response_info.cc
+++ b/net/http/http_response_info.cc
@@ -41,13 +41,26 @@
// This bit is set if the response was received via SPDY.
RESPONSE_INFO_WAS_SPDY = 1 << 13,
+ // This bit is set if the request has NPN negotiated.
+ RESPONSE_INFO_WAS_NPN = 1 << 14,
+
+ // This bit is set if the request was fetched via an explicit proxy.
+ RESPONSE_INFO_WAS_PROXY = 1 << 15,
+
+ // This bit is set if response could use alternate protocol. However, browser
+ // will ingore the alternate protocol if spdy is not enabled.
+ RESPONSE_INFO_WAS_ALTERNATE_PROTOCOL_AVAILABLE = 1 << 16,
+
// TODO(darin): Add other bits to indicate alternate request methods.
// For now, we don't support storing those.
};
HttpResponseInfo::HttpResponseInfo()
: was_cached(false),
- was_fetched_via_spdy(false) {
+ was_fetched_via_spdy(false),
+ was_npn_negotiated(false),
+ was_alternate_protocol_available(false),
+ was_fetched_via_proxy(false) {
}
HttpResponseInfo::~HttpResponseInfo() {
@@ -109,6 +122,13 @@
was_fetched_via_spdy = (flags & RESPONSE_INFO_WAS_SPDY) != 0;
+ was_npn_negotiated = (flags & RESPONSE_INFO_WAS_NPN) != 0;
+
+ was_alternate_protocol_available =
+ (flags & RESPONSE_INFO_WAS_ALTERNATE_PROTOCOL_AVAILABLE) != 0;
+
+ was_fetched_via_proxy = (flags & RESPONSE_INFO_WAS_PROXY) != 0;
+
*response_truncated = (flags & RESPONSE_INFO_TRUNCATED) ? true : false;
return true;
@@ -130,6 +150,12 @@
flags |= RESPONSE_INFO_TRUNCATED;
if (was_fetched_via_spdy)
flags |= RESPONSE_INFO_WAS_SPDY;
+ if (was_npn_negotiated)
+ flags |= RESPONSE_INFO_WAS_NPN;
+ if (was_alternate_protocol_available)
+ flags |= RESPONSE_INFO_WAS_ALTERNATE_PROTOCOL_AVAILABLE;
+ if (was_fetched_via_proxy)
+ flags |= RESPONSE_INFO_WAS_PROXY;
pickle->WriteInt(flags);
pickle->WriteInt64(request_time.ToInternalValue());
diff --git a/net/http/http_response_info.h b/net/http/http_response_info.h
index a3e0123..52c3a0e 100644
--- a/net/http/http_response_info.h
+++ b/net/http/http_response_info.h
@@ -7,6 +7,7 @@
#include "base/time.h"
#include "net/base/auth.h"
+#include "net/base/io_buffer.h"
#include "net/base/ssl_cert_request_info.h"
#include "net/base/ssl_info.h"
#include "net/http/http_response_headers.h"
@@ -27,13 +28,25 @@
// request_time may corresponds to a time "far" in the past. Note that
// stale content (perhaps un-cacheable) may be fetched from cache subject to
// the load flags specified on the request info. For example, this is done
- // when a user presses the back button to re-render pages, or at startup, when
- // reloading previously visited pages (without going over the network).
+ // when a user presses the back button to re-render pages, or at startup,
+ // when reloading previously visited pages (without going over the network).
bool was_cached;
// True if the request was fetched over a SPDY channel.
bool was_fetched_via_spdy;
+ // True if the npn was negotiated for this request.
+ bool was_npn_negotiated;
+
+ // True if response could use alternate protocol. However, browser
+ // will ingore the alternate protocol if spdy is not enabled.
+ bool was_alternate_protocol_available;
+
+ // True if the request was fetched via an explicit proxy. The proxy could
+ // be any type of proxy, HTTP or SOCKS. Note, we do not know if a
+ // transparent proxy may have been involved.
+ bool was_fetched_via_proxy;
+
// The time at which the request was made that resulted in this response.
// For cached responses, this is the last time the cache entry was validated.
base::Time request_time;
@@ -61,6 +74,9 @@
// The "Vary" header data for this response.
HttpVaryData vary_data;
+ // Any metadata asociated with this resource's cached data.
+ scoped_refptr<IOBufferWithSize> metadata;
+
// Initializes from the representation stored in the given pickle.
bool InitFromPickle(const Pickle& pickle, bool* response_truncated);
diff --git a/net/http/http_stream.h b/net/http/http_stream.h
index 49651dd..de2a8d7 100644
--- a/net/http/http_stream.h
+++ b/net/http/http_stream.h
@@ -13,11 +13,11 @@
#include <string>
#include "base/basictypes.h"
-#include "net/socket/client_socket_handle.h"
+#include "net/base/completion_callback.h"
namespace net {
-class HttpRequestInfo;
+struct HttpRequestInfo;
class HttpResponseInfo;
class IOBuffer;
class UploadDataStream;
@@ -53,12 +53,14 @@
// Reads response body data, up to |buf_len| bytes. |buf_len| should be a
// reasonable size (<2MB). The number of bytes read is returned, or an
- // error is returned upon failure. ERR_CONNECTION_CLOSED is returned to
- // indicate end-of-connection. ERR_IO_PENDING is returned if the operation
- // could not be completed synchronously, in which case the result will be
- // passed to the callback when available. If the operation is not completed
- // immediately, the socket acquires a reference to the provided buffer until
- // the callback is invoked or the socket is destroyed.
+ // error is returned upon failure. 0 indicates that the request has been
+ // fully satisfied and there is no more data to read.
+ // ERR_CONNECTION_CLOSED is returned when the connection has been closed
+ // prematurely. ERR_IO_PENDING is returned if the operation could not be
+ // completed synchronously, in which case the result will be passed to the
+ // callback when available. If the operation is not completed immediately,
+ // the socket acquires a reference to the provided buffer until the callback
+ // is invoked or the socket is destroyed.
virtual int ReadResponseBody(IOBuffer* buf, int buf_len,
CompletionCallback* callback) = 0;
diff --git a/net/http/http_stream_parser.cc b/net/http/http_stream_parser.cc
index abbf112..1426957 100644
--- a/net/http/http_stream_parser.cc
+++ b/net/http/http_stream_parser.cc
@@ -1,11 +1,11 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// 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 "net/http/http_stream_parser.h"
#include "base/compiler_specific.h"
-#include "base/trace_event.h"
+#include "base/histogram.h"
#include "net/base/io_buffer.h"
#include "net/http/http_request_info.h"
#include "net/http/http_response_headers.h"
@@ -15,7 +15,7 @@
HttpStreamParser::HttpStreamParser(ClientSocketHandle* connection,
GrowableIOBuffer* read_buffer,
- LoadLog* load_log)
+ const BoundNetLog& net_log)
: io_state_(STATE_NONE),
request_(NULL),
request_headers_(NULL),
@@ -30,12 +30,14 @@
user_read_buf_len_(0),
user_callback_(NULL),
connection_(connection),
- load_log_(load_log),
+ net_log_(net_log),
ALLOW_THIS_IN_INITIALIZER_LIST(
io_callback_(this, &HttpStreamParser::OnIOComplete)) {
DCHECK_EQ(0, read_buffer->offset());
}
+HttpStreamParser::~HttpStreamParser() {}
+
int HttpStreamParser::SendRequest(const HttpRequestInfo* request,
const std::string& headers,
UploadDataStream* request_body,
@@ -90,7 +92,7 @@
}
int HttpStreamParser::ReadResponseBody(IOBuffer* buf, int buf_len,
- CompletionCallback* callback) {
+ CompletionCallback* callback) {
DCHECK(io_state_ == STATE_BODY_PENDING || io_state_ == STATE_DONE);
DCHECK(!user_callback_);
DCHECK(callback);
@@ -127,49 +129,39 @@
do {
switch (io_state_) {
case STATE_SENDING_HEADERS:
- TRACE_EVENT_BEGIN("http.write_headers", request_, request_->url.spec());
if (result < 0)
can_do_more = false;
else
result = DoSendHeaders(result);
- TRACE_EVENT_END("http.write_headers", request_, request_->url.spec());
break;
case STATE_SENDING_BODY:
- TRACE_EVENT_BEGIN("http.write_body", request_, request_->url.spec());
if (result < 0)
can_do_more = false;
else
result = DoSendBody(result);
- TRACE_EVENT_END("http.write_body", request_, request_->url.spec());
break;
case STATE_REQUEST_SENT:
DCHECK(result != ERR_IO_PENDING);
can_do_more = false;
break;
case STATE_READ_HEADERS:
- TRACE_EVENT_BEGIN("http.read_headers", request_, request_->url.spec());
- LoadLog::BeginEvent(load_log_,
- LoadLog::TYPE_HTTP_STREAM_PARSER_READ_HEADERS);
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_STREAM_PARSER_READ_HEADERS, NULL);
result = DoReadHeaders();
break;
case STATE_READ_HEADERS_COMPLETE:
result = DoReadHeadersComplete(result);
- LoadLog::EndEvent(load_log_,
- LoadLog::TYPE_HTTP_STREAM_PARSER_READ_HEADERS);
- TRACE_EVENT_END("http.read_headers", request_, request_->url.spec());
+ net_log_.EndEvent(NetLog::TYPE_HTTP_STREAM_PARSER_READ_HEADERS, NULL);
break;
case STATE_BODY_PENDING:
DCHECK(result != ERR_IO_PENDING);
can_do_more = false;
break;
case STATE_READ_BODY:
- TRACE_EVENT_BEGIN("http.read_body", request_, request_->url.spec());
result = DoReadBody();
// DoReadBodyComplete handles error conditions.
break;
case STATE_READ_BODY_COMPLETE:
result = DoReadBodyComplete(result);
- TRACE_EVENT_END("http.read_body", request_, request_->url.spec());
break;
case STATE_DONE:
DCHECK(result != ERR_IO_PENDING);
@@ -187,15 +179,43 @@
int HttpStreamParser::DoSendHeaders(int result) {
request_headers_->DidConsume(result);
-
- if (request_headers_->BytesRemaining() > 0) {
+ int bytes_remaining = request_headers_->BytesRemaining();
+ if (bytes_remaining > 0) {
// Record our best estimate of the 'request time' as the time when we send
// out the first bytes of the request headers.
- if (request_headers_->BytesRemaining() == request_headers_->size()) {
+ if (bytes_remaining == request_headers_->size()) {
response_->request_time = base::Time::Now();
+
+ // We'll record the count of uncoalesced packets IFF coalescing will help,
+ // and otherwise we'll use an enum to tell why it won't help.
+ enum COALESCE_POTENTIAL {
+ NO_ADVANTAGE = 0, // Coalescing won't reduce packet count.
+ HEADER_ONLY = 1, // There is only a header packet (can't coalesce).
+ COALESCE_POTENTIAL_MAX = 30 // Various cases of coalasced savings.
+ };
+ size_t coalesce = HEADER_ONLY;
+ if (request_body_ != NULL) {
+ const size_t kBytesPerPacket = 1430;
+ uint64 body_packets = (request_body_->size() + kBytesPerPacket - 1) /
+ kBytesPerPacket;
+ uint64 header_packets = (bytes_remaining + kBytesPerPacket - 1) /
+ kBytesPerPacket;
+ uint64 coalesced_packets = (request_body_->size() + bytes_remaining +
+ kBytesPerPacket - 1) / kBytesPerPacket;
+ if (coalesced_packets < header_packets + body_packets) {
+ if (coalesced_packets > COALESCE_POTENTIAL_MAX)
+ coalesce = COALESCE_POTENTIAL_MAX;
+ else
+ coalesce = static_cast<size_t>(header_packets + body_packets);
+ } else {
+ coalesce = NO_ADVANTAGE;
+ }
+ }
+ UMA_HISTOGRAM_ENUMERATION("Net.CoalescePotential", coalesce,
+ COALESCE_POTENTIAL_MAX);
}
result = connection_->socket()->Write(request_headers_,
- request_headers_->BytesRemaining(),
+ bytes_remaining,
&io_callback_);
} else if (request_body_ != NULL && request_body_->size()) {
io_state_ = STATE_SENDING_BODY;
@@ -210,7 +230,7 @@
if (result > 0)
request_body_->DidConsume(result);
- if (request_body_->position() < request_body_->size()) {
+ if (!request_body_->eof()) {
int buf_len = static_cast<int>(request_body_->buf_len());
result = connection_->socket()->Write(request_body_->buf(), buf_len,
&io_callback_);
@@ -310,7 +330,7 @@
io_state_ = STATE_DONE;
int extra_bytes = read_buf_->offset() - read_buf_unused_offset_;
if (extra_bytes) {
- CHECK(extra_bytes > 0);
+ CHECK_GT(extra_bytes, 0);
memmove(read_buf_->StartOfBuffer(),
read_buf_->StartOfBuffer() + read_buf_unused_offset_,
extra_bytes);
@@ -331,7 +351,7 @@
if (read_buf_->offset()) {
int available = read_buf_->offset() - read_buf_unused_offset_;
if (available) {
- CHECK(available > 0);
+ CHECK_GT(available, 0);
int bytes_from_buffer = std::min(available, user_read_buf_len_);
memcpy(user_read_buf_->data(),
read_buf_->StartOfBuffer() + read_buf_unused_offset_,
@@ -358,7 +378,11 @@
}
int HttpStreamParser::DoReadBodyComplete(int result) {
- if (result == 0)
+ // If we didn't get a content-length and aren't using a chunked encoding,
+ // the only way to signal the end of a stream is to close the connection,
+ // so we don't treat that as an error, though in some cases we may not
+ // have completely received the resource.
+ if (result == 0 && !IsResponseBodyComplete() && CanFindEndOfResponse())
result = ERR_CONNECTION_CLOSED;
// Filter incoming data if appropriate. FilterBuf may return an error.
@@ -375,7 +399,7 @@
if (result > 0)
response_body_read_ += result;
- if (result < 0 || IsResponseBodyComplete()) {
+ if (result <= 0 || IsResponseBodyComplete()) {
io_state_ = STATE_DONE;
// Save the overflow data, which can be in two places. There may be
@@ -396,7 +420,7 @@
}
}
- CHECK(save_amount + additional_save_amount <= kMaxBufSize);
+ CHECK_LE(save_amount + additional_save_amount, kMaxBufSize);
if (read_buf_->capacity() < save_amount + additional_save_amount) {
read_buf_->SetCapacity(save_amount + additional_save_amount);
}
diff --git a/net/http/http_stream_parser.h b/net/http/http_stream_parser.h
index c47d09e..44cc9e8 100644
--- a/net/http/http_stream_parser.h
+++ b/net/http/http_stream_parser.h
@@ -9,7 +9,7 @@
#include "base/basictypes.h"
#include "net/base/io_buffer.h"
-#include "net/base/load_log.h"
+#include "net/base/net_log.h"
#include "net/base/upload_data_stream.h"
#include "net/http/http_chunked_decoder.h"
#include "net/http/http_response_info.h"
@@ -18,7 +18,7 @@
namespace net {
class ClientSocketHandle;
-class HttpRequestInfo;
+struct HttpRequestInfo;
class HttpStreamParser {
public:
@@ -29,8 +29,8 @@
// have its capacity changed.
HttpStreamParser(ClientSocketHandle* connection,
GrowableIOBuffer* read_buffer,
- LoadLog* load_log);
- ~HttpStreamParser() {}
+ const BoundNetLog& net_log);
+ ~HttpStreamParser();
// These functions implement the interface described in HttpStream with
// some additional functionality
@@ -162,7 +162,7 @@
// The underlying socket.
ClientSocketHandle* const connection_;
- scoped_refptr<LoadLog> load_log_;
+ BoundNetLog net_log_;
// Callback to be used when doing IO.
CompletionCallbackImpl<HttpStreamParser> io_callback_;
diff --git a/net/http/http_transaction.h b/net/http/http_transaction.h
index 9dfd658..103f8f6 100644
--- a/net/http/http_transaction.h
+++ b/net/http/http_transaction.h
@@ -5,15 +5,17 @@
#ifndef NET_HTTP_HTTP_TRANSACTION_H_
#define NET_HTTP_HTTP_TRANSACTION_H_
+#include <string>
+
#include "net/base/completion_callback.h"
#include "net/base/load_states.h"
namespace net {
-class HttpRequestInfo;
+class BoundNetLog;
+struct HttpRequestInfo;
class HttpResponseInfo;
class IOBuffer;
-class LoadLog;
class X509Certificate;
// Represents a single HTTP transaction (i.e., a single request/response pair).
@@ -37,10 +39,10 @@
//
// NOTE: The transaction is not responsible for deleting the callback object.
//
- // Profiling information for the request is saved to |load_log| if non-NULL.
+ // Profiling information for the request is saved to |net_log| if non-NULL.
virtual int Start(const HttpRequestInfo* request_info,
CompletionCallback* callback,
- LoadLog* load_log) = 0;
+ const BoundNetLog& net_log) = 0;
// Restarts the HTTP transaction, ignoring the last error. This call can
// only be made after a call to Start (or RestartIgnoringLastError) failed.
@@ -89,6 +91,9 @@
virtual int Read(IOBuffer* buf, int buf_len,
CompletionCallback* callback) = 0;
+ // Stops further caching of this request by the HTTP cache, if there is any.
+ virtual void StopCaching() = 0;
+
// Returns the response info for this transaction or NULL if the response
// info is not available.
virtual const HttpResponseInfo* GetResponseInfo() const = 0;
diff --git a/net/http/http_transaction_unittest.cc b/net/http/http_transaction_unittest.cc
index 01e6ea5..4d9daa5 100644
--- a/net/http/http_transaction_unittest.cc
+++ b/net/http/http_transaction_unittest.cc
@@ -105,7 +105,7 @@
};
typedef base::hash_map<std::string, const MockTransaction*>
- MockTransactionMap;
+MockTransactionMap;
static MockTransactionMap mock_transactions;
void AddMockTransaction(const MockTransaction* trans) {
diff --git a/net/http/http_transaction_unittest.h b/net/http/http_transaction_unittest.h
index cffde7a..e550903 100644
--- a/net/http/http_transaction_unittest.h
+++ b/net/http/http_transaction_unittest.h
@@ -10,6 +10,7 @@
#include <algorithm>
#include <string>
+#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/message_loop.h"
#include "base/string_util.h"
@@ -34,9 +35,9 @@
TEST_MODE_SYNC_CACHE_START = 1 << 2,
TEST_MODE_SYNC_CACHE_READ = 1 << 3,
TEST_MODE_SYNC_CACHE_WRITE = 1 << 4,
- TEST_MODE_SYNC_ALL = TEST_MODE_SYNC_NET_START | TEST_MODE_SYNC_NET_READ |
- TEST_MODE_SYNC_CACHE_START | TEST_MODE_SYNC_CACHE_READ |
- TEST_MODE_SYNC_CACHE_WRITE
+ TEST_MODE_SYNC_ALL = (TEST_MODE_SYNC_NET_START | TEST_MODE_SYNC_NET_READ |
+ TEST_MODE_SYNC_CACHE_START | TEST_MODE_SYNC_CACHE_READ |
+ TEST_MODE_SYNC_CACHE_WRITE)
};
typedef void (*MockTransactionHandler)(const net::HttpRequestInfo* request,
@@ -96,7 +97,7 @@
explicit MockHttpRequest(const MockTransaction& t) {
url = GURL(t.url);
method = t.method;
- extra_headers = t.request_headers;
+ extra_headers.AddHeadersFromString(t.request_headers);
load_flags = t.load_flags;
}
};
@@ -118,9 +119,10 @@
~TestTransactionConsumer() {
}
- void Start(const net::HttpRequestInfo* request, net::LoadLog* load_log) {
+ void Start(const net::HttpRequestInfo* request,
+ const net::BoundNetLog& net_log) {
state_ = STARTING;
- int result = trans_->Start(request, this, load_log);
+ int result = trans_->Start(request, this, net_log);
if (result != net::ERR_IO_PENDING)
DidStart(result);
}
@@ -211,7 +213,7 @@
virtual int Start(const net::HttpRequestInfo* request,
net::CompletionCallback* callback,
- net::LoadLog* load_log) {
+ const net::BoundNetLog& net_log) {
const MockTransaction* t = FindMockTransaction(request->url);
if (!t)
return net::ERR_FAILED;
@@ -282,12 +284,15 @@
return net::ERR_IO_PENDING;
}
+ virtual void StopCaching() {}
+
virtual const net::HttpResponseInfo* GetResponseInfo() const {
return &response_;
}
virtual net::LoadState GetLoadState() const {
- NOTREACHED() << "define some mock state transitions";
+ if (data_cursor_)
+ return net::LOAD_STATE_READING_RESPONSE;
return net::LOAD_STATE_IDLE;
}
diff --git a/net/http/http_util.cc b/net/http/http_util.cc
index 5ea46d0..8168102 100644
--- a/net/http/http_util.cc
+++ b/net/http/http_util.cc
@@ -112,15 +112,16 @@
size_t cur_param_end =
FindDelimiter(content_type_str, cur_param_start, ';');
- size_t param_name_start = content_type_str.find_first_not_of(HTTP_LWS,
- cur_param_start);
+ size_t param_name_start = content_type_str.find_first_not_of(
+ HTTP_LWS, cur_param_start);
param_name_start = std::min(param_name_start, cur_param_end);
static const char charset_str[] = "charset=";
- size_t charset_end_offset = std::min(param_name_start +
- sizeof(charset_str) - 1, cur_param_end);
- if (LowerCaseEqualsASCII(content_type_str.begin() + param_name_start,
- content_type_str.begin() + charset_end_offset, charset_str)) {
+ size_t charset_end_offset = std::min(
+ param_name_start + sizeof(charset_str) - 1, cur_param_end);
+ if (LowerCaseEqualsASCII(
+ content_type_str.begin() + param_name_start,
+ content_type_str.begin() + charset_end_offset, charset_str)) {
charset_val = param_name_start + sizeof(charset_str) - 1;
charset_end = cur_param_end;
type_has_charset = true;
@@ -160,9 +161,9 @@
content_type_str.find_first_of('/') != string::npos) {
// Common case here is that mime_type is empty
bool eq = !mime_type->empty() &&
- LowerCaseEqualsASCII(content_type_str.begin() + type_val,
- content_type_str.begin() + type_end,
- mime_type->data());
+ LowerCaseEqualsASCII(content_type_str.begin() + type_val,
+ content_type_str.begin() + type_end,
+ mime_type->data());
if (!eq) {
mime_type->assign(content_type_str.begin() + type_val,
content_type_str.begin() + type_end);
@@ -202,6 +203,12 @@
if (ranges_specifier.empty())
return false;
+ return ParseRangeHeader(ranges_specifier, ranges);
+}
+
+// static
+bool HttpUtil::ParseRangeHeader(const std::string& ranges_specifier,
+ std::vector<HttpByteRange>* ranges) {
size_t equal_char_offset = ranges_specifier.find('=');
if (equal_char_offset == std::string::npos)
return false;
@@ -236,11 +243,11 @@
HttpByteRange range;
// Try to obtain first-byte-pos.
if (!first_byte_pos.empty()) {
- int64 first_byte_position = -1;
- if (!StringToInt64(first_byte_pos, &first_byte_position))
- return false;
- range.set_first_byte_position(first_byte_position);
- }
+ int64 first_byte_position = -1;
+ if (!StringToInt64(first_byte_pos, &first_byte_position))
+ return false;
+ range.set_first_byte_position(first_byte_position);
+ }
std::string::const_iterator last_byte_pos_begin =
byte_range_set_iterator.value_begin() + minus_char_offset + 1;
diff --git a/net/http/http_util.h b/net/http/http_util.h
index c630cfe..447e490 100644
--- a/net/http/http_util.h
+++ b/net/http/http_util.h
@@ -19,14 +19,14 @@
class HttpUtil {
public:
- // Returns the absolute path of the URL, to be used for the http request.
- // The absolute path starts with a '/' and may contain a query.
- static std::string PathForRequest(const GURL& url);
+ // Returns the absolute path of the URL, to be used for the http request.
+ // The absolute path starts with a '/' and may contain a query.
+ static std::string PathForRequest(const GURL& url);
- // Returns the absolute URL, to be used for the http request. This url is
- // made up of the protocol, host, [port], path, [query]. Everything else
- // is stripped (username, password, reference).
- static std::string SpecForRequest(const GURL& url);
+ // Returns the absolute URL, to be used for the http request. This url is
+ // made up of the protocol, host, [port], path, [query]. Everything else
+ // is stripped (username, password, reference).
+ static std::string SpecForRequest(const GURL& url);
// Locates the next occurance of delimiter in line, skipping over quoted
// strings (e.g., commas will not be treated as delimiters if they appear
@@ -54,6 +54,11 @@
static bool ParseRanges(const std::string& headers,
std::vector<HttpByteRange>* ranges);
+ // Same thing as ParseRanges except the Range header is known and its value
+ // is directly passed in, rather than requiring searching through a string.
+ static bool ParseRangeHeader(const std::string& range_specifier,
+ std::vector<HttpByteRange>* ranges);
+
// Scans the '\r\n'-delimited headers for the given header name. Returns
// true if a match is found. Input is assumed to be well-formed.
// TODO(darin): kill this
diff --git a/net/http/http_util_unittest.cc b/net/http/http_util_unittest.cc
index bdae329..0c018a2 100644
--- a/net/http/http_util_unittest.cc
+++ b/net/http/http_util_unittest.cc
@@ -36,21 +36,21 @@
TEST(HttpUtilTest, StripHeaders) {
static const char* headers =
- "Origin: origin\r\n"
- "Content-Type: text/plain\r\n"
- "Cookies: foo1\r\n"
- "Custom: baz\r\n"
- "COOKIES: foo2\r\n"
- "Server: Apache\r\n"
- "OrIGin: origin2\r\n";
+ "Origin: origin\r\n"
+ "Content-Type: text/plain\r\n"
+ "Cookies: foo1\r\n"
+ "Custom: baz\r\n"
+ "COOKIES: foo2\r\n"
+ "Server: Apache\r\n"
+ "OrIGin: origin2\r\n";
static const char* header_names[] = {
"origin", "content-type", "cookies"
};
static const char* expected_stripped_headers =
- "Custom: baz\r\n"
- "Server: Apache\r\n";
+ "Custom: baz\r\n"
+ "Server: Apache\r\n";
EXPECT_EQ(expected_stripped_headers,
HttpUtil::StripHeaders(headers, header_names,
diff --git a/net/http/http_vary_data.cc b/net/http/http_vary_data.cc
index fa7a325..f5c7514 100644
--- a/net/http/http_vary_data.cc
+++ b/net/http/http_vary_data.cc
@@ -8,6 +8,7 @@
#include "base/pickle.h"
#include "base/string_util.h"
+#include "net/http/http_request_headers.h"
#include "net/http/http_request_info.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
@@ -97,33 +98,18 @@
const HttpRequestInfo& request_info,
const std::string& request_header) {
// Some special cases:
- if (LowerCaseEqualsASCII(request_header, "referer"))
+ if (!base::strcasecmp(request_header.c_str(), HttpRequestHeaders::kReferer))
return request_info.referrer.spec();
- if (LowerCaseEqualsASCII(request_header, "user-agent"))
- return request_info.user_agent;
-
- std::string result;
-
- // Check extra headers:
- HttpUtil::HeadersIterator it(request_info.extra_headers.begin(),
- request_info.extra_headers.end(),
- "\r\n");
- while (it.GetNext()) {
- size_t name_len = it.name_end() - it.name_begin();
- if (request_header.size() == name_len &&
- std::equal(it.name_begin(), it.name_end(), request_header.begin(),
- CaseInsensitiveCompare<char>())) {
- if (!result.empty())
- result.append(1, ',');
- result.append(it.values());
- }
- }
// Unfortunately, we do not have access to all of the request headers at this
// point. Most notably, we do not have access to an Authorization header if
// one will be added to the request.
- return result;
+ std::string result;
+ if (request_info.extra_headers.GetHeader(request_header, &result))
+ return result;
+
+ return "";
}
// static
diff --git a/net/http/http_vary_data.h b/net/http/http_vary_data.h
index 360799d..98b94fa 100644
--- a/net/http/http_vary_data.h
+++ b/net/http/http_vary_data.h
@@ -11,7 +11,7 @@
namespace net {
-class HttpRequestInfo;
+struct HttpRequestInfo;
class HttpResponseHeaders;
// Used to implement the HTTP/1.1 Vary header. This class contains a MD5 hash
diff --git a/net/http/http_vary_data_unittest.cc b/net/http/http_vary_data_unittest.cc
index 7b2bd16..38e4e32 100644
--- a/net/http/http_vary_data_unittest.cc
+++ b/net/http/http_vary_data_unittest.cc
@@ -23,7 +23,8 @@
std::replace(temp.begin(), temp.end(), '\n', '\0');
response = new net::HttpResponseHeaders(temp);
- request.extra_headers = request_headers;
+ request.extra_headers.Clear();
+ request.extra_headers.AddHeadersFromString(request_headers);
}
};
@@ -54,13 +55,13 @@
// Init to something valid.
TestTransaction t1;
- t1.Init("Foo: 1\nbar: 23", "HTTP/1.1 200 OK\nVary: foo, bar\n\n");
+ t1.Init("Foo: 1\r\nbar: 23", "HTTP/1.1 200 OK\nVary: foo, bar\n\n");
EXPECT_TRUE(v.Init(t1.request, *t1.response));
EXPECT_TRUE(v.is_valid());
// Now overwrite by initializing to something invalid.
TestTransaction t2;
- t2.Init("Foo: 1\nbar: 23", "HTTP/1.1 200 OK\nVary: *\n\n");
+ t2.Init("Foo: 1\r\nbar: 23", "HTTP/1.1 200 OK\nVary: *\n\n");
EXPECT_FALSE(v.Init(t2.request, *t2.response));
EXPECT_FALSE(v.is_valid());
}
@@ -80,10 +81,10 @@
TEST(HttpVaryDataTest, DoesVary2) {
TestTransaction a;
- a.Init("Foo: 1\nbar: 23", "HTTP/1.1 200 OK\nVary: foo, bar\n\n");
+ a.Init("Foo: 1\r\nbar: 23", "HTTP/1.1 200 OK\nVary: foo, bar\n\n");
TestTransaction b;
- b.Init("Foo: 12\nbar: 3", "HTTP/1.1 200 OK\nVary: foo, bar\n\n");
+ b.Init("Foo: 12\r\nbar: 3", "HTTP/1.1 200 OK\nVary: foo, bar\n\n");
net::HttpVaryData v;
EXPECT_TRUE(v.Init(a.request, *a.response));
@@ -106,10 +107,10 @@
TEST(HttpVaryDataTest, DoesntVary2) {
TestTransaction a;
- a.Init("Foo: 1\nbAr: 2", "HTTP/1.1 200 OK\nVary: foo, bar\n\n");
+ a.Init("Foo: 1\r\nbAr: 2", "HTTP/1.1 200 OK\nVary: foo, bar\n\n");
TestTransaction b;
- b.Init("Foo: 1\nbaR: 2", "HTTP/1.1 200 OK\nVary: foo\nVary: bar\n\n");
+ b.Init("Foo: 1\r\nbaR: 2", "HTTP/1.1 200 OK\nVary: foo\nVary: bar\n\n");
net::HttpVaryData v;
EXPECT_TRUE(v.Init(a.request, *a.response));
diff --git a/net/http/mock_gssapi_library_posix.cc b/net/http/mock_gssapi_library_posix.cc
new file mode 100644
index 0000000..fed2ccd
--- /dev/null
+++ b/net/http/mock_gssapi_library_posix.cc
@@ -0,0 +1,468 @@
+// 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 "net/http/mock_gssapi_library_posix.h"
+
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "net/third_party/gssapi/gssapi.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace test {
+
+struct GssNameMockImpl {
+ std::string name;
+ gss_OID_desc name_type;
+};
+
+} // namespace test
+
+namespace {
+
+// gss_OID helpers.
+// NOTE: gss_OID's do not own the data they point to, which should be static.
+void ClearOid(gss_OID dest) {
+ if (!dest)
+ return;
+ dest->length = 0;
+ dest->elements = NULL;
+}
+
+void SetOid(gss_OID dest, const void* src, size_t length) {
+ if (!dest)
+ return;
+ ClearOid(dest);
+ if (!src)
+ return;
+ dest->length = length;
+ if (length)
+ dest->elements = const_cast<void*>(src);
+}
+
+void CopyOid(gss_OID dest, const gss_OID_desc* src) {
+ if (!dest)
+ return;
+ ClearOid(dest);
+ if (!src)
+ return;
+ SetOid(dest, src->elements, src->length);
+}
+
+std::string OidToString(const gss_OID src) {
+ std::string dest;
+ if (!src)
+ return dest;
+ const char* string = reinterpret_cast<char*>(src->elements);
+ dest.assign(string, src->length);
+ return dest;
+}
+
+void OidFromString(const std::string& src, gss_OID dest) {
+ if (!dest)
+ return;
+ SetOid(dest, src.c_str(), src.length());
+}
+
+// gss_buffer_t helpers.
+void ClearBuffer(gss_buffer_t dest) {
+ if (!dest)
+ return;
+ dest->length = 0;
+ delete [] reinterpret_cast<char*>(dest->value);
+ dest->value = NULL;
+}
+
+void SetBuffer(gss_buffer_t dest, const void* src, size_t length) {
+ if (!dest)
+ return;
+ ClearBuffer(dest);
+ if (!src)
+ return;
+ dest->length = length;
+ if (length) {
+ dest->value = new char[length];
+ memcpy(dest->value, src, length);
+ }
+}
+
+void CopyBuffer(gss_buffer_t dest, const gss_buffer_t src) {
+ if (!dest)
+ return;
+ ClearBuffer(dest);
+ if (!src)
+ return;
+ SetBuffer(dest, src->value, src->length);
+}
+
+std::string BufferToString(const gss_buffer_t src) {
+ std::string dest;
+ if (!src)
+ return dest;
+ const char* string = reinterpret_cast<char*>(src->value);
+ dest.assign(string, src->length);
+ return dest;
+}
+
+void BufferFromString(const std::string& src, gss_buffer_t dest) {
+ if (!dest)
+ return;
+ SetBuffer(dest, src.c_str(), src.length());
+}
+
+// gss_name_t helpers.
+void ClearName(gss_name_t dest) {
+ if (!dest)
+ return;
+ test::GssNameMockImpl* name = reinterpret_cast<test::GssNameMockImpl*>(dest);
+ name->name.clear();
+ ClearOid(&name->name_type);
+}
+
+void SetName(gss_name_t dest, const void* src, size_t length) {
+ if (!dest)
+ return;
+ ClearName(dest);
+ if (!src)
+ return;
+ test::GssNameMockImpl* name = reinterpret_cast<test::GssNameMockImpl*>(dest);
+ name->name.assign(reinterpret_cast<const char*>(src), length);
+}
+
+void CopyName(gss_name_t dest, const gss_name_t src) {
+ if (!dest)
+ return;
+ ClearName(dest);
+ if (!src)
+ return;
+ test::GssNameMockImpl* name_dst =
+ reinterpret_cast<test::GssNameMockImpl*>(dest);
+ test::GssNameMockImpl* name_src =
+ reinterpret_cast<test::GssNameMockImpl*>(dest);
+ name_dst->name = name_src->name;
+ CopyOid(&name_dst->name_type, &name_src->name_type);
+}
+
+std::string NameToString(const gss_name_t& src) {
+ std::string dest;
+ if (!src)
+ return dest;
+ test::GssNameMockImpl* string =
+ reinterpret_cast<test::GssNameMockImpl*>(src);
+ dest = string->name;
+ return dest;
+}
+
+void NameFromString(const std::string& src, gss_name_t dest) {
+ if (!dest)
+ return;
+ SetName(dest, src.c_str(), src.length());
+}
+
+} // namespace
+
+namespace test {
+
+GssContextMockImpl::GssContextMockImpl()
+ : lifetime_rec(0),
+ ctx_flags(0),
+ locally_initiated(0),
+ open(0) {
+ ClearOid(&mech_type);
+}
+
+GssContextMockImpl::GssContextMockImpl(const GssContextMockImpl& other)
+ : src_name(other.src_name),
+ targ_name(other.targ_name),
+ lifetime_rec(other.lifetime_rec),
+ ctx_flags(other.ctx_flags),
+ locally_initiated(other.locally_initiated),
+ open(other.open) {
+ CopyOid(&mech_type, &other.mech_type);
+}
+
+GssContextMockImpl::GssContextMockImpl(const char* src_name_in,
+ const char* targ_name_in,
+ OM_uint32 lifetime_rec_in,
+ const gss_OID_desc& mech_type_in,
+ OM_uint32 ctx_flags_in,
+ int locally_initiated_in,
+ int open_in)
+ : src_name(src_name_in ? src_name_in : ""),
+ targ_name(targ_name_in ? targ_name_in : ""),
+ lifetime_rec(lifetime_rec_in),
+ ctx_flags(ctx_flags_in),
+ locally_initiated(locally_initiated_in),
+ open(open_in) {
+ CopyOid(&mech_type, &mech_type_in);
+}
+
+GssContextMockImpl::~GssContextMockImpl() {
+ ClearOid(&mech_type);
+}
+
+void GssContextMockImpl::Assign(
+ const GssContextMockImpl& other) {
+ if (&other == this)
+ return;
+ src_name = other.src_name;
+ targ_name = other.targ_name;
+ lifetime_rec = other.lifetime_rec;
+ CopyOid(&mech_type, &other.mech_type);
+ ctx_flags = other.ctx_flags;
+ locally_initiated = other.locally_initiated;
+ open = other.open;
+}
+
+MockGSSAPILibrary::MockGSSAPILibrary() {
+}
+
+MockGSSAPILibrary::~MockGSSAPILibrary() {
+}
+
+bool MockGSSAPILibrary::Init() {
+ return true;
+}
+
+// These methods match the ones in the GSSAPI library.
+OM_uint32 MockGSSAPILibrary::import_name(
+ OM_uint32* minor_status,
+ const gss_buffer_t input_name_buffer,
+ const gss_OID input_name_type,
+ gss_name_t* output_name) {
+ if (minor_status)
+ *minor_status = 0;
+ if (!output_name)
+ return GSS_S_BAD_NAME;
+ if (!input_name_buffer)
+ return GSS_S_CALL_BAD_STRUCTURE;
+ if (!input_name_type)
+ return GSS_S_BAD_NAMETYPE;
+ GssNameMockImpl* output = new GssNameMockImpl;
+ if (output == NULL)
+ return GSS_S_FAILURE;
+ output->name_type.length = 0;
+ output->name_type.elements = NULL;
+
+ // Save the data.
+ output->name = BufferToString(input_name_buffer);
+ CopyOid(&output->name_type, input_name_type);
+ *output_name = output;
+
+ return GSS_S_COMPLETE;
+}
+
+OM_uint32 MockGSSAPILibrary::release_name(
+ OM_uint32* minor_status,
+ gss_name_t* input_name) {
+ if (minor_status)
+ *minor_status = 0;
+ if (!input_name)
+ return GSS_S_BAD_NAME;
+ if (!*input_name)
+ return GSS_S_COMPLETE;
+ GssNameMockImpl* name = *reinterpret_cast<GssNameMockImpl**>(input_name);
+ ClearName(*input_name);
+ delete name;
+ *input_name = NULL;
+ return GSS_S_COMPLETE;
+}
+
+OM_uint32 MockGSSAPILibrary::release_buffer(
+ OM_uint32* minor_status,
+ gss_buffer_t buffer) {
+ if (minor_status)
+ *minor_status = 0;
+ if (!buffer)
+ return GSS_S_BAD_NAME;
+ ClearBuffer(buffer);
+ return GSS_S_COMPLETE;
+}
+
+OM_uint32 MockGSSAPILibrary::display_name(
+ OM_uint32* minor_status,
+ const gss_name_t input_name,
+ gss_buffer_t output_name_buffer,
+ gss_OID* output_name_type) {
+ if (minor_status)
+ *minor_status = 0;
+ if (!input_name)
+ return GSS_S_BAD_NAME;
+ if (!output_name_buffer)
+ return GSS_S_CALL_BAD_STRUCTURE;
+ if (!output_name_type)
+ return GSS_S_CALL_BAD_STRUCTURE;
+ std::string name(NameToString(input_name));
+ BufferFromString(name, output_name_buffer);
+ GssNameMockImpl* internal_name =
+ *reinterpret_cast<GssNameMockImpl**>(input_name);
+ if (output_name_type)
+ *output_name_type = internal_name ? &internal_name->name_type : NULL;
+ return GSS_S_COMPLETE;
+}
+
+OM_uint32 MockGSSAPILibrary::display_status(
+ OM_uint32* minor_status,
+ OM_uint32 status_value,
+ int status_type,
+ const gss_OID mech_type,
+ OM_uint32* message_context,
+ gss_buffer_t status_string) {
+ if (minor_status)
+ *minor_status = 0;
+ std::string msg = StringPrintf("Value: %u, Type %u",
+ status_value,
+ status_type);
+ if (message_context)
+ *message_context = 0;
+ BufferFromString(msg, status_string);
+ return GSS_S_COMPLETE;
+}
+
+OM_uint32 MockGSSAPILibrary::init_sec_context(
+ OM_uint32* minor_status,
+ const gss_cred_id_t initiator_cred_handle,
+ gss_ctx_id_t* context_handle,
+ const gss_name_t target_name,
+ const gss_OID mech_type,
+ OM_uint32 req_flags,
+ OM_uint32 time_req,
+ const gss_channel_bindings_t input_chan_bindings,
+ const gss_buffer_t input_token,
+ gss_OID* actual_mech_type,
+ gss_buffer_t output_token,
+ OM_uint32* ret_flags,
+ OM_uint32* time_rec) {
+ if (minor_status)
+ *minor_status = 0;
+ if (!context_handle)
+ return GSS_S_CALL_BAD_STRUCTURE;
+ GssContextMockImpl** internal_context_handle =
+ reinterpret_cast<test::GssContextMockImpl**>(context_handle);
+ // Create it if necessary.
+ if (!*internal_context_handle) {
+ *internal_context_handle = new GssContextMockImpl;
+ }
+ EXPECT_TRUE(*internal_context_handle);
+ GssContextMockImpl& context = **internal_context_handle;
+ if (expected_security_queries_.empty()) {
+ return GSS_S_UNAVAILABLE;
+ }
+ SecurityContextQuery security_query = expected_security_queries_.front();
+ expected_security_queries_.pop_front();
+ EXPECT_EQ(std::string("Negotiate"), security_query.expected_package);
+ OM_uint32 major_status = security_query.response_code;
+ if (minor_status)
+ *minor_status = security_query.minor_response_code;
+ context.src_name = security_query.context_info.src_name;
+ context.targ_name = security_query.context_info.targ_name;
+ context.lifetime_rec = security_query.context_info.lifetime_rec;
+ CopyOid(&context.mech_type, &security_query.context_info.mech_type);
+ context.ctx_flags = security_query.context_info.ctx_flags;
+ context.locally_initiated = security_query.context_info.locally_initiated;
+ context.open = security_query.context_info.open;
+ if (!input_token) {
+ EXPECT_FALSE(security_query.expected_input_token.length);
+ } else {
+ EXPECT_EQ(input_token->length, security_query.expected_input_token.length);
+ if (input_token->length) {
+ EXPECT_EQ(0, memcmp(input_token->value,
+ security_query.expected_input_token.value,
+ input_token->length));
+ }
+ }
+ CopyBuffer(output_token, &security_query.output_token);
+ if (actual_mech_type)
+ CopyOid(*actual_mech_type, mech_type);
+ if (ret_flags)
+ *ret_flags = req_flags;
+ return major_status;
+}
+
+OM_uint32 MockGSSAPILibrary::wrap_size_limit(
+ OM_uint32* minor_status,
+ const gss_ctx_id_t context_handle,
+ int conf_req_flag,
+ gss_qop_t qop_req,
+ OM_uint32 req_output_size,
+ OM_uint32* max_input_size) {
+ if (minor_status)
+ *minor_status = 0;
+ ADD_FAILURE();
+ return GSS_S_UNAVAILABLE;
+}
+
+OM_uint32 MockGSSAPILibrary::delete_sec_context(
+ OM_uint32* minor_status,
+ gss_ctx_id_t* context_handle,
+ gss_buffer_t output_token) {
+ if (minor_status)
+ *minor_status = 0;
+ if (!context_handle)
+ return GSS_S_CALL_BAD_STRUCTURE;
+ GssContextMockImpl** internal_context_handle =
+ reinterpret_cast<GssContextMockImpl**>(context_handle);
+ if (*internal_context_handle) {
+ delete *internal_context_handle;
+ *internal_context_handle = NULL;
+ }
+ return GSS_S_COMPLETE;
+}
+
+OM_uint32 MockGSSAPILibrary::inquire_context(
+ OM_uint32* minor_status,
+ const gss_ctx_id_t context_handle,
+ gss_name_t* src_name,
+ gss_name_t* targ_name,
+ OM_uint32* lifetime_rec,
+ gss_OID* mech_type,
+ OM_uint32* ctx_flags,
+ int* locally_initiated,
+ int* open) {
+ if (minor_status)
+ *minor_status = 0;
+ if (!context_handle)
+ return GSS_S_CALL_BAD_STRUCTURE;
+ GssContextMockImpl* internal_context_ptr =
+ reinterpret_cast<GssContextMockImpl*>(context_handle);
+ GssContextMockImpl& context = *internal_context_ptr;
+ if (src_name)
+ NameFromString(context.src_name, *src_name);
+ if (targ_name)
+ NameFromString(context.targ_name, *targ_name);
+ if (lifetime_rec)
+ *lifetime_rec = context.lifetime_rec;
+ if (mech_type)
+ CopyOid(*mech_type, &context.mech_type);
+ if (ctx_flags)
+ *ctx_flags = context.ctx_flags;
+ if (locally_initiated)
+ *locally_initiated = context.locally_initiated;
+ if (open)
+ *open = context.open;
+ return GSS_S_COMPLETE;
+}
+
+void MockGSSAPILibrary::ExpectSecurityContext(
+ const std::string& expected_package,
+ OM_uint32 response_code,
+ OM_uint32 minor_response_code,
+ const GssContextMockImpl& context_info,
+ const gss_buffer_desc& expected_input_token,
+ const gss_buffer_desc& output_token) {
+ SecurityContextQuery security_query;
+ security_query.expected_package = expected_package;
+ security_query.response_code = response_code;
+ security_query.minor_response_code = minor_response_code;
+ security_query.context_info.Assign(context_info);
+ security_query.expected_input_token = expected_input_token;
+ security_query.output_token = output_token;
+ expected_security_queries_.push_back(security_query);
+}
+
+} // namespace test
+
+} // namespace net
+
diff --git a/net/http/mock_gssapi_library_posix.h b/net/http/mock_gssapi_library_posix.h
new file mode 100644
index 0000000..a78fb00
--- /dev/null
+++ b/net/http/mock_gssapi_library_posix.h
@@ -0,0 +1,185 @@
+// 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.
+
+#ifndef NET_HTTP_MOCK_GSSAPI_LIBRARY_POSIX_H_
+#define NET_HTTP_MOCK_GSSAPI_LIBRARY_POSIX_H_
+
+#include <list>
+#include <set>
+#include <string>
+
+#include "base/gtest_prod_util.h"
+#include "net/http/http_auth_gssapi_posix.h"
+#include "net/third_party/gssapi/gssapi.h"
+
+namespace net {
+
+namespace test {
+
+class GssContextMockImpl {
+ public:
+ GssContextMockImpl();
+ GssContextMockImpl(const GssContextMockImpl& other);
+ GssContextMockImpl(const char* src_name,
+ const char* targ_name,
+ OM_uint32 lifetime_rec,
+ const gss_OID_desc& mech_type,
+ OM_uint32 ctx_flags,
+ int locally_initiated,
+ int open);
+ ~GssContextMockImpl();
+
+ void Assign(const GssContextMockImpl& other);
+
+ std::string src_name;
+ std::string targ_name;
+ OM_uint32 lifetime_rec;
+ gss_OID_desc mech_type;
+ OM_uint32 ctx_flags;
+ int locally_initiated;
+ int open;
+};
+
+// The MockGSSAPILibrary class is intended for unit tests which want to bypass
+// the system GSSAPI library calls.
+class MockGSSAPILibrary : public GSSAPILibrary {
+ public:
+
+ MockGSSAPILibrary();
+ virtual ~MockGSSAPILibrary();
+
+ // GSSAPILibrary methods:
+
+ // Initializes the library, including any necessary dynamic libraries.
+ // This is done separately from construction (which happens at startup time)
+ // in order to delay work until the class is actually needed.
+ virtual bool Init();
+
+ // These methods match the ones in the GSSAPI library.
+ virtual OM_uint32 import_name(
+ OM_uint32* minor_status,
+ const gss_buffer_t input_name_buffer,
+ const gss_OID input_name_type,
+ gss_name_t* output_name);
+ virtual OM_uint32 release_name(
+ OM_uint32* minor_status,
+ gss_name_t* input_name);
+ virtual OM_uint32 release_buffer(
+ OM_uint32* minor_status,
+ gss_buffer_t buffer);
+ virtual OM_uint32 display_name(
+ OM_uint32* minor_status,
+ const gss_name_t input_name,
+ gss_buffer_t output_name_buffer,
+ gss_OID* output_name_type);
+ virtual OM_uint32 display_status(
+ OM_uint32* minor_status,
+ OM_uint32 status_value,
+ int status_type,
+ const gss_OID mech_type,
+ OM_uint32* message_contex,
+ gss_buffer_t status_string);
+ virtual OM_uint32 init_sec_context(
+ OM_uint32* minor_status,
+ const gss_cred_id_t initiator_cred_handle,
+ gss_ctx_id_t* context_handle,
+ const gss_name_t target_name,
+ const gss_OID mech_type,
+ OM_uint32 req_flags,
+ OM_uint32 time_req,
+ const gss_channel_bindings_t input_chan_bindings,
+ const gss_buffer_t input_token,
+ gss_OID* actual_mech_type,
+ gss_buffer_t output_token,
+ OM_uint32* ret_flags,
+ OM_uint32* time_rec);
+ virtual OM_uint32 wrap_size_limit(
+ OM_uint32* minor_status,
+ const gss_ctx_id_t context_handle,
+ int conf_req_flag,
+ gss_qop_t qop_req,
+ OM_uint32 req_output_size,
+ OM_uint32* max_input_size);
+ virtual OM_uint32 delete_sec_context(
+ OM_uint32* minor_status,
+ gss_ctx_id_t* context_handle,
+ gss_buffer_t output_token);
+ virtual OM_uint32 inquire_context(
+ OM_uint32* minor_status,
+ const gss_ctx_id_t context_handle,
+ gss_name_t* src_name,
+ gss_name_t* targ_name,
+ OM_uint32* lifetime_rec,
+ gss_OID* mech_type,
+ OM_uint32* ctx_flags,
+ int* locally_initiated,
+ int* open);
+
+ // Establishes an expectation for a |init_sec_context()| call.
+ //
+ // Each expectation established by |ExpectSecurityContext()| must be
+ // matched by a call to |init_sec_context()| during the lifetime of
+ // the MockGSSAPILibrary. The |expected_package| argument must equal the
+ // value associated with the |target_name| argument to |init_sec_context()|
+ // for there to be a match. The expectations also establish an explicit
+ // ordering.
+ //
+ // For example, this sequence will be successful.
+ // MockGSSAPILibrary lib;
+ // lib.ExpectSecurityContext("NTLM", ...)
+ // lib.ExpectSecurityContext("Negotiate", ...)
+ // lib.init_sec_context("NTLM", ...)
+ // lib.init_sec_context("Negotiate", ...)
+ //
+ // This sequence will fail since the queries do not occur in the order
+ // established by the expectations.
+ // MockGSSAPILibrary lib;
+ // lib.ExpectSecurityContext("NTLM", ...)
+ // lib.ExpectSecurityContext("Negotiate", ...)
+ // lib.init_sec_context("Negotiate", ...)
+ // lib.init_sec_context("NTLM", ...)
+ //
+ // This sequence will fail because there were not enough queries.
+ // MockGSSAPILibrary lib;
+ // lib.ExpectSecurityContext("NTLM", ...)
+ // lib.ExpectSecurityContext("Negotiate", ...)
+ // lib.init_sec_context("NTLM", ...)
+ //
+ // |response_code| is used as the return value for |init_sec_context()|.
+ // If |response_code| is GSS_S_COMPLETE,
+ //
+ // |context_info| is the expected value of the |**context_handle| in after
+ // |init_sec_context()| returns.
+ void ExpectSecurityContext(const std::string& expected_package,
+ OM_uint32 response_code,
+ OM_uint32 minor_response_code,
+ const test::GssContextMockImpl& context_info,
+ const gss_buffer_desc& expected_input_token,
+ const gss_buffer_desc& output_token);
+
+ // Unit tests need access to this. "Friend"ing didn't help.
+ struct SecurityContextQuery {
+ std::string expected_package;
+ OM_uint32 response_code;
+ OM_uint32 minor_response_code;
+ test::GssContextMockImpl context_info;
+ gss_buffer_desc expected_input_token;
+ gss_buffer_desc output_token;
+ };
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(HttpAuthGSSAPIPOSIXTest, GSSAPICycle);
+
+ // |expected_security_queries| contains an ordered list of expected
+ // |init_sec_context()| calls and the return values for those
+ // calls.
+ std::list<SecurityContextQuery> expected_security_queries_;
+};
+
+} // namespace test
+
+} // namespace net
+
+#endif // NET_HTTP_MOCK_GSSAPI_LIBRARY_POSIX_H_
+
diff --git a/net/http/mock_sspi_library_win.cc b/net/http/mock_sspi_library_win.cc
new file mode 100644
index 0000000..d0dae51
--- /dev/null
+++ b/net/http/mock_sspi_library_win.cc
@@ -0,0 +1,100 @@
+// 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 "net/http/mock_sspi_library_win.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+MockSSPILibrary::MockSSPILibrary() {
+}
+
+MockSSPILibrary::~MockSSPILibrary() {
+ EXPECT_TRUE(expected_package_queries_.empty());
+ EXPECT_TRUE(expected_freed_packages_.empty());
+}
+
+SECURITY_STATUS MockSSPILibrary::AcquireCredentialsHandle(
+ LPWSTR pszPrincipal,
+ LPWSTR pszPackage,
+ unsigned long fCredentialUse,
+ void* pvLogonId,
+ void* pvAuthData,
+ SEC_GET_KEY_FN pGetKeyFn,
+ void* pvGetKeyArgument,
+ PCredHandle phCredential,
+ PTimeStamp ptsExpiry) {
+ // Fill in phCredential with arbitrary value.
+ phCredential->dwLower = phCredential->dwUpper = ((ULONG_PTR) ((INT_PTR)0));
+ return SEC_E_OK;
+}
+
+SECURITY_STATUS MockSSPILibrary::InitializeSecurityContext(
+ PCredHandle phCredential,
+ PCtxtHandle phContext,
+ SEC_WCHAR* pszTargetName,
+ unsigned long fContextReq,
+ unsigned long Reserved1,
+ unsigned long TargetDataRep,
+ PSecBufferDesc pInput,
+ unsigned long Reserved2,
+ PCtxtHandle phNewContext,
+ PSecBufferDesc pOutput,
+ unsigned long* contextAttr,
+ PTimeStamp ptsExpiry) {
+ // Fill in the outbound buffer with garbage data.
+ PSecBuffer out_buffer = pOutput->pBuffers;
+ out_buffer->cbBuffer = 2;
+ uint8* buf = reinterpret_cast<uint8 *>(out_buffer->pvBuffer);
+ buf[0] = 0xAB;
+ buf[1] = 0xBA;
+ return SEC_E_OK;
+}
+
+SECURITY_STATUS MockSSPILibrary::QuerySecurityPackageInfo(
+ LPWSTR pszPackageName, PSecPkgInfoW *pkgInfo) {
+ EXPECT_TRUE(!expected_package_queries_.empty());
+ PackageQuery package_query = expected_package_queries_.front();
+ expected_package_queries_.pop_front();
+ std::wstring actual_package(pszPackageName);
+ EXPECT_EQ(package_query.expected_package, actual_package);
+ *pkgInfo = package_query.package_info;
+ if (package_query.response_code == SEC_E_OK)
+ expected_freed_packages_.insert(package_query.package_info);
+ return package_query.response_code;
+}
+
+SECURITY_STATUS MockSSPILibrary::FreeCredentialsHandle(
+ PCredHandle phCredential) {
+ EXPECT_TRUE(phCredential->dwLower == ((ULONG_PTR) ((INT_PTR) 0)));
+ EXPECT_TRUE(phCredential->dwLower == ((ULONG_PTR) ((INT_PTR) 0)));
+ SecInvalidateHandle(phCredential);
+ return SEC_E_OK;
+}
+
+SECURITY_STATUS MockSSPILibrary::DeleteSecurityContext(PCtxtHandle phContext) {
+ ADD_FAILURE();
+ return ERROR_CALL_NOT_IMPLEMENTED;
+}
+
+SECURITY_STATUS MockSSPILibrary::FreeContextBuffer(PVOID pvContextBuffer) {
+ PSecPkgInfoW package_info = static_cast<PSecPkgInfoW>(pvContextBuffer);
+ std::set<PSecPkgInfoW>::iterator it = expected_freed_packages_.find(
+ package_info);
+ EXPECT_TRUE(it != expected_freed_packages_.end());
+ expected_freed_packages_.erase(it);
+ return SEC_E_OK;
+}
+
+void MockSSPILibrary::ExpectQuerySecurityPackageInfo(
+ const std::wstring& expected_package,
+ SECURITY_STATUS response_code,
+ PSecPkgInfoW package_info) {
+ PackageQuery package_query = {expected_package, response_code,
+ package_info};
+ expected_package_queries_.push_back(package_query);
+}
+
+} // namespace net
diff --git a/net/http/mock_sspi_library_win.h b/net/http/mock_sspi_library_win.h
new file mode 100644
index 0000000..eca3eb5
--- /dev/null
+++ b/net/http/mock_sspi_library_win.h
@@ -0,0 +1,111 @@
+// 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.
+
+#ifndef NET_HTTP_MOCK_SSPI_LIBRARY_WIN_H_
+#define NET_HTTP_MOCK_SSPI_LIBRARY_WIN_H_
+
+#include <list>
+#include <set>
+
+#include "net/http/http_auth_sspi_win.h"
+
+namespace net {
+
+// The MockSSPILibrary class is intended for unit tests which want to bypass
+// the system SSPI library calls.
+class MockSSPILibrary : public SSPILibrary {
+ public:
+ MockSSPILibrary();
+ virtual ~MockSSPILibrary();
+
+ // TODO(cbentzel): Only QuerySecurityPackageInfo and FreeContextBuffer
+ // are properly handled currently.
+ // SSPILibrary methods:
+ virtual SECURITY_STATUS AcquireCredentialsHandle(LPWSTR pszPrincipal,
+ LPWSTR pszPackage,
+ unsigned long fCredentialUse,
+ void* pvLogonId,
+ void* pvAuthData,
+ SEC_GET_KEY_FN pGetKeyFn,
+ void* pvGetKeyArgument,
+ PCredHandle phCredential,
+ PTimeStamp ptsExpiry);
+ virtual SECURITY_STATUS InitializeSecurityContext(PCredHandle phCredential,
+ PCtxtHandle phContext,
+ SEC_WCHAR* pszTargetName,
+ unsigned long fContextReq,
+ unsigned long Reserved1,
+ unsigned long TargetDataRep,
+ PSecBufferDesc pInput,
+ unsigned long Reserved2,
+ PCtxtHandle phNewContext,
+ PSecBufferDesc pOutput,
+ unsigned long* contextAttr,
+ PTimeStamp ptsExpiry);
+ virtual SECURITY_STATUS QuerySecurityPackageInfo(LPWSTR pszPackageName,
+ PSecPkgInfoW *pkgInfo);
+ virtual SECURITY_STATUS FreeCredentialsHandle(PCredHandle phCredential);
+ virtual SECURITY_STATUS DeleteSecurityContext(PCtxtHandle phContext);
+ virtual SECURITY_STATUS FreeContextBuffer(PVOID pvContextBuffer);
+
+ // Establishes an expectation for a |QuerySecurityPackageInfo()| call.
+ //
+ // Each expectation established by |ExpectSecurityQueryPackageInfo()| must be
+ // matched by a call to |QuerySecurityPackageInfo()| during the lifetime of
+ // the MockSSPILibrary. The |expected_package| argument must equal the
+ // |*pszPackageName| argument to |QuerySecurityPackageInfo()| for there to be
+ // a match. The expectations also establish an explicit ordering.
+ //
+ // For example, this sequence will be successful.
+ // MockSSPILibrary lib;
+ // lib.ExpectQuerySecurityPackageInfo(L"NTLM", ...)
+ // lib.ExpectQuerySecurityPackageInfo(L"Negotiate", ...)
+ // lib.QuerySecurityPackageInfo(L"NTLM", ...)
+ // lib.QuerySecurityPackageInfo(L"Negotiate", ...)
+ //
+ // This sequence will fail since the queries do not occur in the order
+ // established by the expectations.
+ // MockSSPILibrary lib;
+ // lib.ExpectQuerySecurityPackageInfo(L"NTLM", ...)
+ // lib.ExpectQuerySecurityPackageInfo(L"Negotiate", ...)
+ // lib.QuerySecurityPackageInfo(L"Negotiate", ...)
+ // lib.QuerySecurityPackageInfo(L"NTLM", ...)
+ //
+ // This sequence will fail because there were not enough queries.
+ // MockSSPILibrary lib;
+ // lib.ExpectQuerySecurityPackageInfo(L"NTLM", ...)
+ // lib.ExpectQuerySecurityPackageInfo(L"Negotiate", ...)
+ // lib.QuerySecurityPackageInfo(L"NTLM", ...)
+ //
+ // |response_code| is used as the return value for
+ // |QuerySecurityPackageInfo()|. If |response_code| is SEC_E_OK,
+ // an expectation is also set for a call to |FreeContextBuffer()| after
+ // the matching |QuerySecurityPackageInfo()| is called.
+ //
+ // |package_info| is assigned to |*pkgInfo| in |QuerySecurityPackageInfo|.
+ // The lifetime of |*package_info| should last at least until the matching
+ // |QuerySecurityPackageInfo()| is called.
+ void ExpectQuerySecurityPackageInfo(const std::wstring& expected_package,
+ SECURITY_STATUS response_code,
+ PSecPkgInfoW package_info);
+
+ private:
+ struct PackageQuery {
+ std::wstring expected_package;
+ SECURITY_STATUS response_code;
+ PSecPkgInfoW package_info;
+ };
+
+ // expected_package_queries contains an ordered list of expected
+ // |QuerySecurityPackageInfo()| calls and the return values for those
+ // calls.
+ std::list<PackageQuery> expected_package_queries_;
+
+ // Set of packages which should be freed.
+ std::set<PSecPkgInfoW> expected_freed_packages_;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_MOCK_SSPI_LIBRARY_WIN_H_
diff --git a/net/http/partial_data.cc b/net/http/partial_data.cc
index f727699..49d68d2 100644
--- a/net/http/partial_data.cc
+++ b/net/http/partial_data.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-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.
@@ -12,6 +12,8 @@
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
+namespace net {
+
namespace {
// The headers that we have to process.
@@ -19,13 +21,101 @@
const char kRangeHeader[] = "Content-Range";
const int kDataStream = 1;
+void AddRangeHeader(int64 start, int64 end, HttpRequestHeaders* headers) {
+ DCHECK(start >= 0 || end >= 0);
+ std::string my_start, my_end;
+ if (start >= 0)
+ my_start = Int64ToString(start);
+ if (end >= 0)
+ my_end = Int64ToString(end);
+
+ headers->SetHeader(
+ HttpRequestHeaders::kRange,
+ StringPrintf("bytes=%s-%s", my_start.c_str(), my_end.c_str()));
}
-namespace net {
+} // namespace
-bool PartialData::Init(const std::string& headers) {
+// A core object that can be detached from the Partialdata object at destruction
+// so that asynchronous operations cleanup can be performed.
+class PartialData::Core {
+ public:
+ // Build a new core object. Lifetime management is automatic.
+ static Core* CreateCore(PartialData* owner) {
+ return new Core(owner);
+ }
+
+ // Wrapper for Entry::GetAvailableRange. If this method returns ERR_IO_PENDING
+ // PartialData::GetAvailableRangeCompleted() will be invoked on the owner
+ // object when finished (unless Cancel() is called first).
+ int GetAvailableRange(disk_cache::Entry* entry, int64 offset, int len,
+ int64* start);
+
+ // Cancels a pending operation. It is a mistake to call this method if there
+ // is no operation in progress; in fact, there will be no object to do so.
+ void Cancel();
+
+ private:
+ explicit Core(PartialData* owner);
+ ~Core();
+
+ // Pending io completion routine.
+ void OnIOComplete(int result);
+
+ PartialData* owner_;
+ int64 start_;
+ net::CompletionCallbackImpl<Core> callback_;
+ DISALLOW_COPY_AND_ASSIGN(Core);
+};
+
+PartialData::Core::Core(PartialData* owner)
+ : owner_(owner),
+ ALLOW_THIS_IN_INITIALIZER_LIST(callback_(this, &Core::OnIOComplete)) {
+ DCHECK(!owner_->core_);
+ owner_->core_ = this;
+}
+
+PartialData::Core::~Core() {
+ if (owner_)
+ owner_->core_ = NULL;
+}
+
+void PartialData::Core::Cancel() {
+ DCHECK(owner_);
+ owner_ = NULL;
+}
+
+int PartialData::Core::GetAvailableRange(disk_cache::Entry* entry, int64 offset,
+ int len, int64* start) {
+ int rv = entry->GetAvailableRange(offset, len, &start_, &callback_);
+ if (rv != net::ERR_IO_PENDING) {
+ // The callback will not be invoked. Lets cleanup.
+ *start = start_;
+ delete this;
+ }
+ return rv;
+}
+
+void PartialData::Core::OnIOComplete(int result) {
+ if (owner_)
+ owner_->GetAvailableRangeCompleted(result, start_);
+ delete this;
+}
+
+// -----------------------------------------------------------------------------
+
+PartialData::~PartialData() {
+ if (core_)
+ core_->Cancel();
+}
+
+bool PartialData::Init(const HttpRequestHeaders& headers) {
+ std::string range_header;
+ if (!headers.GetHeader(HttpRequestHeaders::kRange, &range_header))
+ return false;
+
std::vector<HttpByteRange> ranges;
- if (!HttpUtil::ParseRanges(headers, &ranges) || ranges.size() != 1)
+ if (!HttpUtil::ParseRangeHeader(range_header, &ranges) || ranges.size() != 1)
return false;
// We can handle this range request.
@@ -38,38 +128,40 @@
return true;
}
-void PartialData::SetHeaders(const std::string& headers) {
- DCHECK(extra_headers_.empty());
- extra_headers_ = headers;
+void PartialData::SetHeaders(const HttpRequestHeaders& headers) {
+ DCHECK(extra_headers_.IsEmpty());
+ extra_headers_.CopyFrom(headers);
}
-void PartialData::RestoreHeaders(std::string* headers) const {
+void PartialData::RestoreHeaders(HttpRequestHeaders* headers) const {
DCHECK(current_range_start_ >= 0 || byte_range_.IsSuffixByteRange());
int64 end = byte_range_.IsSuffixByteRange() ?
byte_range_.suffix_length() : byte_range_.last_byte_position();
- headers->assign(extra_headers_);
+ headers->CopyFrom(extra_headers_);
if (byte_range_.IsValid())
AddRangeHeader(current_range_start_, end, headers);
}
-int PartialData::PrepareCacheValidation(disk_cache::Entry* entry,
- std::string* headers) {
- DCHECK(current_range_start_ >= 0);
+int PartialData::ShouldValidateCache(disk_cache::Entry* entry,
+ CompletionCallback* callback) {
+ DCHECK_GE(current_range_start_, 0);
// Scan the disk cache for the first cached portion within this range.
- int64 range_len = byte_range_.HasLastBytePosition() ?
- byte_range_.last_byte_position() - current_range_start_ + 1 : kint32max;
- if (range_len > kint32max)
- range_len = kint32max;
- int len = static_cast<int32>(range_len);
+ int len = GetNextRangeLen();
if (!len)
return 0;
- range_present_ = false;
if (sparse_entry_) {
- cached_min_len_ = entry->GetAvailableRange(current_range_start_, len,
- &cached_start_);
+ DCHECK(!callback_);
+ Core* core = Core::CreateCore(this);
+ cached_min_len_ = core->GetAvailableRange(entry, current_range_start_, len,
+ &cached_start_);
+
+ if (cached_min_len_ == ERR_IO_PENDING) {
+ callback_ = callback;
+ return ERR_IO_PENDING;
+ }
} else if (truncated_) {
if (!current_range_start_) {
// Update the cached range only the first time.
@@ -81,12 +173,23 @@
cached_start_ = current_range_start_;
}
- if (cached_min_len_ < 0) {
- DCHECK(cached_min_len_ != ERR_IO_PENDING);
+ if (cached_min_len_ < 0)
return cached_min_len_;
- }
- headers->assign(extra_headers_);
+ // Return a positive number to indicate success (versus error or finished).
+ return 1;
+}
+
+void PartialData::PrepareCacheValidation(disk_cache::Entry* entry,
+ HttpRequestHeaders* headers) {
+ DCHECK_GE(current_range_start_, 0);
+ DCHECK_GE(cached_min_len_, 0);
+
+ int len = GetNextRangeLen();
+ DCHECK_NE(0, len);
+ range_present_ = false;
+
+ headers->CopyFrom(extra_headers_);
if (!cached_min_len_) {
// We don't have anything else stored.
@@ -106,9 +209,6 @@
// This range is not in the cache.
AddRangeHeader(current_range_start_, cached_start_ - 1, headers);
}
-
- // Return a positive number to indicate success (versus error or finished).
- return 1;
}
bool PartialData::IsCurrentRangeCached() const {
@@ -158,11 +258,7 @@
resource_size_ = length_value;
// Make sure that this is really a sparse entry.
- int64 n;
- if (ERR_CACHE_OPERATION_NOT_SUPPORTED == entry->GetAvailableRange(0, 5, &n))
- return false;
-
- return true;
+ return entry->CouldBeSparse();
}
bool PartialData::IsRequestedRangeOK() {
@@ -194,7 +290,7 @@
// We must have a complete range here.
return byte_range_.HasFirstBytePosition() &&
- byte_range_.HasLastBytePosition();
+ byte_range_.HasLastBytePosition();
}
int64 start, end, total_length;
@@ -296,7 +392,7 @@
}
int PartialData::CacheWrite(disk_cache::Entry* entry, IOBuffer* data,
- int data_len, CompletionCallback* callback) {
+ int data_len, CompletionCallback* callback) {
if (sparse_entry_) {
return entry->WriteSparseData(current_range_start_, data, data_len,
callback);
@@ -313,7 +409,7 @@
if (result > 0) {
current_range_start_ += result;
cached_min_len_ -= result;
- DCHECK(cached_min_len_ >= 0);
+ DCHECK_GE(cached_min_len_, 0);
}
}
@@ -322,17 +418,28 @@
current_range_start_ += result;
}
-// Static.
-void PartialData::AddRangeHeader(int64 start, int64 end, std::string* headers) {
- DCHECK(start >= 0 || end >= 0);
- std::string my_start, my_end;
- if (start >= 0)
- my_start = Int64ToString(start);
- if (end >= 0)
- my_end = Int64ToString(end);
+int PartialData::GetNextRangeLen() {
+ int64 range_len =
+ byte_range_.HasLastBytePosition() ?
+ byte_range_.last_byte_position() - current_range_start_ + 1 :
+ kint32max;
+ if (range_len > kint32max)
+ range_len = kint32max;
+ return static_cast<int32>(range_len);
+}
- headers->append(StringPrintf("Range: bytes=%s-%s\r\n", my_start.c_str(),
- my_end.c_str()));
+void PartialData::GetAvailableRangeCompleted(int result, int64 start) {
+ DCHECK(callback_);
+ DCHECK_NE(ERR_IO_PENDING, result);
+
+ cached_start_ = start;
+ cached_min_len_ = result;
+ if (result >= 0)
+ result = 1; // Return success, go ahead and validate the entry.
+
+ CompletionCallback* cb = callback_;
+ callback_ = NULL;
+ cb->Run(result);
}
} // namespace net
diff --git a/net/http/partial_data.h b/net/http/partial_data.h
index 3a30e0a..b720e6e 100644
--- a/net/http/partial_data.h
+++ b/net/http/partial_data.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-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.
@@ -10,6 +10,7 @@
#include "base/basictypes.h"
#include "net/base/completion_callback.h"
#include "net/http/http_byte_range.h"
+#include "net/http/http_request_headers.h"
namespace disk_cache {
class Entry;
@@ -33,30 +34,36 @@
public:
PartialData()
: range_present_(false), final_range_(false), sparse_entry_(true),
- truncated_(false) {}
- ~PartialData() {}
+ truncated_(false), core_(NULL), callback_(NULL) {}
+ ~PartialData();
- // Performs initialization of the object by parsing the request |headers|
+ // Performs initialization of the object by examining the request |headers|
// and verifying that we can process the requested range. Returns true if
// we can process the requested range, and false otherwise.
- bool Init(const std::string& headers);
+ bool Init(const HttpRequestHeaders& headers);
// Sets the headers that we should use to make byte range requests. This is a
// subset of the request extra headers, with byte-range related headers
// removed.
- void SetHeaders(const std::string& headers);
+ void SetHeaders(const HttpRequestHeaders& headers);
// Restores the byte-range headers, by appending the byte range to the headers
// provided to SetHeaders().
- void RestoreHeaders(std::string* headers) const;
+ void RestoreHeaders(HttpRequestHeaders* headers) const;
+
+ // Starts the checks to perform a cache validation. Returns 0 when there is no
+ // need to perform more operations because we reached the end of the request
+ // (so 0 bytes should be actually returned to the user), a positive number to
+ // indicate that PrepareCacheValidation should be called, or an appropriate
+ // error code. If this method returns ERR_IO_PENDING, the |callback| will be
+ // notified when the result is ready.
+ int ShouldValidateCache(disk_cache::Entry* entry,
+ CompletionCallback* callback);
// Builds the required |headers| to perform the proper cache validation for
- // the next range to be fetched. Returns 0 when there is no need to perform
- // more operations because we reached the end of the request (so 0 bytes
- // should be actually returned to the user), a positive number to indicate
- // that |headers| should be used to validate the cache, or an appropriate
- // error code.
- int PrepareCacheValidation(disk_cache::Entry* entry, std::string* headers);
+ // the next range to be fetched.
+ void PrepareCacheValidation(disk_cache::Entry* entry,
+ HttpRequestHeaders* headers);
// Returns true if the current range is stored in the cache.
bool IsCurrentRangeCached() const;
@@ -105,18 +112,26 @@
void OnNetworkReadCompleted(int result);
private:
- static void AddRangeHeader(int64 start, int64 end, std::string* headers);
+ class Core;
+ // Returns the length to use when scanning the cache.
+ int GetNextRangeLen();
+
+ // Completion routine for our callback.
+ void GetAvailableRangeCompleted(int result, int64 start);
int64 current_range_start_;
int64 cached_start_;
int64 resource_size_;
int cached_min_len_;
HttpByteRange byte_range_; // The range requested by the user.
- std::string extra_headers_; // The clean set of extra headers (no ranges).
+ // The clean set of extra headers (no ranges).
+ HttpRequestHeaders extra_headers_;
bool range_present_; // True if next range entry is already stored.
bool final_range_;
bool sparse_entry_;
bool truncated_; // We have an incomplete 200 stored.
+ Core* core_;
+ CompletionCallback* callback_;
DISALLOW_COPY_AND_ASSIGN(PartialData);
};
diff --git a/net/http/url_security_manager.cc b/net/http/url_security_manager.cc
new file mode 100644
index 0000000..d3bc42e
--- /dev/null
+++ b/net/http/url_security_manager.cc
@@ -0,0 +1,22 @@
+// 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 "net/http/url_security_manager.h"
+
+#include "net/http/http_auth_filter.h"
+
+namespace net {
+
+URLSecurityManagerWhitelist::URLSecurityManagerWhitelist(
+ HttpAuthFilter* whitelist) : whitelist_(whitelist) {
+}
+
+bool URLSecurityManagerWhitelist::CanUseDefaultCredentials(
+ const GURL& auth_origin) {
+ if (whitelist_.get())
+ return whitelist_->IsValid(auth_origin, HttpAuth::AUTH_SERVER);
+ return false;
+}
+
+} // namespace net
diff --git a/net/http/url_security_manager.h b/net/http/url_security_manager.h
new file mode 100644
index 0000000..cd80a7d
--- /dev/null
+++ b/net/http/url_security_manager.h
@@ -0,0 +1,68 @@
+// 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.
+
+#ifndef NET_HTTP_URL_SECURITY_MANAGER_H_
+#define NET_HTTP_URL_SECURITY_MANAGER_H_
+
+#include "base/scoped_ptr.h"
+#include "base/basictypes.h"
+
+class GURL;
+
+namespace net {
+
+class HttpAuthFilter;
+
+// The URL security manager controls the policies (allow, deny, prompt user)
+// regarding URL actions (e.g., sending the default credentials to a server).
+class URLSecurityManager {
+ public:
+ URLSecurityManager() {}
+ virtual ~URLSecurityManager() {}
+
+ // Creates a platform-dependent instance of URLSecurityManager.
+ // The URLSecurityManager takes ownership of the HttpAuthFilter.
+ static URLSecurityManager* Create(HttpAuthFilter* whitelist);
+
+ // Returns true if we can send the default credentials to the server at
+ // |auth_origin| for HTTP NTLM or Negotiate authentication.
+ virtual bool CanUseDefaultCredentials(const GURL& auth_origin) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(URLSecurityManager);
+};
+
+class URLSecurityManagerWhitelist : public URLSecurityManager {
+ public:
+ // The URLSecurityManagerWhitelist takes ownership of the HttpAuthFilter.
+ explicit URLSecurityManagerWhitelist(HttpAuthFilter* whitelist);
+
+ // URLSecurityManager methods.
+ virtual bool CanUseDefaultCredentials(const GURL& auth_origin);
+
+ private:
+ scoped_ptr<HttpAuthFilter> whitelist_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLSecurityManagerWhitelist);
+};
+
+#if defined(UNIT_TEST)
+// An URLSecurityManager which always allows default credentials.
+class URLSecurityManagerAllow : public URLSecurityManager {
+ public:
+ URLSecurityManagerAllow() {}
+ virtual ~URLSecurityManagerAllow() {}
+
+ virtual bool CanUseDefaultCredentials(const GURL& auth_origin) {
+ return true;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(URLSecurityManagerAllow);
+};
+#endif // defined(UNIT_TEST)
+
+} // namespace net
+
+#endif // NET_HTTP_URL_SECURITY_MANAGER_H_
diff --git a/net/http/url_security_manager_posix.cc b/net/http/url_security_manager_posix.cc
new file mode 100644
index 0000000..931d9cc
--- /dev/null
+++ b/net/http/url_security_manager_posix.cc
@@ -0,0 +1,17 @@
+// 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 "net/http/url_security_manager.h"
+
+#include "net/http/http_auth_filter.h"
+
+namespace net {
+
+// static
+URLSecurityManager* URLSecurityManager::Create(
+ HttpAuthFilter* whitelist) {
+ return new URLSecurityManagerWhitelist(whitelist);
+}
+
+} // namespace net
diff --git a/net/http/url_security_manager_unittest.cc b/net/http/url_security_manager_unittest.cc
new file mode 100644
index 0000000..b08a7b2
--- /dev/null
+++ b/net/http/url_security_manager_unittest.cc
@@ -0,0 +1,88 @@
+// 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 "net/http/url_security_manager.h"
+
+#include "base/basictypes.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_auth_filter.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+struct TestData {
+ const char* url;
+ bool succeds_in_windows_default;
+ bool succeeds_in_whitelist;
+};
+
+const char* kTestAuthWhitelist = "*example.com,*foobar.com,baz";
+
+// Under Windows the following will be allowed by default:
+// localhost
+// host names without a period.
+// In Posix systems (or on Windows if a whitelist is specified explicitly),
+// everything depends on the whitelist.
+const TestData kTestDataList[] = {
+ { "http://localhost", true, false },
+ { "http://bat", true, false },
+ { "http://www.example.com", false, true },
+ { "http://example.com", false, true },
+ { "http://foobar.com", false, true },
+ { "http://boo.foobar.com", false, true },
+ { "http://baz", true, true },
+ { "http://www.exampl.com", false, false },
+ { "http://example.org", false, false },
+ { "http://foobar.net", false, false },
+ { "http://boo.fubar.com", false, false },
+};
+
+} // namespace
+
+// For Windows, This relies on the contents of the registry, so in theory it
+// might fail.
+TEST(URLSecurityManager, FLAKY_CreateNoWhitelist) {
+ scoped_ptr<URLSecurityManager> url_security_manager(
+ URLSecurityManager::Create(NULL));
+ ASSERT_TRUE(url_security_manager.get());
+
+ for (size_t i = 0; i < arraysize(kTestDataList); ++i) {
+ GURL gurl(kTestDataList[i].url);
+ bool can_use_default =
+ url_security_manager->CanUseDefaultCredentials(gurl);
+
+#if defined(OS_WIN)
+ EXPECT_EQ(kTestDataList[i].succeds_in_windows_default, can_use_default)
+ << " Run: " << i << " URL: '" << gurl << "'";
+#else
+ // No whitelist means nothing can use the default.
+ EXPECT_FALSE(can_use_default)
+ << " Run: " << i << " URL: '" << gurl << "'";
+#endif // OS_WIN
+ }
+}
+
+TEST(URLSecurityManager, CreateWhitelist) {
+ HttpAuthFilterWhitelist* auth_filter = new HttpAuthFilterWhitelist();
+ ASSERT_TRUE(auth_filter);
+ auth_filter->SetWhitelist(kTestAuthWhitelist);
+ // The URL security manager takes ownership of |auth_filter|.
+ scoped_ptr<URLSecurityManager> url_security_manager(
+ URLSecurityManager::Create(auth_filter));
+ ASSERT_TRUE(url_security_manager.get());
+
+ for (size_t i = 0; i < arraysize(kTestDataList); ++i) {
+ GURL gurl(kTestDataList[i].url);
+ bool can_use_default =
+ url_security_manager->CanUseDefaultCredentials(gurl);
+
+ EXPECT_EQ(kTestDataList[i].succeeds_in_whitelist, can_use_default)
+ << " Run: " << i << " URL: '" << gurl << "'";
+ }
+}
+
+} // namespace net
diff --git a/net/http/url_security_manager_win.cc b/net/http/url_security_manager_win.cc
new file mode 100644
index 0000000..4eaef78
--- /dev/null
+++ b/net/http/url_security_manager_win.cc
@@ -0,0 +1,114 @@
+// 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 "net/http/url_security_manager.h"
+
+#include <urlmon.h>
+#pragma comment(lib, "urlmon.lib")
+
+#include "base/scoped_comptr_win.h"
+#include "base/string_util.h"
+#include "googleurl/src/gurl.h"
+
+// The Windows implementation of URLSecurityManager uses WinINet/IE's
+// URL security zone manager. See the MSDN page "URL Security Zones" at
+// http://msdn.microsoft.com/en-us/library/ms537021(VS.85).aspx for more
+// info on the Internet Security Manager and Internet Zone Manager objects.
+//
+// On Windows, we honor the WinINet/IE settings and group policy related to
+// URL Security Zones. See the Microsoft Knowledge Base article 182569
+// "Internet Explorer security zones registry entries for advanced users"
+// (http://support.microsoft.com/kb/182569) for more info on these registry
+// keys.
+
+namespace net {
+
+class URLSecurityManagerWin : public URLSecurityManager {
+ public:
+ URLSecurityManagerWin();
+
+ // URLSecurityManager methods:
+ virtual bool CanUseDefaultCredentials(const GURL& auth_origin);
+
+ private:
+ ScopedComPtr<IInternetSecurityManager> security_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLSecurityManagerWin);
+};
+
+URLSecurityManagerWin::URLSecurityManagerWin() {
+}
+
+
+bool URLSecurityManagerWin::CanUseDefaultCredentials(
+ const GURL& auth_origin) {
+ if (!security_manager_) {
+ HRESULT hr = CoInternetCreateSecurityManager(NULL,
+ security_manager_.Receive(),
+ NULL);
+ if (FAILED(hr) || !security_manager_) {
+ LOG(ERROR) << "Unable to create the Windows Security Manager instance";
+ return false;
+ }
+ }
+
+ std::wstring url_w = ASCIIToWide(auth_origin.spec());
+ DWORD policy = 0;
+ HRESULT hr;
+ hr = security_manager_->ProcessUrlAction(url_w.c_str(),
+ URLACTION_CREDENTIALS_USE,
+ reinterpret_cast<BYTE*>(&policy),
+ sizeof(policy), NULL, 0,
+ PUAF_NOUI, 0);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "IInternetSecurityManager::ProcessUrlAction failed: " << hr;
+ return false;
+ }
+
+ // Four possible policies for URLACTION_CREDENTIALS_USE. See the MSDN page
+ // "About URL Security Zones" at
+ // http://msdn.microsoft.com/en-us/library/ms537183(VS.85).aspx
+ switch (policy) {
+ case URLPOLICY_CREDENTIALS_SILENT_LOGON_OK:
+ return true;
+ case URLPOLICY_CREDENTIALS_CONDITIONAL_PROMPT: {
+ // This policy means "prompt the user for permission if the resource is
+ // not located in the Intranet zone". TODO(wtc): Note that it's
+ // prompting for permission (to use the default credentials), as opposed
+ // to prompting the user to enter a user name and password.
+
+ // URLZONE_LOCAL_MACHINE 0
+ // URLZONE_INTRANET 1
+ // URLZONE_TRUSTED 2
+ // URLZONE_INTERNET 3
+ // URLZONE_UNTRUSTED 4
+ DWORD zone = 0;
+ hr = security_manager_->MapUrlToZone(url_w.c_str(), &zone, 0);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "IInternetSecurityManager::MapUrlToZone failed: " << hr;
+ return false;
+ }
+ return zone <= URLZONE_INTRANET;
+ }
+ case URLPOLICY_CREDENTIALS_MUST_PROMPT_USER:
+ return false;
+ case URLPOLICY_CREDENTIALS_ANONYMOUS_ONLY:
+ // TODO(wtc): we should fail the authentication.
+ return false;
+ default:
+ NOTREACHED();
+ return false;
+ }
+}
+
+// static
+URLSecurityManager* URLSecurityManager::Create(
+ HttpAuthFilter* whitelist) {
+ // If we have a whitelist, just use that.
+ if (whitelist)
+ return new URLSecurityManagerWhitelist(whitelist);
+ return new URLSecurityManagerWin();
+}
+
+} // namespace net
diff --git a/net/http_listen_socket.target.mk b/net/http_listen_socket.target.mk
new file mode 100644
index 0000000..9a98489
--- /dev/null
+++ b/net/http_listen_socket.target.mk
@@ -0,0 +1,154 @@
+# This file is generated by gyp; do not edit.
+
+TOOLSET := target
+TARGET := http_listen_socket
+DEFS_Debug := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-DUNIT_TEST' \
+ '-DGTEST_HAS_RTTI=0' \
+ '-D__STDC_FORMAT_MACROS' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=1' \
+ '-D_DEBUG'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Debug := -Werror \
+ -pthread \
+ -fno-exceptions \
+ -Wall \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/atk-1.0 \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -O0 \
+ -g
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Debug :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Debug := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Debug := -I. \
+ -Itesting/gtest/include
+
+DEFS_Release := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-DUNIT_TEST' \
+ '-DGTEST_HAS_RTTI=0' \
+ '-D__STDC_FORMAT_MACROS' \
+ '-DNDEBUG' \
+ '-DNVALGRIND' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=0'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Release := -Werror \
+ -pthread \
+ -fno-exceptions \
+ -Wall \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/atk-1.0 \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -O2 \
+ -fno-ident \
+ -fdata-sections \
+ -ffunction-sections
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Release :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Release := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Release := -I. \
+ -Itesting/gtest/include
+
+OBJS := $(obj).target/$(TARGET)/net/server/http_listen_socket.o
+
+# Add to the list of files we specially track dependencies for.
+all_deps += $(OBJS)
+
+# CFLAGS et al overrides must be target-local.
+# See "Target-specific Variable Values" in the GNU Make manual.
+$(OBJS): TOOLSET := $(TOOLSET)
+$(OBJS): GYP_CFLAGS := $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_C_$(BUILDTYPE)) $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE))
+$(OBJS): GYP_CXXFLAGS := $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_CC_$(BUILDTYPE)) $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE))
+
+# Suffix rules, putting all outputs into $(obj).
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(srcdir)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+# Try building from generated source, too.
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj).$(TOOLSET)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+# End of this set of suffix rules
+### Rules for final target.
+LDFLAGS_Debug := -pthread \
+ -Wl,-z,noexecstack \
+ -rdynamic
+
+LDFLAGS_Release := -pthread \
+ -Wl,-z,noexecstack \
+ -Wl,--gc-sections
+
+LIBS :=
+
+$(obj).target/net/libhttp_listen_socket.a: GYP_LDFLAGS := $(LDFLAGS_$(BUILDTYPE))
+$(obj).target/net/libhttp_listen_socket.a: LIBS := $(LIBS)
+$(obj).target/net/libhttp_listen_socket.a: TOOLSET := $(TOOLSET)
+$(obj).target/net/libhttp_listen_socket.a: $(OBJS) FORCE_DO_CMD
+ $(call do_cmd,alink)
+
+all_deps += $(obj).target/net/libhttp_listen_socket.a
+# Add target alias
+.PHONY: http_listen_socket
+http_listen_socket: $(obj).target/net/libhttp_listen_socket.a
+
+# Add target alias to "all" target.
+.PHONY: all
+all: http_listen_socket
+
diff --git a/net/net.Makefile b/net/net.Makefile
new file mode 100644
index 0000000..3ac1873
--- /dev/null
+++ b/net/net.Makefile
@@ -0,0 +1,6 @@
+# This file is generated by gyp; do not edit.
+
+export builddir_name ?= /usr/local/google/src/chromium-merge/src/net/out
+.PHONY: all
+all:
+ $(MAKE) -C .. net_resources net_base tld_cleanup net hresolv net_test_support fetch_client http_listen_socket fetch_server run_testserver net_perftests crash_cache net_unittests stress_cache
diff --git a/net/net.gyp b/net/net.gyp
old mode 100755
new mode 100644
index 37e89a8..1dcaf25
--- a/net/net.gyp
+++ b/net/net.gyp
@@ -24,8 +24,12 @@
'base/address_family.h',
'base/address_list.cc',
'base/address_list.h',
+ 'base/address_list_net_log_param.cc',
+ 'base/address_list_net_log_param.h',
'base/auth.h',
'base/cache_type.h',
+ 'base/capturing_net_log.cc',
+ 'base/capturing_net_log.h',
'base/cert_database.h',
'base/cert_database_mac.cc',
'base/cert_database_nss.cc',
@@ -58,22 +62,24 @@
'base/file_stream_win.cc',
'base/filter.cc',
'base/filter.h',
- 'base/fixed_host_resolver.cc',
- 'base/fixed_host_resolver.h',
+ 'base/forwarding_net_log.cc',
+ 'base/forwarding_net_log.h',
'base/gzip_filter.cc',
'base/gzip_filter.h',
'base/gzip_header.cc',
'base/gzip_header.h',
'base/host_cache.cc',
'base/host_cache.h',
+ 'base/host_mapping_rules.cc',
+ 'base/host_mapping_rules.h',
+ 'base/host_port_pair.cc',
+ 'base/host_port_pair.h',
'base/host_resolver.cc',
'base/host_resolver.h',
'base/host_resolver_impl.cc',
'base/host_resolver_impl.h',
'base/host_resolver_proc.cc',
'base/host_resolver_proc.h',
- 'base/https_prober.h',
- 'base/https_prober.cc',
'base/io_buffer.cc',
'base/io_buffer.h',
'base/keygen_handler.h',
@@ -83,12 +89,10 @@
'base/listen_socket.cc',
'base/listen_socket.h',
'base/load_flags.h',
- 'base/load_log.h',
- 'base/load_log.cc',
- 'base/load_log_event_type_list.h',
- 'base/load_log_util.cc',
- 'base/load_log_util.h',
+ 'base/load_flags_list.h',
'base/load_states.h',
+ 'base/mapped_host_resolver.cc',
+ 'base/mapped_host_resolver.h',
'base/mime_sniffer.cc',
'base/mime_sniffer.h',
'base/mime_util.cc',
@@ -99,6 +103,10 @@
'base/net_error_list.h',
'base/net_errors.cc',
'base/net_errors.h',
+ 'base/net_log.cc',
+ 'base/net_log.h',
+ 'base/net_log_event_type_list.h',
+ 'base/net_log_source_type_list.h',
'base/net_module.cc',
'base/net_module.h',
'base/net_util.cc',
@@ -107,12 +115,12 @@
'base/net_util_win.cc',
'base/network_change_notifier.cc',
'base/network_change_notifier.h',
- 'base/network_change_notifier_helper.cc',
- 'base/network_change_notifier_helper.h',
'base/network_change_notifier_linux.cc',
'base/network_change_notifier_linux.h',
'base/network_change_notifier_mac.cc',
'base/network_change_notifier_mac.h',
+ 'base/network_change_notifier_netlink_linux.cc',
+ 'base/network_change_notifier_netlink_linux.h',
'base/network_change_notifier_win.cc',
'base/network_change_notifier_win.h',
'base/nss_memio.c',
@@ -130,6 +138,8 @@
'base/sdch_manager.cc',
'base/sdch_manager.h',
'base/ssl_cert_request_info.h',
+ 'base/ssl_cipher_suite_names.cc',
+ 'base/ssl_cipher_suite_names.h',
'base/ssl_client_auth_cache.cc',
'base/ssl_client_auth_cache.h',
'base/ssl_config_service.cc',
@@ -159,18 +169,33 @@
'base/x509_certificate_mac.cc',
'base/x509_certificate_nss.cc',
'base/x509_certificate_win.cc',
+ 'base/x509_cert_types.cc',
+ 'base/x509_cert_types.h',
+ 'base/x509_cert_types_mac.cc',
+ 'third_party/mozilla_security_manager/nsKeygenHandler.cpp',
+ 'third_party/mozilla_security_manager/nsKeygenHandler.h',
],
'export_dependent_settings': [
'../base/base.gyp:base',
],
'conditions': [
[ 'OS == "linux" or OS == "freebsd" or OS == "openbsd"', {
- 'dependencies': [
- '../build/linux/system.gyp:gconf',
- '../build/linux/system.gyp:gdk',
- '../build/linux/system.gyp:nss',
- ],
- }],
+ 'dependencies': [
+ '../build/linux/system.gyp:gconf',
+ '../build/linux/system.gyp:gdk',
+ '../build/linux/system.gyp:nss',
+ ],
+ },
+ { # else: OS is not in the above list
+ 'sources!': [
+ 'base/cert_database_nss.cc',
+ 'base/keygen_handler_nss.cc',
+ 'base/x509_certificate_nss.cc',
+ 'third_party/mozilla_security_manager/nsKeygenHandler.cpp',
+ 'third_party/mozilla_security_manager/nsKeygenHandler.h',
+ ],
+ },
+ ],
[ 'OS == "win"', {
'dependencies': [
# For nss_memio.{c,h}, which require only NSPR.
@@ -179,27 +204,18 @@
],
},
{ # else: OS != "win"
+ 'dependencies': [
+ '../third_party/libevent/libevent.gyp:libevent',
+ ],
'sources!': [
'base/winsock_init.cc',
],
},
],
- [ 'OS == "linux" or OS == "freebsd" or OS == "openbsd"', {
- },
- { # else: OS is not in the above list
- 'sources!': [
- 'base/cert_database_nss.cc',
- 'base/keygen_handler_nss.cc',
- 'base/x509_certificate_nss.cc',
- ],
- },
- ],
[ 'OS == "mac"', {
- 'sources!': [
- # TODO(wtc): Remove nss_memio.{c,h} when http://crbug.com/30689
- # is fixed.
- 'base/nss_memio.c',
- 'base/nss_memio.h',
+ 'dependencies': [
+ # For nss_memio.{c,h}, which require only NSPR.
+ '../third_party/nss/nss.gyp:nspr',
],
'link_settings': {
'libraries': [
@@ -253,6 +269,10 @@
'disk_cache/hash.cc',
'disk_cache/hash.h',
'disk_cache/histogram_macros.h',
+ 'disk_cache/in_flight_backend_io.cc',
+ 'disk_cache/in_flight_backend_io.h',
+ 'disk_cache/in_flight_io.cc',
+ 'disk_cache/in_flight_io.h',
'disk_cache/mapped_file.h',
'disk_cache/mapped_file_posix.cc',
'disk_cache/mapped_file_win.cc',
@@ -274,23 +294,6 @@
'disk_cache/storage_block.h',
'disk_cache/trace.cc',
'disk_cache/trace.h',
- 'flip/flip_bitmasks.h',
- 'flip/flip_frame_builder.cc',
- 'flip/flip_frame_builder.h',
- 'flip/flip_framer.cc',
- 'flip/flip_framer.h',
- 'flip/flip_io_buffer.cc',
- 'flip/flip_io_buffer.h',
- 'flip/flip_network_transaction.cc',
- 'flip/flip_network_transaction.h',
- 'flip/flip_protocol.h',
- 'flip/flip_session.cc',
- 'flip/flip_session.h',
- 'flip/flip_session_pool.cc',
- 'flip/flip_session_pool.h',
- 'flip/flip_stream.cc',
- 'flip/flip_stream.h',
- 'flip/flip_transaction_factory.h',
'ftp/ftp_auth_cache.cc',
'ftp/ftp_auth_cache.h',
'ftp/ftp_ctrl_response_buffer.cc',
@@ -324,20 +327,30 @@
'ftp/ftp_util.h',
'http/des.cc',
'http/des.h',
+ 'http/http_alternate_protocols.cc',
+ 'http/http_alternate_protocols.h',
'http/http_atom_list.h',
'http/http_auth.cc',
'http/http_auth.h',
'http/http_auth_cache.cc',
'http/http_auth_cache.h',
- 'http/http_auth_handler.h',
+ 'http/http_auth_controller.cc',
+ 'http/http_auth_controller.h',
+ 'http/http_auth_filter.cc',
+ 'http/http_auth_filter.h',
+ 'http/http_auth_filter_win.h',
+ 'http/http_auth_gssapi_posix.cc',
+ 'http/http_auth_gssapi_posix.h',
'http/http_auth_handler.cc',
+ 'http/http_auth_handler.h',
'http/http_auth_handler_basic.cc',
'http/http_auth_handler_basic.h',
'http/http_auth_handler_digest.cc',
'http/http_auth_handler_digest.h',
+ 'http/http_auth_handler_factory.cc',
+ 'http/http_auth_handler_factory.h',
'http/http_auth_handler_negotiate.h',
- 'http/http_auth_handler_negotiate_posix.cc',
- 'http/http_auth_handler_negotiate_win.cc',
+ 'http/http_auth_handler_negotiate.cc',
'http/http_auth_handler_ntlm.cc',
'http/http_auth_handler_ntlm.h',
'http/http_auth_handler_ntlm_portable.cc',
@@ -354,12 +367,16 @@
'http/http_cache_transaction.h',
'http/http_chunked_decoder.cc',
'http/http_chunked_decoder.h',
+ 'http/http_net_log_params.h',
+ 'http/http_network_delegate.h',
'http/http_network_layer.cc',
'http/http_network_layer.h',
'http/http_network_session.cc',
'http/http_network_session.h',
'http/http_network_transaction.cc',
'http/http_network_transaction.h',
+ 'http/http_request_headers.cc',
+ 'http/http_request_headers.h',
'http/http_request_info.h',
'http/http_response_headers.cc',
'http/http_response_headers.h',
@@ -370,6 +387,14 @@
'http/http_stream_parser.h',
'http/http_transaction.h',
'http/http_transaction_factory.h',
+ 'http/url_security_manager.h',
+ 'http/url_security_manager.cc',
+ 'http/url_security_manager_posix.cc',
+ 'http/url_security_manager_win.cc',
+ 'http/http_proxy_client_socket.cc',
+ 'http/http_proxy_client_socket.h',
+ 'http/http_proxy_client_socket_pool.cc',
+ 'http/http_proxy_client_socket_pool.h',
'http/http_util.cc',
'http/http_util_icu.cc',
'http/http_util.h',
@@ -384,6 +409,10 @@
'ocsp/nss_ocsp.h',
'proxy/init_proxy_resolver.cc',
'proxy/init_proxy_resolver.h',
+ 'proxy/multi_threaded_proxy_resolver.cc',
+ 'proxy/multi_threaded_proxy_resolver.h',
+ 'proxy/proxy_bypass_rules.cc',
+ 'proxy/proxy_bypass_rules.h',
'proxy/proxy_config.cc',
'proxy/proxy_config.h',
'proxy/proxy_config_service.h',
@@ -403,7 +432,10 @@
'proxy/proxy_resolver_js_bindings.h',
'proxy/proxy_resolver_mac.cc',
'proxy/proxy_resolver_mac.h',
+ 'proxy/proxy_resolver_request_context.h',
'proxy/proxy_resolver_script.h',
+ 'proxy/proxy_resolver_script_data.cc',
+ 'proxy/proxy_resolver_script_data.h',
'proxy/proxy_resolver_v8.cc',
'proxy/proxy_resolver_v8.h',
'proxy/proxy_resolver_winhttp.cc',
@@ -416,31 +448,39 @@
'proxy/proxy_server.h',
'proxy/proxy_service.cc',
'proxy/proxy_service.h',
- 'proxy/single_threaded_proxy_resolver.cc',
- 'proxy/single_threaded_proxy_resolver.h',
+ 'proxy/sync_host_resolver_bridge.cc',
+ 'proxy/sync_host_resolver_bridge.h',
'socket/client_socket.h',
'socket/client_socket_factory.cc',
'socket/client_socket_factory.h',
'socket/client_socket_handle.cc',
'socket/client_socket_handle.h',
'socket/client_socket_pool.h',
+ 'socket/client_socket_pool.cc',
'socket/client_socket_pool_base.cc',
'socket/client_socket_pool_base.h',
+ 'socket/client_socket_pool_histograms.cc',
+ 'socket/client_socket_pool_histograms.h',
'socket/socket.h',
'socket/socks5_client_socket.cc',
'socket/socks5_client_socket.h',
'socket/socks_client_socket.cc',
'socket/socks_client_socket.h',
+ 'socket/socks_client_socket_pool.cc',
+ 'socket/socks_client_socket_pool.h',
'socket/ssl_client_socket.h',
'socket/ssl_client_socket_mac.cc',
'socket/ssl_client_socket_mac.h',
- 'socket/ssl_client_socket_nss_factory.cc',
+ 'socket/ssl_client_socket_mac_factory.cc',
+ 'socket/ssl_client_socket_mac_factory.h',
'socket/ssl_client_socket_nss.cc',
'socket/ssl_client_socket_nss.h',
+ 'socket/ssl_client_socket_nss_factory.cc',
+ 'socket/ssl_client_socket_nss_factory.h',
+ 'socket/ssl_client_socket_pool.cc',
+ 'socket/ssl_client_socket_pool.h',
'socket/ssl_client_socket_win.cc',
'socket/ssl_client_socket_win.h',
- 'socket/ssl_test_util.cc',
- 'socket/ssl_test_util.h',
'socket/tcp_client_socket.h',
'socket/tcp_client_socket_libevent.cc',
'socket/tcp_client_socket_libevent.h',
@@ -451,11 +491,35 @@
'socket/tcp_pinger.h',
'socket_stream/socket_stream.cc',
'socket_stream/socket_stream.h',
+ 'socket_stream/socket_stream_job.cc',
+ 'socket_stream/socket_stream_job.h',
+ 'socket_stream/socket_stream_job_manager.cc',
+ 'socket_stream/socket_stream_job_manager.h',
'socket_stream/socket_stream_metrics.cc',
'socket_stream/socket_stream_metrics.h',
- 'socket_stream/socket_stream_throttle.cc',
- 'socket_stream/socket_stream_throttle.h',
- 'url_request/request_tracker.h',
+ 'spdy/spdy_bitmasks.h',
+ 'spdy/spdy_frame_builder.cc',
+ 'spdy/spdy_frame_builder.h',
+ 'spdy/spdy_framer.cc',
+ 'spdy/spdy_framer.h',
+ 'spdy/spdy_http_stream.cc',
+ 'spdy/spdy_http_stream.h',
+ 'spdy/spdy_io_buffer.cc',
+ 'spdy/spdy_io_buffer.h',
+ 'spdy/spdy_network_transaction.cc',
+ 'spdy/spdy_network_transaction.h',
+ 'spdy/spdy_protocol.h',
+ 'spdy/spdy_session.cc',
+ 'spdy/spdy_session.h',
+ 'spdy/spdy_session_pool.cc',
+ 'spdy/spdy_session_pool.h',
+ 'spdy/spdy_settings_storage.cc',
+ 'spdy/spdy_settings_storage.h',
+ 'spdy/spdy_stream.cc',
+ 'spdy/spdy_stream.h',
+ 'spdy/spdy_transaction_factory.h',
+ 'url_request/https_prober.h',
+ 'url_request/https_prober.cc',
'url_request/url_request.cc',
'url_request/url_request.h',
'url_request/url_request_about_job.cc',
@@ -471,6 +535,8 @@
'url_request/url_request_file_job.h',
'url_request/url_request_filter.cc',
'url_request/url_request_filter.h',
+ 'url_request/url_request_ftp_job.cc',
+ 'url_request/url_request_ftp_job.h',
'url_request/url_request_http_job.cc',
'url_request/url_request_http_job.h',
'url_request/url_request_job.cc',
@@ -481,8 +547,8 @@
'url_request/url_request_job_metrics.h',
'url_request/url_request_job_tracker.cc',
'url_request/url_request_job_tracker.h',
- 'url_request/url_request_new_ftp_job.cc',
- 'url_request/url_request_new_ftp_job.h',
+ 'url_request/url_request_netlog_params.cc',
+ 'url_request/url_request_netlog_params.h',
'url_request/url_request_redirect_job.cc',
'url_request/url_request_redirect_job.h',
'url_request/url_request_simple_job.cc',
@@ -490,12 +556,20 @@
'url_request/url_request_status.h',
'url_request/url_request_test_job.cc',
'url_request/url_request_test_job.h',
- 'url_request/url_request_view_net_internals_job.cc',
- 'url_request/url_request_view_net_internals_job.h',
'url_request/view_cache_helper.cc',
'url_request/view_cache_helper.h',
'websockets/websocket.cc',
'websockets/websocket.h',
+ 'websockets/websocket_frame_handler.cc',
+ 'websockets/websocket_frame_handler.h',
+ 'websockets/websocket_handshake.cc',
+ 'websockets/websocket_handshake.h',
+ 'websockets/websocket_handshake_draft75.cc',
+ 'websockets/websocket_handshake_draft75.h',
+ 'websockets/websocket_handshake_handler.cc',
+ 'websockets/websocket_handshake_handler.h',
+ 'websockets/websocket_job.cc',
+ 'websockets/websocket_job.h',
'websockets/websocket_throttle.cc',
'websockets/websocket_throttle.h',
],
@@ -509,12 +583,19 @@
],
}],
[ 'OS == "linux" or OS == "freebsd" or OS == "openbsd"', {
- 'dependencies': [
- '../build/linux/system.gyp:gconf',
- '../build/linux/system.gyp:gdk',
- '../build/linux/system.gyp:nss',
- ],
- }],
+ 'dependencies': [
+ '../build/linux/system.gyp:gconf',
+ '../build/linux/system.gyp:gdk',
+ '../build/linux/system.gyp:nss',
+ ],
+ },
+ { # else: OS is not in the above list
+ 'sources!': [
+ 'ocsp/nss_ocsp.cc',
+ 'ocsp/nss_ocsp.h',
+ ],
+ },
+ ],
[ 'OS == "win"', {
'sources!': [
'http/http_auth_handler_ntlm_portable.cc',
@@ -522,32 +603,25 @@
],
'dependencies': [
'../third_party/nss/nss.gyp:nss',
- 'third_party/nss/nss.gyp:ssl',
+ 'third_party/nss/ssl.gyp:ssl',
'tld_cleanup',
],
},
{ # else: OS != "win"
+ 'dependencies': [
+ '../third_party/libevent/libevent.gyp:libevent',
+ ],
'sources!': [
'proxy/proxy_resolver_winhttp.cc',
'socket/ssl_client_socket_nss_factory.cc',
- ],
- },
- ],
- [ 'OS == "linux" or OS == "freebsd" or OS == "openbsd"', {
- },
- { # else: OS != "linux"
- 'sources!': [
- 'ocsp/nss_ocsp.cc',
- 'ocsp/nss_ocsp.h',
+ 'socket/ssl_client_socket_nss_factory.h',
],
},
],
[ 'OS == "mac"', {
- 'sources!': [
- # TODO(wtc): Remove ssl_client_socket_nss.{cc,h} when
- # http://crbug.com/30689 is fixed.
- 'socket/ssl_client_socket_nss.cc',
- 'socket/ssl_client_socket_nss.h',
+ 'dependencies': [
+ '../third_party/nss/nss.gyp:nss',
+ 'third_party/nss/ssl.gyp:ssl',
],
'link_settings': {
'libraries': [
@@ -556,6 +630,12 @@
]
},
},
+ { # else: OS != "mac"
+ 'sources!': [
+ 'socket/ssl_client_socket_mac_factory.cc',
+ 'socket/ssl_client_socket_mac_factory.h',
+ ],
+ },
],
],
},
@@ -582,22 +662,26 @@
'base/file_stream_unittest.cc',
'base/filter_unittest.cc',
'base/filter_unittest.h',
+ 'base/forwarding_net_log_unittest.cc',
'base/gzip_filter_unittest.cc',
'base/host_cache_unittest.cc',
+ 'base/host_mapping_rules_unittest.cc',
'base/host_resolver_impl_unittest.cc',
- 'base/load_log_unittest.cc',
- 'base/load_log_unittest.h',
- 'base/load_log_util_unittest.cc',
+ 'base/keygen_handler_unittest.cc',
+ 'base/leak_annotations.h',
'base/listen_socket_unittest.cc',
'base/listen_socket_unittest.h',
+ 'base/mapped_host_resolver_unittest.cc',
'base/mime_sniffer_unittest.cc',
'base/mime_util_unittest.cc',
- 'base/mock_network_change_notifier.h',
+ 'base/net_log_unittest.h',
'base/net_test_constants.h',
+ 'base/net_test_suite.h',
'base/net_util_unittest.cc',
'base/registry_controlled_domain_unittest.cc',
'base/run_all_unittests.cc',
'base/sdch_filter_unittest.cc',
+ 'base/ssl_cipher_suite_names_unittest.cc',
'base/ssl_client_auth_cache_unittest.cc',
'base/ssl_config_service_mac_unittest.cc',
'base/ssl_config_service_win_unittest.cc',
@@ -606,7 +690,9 @@
'base/telnet_server_unittest.cc',
'base/test_certificate_data.h',
'base/test_completion_callback_unittest.cc',
+ 'base/upload_data_stream_unittest.cc',
'base/x509_certificate_unittest.cc',
+ 'base/x509_cert_types_unittest.cc',
'disk_cache/addr_unittest.cc',
'disk_cache/backend_unittest.cc',
'disk_cache/bitmap_unittest.cc',
@@ -627,14 +713,17 @@
'ftp/ftp_network_transaction_unittest.cc',
'ftp/ftp_util_unittest.cc',
'http/des_unittest.cc',
- 'flip/flip_framer_test.cc',
- 'flip/flip_network_transaction_unittest.cc',
- 'flip/flip_protocol_test.cc',
- 'flip/flip_session_unittest.cc',
- 'flip/flip_stream_unittest.cc',
+ 'http/http_alternate_protocols_unittest.cc',
'http/http_auth_cache_unittest.cc',
+ 'http/http_auth_filter_unittest.cc',
+ 'http/http_auth_gssapi_posix_unittest.cc',
'http/http_auth_handler_basic_unittest.cc',
'http/http_auth_handler_digest_unittest.cc',
+ 'http/http_auth_handler_factory_unittest.cc',
+ 'http/http_auth_handler_mock.cc',
+ 'http/http_auth_handler_mock.h',
+ 'http/http_auth_handler_negotiate_unittest.cc',
+ 'http/http_auth_handler_unittest.cc',
'http/http_auth_sspi_win_unittest.cc',
'http/http_auth_unittest.cc',
'http/http_byte_range_unittest.cc',
@@ -642,13 +731,22 @@
'http/http_chunked_decoder_unittest.cc',
'http/http_network_layer_unittest.cc',
'http/http_network_transaction_unittest.cc',
+ 'http/http_proxy_client_socket_pool_unittest.cc',
+ 'http/http_request_headers_unittest.cc',
'http/http_response_headers_unittest.cc',
'http/http_transaction_unittest.cc',
'http/http_transaction_unittest.h',
'http/http_util_unittest.cc',
'http/http_vary_data_unittest.cc',
+ 'http/mock_gssapi_library_posix.cc',
+ 'http/mock_gssapi_library_posix.h',
+ 'http/mock_sspi_library_win.h',
+ 'http/mock_sspi_library_win.cc',
+ 'http/url_security_manager_unittest.cc',
'proxy/init_proxy_resolver_unittest.cc',
'proxy/mock_proxy_resolver.h',
+ 'proxy/multi_threaded_proxy_resolver_unittest.cc',
+ 'proxy/proxy_bypass_rules_unittest.cc',
'proxy/proxy_config_service_linux_unittest.cc',
'proxy/proxy_config_service_win_unittest.cc',
'proxy/proxy_config_unittest.cc',
@@ -658,19 +756,38 @@
'proxy/proxy_script_fetcher_unittest.cc',
'proxy/proxy_server_unittest.cc',
'proxy/proxy_service_unittest.cc',
- 'proxy/single_threaded_proxy_resolver_unittest.cc',
+ 'proxy/sync_host_resolver_bridge_unittest.cc',
'socket/client_socket_pool_base_unittest.cc',
'socket/socks5_client_socket_unittest.cc',
+ 'socket/socks_client_socket_pool_unittest.cc',
'socket/socks_client_socket_unittest.cc',
'socket/ssl_client_socket_unittest.cc',
+ 'socket/ssl_client_socket_pool_unittest.cc',
'socket/tcp_client_socket_pool_unittest.cc',
'socket/tcp_client_socket_unittest.cc',
'socket/tcp_pinger_unittest.cc',
'socket_stream/socket_stream_metrics_unittest.cc',
'socket_stream/socket_stream_unittest.cc',
- 'url_request/request_tracker_unittest.cc',
+ 'spdy/spdy_framer_test.cc',
+ 'spdy/spdy_http_stream_unittest.cc',
+ 'spdy/spdy_network_transaction_unittest.cc',
+ 'spdy/spdy_protocol_test.cc',
+ 'spdy/spdy_session_unittest.cc',
+ 'spdy/spdy_stream_unittest.cc',
+ 'spdy/spdy_test_util.cc',
+ 'spdy/spdy_test_util.h',
+ 'tools/dump_cache/url_to_filename_encoder.cc',
+ 'tools/dump_cache/url_to_filename_encoder.h',
+ 'tools/dump_cache/url_to_filename_encoder_unittest.cc',
+ 'url_request/url_request_job_tracker_unittest.cc',
'url_request/url_request_unittest.cc',
'url_request/url_request_unittest.h',
+ 'url_request/view_cache_helper_unittest.cc',
+ 'websockets/websocket_frame_handler_unittest.cc',
+ 'websockets/websocket_handshake_draft75_unittest.cc',
+ 'websockets/websocket_handshake_handler_unittest.cc',
+ 'websockets/websocket_handshake_unittest.cc',
+ 'websockets/websocket_job_unittest.cc',
'websockets/websocket_throttle_unittest.cc',
'websockets/websocket_unittest.cc',
],
@@ -678,6 +795,7 @@
[ 'OS == "linux" or OS == "freebsd" or OS == "openbsd"', {
'dependencies': [
'../build/linux/system.gyp:gtk',
+ '../build/linux/system.gyp:nss',
],
'sources!': [
'base/sdch_filter_unittest.cc',
@@ -693,9 +811,12 @@
}],
],
}],
- # This is needed to trigger the dll copy step on windows.
- # TODO(mark): Specifying this here shouldn't be necessary.
[ 'OS == "win"', {
+ 'sources!': [
+ 'http/http_auth_gssapi_posix_unittest.cc',
+ ],
+ # This is needed to trigger the dll copy step on windows.
+ # TODO(mark): Specifying this here shouldn't be necessary.
'dependencies': [
'../third_party/icu/icu.gyp:icudata',
],
@@ -771,6 +892,20 @@
],
},
{
+ 'target_name': 'run_testserver',
+ 'type': 'executable',
+ 'dependencies': [
+ 'net',
+ 'net_test_support',
+ '../base/base.gyp:base',
+ '../testing/gtest.gyp:gtest',
+ ],
+ 'msvs_guid': '506F2468-6B1D-48E2-A67C-9D9C6BAC0EC5',
+ 'sources': [
+ 'tools/testserver/run_testserver.cc',
+ ],
+ },
+ {
'target_name': 'net_test_support',
'type': '<(library)',
'dependencies': [
@@ -779,37 +914,71 @@
'../testing/gtest.gyp:gtest',
],
'sources': [
+ 'base/cert_test_util.cc',
+ 'base/cert_test_util.h',
'disk_cache/disk_cache_test_util.cc',
'disk_cache/disk_cache_test_util.h',
'proxy/proxy_config_service_common_unittest.cc',
'proxy/proxy_config_service_common_unittest.h',
'socket/socket_test_util.cc',
'socket/socket_test_util.h',
+ 'test/test_server.cc',
+ 'test/test_server.h',
+ ],
+ 'conditions': [
+ ['inside_chromium_build==1', {
+ 'dependencies': [
+ '../chrome/browser/sync/protocol/sync_proto.gyp:sync_proto',
+ '../third_party/protobuf2/protobuf.gyp:py_proto',
+ ],
+ }],
+ ['OS == "linux" or OS == "freebsd" or OS == "openbsd"', {
+ 'dependencies': [
+ '../build/linux/system.gyp:nss',
+ ],
+ }],
+ ['OS == "linux"', {
+ 'conditions': [
+ ['linux_use_tcmalloc==1', {
+ 'dependencies': [
+ '../base/allocator/allocator.gyp:allocator',
+ ],
+ }],
+ ],
+ }],
],
},
{
'target_name': 'net_resources',
'type': 'none',
'msvs_guid': '8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942',
+ 'variables': {
+ 'grit_cmd': ['python', '../tools/grit/grit.py'],
+ 'grit_info_cmd': ['python', '../tools/grit/grit_info.py'],
+ 'input_paths': [
+ 'base/net_resources.grd',
+ ],
+ },
'rules': [
{
'rule_name': 'grit',
'extension': 'grd',
'inputs': [
- '../tools/grit/grit.py',
+ '<!@(<(grit_info_cmd) --inputs <(input_paths))',
],
'outputs': [
- '<(SHARED_INTERMEDIATE_DIR)/net/grit/<(RULE_INPUT_ROOT).h',
- '<(SHARED_INTERMEDIATE_DIR)/net/<(RULE_INPUT_ROOT).rc',
- '<(SHARED_INTERMEDIATE_DIR)/net/<(RULE_INPUT_ROOT).pak',
+ '<!@(<(grit_info_cmd) '
+ '--outputs \'<(SHARED_INTERMEDIATE_DIR)/net\' <(input_paths))',
],
'action':
- ['python', '<@(_inputs)', '-i', '<(RULE_INPUT_PATH)', 'build', '-o', '<(SHARED_INTERMEDIATE_DIR)/net'],
+ ['<@(grit_cmd)',
+ '-i', '<(RULE_INPUT_PATH)', 'build',
+ '-o', '<(SHARED_INTERMEDIATE_DIR)/net'],
'message': 'Generating resources from <(RULE_INPUT_PATH)',
},
],
'sources': [
- 'base/net_resources.grd',
+ '<@(input_paths)',
],
'direct_dependent_settings': {
'include_dirs': [
@@ -857,6 +1026,21 @@
],
},
{
+ 'target_name': 'http_listen_socket',
+ 'type': '<(library)',
+ 'dependencies': [
+ 'net',
+ '../base/base.gyp:base',
+ '../testing/gtest.gyp:gtest',
+ ],
+ 'msvs_guid': 'FCB894A4-CC6C-48C2-B495-52C80527E9BE',
+ 'sources': [
+ 'server/http_listen_socket.cc',
+ 'server/http_listen_socket.h',
+ 'server/http_server_request_info.h',
+ ],
+ },
+ {
'target_name': 'hresolv',
'type': 'executable',
'dependencies': [
@@ -869,6 +1053,56 @@
},
],
'conditions': [
+ # ['OS=="linux"', {
+ # 'targets': [
+ # {
+ # 'target_name': 'flip_in_mem_edsm_server',
+ # 'type': 'executable',
+ # 'dependencies': [
+ # '../base/base.gyp:base',
+ # 'net.gyp:net',
+ # ],
+ # 'link_settings': {
+ # 'ldflags': [
+ # '-lssl'
+ # ],
+ # 'libraries': [
+ # '-lssl'
+ # ],
+ # },
+ # 'sources': [
+ # 'tools/flip_server/balsa_enums.h',
+ # 'tools/flip_server/balsa_frame.cc',
+ # 'tools/flip_server/balsa_frame.h',
+ # 'tools/flip_server/balsa_headers.cc',
+ # 'tools/flip_server/balsa_headers.h',
+ # 'tools/flip_server/balsa_headers_token_utils.cc',
+ # 'tools/flip_server/balsa_headers_token_utils.h',
+ # 'tools/flip_server/balsa_visitor_interface.h',
+ # 'tools/flip_server/buffer_interface.h',
+ # 'tools/flip_server/create_listener.cc',
+ # 'tools/flip_server/create_listener.h',
+ # 'tools/flip_server/epoll_server.cc',
+ # 'tools/flip_server/epoll_server.h',
+ # 'tools/flip_server/flip_in_mem_edsm_server.cc',
+ # 'tools/flip_server/http_message_constants.cc',
+ # 'tools/flip_server/http_message_constants.h',
+ # 'tools/flip_server/loadtime_measurement.h',
+ # 'tools/flip_server/porting.txt',
+ # 'tools/flip_server/ring_buffer.cc',
+ # 'tools/flip_server/ring_buffer.h',
+ # 'tools/flip_server/simple_buffer.cc',
+ # 'tools/flip_server/simple_buffer.h',
+ # 'tools/flip_server/split.h',
+ # 'tools/flip_server/split.cc',
+ # 'tools/flip_server/string_piece_utils.h',
+ # 'tools/flip_server/thread.h',
+ # 'tools/flip_server/url_to_filename_encoder.h',
+ # 'tools/flip_server/url_utilities.h',
+ # ],
+ # },
+ # ]
+ # }],
['OS=="win"', {
'targets': [
{
@@ -885,7 +1119,9 @@
'tools/dump_cache/dump_cache.cc',
'tools/dump_cache/dump_files.cc',
'tools/dump_cache/upgrade.cc',
+ 'tools/dump_cache/url_to_filename_encoder.cc',
'tools/dump_cache/url_to_filename_encoder.h',
+ 'tools/dump_cache/url_utilties.h',
],
},
],
diff --git a/net/net.target.mk b/net/net.target.mk
new file mode 100644
index 0000000..0d01fba
--- /dev/null
+++ b/net/net.target.mk
@@ -0,0 +1,351 @@
+# This file is generated by gyp; do not edit.
+
+TOOLSET := target
+TARGET := net
+DEFS_Debug := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-DU_STATIC_IMPLEMENTATION' \
+ '-DUSE_SYSTEM_ZLIB' \
+ '-D__STDC_FORMAT_MACROS' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=1' \
+ '-D_DEBUG'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Debug := -Werror \
+ -pthread \
+ -fno-exceptions \
+ -Wall \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/atk-1.0 \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -DORBIT2=1 \
+ -pthread \
+ -I/usr/include/gconf/2 \
+ -I/usr/include/orbit-2.0 \
+ -I/usr/include/dbus-1.0 \
+ -I/usr/lib/dbus-1.0/include \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -I../net/third_party/nss/ssl \
+ -Inet/third_party/nss/ssl \
+ -IWebKit/chromium/net/third_party/nss/ssl \
+ -I/usr/include/nss \
+ -I/usr/include/nspr \
+ -O0 \
+ -g
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Debug :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Debug := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Debug := -Ithird_party/icu/public/common \
+ -Ithird_party/icu/public/i18n \
+ -I. \
+ -Isdch/open-vcdiff/src \
+ -I$(obj)/gen/net \
+ -Iv8/include
+
+DEFS_Release := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-DU_STATIC_IMPLEMENTATION' \
+ '-DUSE_SYSTEM_ZLIB' \
+ '-D__STDC_FORMAT_MACROS' \
+ '-DNDEBUG' \
+ '-DNVALGRIND' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=0'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Release := -Werror \
+ -pthread \
+ -fno-exceptions \
+ -Wall \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/atk-1.0 \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -DORBIT2=1 \
+ -pthread \
+ -I/usr/include/gconf/2 \
+ -I/usr/include/orbit-2.0 \
+ -I/usr/include/dbus-1.0 \
+ -I/usr/lib/dbus-1.0/include \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -I../net/third_party/nss/ssl \
+ -Inet/third_party/nss/ssl \
+ -IWebKit/chromium/net/third_party/nss/ssl \
+ -I/usr/include/nss \
+ -I/usr/include/nspr \
+ -O2 \
+ -fno-ident \
+ -fdata-sections \
+ -ffunction-sections
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Release :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Release := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Release := -Ithird_party/icu/public/common \
+ -Ithird_party/icu/public/i18n \
+ -I. \
+ -Isdch/open-vcdiff/src \
+ -I$(obj)/gen/net \
+ -Iv8/include
+
+OBJS := $(obj).target/$(TARGET)/net/disk_cache/addr.o \
+ $(obj).target/$(TARGET)/net/disk_cache/backend_impl.o \
+ $(obj).target/$(TARGET)/net/disk_cache/bitmap.o \
+ $(obj).target/$(TARGET)/net/disk_cache/block_files.o \
+ $(obj).target/$(TARGET)/net/disk_cache/cache_util_posix.o \
+ $(obj).target/$(TARGET)/net/disk_cache/entry_impl.o \
+ $(obj).target/$(TARGET)/net/disk_cache/eviction.o \
+ $(obj).target/$(TARGET)/net/disk_cache/file_lock.o \
+ $(obj).target/$(TARGET)/net/disk_cache/file_posix.o \
+ $(obj).target/$(TARGET)/net/disk_cache/hash.o \
+ $(obj).target/$(TARGET)/net/disk_cache/in_flight_backend_io.o \
+ $(obj).target/$(TARGET)/net/disk_cache/in_flight_io.o \
+ $(obj).target/$(TARGET)/net/disk_cache/mapped_file_posix.o \
+ $(obj).target/$(TARGET)/net/disk_cache/mem_backend_impl.o \
+ $(obj).target/$(TARGET)/net/disk_cache/mem_entry_impl.o \
+ $(obj).target/$(TARGET)/net/disk_cache/mem_rankings.o \
+ $(obj).target/$(TARGET)/net/disk_cache/rankings.o \
+ $(obj).target/$(TARGET)/net/disk_cache/sparse_control.o \
+ $(obj).target/$(TARGET)/net/disk_cache/stats.o \
+ $(obj).target/$(TARGET)/net/disk_cache/stats_histogram.o \
+ $(obj).target/$(TARGET)/net/disk_cache/trace.o \
+ $(obj).target/$(TARGET)/net/ftp/ftp_auth_cache.o \
+ $(obj).target/$(TARGET)/net/ftp/ftp_ctrl_response_buffer.o \
+ $(obj).target/$(TARGET)/net/ftp/ftp_directory_listing_buffer.o \
+ $(obj).target/$(TARGET)/net/ftp/ftp_directory_listing_parser.o \
+ $(obj).target/$(TARGET)/net/ftp/ftp_directory_listing_parser_ls.o \
+ $(obj).target/$(TARGET)/net/ftp/ftp_directory_listing_parser_mlsd.o \
+ $(obj).target/$(TARGET)/net/ftp/ftp_directory_listing_parser_netware.o \
+ $(obj).target/$(TARGET)/net/ftp/ftp_directory_listing_parser_vms.o \
+ $(obj).target/$(TARGET)/net/ftp/ftp_directory_listing_parser_windows.o \
+ $(obj).target/$(TARGET)/net/ftp/ftp_network_layer.o \
+ $(obj).target/$(TARGET)/net/ftp/ftp_network_transaction.o \
+ $(obj).target/$(TARGET)/net/ftp/ftp_server_type_histograms.o \
+ $(obj).target/$(TARGET)/net/ftp/ftp_util.o \
+ $(obj).target/$(TARGET)/net/http/des.o \
+ $(obj).target/$(TARGET)/net/http/http_alternate_protocols.o \
+ $(obj).target/$(TARGET)/net/http/http_auth.o \
+ $(obj).target/$(TARGET)/net/http/http_auth_cache.o \
+ $(obj).target/$(TARGET)/net/http/http_auth_controller.o \
+ $(obj).target/$(TARGET)/net/http/http_auth_filter.o \
+ $(obj).target/$(TARGET)/net/http/http_auth_gssapi_posix.o \
+ $(obj).target/$(TARGET)/net/http/http_auth_handler.o \
+ $(obj).target/$(TARGET)/net/http/http_auth_handler_basic.o \
+ $(obj).target/$(TARGET)/net/http/http_auth_handler_digest.o \
+ $(obj).target/$(TARGET)/net/http/http_auth_handler_factory.o \
+ $(obj).target/$(TARGET)/net/http/http_auth_handler_negotiate.o \
+ $(obj).target/$(TARGET)/net/http/http_auth_handler_ntlm.o \
+ $(obj).target/$(TARGET)/net/http/http_auth_handler_ntlm_portable.o \
+ $(obj).target/$(TARGET)/net/http/http_basic_stream.o \
+ $(obj).target/$(TARGET)/net/http/http_byte_range.o \
+ $(obj).target/$(TARGET)/net/http/http_cache.o \
+ $(obj).target/$(TARGET)/net/http/http_cache_transaction.o \
+ $(obj).target/$(TARGET)/net/http/http_chunked_decoder.o \
+ $(obj).target/$(TARGET)/net/http/http_network_layer.o \
+ $(obj).target/$(TARGET)/net/http/http_network_session.o \
+ $(obj).target/$(TARGET)/net/http/http_network_transaction.o \
+ $(obj).target/$(TARGET)/net/http/http_request_headers.o \
+ $(obj).target/$(TARGET)/net/http/http_response_headers.o \
+ $(obj).target/$(TARGET)/net/http/http_response_info.o \
+ $(obj).target/$(TARGET)/net/http/http_stream_parser.o \
+ $(obj).target/$(TARGET)/net/http/url_security_manager.o \
+ $(obj).target/$(TARGET)/net/http/url_security_manager_posix.o \
+ $(obj).target/$(TARGET)/net/http/http_proxy_client_socket.o \
+ $(obj).target/$(TARGET)/net/http/http_proxy_client_socket_pool.o \
+ $(obj).target/$(TARGET)/net/http/http_util.o \
+ $(obj).target/$(TARGET)/net/http/http_util_icu.o \
+ $(obj).target/$(TARGET)/net/http/http_vary_data.o \
+ $(obj).target/$(TARGET)/net/http/md4.o \
+ $(obj).target/$(TARGET)/net/http/partial_data.o \
+ $(obj).target/$(TARGET)/net/ocsp/nss_ocsp.o \
+ $(obj).target/$(TARGET)/net/proxy/init_proxy_resolver.o \
+ $(obj).target/$(TARGET)/net/proxy/multi_threaded_proxy_resolver.o \
+ $(obj).target/$(TARGET)/net/proxy/proxy_bypass_rules.o \
+ $(obj).target/$(TARGET)/net/proxy/proxy_config.o \
+ $(obj).target/$(TARGET)/net/proxy/proxy_config_service_linux.o \
+ $(obj).target/$(TARGET)/net/proxy/proxy_info.o \
+ $(obj).target/$(TARGET)/net/proxy/proxy_list.o \
+ $(obj).target/$(TARGET)/net/proxy/proxy_resolver_js_bindings.o \
+ $(obj).target/$(TARGET)/net/proxy/proxy_resolver_script_data.o \
+ $(obj).target/$(TARGET)/net/proxy/proxy_resolver_v8.o \
+ $(obj).target/$(TARGET)/net/proxy/proxy_script_fetcher.o \
+ $(obj).target/$(TARGET)/net/proxy/proxy_server.o \
+ $(obj).target/$(TARGET)/net/proxy/proxy_service.o \
+ $(obj).target/$(TARGET)/net/proxy/sync_host_resolver_bridge.o \
+ $(obj).target/$(TARGET)/net/socket/client_socket_factory.o \
+ $(obj).target/$(TARGET)/net/socket/client_socket_handle.o \
+ $(obj).target/$(TARGET)/net/socket/client_socket_pool.o \
+ $(obj).target/$(TARGET)/net/socket/client_socket_pool_base.o \
+ $(obj).target/$(TARGET)/net/socket/client_socket_pool_histograms.o \
+ $(obj).target/$(TARGET)/net/socket/socks5_client_socket.o \
+ $(obj).target/$(TARGET)/net/socket/socks_client_socket.o \
+ $(obj).target/$(TARGET)/net/socket/socks_client_socket_pool.o \
+ $(obj).target/$(TARGET)/net/socket/ssl_client_socket_nss.o \
+ $(obj).target/$(TARGET)/net/socket/ssl_client_socket_pool.o \
+ $(obj).target/$(TARGET)/net/socket/tcp_client_socket_libevent.o \
+ $(obj).target/$(TARGET)/net/socket/tcp_client_socket_pool.o \
+ $(obj).target/$(TARGET)/net/socket_stream/socket_stream.o \
+ $(obj).target/$(TARGET)/net/socket_stream/socket_stream_job.o \
+ $(obj).target/$(TARGET)/net/socket_stream/socket_stream_job_manager.o \
+ $(obj).target/$(TARGET)/net/socket_stream/socket_stream_metrics.o \
+ $(obj).target/$(TARGET)/net/spdy/spdy_frame_builder.o \
+ $(obj).target/$(TARGET)/net/spdy/spdy_framer.o \
+ $(obj).target/$(TARGET)/net/spdy/spdy_http_stream.o \
+ $(obj).target/$(TARGET)/net/spdy/spdy_io_buffer.o \
+ $(obj).target/$(TARGET)/net/spdy/spdy_network_transaction.o \
+ $(obj).target/$(TARGET)/net/spdy/spdy_session.o \
+ $(obj).target/$(TARGET)/net/spdy/spdy_session_pool.o \
+ $(obj).target/$(TARGET)/net/spdy/spdy_settings_storage.o \
+ $(obj).target/$(TARGET)/net/spdy/spdy_stream.o \
+ $(obj).target/$(TARGET)/net/url_request/https_prober.o \
+ $(obj).target/$(TARGET)/net/url_request/url_request.o \
+ $(obj).target/$(TARGET)/net/url_request/url_request_about_job.o \
+ $(obj).target/$(TARGET)/net/url_request/url_request_data_job.o \
+ $(obj).target/$(TARGET)/net/url_request/url_request_error_job.o \
+ $(obj).target/$(TARGET)/net/url_request/url_request_file_dir_job.o \
+ $(obj).target/$(TARGET)/net/url_request/url_request_file_job.o \
+ $(obj).target/$(TARGET)/net/url_request/url_request_filter.o \
+ $(obj).target/$(TARGET)/net/url_request/url_request_ftp_job.o \
+ $(obj).target/$(TARGET)/net/url_request/url_request_http_job.o \
+ $(obj).target/$(TARGET)/net/url_request/url_request_job.o \
+ $(obj).target/$(TARGET)/net/url_request/url_request_job_manager.o \
+ $(obj).target/$(TARGET)/net/url_request/url_request_job_metrics.o \
+ $(obj).target/$(TARGET)/net/url_request/url_request_job_tracker.o \
+ $(obj).target/$(TARGET)/net/url_request/url_request_netlog_params.o \
+ $(obj).target/$(TARGET)/net/url_request/url_request_redirect_job.o \
+ $(obj).target/$(TARGET)/net/url_request/url_request_simple_job.o \
+ $(obj).target/$(TARGET)/net/url_request/url_request_test_job.o \
+ $(obj).target/$(TARGET)/net/url_request/view_cache_helper.o \
+ $(obj).target/$(TARGET)/net/websockets/websocket.o \
+ $(obj).target/$(TARGET)/net/websockets/websocket_frame_handler.o \
+ $(obj).target/$(TARGET)/net/websockets/websocket_handshake.o \
+ $(obj).target/$(TARGET)/net/websockets/websocket_handshake_draft75.o \
+ $(obj).target/$(TARGET)/net/websockets/websocket_handshake_handler.o \
+ $(obj).target/$(TARGET)/net/websockets/websocket_job.o \
+ $(obj).target/$(TARGET)/net/websockets/websocket_throttle.o
+
+# Add to the list of files we specially track dependencies for.
+all_deps += $(OBJS)
+
+# Make sure our dependencies are built before any of us.
+$(OBJS): | $(obj).target/net/net_resources.stamp $(obj).target/v8/tools/gyp/v8.stamp
+
+# CFLAGS et al overrides must be target-local.
+# See "Target-specific Variable Values" in the GNU Make manual.
+$(OBJS): TOOLSET := $(TOOLSET)
+$(OBJS): GYP_CFLAGS := $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_C_$(BUILDTYPE)) $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE))
+$(OBJS): GYP_CXXFLAGS := $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_CC_$(BUILDTYPE)) $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE))
+
+# Suffix rules, putting all outputs into $(obj).
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(srcdir)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+# Try building from generated source, too.
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj).$(TOOLSET)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+# End of this set of suffix rules
+### Rules for final target.
+LDFLAGS_Debug := -pthread \
+ -Wl,-z,noexecstack \
+ -rdynamic
+
+LDFLAGS_Release := -pthread \
+ -Wl,-z,noexecstack \
+ -Wl,--gc-sections
+
+LIBS :=
+
+$(obj).target/net/libnet.a: GYP_LDFLAGS := $(LDFLAGS_$(BUILDTYPE))
+$(obj).target/net/libnet.a: LIBS := $(LIBS)
+$(obj).target/net/libnet.a: TOOLSET := $(TOOLSET)
+$(obj).target/net/libnet.a: $(OBJS) FORCE_DO_CMD
+ $(call do_cmd,alink)
+
+all_deps += $(obj).target/net/libnet.a
+# Add target alias
+.PHONY: net
+net: $(obj).target/net/libnet.a
+
+# Add target alias to "all" target.
+.PHONY: all
+all: net
+
diff --git a/net/net_base.target.mk b/net/net_base.target.mk
new file mode 100644
index 0000000..a3c4d6d
--- /dev/null
+++ b/net/net_base.target.mk
@@ -0,0 +1,288 @@
+# This file is generated by gyp; do not edit.
+
+TOOLSET := target
+TARGET := net_base
+DEFS_Debug := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-DU_STATIC_IMPLEMENTATION' \
+ '-DUSE_SYSTEM_ZLIB' \
+ '-D__STDC_FORMAT_MACROS' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=1' \
+ '-D_DEBUG'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Debug := -Werror \
+ -pthread \
+ -fno-exceptions \
+ -Wall \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/atk-1.0 \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -DORBIT2=1 \
+ -pthread \
+ -I/usr/include/gconf/2 \
+ -I/usr/include/orbit-2.0 \
+ -I/usr/include/dbus-1.0 \
+ -I/usr/lib/dbus-1.0/include \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -I../net/third_party/nss/ssl \
+ -Inet/third_party/nss/ssl \
+ -IWebKit/chromium/net/third_party/nss/ssl \
+ -I/usr/include/nss \
+ -I/usr/include/nspr \
+ -O0 \
+ -g
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Debug :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Debug := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Debug := -Ithird_party/icu/public/common \
+ -Ithird_party/icu/public/i18n \
+ -I. \
+ -Isdch/open-vcdiff/src \
+ -I$(obj)/gen/net
+
+DEFS_Release := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-DU_STATIC_IMPLEMENTATION' \
+ '-DUSE_SYSTEM_ZLIB' \
+ '-D__STDC_FORMAT_MACROS' \
+ '-DNDEBUG' \
+ '-DNVALGRIND' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=0'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Release := -Werror \
+ -pthread \
+ -fno-exceptions \
+ -Wall \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/atk-1.0 \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -DORBIT2=1 \
+ -pthread \
+ -I/usr/include/gconf/2 \
+ -I/usr/include/orbit-2.0 \
+ -I/usr/include/dbus-1.0 \
+ -I/usr/lib/dbus-1.0/include \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -I../net/third_party/nss/ssl \
+ -Inet/third_party/nss/ssl \
+ -IWebKit/chromium/net/third_party/nss/ssl \
+ -I/usr/include/nss \
+ -I/usr/include/nspr \
+ -O2 \
+ -fno-ident \
+ -fdata-sections \
+ -ffunction-sections
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Release :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Release := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Release := -Ithird_party/icu/public/common \
+ -Ithird_party/icu/public/i18n \
+ -I. \
+ -Isdch/open-vcdiff/src \
+ -I$(obj)/gen/net
+
+OBJS := $(obj).target/$(TARGET)/net/base/address_list.o \
+ $(obj).target/$(TARGET)/net/base/address_list_net_log_param.o \
+ $(obj).target/$(TARGET)/net/base/capturing_net_log.o \
+ $(obj).target/$(TARGET)/net/base/cert_database_nss.o \
+ $(obj).target/$(TARGET)/net/base/cert_status_flags.o \
+ $(obj).target/$(TARGET)/net/base/cert_verifier.o \
+ $(obj).target/$(TARGET)/net/base/connection_type_histograms.o \
+ $(obj).target/$(TARGET)/net/base/cookie_monster.o \
+ $(obj).target/$(TARGET)/net/base/data_url.o \
+ $(obj).target/$(TARGET)/net/base/directory_lister.o \
+ $(obj).target/$(TARGET)/net/base/dns_util.o \
+ $(obj).target/$(TARGET)/net/base/escape.o \
+ $(obj).target/$(TARGET)/net/base/ev_root_ca_metadata.o \
+ $(obj).target/$(TARGET)/net/base/file_stream_posix.o \
+ $(obj).target/$(TARGET)/net/base/filter.o \
+ $(obj).target/$(TARGET)/net/base/forwarding_net_log.o \
+ $(obj).target/$(TARGET)/net/base/gzip_filter.o \
+ $(obj).target/$(TARGET)/net/base/gzip_header.o \
+ $(obj).target/$(TARGET)/net/base/host_cache.o \
+ $(obj).target/$(TARGET)/net/base/host_mapping_rules.o \
+ $(obj).target/$(TARGET)/net/base/host_port_pair.o \
+ $(obj).target/$(TARGET)/net/base/host_resolver.o \
+ $(obj).target/$(TARGET)/net/base/host_resolver_impl.o \
+ $(obj).target/$(TARGET)/net/base/host_resolver_proc.o \
+ $(obj).target/$(TARGET)/net/base/io_buffer.o \
+ $(obj).target/$(TARGET)/net/base/keygen_handler_nss.o \
+ $(obj).target/$(TARGET)/net/base/listen_socket.o \
+ $(obj).target/$(TARGET)/net/base/mapped_host_resolver.o \
+ $(obj).target/$(TARGET)/net/base/mime_sniffer.o \
+ $(obj).target/$(TARGET)/net/base/mime_util.o \
+ $(obj).target/$(TARGET)/net/base/mock_host_resolver.o \
+ $(obj).target/$(TARGET)/net/base/net_errors.o \
+ $(obj).target/$(TARGET)/net/base/net_log.o \
+ $(obj).target/$(TARGET)/net/base/net_module.o \
+ $(obj).target/$(TARGET)/net/base/net_util.o \
+ $(obj).target/$(TARGET)/net/base/net_util_posix.o \
+ $(obj).target/$(TARGET)/net/base/network_change_notifier.o \
+ $(obj).target/$(TARGET)/net/base/network_change_notifier_linux.o \
+ $(obj).target/$(TARGET)/net/base/network_change_notifier_netlink_linux.o \
+ $(obj).target/$(TARGET)/net/base/nss_memio.o \
+ $(obj).target/$(TARGET)/net/base/platform_mime_util_linux.o \
+ $(obj).target/$(TARGET)/net/base/registry_controlled_domain.o \
+ $(obj).target/$(TARGET)/net/base/sdch_filter.o \
+ $(obj).target/$(TARGET)/net/base/sdch_manager.o \
+ $(obj).target/$(TARGET)/net/base/ssl_cipher_suite_names.o \
+ $(obj).target/$(TARGET)/net/base/ssl_client_auth_cache.o \
+ $(obj).target/$(TARGET)/net/base/ssl_config_service.o \
+ $(obj).target/$(TARGET)/net/base/static_cookie_policy.o \
+ $(obj).target/$(TARGET)/net/base/transport_security_state.o \
+ $(obj).target/$(TARGET)/net/base/telnet_server.o \
+ $(obj).target/$(TARGET)/net/base/upload_data.o \
+ $(obj).target/$(TARGET)/net/base/upload_data_stream.o \
+ $(obj).target/$(TARGET)/net/base/x509_certificate.o \
+ $(obj).target/$(TARGET)/net/base/x509_certificate_nss.o \
+ $(obj).target/$(TARGET)/net/base/x509_cert_types.o \
+ $(obj).target/$(TARGET)/net/third_party/mozilla_security_manager/nsKeygenHandler.o
+
+# Add to the list of files we specially track dependencies for.
+all_deps += $(OBJS)
+
+# Make sure our dependencies are built before any of us.
+$(OBJS): | $(obj).target/net/net_resources.stamp
+
+# CFLAGS et al overrides must be target-local.
+# See "Target-specific Variable Values" in the GNU Make manual.
+$(OBJS): TOOLSET := $(TOOLSET)
+$(OBJS): GYP_CFLAGS := $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_C_$(BUILDTYPE)) $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE))
+$(OBJS): GYP_CXXFLAGS := $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_CC_$(BUILDTYPE)) $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE))
+
+# Suffix rules, putting all outputs into $(obj).
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(srcdir)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(srcdir)/%.c FORCE_DO_CMD
+ @$(call do_cmd,cc,1)
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(srcdir)/%.cpp FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+# Try building from generated source, too.
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj).$(TOOLSET)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj).$(TOOLSET)/%.c FORCE_DO_CMD
+ @$(call do_cmd,cc,1)
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj).$(TOOLSET)/%.cpp FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj)/%.c FORCE_DO_CMD
+ @$(call do_cmd,cc,1)
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj)/%.cpp FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+# End of this set of suffix rules
+### Rules for final target.
+LDFLAGS_Debug := -pthread \
+ -Wl,-z,noexecstack \
+ -rdynamic
+
+LDFLAGS_Release := -pthread \
+ -Wl,-z,noexecstack \
+ -Wl,--gc-sections
+
+LIBS :=
+
+$(obj).target/net/libnet_base.a: GYP_LDFLAGS := $(LDFLAGS_$(BUILDTYPE))
+$(obj).target/net/libnet_base.a: LIBS := $(LIBS)
+$(obj).target/net/libnet_base.a: TOOLSET := $(TOOLSET)
+$(obj).target/net/libnet_base.a: $(OBJS) FORCE_DO_CMD
+ $(call do_cmd,alink)
+
+all_deps += $(obj).target/net/libnet_base.a
+# Add target alias
+.PHONY: net_base
+net_base: $(obj).target/net/libnet_base.a
+
+# Add target alias to "all" target.
+.PHONY: all
+all: net_base
+
diff --git a/net/net_perftests.target.mk b/net/net_perftests.target.mk
new file mode 100644
index 0000000..65afebf
--- /dev/null
+++ b/net/net_perftests.target.mk
@@ -0,0 +1,193 @@
+# This file is generated by gyp; do not edit.
+
+TOOLSET := target
+TARGET := net_perftests
+DEFS_Debug := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-DPERF_TEST' \
+ '-DUNIT_TEST' \
+ '-DGTEST_HAS_RTTI=0' \
+ '-D__STDC_FORMAT_MACROS' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=1' \
+ '-D_DEBUG'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Debug := -Werror \
+ -pthread \
+ -fno-exceptions \
+ -Wall \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/atk-1.0 \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -O0 \
+ -g
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Debug :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Debug := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Debug := -I. \
+ -Itesting/gtest/include
+
+DEFS_Release := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-DPERF_TEST' \
+ '-DUNIT_TEST' \
+ '-DGTEST_HAS_RTTI=0' \
+ '-D__STDC_FORMAT_MACROS' \
+ '-DNDEBUG' \
+ '-DNVALGRIND' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=0'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Release := -Werror \
+ -pthread \
+ -fno-exceptions \
+ -Wall \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/atk-1.0 \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -O2 \
+ -fno-ident \
+ -fdata-sections \
+ -ffunction-sections
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Release :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Release := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Release := -I. \
+ -Itesting/gtest/include
+
+OBJS := $(obj).target/$(TARGET)/net/base/cookie_monster_perftest.o \
+ $(obj).target/$(TARGET)/net/disk_cache/disk_cache_perftest.o \
+ $(obj).target/$(TARGET)/net/proxy/proxy_resolver_perftest.o
+
+# Add to the list of files we specially track dependencies for.
+all_deps += $(OBJS)
+
+# Make sure our dependencies are built before any of us.
+$(OBJS): | $(obj).target/net/libnet.a $(obj).target/net/libnet_test_support.a $(obj).target/base/libbase.a $(obj).target/base/libbase_i18n.a $(obj).target/base/libtest_support_base.a $(obj).target/base/libtest_support_perf.a $(obj).target/testing/libgtest.a $(obj).target/third_party/modp_b64/libmodp_b64.a $(obj).target/base/third_party/dynamic_annotations/libdynamic_annotations.a $(obj).target/base/libsymbolize.a $(obj).target/net/third_party/nss/libssl.a $(obj).target/third_party/zlib/libzlib.a $(obj).target/base/libxdg_mime.a $(obj).target/base/allocator/liballocator.a $(obj).target/third_party/libevent/libevent.a $(obj).target/third_party/icu/libicui18n.a $(obj).target/third_party/icu/libicuuc.a $(obj).target/third_party/icu/libicudata.a $(obj).target/build/temp_gyp/libgoogleurl.a $(obj).target/sdch/libsdch.a $(obj).target/net/libnet_base.a $(obj).target/v8/tools/gyp/libv8_snapshot.a $(obj).target/v8/tools/gyp/libv8_base.a
+
+# CFLAGS et al overrides must be target-local.
+# See "Target-specific Variable Values" in the GNU Make manual.
+$(OBJS): TOOLSET := $(TOOLSET)
+$(OBJS): GYP_CFLAGS := $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_C_$(BUILDTYPE)) $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE))
+$(OBJS): GYP_CXXFLAGS := $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_CC_$(BUILDTYPE)) $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE))
+
+# Suffix rules, putting all outputs into $(obj).
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(srcdir)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+# Try building from generated source, too.
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj).$(TOOLSET)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+# End of this set of suffix rules
+### Rules for final target.
+LDFLAGS_Debug := -pthread \
+ -Wl,-z,noexecstack \
+ -Wl,-uIsHeapProfilerRunning,-uProfilerStart \
+ -Wl,-u_Z21InitialMallocHook_NewPKvj,-u_Z22InitialMallocHook_MMapPKvS0_jiiix,-u_Z22InitialMallocHook_SbrkPKvi \
+ -Wl,-u_Z21InitialMallocHook_NewPKvm,-u_Z22InitialMallocHook_MMapPKvS0_miiil,-u_Z22InitialMallocHook_SbrkPKvl \
+ -rdynamic
+
+LDFLAGS_Release := -pthread \
+ -Wl,-z,noexecstack \
+ -Wl,-uIsHeapProfilerRunning,-uProfilerStart \
+ -Wl,-u_Z21InitialMallocHook_NewPKvj,-u_Z22InitialMallocHook_MMapPKvS0_jiiix,-u_Z22InitialMallocHook_SbrkPKvi \
+ -Wl,-u_Z21InitialMallocHook_NewPKvm,-u_Z22InitialMallocHook_MMapPKvS0_miiil,-u_Z22InitialMallocHook_SbrkPKvl \
+ -Wl,--gc-sections
+
+LIBS := -lrt \
+ -ldl \
+ -lgtk-x11-2.0 \
+ -lgdk-x11-2.0 \
+ -latk-1.0 \
+ -lgio-2.0 \
+ -lpangoft2-1.0 \
+ -lgdk_pixbuf-2.0 \
+ -lm \
+ -lpangocairo-1.0 \
+ -lcairo \
+ -lpango-1.0 \
+ -lfreetype \
+ -lfontconfig \
+ -lgobject-2.0 \
+ -lgmodule-2.0 \
+ -lgthread-2.0 \
+ -lglib-2.0 \
+ -lnss3 \
+ -lnssutil3 \
+ -lsmime3 \
+ -lplds4 \
+ -lplc4 \
+ -lnspr4 \
+ -lpthread \
+ -lz \
+ -lgconf-2
+
+$(builddir)/net_perftests: GYP_LDFLAGS := $(LDFLAGS_$(BUILDTYPE))
+$(builddir)/net_perftests: LIBS := $(LIBS)
+$(builddir)/net_perftests: TOOLSET := $(TOOLSET)
+$(builddir)/net_perftests: $(OBJS) $(obj).target/net/libnet.a $(obj).target/net/libnet_test_support.a $(obj).target/base/libbase.a $(obj).target/base/libbase_i18n.a $(obj).target/base/libtest_support_base.a $(obj).target/base/libtest_support_perf.a $(obj).target/testing/libgtest.a $(obj).target/third_party/modp_b64/libmodp_b64.a $(obj).target/base/third_party/dynamic_annotations/libdynamic_annotations.a $(obj).target/base/libsymbolize.a $(obj).target/net/third_party/nss/libssl.a $(obj).target/third_party/zlib/libzlib.a $(obj).target/base/libxdg_mime.a $(obj).target/base/allocator/liballocator.a $(obj).target/third_party/libevent/libevent.a $(obj).target/third_party/icu/libicui18n.a $(obj).target/third_party/icu/libicuuc.a $(obj).target/third_party/icu/libicudata.a $(obj).target/build/temp_gyp/libgoogleurl.a $(obj).target/sdch/libsdch.a $(obj).target/net/libnet_base.a $(obj).target/v8/tools/gyp/libv8_snapshot.a $(obj).target/v8/tools/gyp/libv8_base.a FORCE_DO_CMD
+ $(call do_cmd,link)
+
+all_deps += $(builddir)/net_perftests
+# Add target alias
+.PHONY: net_perftests
+net_perftests: $(builddir)/net_perftests
+
+# Add executable to "all" target.
+.PHONY: all
+all: $(builddir)/net_perftests
+
diff --git a/net/net_resources.target.mk b/net/net_resources.target.mk
new file mode 100644
index 0000000..6b8fe7a
--- /dev/null
+++ b/net/net_resources.target.mk
@@ -0,0 +1,121 @@
+# This file is generated by gyp; do not edit.
+
+TOOLSET := target
+TARGET := net_resources
+### Generated for rule net_resources_grit:
+$(obj)/gen/net/grit/net_resources.h: obj := $(abs_obj)
+
+$(obj)/gen/net/grit/net_resources.h: builddir := $(abs_builddir)
+
+$(obj)/gen/net/grit/net_resources.h: TOOLSET := $(TOOLSET)
+$(obj)/gen/net/grit/net_resources.h: net/base/net_resources.grd net/base/dir_header.html net/base/net_resources.grd tools/grit/grit.py tools/grit/grit_info.py tools/grit/resource_ids tools/grit/grit/exception.py tools/grit/grit/tclib.py tools/grit/grit/pseudo_unittest.py tools/grit/grit/grd_reader_unittest.py tools/grit/grit/grd_reader.py tools/grit/grit/scons.py tools/grit/grit/clique_unittest.py tools/grit/grit/constants.py tools/grit/grit/grit_runner.py tools/grit/grit/clique.py tools/grit/grit/grit_runner_unittest.py tools/grit/grit/shortcuts.py tools/grit/grit/util_unittest.py tools/grit/grit/__init__.py tools/grit/grit/tclib_unittest.py tools/grit/grit/xtb_reader_unittest.py tools/grit/grit/xtb_reader.py tools/grit/grit/pseudo.py tools/grit/grit/test_suite_all.py tools/grit/grit/util.py tools/grit/grit/shortcuts_unittests.py tools/grit/grit/extern/tclib.py tools/grit/grit/extern/FP.py tools/grit/grit/extern/__init__.py tools/grit/grit/node/structure.py tools/grit/grit/node/io_unittest.py tools/grit/grit/node/message.py tools/grit/grit/node/structure_unittest.py tools/grit/grit/node/base.py tools/grit/grit/node/empty.py tools/grit/grit/node/misc.py tools/grit/grit/node/__init__.py tools/grit/grit/node/include.py tools/grit/grit/node/mapping.py tools/grit/grit/node/message_unittest.py tools/grit/grit/node/io.py tools/grit/grit/node/variant.py tools/grit/grit/node/base_unittest.py tools/grit/grit/node/misc_unittest.py tools/grit/grit/node/custom/filename_unittest.py tools/grit/grit/node/custom/filename.py tools/grit/grit/node/custom/__init__.py tools/grit/grit/tool/newgrd.py tools/grit/grit/tool/transl2tc.py tools/grit/grit/tool/postprocess_interface.py tools/grit/grit/tool/preprocess_unittest.py tools/grit/grit/tool/test.py tools/grit/grit/tool/count.py tools/grit/grit/tool/toolbar_preprocess.py tools/grit/grit/tool/toolbar_postprocess.py tools/grit/grit/tool/resize.py tools/grit/grit/tool/unit.py tools/grit/grit/tool/preprocess_interface.py tools/grit/grit/tool/menu_from_parts.py tools/grit/grit/tool/__init__.py tools/grit/grit/tool/rc2grd_unittest.py tools/grit/grit/tool/interface.py tools/grit/grit/tool/postprocess_unittest.py tools/grit/grit/tool/diff_structures.py tools/grit/grit/tool/transl2tc_unittest.py tools/grit/grit/tool/rc2grd.py tools/grit/grit/tool/build.py tools/grit/grit/gather/admin_template.py tools/grit/grit/gather/muppet_strings_unittest.py tools/grit/grit/gather/rc_unittest.py tools/grit/grit/gather/tr_html.py tools/grit/grit/gather/regexp.py tools/grit/grit/gather/__init__.py tools/grit/grit/gather/admin_template_unittest.py tools/grit/grit/gather/interface.py tools/grit/grit/gather/muppet_strings.py tools/grit/grit/gather/rc.py tools/grit/grit/gather/txt_unittest.py tools/grit/grit/gather/tr_html_unittest.py tools/grit/grit/gather/txt.py tools/grit/grit/format/rc_header_unittest.py tools/grit/grit/format/data_pack.py tools/grit/grit/format/data_pack_unittest.py tools/grit/grit/format/rc_unittest.py tools/grit/grit/format/js_map_format.py tools/grit/grit/format/resource_map.py tools/grit/grit/format/rc_header.py tools/grit/grit/format/__init__.py tools/grit/grit/format/html_inline.py tools/grit/grit/format/interface.py tools/grit/grit/format/rc.py tools/grit/grit/format/js_map_format_unittest.py FORCE_DO_CMD
+ $(call do_cmd,net_resources_grit_0)
+$(obj)/gen/net/net_resources.pak $(obj)/gen/net/net_resources.rc: $(obj)/gen/net/grit/net_resources.h
+$(obj)/gen/net/net_resources.pak $(obj)/gen/net/net_resources.rc: ;
+
+all_deps += $(obj)/gen/net/grit/net_resources.h $(obj)/gen/net/net_resources.pak $(obj)/gen/net/net_resources.rc
+cmd_net_resources_grit_0 = export LD_LIBRARY_PATH=$(builddir)/lib.host:$(builddir)/lib.target:$$LD_LIBRARY_PATH; cd net; mkdir -p $(obj)/gen/net/grit $(obj)/gen/net; python ../tools/grit/grit.py -i "$(abspath $<)" build -o "$(obj)/gen/net"
+quiet_cmd_net_resources_grit_0 = RULE net_resources_grit_0 $@
+
+rule_net_resources_grit_outputs := $(obj)/gen/net/grit/net_resources.h \
+ $(obj)/gen/net/net_resources.pak \
+ $(obj)/gen/net/net_resources.rc
+
+### Finished generating for rule: net_resources_grit
+
+### Finished generating for all rules
+
+DEFS_Debug := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-D__STDC_FORMAT_MACROS' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=1' \
+ '-D_DEBUG'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Debug := -Werror \
+ -pthread \
+ -fno-exceptions \
+ -Wall \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -O0 \
+ -g
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Debug :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Debug := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Debug :=
+
+DEFS_Release := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-D__STDC_FORMAT_MACROS' \
+ '-DNDEBUG' \
+ '-DNVALGRIND' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=0'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Release := -Werror \
+ -pthread \
+ -fno-exceptions \
+ -Wall \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -O2 \
+ -fno-ident \
+ -fdata-sections \
+ -ffunction-sections
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Release :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Release := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Release :=
+
+OBJS :=
+
+# Add to the list of files we specially track dependencies for.
+all_deps += $(OBJS)
+
+# Make sure our actions/rules run before any of us.
+$(OBJS): | $(rule_net_resources_grit_outputs)
+
+
+### Rules for final target.
+# Build our special outputs first.
+$(obj).target/net/net_resources.stamp: | $(rule_net_resources_grit_outputs)
+
+# Preserve order dependency of special output on deps.
+$(rule_net_resources_grit_outputs): |
+
+$(obj).target/net/net_resources.stamp: TOOLSET := $(TOOLSET)
+$(obj).target/net/net_resources.stamp: FORCE_DO_CMD
+ $(call do_cmd,touch)
+
+all_deps += $(obj).target/net/net_resources.stamp
+# Add target alias
+.PHONY: net_resources
+net_resources: $(obj).target/net/net_resources.stamp
+
+# Add target alias to "all" target.
+.PHONY: all
+all: net_resources
+
diff --git a/net/net_test_support.target.mk b/net/net_test_support.target.mk
new file mode 100644
index 0000000..8e2077e
--- /dev/null
+++ b/net/net_test_support.target.mk
@@ -0,0 +1,171 @@
+# This file is generated by gyp; do not edit.
+
+TOOLSET := target
+TARGET := net_test_support
+DEFS_Debug := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-DUNIT_TEST' \
+ '-DGTEST_HAS_RTTI=0' \
+ '-D__STDC_FORMAT_MACROS' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=1' \
+ '-D_DEBUG'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Debug := -Werror \
+ -pthread \
+ -fno-exceptions \
+ -Wall \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/atk-1.0 \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -I../net/third_party/nss/ssl \
+ -Inet/third_party/nss/ssl \
+ -IWebKit/chromium/net/third_party/nss/ssl \
+ -I/usr/include/nss \
+ -I/usr/include/nspr \
+ -O0 \
+ -g
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Debug :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Debug := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Debug := -I. \
+ -Itesting/gtest/include
+
+DEFS_Release := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-DUNIT_TEST' \
+ '-DGTEST_HAS_RTTI=0' \
+ '-D__STDC_FORMAT_MACROS' \
+ '-DNDEBUG' \
+ '-DNVALGRIND' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=0'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Release := -Werror \
+ -pthread \
+ -fno-exceptions \
+ -Wall \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/atk-1.0 \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -I../net/third_party/nss/ssl \
+ -Inet/third_party/nss/ssl \
+ -IWebKit/chromium/net/third_party/nss/ssl \
+ -I/usr/include/nss \
+ -I/usr/include/nspr \
+ -O2 \
+ -fno-ident \
+ -fdata-sections \
+ -ffunction-sections
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Release :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Release := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Release := -I. \
+ -Itesting/gtest/include
+
+OBJS := $(obj).target/$(TARGET)/net/base/cert_test_util.o \
+ $(obj).target/$(TARGET)/net/disk_cache/disk_cache_test_util.o \
+ $(obj).target/$(TARGET)/net/proxy/proxy_config_service_common_unittest.o \
+ $(obj).target/$(TARGET)/net/socket/socket_test_util.o \
+ $(obj).target/$(TARGET)/net/test/test_server.o
+
+# Add to the list of files we specially track dependencies for.
+all_deps += $(OBJS)
+
+# Make sure our dependencies are built before any of us.
+$(OBJS): | $(obj).target/chrome/browser/sync/protocol/sync_proto.stamp $(obj).target/third_party/protobuf2/py_proto.stamp
+
+# CFLAGS et al overrides must be target-local.
+# See "Target-specific Variable Values" in the GNU Make manual.
+$(OBJS): TOOLSET := $(TOOLSET)
+$(OBJS): GYP_CFLAGS := $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_C_$(BUILDTYPE)) $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE))
+$(OBJS): GYP_CXXFLAGS := $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_CC_$(BUILDTYPE)) $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE))
+
+# Suffix rules, putting all outputs into $(obj).
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(srcdir)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+# Try building from generated source, too.
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj).$(TOOLSET)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+# End of this set of suffix rules
+### Rules for final target.
+LDFLAGS_Debug := -pthread \
+ -Wl,-z,noexecstack \
+ -rdynamic
+
+LDFLAGS_Release := -pthread \
+ -Wl,-z,noexecstack \
+ -Wl,--gc-sections
+
+LIBS :=
+
+$(obj).target/net/libnet_test_support.a: GYP_LDFLAGS := $(LDFLAGS_$(BUILDTYPE))
+$(obj).target/net/libnet_test_support.a: LIBS := $(LIBS)
+$(obj).target/net/libnet_test_support.a: TOOLSET := $(TOOLSET)
+$(obj).target/net/libnet_test_support.a: $(OBJS) FORCE_DO_CMD
+ $(call do_cmd,alink)
+
+all_deps += $(obj).target/net/libnet_test_support.a
+# Add target alias
+.PHONY: net_test_support
+net_test_support: $(obj).target/net/libnet_test_support.a
+
+# Add target alias to "all" target.
+.PHONY: all
+all: net_test_support
+
diff --git a/net/net_unittests.target.mk b/net/net_unittests.target.mk
new file mode 100644
index 0000000..4b45d03
--- /dev/null
+++ b/net/net_unittests.target.mk
@@ -0,0 +1,317 @@
+# This file is generated by gyp; do not edit.
+
+TOOLSET := target
+TARGET := net_unittests
+DEFS_Debug := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-DUNIT_TEST' \
+ '-DGTEST_HAS_RTTI=0' \
+ '-DUSE_SYSTEM_ZLIB' \
+ '-D__STDC_FORMAT_MACROS' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=1' \
+ '-D_DEBUG'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Debug := -Werror \
+ -pthread \
+ -fno-exceptions \
+ -Wall \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/atk-1.0 \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -I../net/third_party/nss/ssl \
+ -Inet/third_party/nss/ssl \
+ -IWebKit/chromium/net/third_party/nss/ssl \
+ -I/usr/include/nss \
+ -I/usr/include/nspr \
+ -O0 \
+ -g
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Debug :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Debug := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Debug := -I. \
+ -Itesting/gmock/include \
+ -Itesting/gtest/include
+
+DEFS_Release := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-DUNIT_TEST' \
+ '-DGTEST_HAS_RTTI=0' \
+ '-DUSE_SYSTEM_ZLIB' \
+ '-D__STDC_FORMAT_MACROS' \
+ '-DNDEBUG' \
+ '-DNVALGRIND' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=0'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Release := -Werror \
+ -pthread \
+ -fno-exceptions \
+ -Wall \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/atk-1.0 \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -I../net/third_party/nss/ssl \
+ -Inet/third_party/nss/ssl \
+ -IWebKit/chromium/net/third_party/nss/ssl \
+ -I/usr/include/nss \
+ -I/usr/include/nspr \
+ -O2 \
+ -fno-ident \
+ -fdata-sections \
+ -ffunction-sections
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Release :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Release := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Release := -I. \
+ -Itesting/gmock/include \
+ -Itesting/gtest/include
+
+OBJS := $(obj).target/$(TARGET)/net/base/address_list_unittest.o \
+ $(obj).target/$(TARGET)/net/base/cookie_monster_unittest.o \
+ $(obj).target/$(TARGET)/net/base/data_url_unittest.o \
+ $(obj).target/$(TARGET)/net/base/directory_lister_unittest.o \
+ $(obj).target/$(TARGET)/net/base/dns_util_unittest.o \
+ $(obj).target/$(TARGET)/net/base/escape_unittest.o \
+ $(obj).target/$(TARGET)/net/base/file_stream_unittest.o \
+ $(obj).target/$(TARGET)/net/base/filter_unittest.o \
+ $(obj).target/$(TARGET)/net/base/forwarding_net_log_unittest.o \
+ $(obj).target/$(TARGET)/net/base/gzip_filter_unittest.o \
+ $(obj).target/$(TARGET)/net/base/host_cache_unittest.o \
+ $(obj).target/$(TARGET)/net/base/host_mapping_rules_unittest.o \
+ $(obj).target/$(TARGET)/net/base/host_resolver_impl_unittest.o \
+ $(obj).target/$(TARGET)/net/base/keygen_handler_unittest.o \
+ $(obj).target/$(TARGET)/net/base/listen_socket_unittest.o \
+ $(obj).target/$(TARGET)/net/base/mapped_host_resolver_unittest.o \
+ $(obj).target/$(TARGET)/net/base/mime_sniffer_unittest.o \
+ $(obj).target/$(TARGET)/net/base/mime_util_unittest.o \
+ $(obj).target/$(TARGET)/net/base/net_util_unittest.o \
+ $(obj).target/$(TARGET)/net/base/registry_controlled_domain_unittest.o \
+ $(obj).target/$(TARGET)/net/base/run_all_unittests.o \
+ $(obj).target/$(TARGET)/net/base/ssl_cipher_suite_names_unittest.o \
+ $(obj).target/$(TARGET)/net/base/ssl_client_auth_cache_unittest.o \
+ $(obj).target/$(TARGET)/net/base/static_cookie_policy_unittest.o \
+ $(obj).target/$(TARGET)/net/base/transport_security_state_unittest.o \
+ $(obj).target/$(TARGET)/net/base/telnet_server_unittest.o \
+ $(obj).target/$(TARGET)/net/base/test_completion_callback_unittest.o \
+ $(obj).target/$(TARGET)/net/base/upload_data_stream_unittest.o \
+ $(obj).target/$(TARGET)/net/base/x509_certificate_unittest.o \
+ $(obj).target/$(TARGET)/net/base/x509_cert_types_unittest.o \
+ $(obj).target/$(TARGET)/net/disk_cache/addr_unittest.o \
+ $(obj).target/$(TARGET)/net/disk_cache/backend_unittest.o \
+ $(obj).target/$(TARGET)/net/disk_cache/bitmap_unittest.o \
+ $(obj).target/$(TARGET)/net/disk_cache/block_files_unittest.o \
+ $(obj).target/$(TARGET)/net/disk_cache/disk_cache_test_base.o \
+ $(obj).target/$(TARGET)/net/disk_cache/entry_unittest.o \
+ $(obj).target/$(TARGET)/net/disk_cache/mapped_file_unittest.o \
+ $(obj).target/$(TARGET)/net/disk_cache/storage_block_unittest.o \
+ $(obj).target/$(TARGET)/net/ftp/ftp_auth_cache_unittest.o \
+ $(obj).target/$(TARGET)/net/ftp/ftp_ctrl_response_buffer_unittest.o \
+ $(obj).target/$(TARGET)/net/ftp/ftp_directory_listing_buffer_unittest.o \
+ $(obj).target/$(TARGET)/net/ftp/ftp_directory_listing_parser_ls_unittest.o \
+ $(obj).target/$(TARGET)/net/ftp/ftp_directory_listing_parser_mlsd_unittest.o \
+ $(obj).target/$(TARGET)/net/ftp/ftp_directory_listing_parser_netware_unittest.o \
+ $(obj).target/$(TARGET)/net/ftp/ftp_directory_listing_parser_vms_unittest.o \
+ $(obj).target/$(TARGET)/net/ftp/ftp_directory_listing_parser_windows_unittest.o \
+ $(obj).target/$(TARGET)/net/ftp/ftp_network_transaction_unittest.o \
+ $(obj).target/$(TARGET)/net/ftp/ftp_util_unittest.o \
+ $(obj).target/$(TARGET)/net/http/des_unittest.o \
+ $(obj).target/$(TARGET)/net/http/http_alternate_protocols_unittest.o \
+ $(obj).target/$(TARGET)/net/http/http_auth_cache_unittest.o \
+ $(obj).target/$(TARGET)/net/http/http_auth_filter_unittest.o \
+ $(obj).target/$(TARGET)/net/http/http_auth_gssapi_posix_unittest.o \
+ $(obj).target/$(TARGET)/net/http/http_auth_handler_basic_unittest.o \
+ $(obj).target/$(TARGET)/net/http/http_auth_handler_digest_unittest.o \
+ $(obj).target/$(TARGET)/net/http/http_auth_handler_factory_unittest.o \
+ $(obj).target/$(TARGET)/net/http/http_auth_handler_mock.o \
+ $(obj).target/$(TARGET)/net/http/http_auth_handler_negotiate_unittest.o \
+ $(obj).target/$(TARGET)/net/http/http_auth_handler_unittest.o \
+ $(obj).target/$(TARGET)/net/http/http_auth_unittest.o \
+ $(obj).target/$(TARGET)/net/http/http_byte_range_unittest.o \
+ $(obj).target/$(TARGET)/net/http/http_cache_unittest.o \
+ $(obj).target/$(TARGET)/net/http/http_chunked_decoder_unittest.o \
+ $(obj).target/$(TARGET)/net/http/http_network_layer_unittest.o \
+ $(obj).target/$(TARGET)/net/http/http_network_transaction_unittest.o \
+ $(obj).target/$(TARGET)/net/http/http_proxy_client_socket_pool_unittest.o \
+ $(obj).target/$(TARGET)/net/http/http_request_headers_unittest.o \
+ $(obj).target/$(TARGET)/net/http/http_response_headers_unittest.o \
+ $(obj).target/$(TARGET)/net/http/http_transaction_unittest.o \
+ $(obj).target/$(TARGET)/net/http/http_util_unittest.o \
+ $(obj).target/$(TARGET)/net/http/http_vary_data_unittest.o \
+ $(obj).target/$(TARGET)/net/http/mock_gssapi_library_posix.o \
+ $(obj).target/$(TARGET)/net/http/url_security_manager_unittest.o \
+ $(obj).target/$(TARGET)/net/proxy/init_proxy_resolver_unittest.o \
+ $(obj).target/$(TARGET)/net/proxy/multi_threaded_proxy_resolver_unittest.o \
+ $(obj).target/$(TARGET)/net/proxy/proxy_bypass_rules_unittest.o \
+ $(obj).target/$(TARGET)/net/proxy/proxy_config_service_linux_unittest.o \
+ $(obj).target/$(TARGET)/net/proxy/proxy_config_unittest.o \
+ $(obj).target/$(TARGET)/net/proxy/proxy_list_unittest.o \
+ $(obj).target/$(TARGET)/net/proxy/proxy_resolver_js_bindings_unittest.o \
+ $(obj).target/$(TARGET)/net/proxy/proxy_resolver_v8_unittest.o \
+ $(obj).target/$(TARGET)/net/proxy/proxy_script_fetcher_unittest.o \
+ $(obj).target/$(TARGET)/net/proxy/proxy_server_unittest.o \
+ $(obj).target/$(TARGET)/net/proxy/proxy_service_unittest.o \
+ $(obj).target/$(TARGET)/net/proxy/sync_host_resolver_bridge_unittest.o \
+ $(obj).target/$(TARGET)/net/socket/client_socket_pool_base_unittest.o \
+ $(obj).target/$(TARGET)/net/socket/socks5_client_socket_unittest.o \
+ $(obj).target/$(TARGET)/net/socket/socks_client_socket_pool_unittest.o \
+ $(obj).target/$(TARGET)/net/socket/socks_client_socket_unittest.o \
+ $(obj).target/$(TARGET)/net/socket/ssl_client_socket_unittest.o \
+ $(obj).target/$(TARGET)/net/socket/ssl_client_socket_pool_unittest.o \
+ $(obj).target/$(TARGET)/net/socket/tcp_client_socket_pool_unittest.o \
+ $(obj).target/$(TARGET)/net/socket/tcp_client_socket_unittest.o \
+ $(obj).target/$(TARGET)/net/socket/tcp_pinger_unittest.o \
+ $(obj).target/$(TARGET)/net/socket_stream/socket_stream_metrics_unittest.o \
+ $(obj).target/$(TARGET)/net/socket_stream/socket_stream_unittest.o \
+ $(obj).target/$(TARGET)/net/spdy/spdy_framer_test.o \
+ $(obj).target/$(TARGET)/net/spdy/spdy_http_stream_unittest.o \
+ $(obj).target/$(TARGET)/net/spdy/spdy_network_transaction_unittest.o \
+ $(obj).target/$(TARGET)/net/spdy/spdy_protocol_test.o \
+ $(obj).target/$(TARGET)/net/spdy/spdy_session_unittest.o \
+ $(obj).target/$(TARGET)/net/spdy/spdy_stream_unittest.o \
+ $(obj).target/$(TARGET)/net/spdy/spdy_test_util.o \
+ $(obj).target/$(TARGET)/net/tools/dump_cache/url_to_filename_encoder.o \
+ $(obj).target/$(TARGET)/net/tools/dump_cache/url_to_filename_encoder_unittest.o \
+ $(obj).target/$(TARGET)/net/url_request/url_request_job_tracker_unittest.o \
+ $(obj).target/$(TARGET)/net/url_request/url_request_unittest.o \
+ $(obj).target/$(TARGET)/net/url_request/view_cache_helper_unittest.o \
+ $(obj).target/$(TARGET)/net/websockets/websocket_frame_handler_unittest.o \
+ $(obj).target/$(TARGET)/net/websockets/websocket_handshake_draft75_unittest.o \
+ $(obj).target/$(TARGET)/net/websockets/websocket_handshake_handler_unittest.o \
+ $(obj).target/$(TARGET)/net/websockets/websocket_handshake_unittest.o \
+ $(obj).target/$(TARGET)/net/websockets/websocket_job_unittest.o \
+ $(obj).target/$(TARGET)/net/websockets/websocket_throttle_unittest.o \
+ $(obj).target/$(TARGET)/net/websockets/websocket_unittest.o
+
+# Add to the list of files we specially track dependencies for.
+all_deps += $(OBJS)
+
+# Make sure our dependencies are built before any of us.
+$(OBJS): | $(obj).target/net/libnet.a $(obj).target/net/libnet_test_support.a $(obj).target/base/libbase.a $(obj).target/base/libbase_i18n.a $(obj).target/testing/libgmock.a $(obj).target/testing/libgtest.a $(obj).target/third_party/zlib/libzlib.a $(obj).target/base/allocator/liballocator.a $(obj).target/third_party/modp_b64/libmodp_b64.a $(obj).target/base/third_party/dynamic_annotations/libdynamic_annotations.a $(obj).target/base/libsymbolize.a $(obj).target/net/third_party/nss/libssl.a $(obj).target/base/libxdg_mime.a $(obj).target/third_party/libevent/libevent.a $(obj).target/third_party/icu/libicui18n.a $(obj).target/third_party/icu/libicuuc.a $(obj).target/third_party/icu/libicudata.a $(obj).target/build/temp_gyp/libgoogleurl.a $(obj).target/sdch/libsdch.a $(obj).target/net/libnet_base.a $(obj).target/v8/tools/gyp/libv8_snapshot.a $(obj).target/v8/tools/gyp/libv8_base.a
+
+# CFLAGS et al overrides must be target-local.
+# See "Target-specific Variable Values" in the GNU Make manual.
+$(OBJS): TOOLSET := $(TOOLSET)
+$(OBJS): GYP_CFLAGS := $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_C_$(BUILDTYPE)) $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE))
+$(OBJS): GYP_CXXFLAGS := $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_CC_$(BUILDTYPE)) $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE))
+
+# Suffix rules, putting all outputs into $(obj).
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(srcdir)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+# Try building from generated source, too.
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj).$(TOOLSET)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+# End of this set of suffix rules
+### Rules for final target.
+LDFLAGS_Debug := -pthread \
+ -Wl,-z,noexecstack \
+ -Wl,-uIsHeapProfilerRunning,-uProfilerStart \
+ -Wl,-u_Z21InitialMallocHook_NewPKvj,-u_Z22InitialMallocHook_MMapPKvS0_jiiix,-u_Z22InitialMallocHook_SbrkPKvi \
+ -Wl,-u_Z21InitialMallocHook_NewPKvm,-u_Z22InitialMallocHook_MMapPKvS0_miiil,-u_Z22InitialMallocHook_SbrkPKvl \
+ -rdynamic
+
+LDFLAGS_Release := -pthread \
+ -Wl,-z,noexecstack \
+ -Wl,-uIsHeapProfilerRunning,-uProfilerStart \
+ -Wl,-u_Z21InitialMallocHook_NewPKvj,-u_Z22InitialMallocHook_MMapPKvS0_jiiix,-u_Z22InitialMallocHook_SbrkPKvi \
+ -Wl,-u_Z21InitialMallocHook_NewPKvm,-u_Z22InitialMallocHook_MMapPKvS0_miiil,-u_Z22InitialMallocHook_SbrkPKvl \
+ -Wl,--gc-sections
+
+LIBS := -lrt \
+ -ldl \
+ -lgtk-x11-2.0 \
+ -lgdk-x11-2.0 \
+ -latk-1.0 \
+ -lgio-2.0 \
+ -lpangoft2-1.0 \
+ -lgdk_pixbuf-2.0 \
+ -lm \
+ -lpangocairo-1.0 \
+ -lcairo \
+ -lpango-1.0 \
+ -lfreetype \
+ -lfontconfig \
+ -lgobject-2.0 \
+ -lgmodule-2.0 \
+ -lgthread-2.0 \
+ -lglib-2.0 \
+ -lnss3 \
+ -lnssutil3 \
+ -lsmime3 \
+ -lplds4 \
+ -lplc4 \
+ -lnspr4 \
+ -lpthread \
+ -lz \
+ -lgconf-2
+
+$(builddir)/net_unittests: GYP_LDFLAGS := $(LDFLAGS_$(BUILDTYPE))
+$(builddir)/net_unittests: LIBS := $(LIBS)
+$(builddir)/net_unittests: TOOLSET := $(TOOLSET)
+$(builddir)/net_unittests: $(OBJS) $(obj).target/net/libnet.a $(obj).target/net/libnet_test_support.a $(obj).target/base/libbase.a $(obj).target/base/libbase_i18n.a $(obj).target/testing/libgmock.a $(obj).target/testing/libgtest.a $(obj).target/third_party/zlib/libzlib.a $(obj).target/base/allocator/liballocator.a $(obj).target/third_party/modp_b64/libmodp_b64.a $(obj).target/base/third_party/dynamic_annotations/libdynamic_annotations.a $(obj).target/base/libsymbolize.a $(obj).target/net/third_party/nss/libssl.a $(obj).target/base/libxdg_mime.a $(obj).target/third_party/libevent/libevent.a $(obj).target/third_party/icu/libicui18n.a $(obj).target/third_party/icu/libicuuc.a $(obj).target/third_party/icu/libicudata.a $(obj).target/build/temp_gyp/libgoogleurl.a $(obj).target/sdch/libsdch.a $(obj).target/net/libnet_base.a $(obj).target/v8/tools/gyp/libv8_snapshot.a $(obj).target/v8/tools/gyp/libv8_base.a FORCE_DO_CMD
+ $(call do_cmd,link)
+
+all_deps += $(builddir)/net_unittests
+# Add target alias
+.PHONY: net_unittests
+net_unittests: $(builddir)/net_unittests
+
+# Add executable to "all" target.
+.PHONY: all
+all: $(builddir)/net_unittests
+
diff --git a/net/ocsp/nss_ocsp.cc b/net/ocsp/nss_ocsp.cc
index c135dfd..702baa6 100644
--- a/net/ocsp/nss_ocsp.cc
+++ b/net/ocsp/nss_ocsp.cc
@@ -15,6 +15,7 @@
#include "base/compiler_specific.h"
#include "base/condition_variable.h"
+#include "base/histogram.h"
#include "base/logging.h"
#include "base/message_loop.h"
#include "base/singleton.h"
@@ -23,6 +24,7 @@
#include "googleurl/src/gurl.h"
#include "net/base/io_buffer.h"
#include "net/base/load_flags.h"
+#include "net/http/http_request_headers.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
@@ -63,6 +65,8 @@
PRUint32* http_response_data_len);
SECStatus OCSPFree(SEC_HTTP_REQUEST_SESSION request);
+char* GetAlternateOCSPAIAInfo(CERTCertificate *cert);
+
class OCSPInitSingleton : public MessageLoop::DestructionObserver {
public:
// Called on IO thread.
@@ -97,6 +101,11 @@
: io_loop_(MessageLoopForIO::current()) {
DCHECK(io_loop_);
io_loop_->AddDestructionObserver(this);
+
+ // NSS calls the functions in the function table to download certificates
+ // or CRLs or talk to OCSP responders over HTTP. These functions must
+ // set an NSS/NSPR error code when they fail. Otherwise NSS will get the
+ // residual error code from an earlier failed function call.
client_fcn_.version = 1;
SEC_HttpClientFcnV1Struct *ft = &client_fcn_.fcnTable.ftable1;
ft->createSessionFcn = OCSPCreateSession;
@@ -112,6 +121,20 @@
if (status != SECSuccess) {
NOTREACHED() << "Error initializing OCSP: " << PR_GetError();
}
+
+ // Work around NSS bugs 524013 and 564334. NSS incorrectly thinks the
+ // CRLs for Network Solutions Certificate Authority have bad signatures,
+ // which causes certificates issued by that CA to be reported as revoked.
+ // By using OCSP for those certificates, which don't have AIA extensions,
+ // we can work around these bugs. See http://crbug.com/41730.
+ CERT_StringFromCertFcn old_callback = NULL;
+ status = CERT_RegisterAlternateOCSPAIAInfoCallBack(
+ GetAlternateOCSPAIAInfo, &old_callback);
+ if (status == SECSuccess) {
+ DCHECK(!old_callback);
+ } else {
+ NOTREACHED() << "Error initializing OCSP: " << PR_GetError();
+ }
}
virtual ~OCSPInitSingleton() {
@@ -166,10 +189,8 @@
}
void AddHeader(const char* http_header_name, const char* http_header_value) {
- if (!extra_request_headers_.empty())
- extra_request_headers_ += "\r\n";
- StringAppendF(&extra_request_headers_,
- "%s: %s", http_header_name, http_header_value);
+ extra_request_headers_.SetHeader(http_header_name,
+ http_header_value);
}
void Start() {
@@ -343,14 +364,12 @@
DCHECK(!upload_content_type_.empty());
request_->set_method("POST");
- if (!extra_request_headers_.empty())
- extra_request_headers_ += "\r\n";
- StringAppendF(&extra_request_headers_,
- "Content-Type: %s", upload_content_type_.c_str());
+ extra_request_headers_.SetHeader(
+ net::HttpRequestHeaders::kContentType, upload_content_type_);
request_->AppendBytesToUpload(upload_content_.data(),
static_cast<int>(upload_content_.size()));
}
- if (!extra_request_headers_.empty())
+ if (!extra_request_headers_.IsEmpty())
request_->SetExtraRequestHeaders(extra_request_headers_);
request_->Start();
@@ -388,7 +407,7 @@
base::TimeDelta timeout_; // The timeout for OCSP
URLRequest* request_; // The actual request this wraps
scoped_refptr<net::IOBuffer> buffer_; // Read buffer
- std::string extra_request_headers_; // Extra headers for the request, if any
+ net::HttpRequestHeaders extra_request_headers_;
std::string upload_content_; // HTTP POST payload
std::string upload_content_type_; // MIME type of POST payload
@@ -421,8 +440,10 @@
// We dont' support "https" because we haven't thought about
// whether it's safe to re-enter this code from talking to an OCSP
// responder over SSL.
- if (strcmp(http_protocol_variant, "http") != 0)
+ if (strcmp(http_protocol_variant, "http") != 0) {
+ PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
return NULL;
+ }
// TODO(ukai): If |host| is an IPv6 literal, we need to quote it with
// square brackets [].
@@ -455,6 +476,10 @@
DCHECK(!MessageLoop::current());
if (OCSPInitSingleton::url_request_context() == NULL) {
LOG(ERROR) << "No URLRequestContext for OCSP handler.";
+ // The application failed to call SetURLRequestContextForOCSP, so we
+ // can't create and use URLRequest. PR_NOT_IMPLEMENTED_ERROR is not an
+ // accurate error code for this error condition, but is close enough.
+ PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
return SECFailure;
}
*pSession = new OCSPServerSession(host, portnum);
@@ -532,20 +557,21 @@
// It is helper routine for OCSP trySendAndReceiveFcn.
// |http_response_data_len| could be used as input parameter. If it has
// non-zero value, it is considered as maximum size of |http_response_data|.
-bool OCSPSetResponse(OCSPRequestSession* req,
- PRUint16* http_response_code,
- const char** http_response_content_type,
- const char** http_response_headers,
- const char** http_response_data,
- PRUint32* http_response_data_len) {
+SECStatus OCSPSetResponse(OCSPRequestSession* req,
+ PRUint16* http_response_code,
+ const char** http_response_content_type,
+ const char** http_response_headers,
+ const char** http_response_data,
+ PRUint32* http_response_data_len) {
DCHECK(req->Finished());
const std::string& data = req->http_response_data();
if (http_response_data_len && *http_response_data_len) {
if (*http_response_data_len < data.size()) {
- LOG(ERROR) << "data size too large: " << *http_response_data_len
+ LOG(ERROR) << "response body too large: " << *http_response_data_len
<< " < " << data.size();
*http_response_data_len = data.size();
- return false;
+ PORT_SetError(SEC_ERROR_BAD_HTTP_RESPONSE);
+ return SECFailure;
}
}
LOG(INFO) << "OCSP response "
@@ -563,7 +589,7 @@
*http_response_data = data.data();
if (http_response_data_len)
*http_response_data_len = data.size();
- return true;
+ return SECSuccess;
}
SECStatus OCSPTrySendAndReceive(SEC_HTTP_REQUEST_SESSION request,
@@ -573,6 +599,12 @@
const char** http_response_headers,
const char** http_response_data,
PRUint32* http_response_data_len) {
+ if (http_response_data_len) {
+ // We must always set an output value, even on failure. The output value 0
+ // means the failure was unrelated to the acceptable response data length.
+ *http_response_data_len = 0;
+ }
+
LOG(INFO) << "OCSP try send and receive";
DCHECK(!MessageLoop::current());
OCSPRequestSession* req = reinterpret_cast<OCSPRequestSession*>(request);
@@ -584,30 +616,70 @@
// We support blocking mode only, so this function shouldn't be called
// again when req has stareted or finished.
NOTREACHED();
- goto failed;
+ PORT_SetError(SEC_ERROR_BAD_HTTP_RESPONSE); // Simple approximation.
+ return SECFailure;
}
- req->Start();
- if (!req->Wait())
- goto failed;
- // If the response code is -1, the request failed and there is no response.
- if (req->http_response_code() == static_cast<PRUint16>(-1))
- goto failed;
+ const base::Time start_time = base::Time::Now();
+ req->Start();
+ if (!req->Wait() || req->http_response_code() == static_cast<PRUint16>(-1)) {
+ // If the response code is -1, the request failed and there is no response.
+ PORT_SetError(SEC_ERROR_BAD_HTTP_RESPONSE); // Simple approximation.
+ return SECFailure;
+ }
+ const base::TimeDelta duration = base::Time::Now() - start_time;
+
+ // We want to know if this was:
+ // 1) An OCSP request
+ // 2) A CRL request
+ // 3) A request for a missing intermediate certificate
+ // There's no sure way to do this, so we use heuristics like MIME type and
+ // URL.
+ const char* mime_type = req->http_response_content_type().c_str();
+ bool is_ocsp_resp =
+ strcasecmp(mime_type, "application/ocsp-response") == 0;
+ bool is_crl_resp = strcasecmp(mime_type, "application/x-pkcs7-crl") == 0 ||
+ strcasecmp(mime_type, "application/x-x509-crl") == 0 ||
+ strcasecmp(mime_type, "application/pkix-crl") == 0;
+ bool is_crt_resp =
+ strcasecmp(mime_type, "application/x-x509-ca-cert") == 0 ||
+ strcasecmp(mime_type, "application/x-x509-server-cert") == 0 ||
+ strcasecmp(mime_type, "application/pkix-cert") == 0 ||
+ strcasecmp(mime_type, "application/pkcs7-mime") == 0;
+ bool known_resp_type = is_crt_resp || is_crt_resp || is_ocsp_resp;
+
+ bool crl_in_url = false, crt_in_url = false, ocsp_in_url = false,
+ have_url_hint = false;
+ if (!known_resp_type) {
+ const std::string path = req->url().path();
+ const std::string host = req->url().host();
+ crl_in_url = strcasestr(path.c_str(), ".crl") != NULL;
+ crt_in_url = strcasestr(path.c_str(), ".crt") != NULL ||
+ strcasestr(path.c_str(), ".p7c") != NULL ||
+ strcasestr(path.c_str(), ".cer") != NULL;
+ ocsp_in_url = strcasestr(host.c_str(), "ocsp") != NULL;
+ have_url_hint = crl_in_url || crt_in_url || ocsp_in_url;
+ }
+
+ if (is_ocsp_resp ||
+ (!known_resp_type && (ocsp_in_url ||
+ (!have_url_hint &&
+ req->http_request_method() == "POST")))) {
+ UMA_HISTOGRAM_TIMES("Net.OCSPRequestTimeMs", duration);
+ } else if (is_crl_resp || (!known_resp_type && crl_in_url)) {
+ UMA_HISTOGRAM_TIMES("Net.CRLRequestTimeMs", duration);
+ } else if (is_crt_resp || (!known_resp_type && crt_in_url)) {
+ UMA_HISTOGRAM_TIMES("Net.CRTRequestTimeMs", duration);
+ } else {
+ UMA_HISTOGRAM_TIMES("Net.UnknownTypeRequestTimeMs", duration);
+ }
return OCSPSetResponse(
req, http_response_code,
http_response_content_type,
http_response_headers,
http_response_data,
- http_response_data_len) ? SECSuccess : SECFailure;
-
- failed:
- if (http_response_data_len) {
- // We must always set an output value, even on failure. The output value 0
- // means the failure was unrelated to the acceptable response data length.
- *http_response_data_len = 0;
- }
- return SECFailure;
+ http_response_data_len);
}
SECStatus OCSPFree(SEC_HTTP_REQUEST_SESSION request) {
@@ -619,6 +691,92 @@
return SECSuccess;
}
+// Data for GetAlternateOCSPAIAInfo.
+
+// CN=Network Solutions Certificate Authority,O=Network Solutions L.L.C.,C=US
+//
+// There are two CAs with this name. Their key IDs are listed next.
+const unsigned char network_solutions_ca_name[] = {
+ 0x30, 0x62, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04,
+ 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x21, 0x30, 0x1f, 0x06,
+ 0x03, 0x55, 0x04, 0x0a, 0x13, 0x18, 0x4e, 0x65, 0x74, 0x77,
+ 0x6f, 0x72, 0x6b, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69,
+ 0x6f, 0x6e, 0x73, 0x20, 0x4c, 0x2e, 0x4c, 0x2e, 0x43, 0x2e,
+ 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13,
+ 0x27, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x53,
+ 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x43,
+ 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65,
+ 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79
+};
+const unsigned int network_solutions_ca_name_len = 100;
+
+// This CA is an intermediate CA, subordinate to UTN-USERFirst-Hardware.
+const unsigned char network_solutions_ca_key_id[] = {
+ 0x3c, 0x41, 0xe2, 0x8f, 0x08, 0x08, 0xa9, 0x4c, 0x25, 0x89,
+ 0x8d, 0x6d, 0xc5, 0x38, 0xd0, 0xfc, 0x85, 0x8c, 0x62, 0x17
+};
+const unsigned int network_solutions_ca_key_id_len = 20;
+
+// This CA is a root CA. It is also cross-certified by
+// UTN-USERFirst-Hardware.
+const unsigned char network_solutions_ca_key_id2[] = {
+ 0x21, 0x30, 0xc9, 0xfb, 0x00, 0xd7, 0x4e, 0x98, 0xda, 0x87,
+ 0xaa, 0x2a, 0xd0, 0xa7, 0x2e, 0xb1, 0x40, 0x31, 0xa7, 0x4c
+};
+const unsigned int network_solutions_ca_key_id2_len = 20;
+
+// An entry in our OCSP responder table. |issuer| and |issuer_key_id| are
+// the key. |ocsp_url| is the value.
+struct OCSPResponderTableEntry {
+ SECItem issuer;
+ SECItem issuer_key_id;
+ const char *ocsp_url;
+};
+
+const OCSPResponderTableEntry g_ocsp_responder_table[] = {
+ {
+ {
+ siBuffer,
+ const_cast<unsigned char*>(network_solutions_ca_name),
+ network_solutions_ca_name_len
+ },
+ {
+ siBuffer,
+ const_cast<unsigned char*>(network_solutions_ca_key_id),
+ network_solutions_ca_key_id_len
+ },
+ "http://ocsp.netsolssl.com"
+ },
+ {
+ {
+ siBuffer,
+ const_cast<unsigned char*>(network_solutions_ca_name),
+ network_solutions_ca_name_len
+ },
+ {
+ siBuffer,
+ const_cast<unsigned char*>(network_solutions_ca_key_id2),
+ network_solutions_ca_key_id2_len
+ },
+ "http://ocsp.netsolssl.com"
+ }
+};
+
+char* GetAlternateOCSPAIAInfo(CERTCertificate *cert) {
+ if (cert && !cert->isRoot && cert->authKeyID) {
+ for (unsigned int i=0; i < arraysize(g_ocsp_responder_table); i++) {
+ if (SECITEM_CompareItem(&g_ocsp_responder_table[i].issuer,
+ &cert->derIssuer) == SECEqual &&
+ SECITEM_CompareItem(&g_ocsp_responder_table[i].issuer_key_id,
+ &cert->authKeyID->keyID) == SECEqual) {
+ return PORT_Strdup(g_ocsp_responder_table[i].ocsp_url);
+ }
+ }
+ }
+
+ return NULL;
+}
+
} // anonymous namespace
namespace net {
diff --git a/net/proxy/init_proxy_resolver.cc b/net/proxy/init_proxy_resolver.cc
index 236e094..04f828b 100644
--- a/net/proxy/init_proxy_resolver.cc
+++ b/net/proxy/init_proxy_resolver.cc
@@ -8,7 +8,7 @@
#include "base/format_macros.h"
#include "base/logging.h"
#include "base/string_util.h"
-#include "net/base/load_log.h"
+#include "net/base/net_log.h"
#include "net/base/net_errors.h"
#include "net/proxy/proxy_config.h"
#include "net/proxy/proxy_resolver.h"
@@ -17,14 +17,17 @@
namespace net {
InitProxyResolver::InitProxyResolver(ProxyResolver* resolver,
- ProxyScriptFetcher* proxy_script_fetcher)
+ ProxyScriptFetcher* proxy_script_fetcher,
+ NetLog* net_log)
: resolver_(resolver),
proxy_script_fetcher_(proxy_script_fetcher),
ALLOW_THIS_IN_INITIALIZER_LIST(io_callback_(
this, &InitProxyResolver::OnIOCompletion)),
user_callback_(NULL),
current_pac_url_index_(0u),
- next_state_(STATE_NONE) {
+ next_state_(STATE_NONE),
+ net_log_(BoundNetLog::Make(
+ net_log, NetLog::SOURCE_INIT_PROXY_RESOLVER)) {
}
InitProxyResolver::~InitProxyResolver() {
@@ -33,16 +36,12 @@
}
int InitProxyResolver::Init(const ProxyConfig& config,
- CompletionCallback* callback,
- LoadLog* load_log) {
+ CompletionCallback* callback) {
DCHECK_EQ(STATE_NONE, next_state_);
DCHECK(callback);
DCHECK(config.MayRequirePACResolver());
- DCHECK(!load_log_);
- load_log_ = load_log;
-
- LoadLog::BeginEvent(load_log_, LoadLog::TYPE_INIT_PROXY_RESOLVER);
+ net_log_.BeginEvent(NetLog::TYPE_INIT_PROXY_RESOLVER, NULL);
pac_urls_ = BuildPacUrlsFallbackList(config);
DCHECK(!pac_urls_.empty());
@@ -64,13 +63,10 @@
InitProxyResolver::UrlList InitProxyResolver::BuildPacUrlsFallbackList(
const ProxyConfig& config) const {
UrlList pac_urls;
- if (config.auto_detect) {
- GURL pac_url = resolver_->expects_pac_bytes() ?
- GURL("http://wpad/wpad.dat") : GURL();
- pac_urls.push_back(pac_url);
- }
- if (config.pac_url.is_valid())
- pac_urls.push_back(config.pac_url);
+ if (config.auto_detect())
+ pac_urls.push_back(PacURL(true, GURL()));
+ if (config.has_pac_url())
+ pac_urls.push_back(PacURL(false, config.pac_url()));
return pac_urls;
}
@@ -122,70 +118,73 @@
int InitProxyResolver::DoFetchPacScript() {
DCHECK(resolver_->expects_pac_bytes());
- LoadLog::BeginEvent(load_log_,
- LoadLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT);
-
next_state_ = STATE_FETCH_PAC_SCRIPT_COMPLETE;
- const GURL& pac_url = current_pac_url();
+ const PacURL& pac_url = current_pac_url();
- LoadLog::AddString(load_log_, pac_url.spec());
+ const GURL effective_pac_url =
+ pac_url.auto_detect ? GURL("http://wpad/wpad.dat") : pac_url.url;
+
+ net_log_.BeginEvent(
+ NetLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT,
+ new NetLogStringParameter("url",
+ effective_pac_url.possibly_invalid_spec()));
if (!proxy_script_fetcher_) {
- LoadLog::AddStringLiteral(load_log_,
- "Can't download PAC script, because no fetcher was specified");
+ net_log_.AddEvent(NetLog::TYPE_INIT_PROXY_RESOLVER_HAS_NO_FETCHER, NULL);
return ERR_UNEXPECTED;
}
- return proxy_script_fetcher_->Fetch(pac_url, &pac_bytes_, &io_callback_);
+ return proxy_script_fetcher_->Fetch(effective_pac_url,
+ &pac_script_,
+ &io_callback_);
}
int InitProxyResolver::DoFetchPacScriptComplete(int result) {
DCHECK(resolver_->expects_pac_bytes());
- LoadLog::AddString(load_log_,
- StringPrintf(
- "Completed fetch with result %s. Received %" PRIuS " bytes",
- ErrorToString(result),
- pac_bytes_.size()));
-
- LoadLog::EndEvent(load_log_,
- LoadLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT);
-
- if (result != OK)
+ if (result == OK) {
+ net_log_.EndEvent(NetLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT, NULL);
+ } else {
+ net_log_.EndEvent(
+ NetLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT,
+ new NetLogIntegerParameter("net_error", result));
return TryToFallbackPacUrl(result);
+ }
next_state_ = STATE_SET_PAC_SCRIPT;
return result;
}
int InitProxyResolver::DoSetPacScript() {
- LoadLog::BeginEvent(load_log_,
- LoadLog::TYPE_INIT_PROXY_RESOLVER_SET_PAC_SCRIPT);
+ net_log_.BeginEvent(NetLog::TYPE_INIT_PROXY_RESOLVER_SET_PAC_SCRIPT, NULL);
- const GURL& pac_url = current_pac_url();
+ const PacURL& pac_url = current_pac_url();
next_state_ = STATE_SET_PAC_SCRIPT_COMPLETE;
- return resolver_->expects_pac_bytes() ?
- resolver_->SetPacScriptByData(pac_bytes_, &io_callback_) :
- resolver_->SetPacScriptByUrl(pac_url, &io_callback_);
+ scoped_refptr<ProxyResolverScriptData> script_data;
+
+ if (resolver_->expects_pac_bytes()) {
+ script_data = ProxyResolverScriptData::FromUTF16(pac_script_);
+ } else {
+ script_data = pac_url.auto_detect ?
+ ProxyResolverScriptData::ForAutoDetect() :
+ ProxyResolverScriptData::FromURL(pac_url.url);
+ }
+
+ return resolver_->SetPacScript(script_data, &io_callback_);
}
int InitProxyResolver::DoSetPacScriptComplete(int result) {
if (result != OK) {
- LoadLog::AddString(load_log_,
- StringPrintf("Failed initializing the PAC script with error: %s",
- ErrorToString(result)));
- LoadLog::EndEvent(load_log_,
- LoadLog::TYPE_INIT_PROXY_RESOLVER_SET_PAC_SCRIPT);
+ net_log_.EndEvent(
+ NetLog::TYPE_INIT_PROXY_RESOLVER_SET_PAC_SCRIPT,
+ new NetLogIntegerParameter("net_error", result));
return TryToFallbackPacUrl(result);
}
- LoadLog::AddStringLiteral(load_log_, "Successfully initialized PAC script.");
-
- LoadLog::EndEvent(load_log_,
- LoadLog::TYPE_INIT_PROXY_RESOLVER_SET_PAC_SCRIPT);
+ net_log_.EndEvent(NetLog::TYPE_INIT_PROXY_RESOLVER_SET_PAC_SCRIPT, NULL);
return result;
}
@@ -200,7 +199,8 @@
// Advance to next URL in our list.
++current_pac_url_index_;
- LoadLog::AddStringLiteral(load_log_, "Falling back to next PAC URL...");
+ net_log_.AddEvent(
+ NetLog::TYPE_INIT_PROXY_RESOLVER_FALLING_BACK_TO_NEXT_PAC_URL, NULL);
next_state_ = GetStartState();
@@ -212,19 +212,19 @@
STATE_FETCH_PAC_SCRIPT : STATE_SET_PAC_SCRIPT;
}
-const GURL& InitProxyResolver::current_pac_url() const {
+const InitProxyResolver::PacURL& InitProxyResolver::current_pac_url() const {
DCHECK_LT(current_pac_url_index_, pac_urls_.size());
return pac_urls_[current_pac_url_index_];
}
void InitProxyResolver::DidCompleteInit() {
- LoadLog::EndEvent(load_log_, LoadLog::TYPE_INIT_PROXY_RESOLVER);
+ net_log_.EndEvent(NetLog::TYPE_INIT_PROXY_RESOLVER, NULL);
}
void InitProxyResolver::Cancel() {
DCHECK_NE(STATE_NONE, next_state_);
- LoadLog::AddEvent(load_log_, LoadLog::TYPE_CANCELLED);
+ net_log_.AddEvent(NetLog::TYPE_CANCELLED, NULL);
switch (next_state_) {
case STATE_FETCH_PAC_SCRIPT_COMPLETE:
diff --git a/net/proxy/init_proxy_resolver.h b/net/proxy/init_proxy_resolver.h
index 0bec8e9..814e932 100644
--- a/net/proxy/init_proxy_resolver.h
+++ b/net/proxy/init_proxy_resolver.h
@@ -10,10 +10,10 @@
#include "googleurl/src/gurl.h"
#include "net/base/completion_callback.h"
+#include "net/base/net_log.h"
namespace net {
-class LoadLog;
class ProxyConfig;
class ProxyResolver;
class ProxyScriptFetcher;
@@ -36,18 +36,18 @@
//
class InitProxyResolver {
public:
- // |resolver| and |proxy_script_fetcher| must remain valid for
+ // |resolver|, |proxy_script_fetcher| and |net_log| must remain valid for
// the lifespan of InitProxyResolver.
InitProxyResolver(ProxyResolver* resolver,
- ProxyScriptFetcher* proxy_script_fetcher);
+ ProxyScriptFetcher* proxy_script_fetcher,
+ NetLog* net_log);
// Aborts any in-progress request.
~InitProxyResolver();
// Apply the PAC settings of |config| to |resolver_|.
int Init(const ProxyConfig& config,
- CompletionCallback* callback,
- LoadLog* load_log);
+ CompletionCallback* callback);
private:
enum State {
@@ -57,7 +57,15 @@
STATE_SET_PAC_SCRIPT,
STATE_SET_PAC_SCRIPT_COMPLETE,
};
- typedef std::vector<GURL> UrlList;
+
+ struct PacURL {
+ PacURL(bool auto_detect, const GURL& url)
+ : auto_detect(auto_detect), url(url) {}
+ bool auto_detect;
+ GURL url;
+ };
+
+ typedef std::vector<PacURL> UrlList;
// Returns ordered list of PAC urls to try for |config|.
UrlList BuildPacUrlsFallbackList(const ProxyConfig& config) const;
@@ -83,7 +91,7 @@
State GetStartState() const;
// Returns the current PAC URL we are fetching/testing.
- const GURL& current_pac_url() const;
+ const PacURL& current_pac_url() const;
void DidCompleteInit();
void Cancel();
@@ -97,12 +105,12 @@
size_t current_pac_url_index_;
// Filled when the PAC script fetch completes.
- std::string pac_bytes_;
+ string16 pac_script_;
UrlList pac_urls_;
State next_state_;
- scoped_refptr<LoadLog> load_log_;
+ BoundNetLog net_log_;
DISALLOW_COPY_AND_ASSIGN(InitProxyResolver);
};
diff --git a/net/proxy/init_proxy_resolver_unittest.cc b/net/proxy/init_proxy_resolver_unittest.cc
index 46b8b79..458cc53 100644
--- a/net/proxy/init_proxy_resolver_unittest.cc
+++ b/net/proxy/init_proxy_resolver_unittest.cc
@@ -4,10 +4,11 @@
#include <vector>
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
#include "net/base/net_errors.h"
-#include "net/base/load_log.h"
-#include "net/base/load_log_util.h"
-#include "net/base/load_log_unittest.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
#include "net/base/test_completion_callback.h"
#include "net/proxy/init_proxy_resolver.h"
#include "net/proxy/proxy_config.h"
@@ -34,12 +35,12 @@
set_pac_error(set_pac_error) {
}
- std::string bytes() const {
+ string16 text() const {
if (set_pac_error == OK)
- return url.spec() + "!valid-script";
+ return UTF8ToUTF16(url.spec() + "!valid-script");
if (fetch_error == OK)
- return url.spec() + "!invalid-script";
- return std::string();
+ return UTF8ToUTF16(url.spec() + "!invalid-script");
+ return string16();
}
GURL url;
@@ -69,17 +70,17 @@
if (it->url == url)
return *it;
}
- CHECK(false) << "Rule not found for " << url;
+ LOG(FATAL) << "Rule not found for " << url;
return rules_[0];
}
- const Rule& GetRuleByBytes(const std::string& bytes) const {
+ const Rule& GetRuleByText(const string16& text) const {
for (RuleList::const_iterator it = rules_.begin(); it != rules_.end();
++it) {
- if (it->bytes() == bytes)
+ if (it->text() == text)
return *it;
}
- CHECK(false) << "Rule not found for " << bytes;
+ LOG(FATAL) << "Rule not found for " << text;
return rules_[0];
}
@@ -94,13 +95,13 @@
// ProxyScriptFetcher implementation.
virtual int Fetch(const GURL& url,
- std::string* bytes,
+ string16* text,
CompletionCallback* callback) {
const Rules::Rule& rule = rules_->GetRuleByUrl(url);
int rv = rule.fetch_error;
EXPECT_NE(ERR_UNEXPECTED, rv);
if (rv == OK)
- *bytes = rule.bytes();
+ *text = rule.text();
return rv;
}
@@ -120,7 +121,7 @@
ProxyInfo* /*results*/,
CompletionCallback* /*callback*/,
RequestHandle* /*request_handle*/,
- LoadLog* /*load_log*/) {
+ const BoundNetLog& /*net_log*/) {
NOTREACHED();
return ERR_UNEXPECTED;
}
@@ -129,35 +130,37 @@
NOTREACHED();
}
- virtual int SetPacScript(const GURL& pac_url,
- const std::string& pac_bytes,
- CompletionCallback* callback) {
+ virtual int SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ CompletionCallback* callback) {
+
+ const GURL url =
+ script_data->type() == ProxyResolverScriptData::TYPE_SCRIPT_URL ?
+ script_data->url() : GURL();
+
const Rules::Rule& rule = expects_pac_bytes() ?
- rules_->GetRuleByBytes(pac_bytes) :
- rules_->GetRuleByUrl(pac_url);
+ rules_->GetRuleByText(script_data->utf16()) :
+ rules_->GetRuleByUrl(url);
int rv = rule.set_pac_error;
EXPECT_NE(ERR_UNEXPECTED, rv);
- if (expects_pac_bytes())
- EXPECT_EQ(rule.bytes(), pac_bytes);
- else
- EXPECT_EQ(rule.url, pac_url);
-
- if (rv == OK) {
- pac_bytes_ = pac_bytes;
- pac_url_ = pac_url;
+ if (expects_pac_bytes()) {
+ EXPECT_EQ(rule.text(), script_data->utf16());
+ } else {
+ EXPECT_EQ(rule.url, url);
}
+
+ if (rv == OK)
+ script_data_ = script_data;
return rv;
}
- const std::string& pac_bytes() const { return pac_bytes_; }
- const GURL& pac_url() const { return pac_url_; }
+ const ProxyResolverScriptData* script_data() const { return script_data_; }
private:
const Rules* rules_;
- std::string pac_bytes_;
- GURL pac_url_;
+ scoped_refptr<ProxyResolverScriptData> script_data_;
};
// Succeed using custom PAC script.
@@ -167,29 +170,30 @@
RuleBasedProxyScriptFetcher fetcher(&rules);
ProxyConfig config;
- config.pac_url = GURL("http://custom/proxy.pac");
+ config.set_pac_url(GURL("http://custom/proxy.pac"));
Rules::Rule rule = rules.AddSuccessRule("http://custom/proxy.pac");
TestCompletionCallback callback;
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
- InitProxyResolver init(&resolver, &fetcher);
- EXPECT_EQ(OK, init.Init(config, &callback, log));
- EXPECT_EQ(rule.bytes(), resolver.pac_bytes());
+ CapturingNetLog log(CapturingNetLog::kUnbounded);
+ InitProxyResolver init(&resolver, &fetcher, &log);
+ EXPECT_EQ(OK, init.Init(config, &callback));
+ EXPECT_EQ(rule.text(), resolver.script_data()->utf16());
- // Check the LoadLog was filled correctly.
- EXPECT_EQ(9u, log->entries().size());
+ // Check the NetLog was filled correctly.
+ EXPECT_EQ(6u, log.entries().size());
EXPECT_TRUE(LogContainsBeginEvent(
- *log, 0, LoadLog::TYPE_INIT_PROXY_RESOLVER));
+ log.entries(), 0, NetLog::TYPE_INIT_PROXY_RESOLVER));
EXPECT_TRUE(LogContainsBeginEvent(
- *log, 1, LoadLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT));
+ log.entries(), 1, NetLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT));
EXPECT_TRUE(LogContainsEndEvent(
- *log, 4, LoadLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT));
+ log.entries(), 2, NetLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT));
EXPECT_TRUE(LogContainsBeginEvent(
- *log, 5, LoadLog::TYPE_INIT_PROXY_RESOLVER_SET_PAC_SCRIPT));
+ log.entries(), 3, NetLog::TYPE_INIT_PROXY_RESOLVER_SET_PAC_SCRIPT));
EXPECT_TRUE(LogContainsEndEvent(
- *log, 7, LoadLog::TYPE_INIT_PROXY_RESOLVER_SET_PAC_SCRIPT));
- EXPECT_TRUE(LogContainsEndEvent(*log, 8, LoadLog::TYPE_INIT_PROXY_RESOLVER));
+ log.entries(), 4, NetLog::TYPE_INIT_PROXY_RESOLVER_SET_PAC_SCRIPT));
+ EXPECT_TRUE(LogContainsEndEvent(
+ log.entries(), 5, NetLog::TYPE_INIT_PROXY_RESOLVER));
}
// Fail downloading the custom PAC script.
@@ -199,25 +203,26 @@
RuleBasedProxyScriptFetcher fetcher(&rules);
ProxyConfig config;
- config.pac_url = GURL("http://custom/proxy.pac");
+ config.set_pac_url(GURL("http://custom/proxy.pac"));
rules.AddFailDownloadRule("http://custom/proxy.pac");
TestCompletionCallback callback;
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
- InitProxyResolver init(&resolver, &fetcher);
- EXPECT_EQ(kFailedDownloading, init.Init(config, &callback, log));
- EXPECT_EQ("", resolver.pac_bytes());
+ CapturingNetLog log(CapturingNetLog::kUnbounded);
+ InitProxyResolver init(&resolver, &fetcher, &log);
+ EXPECT_EQ(kFailedDownloading, init.Init(config, &callback));
+ EXPECT_EQ(NULL, resolver.script_data());
- // Check the LoadLog was filled correctly.
- EXPECT_EQ(6u, log->entries().size());
+ // Check the NetLog was filled correctly.
+ EXPECT_EQ(4u, log.entries().size());
EXPECT_TRUE(LogContainsBeginEvent(
- *log, 0, LoadLog::TYPE_INIT_PROXY_RESOLVER));
+ log.entries(), 0, NetLog::TYPE_INIT_PROXY_RESOLVER));
EXPECT_TRUE(LogContainsBeginEvent(
- *log, 1, LoadLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT));
+ log.entries(), 1, NetLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT));
EXPECT_TRUE(LogContainsEndEvent(
- *log, 4, LoadLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT));
- EXPECT_TRUE(LogContainsEndEvent(*log, 5, LoadLog::TYPE_INIT_PROXY_RESOLVER));
+ log.entries(), 2, NetLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT));
+ EXPECT_TRUE(LogContainsEndEvent(
+ log.entries(), 3, NetLog::TYPE_INIT_PROXY_RESOLVER));
}
// Fail parsing the custom PAC script.
@@ -227,14 +232,14 @@
RuleBasedProxyScriptFetcher fetcher(&rules);
ProxyConfig config;
- config.pac_url = GURL("http://custom/proxy.pac");
+ config.set_pac_url(GURL("http://custom/proxy.pac"));
rules.AddFailParsingRule("http://custom/proxy.pac");
TestCompletionCallback callback;
- InitProxyResolver init(&resolver, &fetcher);
- EXPECT_EQ(kFailedParsing, init.Init(config, &callback, NULL));
- EXPECT_EQ("", resolver.pac_bytes());
+ InitProxyResolver init(&resolver, &fetcher, NULL);
+ EXPECT_EQ(kFailedParsing, init.Init(config, &callback));
+ EXPECT_EQ(NULL, resolver.script_data());
}
// Fail downloading the custom PAC script, because the fetcher was NULL.
@@ -243,12 +248,12 @@
RuleBasedProxyResolver resolver(&rules, true /*expects_pac_bytes*/);
ProxyConfig config;
- config.pac_url = GURL("http://custom/proxy.pac");
+ config.set_pac_url(GURL("http://custom/proxy.pac"));
TestCompletionCallback callback;
- InitProxyResolver init(&resolver, NULL);
- EXPECT_EQ(ERR_UNEXPECTED, init.Init(config, &callback, NULL));
- EXPECT_EQ("", resolver.pac_bytes());
+ InitProxyResolver init(&resolver, NULL, NULL);
+ EXPECT_EQ(ERR_UNEXPECTED, init.Init(config, &callback));
+ EXPECT_EQ(NULL, resolver.script_data());
}
// Succeeds in choosing autodetect (wpad).
@@ -258,14 +263,14 @@
RuleBasedProxyScriptFetcher fetcher(&rules);
ProxyConfig config;
- config.auto_detect = true;
+ config.set_auto_detect(true);
Rules::Rule rule = rules.AddSuccessRule("http://wpad/wpad.dat");
TestCompletionCallback callback;
- InitProxyResolver init(&resolver, &fetcher);
- EXPECT_EQ(OK, init.Init(config, &callback, NULL));
- EXPECT_EQ(rule.bytes(), resolver.pac_bytes());
+ InitProxyResolver init(&resolver, &fetcher, NULL);
+ EXPECT_EQ(OK, init.Init(config, &callback));
+ EXPECT_EQ(rule.text(), resolver.script_data()->utf16());
}
// Fails at WPAD (downloading), but succeeds in choosing the custom PAC.
@@ -275,16 +280,16 @@
RuleBasedProxyScriptFetcher fetcher(&rules);
ProxyConfig config;
- config.auto_detect = true;
- config.pac_url = GURL("http://custom/proxy.pac");
+ config.set_auto_detect(true);
+ config.set_pac_url(GURL("http://custom/proxy.pac"));
rules.AddFailDownloadRule("http://wpad/wpad.dat");
Rules::Rule rule = rules.AddSuccessRule("http://custom/proxy.pac");
TestCompletionCallback callback;
- InitProxyResolver init(&resolver, &fetcher);
- EXPECT_EQ(OK, init.Init(config, &callback, NULL));
- EXPECT_EQ(rule.bytes(), resolver.pac_bytes());
+ InitProxyResolver init(&resolver, &fetcher, NULL);
+ EXPECT_EQ(OK, init.Init(config, &callback));
+ EXPECT_EQ(rule.text(), resolver.script_data()->utf16());
}
// Fails at WPAD (parsing), but succeeds in choosing the custom PAC.
@@ -294,41 +299,46 @@
RuleBasedProxyScriptFetcher fetcher(&rules);
ProxyConfig config;
- config.auto_detect = true;
- config.pac_url = GURL("http://custom/proxy.pac");
+ config.set_auto_detect(true);
+ config.set_pac_url(GURL("http://custom/proxy.pac"));
rules.AddFailParsingRule("http://wpad/wpad.dat");
Rules::Rule rule = rules.AddSuccessRule("http://custom/proxy.pac");
TestCompletionCallback callback;
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
- InitProxyResolver init(&resolver, &fetcher);
- EXPECT_EQ(OK, init.Init(config, &callback, log));
- EXPECT_EQ(rule.bytes(), resolver.pac_bytes());
+ CapturingNetLog log(CapturingNetLog::kUnbounded);
+ InitProxyResolver init(&resolver, &fetcher, &log);
+ EXPECT_EQ(OK, init.Init(config, &callback));
+ EXPECT_EQ(rule.text(), resolver.script_data()->utf16());
- // Check the LoadLog was filled correctly.
+ // Check the NetLog was filled correctly.
// (Note that the Fetch and Set states are repeated since both WPAD and custom
// PAC scripts are tried).
- EXPECT_EQ(17u, log->entries().size());
+ EXPECT_EQ(11u, log.entries().size());
EXPECT_TRUE(LogContainsBeginEvent(
- *log, 0, LoadLog::TYPE_INIT_PROXY_RESOLVER));
+ log.entries(), 0, NetLog::TYPE_INIT_PROXY_RESOLVER));
EXPECT_TRUE(LogContainsBeginEvent(
- *log, 1, LoadLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT));
+ log.entries(), 1, NetLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT));
EXPECT_TRUE(LogContainsEndEvent(
- *log, 4, LoadLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT));
+ log.entries(), 2, NetLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT));
EXPECT_TRUE(LogContainsBeginEvent(
- *log, 5, LoadLog::TYPE_INIT_PROXY_RESOLVER_SET_PAC_SCRIPT));
+ log.entries(), 3, NetLog::TYPE_INIT_PROXY_RESOLVER_SET_PAC_SCRIPT));
EXPECT_TRUE(LogContainsEndEvent(
- *log, 7, LoadLog::TYPE_INIT_PROXY_RESOLVER_SET_PAC_SCRIPT));
+ log.entries(), 4, NetLog::TYPE_INIT_PROXY_RESOLVER_SET_PAC_SCRIPT));
+ EXPECT_TRUE(LogContainsEvent(
+ log.entries(), 5,
+ NetLog::TYPE_INIT_PROXY_RESOLVER_FALLING_BACK_TO_NEXT_PAC_URL,
+ NetLog::PHASE_NONE));
EXPECT_TRUE(LogContainsBeginEvent(
- *log, 9, LoadLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT));
+ log.entries(), 6, NetLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT));
EXPECT_TRUE(LogContainsEndEvent(
- *log, 12, LoadLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT));
+ log.entries(), 7, NetLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT));
EXPECT_TRUE(LogContainsBeginEvent(
- *log, 13, LoadLog::TYPE_INIT_PROXY_RESOLVER_SET_PAC_SCRIPT));
+ log.entries(), 8, NetLog::TYPE_INIT_PROXY_RESOLVER_SET_PAC_SCRIPT));
EXPECT_TRUE(LogContainsEndEvent(
- *log, 15, LoadLog::TYPE_INIT_PROXY_RESOLVER_SET_PAC_SCRIPT));
- EXPECT_TRUE(LogContainsEndEvent(*log, 16, LoadLog::TYPE_INIT_PROXY_RESOLVER));
+ log.entries(), 9, NetLog::TYPE_INIT_PROXY_RESOLVER_SET_PAC_SCRIPT));
+ EXPECT_TRUE(LogContainsEndEvent(
+ log.entries(), 10, NetLog::TYPE_INIT_PROXY_RESOLVER));
}
// Fails at WPAD (downloading), and fails at custom PAC (downloading).
@@ -338,16 +348,16 @@
RuleBasedProxyScriptFetcher fetcher(&rules);
ProxyConfig config;
- config.auto_detect = true;
- config.pac_url = GURL("http://custom/proxy.pac");
+ config.set_auto_detect(true);
+ config.set_pac_url(GURL("http://custom/proxy.pac"));
rules.AddFailDownloadRule("http://wpad/wpad.dat");
rules.AddFailDownloadRule("http://custom/proxy.pac");
TestCompletionCallback callback;
- InitProxyResolver init(&resolver, &fetcher);
- EXPECT_EQ(kFailedDownloading, init.Init(config, &callback, NULL));
- EXPECT_EQ("", resolver.pac_bytes());
+ InitProxyResolver init(&resolver, &fetcher, NULL);
+ EXPECT_EQ(kFailedDownloading, init.Init(config, &callback));
+ EXPECT_EQ(NULL, resolver.script_data());
}
// Fails at WPAD (downloading), and fails at custom PAC (parsing).
@@ -357,16 +367,16 @@
RuleBasedProxyScriptFetcher fetcher(&rules);
ProxyConfig config;
- config.auto_detect = true;
- config.pac_url = GURL("http://custom/proxy.pac");
+ config.set_auto_detect(true);
+ config.set_pac_url(GURL("http://custom/proxy.pac"));
rules.AddFailDownloadRule("http://wpad/wpad.dat");
rules.AddFailParsingRule("http://custom/proxy.pac");
TestCompletionCallback callback;
- InitProxyResolver init(&resolver, &fetcher);
- EXPECT_EQ(kFailedParsing, init.Init(config, &callback, NULL));
- EXPECT_EQ("", resolver.pac_bytes());
+ InitProxyResolver init(&resolver, &fetcher, NULL);
+ EXPECT_EQ(kFailedParsing, init.Init(config, &callback));
+ EXPECT_EQ(NULL, resolver.script_data());
}
// Fails at WPAD (parsing), but succeeds in choosing the custom PAC.
@@ -378,16 +388,16 @@
RuleBasedProxyScriptFetcher fetcher(&rules);
ProxyConfig config;
- config.auto_detect = true;
- config.pac_url = GURL("http://custom/proxy.pac");
+ config.set_auto_detect(true);
+ config.set_pac_url(GURL("http://custom/proxy.pac"));
rules.AddFailParsingRule(""); // Autodetect.
Rules::Rule rule = rules.AddSuccessRule("http://custom/proxy.pac");
TestCompletionCallback callback;
- InitProxyResolver init(&resolver, &fetcher);
- EXPECT_EQ(OK, init.Init(config, &callback, NULL));
- EXPECT_EQ(rule.url, resolver.pac_url());
+ InitProxyResolver init(&resolver, &fetcher, NULL);
+ EXPECT_EQ(OK, init.Init(config, &callback));
+ EXPECT_EQ(rule.url, resolver.script_data()->url());
}
} // namespace
diff --git a/net/proxy/mock_proxy_resolver.h b/net/proxy/mock_proxy_resolver.h
index c284eb1..9babb66 100644
--- a/net/proxy/mock_proxy_resolver.h
+++ b/net/proxy/mock_proxy_resolver.h
@@ -59,19 +59,17 @@
class SetPacScriptRequest {
public:
- SetPacScriptRequest(MockAsyncProxyResolverBase* resolver,
- const GURL& pac_url,
- const std::string& pac_bytes,
- CompletionCallback* callback)
+ SetPacScriptRequest(
+ MockAsyncProxyResolverBase* resolver,
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ CompletionCallback* callback)
: resolver_(resolver),
- pac_url_(pac_url),
- pac_bytes_(pac_bytes),
+ script_data_(script_data),
callback_(callback),
origin_loop_(MessageLoop::current()) {
}
- const GURL& pac_url() const { return pac_url_; }
- const std::string& pac_bytes() const { return pac_bytes_; }
+ const ProxyResolverScriptData* script_data() const { return script_data_; }
void CompleteNow(int rv) {
CompletionCallback* callback = callback_;
@@ -84,8 +82,7 @@
private:
MockAsyncProxyResolverBase* resolver_;
- const GURL pac_url_;
- const std::string pac_bytes_;
+ const scoped_refptr<ProxyResolverScriptData> script_data_;
CompletionCallback* callback_;
MessageLoop* origin_loop_;
};
@@ -97,7 +94,7 @@
ProxyInfo* results,
CompletionCallback* callback,
RequestHandle* request_handle,
- LoadLog* /*load_log*/) {
+ const BoundNetLog& /*net_log*/) {
scoped_refptr<Request> request = new Request(this, url, results, callback);
pending_requests_.push_back(request);
@@ -114,12 +111,12 @@
RemovePendingRequest(request);
}
- virtual int SetPacScript(const GURL& pac_url,
- const std::string& pac_bytes,
- CompletionCallback* callback) {
+ virtual int SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ CompletionCallback* callback) {
DCHECK(!pending_set_pac_script_request_.get());
pending_set_pac_script_request_.reset(
- new SetPacScriptRequest(this, pac_url, pac_bytes, callback));
+ new SetPacScriptRequest(this, script_data, callback));
// Finished when user calls SetPacScriptRequest::CompleteNow().
return ERR_IO_PENDING;
}
diff --git a/net/proxy/multi_threaded_proxy_resolver.cc b/net/proxy/multi_threaded_proxy_resolver.cc
new file mode 100644
index 0000000..69e9ef7
--- /dev/null
+++ b/net/proxy/multi_threaded_proxy_resolver.cc
@@ -0,0 +1,575 @@
+// 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 "net/proxy/multi_threaded_proxy_resolver.h"
+
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "base/thread.h"
+#include "net/base/capturing_net_log.h"
+#include "net/base/net_errors.h"
+#include "net/proxy/proxy_info.h"
+
+// TODO(eroman): Have the MultiThreadedProxyResolver clear its PAC script
+// data when SetPacScript fails. That will reclaim memory when
+// testing bogus scripts.
+
+namespace net {
+
+namespace {
+
+class PurgeMemoryTask : public base::RefCountedThreadSafe<PurgeMemoryTask> {
+ public:
+ explicit PurgeMemoryTask(ProxyResolver* resolver) : resolver_(resolver) {}
+ void PurgeMemory() { resolver_->PurgeMemory(); }
+ private:
+ friend class base::RefCountedThreadSafe<PurgeMemoryTask>;
+ ~PurgeMemoryTask() {}
+ ProxyResolver* resolver_;
+};
+
+} // namespace
+
+// An "executor" is a job-runner for PAC requests. It encapsulates a worker
+// thread and a synchronous ProxyResolver (which will be operated on said
+// thread.)
+class MultiThreadedProxyResolver::Executor
+ : public base::RefCountedThreadSafe<MultiThreadedProxyResolver::Executor > {
+ public:
+ // |coordinator| must remain valid throughout our lifetime. It is used to
+ // signal when the executor is ready to receive work by calling
+ // |coordinator->OnExecutorReady()|.
+ // The constructor takes ownership of |resolver|.
+ // |thread_number| is an identifier used when naming the worker thread.
+ Executor(MultiThreadedProxyResolver* coordinator,
+ ProxyResolver* resolver,
+ int thread_number);
+
+ // Submit a job to this executor.
+ void StartJob(Job* job);
+
+ // Callback for when a job has completed running on the executor's thread.
+ void OnJobCompleted(Job* job);
+
+ // Cleanup the executor. Cancels all outstanding work, and frees the thread
+ // and resolver.
+ void Destroy();
+
+ void PurgeMemory();
+
+ // Returns the outstanding job, or NULL.
+ Job* outstanding_job() const { return outstanding_job_.get(); }
+
+ ProxyResolver* resolver() { return resolver_.get(); }
+
+ int thread_number() const { return thread_number_; }
+
+ private:
+ friend class base::RefCountedThreadSafe<Executor>;
+ ~Executor();
+
+ MultiThreadedProxyResolver* coordinator_;
+ const int thread_number_;
+
+ // The currently active job for this executor (either a SetPacScript or
+ // GetProxyForURL task).
+ scoped_refptr<Job> outstanding_job_;
+
+ // The synchronous resolver implementation.
+ scoped_ptr<ProxyResolver> resolver_;
+
+ // The thread where |resolver_| is run on.
+ // Note that declaration ordering is important here. |thread_| needs to be
+ // destroyed *before* |resolver_|, in case |resolver_| is currently
+ // executing on |thread_|.
+ scoped_ptr<base::Thread> thread_;
+};
+
+// MultiThreadedProxyResolver::Job ---------------------------------------------
+
+class MultiThreadedProxyResolver::Job
+ : public base::RefCountedThreadSafe<MultiThreadedProxyResolver::Job> {
+ public:
+ // Identifies the subclass of Job (only being used for debugging purposes).
+ enum Type {
+ TYPE_GET_PROXY_FOR_URL,
+ TYPE_SET_PAC_SCRIPT,
+ TYPE_SET_PAC_SCRIPT_INTERNAL,
+ };
+
+ Job(Type type, CompletionCallback* user_callback)
+ : type_(type),
+ user_callback_(user_callback),
+ executor_(NULL),
+ was_cancelled_(false) {
+ }
+
+ void set_executor(Executor* executor) {
+ executor_ = executor;
+ }
+
+ // The "executor" is the job runner that is scheduling this job. If
+ // this job has not been submitted to an executor yet, this will be
+ // NULL (and we know it hasn't started yet).
+ Executor* executor() {
+ return executor_;
+ }
+
+ // Mark the job as having been cancelled.
+ void Cancel() {
+ was_cancelled_ = true;
+ }
+
+ // Returns true if Cancel() has been called.
+ bool was_cancelled() const { return was_cancelled_; }
+
+ Type type() const { return type_; }
+
+ // Returns true if this job still has a user callback. Some jobs
+ // do not have a user callback, because they were helper jobs
+ // scheduled internally (for example TYPE_SET_PAC_SCRIPT_INTERNAL).
+ //
+ // Otherwise jobs that correspond with user-initiated work will
+ // have a non-NULL callback up until the callback is run.
+ bool has_user_callback() const { return user_callback_ != NULL; }
+
+ // This method is called when the job is inserted into a wait queue
+ // because no executors were ready to accept it.
+ virtual void WaitingForThread() {}
+
+ // This method is called just before the job is posted to the work thread.
+ virtual void FinishedWaitingForThread() {}
+
+ // This method is called on the worker thread to do the job's work. On
+ // completion, implementors are expected to call OnJobCompleted() on
+ // |origin_loop|.
+ virtual void Run(MessageLoop* origin_loop) = 0;
+
+ protected:
+ void OnJobCompleted() {
+ // |executor_| will be NULL if the executor has already been deleted.
+ if (executor_)
+ executor_->OnJobCompleted(this);
+ }
+
+ void RunUserCallback(int result) {
+ DCHECK(has_user_callback());
+ CompletionCallback* callback = user_callback_;
+ // Null the callback so has_user_callback() will now return false.
+ user_callback_ = NULL;
+ callback->Run(result);
+ }
+
+ friend class base::RefCountedThreadSafe<MultiThreadedProxyResolver::Job>;
+
+ virtual ~Job() {}
+
+ private:
+ const Type type_;
+ CompletionCallback* user_callback_;
+ Executor* executor_;
+ bool was_cancelled_;
+};
+
+// MultiThreadedProxyResolver::SetPacScriptJob ---------------------------------
+
+// Runs on the worker thread to call ProxyResolver::SetPacScript.
+class MultiThreadedProxyResolver::SetPacScriptJob
+ : public MultiThreadedProxyResolver::Job {
+ public:
+ SetPacScriptJob(const scoped_refptr<ProxyResolverScriptData>& script_data,
+ CompletionCallback* callback)
+ : Job(callback ? TYPE_SET_PAC_SCRIPT : TYPE_SET_PAC_SCRIPT_INTERNAL,
+ callback),
+ script_data_(script_data) {
+ }
+
+ // Runs on the worker thread.
+ virtual void Run(MessageLoop* origin_loop) {
+ ProxyResolver* resolver = executor()->resolver();
+ int rv = resolver->SetPacScript(script_data_, NULL);
+
+ DCHECK_NE(rv, ERR_IO_PENDING);
+ origin_loop->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &SetPacScriptJob::RequestComplete, rv));
+ }
+
+ private:
+ // Runs the completion callback on the origin thread.
+ void RequestComplete(int result_code) {
+ // The task may have been cancelled after it was started.
+ if (!was_cancelled() && has_user_callback()) {
+ RunUserCallback(result_code);
+ }
+ OnJobCompleted();
+ }
+
+ const scoped_refptr<ProxyResolverScriptData> script_data_;
+};
+
+// MultiThreadedProxyResolver::GetProxyForURLJob ------------------------------
+
+class MultiThreadedProxyResolver::GetProxyForURLJob
+ : public MultiThreadedProxyResolver::Job {
+ public:
+ // |url| -- the URL of the query.
+ // |results| -- the structure to fill with proxy resolve results.
+ GetProxyForURLJob(const GURL& url,
+ ProxyInfo* results,
+ CompletionCallback* callback,
+ const BoundNetLog& net_log)
+ : Job(TYPE_GET_PROXY_FOR_URL, callback),
+ results_(results),
+ net_log_(net_log),
+ url_(url),
+ was_waiting_for_thread_(false) {
+ DCHECK(callback);
+ }
+
+ BoundNetLog* net_log() { return &net_log_; }
+
+ virtual void WaitingForThread() {
+ was_waiting_for_thread_ = true;
+ net_log_.BeginEvent(
+ NetLog::TYPE_WAITING_FOR_PROXY_RESOLVER_THREAD, NULL);
+ }
+
+ virtual void FinishedWaitingForThread() {
+ DCHECK(executor());
+
+ if (was_waiting_for_thread_) {
+ net_log_.EndEvent(
+ NetLog::TYPE_WAITING_FOR_PROXY_RESOLVER_THREAD, NULL);
+ }
+
+ net_log_.AddEvent(
+ NetLog::TYPE_SUBMITTED_TO_RESOLVER_THREAD,
+ new NetLogIntegerParameter(
+ "thread_number", executor()->thread_number()));
+ }
+
+ // Runs on the worker thread.
+ virtual void Run(MessageLoop* origin_loop) {
+ const size_t kNetLogBound = 50u;
+ worker_log_.reset(new CapturingNetLog(kNetLogBound));
+ BoundNetLog bound_worker_log(NetLog::Source(), worker_log_.get());
+
+ ProxyResolver* resolver = executor()->resolver();
+ int rv = resolver->GetProxyForURL(
+ url_, &results_buf_, NULL, NULL, bound_worker_log);
+ DCHECK_NE(rv, ERR_IO_PENDING);
+
+ origin_loop->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &GetProxyForURLJob::QueryComplete, rv));
+ }
+
+ private:
+ // Runs the completion callback on the origin thread.
+ void QueryComplete(int result_code) {
+ // The Job may have been cancelled after it was started.
+ if (!was_cancelled()) {
+ // Merge the load log that was generated on the worker thread, into the
+ // main log.
+ CapturingBoundNetLog bound_worker_log(NetLog::Source(),
+ worker_log_.release());
+ bound_worker_log.AppendTo(net_log_);
+
+ if (result_code >= OK) { // Note: unit-tests use values > 0.
+ results_->Use(results_buf_);
+ }
+ RunUserCallback(result_code);
+ }
+ OnJobCompleted();
+ }
+
+ // Must only be used on the "origin" thread.
+ ProxyInfo* results_;
+ BoundNetLog net_log_;
+ const GURL url_;
+
+ // Usable from within DoQuery on the worker thread.
+ ProxyInfo results_buf_;
+
+ // Used to pass the captured events between DoQuery [worker thread] and
+ // QueryComplete [origin thread].
+ scoped_ptr<CapturingNetLog> worker_log_;
+
+ bool was_waiting_for_thread_;
+};
+
+// MultiThreadedProxyResolver::Executor ----------------------------------------
+
+MultiThreadedProxyResolver::Executor::Executor(
+ MultiThreadedProxyResolver* coordinator,
+ ProxyResolver* resolver,
+ int thread_number)
+ : coordinator_(coordinator),
+ thread_number_(thread_number),
+ resolver_(resolver) {
+ DCHECK(coordinator);
+ DCHECK(resolver);
+ // Start up the thread.
+ // Note that it is safe to pass a temporary C-String to Thread(), as it will
+ // make a copy.
+ std::string thread_name =
+ StringPrintf("PAC thread #%d", thread_number);
+ thread_.reset(new base::Thread(thread_name.c_str()));
+ thread_->Start();
+}
+
+void MultiThreadedProxyResolver::Executor::StartJob(Job* job) {
+ DCHECK(!outstanding_job_);
+ outstanding_job_ = job;
+
+ // Run the job. Once it has completed (regardless of whether it was
+ // cancelled), it will invoke OnJobCompleted() on this thread.
+ job->set_executor(this);
+ job->FinishedWaitingForThread();
+ thread_->message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(job, &Job::Run, MessageLoop::current()));
+}
+
+void MultiThreadedProxyResolver::Executor::OnJobCompleted(Job* job) {
+ DCHECK_EQ(job, outstanding_job_.get());
+ outstanding_job_ = NULL;
+ coordinator_->OnExecutorReady(this);
+}
+
+void MultiThreadedProxyResolver::Executor::Destroy() {
+ DCHECK(coordinator_);
+
+ // Give the resolver an opportunity to shutdown from THIS THREAD before
+ // joining on the resolver thread. This allows certain implementations
+ // to avoid deadlocks.
+ resolver_->Shutdown();
+
+ // Join the worker thread.
+ thread_.reset();
+
+ // Cancel any outstanding job.
+ if (outstanding_job_) {
+ outstanding_job_->Cancel();
+ // Orphan the job (since this executor may be deleted soon).
+ outstanding_job_->set_executor(NULL);
+ }
+
+ // It is now safe to free the ProxyResolver, since all the tasks that
+ // were using it on the resolver thread have completed.
+ resolver_.reset();
+
+ // Null some stuff as a precaution.
+ coordinator_ = NULL;
+ outstanding_job_ = NULL;
+}
+
+void MultiThreadedProxyResolver::Executor::PurgeMemory() {
+ scoped_refptr<PurgeMemoryTask> helper(new PurgeMemoryTask(resolver_.get()));
+ thread_->message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(helper.get(), &PurgeMemoryTask::PurgeMemory));
+}
+
+MultiThreadedProxyResolver::Executor::~Executor() {
+ // The important cleanup happens as part of Destroy(), which should always be
+ // called first.
+ DCHECK(!coordinator_) << "Destroy() was not called";
+ DCHECK(!thread_.get());
+ DCHECK(!resolver_.get());
+ DCHECK(!outstanding_job_);
+}
+
+// MultiThreadedProxyResolver --------------------------------------------------
+
+MultiThreadedProxyResolver::MultiThreadedProxyResolver(
+ ProxyResolverFactory* resolver_factory,
+ size_t max_num_threads)
+ : ProxyResolver(resolver_factory->resolvers_expect_pac_bytes()),
+ resolver_factory_(resolver_factory),
+ max_num_threads_(max_num_threads) {
+ DCHECK_GE(max_num_threads, 1u);
+}
+
+MultiThreadedProxyResolver::~MultiThreadedProxyResolver() {
+ // We will cancel all outstanding requests.
+ pending_jobs_.clear();
+ ReleaseAllExecutors();
+}
+
+int MultiThreadedProxyResolver::GetProxyForURL(const GURL& url,
+ ProxyInfo* results,
+ CompletionCallback* callback,
+ RequestHandle* request,
+ const BoundNetLog& net_log) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(callback);
+ DCHECK(current_script_data_.get())
+ << "Resolver is un-initialized. Must call SetPacScript() first!";
+
+ scoped_refptr<GetProxyForURLJob> job =
+ new GetProxyForURLJob(url, results, callback, net_log);
+
+ // Completion will be notified through |callback|, unless the caller cancels
+ // the request using |request|.
+ if (request)
+ *request = reinterpret_cast<RequestHandle>(job.get());
+
+ // If there is an executor that is ready to run this request, submit it!
+ Executor* executor = FindIdleExecutor();
+ if (executor) {
+ DCHECK_EQ(0u, pending_jobs_.size());
+ executor->StartJob(job);
+ return ERR_IO_PENDING;
+ }
+
+ // Otherwise queue this request. (We will schedule it to a thread once one
+ // becomes available).
+ job->WaitingForThread();
+ pending_jobs_.push_back(job);
+
+ // If we haven't already reached the thread limit, provision a new thread to
+ // drain the requests more quickly.
+ if (executors_.size() < max_num_threads_) {
+ executor = AddNewExecutor();
+ executor->StartJob(
+ new SetPacScriptJob(current_script_data_, NULL));
+ }
+
+ return ERR_IO_PENDING;
+}
+
+void MultiThreadedProxyResolver::CancelRequest(RequestHandle req) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(req);
+
+ Job* job = reinterpret_cast<Job*>(req);
+ DCHECK_EQ(Job::TYPE_GET_PROXY_FOR_URL, job->type());
+
+ if (job->executor()) {
+ // If the job was already submitted to the executor, just mark it
+ // as cancelled so the user callback isn't run on completion.
+ job->Cancel();
+ } else {
+ // Otherwise the job is just sitting in a queue.
+ PendingJobsQueue::iterator it =
+ std::find(pending_jobs_.begin(), pending_jobs_.end(), job);
+ DCHECK(it != pending_jobs_.end());
+ pending_jobs_.erase(it);
+ }
+}
+
+void MultiThreadedProxyResolver::CancelSetPacScript() {
+ DCHECK(CalledOnValidThread());
+ DCHECK_EQ(0u, pending_jobs_.size());
+ DCHECK_EQ(1u, executors_.size());
+ DCHECK_EQ(Job::TYPE_SET_PAC_SCRIPT,
+ executors_[0]->outstanding_job()->type());
+
+ // Defensively clear some data which shouldn't be getting used
+ // anymore.
+ current_script_data_ = NULL;
+
+ ReleaseAllExecutors();
+}
+
+void MultiThreadedProxyResolver::PurgeMemory() {
+ DCHECK(CalledOnValidThread());
+ for (ExecutorList::iterator it = executors_.begin();
+ it != executors_.end(); ++it) {
+ Executor* executor = *it;
+ executor->PurgeMemory();
+ }
+}
+
+int MultiThreadedProxyResolver::SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ CompletionCallback* callback) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(callback);
+
+ // Save the script details, so we can provision new executors later.
+ current_script_data_ = script_data;
+
+ // The user should not have any outstanding requests when they call
+ // SetPacScript().
+ CheckNoOutstandingUserRequests();
+
+ // Destroy all of the current threads and their proxy resolvers.
+ ReleaseAllExecutors();
+
+ // Provision a new executor, and run the SetPacScript request. On completion
+ // notification will be sent through |callback|.
+ Executor* executor = AddNewExecutor();
+ executor->StartJob(new SetPacScriptJob(script_data, callback));
+ return ERR_IO_PENDING;
+}
+
+void MultiThreadedProxyResolver::CheckNoOutstandingUserRequests() const {
+ DCHECK(CalledOnValidThread());
+ CHECK_EQ(0u, pending_jobs_.size());
+
+ for (ExecutorList::const_iterator it = executors_.begin();
+ it != executors_.end(); ++it) {
+ const Executor* executor = *it;
+ Job* job = executor->outstanding_job();
+ // The "has_user_callback()" is to exclude jobs for which the callback
+ // has already been invoked, or was not user-initiated (as in the case of
+ // lazy thread provisions). User-initiated jobs may !has_user_callback()
+ // when the callback has already been run. (Since we only clear the
+ // outstanding job AFTER the callback has been invoked, it is possible
+ // for a new request to be started from within the callback).
+ CHECK(!job || job->was_cancelled() || !job->has_user_callback());
+ }
+}
+
+void MultiThreadedProxyResolver::ReleaseAllExecutors() {
+ DCHECK(CalledOnValidThread());
+ for (ExecutorList::iterator it = executors_.begin();
+ it != executors_.end(); ++it) {
+ Executor* executor = *it;
+ executor->Destroy();
+ }
+ executors_.clear();
+}
+
+MultiThreadedProxyResolver::Executor*
+MultiThreadedProxyResolver::FindIdleExecutor() {
+ DCHECK(CalledOnValidThread());
+ for (ExecutorList::iterator it = executors_.begin();
+ it != executors_.end(); ++it) {
+ Executor* executor = *it;
+ if (!executor->outstanding_job())
+ return executor;
+ }
+ return NULL;
+}
+
+MultiThreadedProxyResolver::Executor*
+MultiThreadedProxyResolver::AddNewExecutor() {
+ DCHECK(CalledOnValidThread());
+ DCHECK_LT(executors_.size(), max_num_threads_);
+ // The "thread number" is used to give the thread a unique name.
+ int thread_number = executors_.size();
+ ProxyResolver* resolver = resolver_factory_->CreateProxyResolver();
+ Executor* executor = new Executor(
+ this, resolver, thread_number);
+ executors_.push_back(executor);
+ return executor;
+}
+
+void MultiThreadedProxyResolver::OnExecutorReady(Executor* executor) {
+ DCHECK(CalledOnValidThread());
+ if (pending_jobs_.empty())
+ return;
+
+ // Get the next job to process (FIFO). Transfer it from the pending queue
+ // to the executor.
+ scoped_refptr<Job> job = pending_jobs_.front();
+ pending_jobs_.pop_front();
+ executor->StartJob(job);
+}
+
+} // namespace net
diff --git a/net/proxy/multi_threaded_proxy_resolver.h b/net/proxy/multi_threaded_proxy_resolver.h
new file mode 100644
index 0000000..a69699a
--- /dev/null
+++ b/net/proxy/multi_threaded_proxy_resolver.h
@@ -0,0 +1,140 @@
+// 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.
+
+#ifndef NET_PROXY_MULTI_THREADED_PROXY_RESOLVER_H_
+#define NET_PROXY_MULTI_THREADED_PROXY_RESOLVER_H_
+
+#include <deque>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/non_thread_safe.h"
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "net/proxy/proxy_resolver.h"
+
+namespace base {
+class Thread;
+} // namespace base
+
+namespace net {
+
+// ProxyResolverFactory is an interface for creating ProxyResolver instances.
+class ProxyResolverFactory {
+ public:
+ explicit ProxyResolverFactory(bool resolvers_expect_pac_bytes)
+ : resolvers_expect_pac_bytes_(resolvers_expect_pac_bytes) {}
+
+ virtual ~ProxyResolverFactory() {}
+
+ // Creates a new ProxyResolver. The caller is responsible for freeing this
+ // object.
+ virtual ProxyResolver* CreateProxyResolver() = 0;
+
+ bool resolvers_expect_pac_bytes() const {
+ return resolvers_expect_pac_bytes_;
+ }
+
+ private:
+ bool resolvers_expect_pac_bytes_;
+ DISALLOW_COPY_AND_ASSIGN(ProxyResolverFactory);
+};
+
+// MultiThreadedProxyResolver is a ProxyResolver implementation that runs
+// synchronous ProxyResolver implementations on worker threads.
+//
+// Threads are created lazily on demand, up to a maximum total. The advantage
+// of having a pool of threads, is faster performance. In particular, being
+// able to keep servicing PAC requests even if one blocks its execution.
+//
+// During initialization (SetPacScript), a single thread is spun up to test
+// the script. If this succeeds, we cache the input script, and will re-use
+// this to lazily provision any new threads as needed.
+//
+// For each new thread that we spawn, a corresponding new ProxyResolver is
+// created using ProxyResolverFactory.
+//
+// Because we are creating multiple ProxyResolver instances, this means we
+// are duplicating script contexts for what is ordinarily seen as being a
+// single script. This can affect compatibility on some classes of PAC
+// script:
+//
+// (a) Scripts whose initialization has external dependencies on network or
+// time may end up successfully initializing on some threads, but not
+// others. So depending on what thread services the request, the result
+// may jump between several possibilities.
+//
+// (b) Scripts whose FindProxyForURL() depends on side-effects may now
+// work differently. For example, a PAC script which was incrementing
+// a global counter and using that to make a decision. In the
+// multi-threaded model, each thread may have a different value for this
+// counter, so it won't globally be seen as monotonically increasing!
+class MultiThreadedProxyResolver : public ProxyResolver, public NonThreadSafe {
+ public:
+ // Creates an asynchronous ProxyResolver that runs requests on up to
+ // |max_num_threads|.
+ //
+ // For each thread that is created, an accompanying synchronous ProxyResolver
+ // will be provisioned using |resolver_factory|. All methods on these
+ // ProxyResolvers will be called on the one thread, with the exception of
+ // ProxyResolver::Shutdown() which will be called from the origin thread
+ // prior to destruction.
+ //
+ // The constructor takes ownership of |resolver_factory|.
+ MultiThreadedProxyResolver(ProxyResolverFactory* resolver_factory,
+ size_t max_num_threads);
+
+ virtual ~MultiThreadedProxyResolver();
+
+ // ProxyResolver implementation:
+ virtual int GetProxyForURL(const GURL& url,
+ ProxyInfo* results,
+ CompletionCallback* callback,
+ RequestHandle* request,
+ const BoundNetLog& net_log);
+ virtual void CancelRequest(RequestHandle request);
+ virtual void CancelSetPacScript();
+ virtual void PurgeMemory();
+ virtual int SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ CompletionCallback* callback);
+
+ private:
+ class Executor;
+ class Job;
+ class SetPacScriptJob;
+ class GetProxyForURLJob;
+ // FIFO queue of pending jobs waiting to be started.
+ // TODO(eroman): Make this priority queue.
+ typedef std::deque<scoped_refptr<Job> > PendingJobsQueue;
+ typedef std::vector<scoped_refptr<Executor> > ExecutorList;
+
+ // Asserts that there are no outstanding user-initiated jobs on any of the
+ // worker threads.
+ void CheckNoOutstandingUserRequests() const;
+
+ // Stops and deletes all of the worker threads.
+ void ReleaseAllExecutors();
+
+ // Returns an idle worker thread which is ready to receive GetProxyForURL()
+ // requests. If all threads are occupied, returns NULL.
+ Executor* FindIdleExecutor();
+
+ // Creates a new worker thread, and appends it to |executors_|.
+ Executor* AddNewExecutor();
+
+ // Starts the next job from |pending_jobs_| if possible.
+ void OnExecutorReady(Executor* executor);
+
+ const scoped_ptr<ProxyResolverFactory> resolver_factory_;
+ const size_t max_num_threads_;
+ PendingJobsQueue pending_jobs_;
+ ExecutorList executors_;
+ scoped_refptr<ProxyResolverScriptData> current_script_data_;
+};
+
+} // namespace net
+
+#endif // NET_PROXY_MULTI_THREADED_PROXY_RESOLVER_H_
diff --git a/net/proxy/multi_threaded_proxy_resolver_unittest.cc b/net/proxy/multi_threaded_proxy_resolver_unittest.cc
new file mode 100644
index 0000000..1a76d21
--- /dev/null
+++ b/net/proxy/multi_threaded_proxy_resolver_unittest.cc
@@ -0,0 +1,751 @@
+// Copyright (c) 2009 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 "net/proxy/multi_threaded_proxy_resolver.h"
+
+#include "base/stl_util-inl.h"
+#include "base/string_util.h"
+#include "base/waitable_event.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/proxy/proxy_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+// A synchronous mock ProxyResolver implementation, which can be used in
+// conjunction with MultiThreadedProxyResolver.
+// - returns a single-item proxy list with the query's host.
+class MockProxyResolver : public ProxyResolver {
+ public:
+ MockProxyResolver()
+ : ProxyResolver(true /*expects_pac_bytes*/),
+ wrong_loop_(MessageLoop::current()),
+ request_count_(0),
+ purge_count_(0),
+ resolve_latency_ms_(0) {}
+
+ // ProxyResolver implementation:
+ virtual int GetProxyForURL(const GURL& query_url,
+ ProxyInfo* results,
+ CompletionCallback* callback,
+ RequestHandle* request,
+ const BoundNetLog& net_log) {
+ if (resolve_latency_ms_)
+ PlatformThread::Sleep(resolve_latency_ms_);
+
+ CheckIsOnWorkerThread();
+
+ EXPECT_TRUE(callback == NULL);
+ EXPECT_TRUE(request == NULL);
+
+ // Write something into |net_log| (doesn't really have any meaning.)
+ net_log.BeginEvent(NetLog::TYPE_PAC_JAVASCRIPT_DNS_RESOLVE, NULL);
+
+ results->UseNamedProxy(query_url.host());
+
+ // Return a success code which represents the request's order.
+ return request_count_++;
+ }
+
+ virtual void CancelRequest(RequestHandle request) {
+ NOTREACHED();
+ }
+
+ virtual int SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ CompletionCallback* callback) {
+ CheckIsOnWorkerThread();
+ last_script_data_ = script_data;
+ return OK;
+ }
+
+ virtual void PurgeMemory() {
+ CheckIsOnWorkerThread();
+ ++purge_count_;
+ }
+
+ int purge_count() const { return purge_count_; }
+ int request_count() const { return request_count_; }
+
+ const ProxyResolverScriptData* last_script_data() const {
+ return last_script_data_;
+ }
+
+ void SetResolveLatency(int latency_ms) {
+ resolve_latency_ms_ = latency_ms;
+ }
+
+ private:
+ void CheckIsOnWorkerThread() {
+ // We should be running on the worker thread -- while we don't know the
+ // message loop of MultiThreadedProxyResolver's worker thread, we do
+ // know that it is going to be distinct from the loop running the
+ // test, so at least make sure it isn't the main loop.
+ EXPECT_NE(MessageLoop::current(), wrong_loop_);
+ }
+
+ MessageLoop* wrong_loop_;
+ int request_count_;
+ int purge_count_;
+ scoped_refptr<ProxyResolverScriptData> last_script_data_;
+ int resolve_latency_ms_;
+};
+
+
+// A mock synchronous ProxyResolver which can be set to block upon reaching
+// GetProxyForURL().
+// TODO(eroman): WaitUntilBlocked() *must* be called before calling Unblock(),
+// otherwise there will be a race on |should_block_| since it is
+// read without any synchronization.
+class BlockableProxyResolver : public MockProxyResolver {
+ public:
+ BlockableProxyResolver()
+ : should_block_(false),
+ unblocked_(true, true),
+ blocked_(true, false) {
+ }
+
+ void Block() {
+ should_block_ = true;
+ unblocked_.Reset();
+ }
+
+ void Unblock() {
+ should_block_ = false;
+ blocked_.Reset();
+ unblocked_.Signal();
+ }
+
+ void WaitUntilBlocked() {
+ blocked_.Wait();
+ }
+
+ virtual int GetProxyForURL(const GURL& query_url,
+ ProxyInfo* results,
+ CompletionCallback* callback,
+ RequestHandle* request,
+ const BoundNetLog& net_log) {
+ if (should_block_) {
+ blocked_.Signal();
+ unblocked_.Wait();
+ }
+
+ return MockProxyResolver::GetProxyForURL(
+ query_url, results, callback, request, net_log);
+ }
+
+ private:
+ bool should_block_;
+ base::WaitableEvent unblocked_;
+ base::WaitableEvent blocked_;
+};
+
+// ForwardingProxyResolver forwards all requests to |impl|.
+class ForwardingProxyResolver : public ProxyResolver {
+ public:
+ explicit ForwardingProxyResolver(ProxyResolver* impl)
+ : ProxyResolver(impl->expects_pac_bytes()),
+ impl_(impl) {}
+
+ virtual int GetProxyForURL(const GURL& query_url,
+ ProxyInfo* results,
+ CompletionCallback* callback,
+ RequestHandle* request,
+ const BoundNetLog& net_log) {
+ return impl_->GetProxyForURL(
+ query_url, results, callback, request, net_log);
+ }
+
+ virtual void CancelRequest(RequestHandle request) {
+ impl_->CancelRequest(request);
+ }
+
+ virtual int SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ CompletionCallback* callback) {
+ return impl_->SetPacScript(script_data, callback);
+ }
+
+ virtual void PurgeMemory() {
+ impl_->PurgeMemory();
+ }
+
+ private:
+ ProxyResolver* impl_;
+};
+
+// This factory returns ProxyResolvers that forward all requests to
+// |resolver|.
+class ForwardingProxyResolverFactory : public ProxyResolverFactory {
+ public:
+ explicit ForwardingProxyResolverFactory(ProxyResolver* resolver)
+ : ProxyResolverFactory(resolver->expects_pac_bytes()),
+ resolver_(resolver) {}
+
+ virtual ProxyResolver* CreateProxyResolver() {
+ return new ForwardingProxyResolver(resolver_);
+ }
+
+ private:
+ ProxyResolver* resolver_;
+};
+
+// This factory returns new instances of BlockableProxyResolver.
+class BlockableProxyResolverFactory : public ProxyResolverFactory {
+ public:
+ BlockableProxyResolverFactory() : ProxyResolverFactory(true) {}
+
+ ~BlockableProxyResolverFactory() {
+ STLDeleteElements(&resolvers_);
+ }
+
+ virtual ProxyResolver* CreateProxyResolver() {
+ BlockableProxyResolver* resolver = new BlockableProxyResolver;
+ resolvers_.push_back(resolver);
+ return new ForwardingProxyResolver(resolver);
+ }
+
+ std::vector<BlockableProxyResolver*> resolvers() {
+ return resolvers_;
+ }
+
+ private:
+ std::vector<BlockableProxyResolver*> resolvers_;
+};
+
+TEST(MultiThreadedProxyResolverTest, SingleThread_Basic) {
+ const size_t kNumThreads = 1u;
+ scoped_ptr<MockProxyResolver> mock(new MockProxyResolver);
+ MultiThreadedProxyResolver resolver(
+ new ForwardingProxyResolverFactory(mock.get()), kNumThreads);
+
+ int rv;
+
+ EXPECT_TRUE(resolver.expects_pac_bytes());
+
+ // Call SetPacScriptByData() -- verify that it reaches the synchronous
+ // resolver.
+ TestCompletionCallback set_script_callback;
+ rv = resolver.SetPacScript(
+ ProxyResolverScriptData::FromUTF8("pac script bytes"),
+ &set_script_callback);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, set_script_callback.WaitForResult());
+ EXPECT_EQ(ASCIIToUTF16("pac script bytes"),
+ mock->last_script_data()->utf16());
+
+ // Start request 0.
+ TestCompletionCallback callback0;
+ CapturingBoundNetLog log0(CapturingNetLog::kUnbounded);
+ ProxyInfo results0;
+ rv = resolver.GetProxyForURL(
+ GURL("http://request0"), &results0, &callback0, NULL, log0.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Wait for request 0 to finish.
+ rv = callback0.WaitForResult();
+ EXPECT_EQ(0, rv);
+ EXPECT_EQ("PROXY request0:80", results0.ToPacString());
+
+ // The mock proxy resolver should have written 1 log entry. And
+ // on completion, this should have been copied into |log0|.
+ // We also have 1 log entry that was emitted by the
+ // MultiThreadedProxyResolver.
+ ASSERT_EQ(2u, log0.entries().size());
+ EXPECT_EQ(NetLog::TYPE_SUBMITTED_TO_RESOLVER_THREAD,
+ log0.entries()[0].type);
+
+ // Start 3 more requests (request1 to request3).
+
+ TestCompletionCallback callback1;
+ ProxyInfo results1;
+ rv = resolver.GetProxyForURL(
+ GURL("http://request1"), &results1, &callback1, NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ TestCompletionCallback callback2;
+ ProxyInfo results2;
+ rv = resolver.GetProxyForURL(
+ GURL("http://request2"), &results2, &callback2, NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ TestCompletionCallback callback3;
+ ProxyInfo results3;
+ rv = resolver.GetProxyForURL(
+ GURL("http://request3"), &results3, &callback3, NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Wait for the requests to finish (they must finish in the order they were
+ // started, which is what we check for from their magic return value)
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(1, rv);
+ EXPECT_EQ("PROXY request1:80", results1.ToPacString());
+
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(2, rv);
+ EXPECT_EQ("PROXY request2:80", results2.ToPacString());
+
+ rv = callback3.WaitForResult();
+ EXPECT_EQ(3, rv);
+ EXPECT_EQ("PROXY request3:80", results3.ToPacString());
+
+ // Ensure that PurgeMemory() reaches the wrapped resolver and happens on the
+ // right thread.
+ EXPECT_EQ(0, mock->purge_count());
+ resolver.PurgeMemory();
+ // There is no way to get a callback directly when PurgeMemory() completes, so
+ // we queue up a dummy request after the PurgeMemory() call and wait until it
+ // finishes to ensure PurgeMemory() has had a chance to run.
+ TestCompletionCallback dummy_callback;
+ rv = resolver.SetPacScript(ProxyResolverScriptData::FromUTF8("dummy"),
+ &dummy_callback);
+ EXPECT_EQ(OK, dummy_callback.WaitForResult());
+ EXPECT_EQ(1, mock->purge_count());
+}
+
+// Tests that the NetLog is updated to include the time the request was waiting
+// to be scheduled to a thread.
+TEST(MultiThreadedProxyResolverTest,
+ SingleThread_UpdatesNetLogWithThreadWait) {
+ const size_t kNumThreads = 1u;
+ scoped_ptr<BlockableProxyResolver> mock(new BlockableProxyResolver);
+ MultiThreadedProxyResolver resolver(
+ new ForwardingProxyResolverFactory(mock.get()), kNumThreads);
+
+ int rv;
+
+ // Initialize the resolver.
+ TestCompletionCallback init_callback;
+ rv = resolver.SetPacScript(ProxyResolverScriptData::FromUTF8("foo"),
+ &init_callback);
+ EXPECT_EQ(OK, init_callback.WaitForResult());
+
+ // Block the proxy resolver, so no request can complete.
+ mock->Block();
+
+ // Start request 0.
+ ProxyResolver::RequestHandle request0;
+ TestCompletionCallback callback0;
+ ProxyInfo results0;
+ CapturingBoundNetLog log0(CapturingNetLog::kUnbounded);
+ rv = resolver.GetProxyForURL(
+ GURL("http://request0"), &results0, &callback0, &request0, log0.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Start 2 more requests (request1 and request2).
+
+ TestCompletionCallback callback1;
+ ProxyInfo results1;
+ CapturingBoundNetLog log1(CapturingNetLog::kUnbounded);
+ rv = resolver.GetProxyForURL(
+ GURL("http://request1"), &results1, &callback1, NULL, log1.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ ProxyResolver::RequestHandle request2;
+ TestCompletionCallback callback2;
+ ProxyInfo results2;
+ CapturingBoundNetLog log2(CapturingNetLog::kUnbounded);
+ rv = resolver.GetProxyForURL(
+ GURL("http://request2"), &results2, &callback2, &request2, log2.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Unblock the worker thread so the requests can continue running.
+ mock->WaitUntilBlocked();
+ mock->Unblock();
+
+ // Check that request 0 completed as expected.
+ // The NetLog has 1 entry that came from the MultiThreadedProxyResolver, and
+ // 1 entry from the mock proxy resolver.
+ EXPECT_EQ(0, callback0.WaitForResult());
+ EXPECT_EQ("PROXY request0:80", results0.ToPacString());
+ ASSERT_EQ(2u, log0.entries().size());
+ EXPECT_EQ(NetLog::TYPE_SUBMITTED_TO_RESOLVER_THREAD,
+ log0.entries()[0].type);
+
+ // Check that request 1 completed as expected.
+ EXPECT_EQ(1, callback1.WaitForResult());
+ EXPECT_EQ("PROXY request1:80", results1.ToPacString());
+ ASSERT_EQ(4u, log1.entries().size());
+ EXPECT_TRUE(LogContainsBeginEvent(
+ log1.entries(), 0,
+ NetLog::TYPE_WAITING_FOR_PROXY_RESOLVER_THREAD));
+ EXPECT_TRUE(LogContainsEndEvent(
+ log1.entries(), 1,
+ NetLog::TYPE_WAITING_FOR_PROXY_RESOLVER_THREAD));
+
+ // Check that request 2 completed as expected.
+ EXPECT_EQ(2, callback2.WaitForResult());
+ EXPECT_EQ("PROXY request2:80", results2.ToPacString());
+ ASSERT_EQ(4u, log2.entries().size());
+ EXPECT_TRUE(LogContainsBeginEvent(
+ log2.entries(), 0,
+ NetLog::TYPE_WAITING_FOR_PROXY_RESOLVER_THREAD));
+ EXPECT_TRUE(LogContainsEndEvent(
+ log2.entries(), 1,
+ NetLog::TYPE_WAITING_FOR_PROXY_RESOLVER_THREAD));
+}
+
+// Cancel a request which is in progress, and then cancel a request which
+// is pending.
+TEST(MultiThreadedProxyResolverTest, SingleThread_CancelRequest) {
+ const size_t kNumThreads = 1u;
+ scoped_ptr<BlockableProxyResolver> mock(new BlockableProxyResolver);
+ MultiThreadedProxyResolver resolver(
+ new ForwardingProxyResolverFactory(mock.get()),
+ kNumThreads);
+
+ int rv;
+
+ // Initialize the resolver.
+ TestCompletionCallback init_callback;
+ rv = resolver.SetPacScript(ProxyResolverScriptData::FromUTF8("foo"),
+ &init_callback);
+ EXPECT_EQ(OK, init_callback.WaitForResult());
+
+ // Block the proxy resolver, so no request can complete.
+ mock->Block();
+
+ // Start request 0.
+ ProxyResolver::RequestHandle request0;
+ TestCompletionCallback callback0;
+ ProxyInfo results0;
+ rv = resolver.GetProxyForURL(
+ GURL("http://request0"), &results0, &callback0, &request0, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Wait until requests 0 reaches the worker thread.
+ mock->WaitUntilBlocked();
+
+ // Start 3 more requests (request1 : request3).
+
+ TestCompletionCallback callback1;
+ ProxyInfo results1;
+ rv = resolver.GetProxyForURL(
+ GURL("http://request1"), &results1, &callback1, NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ ProxyResolver::RequestHandle request2;
+ TestCompletionCallback callback2;
+ ProxyInfo results2;
+ rv = resolver.GetProxyForURL(
+ GURL("http://request2"), &results2, &callback2, &request2, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ TestCompletionCallback callback3;
+ ProxyInfo results3;
+ rv = resolver.GetProxyForURL(
+ GURL("http://request3"), &results3, &callback3, NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Cancel request0 (inprogress) and request2 (pending).
+ resolver.CancelRequest(request0);
+ resolver.CancelRequest(request2);
+
+ // Unblock the worker thread so the requests can continue running.
+ mock->Unblock();
+
+ // Wait for requests 1 and 3 to finish.
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(1, rv);
+ EXPECT_EQ("PROXY request1:80", results1.ToPacString());
+
+ rv = callback3.WaitForResult();
+ // Note that since request2 was cancelled before reaching the resolver,
+ // the request count is 2 and not 3 here.
+ EXPECT_EQ(2, rv);
+ EXPECT_EQ("PROXY request3:80", results3.ToPacString());
+
+ // Requests 0 and 2 which were cancelled, hence their completion callbacks
+ // were never summoned.
+ EXPECT_FALSE(callback0.have_result());
+ EXPECT_FALSE(callback2.have_result());
+}
+
+// Test that deleting MultiThreadedProxyResolver while requests are
+// outstanding cancels them (and doesn't leak anything).
+TEST(MultiThreadedProxyResolverTest, SingleThread_CancelRequestByDeleting) {
+ const size_t kNumThreads = 1u;
+ scoped_ptr<BlockableProxyResolver> mock(new BlockableProxyResolver);
+ scoped_ptr<MultiThreadedProxyResolver> resolver(
+ new MultiThreadedProxyResolver(
+ new ForwardingProxyResolverFactory(mock.get()), kNumThreads));
+
+ int rv;
+
+ // Initialize the resolver.
+ TestCompletionCallback init_callback;
+ rv = resolver->SetPacScript(ProxyResolverScriptData::FromUTF8("foo"),
+ &init_callback);
+ EXPECT_EQ(OK, init_callback.WaitForResult());
+
+ // Block the proxy resolver, so no request can complete.
+ mock->Block();
+
+ // Start 3 requests.
+
+ TestCompletionCallback callback0;
+ ProxyInfo results0;
+ rv = resolver->GetProxyForURL(
+ GURL("http://request0"), &results0, &callback0, NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ TestCompletionCallback callback1;
+ ProxyInfo results1;
+ rv = resolver->GetProxyForURL(
+ GURL("http://request1"), &results1, &callback1, NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ TestCompletionCallback callback2;
+ ProxyInfo results2;
+ rv = resolver->GetProxyForURL(
+ GURL("http://request2"), &results2, &callback2, NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Wait until request 0 reaches the worker thread.
+ mock->WaitUntilBlocked();
+
+ // Add some latency, to improve the chance that when
+ // MultiThreadedProxyResolver is deleted below we are still running inside
+ // of the worker thread. The test will pass regardless, so this race doesn't
+ // cause flakiness. However the destruction during execution is a more
+ // interesting case to test.
+ mock->SetResolveLatency(100);
+
+ // Unblock the worker thread and delete the underlying
+ // MultiThreadedProxyResolver immediately.
+ mock->Unblock();
+ resolver.reset();
+
+ // Give any posted tasks a chance to run (in case there is badness).
+ MessageLoop::current()->RunAllPending();
+
+ // Check that none of the outstanding requests were completed.
+ EXPECT_FALSE(callback0.have_result());
+ EXPECT_FALSE(callback1.have_result());
+ EXPECT_FALSE(callback2.have_result());
+}
+
+// Cancel an outstanding call to SetPacScriptByData().
+TEST(MultiThreadedProxyResolverTest, SingleThread_CancelSetPacScript) {
+ const size_t kNumThreads = 1u;
+ scoped_ptr<BlockableProxyResolver> mock(new BlockableProxyResolver);
+ MultiThreadedProxyResolver resolver(
+ new ForwardingProxyResolverFactory(mock.get()), kNumThreads);
+
+ int rv;
+
+ TestCompletionCallback set_pac_script_callback;
+ rv = resolver.SetPacScript(ProxyResolverScriptData::FromUTF8("data"),
+ &set_pac_script_callback);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Cancel the SetPacScriptByData request.
+ resolver.CancelSetPacScript();
+
+ // Start another SetPacScript request
+ TestCompletionCallback set_pac_script_callback2;
+ rv = resolver.SetPacScript(ProxyResolverScriptData::FromUTF8("data2"),
+ &set_pac_script_callback2);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Wait for the initialization to complete.
+
+ rv = set_pac_script_callback2.WaitForResult();
+ EXPECT_EQ(0, rv);
+ EXPECT_EQ(ASCIIToUTF16("data2"), mock->last_script_data()->utf16());
+
+ // The first SetPacScript callback should never have been completed.
+ EXPECT_FALSE(set_pac_script_callback.have_result());
+}
+
+// Tests setting the PAC script once, lazily creating new threads, and
+// cancelling requests.
+TEST(MultiThreadedProxyResolverTest, ThreeThreads_Basic) {
+ const size_t kNumThreads = 3u;
+ BlockableProxyResolverFactory* factory = new BlockableProxyResolverFactory;
+ MultiThreadedProxyResolver resolver(factory, kNumThreads);
+
+ int rv;
+
+ EXPECT_TRUE(resolver.expects_pac_bytes());
+
+ // Call SetPacScriptByData() -- verify that it reaches the synchronous
+ // resolver.
+ TestCompletionCallback set_script_callback;
+ rv = resolver.SetPacScript(
+ ProxyResolverScriptData::FromUTF8("pac script bytes"),
+ &set_script_callback);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, set_script_callback.WaitForResult());
+ // One thread has been provisioned (i.e. one ProxyResolver was created).
+ ASSERT_EQ(1u, factory->resolvers().size());
+ EXPECT_EQ(ASCIIToUTF16("pac script bytes"),
+ factory->resolvers()[0]->last_script_data()->utf16());
+
+ const int kNumRequests = 9;
+ TestCompletionCallback callback[kNumRequests];
+ ProxyInfo results[kNumRequests];
+ ProxyResolver::RequestHandle request[kNumRequests];
+
+ // Start request 0 -- this should run on thread 0 as there is nothing else
+ // going on right now.
+ rv = resolver.GetProxyForURL(
+ GURL("http://request0"), &results[0], &callback[0], &request[0],
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Wait for request 0 to finish.
+ rv = callback[0].WaitForResult();
+ EXPECT_EQ(0, rv);
+ EXPECT_EQ("PROXY request0:80", results[0].ToPacString());
+ ASSERT_EQ(1u, factory->resolvers().size());
+ EXPECT_EQ(1, factory->resolvers()[0]->request_count());
+
+ MessageLoop::current()->RunAllPending();
+
+ // We now start 8 requests in parallel -- this will cause the maximum of
+ // three threads to be provisioned (an additional two from what we already
+ // have).
+
+ for (int i = 1; i < kNumRequests; ++i) {
+ rv = resolver.GetProxyForURL(
+ GURL(StringPrintf("http://request%d", i)), &results[i], &callback[i],
+ &request[i], BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ }
+
+ // We should now have a total of 3 threads, each with its own ProxyResolver
+ // that will get initialized with the same data. (We check this later since
+ // the assignment happens on the worker threads and may not have occurred
+ // yet.)
+ ASSERT_EQ(3u, factory->resolvers().size());
+
+ // Cancel 3 of the 8 oustanding requests.
+ resolver.CancelRequest(request[1]);
+ resolver.CancelRequest(request[3]);
+ resolver.CancelRequest(request[6]);
+
+ // Wait for the remaining requests to complete.
+ int kNonCancelledRequests[] = {2, 4, 5, 7, 8};
+ for (size_t i = 0; i < arraysize(kNonCancelledRequests); ++i) {
+ int request_index = kNonCancelledRequests[i];
+ EXPECT_GE(callback[request_index].WaitForResult(), 0);
+ }
+
+ // Check that the cancelled requests never invoked their callback.
+ EXPECT_FALSE(callback[1].have_result());
+ EXPECT_FALSE(callback[3].have_result());
+ EXPECT_FALSE(callback[6].have_result());
+
+ // We call SetPacScript again, solely to stop the current worker threads.
+ // (That way we can test to see the values observed by the synchronous
+ // resolvers in a non-racy manner).
+ TestCompletionCallback set_script_callback2;
+ rv = resolver.SetPacScript(ProxyResolverScriptData::FromUTF8("xyz"),
+ &set_script_callback2);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, set_script_callback2.WaitForResult());
+ ASSERT_EQ(4u, factory->resolvers().size());
+
+ for (int i = 0; i < 3; ++i) {
+ EXPECT_EQ(
+ ASCIIToUTF16("pac script bytes"),
+ factory->resolvers()[i]->last_script_data()->utf16()) << "i=" << i;
+ }
+
+ EXPECT_EQ(ASCIIToUTF16("xyz"),
+ factory->resolvers()[3]->last_script_data()->utf16());
+
+ // We don't know the exact ordering that requests ran on threads with,
+ // but we do know the total count that should have reached the threads.
+ // 8 total were submitted, and three were cancelled. Of the three that
+ // were cancelled, one of them (request 1) was cancelled after it had
+ // already been posted to the worker thread. So the resolvers will
+ // have seen 6 total (and 1 from the run prior).
+ ASSERT_EQ(4u, factory->resolvers().size());
+ int total_count = 0;
+ for (int i = 0; i < 3; ++i) {
+ total_count += factory->resolvers()[i]->request_count();
+ }
+ EXPECT_EQ(7, total_count);
+}
+
+// Tests using two threads. The first request hangs the first thread. Checks
+// that other requests are able to complete while this first request remains
+// stalled.
+TEST(MultiThreadedProxyResolverTest, OneThreadBlocked) {
+ const size_t kNumThreads = 2u;
+ BlockableProxyResolverFactory* factory = new BlockableProxyResolverFactory;
+ MultiThreadedProxyResolver resolver(factory, kNumThreads);
+
+ int rv;
+
+ EXPECT_TRUE(resolver.expects_pac_bytes());
+
+ // Initialize the resolver.
+ TestCompletionCallback set_script_callback;
+ rv = resolver.SetPacScript(
+ ProxyResolverScriptData::FromUTF8("pac script bytes"),
+ &set_script_callback);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, set_script_callback.WaitForResult());
+ // One thread has been provisioned (i.e. one ProxyResolver was created).
+ ASSERT_EQ(1u, factory->resolvers().size());
+ EXPECT_EQ(ASCIIToUTF16("pac script bytes"),
+ factory->resolvers()[0]->last_script_data()->utf16());
+
+ const int kNumRequests = 4;
+ TestCompletionCallback callback[kNumRequests];
+ ProxyInfo results[kNumRequests];
+ ProxyResolver::RequestHandle request[kNumRequests];
+
+ // Start a request that will block the first thread.
+
+ factory->resolvers()[0]->Block();
+
+ rv = resolver.GetProxyForURL(
+ GURL("http://request0"), &results[0], &callback[0], &request[0],
+ BoundNetLog());
+
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ factory->resolvers()[0]->WaitUntilBlocked();
+
+ // Start 3 more requests -- they should all be serviced by thread #2
+ // since thread #1 is blocked.
+
+ for (int i = 1; i < kNumRequests; ++i) {
+ rv = resolver.GetProxyForURL(
+ GURL(StringPrintf("http://request%d", i)),
+ &results[i], &callback[i], &request[i], BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ }
+
+ // Wait for the three requests to complete (they should complete in FIFO
+ // order).
+ for (int i = 1; i < kNumRequests; ++i) {
+ EXPECT_EQ(i - 1, callback[i].WaitForResult());
+ }
+
+ // Unblock the first thread.
+ factory->resolvers()[0]->Unblock();
+ EXPECT_EQ(0, callback[0].WaitForResult());
+
+ // All in all, the first thread should have seen just 1 request. And the
+ // second thread 3 requests.
+ ASSERT_EQ(2u, factory->resolvers().size());
+ EXPECT_EQ(1, factory->resolvers()[0]->request_count());
+ EXPECT_EQ(3, factory->resolvers()[1]->request_count());
+}
+
+} // namespace
+
+} // namespace net
diff --git a/net/proxy/proxy_bypass_rules.cc b/net/proxy/proxy_bypass_rules.cc
new file mode 100644
index 0000000..e273d67
--- /dev/null
+++ b/net/proxy/proxy_bypass_rules.cc
@@ -0,0 +1,282 @@
+// 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 "net/proxy/proxy_bypass_rules.h"
+
+#include "base/logging.h"
+#include "base/string_tokenizer.h"
+#include "base/string_util.h"
+#include "net/base/net_util.h"
+
+namespace net {
+
+namespace {
+
+class HostnamePatternRule : public ProxyBypassRules::Rule {
+ public:
+ HostnamePatternRule(const std::string& optional_scheme,
+ const std::string& hostname_pattern,
+ int optional_port)
+ : optional_scheme_(StringToLowerASCII(optional_scheme)),
+ hostname_pattern_(StringToLowerASCII(hostname_pattern)),
+ optional_port_(optional_port) {
+ }
+
+ virtual bool Matches(const GURL& url) const {
+ if (optional_port_ != -1 && url.EffectiveIntPort() != optional_port_)
+ return false; // Didn't match port expectation.
+
+ if (!optional_scheme_.empty() && url.scheme() != optional_scheme_)
+ return false; // Didn't match scheme expectation.
+
+ // Note it is necessary to lower-case the host, since GURL uses capital
+ // letters for percent-escaped characters.
+ return MatchPatternASCII(StringToLowerASCII(url.host()),
+ hostname_pattern_);
+ }
+
+ virtual std::string ToString() const {
+ std::string str;
+ if (!optional_scheme_.empty())
+ StringAppendF(&str, "%s://", optional_scheme_.c_str());
+ str += hostname_pattern_;
+ if (optional_port_ != -1)
+ StringAppendF(&str, ":%d", optional_port_);
+ return str;
+ }
+
+ private:
+ const std::string optional_scheme_;
+ const std::string hostname_pattern_;
+ const int optional_port_;
+};
+
+class BypassLocalRule : public ProxyBypassRules::Rule {
+ public:
+ virtual bool Matches(const GURL& url) const {
+ const std::string& host = url.host();
+ if (host == "127.0.0.1" || host == "[::1]")
+ return true;
+ return host.find('.') == std::string::npos;
+ }
+
+ virtual std::string ToString() const {
+ return "<local>";
+ }
+};
+
+// Rule for matching a URL that is an IP address, if that IP address falls
+// within a certain numeric range. For example, you could use this rule to
+// match all the IPs in the CIDR block 10.10.3.4/24.
+class BypassIPBlockRule : public ProxyBypassRules::Rule {
+ public:
+ // |ip_prefix| + |prefix_length| define the IP block to match.
+ BypassIPBlockRule(const std::string& description,
+ const std::string& optional_scheme,
+ const IPAddressNumber& ip_prefix,
+ size_t prefix_length_in_bits)
+ : description_(description),
+ optional_scheme_(optional_scheme),
+ ip_prefix_(ip_prefix),
+ prefix_length_in_bits_(prefix_length_in_bits) {
+ }
+
+ virtual bool Matches(const GURL& url) const {
+ if (!url.HostIsIPAddress())
+ return false;
+
+ if (!optional_scheme_.empty() && url.scheme() != optional_scheme_)
+ return false; // Didn't match scheme expectation.
+
+ // Parse the input IP literal to a number.
+ IPAddressNumber ip_number;
+ if (!ParseIPLiteralToNumber(url.HostNoBrackets(), &ip_number))
+ return false;
+
+ // Test if it has the expected prefix.
+ return IPNumberMatchesPrefix(ip_number, ip_prefix_,
+ prefix_length_in_bits_);
+ }
+
+ virtual std::string ToString() const {
+ return description_;
+ }
+
+ private:
+ const std::string description_;
+ const std::string optional_scheme_;
+ const IPAddressNumber ip_prefix_;
+ const size_t prefix_length_in_bits_;
+};
+
+// Returns true if the given string represents an IP address.
+bool IsIPAddress(const std::string& domain) {
+ // From GURL::HostIsIPAddress()
+ url_canon::RawCanonOutputT<char, 128> ignored_output;
+ url_canon::CanonHostInfo host_info;
+ url_parse::Component domain_comp(0, domain.size());
+ url_canon::CanonicalizeIPAddress(domain.c_str(), domain_comp,
+ &ignored_output, &host_info);
+ return host_info.IsIPAddress();
+}
+
+} // namespace
+
+ProxyBypassRules::~ProxyBypassRules() {
+}
+
+bool ProxyBypassRules::Matches(const GURL& url) const {
+ for (RuleList::const_iterator it = rules_.begin(); it != rules_.end(); ++it) {
+ if ((*it)->Matches(url))
+ return true;
+ }
+ return false;
+}
+
+bool ProxyBypassRules::Equals(const ProxyBypassRules& other) const {
+ if (rules_.size() != other.rules().size())
+ return false;
+
+ for (size_t i = 0; i < rules_.size(); ++i) {
+ if (!rules_[i]->Equals(*other.rules()[i]))
+ return false;
+ }
+ return true;
+}
+
+void ProxyBypassRules::ParseFromString(const std::string& raw) {
+ ParseFromStringInternal(raw, false);
+}
+
+void ProxyBypassRules::ParseFromStringUsingSuffixMatching(
+ const std::string& raw) {
+ ParseFromStringInternal(raw, true);
+}
+
+bool ProxyBypassRules::AddRuleForHostname(const std::string& optional_scheme,
+ const std::string& hostname_pattern,
+ int optional_port) {
+ if (hostname_pattern.empty())
+ return false;
+
+ rules_.push_back(new HostnamePatternRule(optional_scheme,
+ hostname_pattern,
+ optional_port));
+ return true;
+}
+
+void ProxyBypassRules::AddRuleToBypassLocal() {
+ rules_.push_back(new BypassLocalRule);
+}
+
+bool ProxyBypassRules::AddRuleFromString(const std::string& raw) {
+ return AddRuleFromStringInternalWithLogging(raw, false);
+}
+
+bool ProxyBypassRules::AddRuleFromStringUsingSuffixMatching(
+ const std::string& raw) {
+ return AddRuleFromStringInternalWithLogging(raw, true);
+}
+
+void ProxyBypassRules::Clear() {
+ rules_.clear();
+}
+
+void ProxyBypassRules::ParseFromStringInternal(
+ const std::string& raw,
+ bool use_hostname_suffix_matching) {
+ Clear();
+
+ StringTokenizer entries(raw, ",;");
+ while (entries.GetNext()) {
+ AddRuleFromStringInternalWithLogging(entries.token(),
+ use_hostname_suffix_matching);
+ }
+}
+
+bool ProxyBypassRules::AddRuleFromStringInternal(
+ const std::string& raw_untrimmed,
+ bool use_hostname_suffix_matching) {
+ std::string raw;
+ TrimWhitespaceASCII(raw_untrimmed, TRIM_ALL, &raw);
+
+ // This is the special syntax used by WinInet's bypass list -- we allow it
+ // on all platforms and interpret it the same way.
+ if (LowerCaseEqualsASCII(raw, "<local>")) {
+ AddRuleToBypassLocal();
+ return true;
+ }
+
+ // Extract any scheme-restriction.
+ std::string::size_type scheme_pos = raw.find("://");
+ std::string scheme;
+ if (scheme_pos != std::string::npos) {
+ scheme = raw.substr(0, scheme_pos);
+ raw = raw.substr(scheme_pos + 3);
+ if (scheme.empty())
+ return false;
+ }
+
+ if (raw.empty())
+ return false;
+
+ // If there is a forward slash in the input, it is probably a CIDR style
+ // mask.
+ if (raw.find('/') != std::string::npos) {
+ IPAddressNumber ip_prefix;
+ size_t prefix_length_in_bits;
+
+ if (!ParseCIDRBlock(raw, &ip_prefix, &prefix_length_in_bits))
+ return false;
+
+ rules_.push_back(
+ new BypassIPBlockRule(raw, scheme, ip_prefix, prefix_length_in_bits));
+
+ return true;
+ }
+
+ // Check if we have an <ip-address>[:port] input. We need to treat this
+ // separately since the IP literal may not be in a canonical form.
+ std::string host;
+ int port;
+ if (ParseHostAndPort(raw, &host, &port)) {
+ if (IsIPAddress(host)) {
+ // Canonicalize the IP literal before adding it as a string pattern.
+ GURL tmp_url("http://" + host);
+ return AddRuleForHostname(scheme, tmp_url.host(), port);
+ }
+ }
+
+ // Otherwise assume we have <hostname-pattern>[:port].
+ std::string::size_type pos_colon = raw.rfind(':');
+ host = raw;
+ port = -1;
+ if (pos_colon != std::string::npos) {
+ if (!StringToInt(raw.substr(pos_colon + 1), &port) ||
+ (port < 0 || port > 0xFFFF)) {
+ return false; // Port was invalid.
+ }
+ raw = raw.substr(0, pos_colon);
+ }
+
+ // Special-case hostnames that begin with a period.
+ // For example, we remap ".google.com" --> "*.google.com".
+ if (StartsWithASCII(raw, ".", false))
+ raw = "*" + raw;
+
+ // If suffix matching was asked for, make sure the pattern starts with a
+ // wildcard.
+ if (use_hostname_suffix_matching && !StartsWithASCII(raw, "*", false))
+ raw = "*" + raw;
+
+ return AddRuleForHostname(scheme, raw, port);
+}
+
+bool ProxyBypassRules::AddRuleFromStringInternalWithLogging(
+ const std::string& raw,
+ bool use_hostname_suffix_matching) {
+ return AddRuleFromStringInternal(raw, use_hostname_suffix_matching);
+}
+
+} // namespace net
diff --git a/net/proxy/proxy_bypass_rules.h b/net/proxy/proxy_bypass_rules.h
new file mode 100644
index 0000000..267fdc9
--- /dev/null
+++ b/net/proxy/proxy_bypass_rules.h
@@ -0,0 +1,170 @@
+// 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.
+
+#ifndef NET_PROXY_PROXY_BYPASS_RULES_H_
+#define NET_PROXY_PROXY_BYPASS_RULES_H_
+
+#include <string>
+#include <vector>
+
+#include "base/ref_counted.h"
+#include "googleurl/src/gurl.h"
+
+namespace net {
+
+// ProxyBypassRules describes the set of URLs that should bypass the proxy
+// settings, as a list of rules. A URL is said to match the bypass rules
+// if it matches any one of these rules.
+class ProxyBypassRules {
+ public:
+ // Interface for an individual proxy bypass rule.
+ class Rule : public base::RefCounted<Rule> {
+ public:
+ Rule() {}
+ virtual ~Rule() {}
+
+ // Returns true if |url| matches the rule.
+ virtual bool Matches(const GURL& url) const = 0;
+
+ // Returns a string representation of this rule. This is used both for
+ // visualizing the rules, and also to test equality of a rules list.
+ virtual std::string ToString() const = 0;
+
+ bool Equals(const Rule& rule) const {
+ return ToString() == rule.ToString();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Rule);
+ };
+
+ typedef std::vector<scoped_refptr<Rule> > RuleList;
+
+ // Note: This class supports copy constructor and assignment.
+
+ ~ProxyBypassRules();
+
+ // Returns the current list of rules.
+ const RuleList& rules() const { return rules_; }
+
+ // Returns true if |url| matches any of the proxy bypass rules.
+ bool Matches(const GURL& url) const;
+
+ // Returns true if |*this| is equal to |other|; in other words, whether they
+ // describe the same set of rules.
+ bool Equals(const ProxyBypassRules& other) const;
+
+ // Initializes the list of rules by parsing the string |raw|. |raw| is a
+ // comma separated list of rules. See AddRuleFromString() to see the list
+ // of supported formats.
+ void ParseFromString(const std::string& raw);
+
+ // This is a variant of ParseFromString, which interprets hostname patterns
+ // as suffix tests rather than hostname tests (so "google.com" would actually
+ // match "*google.com"). This is only currently used for the linux no_proxy
+ // evironment variable. It is less flexible, since with the suffix matching
+ // format you can't match an individual host.
+ // NOTE: Use ParseFromString() unless you truly need this behavior.
+ void ParseFromStringUsingSuffixMatching(const std::string& raw);
+
+ // Adds a rule that matches a URL when all of the following are true:
+ // (a) The URL's scheme matches |optional_scheme|, if
+ // |!optional_scheme.empty()|
+ // (b) The URL's hostname matches |hostname_pattern|.
+ // (c) The URL's (effective) port number matches |optional_port| if
+ // |optional_port != -1|
+ // Returns true if the rule was successfully added.
+ bool AddRuleForHostname(const std::string& optional_scheme,
+ const std::string& hostname_pattern,
+ int optional_port);
+
+ // Adds a rule that bypasses all "local" hostnames.
+ // This matches IE's interpretation of the
+ // "Bypass proxy server for local addresses" settings checkbox. Fully
+ // qualified domain names or IP addresses are considered non-local,
+ // regardless of what they map to (except for the loopback addresses).
+ void AddRuleToBypassLocal();
+
+ // Adds a rule given by the string |raw|. The format of |raw| can be any of
+ // the following:
+ //
+ // (1) [ URL_SCHEME "://" ] HOSTNAME_PATTERN [ ":" <port> ]
+ //
+ // Match all hostnames that match the pattern HOSTNAME_PATTERN.
+ //
+ // Examples:
+ // "foobar.com", "*foobar.com", "*.foobar.com", "*foobar.com:99",
+ // "https://x.*.y.com:99"
+ //
+ // (2) "." HOSTNAME_SUFFIX_PATTERN [ ":" PORT ]
+ //
+ // Match a particular domain suffix.
+ //
+ // Examples:
+ // ".google.com", ".com", "http://.google.com"
+ //
+ // (3) [ SCHEME "://" ] IP_LITERAL [ ":" PORT ]
+ //
+ // Match URLs which are IP address literals.
+ //
+ // Conceptually this is the similar to (1), but with special cases
+ // to handle IP literal canonicalization. For example matching
+ // on "[0:0:0::1]" would be the same as matching on "[::1]" since
+ // the IPv6 canonicalization is done internally.
+ //
+ // Examples:
+ // "127.0.1", "[0:0::1]", "[::1]", "http://[::1]:99"
+ //
+ // (4) IP_LITERAL "/" PREFIX_LENGHT_IN_BITS
+ //
+ // Match any URL that is to an IP literal that falls between the
+ // given range. IP range is specified using CIDR notation.
+ //
+ // Examples:
+ // "192.168.1.1/16", "fefe:13::abc/33".
+ //
+ // (5) "<local>"
+ //
+ // Match local addresses. The meaning of "<local>" is whether the
+ // host matches one of: "127.0.0.1", "::1", "localhost".
+ //
+ // See the unit-tests for more examples.
+ //
+ // Returns true if the rule was successfully added.
+ //
+ // TODO(eroman): support IPv6 literals without brackets.
+ //
+ bool AddRuleFromString(const std::string& raw);
+
+ // This is a variant of AddFromString, which interprets hostname patterns as
+ // suffix tests rather than hostname tests (so "google.com" would actually
+ // match "*google.com"). This is used for KDE which interprets every rule as
+ // a suffix test. It is less flexible, since with the suffix matching format
+ // you can't match an individual host.
+ //
+ // Returns true if the rule was successfully added.
+ //
+ // NOTE: Use AddRuleFromString() unless you truly need this behavior.
+ bool AddRuleFromStringUsingSuffixMatching(const std::string& raw);
+
+ // Removes all the rules.
+ void Clear();
+
+ private:
+ // The following are variants of ParseFromString() and AddRuleFromString(),
+ // which additionally prefix hostname patterns with a wildcard if
+ // |use_hostname_suffix_matching| was true.
+ void ParseFromStringInternal(const std::string& raw,
+ bool use_hostname_suffix_matching);
+ bool AddRuleFromStringInternal(const std::string& raw,
+ bool use_hostname_suffix_matching);
+ bool AddRuleFromStringInternalWithLogging(const std::string& raw,
+ bool use_hostname_suffix_matching);
+
+ RuleList rules_;
+};
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_BYPASS_RULES_H_
diff --git a/net/proxy/proxy_bypass_rules_unittest.cc b/net/proxy/proxy_bypass_rules_unittest.cc
new file mode 100644
index 0000000..208f9cd
--- /dev/null
+++ b/net/proxy/proxy_bypass_rules_unittest.cc
@@ -0,0 +1,314 @@
+// 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 "net/proxy/proxy_bypass_rules.h"
+
+#include "base/string_util.h"
+#include "net/proxy/proxy_config_service_common_unittest.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+TEST(ProxyBypassRulesTest, ParseAndMatchBasicHost) {
+ ProxyBypassRules rules;
+ rules.ParseFromString("wWw.gOogle.com");
+ ASSERT_EQ(1u, rules.rules().size());
+ EXPECT_EQ("www.google.com", rules.rules()[0]->ToString());
+
+ // All of these match; port, scheme, and non-hostname components don't
+ // matter.
+ EXPECT_TRUE(rules.Matches(GURL("http://www.google.com")));
+ EXPECT_TRUE(rules.Matches(GURL("ftp://www.google.com:99")));
+ EXPECT_TRUE(rules.Matches(GURL("https://www.google.com:81")));
+
+ // Must be a strict host match to work.
+ EXPECT_FALSE(rules.Matches(GURL("http://foo.www.google.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://xxx.google.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://google.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://www.google.com.baz.org")));
+}
+
+TEST(ProxyBypassRulesTest, ParseAndMatchBasicDomain) {
+ ProxyBypassRules rules;
+ rules.ParseFromString(".gOOgle.com");
+ ASSERT_EQ(1u, rules.rules().size());
+ // Note that we inferred this was an "ends with" test.
+ EXPECT_EQ("*.google.com", rules.rules()[0]->ToString());
+
+ // All of these match; port, scheme, and non-hostname components don't
+ // matter.
+ EXPECT_TRUE(rules.Matches(GURL("http://www.google.com")));
+ EXPECT_TRUE(rules.Matches(GURL("ftp://www.google.com:99")));
+ EXPECT_TRUE(rules.Matches(GURL("https://a.google.com:81")));
+ EXPECT_TRUE(rules.Matches(GURL("http://foo.google.com/x/y?q")));
+ EXPECT_TRUE(rules.Matches(GURL("http://foo:bar@baz.google.com#x")));
+
+ // Must be a strict "ends with" to work.
+ EXPECT_FALSE(rules.Matches(GURL("http://google.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://foo.google.com.baz.org")));
+}
+
+TEST(ProxyBypassRulesTest, ParseAndMatchBasicDomainWithPort) {
+ ProxyBypassRules rules;
+ rules.ParseFromString("*.GOOGLE.com:80");
+ ASSERT_EQ(1u, rules.rules().size());
+ EXPECT_EQ("*.google.com:80", rules.rules()[0]->ToString());
+
+ // All of these match; scheme, and non-hostname components don't matter.
+ EXPECT_TRUE(rules.Matches(GURL("http://www.google.com")));
+ EXPECT_TRUE(rules.Matches(GURL("ftp://www.google.com:80")));
+ EXPECT_TRUE(rules.Matches(GURL("https://a.google.com:80?x")));
+
+ // Must be a strict "ends with" to work.
+ EXPECT_FALSE(rules.Matches(GURL("http://google.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://foo.google.com.baz.org")));
+
+ // The ports must match.
+ EXPECT_FALSE(rules.Matches(GURL("http://www.google.com:90")));
+ EXPECT_FALSE(rules.Matches(GURL("https://www.google.com")));
+}
+
+TEST(ProxyBypassRulesTest, MatchAll) {
+ ProxyBypassRules rules;
+ rules.ParseFromString("*");
+ ASSERT_EQ(1u, rules.rules().size());
+ EXPECT_EQ("*", rules.rules()[0]->ToString());
+
+ EXPECT_TRUE(rules.Matches(GURL("http://www.google.com")));
+ EXPECT_TRUE(rules.Matches(GURL("ftp://www.foobar.com:99")));
+ EXPECT_TRUE(rules.Matches(GURL("https://a.google.com:80?x")));
+}
+
+TEST(ProxyBypassRulesTest, WildcardAtStart) {
+ ProxyBypassRules rules;
+ rules.ParseFromString("*.org:443");
+ ASSERT_EQ(1u, rules.rules().size());
+ EXPECT_EQ("*.org:443", rules.rules()[0]->ToString());
+
+ EXPECT_TRUE(rules.Matches(GURL("http://www.google.org:443")));
+ EXPECT_TRUE(rules.Matches(GURL("https://www.google.org")));
+
+ EXPECT_FALSE(rules.Matches(GURL("http://www.google.org")));
+ EXPECT_FALSE(rules.Matches(GURL("https://www.google.com")));
+ EXPECT_FALSE(rules.Matches(GURL("https://www.google.org.com")));
+}
+
+TEST(ProxyBypassRulesTest, IPV4Address) {
+ ProxyBypassRules rules;
+ rules.ParseFromString("192.168.1.1");
+ ASSERT_EQ(1u, rules.rules().size());
+ EXPECT_EQ("192.168.1.1", rules.rules()[0]->ToString());
+
+ EXPECT_TRUE(rules.Matches(GURL("http://192.168.1.1")));
+ EXPECT_TRUE(rules.Matches(GURL("https://192.168.1.1:90")));
+
+ EXPECT_FALSE(rules.Matches(GURL("http://www.google.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://sup.192.168.1.1")));
+}
+
+TEST(ProxyBypassRulesTest, IPV4AddressWithPort) {
+ ProxyBypassRules rules;
+ rules.ParseFromString("192.168.1.1:33");
+ ASSERT_EQ(1u, rules.rules().size());
+ EXPECT_EQ("192.168.1.1:33", rules.rules()[0]->ToString());
+
+ EXPECT_TRUE(rules.Matches(GURL("http://192.168.1.1:33")));
+
+ EXPECT_FALSE(rules.Matches(GURL("http://www.google.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://192.168.1.1")));
+ EXPECT_FALSE(rules.Matches(GURL("http://sup.192.168.1.1:33")));
+}
+
+TEST(ProxyBypassRulesTest, IPV6Address) {
+ ProxyBypassRules rules;
+ rules.ParseFromString("[3ffe:2a00:100:7031:0:0::1]");
+ ASSERT_EQ(1u, rules.rules().size());
+ // Note that we canonicalized the IP address.
+ EXPECT_EQ("[3ffe:2a00:100:7031::1]", rules.rules()[0]->ToString());
+
+ EXPECT_TRUE(rules.Matches(GURL("http://[3ffe:2a00:100:7031::1]")));
+ EXPECT_TRUE(rules.Matches(GURL("http://[3ffe:2a00:100:7031::1]:33")));
+
+ EXPECT_FALSE(rules.Matches(GURL("http://www.google.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://sup.192.168.1.1:33")));
+}
+
+TEST(ProxyBypassRulesTest, IPV6AddressWithPort) {
+ ProxyBypassRules rules;
+ rules.ParseFromString("[3ffe:2a00:100:7031::1]:33");
+ ASSERT_EQ(1u, rules.rules().size());
+ EXPECT_EQ("[3ffe:2a00:100:7031::1]:33", rules.rules()[0]->ToString());
+
+ EXPECT_TRUE(rules.Matches(GURL("http://[3ffe:2a00:100:7031::1]:33")));
+
+ EXPECT_FALSE(rules.Matches(GURL("http://[3ffe:2a00:100:7031::1]")));
+ EXPECT_FALSE(rules.Matches(GURL("http://www.google.com")));
+}
+
+TEST(ProxyBypassRulesTest, HTTPOnly) {
+ ProxyBypassRules rules;
+ rules.ParseFromString("http://www.google.com");
+ ASSERT_EQ(1u, rules.rules().size());
+ EXPECT_EQ("http://www.google.com", rules.rules()[0]->ToString());
+
+ EXPECT_TRUE(rules.Matches(GURL("http://www.google.com/foo")));
+ EXPECT_TRUE(rules.Matches(GURL("http://www.google.com:99")));
+
+ EXPECT_FALSE(rules.Matches(GURL("https://www.google.com")));
+ EXPECT_FALSE(rules.Matches(GURL("ftp://www.google.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://foo.www.google.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://www.google.com.org")));
+ EXPECT_FALSE(rules.Matches(GURL("https://www.google.com")));
+}
+
+TEST(ProxyBypassRulesTest, HTTPOnlyWithWildcard) {
+ ProxyBypassRules rules;
+ rules.ParseFromString("http://*www.google.com");
+ ASSERT_EQ(1u, rules.rules().size());
+ EXPECT_EQ("http://*www.google.com", rules.rules()[0]->ToString());
+
+ EXPECT_TRUE(rules.Matches(GURL("http://www.google.com/foo")));
+ EXPECT_TRUE(rules.Matches(GURL("http://www.google.com:99")));
+ EXPECT_TRUE(rules.Matches(GURL("http://foo.www.google.com")));
+
+ EXPECT_FALSE(rules.Matches(GURL("https://www.google.com")));
+ EXPECT_FALSE(rules.Matches(GURL("ftp://www.google.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://www.google.com.org")));
+ EXPECT_FALSE(rules.Matches(GURL("https://www.google.com")));
+}
+
+TEST(ProxyBypassRulesTest, UseSuffixMatching) {
+ ProxyBypassRules rules;
+ rules.ParseFromStringUsingSuffixMatching(
+ "foo1.com, .foo2.com, 192.168.1.1, "
+ "*foobar.com:80, *.foo, http://baz, <local>");
+ ASSERT_EQ(7u, rules.rules().size());
+ EXPECT_EQ("*foo1.com", rules.rules()[0]->ToString());
+ EXPECT_EQ("*.foo2.com", rules.rules()[1]->ToString());
+ EXPECT_EQ("192.168.1.1", rules.rules()[2]->ToString());
+ EXPECT_EQ("*foobar.com:80", rules.rules()[3]->ToString());
+ EXPECT_EQ("*.foo", rules.rules()[4]->ToString());
+ EXPECT_EQ("http://*baz", rules.rules()[5]->ToString());
+ EXPECT_EQ("<local>", rules.rules()[6]->ToString());
+
+ EXPECT_TRUE(rules.Matches(GURL("http://foo1.com")));
+ EXPECT_TRUE(rules.Matches(GURL("http://aaafoo1.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://aaafoo1.com.net")));
+}
+
+TEST(ProxyBypassRulesTest, MultipleRules) {
+ ProxyBypassRules rules;
+ rules.ParseFromString(".google.com , .foobar.com:30");
+ ASSERT_EQ(2u, rules.rules().size());
+
+ EXPECT_TRUE(rules.Matches(GURL("http://baz.google.com:40")));
+ EXPECT_FALSE(rules.Matches(GURL("http://google.com:40")));
+ EXPECT_TRUE(rules.Matches(GURL("http://bar.foobar.com:30")));
+ EXPECT_FALSE(rules.Matches(GURL("http://bar.foobar.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://bar.foobar.com:33")));
+}
+
+TEST(ProxyBypassRulesTest, BadInputs) {
+ ProxyBypassRules rules;
+ EXPECT_FALSE(rules.AddRuleFromString("://"));
+ EXPECT_FALSE(rules.AddRuleFromString(" "));
+ EXPECT_FALSE(rules.AddRuleFromString("http://"));
+ EXPECT_FALSE(rules.AddRuleFromString("*.foo.com:-34"));
+ EXPECT_EQ(0u, rules.rules().size());
+}
+
+TEST(ProxyBypassRulesTest, Equals) {
+ ProxyBypassRules rules1;
+ ProxyBypassRules rules2;
+
+ rules1.ParseFromString("foo1.com, .foo2.com");
+ rules2.ParseFromString("foo1.com,.FOo2.com");
+
+ EXPECT_TRUE(rules1.Equals(rules2));
+ EXPECT_TRUE(rules2.Equals(rules1));
+
+ rules1.ParseFromString(".foo2.com");
+ rules2.ParseFromString("foo1.com,.FOo2.com");
+
+ EXPECT_FALSE(rules1.Equals(rules2));
+ EXPECT_FALSE(rules2.Equals(rules1));
+}
+
+TEST(ProxyBypassRulesTest, BypassLocalNames) {
+ const struct {
+ const char* url;
+ bool expected_is_local;
+ } tests[] = {
+ // Single-component hostnames are considered local.
+ {"http://localhost/x", true},
+ {"http://www", true},
+
+ // IPv4 loopback interface.
+ {"http://127.0.0.1/x", true},
+ {"http://127.0.0.1:80/x", true},
+
+ // IPv6 loopback interface.
+ {"http://[::1]:80/x", true},
+ {"http://[0:0::1]:6233/x", true},
+ {"http://[0:0:0:0:0:0:0:1]/x", true},
+
+ // Non-local URLs.
+ {"http://foo.com/", false},
+ {"http://localhost.i/", false},
+ {"http://www.google.com/", false},
+ {"http://192.168.0.1/", false},
+
+ // Try with different protocols.
+ {"ftp://127.0.0.1/x", true},
+ {"ftp://foobar.com/x", false},
+
+ // This is a bit of a gray-area, but GURL does not strip trailing dots
+ // in host-names, so the following are considered non-local.
+ {"http://www./x", false},
+ {"http://localhost./x", false},
+ };
+
+ ProxyBypassRules rules;
+ rules.ParseFromString("<local>");
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ SCOPED_TRACE(StringPrintf(
+ "Test[%d]: %s", static_cast<int>(i), tests[i].url));
+ EXPECT_EQ(tests[i].expected_is_local, rules.Matches(GURL(tests[i].url)));
+ }
+}
+
+TEST(ProxyBypassRulesTest, ParseAndMatchCIDR_IPv4) {
+ ProxyBypassRules rules;
+ rules.ParseFromString("192.168.1.1/16");
+ ASSERT_EQ(1u, rules.rules().size());
+ EXPECT_EQ("192.168.1.1/16", rules.rules()[0]->ToString());
+
+ EXPECT_TRUE(rules.Matches(GURL("http://192.168.1.1")));
+ EXPECT_TRUE(rules.Matches(GURL("ftp://192.168.4.4")));
+ EXPECT_TRUE(rules.Matches(GURL("https://192.168.0.0:81")));
+ EXPECT_TRUE(rules.Matches(GURL("http://[::ffff:192.168.11.11]")));
+
+ EXPECT_FALSE(rules.Matches(GURL("http://foobar.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://192.169.1.1")));
+ EXPECT_FALSE(rules.Matches(GURL("http://xxx.192.168.1.1")));
+ EXPECT_FALSE(rules.Matches(GURL("http://192.168.1.1.xx")));
+}
+
+TEST(ProxyBypassRulesTest, ParseAndMatchCIDR_IPv6) {
+ ProxyBypassRules rules;
+ rules.ParseFromString("a:b:c:d::/48");
+ ASSERT_EQ(1u, rules.rules().size());
+ EXPECT_EQ("a:b:c:d::/48", rules.rules()[0]->ToString());
+
+ EXPECT_TRUE(rules.Matches(GURL("http://[A:b:C:9::]")));
+ EXPECT_FALSE(rules.Matches(GURL("http://foobar.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://192.169.1.1")));
+}
+
+} // namespace
+
+} // namespace net
diff --git a/net/proxy/proxy_config.cc b/net/proxy/proxy_config.cc
index 836559e..6e73101 100644
--- a/net/proxy/proxy_config.cc
+++ b/net/proxy/proxy_config.cc
@@ -6,27 +6,57 @@
#include "base/string_tokenizer.h"
#include "base/string_util.h"
+#include "net/proxy/proxy_info.h"
namespace net {
-ProxyConfig::ProxyConfig()
- : auto_detect(false),
- proxy_bypass_local_names(false),
- id_(INVALID_ID) {
+bool ProxyConfig::ProxyRules::Equals(const ProxyRules& other) const {
+ return type == other.type &&
+ single_proxy == other.single_proxy &&
+ proxy_for_http == other.proxy_for_http &&
+ proxy_for_https == other.proxy_for_https &&
+ proxy_for_ftp == other.proxy_for_ftp &&
+ socks_proxy == other.socks_proxy &&
+ bypass_rules.Equals(other.bypass_rules) &&
+ reverse_bypass == other.reverse_bypass;
}
-bool ProxyConfig::Equals(const ProxyConfig& other) const {
- // The two configs can have different IDs. We are just interested in if they
- // have the same settings.
- return auto_detect == other.auto_detect &&
- pac_url == other.pac_url &&
- proxy_rules == other.proxy_rules &&
- proxy_bypass == other.proxy_bypass &&
- proxy_bypass_local_names == other.proxy_bypass_local_names;
-}
+void ProxyConfig::ProxyRules::Apply(const GURL& url, ProxyInfo* result) {
+ if (empty()) {
+ result->UseDirect();
+ return;
+ }
-bool ProxyConfig::MayRequirePACResolver() const {
- return auto_detect || pac_url.is_valid();
+ bool bypass_proxy = bypass_rules.Matches(url);
+ if (reverse_bypass)
+ bypass_proxy = !bypass_proxy;
+ if (bypass_proxy) {
+ result->UseDirect();
+ return;
+ }
+
+ switch (type) {
+ case ProxyRules::TYPE_SINGLE_PROXY: {
+ result->UseProxyServer(single_proxy);
+ return;
+ }
+ case ProxyRules::TYPE_PROXY_PER_SCHEME: {
+ const ProxyServer* entry = MapUrlSchemeToProxy(url.scheme());
+ if (entry) {
+ result->UseProxyServer(*entry);
+ } else {
+ // We failed to find a matching proxy server for the current URL
+ // scheme. Default to direct.
+ result->UseDirect();
+ }
+ return;
+ }
+ default: {
+ result->UseDirect();
+ NOTREACHED();
+ return;
+ }
+ }
}
void ProxyConfig::ProxyRules::ParseFromString(const std::string& proxy_rules) {
@@ -98,70 +128,19 @@
return NULL; // No mapping for this scheme.
}
-namespace {
-
-// Returns true if the given string represents an IP address.
-bool IsIPAddress(const std::string& domain) {
- // From GURL::HostIsIPAddress()
- url_canon::RawCanonOutputT<char, 128> ignored_output;
- url_canon::CanonHostInfo host_info;
- url_parse::Component domain_comp(0, domain.size());
- url_canon::CanonicalizeIPAddress(domain.c_str(), domain_comp,
- &ignored_output, &host_info);
- return host_info.IsIPAddress();
+ProxyConfig::ProxyConfig() : auto_detect_(false), id_(INVALID_ID) {
}
-} // namespace
+bool ProxyConfig::Equals(const ProxyConfig& other) const {
+ // The two configs can have different IDs. We are just interested in if they
+ // have the same settings.
+ return auto_detect_ == other.auto_detect_ &&
+ pac_url_ == other.pac_url_ &&
+ proxy_rules_.Equals(other.proxy_rules());
+}
-void ProxyConfig::ParseNoProxyList(const std::string& no_proxy) {
- proxy_bypass.clear();
- if (no_proxy.empty())
- return;
- // Traditional semantics:
- // A single "*" is specifically allowed and unproxies anything.
- // "*" wildcards other than a single "*" entry are not universally
- // supported. We will support them, as we get * wildcards for free
- // (see MatchPatternASCII() called from
- // ProxyService::ShouldBypassProxyForURL()).
- // no_proxy is a comma-separated list of <trailing_domain>[:<port>].
- // If no port is specified then any port matches.
- // The historical definition has trailing_domain match using a simple
- // string "endswith" test, so that the match need not correspond to a
- // "." boundary. For example: "google.com" matches "igoogle.com" too.
- // Seems like that could be confusing, but we'll obey tradition.
- // IP CIDR patterns are supposed to be supported too. We intend
- // to do this in proxy_service.cc, but it's currently a TODO.
- // See: http://crbug.com/9835.
- StringTokenizer no_proxy_list(no_proxy, ",");
- while (no_proxy_list.GetNext()) {
- std::string bypass_entry = no_proxy_list.token();
- TrimWhitespaceASCII(bypass_entry, TRIM_ALL, &bypass_entry);
- if (bypass_entry.empty())
- continue;
- if (bypass_entry.at(0) != '*') {
- // Insert a wildcard * to obtain an endsWith match, unless the
- // entry looks like it might be an IP or CIDR.
- // First look for either a :<port> or CIDR mask length suffix.
- std::string::const_iterator begin = bypass_entry.begin();
- std::string::const_iterator scan = bypass_entry.end() - 1;
- while (scan > begin && IsAsciiDigit(*scan))
- --scan;
- std::string potential_ip;
- if (*scan == '/' || *scan == ':')
- potential_ip = std::string(begin, scan - 1);
- else
- potential_ip = bypass_entry;
- if (!IsIPAddress(potential_ip)) {
- // Do insert a wildcard.
- bypass_entry.insert(0, "*");
- }
- // TODO(sdoyon): When CIDR matching is implemented in
- // proxy_service.cc, consider making proxy_bypass more
- // sophisticated to avoid parsing out the string on every
- // request.
- }
- proxy_bypass.push_back(bypass_entry);
- }
+bool ProxyConfig::MayRequirePACResolver() const {
+ return auto_detect_ || has_pac_url();
}
} // namespace net
@@ -213,10 +192,10 @@
std::ostream& operator<<(std::ostream& out, const net::ProxyConfig& config) {
// "Automatic" settings.
out << "Automatic settings:\n";
- out << " Auto-detect: " << BoolToYesNoString(config.auto_detect) << "\n";
+ out << " Auto-detect: " << BoolToYesNoString(config.auto_detect()) << "\n";
out << " Custom PAC script: ";
- if (config.pac_url.is_valid())
- out << config.pac_url;
+ if (config.has_pac_url())
+ out << config.pac_url();
else
out << "[None]";
out << "\n";
@@ -225,40 +204,42 @@
out << "Manual settings:\n";
out << " Proxy server: ";
- switch (config.proxy_rules.type) {
+ switch (config.proxy_rules().type) {
case net::ProxyConfig::ProxyRules::TYPE_NO_RULES:
out << "[None]\n";
break;
case net::ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY:
- out << config.proxy_rules.single_proxy;
+ out << config.proxy_rules().single_proxy;
out << "\n";
break;
case net::ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME:
out << "\n";
- if (config.proxy_rules.proxy_for_http.is_valid())
- out << " HTTP: " << config.proxy_rules.proxy_for_http << "\n";
- if (config.proxy_rules.proxy_for_https.is_valid())
- out << " HTTPS: " << config.proxy_rules.proxy_for_https << "\n";
- if (config.proxy_rules.proxy_for_ftp.is_valid())
- out << " FTP: " << config.proxy_rules.proxy_for_ftp << "\n";
- if (config.proxy_rules.socks_proxy.is_valid())
- out << " SOCKS: " << config.proxy_rules.socks_proxy << "\n";
+ if (config.proxy_rules().proxy_for_http.is_valid())
+ out << " HTTP: " << config.proxy_rules().proxy_for_http << "\n";
+ if (config.proxy_rules().proxy_for_https.is_valid())
+ out << " HTTPS: " << config.proxy_rules().proxy_for_https << "\n";
+ if (config.proxy_rules().proxy_for_ftp.is_valid())
+ out << " FTP: " << config.proxy_rules().proxy_for_ftp << "\n";
+ if (config.proxy_rules().socks_proxy.is_valid())
+ out << " SOCKS: " << config.proxy_rules().socks_proxy << "\n";
break;
}
- out << " Bypass list: ";
- if (config.proxy_bypass.empty()) {
- out << "[None]\n";
+ if (config.proxy_rules().reverse_bypass)
+ out << " Only use proxy for: ";
+ else
+ out << " Bypass list: ";
+ if (config.proxy_rules().bypass_rules.rules().empty()) {
+ out << "[None]";
} else {
- out << "\n";
- std::vector<std::string>::const_iterator it;
- for (it = config.proxy_bypass.begin();
- it != config.proxy_bypass.end(); ++it) {
- out << " " << *it << "\n";
+ const net::ProxyBypassRules& bypass_rules =
+ config.proxy_rules().bypass_rules;
+ net::ProxyBypassRules::RuleList::const_iterator it;
+ for (it = bypass_rules.rules().begin();
+ it != bypass_rules.rules().end(); ++it) {
+ out << "\n " << (*it)->ToString();
}
}
- out << " Bypass local names: "
- << BoolToYesNoString(config.proxy_bypass_local_names);
return out;
}
diff --git a/net/proxy/proxy_config.h b/net/proxy/proxy_config.h
index c48f74b..a247e38 100644
--- a/net/proxy/proxy_config.h
+++ b/net/proxy/proxy_config.h
@@ -10,32 +10,28 @@
#include <vector>
#include "googleurl/src/gurl.h"
+#include "net/proxy/proxy_bypass_rules.h"
#include "net/proxy/proxy_server.h"
namespace net {
-// Proxy configuration used to by the ProxyService.
+class ProxyInfo;
+
+// ProxyConfig describes a user's proxy settings.
+//
+// There are two categories of proxy settings:
+// (1) Automatic (indicates the methods to obtain a PAC script)
+// (2) Manual (simple set of proxy servers per scheme, and bypass patterns)
+//
+// When both automatic and manual settings are specified, the Automatic ones
+// take precedence over the manual ones.
+//
+// For more details see:
+// http://www.chromium.org/developers/design-documents/proxy-settings-fallback
class ProxyConfig {
public:
- typedef int ID;
-
- // Indicates an invalid proxy config.
- enum { INVALID_ID = 0 };
-
- ProxyConfig();
- // Default copy-constructor and assignment operator are OK!
-
- // Used to numerically identify this configuration.
- ID id() const { return id_; }
- void set_id(int id) { id_ = id; }
- bool is_valid() { return id_ != INVALID_ID; }
-
- // True if the proxy configuration should be auto-detected.
- bool auto_detect;
-
- // If non-empty, indicates the URL of the proxy auto-config file to use.
- GURL pac_url;
-
+ // ProxyRules describes the "manual" proxy settings.
+ // TODO(eroman): Turn this into a class.
struct ProxyRules {
enum Type {
TYPE_NO_RULES,
@@ -45,15 +41,18 @@
// Note that the default of TYPE_NO_RULES results in direct connections
// being made when using this ProxyConfig.
- ProxyRules() : type(TYPE_NO_RULES) {}
+ ProxyRules() : reverse_bypass(false), type(TYPE_NO_RULES) {}
bool empty() const {
return type == TYPE_NO_RULES;
}
+ // Sets |result| with the proxy to use for |url| based on the current rules.
+ void Apply(const GURL& url, ProxyInfo* result);
+
// Parses the rules from a string, indicating which proxies to use.
//
- // proxy-uri = [<proxy-scheme>://]<proxy-host>[:"<proxy-port>]
+ // proxy-uri = [<proxy-scheme>"://"]<proxy-host>[":"<proxy-port>]
//
// If the proxy to use depends on the scheme of the URL, can instead specify
// a semicolon separated list of:
@@ -61,8 +60,9 @@
// <url-scheme>"="<proxy-uri>
//
// For example:
- // "http=foopy:80;ftp=foopy2" -- use HTTP proxy "foopy:80" for http URLs,
- // and HTTP proxy "foopy2:80" for ftp URLs.
+ // "http=foopy:80;ftp=foopy2" -- use HTTP proxy "foopy:80" for http://
+ // URLs, and HTTP proxy "foopy2:80" for
+ // ftp:// URLs.
// "foopy:80" -- use HTTP proxy "foopy:80" for all URLs.
// "socks4://foopy" -- use SOCKS v4 proxy "foopy:1080" for all
// URLs.
@@ -75,14 +75,14 @@
// Should only call this if the type is TYPE_PROXY_PER_SCHEME.
const ProxyServer* MapUrlSchemeToProxy(const std::string& url_scheme) const;
- bool operator==(const ProxyRules& other) const {
- return type == other.type &&
- single_proxy == other.single_proxy &&
- proxy_for_http == other.proxy_for_http &&
- proxy_for_https == other.proxy_for_https &&
- proxy_for_ftp == other.proxy_for_ftp &&
- socks_proxy == other.socks_proxy;
- }
+ // Returns true if |*this| describes the same configuration as |other|.
+ bool Equals(const ProxyRules& other) const;
+
+ // Exceptions for when not to use a proxy.
+ ProxyBypassRules bypass_rules;
+
+ // Reverse the meaning of |bypass_rules|.
+ bool reverse_bypass;
Type type;
@@ -94,8 +94,9 @@
ProxyServer proxy_for_https;
ProxyServer proxy_for_ftp;
- // Set if configuration has SOCKS proxy.
+ // Set if the configuration has a SOCKS proxy fallback.
ProxyServer socks_proxy;
+
private:
// Returns one of {&proxy_for_http, &proxy_for_https, &proxy_for_ftp,
// &socks_proxy}, or NULL if it is a scheme that we don't have a mapping
@@ -103,21 +104,17 @@
ProxyServer* MapSchemeToProxy(const std::string& scheme);
};
- ProxyRules proxy_rules;
+ typedef int ID;
- // Parses entries from a comma-separated list of hosts for which proxy
- // configurations should be bypassed. Clears proxy_bypass and sets it to the
- // resulting list.
- void ParseNoProxyList(const std::string& no_proxy);
+ // Indicates an invalid proxy config.
+ enum { INVALID_ID = 0 };
- // Indicates a list of hosts that should bypass any proxy configuration. For
- // these hosts, a direct connection should always be used.
- // The form <host>:<port> is also supported, meaning that only
- // connections on the specified port should be direct.
- std::vector<std::string> proxy_bypass;
+ ProxyConfig();
- // Indicates whether local names (no dots) bypass proxies.
- bool proxy_bypass_local_names;
+ // Used to numerically identify this configuration.
+ ID id() const { return id_; }
+ void set_id(int id) { id_ = id; }
+ bool is_valid() { return id_ != INVALID_ID; }
// Returns true if the given config is equivalent to this config.
bool Equals(const ProxyConfig& other) const;
@@ -126,7 +123,62 @@
// use a PAC resolver.
bool MayRequirePACResolver() const;
+ ProxyRules& proxy_rules() {
+ return proxy_rules_;
+ }
+
+ const ProxyRules& proxy_rules() const {
+ return proxy_rules_;
+ }
+
+ void set_pac_url(const GURL& url) {
+ pac_url_ = url;
+ }
+
+ const GURL& pac_url() const {
+ return pac_url_;
+ }
+
+ bool has_pac_url() const {
+ return pac_url_.is_valid();
+ }
+
+ void set_auto_detect(bool enable_auto_detect) {
+ auto_detect_ = enable_auto_detect;
+ }
+
+ bool auto_detect() const {
+ return auto_detect_;
+ }
+
+ // Helpers to construct some common proxy configurations.
+
+ static ProxyConfig CreateDirect() {
+ return ProxyConfig();
+ }
+
+ static ProxyConfig CreateAutoDetect() {
+ ProxyConfig config;
+ config.set_auto_detect(true);
+ return config;
+ }
+
+ static ProxyConfig CreateFromCustomPacURL(const GURL& pac_url) {
+ ProxyConfig config;
+ config.set_pac_url(pac_url);
+ return config;
+ }
+
private:
+ // True if the proxy configuration should be auto-detected.
+ bool auto_detect_;
+
+ // If non-empty, indicates the URL of the proxy auto-config file to use.
+ GURL pac_url_;
+
+ // Manual proxy settings.
+ ProxyRules proxy_rules_;
+
int id_;
};
diff --git a/net/proxy/proxy_config_service_common_unittest.cc b/net/proxy/proxy_config_service_common_unittest.cc
index 1a61468..9e5fd76 100644
--- a/net/proxy/proxy_config_service_common_unittest.cc
+++ b/net/proxy/proxy_config_service_common_unittest.cc
@@ -9,58 +9,138 @@
#include "net/proxy/proxy_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
namespace net {
-ProxyConfig::ProxyRules MakeProxyRules(
- ProxyConfig::ProxyRules::Type type,
+namespace {
+
+// Helper to verify that |expected_proxy| matches |actual_proxy|. If it does
+// not, then |*did_fail| is set to true, and |*failure_details| is filled with
+// a description of the failure.
+void MatchesProxyServerHelper(const char* failure_message,
+ const char* expected_proxy,
+ const ProxyServer& actual_proxy,
+ ::testing::AssertionResult* failure_details,
+ bool* did_fail) {
+ std::string actual_proxy_string;
+ if (actual_proxy.is_valid())
+ actual_proxy_string = actual_proxy.ToURI();
+
+ if (std::string(expected_proxy) != actual_proxy_string) {
+ *failure_details
+ << failure_message << ". Was expecting: \"" << expected_proxy
+ << "\" but got: \"" << actual_proxy_string << "\"";
+ *did_fail = true;
+ }
+}
+
+std::string FlattenProxyBypass(const ProxyBypassRules& bypass_rules) {
+ std::string flattened_proxy_bypass;
+ for (ProxyBypassRules::RuleList::const_iterator it =
+ bypass_rules.rules().begin();
+ it != bypass_rules.rules().end(); ++it) {
+ if (!flattened_proxy_bypass.empty())
+ flattened_proxy_bypass += ",";
+ flattened_proxy_bypass += (*it)->ToString();
+ }
+ return flattened_proxy_bypass;
+}
+
+} // namespace
+
+::testing::AssertionResult ProxyRulesExpectation::Matches(
+ const ProxyConfig::ProxyRules& rules) const {
+ ::testing::AssertionResult failure_details = ::testing::AssertionFailure();
+ bool failed = false;
+
+ if (rules.type != type) {
+ failure_details << "Type mismatch. Expected: "
+ << type << " but was: " << rules.type;
+ failed = true;
+ }
+
+ MatchesProxyServerHelper("Bad single_proxy", single_proxy,
+ rules.single_proxy, &failure_details, &failed);
+ MatchesProxyServerHelper("Bad proxy_for_http", proxy_for_http,
+ rules.proxy_for_http, &failure_details, &failed);
+ MatchesProxyServerHelper("Bad proxy_for_https", proxy_for_https,
+ rules.proxy_for_https, &failure_details, &failed);
+ MatchesProxyServerHelper("Bad proxy_for_socks", socks_proxy,
+ rules.socks_proxy, &failure_details, &failed);
+
+ std::string actual_flattened_bypass = FlattenProxyBypass(rules.bypass_rules);
+ if (std::string(flattened_bypass_rules) != actual_flattened_bypass) {
+ failure_details
+ << "Bad bypass rules. Expected: \"" << flattened_bypass_rules
+ << "\" but got: \"" << actual_flattened_bypass << "\"";
+ failed = true;
+ }
+
+ if (rules.reverse_bypass != reverse_bypass) {
+ failure_details << "Bad reverse_bypass. Expected: " << reverse_bypass
+ << " but got: " << rules.reverse_bypass;
+ failed = true;
+ }
+
+ return failed ? failure_details : ::testing::AssertionSuccess();
+}
+
+// static
+ProxyRulesExpectation ProxyRulesExpectation::Empty() {
+ return ProxyRulesExpectation(ProxyConfig::ProxyRules::TYPE_NO_RULES,
+ "", "", "", "", "", "", false);
+}
+
+// static
+ProxyRulesExpectation ProxyRulesExpectation::EmptyWithBypass(
+ const char* flattened_bypass_rules) {
+ return ProxyRulesExpectation(ProxyConfig::ProxyRules::TYPE_NO_RULES,
+ "", "", "", "", "", flattened_bypass_rules,
+ false);
+}
+
+// static
+ProxyRulesExpectation ProxyRulesExpectation::Single(
const char* single_proxy,
- const char* proxy_for_http,
- const char* proxy_for_https,
- const char* proxy_for_ftp,
- const char* socks_proxy) {
- ProxyConfig::ProxyRules rules;
- rules.type = type;
- rules.single_proxy = ProxyServer::FromURI(single_proxy,
- ProxyServer::SCHEME_HTTP);
- rules.proxy_for_http = ProxyServer::FromURI(proxy_for_http,
- ProxyServer::SCHEME_HTTP);
- rules.proxy_for_https = ProxyServer::FromURI(proxy_for_https,
- ProxyServer::SCHEME_HTTP);
- rules.proxy_for_ftp = ProxyServer::FromURI(proxy_for_ftp,
- ProxyServer::SCHEME_HTTP);
- rules.socks_proxy = ProxyServer::FromURI(socks_proxy,
- ProxyServer::SCHEME_SOCKS4);
- return rules;
+ const char* flattened_bypass_rules) {
+ return ProxyRulesExpectation(ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY,
+ single_proxy, "", "", "", "",
+ flattened_bypass_rules, false);
}
-ProxyConfig::ProxyRules MakeSingleProxyRules(const char* single_proxy) {
- return MakeProxyRules(ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY,
- single_proxy, "", "", "", "");
-}
-
-ProxyConfig::ProxyRules MakeProxyPerSchemeRules(
- const char* proxy_http,
- const char* proxy_https,
- const char* proxy_ftp) {
- return MakeProxyRules(ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME,
- "", proxy_http, proxy_https, proxy_ftp, "");
-}
-ProxyConfig::ProxyRules MakeProxyPerSchemeRules(
+// static
+ProxyRulesExpectation ProxyRulesExpectation::PerScheme(
const char* proxy_http,
const char* proxy_https,
const char* proxy_ftp,
- const char* socks_proxy) {
- return MakeProxyRules(ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME,
- "", proxy_http, proxy_https, proxy_ftp, socks_proxy);
+ const char* flattened_bypass_rules) {
+ return ProxyRulesExpectation(ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME,
+ "", proxy_http, proxy_https, proxy_ftp, "",
+ flattened_bypass_rules, false);
}
-std::string FlattenProxyBypass(const BypassList& proxy_bypass) {
- std::string flattened_proxy_bypass;
- for (BypassList::const_iterator it = proxy_bypass.begin();
- it != proxy_bypass.end(); ++it) {
- flattened_proxy_bypass += *it + "\n";
- }
- return flattened_proxy_bypass;
+// static
+ProxyRulesExpectation ProxyRulesExpectation::PerSchemeWithSocks(
+ const char* proxy_http,
+ const char* proxy_https,
+ const char* proxy_ftp,
+ const char* socks_proxy,
+ const char* flattened_bypass_rules) {
+ return ProxyRulesExpectation(ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME,
+ "", proxy_http, proxy_https, proxy_ftp,
+ socks_proxy, flattened_bypass_rules, false);
+}
+
+// static
+ProxyRulesExpectation ProxyRulesExpectation::PerSchemeWithBypassReversed(
+ const char* proxy_http,
+ const char* proxy_https,
+ const char* proxy_ftp,
+ const char* flattened_bypass_rules) {
+ return ProxyRulesExpectation(ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME,
+ "", proxy_http, proxy_https, proxy_ftp, "",
+ flattened_bypass_rules, true);
}
} // namespace net
diff --git a/net/proxy/proxy_config_service_common_unittest.h b/net/proxy/proxy_config_service_common_unittest.h
index 783fc6f..3b70ef0 100644
--- a/net/proxy/proxy_config_service_common_unittest.h
+++ b/net/proxy/proxy_config_service_common_unittest.h
@@ -9,36 +9,86 @@
#include <vector>
#include "net/proxy/proxy_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
-// A few small helper functions common to the win and linux unittests.
+// Helper functions to describe the expected value of a
+// ProxyConfig::ProxyRules, and to check for a match.
namespace net {
-ProxyConfig::ProxyRules MakeProxyRules(
- ProxyConfig::ProxyRules::Type type,
- const char* single_proxy,
- const char* proxy_for_http,
- const char* proxy_for_https,
- const char* proxy_for_ftp,
- const char* socks_proxy);
+class ProxyBypassRules;
-ProxyConfig::ProxyRules MakeSingleProxyRules(const char* single_proxy);
+// This structure contains our expectations on what values the ProxyRules
+// should have.
+struct ProxyRulesExpectation {
+ ProxyRulesExpectation(ProxyConfig::ProxyRules::Type type,
+ const char* single_proxy,
+ const char* proxy_for_http,
+ const char* proxy_for_https,
+ const char* proxy_for_ftp,
+ const char* socks_proxy,
+ const char* flattened_bypass_rules,
+ bool reverse_bypass)
+ : type(type),
+ single_proxy(single_proxy),
+ proxy_for_http(proxy_for_http),
+ proxy_for_https(proxy_for_https),
+ proxy_for_ftp(proxy_for_ftp),
+ socks_proxy(socks_proxy),
+ flattened_bypass_rules(flattened_bypass_rules),
+ reverse_bypass(reverse_bypass) {
+ }
-ProxyConfig::ProxyRules MakeProxyPerSchemeRules(
- const char* proxy_http,
- const char* proxy_https,
- const char* proxy_ftp);
-ProxyConfig::ProxyRules MakeProxyPerSchemeRules(
- const char* proxy_http,
- const char* proxy_https,
- const char* proxy_ftp,
- const char* socks_proxy);
+ // Call this within an EXPECT_TRUE(), to assert that |rules| matches
+ // our expected values |*this|.
+ ::testing::AssertionResult Matches(
+ const ProxyConfig::ProxyRules& rules) const;
-typedef std::vector<std::string> BypassList;
+ // Creates an expectation that the ProxyRules has no rules.
+ static ProxyRulesExpectation Empty();
-// Joins the proxy bypass list using "\n" to make it into a single string.
-std::string FlattenProxyBypass(const BypassList& proxy_bypass);
+ // Creates an expectation that the ProxyRules has nothing other than
+ // the specified bypass rules.
+ static ProxyRulesExpectation EmptyWithBypass(
+ const char* flattened_bypass_rules);
+
+ // Creates an expectation that the ProxyRules is for a single proxy
+ // server for all schemes.
+ static ProxyRulesExpectation Single(const char* single_proxy,
+ const char* flattened_bypass_rules);
+
+ // Creates an expectation that the ProxyRules specifies a different
+ // proxy server for each URL scheme.
+ static ProxyRulesExpectation PerScheme(const char* proxy_http,
+ const char* proxy_https,
+ const char* proxy_ftp,
+ const char* flattened_bypass_rules);
+
+ // Same as above, but additionally with a SOCKS fallback.
+ static ProxyRulesExpectation PerSchemeWithSocks(
+ const char* proxy_http,
+ const char* proxy_https,
+ const char* proxy_ftp,
+ const char* socks_proxy,
+ const char* flattened_bypass_rules);
+
+ // Same as PerScheme, but with the bypass rules reversed
+ static ProxyRulesExpectation PerSchemeWithBypassReversed(
+ const char* proxy_http,
+ const char* proxy_https,
+ const char* proxy_ftp,
+ const char* flattened_bypass_rules);
+
+ ProxyConfig::ProxyRules::Type type;
+ const char* single_proxy;
+ const char* proxy_for_http;
+ const char* proxy_for_https;
+ const char* proxy_for_ftp;
+ const char* socks_proxy;
+ const char* flattened_bypass_rules;
+ bool reverse_bypass;
+};
} // namespace net
diff --git a/net/proxy/proxy_config_service_fixed.h b/net/proxy/proxy_config_service_fixed.h
index 54fd9ac..b677eb4 100644
--- a/net/proxy/proxy_config_service_fixed.h
+++ b/net/proxy/proxy_config_service_fixed.h
@@ -6,6 +6,7 @@
#define NET_PROXY_PROXY_CONFIG_SERVICE_FIXED_H_
#include "net/base/net_errors.h"
+#include "net/proxy/proxy_config.h"
#include "net/proxy/proxy_config_service.h"
namespace net {
diff --git a/net/proxy/proxy_config_service_linux.cc b/net/proxy/proxy_config_service_linux.cc
index 3ca35c5..bd863bd 100644
--- a/net/proxy/proxy_config_service_linux.cc
+++ b/net/proxy/proxy_config_service_linux.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -13,6 +13,9 @@
#include <sys/inotify.h>
#include <unistd.h>
+#include <map>
+
+#include "base/env_var.h"
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/logging.h"
@@ -21,6 +24,7 @@
#include "base/string_util.h"
#include "base/task.h"
#include "base/timer.h"
+#include "base/xdg_util.h"
#include "googleurl/src/url_canon.h"
#include "net/base/net_errors.h"
#include "net/http/http_util.h"
@@ -78,7 +82,7 @@
const char* variable, ProxyServer::Scheme scheme,
ProxyServer* result_server) {
std::string env_value;
- if (env_var_getter_->Getenv(variable, &env_value)) {
+ if (env_var_getter_->GetEnv(variable, &env_value)) {
if (!env_value.empty()) {
env_value = FixupProxyHostScheme(scheme, env_value);
ProxyServer proxy_server =
@@ -106,25 +110,25 @@
// extension has ever used this, but it still sounds like a good
// idea.
std::string auto_proxy;
- if (env_var_getter_->Getenv("auto_proxy", &auto_proxy)) {
+ if (env_var_getter_->GetEnv("auto_proxy", &auto_proxy)) {
if (auto_proxy.empty()) {
// Defined and empty => autodetect
- config->auto_detect = true;
+ config->set_auto_detect(true);
} else {
// specified autoconfig URL
- config->pac_url = GURL(auto_proxy);
+ config->set_pac_url(GURL(auto_proxy));
}
return true;
}
// "all_proxy" is a shortcut to avoid defining {http,https,ftp}_proxy.
ProxyServer proxy_server;
if (GetProxyFromEnvVar("all_proxy", &proxy_server)) {
- config->proxy_rules.type = ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY;
- config->proxy_rules.single_proxy = proxy_server;
+ config->proxy_rules().type = ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY;
+ config->proxy_rules().single_proxy = proxy_server;
} else {
bool have_http = GetProxyFromEnvVar("http_proxy", &proxy_server);
if (have_http)
- config->proxy_rules.proxy_for_http = proxy_server;
+ config->proxy_rules().proxy_for_http = proxy_server;
// It would be tempting to let http_proxy apply for all protocols
// if https_proxy and ftp_proxy are not defined. Googling turns up
// several documents that mention only http_proxy. But then the
@@ -132,38 +136,42 @@
// like other apps do this. So we will refrain.
bool have_https = GetProxyFromEnvVar("https_proxy", &proxy_server);
if (have_https)
- config->proxy_rules.proxy_for_https = proxy_server;
+ config->proxy_rules().proxy_for_https = proxy_server;
bool have_ftp = GetProxyFromEnvVar("ftp_proxy", &proxy_server);
if (have_ftp)
- config->proxy_rules.proxy_for_ftp = proxy_server;
+ config->proxy_rules().proxy_for_ftp = proxy_server;
if (have_http || have_https || have_ftp) {
// mustn't change type unless some rules are actually set.
- config->proxy_rules.type = ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
+ config->proxy_rules().type =
+ ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
}
}
- if (config->proxy_rules.empty()) {
+ if (config->proxy_rules().empty()) {
// If the above were not defined, try for socks.
ProxyServer::Scheme scheme = ProxyServer::SCHEME_SOCKS4;
std::string env_version;
- if (env_var_getter_->Getenv("SOCKS_VERSION", &env_version)
+ if (env_var_getter_->GetEnv("SOCKS_VERSION", &env_version)
&& env_version == "5")
scheme = ProxyServer::SCHEME_SOCKS5;
if (GetProxyFromEnvVarForScheme("SOCKS_SERVER", scheme, &proxy_server)) {
- config->proxy_rules.type = ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY;
- config->proxy_rules.single_proxy = proxy_server;
+ config->proxy_rules().type = ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY;
+ config->proxy_rules().single_proxy = proxy_server;
}
}
// Look for the proxy bypass list.
std::string no_proxy;
- env_var_getter_->Getenv("no_proxy", &no_proxy);
- if (config->proxy_rules.empty()) {
+ env_var_getter_->GetEnv("no_proxy", &no_proxy);
+ if (config->proxy_rules().empty()) {
// Having only "no_proxy" set, presumably to "*", makes it
// explicit that env vars do specify a configuration: having no
// rules specified only means the user explicitly asks for direct
// connections.
return !no_proxy.empty();
}
- config->ParseNoProxyList(no_proxy);
+ // Note that this uses "suffix" matching. So a bypass of "google.com"
+ // is understood to mean a bypass of "*google.com".
+ config->proxy_rules().bypass_rules.ParseFromStringUsingSuffixMatching(
+ no_proxy);
return true;
}
@@ -343,6 +351,15 @@
return true;
}
+ virtual bool BypassListIsReversed() {
+ // This is a KDE-specific setting.
+ return false;
+ }
+
+ virtual bool MatchHostsUsingSuffixMatching() {
+ return false;
+ }
+
private:
// Logs and frees a glib error. Returns false if there was no error
// (error is NULL).
@@ -404,21 +421,60 @@
: public ProxyConfigServiceLinux::GConfSettingGetter,
public base::MessagePumpLibevent::Watcher {
public:
- explicit GConfSettingGetterImplKDE(
- base::EnvironmentVariableGetter* env_var_getter)
+ explicit GConfSettingGetterImplKDE(base::EnvVarGetter* env_var_getter)
: inotify_fd_(-1), notify_delegate_(NULL), indirect_manual_(false),
- auto_no_pac_(false), reversed_exception_(false), file_loop_(NULL) {
- // We don't save the env var getter for later use since we don't own it.
- // Instead we use it here and save the result we actually care about.
- std::string kde_home;
- if (!env_var_getter->Getenv("KDE_HOME", &kde_home)) {
- if (!env_var_getter->Getenv("HOME", &kde_home))
+ auto_no_pac_(false), reversed_bypass_list_(false),
+ env_var_getter_(env_var_getter), file_loop_(NULL) {
+ // Derive the location of the kde config dir from the environment.
+ std::string home;
+ if (env_var_getter->GetEnv("KDEHOME", &home) && !home.empty()) {
+ // $KDEHOME is set. Use it unconditionally.
+ kde_config_dir_ = KDEHomeToConfigPath(FilePath(home));
+ } else {
+ // $KDEHOME is unset. Try to figure out what to use. This seems to be
+ // the common case on most distributions.
+ if (!env_var_getter->GetEnv(base::env_vars::kHome, &home))
// User has no $HOME? Give up. Later we'll report the failure.
return;
- kde_home = FilePath(kde_home).Append(FILE_PATH_LITERAL(".kde")).value();
+ if (base::GetDesktopEnvironment(env_var_getter) ==
+ base::DESKTOP_ENVIRONMENT_KDE3) {
+ // KDE3 always uses .kde for its configuration.
+ FilePath kde_path = FilePath(home).Append(".kde");
+ kde_config_dir_ = KDEHomeToConfigPath(kde_path);
+ } else {
+ // Some distributions patch KDE4 to use .kde4 instead of .kde, so that
+ // both can be installed side-by-side. Sadly they don't all do this, and
+ // they don't always do this: some distributions have started switching
+ // back as well. So if there is a .kde4 directory, check the timestamps
+ // of the config directories within and use the newest one.
+ // Note that we should currently be running in the UI thread, because in
+ // the gconf version, that is the only thread that can access the proxy
+ // settings (a gconf restriction). As noted below, the initial read of
+ // the proxy settings will be done in this thread anyway, so we check
+ // for .kde4 here in this thread as well.
+ FilePath kde3_path = FilePath(home).Append(".kde");
+ FilePath kde3_config = KDEHomeToConfigPath(kde3_path);
+ FilePath kde4_path = FilePath(home).Append(".kde4");
+ FilePath kde4_config = KDEHomeToConfigPath(kde4_path);
+ bool use_kde4 = false;
+ if (file_util::DirectoryExists(kde4_path)) {
+ file_util::FileInfo kde3_info;
+ file_util::FileInfo kde4_info;
+ if (file_util::GetFileInfo(kde4_config, &kde4_info)) {
+ if (file_util::GetFileInfo(kde3_config, &kde3_info)) {
+ use_kde4 = kde4_info.last_modified >= kde3_info.last_modified;
+ } else {
+ use_kde4 = true;
+ }
+ }
+ }
+ if (use_kde4) {
+ kde_config_dir_ = KDEHomeToConfigPath(kde4_path);
+ } else {
+ kde_config_dir_ = KDEHomeToConfigPath(kde3_path);
+ }
+ }
}
- kde_config_dir_ = FilePath(kde_home).Append(
- FILE_PATH_LITERAL("share")).Append(FILE_PATH_LITERAL("config"));
}
virtual ~GConfSettingGetterImplKDE() {
@@ -523,16 +579,28 @@
return true;
}
+ virtual bool BypassListIsReversed() {
+ return reversed_bypass_list_;
+ }
+
+ virtual bool MatchHostsUsingSuffixMatching() {
+ return true;
+ }
+
private:
void ResetCachedSettings() {
string_table_.clear();
strings_table_.clear();
indirect_manual_ = false;
auto_no_pac_ = false;
- reversed_exception_ = false;
+ reversed_bypass_list_ = false;
}
- void AddProxy(std::string prefix, std::string value) {
+ FilePath KDEHomeToConfigPath(const FilePath& kde_home) {
+ return kde_home.Append("share").Append("config");
+ }
+
+ void AddProxy(const std::string& prefix, const std::string& value) {
if (value.empty() || value.substr(0, 3) == "//:")
// No proxy.
return;
@@ -542,6 +610,17 @@
string_table_[prefix + "host"] = value;
}
+ void AddHostList(const std::string& key, const std::string& value) {
+ std::vector<std::string> tokens;
+ StringTokenizer tk(value, ", ");
+ while (tk.GetNext()) {
+ std::string token = tk.token();
+ if (!token.empty())
+ tokens.push_back(token);
+ }
+ strings_table_[key] = tokens;
+ }
+
void AddKDESetting(const std::string& key, const std::string& value) {
// The astute reader may notice that there is no mention of SOCKS
// here. That's because KDE handles socks is a strange way, and we
@@ -584,16 +663,9 @@
// We count "true" or any nonzero number as true, otherwise false.
// Note that if the value is not actually numeric StringToInt()
// will return 0, which we count as false.
- reversed_exception_ = value == "true" || StringToInt(value);
+ reversed_bypass_list_ = (value == "true" || StringToInt(value));
} else if (key == "NoProxyFor") {
- std::vector<std::string> exceptions;
- StringTokenizer tk(value, ",");
- while (tk.GetNext()) {
- std::string token = tk.token();
- if (!token.empty())
- exceptions.push_back(token);
- }
- strings_table_["/system/http_proxy/ignore_hosts"] = exceptions;
+ AddHostList("/system/http_proxy/ignore_hosts", value);
} else if (key == "AuthMode") {
// Check for authentication, just so we can warn.
int mode = StringToInt(value);
@@ -606,16 +678,26 @@
}
}
- void ResolveIndirect(std::string key) {
- // We can't save the environment variable getter that was passed
- // when this object was constructed, but this setting is likely
- // to be pretty unusual and the actual values it would return can
- // be tested without using it. So we just use getenv() here.
+ void ResolveIndirect(const std::string& key) {
string_map_type::iterator it = string_table_.find(key);
if (it != string_table_.end()) {
- char* value = getenv(it->second.c_str());
- if (value)
+ std::string value;
+ if (env_var_getter_->GetEnv(it->second.c_str(), &value))
it->second = value;
+ else
+ string_table_.erase(it);
+ }
+ }
+
+ void ResolveIndirectList(const std::string& key) {
+ strings_map_type::iterator it = strings_table_.find(key);
+ if (it != strings_table_.end()) {
+ std::string value;
+ if (!it->second.empty() &&
+ env_var_getter_->GetEnv(it->second[0].c_str(), &value))
+ AddHostList(key, value);
+ else
+ strings_table_.erase(it);
}
}
@@ -628,26 +710,18 @@
ResolveIndirect("/system/http_proxy/host");
ResolveIndirect("/system/proxy/secure_host");
ResolveIndirect("/system/proxy/ftp_host");
+ ResolveIndirectList("/system/http_proxy/ignore_hosts");
}
if (auto_no_pac_) {
// Remove the PAC URL; we're not supposed to use it.
string_table_.erase("/system/proxy/autoconfig_url");
}
- if (reversed_exception_) {
- // We don't actually support this setting. (It means to use the proxy
- // *only* for the exception list, rather than everything but them.)
- // Nevertheless we can do better than *exactly the opposite* of the
- // desired behavior by clearing the exception list and warning.
- strings_table_.erase("/system/http_proxy/ignore_hosts");
- LOG(WARNING) << "KDE reversed proxy exception list not supported";
- }
}
// Reads kioslaverc one line at a time and calls AddKDESetting() to add
// each relevant name-value pair to the appropriate value table.
void UpdateCachedSettings() {
- FilePath kioslaverc = kde_config_dir_.Append(
- FILE_PATH_LITERAL("kioslaverc"));
+ FilePath kioslaverc = kde_config_dir_.Append("kioslaverc");
file_util::ScopedFILE input(file_util::OpenFile(kioslaverc, "r"));
if (!input.get())
return;
@@ -747,8 +821,8 @@
while (event_ptr < event_buf + r) {
inotify_event* event = reinterpret_cast<inotify_event*>(event_ptr);
// The kernel always feeds us whole events.
- CHECK(event_ptr + sizeof(inotify_event) <= event_buf + r);
- CHECK(event->name + event->len <= event_buf + r);
+ CHECK_LE(event_ptr + sizeof(inotify_event), event_buf + r);
+ CHECK_LE(event->name + event->len, event_buf + r);
if (!strcmp(event->name, "kioslaverc"))
kioslaverc_touched = true;
// Advance the pointer just past the end of the filename.
@@ -795,7 +869,11 @@
FilePath kde_config_dir_;
bool indirect_manual_;
bool auto_no_pac_;
- bool reversed_exception_;
+ bool reversed_bypass_list_;
+ // We don't own |env_var_getter_|. It's safe to hold a pointer to it, since
+ // both it and us are owned by ProxyConfigServiceLinux::Delegate, and have the
+ // same lifetime.
+ base::EnvVarGetter* env_var_getter_;
// We cache these settings whenever we re-read the kioslaverc file.
string_map_type string_table_;
@@ -862,11 +940,11 @@
GURL pac_url(pac_url_str);
if (!pac_url.is_valid())
return false;
- config->pac_url = pac_url;
+ config->set_pac_url(pac_url);
return true;
}
}
- config->auto_detect = true;
+ config->set_auto_detect(true);
return true;
}
@@ -896,37 +974,37 @@
if (GetProxyFromGConf("/system/proxy/socks_", true, &proxy_server)) {
// gconf settings do not appear to distinguish between socks
// version. We default to version 4.
- config->proxy_rules.type = ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY;
- config->proxy_rules.single_proxy = proxy_server;
+ config->proxy_rules().type = ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY;
+ config->proxy_rules().single_proxy = proxy_server;
}
}
- if (config->proxy_rules.empty()) {
+ if (config->proxy_rules().empty()) {
bool have_http = GetProxyFromGConf("/system/http_proxy/", false,
&proxy_server);
if (same_proxy) {
if (have_http) {
- config->proxy_rules.type = ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY;
- config->proxy_rules.single_proxy = proxy_server;
+ config->proxy_rules().type = ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY;
+ config->proxy_rules().single_proxy = proxy_server;
}
} else {
// Protocol specific settings.
if (have_http)
- config->proxy_rules.proxy_for_http = proxy_server;
+ config->proxy_rules().proxy_for_http = proxy_server;
bool have_secure = GetProxyFromGConf("/system/proxy/secure_", false,
&proxy_server);
if (have_secure)
- config->proxy_rules.proxy_for_https = proxy_server;
+ config->proxy_rules().proxy_for_https = proxy_server;
bool have_ftp = GetProxyFromGConf("/system/proxy/ftp_", false,
&proxy_server);
if (have_ftp)
- config->proxy_rules.proxy_for_ftp = proxy_server;
+ config->proxy_rules().proxy_for_ftp = proxy_server;
if (have_http || have_secure || have_ftp)
- config->proxy_rules.type =
+ config->proxy_rules().type =
ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
}
}
- if (config->proxy_rules.empty()) {
+ if (config->proxy_rules().empty()) {
// Manual mode but we couldn't parse any rules.
return false;
}
@@ -943,16 +1021,31 @@
}
// Now the bypass list.
- gconf_getter_->GetStringList("/system/http_proxy/ignore_hosts",
- &config->proxy_bypass);
+ std::vector<std::string> ignore_hosts_list;
+ config->proxy_rules().bypass_rules.Clear();
+ if (gconf_getter_->GetStringList("/system/http_proxy/ignore_hosts",
+ &ignore_hosts_list)) {
+ std::vector<std::string>::const_iterator it(ignore_hosts_list.begin());
+ for (; it != ignore_hosts_list.end(); ++it) {
+ if (gconf_getter_->MatchHostsUsingSuffixMatching()) {
+ config->proxy_rules().bypass_rules.
+ AddRuleFromStringUsingSuffixMatching(*it);
+ } else {
+ config->proxy_rules().bypass_rules.AddRuleFromString(*it);
+ }
+ }
+ }
// Note that there are no settings with semantics corresponding to
- // config->proxy_bypass_local_names.
+ // bypass of local names in GNOME. In KDE, "<local>" is supported
+ // as a hostname rule.
+
+ // KDE allows one to reverse the bypass rules.
+ config->proxy_rules().reverse_bypass = gconf_getter_->BypassListIsReversed();
return true;
}
-ProxyConfigServiceLinux::Delegate::Delegate(
- base::EnvironmentVariableGetter* env_var_getter)
+ProxyConfigServiceLinux::Delegate::Delegate(base::EnvVarGetter* env_var_getter)
: env_var_getter_(env_var_getter),
glib_default_loop_(NULL), io_loop_(NULL) {
// Figure out which GConfSettingGetterImpl to use, if any.
@@ -964,13 +1057,13 @@
case base::DESKTOP_ENVIRONMENT_KDE4:
gconf_getter_.reset(new GConfSettingGetterImplKDE(env_var_getter));
break;
+ case base::DESKTOP_ENVIRONMENT_XFCE:
case base::DESKTOP_ENVIRONMENT_OTHER:
break;
}
}
-ProxyConfigServiceLinux::Delegate::Delegate(
- base::EnvironmentVariableGetter* env_var_getter,
+ProxyConfigServiceLinux::Delegate::Delegate(base::EnvVarGetter* env_var_getter,
GConfSettingGetter* gconf_getter)
: env_var_getter_(env_var_getter), gconf_getter_(gconf_getter),
glib_default_loop_(NULL), io_loop_(NULL) {
@@ -998,7 +1091,7 @@
// the ProxyService.
// Note: It would be nice to prioritize environment variables
- // and only fallback to gconf if env vars were unset. But
+ // and only fall back to gconf if env vars were unset. But
// gnome-terminal "helpfully" sets http_proxy and no_proxy, and it
// does so even if the proxy mode is set to auto, which would
// mislead us.
@@ -1108,16 +1201,16 @@
}
ProxyConfigServiceLinux::ProxyConfigServiceLinux()
- : delegate_(new Delegate(base::EnvironmentVariableGetter::Create())) {
+ : delegate_(new Delegate(base::EnvVarGetter::Create())) {
}
ProxyConfigServiceLinux::ProxyConfigServiceLinux(
- base::EnvironmentVariableGetter* env_var_getter)
+ base::EnvVarGetter* env_var_getter)
: delegate_(new Delegate(env_var_getter)) {
}
ProxyConfigServiceLinux::ProxyConfigServiceLinux(
- base::EnvironmentVariableGetter* env_var_getter,
+ base::EnvVarGetter* env_var_getter,
GConfSettingGetter* gconf_getter)
: delegate_(new Delegate(env_var_getter, gconf_getter)) {
}
diff --git a/net/proxy/proxy_config_service_linux.h b/net/proxy/proxy_config_service_linux.h
index a55ba35..c6e9c90 100644
--- a/net/proxy/proxy_config_service_linux.h
+++ b/net/proxy/proxy_config_service_linux.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -9,7 +9,7 @@
#include <vector>
#include "base/basictypes.h"
-#include "base/linux_util.h"
+#include "base/env_var.h"
#include "base/message_loop.h"
#include "base/ref_counted.h"
#include "base/scoped_ptr.h"
@@ -76,6 +76,14 @@
virtual bool GetStringList(const char* key,
std::vector<std::string>* result) = 0;
+ // Returns true if the bypass list should be interpreted as a proxy
+ // whitelist rather than blacklist. (This is KDE-specific.)
+ virtual bool BypassListIsReversed() = 0;
+
+ // Returns true if the bypass rules should be interpreted as
+ // suffix-matching rules.
+ virtual bool MatchHostsUsingSuffixMatching() = 0;
+
private:
DISALLOW_COPY_AND_ASSIGN(GConfSettingGetter);
};
@@ -105,10 +113,10 @@
public:
// Constructor receives env var getter implementation to use, and
// takes ownership of it. This is the normal constructor.
- explicit Delegate(base::EnvironmentVariableGetter* env_var_getter);
+ explicit Delegate(base::EnvVarGetter* env_var_getter);
// Constructor receives gconf and env var getter implementations
// to use, and takes ownership of them. Used for testing.
- Delegate(base::EnvironmentVariableGetter* env_var_getter,
+ Delegate(base::EnvVarGetter* env_var_getter,
GConfSettingGetter* gconf_getter);
// Synchronously obtains the proxy configuration. If gconf is
// used, also enables gconf notification for setting
@@ -168,7 +176,7 @@
// carry the new config information.
void SetNewProxyConfig(const ProxyConfig& new_config);
- scoped_ptr<base::EnvironmentVariableGetter> env_var_getter_;
+ scoped_ptr<base::EnvVarGetter> env_var_getter_;
scoped_ptr<GConfSettingGetter> gconf_getter_;
// Cached proxy configuration, to be returned by
@@ -203,9 +211,8 @@
// Usual constructor
ProxyConfigServiceLinux();
// For testing: take alternate gconf and env var getter implementations.
- explicit ProxyConfigServiceLinux(
- base::EnvironmentVariableGetter* env_var_getter);
- ProxyConfigServiceLinux(base::EnvironmentVariableGetter* env_var_getter,
+ explicit ProxyConfigServiceLinux(base::EnvVarGetter* env_var_getter);
+ ProxyConfigServiceLinux(base::EnvVarGetter* env_var_getter,
GConfSettingGetter* gconf_getter);
virtual ~ProxyConfigServiceLinux() {
diff --git a/net/proxy/proxy_config_service_linux_unittest.cc b/net/proxy/proxy_config_service_linux_unittest.cc
index 4ebc12e..0a84548 100644
--- a/net/proxy/proxy_config_service_linux_unittest.cc
+++ b/net/proxy/proxy_config_service_linux_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -29,7 +29,8 @@
struct EnvVarValues {
// The strange capitalization is so that the field matches the
// environment variable name exactly.
- const char *DESKTOP_SESSION, *KDE_HOME,
+ const char *DESKTOP_SESSION, *HOME,
+ *KDEHOME, *KDE_SESSION_VERSION,
*auto_proxy, *all_proxy,
*http_proxy, *https_proxy, *ftp_proxy,
*SOCKS_SERVER, *SOCKS_VERSION,
@@ -77,12 +78,14 @@
map_type settings;
};
-class MockEnvironmentVariableGetter : public base::EnvironmentVariableGetter {
+class MockEnvVarGetter : public base::EnvVarGetter {
public:
- MockEnvironmentVariableGetter() {
+ MockEnvVarGetter() {
#define ENTRY(x) table.settings[#x] = &values.x
ENTRY(DESKTOP_SESSION);
- ENTRY(KDE_HOME);
+ ENTRY(HOME);
+ ENTRY(KDEHOME);
+ ENTRY(KDE_SESSION_VERSION);
ENTRY(auto_proxy);
ENTRY(all_proxy);
ENTRY(http_proxy);
@@ -101,7 +104,7 @@
values = zero_values;
}
- virtual bool Getenv(const char* variable_name, std::string* result) {
+ virtual bool GetEnv(const char* variable_name, std::string* result) {
const char* env_value = table.Get(variable_name);
if (env_value) {
// Note that the variable may be defined but empty.
@@ -111,6 +114,11 @@
return false;
}
+ virtual bool SetEnv(const char* variable_name, const std::string& new_value) {
+ NOTIMPLEMENTED();
+ return false;
+ }
+
// Intentionally public, for convenience when setting up a test.
EnvVarValues values;
@@ -210,6 +218,14 @@
return !result->empty();
}
+ virtual bool BypassListIsReversed() {
+ return false;
+ }
+
+ virtual bool MatchHostsUsingSuffixMatching() {
+ return false;
+ }
+
// Intentionally public, for convenience when setting up a test.
GConfValues values;
@@ -309,11 +325,7 @@
int get_config_result_; // Return value from GetProxyConfig().
};
-template <>
-struct RunnableMethodTraits<SynchConfigGetter> {
- void RetainCallee(SynchConfigGetter*) {}
- void ReleaseCallee(SynchConfigGetter*) {}
-};
+DISABLE_RUNNABLE_METHOD_REFCOUNT(SynchConfigGetter);
namespace net {
@@ -325,23 +337,34 @@
virtual void SetUp() {
PlatformTest::SetUp();
// Set up a temporary KDE home directory.
- std::string prefix("ProxyConfigServiceLinuxTest_kde_home");
- file_util::CreateNewTempDirectory(prefix, &kde_home_);
+ std::string prefix("ProxyConfigServiceLinuxTest_user_home");
+ file_util::CreateNewTempDirectory(prefix, &user_home_);
+ kde_home_ = user_home_.Append(FILE_PATH_LITERAL(".kde"));
FilePath path = kde_home_.Append(FILE_PATH_LITERAL("share"));
- file_util::CreateDirectory(path);
path = path.Append(FILE_PATH_LITERAL("config"));
file_util::CreateDirectory(path);
kioslaverc_ = path.Append(FILE_PATH_LITERAL("kioslaverc"));
+ // Set up paths but do not create the directory for .kde4.
+ kde4_home_ = user_home_.Append(FILE_PATH_LITERAL(".kde4"));
+ path = kde4_home_.Append(FILE_PATH_LITERAL("share"));
+ kde4_config_ = path.Append(FILE_PATH_LITERAL("config"));
+ kioslaverc4_ = kde4_config_.Append(FILE_PATH_LITERAL("kioslaverc"));
}
virtual void TearDown() {
// Delete the temporary KDE home directory.
- file_util::Delete(kde_home_, true);
+ file_util::Delete(user_home_, true);
PlatformTest::TearDown();
}
+ FilePath user_home_;
+ // KDE3 paths.
FilePath kde_home_;
FilePath kioslaverc_;
+ // KDE4 paths.
+ FilePath kde4_home_;
+ FilePath kde4_config_;
+ FilePath kioslaverc4_;
};
// Builds an identifier for each test in an array.
@@ -365,9 +388,7 @@
// Expected outputs (fields of the ProxyConfig).
bool auto_detect;
GURL pac_url;
- ProxyConfig::ProxyRules proxy_rules;
- const char* proxy_bypass_list; // newline separated
- bool bypass_local_names;
+ ProxyRulesExpectation proxy_rules;
} tests[] = {
{
TEST_DESC("No proxying"),
@@ -383,9 +404,7 @@
// Expected result.
false, // auto_detect
GURL(), // pac_url
- ProxyConfig::ProxyRules(), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::Empty(),
},
{
@@ -402,13 +421,11 @@
// Expected result.
true, // auto_detect
GURL(), // pac_url
- ProxyConfig::ProxyRules(), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::Empty(),
},
{
- TEST_DESC("Valid PAC url"),
+ TEST_DESC("Valid PAC URL"),
{ // Input.
"auto", // mode
"http://wpad/wpad.dat", // autoconfig_url
@@ -421,13 +438,11 @@
// Expected result.
false, // auto_detect
GURL("http://wpad/wpad.dat"), // pac_url
- ProxyConfig::ProxyRules(), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::Empty(),
},
{
- TEST_DESC("Invalid PAC url"),
+ TEST_DESC("Invalid PAC URL"),
{ // Input.
"auto", // mode
"wpad.dat", // autoconfig_url
@@ -440,9 +455,7 @@
// Expected result.
false, // auto_detect
GURL(), // pac_url
- ProxyConfig::ProxyRules(), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::Empty(),
},
{
@@ -459,9 +472,9 @@
// Expected result.
false, // auto_detect
GURL(), // pac_url
- MakeSingleProxyRules("www.google.com"), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::Single(
+ "www.google.com:80", // single proxy
+ ""), // bypass rules
},
{
@@ -478,9 +491,7 @@
// Expected result.
false, // auto_detect
GURL(), // pac_url
- ProxyConfig::ProxyRules(), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::Empty(),
},
{
@@ -497,10 +508,11 @@
// Expected result.
false, // auto_detect
GURL(), // pac_url
- MakeProxyPerSchemeRules("www.google.com", // proxy_rules
- "", ""),
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:80", // http
+ "", // https
+ "", // ftp
+ ""), // bypass rules
},
{
@@ -517,9 +529,9 @@
// Expected result.
false, // auto_detect
GURL(), // pac_url
- MakeSingleProxyRules("www.google.com:88"), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::Single(
+ "www.google.com:88", // single proxy
+ ""), // bypass rules
},
{
@@ -539,11 +551,11 @@
// Expected result.
false, // auto_detect
GURL(), // pac_url
- MakeProxyPerSchemeRules("www.google.com:88", // proxy_rules
- "www.foo.com:110",
- "ftp.foo.com:121"),
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:88", // http
+ "www.foo.com:110", // https
+ "ftp.foo.com:121", // ftp
+ ""), // bypass rules
},
{
@@ -560,9 +572,9 @@
// Expected result.
false, // auto_detect
GURL(), // pac_url
- MakeSingleProxyRules("socks4://socks.com:99"), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::Single(
+ "socks4://socks.com:99", // single proxy
+ "") // bypass rules
},
{
@@ -578,17 +590,16 @@
false, // auto_detect
GURL(), // pac_url
- MakeSingleProxyRules("www.google.com"), // proxy_rules
- "*.google.com\n", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::Single(
+ "www.google.com:80", // single proxy
+ "*.google.com"), // bypass rules
},
};
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
SCOPED_TRACE(StringPrintf("Test[%" PRIuS "] %s", i,
tests[i].description.c_str()));
- MockEnvironmentVariableGetter* env_getter =
- new MockEnvironmentVariableGetter;
+ MockEnvVarGetter* env_getter = new MockEnvVarGetter;
MockGConfSettingGetter* gconf_getter = new MockGConfSettingGetter;
SynchConfigGetter sync_config_getter(
new ProxyConfigServiceLinux(env_getter, gconf_getter));
@@ -597,12 +608,9 @@
sync_config_getter.SetupAndInitialFetch();
sync_config_getter.SyncGetProxyConfig(&config);
- EXPECT_EQ(tests[i].auto_detect, config.auto_detect);
- EXPECT_EQ(tests[i].pac_url, config.pac_url);
- EXPECT_EQ(tests[i].proxy_bypass_list,
- FlattenProxyBypass(config.proxy_bypass));
- EXPECT_EQ(tests[i].bypass_local_names, config.proxy_bypass_local_names);
- EXPECT_EQ(tests[i].proxy_rules, config.proxy_rules);
+ EXPECT_EQ(tests[i].auto_detect, config.auto_detect());
+ EXPECT_EQ(tests[i].pac_url, config.pac_url());
+ EXPECT_TRUE(tests[i].proxy_rules.Matches(config.proxy_rules()));
}
}
@@ -618,15 +626,15 @@
// Expected outputs (fields of the ProxyConfig).
bool auto_detect;
GURL pac_url;
- ProxyConfig::ProxyRules proxy_rules;
- const char* proxy_bypass_list; // newline separated
- bool bypass_local_names;
+ ProxyRulesExpectation proxy_rules;
} tests[] = {
{
TEST_DESC("No proxying"),
{ // Input.
NULL, // DESKTOP_SESSION
- NULL, // KDE_HOME
+ NULL, // HOME
+ NULL, // KDEHOME
+ NULL, // KDE_SESSION_VERSION
NULL, // auto_proxy
NULL, // all_proxy
NULL, NULL, NULL, // per-proto proxies
@@ -637,16 +645,16 @@
// Expected result.
false, // auto_detect
GURL(), // pac_url
- ProxyConfig::ProxyRules(), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::Empty(),
},
{
TEST_DESC("Auto detect"),
{ // Input.
NULL, // DESKTOP_SESSION
- NULL, // KDE_HOME
+ NULL, // HOME
+ NULL, // KDEHOME
+ NULL, // KDE_SESSION_VERSION
"", // auto_proxy
NULL, // all_proxy
NULL, NULL, NULL, // per-proto proxies
@@ -657,16 +665,16 @@
// Expected result.
true, // auto_detect
GURL(), // pac_url
- ProxyConfig::ProxyRules(), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::Empty(),
},
{
- TEST_DESC("Valid PAC url"),
+ TEST_DESC("Valid PAC URL"),
{ // Input.
NULL, // DESKTOP_SESSION
- NULL, // KDE_HOME
+ NULL, // HOME
+ NULL, // KDEHOME
+ NULL, // KDE_SESSION_VERSION
"http://wpad/wpad.dat", // auto_proxy
NULL, // all_proxy
NULL, NULL, NULL, // per-proto proxies
@@ -677,16 +685,16 @@
// Expected result.
false, // auto_detect
GURL("http://wpad/wpad.dat"), // pac_url
- ProxyConfig::ProxyRules(), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::Empty(),
},
{
- TEST_DESC("Invalid PAC url"),
+ TEST_DESC("Invalid PAC URL"),
{ // Input.
NULL, // DESKTOP_SESSION
- NULL, // KDE_HOME
+ NULL, // HOME
+ NULL, // KDEHOME
+ NULL, // KDE_SESSION_VERSION
"wpad.dat", // auto_proxy
NULL, // all_proxy
NULL, NULL, NULL, // per-proto proxies
@@ -697,16 +705,16 @@
// Expected result.
false, // auto_detect
GURL(), // pac_url
- ProxyConfig::ProxyRules(), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::Empty(),
},
{
TEST_DESC("Single-host in proxy list"),
{ // Input.
NULL, // DESKTOP_SESSION
- NULL, // KDE_HOME
+ NULL, // HOME
+ NULL, // KDEHOME
+ NULL, // KDE_SESSION_VERSION
NULL, // auto_proxy
"www.google.com", // all_proxy
NULL, NULL, NULL, // per-proto proxies
@@ -717,16 +725,18 @@
// Expected result.
false, // auto_detect
GURL(), // pac_url
- MakeSingleProxyRules("www.google.com"), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::Single(
+ "www.google.com:80", // single proxy
+ ""), // bypass rules
},
{
TEST_DESC("Single-host, different port"),
{ // Input.
NULL, // DESKTOP_SESSION
- NULL, // KDE_HOME
+ NULL, // HOME
+ NULL, // KDEHOME
+ NULL, // KDE_SESSION_VERSION
NULL, // auto_proxy
"www.google.com:99", // all_proxy
NULL, NULL, NULL, // per-proto proxies
@@ -737,16 +747,18 @@
// Expected result.
false, // auto_detect
GURL(), // pac_url
- MakeSingleProxyRules("www.google.com:99"), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::Single(
+ "www.google.com:99", // single
+ ""), // bypass rules
},
{
TEST_DESC("Tolerate a scheme"),
{ // Input.
NULL, // DESKTOP_SESSION
- NULL, // KDE_HOME
+ NULL, // HOME
+ NULL, // KDEHOME
+ NULL, // KDE_SESSION_VERSION
NULL, // auto_proxy
"http://www.google.com:99", // all_proxy
NULL, NULL, NULL, // per-proto proxies
@@ -757,16 +769,18 @@
// Expected result.
false, // auto_detect
GURL(), // pac_url
- MakeSingleProxyRules("www.google.com:99"), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::Single(
+ "www.google.com:99", // single proxy
+ ""), // bypass rules
},
{
TEST_DESC("Per-scheme proxy rules"),
{ // Input.
NULL, // DESKTOP_SESSION
- NULL, // KDE_HOME
+ NULL, // HOME
+ NULL, // KDEHOME
+ NULL, // KDE_SESSION_VERSION
NULL, // auto_proxy
NULL, // all_proxy
"www.google.com:80", "www.foo.com:110", "ftp.foo.com:121", // per-proto
@@ -777,17 +791,20 @@
// Expected result.
false, // auto_detect
GURL(), // pac_url
- MakeProxyPerSchemeRules("www.google.com", "www.foo.com:110",
- "ftp.foo.com:121"),
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:80", // http
+ "www.foo.com:110", // https
+ "ftp.foo.com:121", // ftp
+ ""), // bypass rules
},
{
TEST_DESC("socks"),
{ // Input.
NULL, // DESKTOP_SESSION
- NULL, // KDE_HOME
+ NULL, // HOME
+ NULL, // KDEHOME
+ NULL, // KDE_SESSION_VERSION
NULL, // auto_proxy
"", // all_proxy
NULL, NULL, NULL, // per-proto proxies
@@ -798,16 +815,18 @@
// Expected result.
false, // auto_detect
GURL(), // pac_url
- MakeSingleProxyRules("socks4://socks.com:888"), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::Single(
+ "socks4://socks.com:888", // single proxy
+ ""), // bypass rules
},
{
TEST_DESC("socks5"),
{ // Input.
NULL, // DESKTOP_SESSION
- NULL, // KDE_HOME
+ NULL, // HOME
+ NULL, // KDEHOME
+ NULL, // KDE_SESSION_VERSION
NULL, // auto_proxy
"", // all_proxy
NULL, NULL, NULL, // per-proto proxies
@@ -818,16 +837,18 @@
// Expected result.
false, // auto_detect
GURL(), // pac_url
- MakeSingleProxyRules("socks5://socks.com:888"), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::Single(
+ "socks5://socks.com:888", // single proxy
+ ""), // bypass rules
},
{
TEST_DESC("socks default port"),
{ // Input.
NULL, // DESKTOP_SESSION
- NULL, // KDE_HOME
+ NULL, // HOME
+ NULL, // KDEHOME
+ NULL, // KDE_SESSION_VERSION
NULL, // auto_proxy
"", // all_proxy
NULL, NULL, NULL, // per-proto proxies
@@ -838,16 +859,18 @@
// Expected result.
false, // auto_detect
GURL(), // pac_url
- MakeSingleProxyRules("socks4://socks.com"), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::Single(
+ "socks4://socks.com:1080", // single proxy
+ ""), // bypass rules
},
{
TEST_DESC("bypass"),
{ // Input.
NULL, // DESKTOP_SESSION
- NULL, // KDE_HOME
+ NULL, // HOME
+ NULL, // KDEHOME
+ NULL, // KDE_SESSION_VERSION
NULL, // auto_proxy
"www.google.com", // all_proxy
NULL, NULL, NULL, // per-proto
@@ -857,18 +880,16 @@
false, // auto_detect
GURL(), // pac_url
- MakeSingleProxyRules("www.google.com"), // proxy_rules
- // proxy_bypass_list
- "*.google.com\n*foo.com:99\n1.2.3.4:22\n127.0.0.1/8\n",
- false, // bypass_local_names
+ ProxyRulesExpectation::Single(
+ "www.google.com:80",
+ "*.google.com,*foo.com:99,1.2.3.4:22,127.0.0.1/8"),
},
};
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
SCOPED_TRACE(StringPrintf("Test[%" PRIuS "] %s", i,
tests[i].description.c_str()));
- MockEnvironmentVariableGetter* env_getter =
- new MockEnvironmentVariableGetter;
+ MockEnvVarGetter* env_getter = new MockEnvVarGetter;
MockGConfSettingGetter* gconf_getter = new MockGConfSettingGetter;
SynchConfigGetter sync_config_getter(
new ProxyConfigServiceLinux(env_getter, gconf_getter));
@@ -877,18 +898,14 @@
sync_config_getter.SetupAndInitialFetch();
sync_config_getter.SyncGetProxyConfig(&config);
- EXPECT_EQ(tests[i].auto_detect, config.auto_detect);
- EXPECT_EQ(tests[i].pac_url, config.pac_url);
- EXPECT_EQ(tests[i].proxy_bypass_list,
- FlattenProxyBypass(config.proxy_bypass));
- EXPECT_EQ(tests[i].bypass_local_names, config.proxy_bypass_local_names);
- EXPECT_EQ(tests[i].proxy_rules, config.proxy_rules);
+ EXPECT_EQ(tests[i].auto_detect, config.auto_detect());
+ EXPECT_EQ(tests[i].pac_url, config.pac_url());
+ EXPECT_TRUE(tests[i].proxy_rules.Matches(config.proxy_rules()));
}
}
TEST_F(ProxyConfigServiceLinuxTest, GconfNotification) {
- MockEnvironmentVariableGetter* env_getter =
- new MockEnvironmentVariableGetter;
+ MockEnvVarGetter* env_getter = new MockEnvVarGetter;
MockGConfSettingGetter* gconf_getter = new MockGConfSettingGetter;
ProxyConfigServiceLinux* service =
new ProxyConfigServiceLinux(env_getter, gconf_getter);
@@ -899,14 +916,14 @@
gconf_getter->values.mode = "none";
sync_config_getter.SetupAndInitialFetch();
sync_config_getter.SyncGetProxyConfig(&config);
- EXPECT_FALSE(config.auto_detect);
+ EXPECT_FALSE(config.auto_detect());
// Now set to auto-detect.
gconf_getter->values.mode = "auto";
// Simulate gconf notification callback.
service->OnCheckProxyConfigSettings();
sync_config_getter.SyncGetProxyConfig(&config);
- EXPECT_TRUE(config.auto_detect);
+ EXPECT_TRUE(config.auto_detect());
}
TEST_F(ProxyConfigServiceLinuxTest, KDEConfigParser) {
@@ -924,26 +941,24 @@
// Input.
std::string kioslaverc;
+ EnvVarValues env_values;
// Expected outputs (fields of the ProxyConfig).
bool auto_detect;
GURL pac_url;
- ProxyConfig::ProxyRules proxy_rules;
- const char* proxy_bypass_list; // newline separated
- bool bypass_local_names;
+ ProxyRulesExpectation proxy_rules;
} tests[] = {
{
TEST_DESC("No proxying"),
// Input.
"[Proxy Settings]\nProxyType=0\n",
+ {}, // env_values
// Expected result.
false, // auto_detect
GURL(), // pac_url
- ProxyConfig::ProxyRules(), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::Empty(),
},
{
@@ -951,28 +966,26 @@
// Input.
"[Proxy Settings]\nProxyType=3\n",
+ {}, // env_values
// Expected result.
true, // auto_detect
GURL(), // pac_url
- ProxyConfig::ProxyRules(), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::Empty(),
},
{
- TEST_DESC("Valid PAC url"),
+ TEST_DESC("Valid PAC URL"),
// Input.
"[Proxy Settings]\nProxyType=2\n"
"Proxy Config Script=http://wpad/wpad.dat\n",
+ {}, // env_values
// Expected result.
false, // auto_detect
GURL("http://wpad/wpad.dat"), // pac_url
- ProxyConfig::ProxyRules(), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::Empty(),
},
{
@@ -981,15 +994,16 @@
// Input.
"[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n"
"httpsProxy=www.foo.com\nftpProxy=ftp.foo.com\n",
+ {}, // env_values
// Expected result.
false, // auto_detect
GURL(), // pac_url
- MakeProxyPerSchemeRules("www.google.com",
- "www.foo.com",
- "ftp.foo.com"), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:80", // http
+ "www.foo.com:80", // https
+ "ftp.foo.com:80", // http
+ ""), // bypass rules
},
{
@@ -998,14 +1012,16 @@
// Input.
"[Proxy Settings]\nProxyType=1\n"
"httpProxy=www.google.com\n",
+ {}, // env_values
// Expected result.
false, // auto_detect
GURL(), // pac_url
- MakeProxyPerSchemeRules("www.google.com",
- "", ""), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:80", // http
+ "", // https
+ "", // ftp
+ ""), // bypass rules
},
{
@@ -1014,14 +1030,16 @@
// Input.
"[Proxy Settings]\nProxyType=1\n"
"httpProxy=www.google.com:88\n",
+ {}, // env_values
// Expected result.
false, // auto_detect
GURL(), // pac_url
- MakeProxyPerSchemeRules("www.google.com:88",
- "", ""), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:88", // http
+ "", // https
+ "", // ftp
+ ""), // bypass rules
},
{
@@ -1029,14 +1047,16 @@
// Input.
"[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n"
- "NoProxyFor=*.google.com\n",
+ "NoProxyFor=.google.com\n",
+ {}, // env_values
false, // auto_detect
GURL(), // pac_url
- MakeProxyPerSchemeRules("www.google.com",
- "", ""), // proxy_rules
- "*.google.com\n", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:80", // http
+ "", // https
+ "", // ftp
+ "*.google.com"), // bypass rules
},
{
@@ -1044,29 +1064,50 @@
// Input.
"[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n"
- "NoProxyFor=*.google.com,*.kde.org\n",
+ "NoProxyFor=.google.com,.kde.org\n",
+ {}, // env_values
false, // auto_detect
GURL(), // pac_url
- MakeProxyPerSchemeRules("www.google.com",
- "", ""), // proxy_rules
- "*.google.com\n*.kde.org\n", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:80", // http
+ "", // https
+ "", // ftp
+ "*.google.com,*.kde.org"), // bypass rules
},
{
- TEST_DESC("Ignore bypass list with ReversedException"),
+ TEST_DESC("Correctly parse bypass list with ReversedException"),
// Input.
"[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n"
- "NoProxyFor=*.google.com\nReversedException=true\n",
+ "NoProxyFor=.google.com\nReversedException=true\n",
+ {}, // env_values
false, // auto_detect
GURL(), // pac_url
- MakeProxyPerSchemeRules("www.google.com",
- "", ""), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::PerSchemeWithBypassReversed(
+ "www.google.com:80", // http
+ "", // https
+ "", // ftp
+ "*.google.com"), // bypass rules
+ },
+
+ {
+ TEST_DESC("Treat all hostname patterns as wildcard patterns"),
+
+ // Input.
+ "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n"
+ "NoProxyFor=google.com,kde.org,<local>\n",
+ {}, // env_values
+
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:80", // http
+ "", // https
+ "", // ftp
+ "*google.com,*kde.org,<local>"), // bypass rules
},
{
@@ -1074,14 +1115,16 @@
// Input.
"[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n"
- "NoProxyFor=*.google.com\nReversedException=true \n",
+ "NoProxyFor=.google.com\nReversedException=true \n",
+ {}, // env_values
false, // auto_detect
GURL(), // pac_url
- MakeProxyPerSchemeRules("www.google.com",
- "", ""), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::PerSchemeWithBypassReversed(
+ "www.google.com:80", // http
+ "", // https
+ "", // ftp
+ "*.google.com"), // bypass rules
},
{
@@ -1090,13 +1133,15 @@
// Input.
"httpsProxy=www.foo.com\n[Proxy Settings]\nProxyType=1\n"
"httpProxy=www.google.com\n[Other Section]\nftpProxy=ftp.foo.com\n",
+ {}, // env_values
false, // auto_detect
GURL(), // pac_url
- MakeProxyPerSchemeRules("www.google.com",
- "", ""), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:80", // http
+ "", // https
+ "", // ftp
+ ""), // bypass rules
},
{
@@ -1104,13 +1149,15 @@
// Input.
"[Proxy Settings]\r\nProxyType=1\r\nhttpProxy=www.google.com\r\n",
+ {}, // env_values
false, // auto_detect
GURL(), // pac_url
- MakeProxyPerSchemeRules("www.google.com",
- "", ""), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:80", // http
+ "", // https
+ "", // ftp
+ ""), // bypass rules
},
{
@@ -1118,13 +1165,15 @@
// Input.
"[Proxy Settings]\r\n\nProxyType=1\n\r\nhttpProxy=www.google.com\n\n",
+ {}, // env_values
false, // auto_detect
GURL(), // pac_url
- MakeProxyPerSchemeRules("www.google.com",
- "", ""), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:80", // http
+ "", // https
+ "", // ftp
+ ""), // bypass rules
},
{
@@ -1132,13 +1181,15 @@
// Input.
"[Proxy Settings]\nProxyType[$e]=1\nhttpProxy[$e]=www.google.com\n",
+ {}, // env_values
false, // auto_detect
GURL(), // pac_url
- MakeProxyPerSchemeRules("www.google.com",
- "", ""), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:80", // http
+ "", // https
+ "", // ftp
+ ""), // bypass rules
},
{
@@ -1147,13 +1198,15 @@
// Input.
"[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n"
"httpsProxy$e]=www.foo.com\nftpProxy=ftp.foo.com\n",
+ {}, // env_values
false, // auto_detect
GURL(), // pac_url
- MakeProxyPerSchemeRules("www.google.com",
- "", "ftp.foo.com"), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:80", // http
+ "", // https
+ "ftp.foo.com:80", // ftp
+ ""), // bypass rules
},
{
@@ -1162,12 +1215,11 @@
// Input.
"[Proxy Settings]\nProxyType [$e] =2\n"
" Proxy Config Script = http:// foo\n",
+ {}, // env_values
false, // auto_detect
GURL("http:// foo"), // pac_url
- ProxyConfig::ProxyRules(), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::Empty(),
},
{
@@ -1176,24 +1228,69 @@
// Input.
std::string("[Proxy Settings]\nProxyType=1\nftpProxy=ftp.foo.com\n") +
long_line + "httpsProxy=www.foo.com\nhttpProxy=www.google.com\n",
+ {}, // env_values
false, // auto_detect
GURL(), // pac_url
- MakeProxyPerSchemeRules("www.google.com",
- "", "ftp.foo.com"), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:80", // http
+ "", // https
+ "ftp.foo.com:80", // ftp
+ ""), // bypass rules
},
+
+ {
+ TEST_DESC("Indirect Proxy - no env vars set"),
+
+ // Input.
+ "[Proxy Settings]\nProxyType=4\nhttpProxy=http_proxy\n"
+ "httpsProxy=https_proxy\nftpProxy=ftp_proxy\nNoProxyFor=no_proxy\n",
+ {}, // env_values
+
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::Empty(),
+ },
+
+ {
+ TEST_DESC("Indirect Proxy - with env vars set"),
+
+ // Input.
+ "[Proxy Settings]\nProxyType=4\nhttpProxy=http_proxy\n"
+ "httpsProxy=https_proxy\nftpProxy=ftp_proxy\nNoProxyFor=no_proxy\n",
+ { // env_values
+ NULL, // DESKTOP_SESSION
+ NULL, // HOME
+ NULL, // KDEHOME
+ NULL, // KDE_SESSION_VERSION
+ NULL, // auto_proxy
+ NULL, // all_proxy
+ "www.normal.com", // http_proxy
+ "www.secure.com", // https_proxy
+ "ftp.foo.com", // ftp_proxy
+ NULL, NULL, // SOCKS
+ ".google.com, .kde.org", // no_proxy
+ },
+
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::PerScheme(
+ "www.normal.com:80", // http
+ "www.secure.com:80", // https
+ "ftp.foo.com:80", // ftp
+ "*.google.com,*.kde.org"), // bypass rules
+ },
+
};
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
SCOPED_TRACE(StringPrintf("Test[%" PRIuS "] %s", i,
tests[i].description.c_str()));
- MockEnvironmentVariableGetter* env_getter =
- new MockEnvironmentVariableGetter;
+ MockEnvVarGetter* env_getter = new MockEnvVarGetter;
+ env_getter->values = tests[i].env_values;
// Force the KDE getter to be used and tell it where the test is.
env_getter->values.DESKTOP_SESSION = "kde4";
- env_getter->values.KDE_HOME = kde_home_.value().c_str();
+ env_getter->values.KDEHOME = kde_home_.value().c_str();
SynchConfigGetter sync_config_getter(
new ProxyConfigServiceLinux(env_getter));
ProxyConfig config;
@@ -1203,12 +1300,101 @@
sync_config_getter.SetupAndInitialFetch();
sync_config_getter.SyncGetProxyConfig(&config);
- EXPECT_EQ(tests[i].auto_detect, config.auto_detect);
- EXPECT_EQ(tests[i].pac_url, config.pac_url);
- EXPECT_EQ(tests[i].proxy_bypass_list,
- FlattenProxyBypass(config.proxy_bypass));
- EXPECT_EQ(tests[i].bypass_local_names, config.proxy_bypass_local_names);
- EXPECT_EQ(tests[i].proxy_rules, config.proxy_rules);
+ EXPECT_EQ(tests[i].auto_detect, config.auto_detect());
+ EXPECT_EQ(tests[i].pac_url, config.pac_url());
+ EXPECT_TRUE(tests[i].proxy_rules.Matches(config.proxy_rules()));
+ }
+}
+
+TEST_F(ProxyConfigServiceLinuxTest, KDEHomePicker) {
+ // Auto detect proxy settings.
+ std::string slaverc3 = "[Proxy Settings]\nProxyType=3\n";
+ // Valid PAC URL.
+ std::string slaverc4 = "[Proxy Settings]\nProxyType=2\n"
+ "Proxy Config Script=http://wpad/wpad.dat\n";
+ GURL slaverc4_pac_url("http://wpad/wpad.dat");
+
+ // Overwrite the .kde kioslaverc file.
+ file_util::WriteFile(kioslaverc_, slaverc3.c_str(), slaverc3.length());
+
+ // If .kde4 exists it will mess up the first test. It should not, as
+ // we created the directory for $HOME in the test setup.
+ CHECK(!file_util::DirectoryExists(kde4_home_));
+
+ { SCOPED_TRACE("KDE4, no .kde4 directory, verify fallback");
+ MockEnvVarGetter* env_getter = new MockEnvVarGetter;
+ env_getter->values.DESKTOP_SESSION = "kde4";
+ env_getter->values.HOME = user_home_.value().c_str();
+ SynchConfigGetter sync_config_getter(
+ new ProxyConfigServiceLinux(env_getter));
+ ProxyConfig config;
+ sync_config_getter.SetupAndInitialFetch();
+ sync_config_getter.SyncGetProxyConfig(&config);
+ EXPECT_TRUE(config.auto_detect());
+ EXPECT_EQ(GURL(), config.pac_url());
+ }
+
+ // Now create .kde4 and put a kioslaverc in the config directory.
+ // Note that its timestamp will be at least as new as the .kde one.
+ file_util::CreateDirectory(kde4_config_);
+ file_util::WriteFile(kioslaverc4_, slaverc4.c_str(), slaverc4.length());
+ CHECK(file_util::PathExists(kioslaverc4_));
+
+ { SCOPED_TRACE("KDE4, .kde4 directory present, use it");
+ MockEnvVarGetter* env_getter = new MockEnvVarGetter;
+ env_getter->values.DESKTOP_SESSION = "kde4";
+ env_getter->values.HOME = user_home_.value().c_str();
+ SynchConfigGetter sync_config_getter(
+ new ProxyConfigServiceLinux(env_getter));
+ ProxyConfig config;
+ sync_config_getter.SetupAndInitialFetch();
+ sync_config_getter.SyncGetProxyConfig(&config);
+ EXPECT_FALSE(config.auto_detect());
+ EXPECT_EQ(slaverc4_pac_url, config.pac_url());
+ }
+
+ { SCOPED_TRACE("KDE3, .kde4 directory present, ignore it");
+ MockEnvVarGetter* env_getter = new MockEnvVarGetter;
+ env_getter->values.DESKTOP_SESSION = "kde";
+ env_getter->values.HOME = user_home_.value().c_str();
+ SynchConfigGetter sync_config_getter(
+ new ProxyConfigServiceLinux(env_getter));
+ ProxyConfig config;
+ sync_config_getter.SetupAndInitialFetch();
+ sync_config_getter.SyncGetProxyConfig(&config);
+ EXPECT_TRUE(config.auto_detect());
+ EXPECT_EQ(GURL(), config.pac_url());
+ }
+
+ { SCOPED_TRACE("KDE4, .kde4 directory present, KDEHOME set to .kde");
+ MockEnvVarGetter* env_getter = new MockEnvVarGetter;
+ env_getter->values.DESKTOP_SESSION = "kde4";
+ env_getter->values.HOME = user_home_.value().c_str();
+ env_getter->values.KDEHOME = kde_home_.value().c_str();
+ SynchConfigGetter sync_config_getter(
+ new ProxyConfigServiceLinux(env_getter));
+ ProxyConfig config;
+ sync_config_getter.SetupAndInitialFetch();
+ sync_config_getter.SyncGetProxyConfig(&config);
+ EXPECT_TRUE(config.auto_detect());
+ EXPECT_EQ(GURL(), config.pac_url());
+ }
+
+ // Finally, make the .kde4 config directory older than the .kde directory
+ // and make sure we then use .kde instead of .kde4 since it's newer.
+ file_util::SetLastModifiedTime(kde4_config_, base::Time());
+
+ { SCOPED_TRACE("KDE4, very old .kde4 directory present, use .kde");
+ MockEnvVarGetter* env_getter = new MockEnvVarGetter;
+ env_getter->values.DESKTOP_SESSION = "kde4";
+ env_getter->values.HOME = user_home_.value().c_str();
+ SynchConfigGetter sync_config_getter(
+ new ProxyConfigServiceLinux(env_getter));
+ ProxyConfig config;
+ sync_config_getter.SetupAndInitialFetch();
+ sync_config_getter.SyncGetProxyConfig(&config);
+ EXPECT_TRUE(config.auto_detect());
+ EXPECT_EQ(GURL(), config.pac_url());
}
}
diff --git a/net/proxy/proxy_config_service_mac.cc b/net/proxy/proxy_config_service_mac.cc
index 3359cc3..1e04ff0 100644
--- a/net/proxy/proxy_config_service_mac.cc
+++ b/net/proxy/proxy_config_service_mac.cc
@@ -49,10 +49,10 @@
// There appears to be no UI for this configuration option, and we're not sure
// if Apple's proxy code even takes it into account. But the constant is in
// the header file so we'll use it.
- config->auto_detect =
+ config->set_auto_detect(
GetBoolFromDictionary(config_dict.get(),
kSCPropNetProxiesProxyAutoDiscoveryEnable,
- false);
+ false));
// PAC file
@@ -64,7 +64,7 @@
kSCPropNetProxiesProxyAutoConfigURLString,
CFStringGetTypeID());
if (pac_url_ref)
- config->pac_url = GURL(base::SysCFStringRefToUTF8(pac_url_ref));
+ config->set_pac_url(GURL(base::SysCFStringRefToUTF8(pac_url_ref)));
}
// proxies (for now ftp, http, https, and SOCKS)
@@ -78,8 +78,9 @@
kSCPropNetProxiesFTPProxy,
kSCPropNetProxiesFTPPort);
if (proxy_server.is_valid()) {
- config->proxy_rules.type = ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
- config->proxy_rules.proxy_for_ftp = proxy_server;
+ config->proxy_rules().type =
+ ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
+ config->proxy_rules().proxy_for_ftp = proxy_server;
}
}
if (GetBoolFromDictionary(config_dict.get(),
@@ -91,8 +92,9 @@
kSCPropNetProxiesHTTPProxy,
kSCPropNetProxiesHTTPPort);
if (proxy_server.is_valid()) {
- config->proxy_rules.type = ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
- config->proxy_rules.proxy_for_http = proxy_server;
+ config->proxy_rules().type =
+ ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
+ config->proxy_rules().proxy_for_http = proxy_server;
}
}
if (GetBoolFromDictionary(config_dict.get(),
@@ -104,8 +106,9 @@
kSCPropNetProxiesHTTPSProxy,
kSCPropNetProxiesHTTPSPort);
if (proxy_server.is_valid()) {
- config->proxy_rules.type = ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
- config->proxy_rules.proxy_for_https = proxy_server;
+ config->proxy_rules().type =
+ ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
+ config->proxy_rules().proxy_for_https = proxy_server;
}
}
if (GetBoolFromDictionary(config_dict.get(),
@@ -117,8 +120,9 @@
kSCPropNetProxiesSOCKSProxy,
kSCPropNetProxiesSOCKSPort);
if (proxy_server.is_valid()) {
- config->proxy_rules.type = ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
- config->proxy_rules.socks_proxy = proxy_server;
+ config->proxy_rules().type =
+ ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
+ config->proxy_rules().socks_proxy = proxy_server;
}
}
@@ -140,7 +144,7 @@
" to be a CFStringRef but it was not";
} else {
- config->proxy_bypass.push_back(
+ config->proxy_rules().bypass_rules.AddRuleFromString(
base::SysCFStringRefToUTF8(bypass_item_ref));
}
}
@@ -148,10 +152,11 @@
// proxy bypass boolean
- config->proxy_bypass_local_names =
- GetBoolFromDictionary(config_dict.get(),
+ if (GetBoolFromDictionary(config_dict.get(),
kSCPropNetProxiesExcludeSimpleHostnames,
- false);
+ false)) {
+ config->proxy_rules().bypass_rules.AddRuleToBypassLocal();
+ }
return OK;
}
diff --git a/net/proxy/proxy_config_service_win.cc b/net/proxy/proxy_config_service_win.cc
index 30c4adb..38819de 100644
--- a/net/proxy/proxy_config_service_win.cc
+++ b/net/proxy/proxy_config_service_win.cc
@@ -43,11 +43,11 @@
ProxyConfig* config,
const WINHTTP_CURRENT_USER_IE_PROXY_CONFIG& ie_config) {
if (ie_config.fAutoDetect)
- config->auto_detect = true;
+ config->set_auto_detect(true);
if (ie_config.lpszProxy) {
// lpszProxy may be a single proxy, or a proxy per scheme. The format
// is compatible with ProxyConfig::ProxyRules's string format.
- config->proxy_rules.ParseFromString(WideToASCII(ie_config.lpszProxy));
+ config->proxy_rules().ParseFromString(WideToASCII(ie_config.lpszProxy));
}
if (ie_config.lpszProxyBypass) {
std::string proxy_bypass = WideToASCII(ie_config.lpszProxyBypass);
@@ -55,14 +55,11 @@
StringTokenizer proxy_server_bypass_list(proxy_bypass, "; \t\n\r");
while (proxy_server_bypass_list.GetNext()) {
std::string bypass_url_domain = proxy_server_bypass_list.token();
- if (bypass_url_domain == "<local>")
- config->proxy_bypass_local_names = true;
- else
- config->proxy_bypass.push_back(bypass_url_domain);
+ config->proxy_rules().bypass_rules.AddRuleFromString(bypass_url_domain);
}
}
if (ie_config.lpszAutoConfigUrl)
- config->pac_url = GURL(ie_config.lpszAutoConfigUrl);
+ config->set_pac_url(GURL(ie_config.lpszAutoConfigUrl));
}
} // namespace net
diff --git a/net/proxy/proxy_config_service_win_unittest.cc b/net/proxy/proxy_config_service_win_unittest.cc
index 2b0ff66..1d4f45d 100644
--- a/net/proxy/proxy_config_service_win_unittest.cc
+++ b/net/proxy/proxy_config_service_win_unittest.cc
@@ -19,9 +19,8 @@
// Expected outputs (fields of the ProxyConfig).
bool auto_detect;
GURL pac_url;
- ProxyConfig::ProxyRules proxy_rules;
+ ProxyRulesExpectation proxy_rules;
const char* proxy_bypass_list; // newline separated
- bool bypass_local_names;
} tests[] = {
// Auto detect.
{
@@ -35,9 +34,7 @@
// Expected result.
true, // auto_detect
GURL(), // pac_url
- ProxyConfig::ProxyRules(), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::Empty(),
},
// Valid PAC url
@@ -52,9 +49,7 @@
// Expected result.
false, // auto_detect
GURL("http://wpad/wpad.dat"), // pac_url
- ProxyConfig::ProxyRules(), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::Empty(),
},
// Invalid PAC url string.
@@ -69,9 +64,7 @@
// Expected result.
false, // auto_detect
GURL(), // pac_url
- ProxyConfig::ProxyRules(), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::Empty(),
},
// Single-host in proxy list.
@@ -86,9 +79,9 @@
// Expected result.
false, // auto_detect
GURL(), // pac_url
- MakeSingleProxyRules("www.google.com"), // proxy_rules
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::Single(
+ "www.google.com:80", // single proxy
+ ""), // bypass rules
},
// Per-scheme proxy rules.
@@ -103,9 +96,11 @@
// Expected result.
false, // auto_detect
GURL(), // pac_url
- MakeProxyPerSchemeRules("www.google.com:80", "www.foo.com:110", ""),
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:80", // http
+ "www.foo.com:110", // https
+ "", // ftp
+ ""), // bypass rules
},
// SOCKS proxy configuration
@@ -121,10 +116,12 @@
// Expected result.
false, // auto_detect
GURL(), // pac_url
- MakeProxyPerSchemeRules("www.google.com:80", "www.foo.com:110",
- "ftpproxy:20", "foopy:130"),
- "", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::PerSchemeWithSocks(
+ "www.google.com:80", // http
+ "www.foo.com:110", // https
+ "ftpproxy:20", // ftp
+ "socks4://foopy:130", // socks
+ ""), // bypass rules
},
// Bypass local names.
@@ -138,9 +135,7 @@
true, // auto_detect
GURL(), // pac_url
- ProxyConfig::ProxyRules(), // proxy_rules
- "", // proxy_bypass_list
- true, // bypass_local_names
+ ProxyRulesExpectation::EmptyWithBypass("<local>"),
},
// Bypass "google.com" and local names, using semicolon as delimeter
@@ -156,9 +151,7 @@
// Expected result.
true, // auto_detect
GURL(), // pac_url
- ProxyConfig::ProxyRules(), // proxy_rules
- "google.com\n", // proxy_bypass_list
- true, // bypass_local_names
+ ProxyRulesExpectation::EmptyWithBypass("<local>,google.com"),
},
// Bypass "foo.com" and "google.com", using lines as delimeter.
@@ -173,9 +166,7 @@
// Expected result.
true, // auto_detect
GURL(), // pac_url
- ProxyConfig::ProxyRules(), // proxy_rules
- "foo.com\ngoogle.com\n", // proxy_bypass_list
- false, // bypass_local_names
+ ProxyRulesExpectation::EmptyWithBypass("foo.com,google.com"),
},
};
@@ -183,12 +174,9 @@
ProxyConfig config;
ProxyConfigServiceWin::SetFromIEConfig(&config, tests[i].ie_config);
- EXPECT_EQ(tests[i].auto_detect, config.auto_detect);
- EXPECT_EQ(tests[i].pac_url, config.pac_url);
- EXPECT_EQ(tests[i].proxy_bypass_list,
- FlattenProxyBypass(config.proxy_bypass));
- EXPECT_EQ(tests[i].bypass_local_names, config.proxy_bypass_local_names);
- EXPECT_EQ(tests[i].proxy_rules, config.proxy_rules);
+ EXPECT_EQ(tests[i].auto_detect, config.auto_detect());
+ EXPECT_EQ(tests[i].pac_url, config.pac_url());
+ EXPECT_TRUE(tests[i].proxy_rules.Matches(config.proxy_rules()));
}
}
diff --git a/net/proxy/proxy_config_unittest.cc b/net/proxy/proxy_config_unittest.cc
index 259f1fa..5806f30 100644
--- a/net/proxy/proxy_config_unittest.cc
+++ b/net/proxy/proxy_config_unittest.cc
@@ -6,6 +6,7 @@
#include "net/proxy/proxy_config.h"
#include "net/proxy/proxy_config_service_common_unittest.h"
+#include "net/proxy/proxy_info.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace net {
@@ -24,73 +25,73 @@
// Test |ProxyConfig::auto_detect|.
ProxyConfig config1;
- config1.auto_detect = true;
+ config1.set_auto_detect(true);
ProxyConfig config2;
- config2.auto_detect = false;
+ config2.set_auto_detect(false);
EXPECT_FALSE(config1.Equals(config2));
EXPECT_FALSE(config2.Equals(config1));
- config2.auto_detect = true;
+ config2.set_auto_detect(true);
EXPECT_TRUE(config1.Equals(config2));
EXPECT_TRUE(config2.Equals(config1));
// Test |ProxyConfig::pac_url|.
- config2.pac_url = GURL("http://wpad/wpad.dat");
+ config2.set_pac_url(GURL("http://wpad/wpad.dat"));
EXPECT_FALSE(config1.Equals(config2));
EXPECT_FALSE(config2.Equals(config1));
- config1.pac_url = GURL("http://wpad/wpad.dat");
+ config1.set_pac_url(GURL("http://wpad/wpad.dat"));
EXPECT_TRUE(config1.Equals(config2));
EXPECT_TRUE(config2.Equals(config1));
// Test |ProxyConfig::proxy_rules|.
- config2.proxy_rules.type = ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY;
- config2.proxy_rules.single_proxy =
+ config2.proxy_rules().type = ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY;
+ config2.proxy_rules().single_proxy =
ProxyServer::FromURI("myproxy:80", ProxyServer::SCHEME_HTTP);
EXPECT_FALSE(config1.Equals(config2));
EXPECT_FALSE(config2.Equals(config1));
- config1.proxy_rules.type = ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY;
- config1.proxy_rules.single_proxy =
+ config1.proxy_rules().type = ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY;
+ config1.proxy_rules().single_proxy =
ProxyServer::FromURI("myproxy:100", ProxyServer::SCHEME_HTTP);
EXPECT_FALSE(config1.Equals(config2));
EXPECT_FALSE(config2.Equals(config1));
- config1.proxy_rules.single_proxy =
+ config1.proxy_rules().single_proxy =
ProxyServer::FromURI("myproxy", ProxyServer::SCHEME_HTTP);
EXPECT_TRUE(config1.Equals(config2));
EXPECT_TRUE(config2.Equals(config1));
- // Test |ProxyConfig::proxy_bypass|.
+ // Test |ProxyConfig::bypass_rules|.
- config2.proxy_bypass.push_back("*.google.com");
+ config2.proxy_rules().bypass_rules.AddRuleFromString("*.google.com");
EXPECT_FALSE(config1.Equals(config2));
EXPECT_FALSE(config2.Equals(config1));
- config1.proxy_bypass.push_back("*.google.com");
+ config1.proxy_rules().bypass_rules.AddRuleFromString("*.google.com");
EXPECT_TRUE(config1.Equals(config2));
EXPECT_TRUE(config2.Equals(config1));
- // Test |ProxyConfig::proxy_bypass_local_names|.
+ // Test |ProxyConfig::proxy_rules.reverse_bypass|.
- config1.proxy_bypass_local_names = true;
+ config2.proxy_rules().reverse_bypass = true;
EXPECT_FALSE(config1.Equals(config2));
EXPECT_FALSE(config2.Equals(config1));
- config2.proxy_bypass_local_names = true;
+ config1.proxy_rules().reverse_bypass = true;
EXPECT_TRUE(config1.Equals(config2));
EXPECT_TRUE(config2.Equals(config1));
@@ -236,52 +237,19 @@
ProxyConfig config;
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
- config.proxy_rules.ParseFromString(tests[i].proxy_rules);
+ config.proxy_rules().ParseFromString(tests[i].proxy_rules);
- EXPECT_EQ(tests[i].type, config.proxy_rules.type);
+ EXPECT_EQ(tests[i].type, config.proxy_rules().type);
ExpectProxyServerEquals(tests[i].single_proxy,
- config.proxy_rules.single_proxy);
+ config.proxy_rules().single_proxy);
ExpectProxyServerEquals(tests[i].proxy_for_http,
- config.proxy_rules.proxy_for_http);
+ config.proxy_rules().proxy_for_http);
ExpectProxyServerEquals(tests[i].proxy_for_https,
- config.proxy_rules.proxy_for_https);
+ config.proxy_rules().proxy_for_https);
ExpectProxyServerEquals(tests[i].proxy_for_ftp,
- config.proxy_rules.proxy_for_ftp);
+ config.proxy_rules().proxy_for_ftp);
ExpectProxyServerEquals(tests[i].socks_proxy,
- config.proxy_rules.socks_proxy);
- }
-}
-
-TEST(ProxyConfigTest, ParseProxyBypassList) {
- struct bypass_test {
- const char* proxy_bypass_input;
- const char* flattened_output;
- };
-
- const struct {
- const char* proxy_bypass_input;
- const char* flattened_output;
- } tests[] = {
- {
- "*",
- "*\n"
- },
- {
- ".google.com, .foo.com:42",
- "*.google.com\n*.foo.com:42\n"
- },
- {
- ".google.com, foo.com:99, 1.2.3.4:22, 127.0.0.1/8",
- "*.google.com\n*foo.com:99\n1.2.3.4:22\n127.0.0.1/8\n"
- }
- };
-
- ProxyConfig config;
-
- for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
- config.ParseNoProxyList(tests[i].proxy_bypass_input);
- EXPECT_EQ(tests[i].flattened_output,
- FlattenProxyBypass(config.proxy_bypass));
+ config.proxy_rules().socks_proxy);
}
}
@@ -295,44 +263,42 @@
// Manual proxy.
{
ProxyConfig config;
- config.auto_detect = false;
- config.proxy_rules.ParseFromString("http://single-proxy:81");
+ config.set_auto_detect(false);
+ config.proxy_rules().ParseFromString("http://single-proxy:81");
EXPECT_EQ("Automatic settings:\n"
" Auto-detect: No\n"
" Custom PAC script: [None]\n"
"Manual settings:\n"
" Proxy server: single-proxy:81\n"
- " Bypass list: [None]\n"
- " Bypass local names: No",
+ " Bypass list: [None]",
ProxyConfigToString(config));
}
// Autodetect + custom PAC + manual proxy.
{
ProxyConfig config;
- config.auto_detect = true;
- config.pac_url = GURL("http://custom/pac.js");
- config.proxy_rules.ParseFromString("http://single-proxy:81");
+ config.set_auto_detect(true);
+ config.set_pac_url(GURL("http://custom/pac.js"));
+ config.proxy_rules().ParseFromString("http://single-proxy:81");
EXPECT_EQ("Automatic settings:\n"
" Auto-detect: Yes\n"
" Custom PAC script: http://custom/pac.js\n"
"Manual settings:\n"
" Proxy server: single-proxy:81\n"
- " Bypass list: [None]\n"
- " Bypass local names: No",
+ " Bypass list: [None]",
ProxyConfigToString(config));
}
// Manual proxy with bypass list + bypass local.
{
ProxyConfig config;
- config.auto_detect = false;
- config.proxy_rules.ParseFromString("http://single-proxy:81");
- config.proxy_bypass.push_back("google.com");
- config.proxy_bypass.push_back("bypass2.net:1730");
- config.proxy_bypass_local_names = true;
+ config.set_auto_detect(false);
+ config.proxy_rules().ParseFromString("http://single-proxy:81");
+ config.proxy_rules().bypass_rules.AddRuleFromString("google.com");
+ config.proxy_rules().bypass_rules.AddRuleFromString("bypass2.net:1730");
+ config.proxy_rules().bypass_rules.AddRuleToBypassLocal();
EXPECT_EQ("Automatic settings:\n"
" Auto-detect: No\n"
@@ -342,15 +308,15 @@
" Bypass list: \n"
" google.com\n"
" bypass2.net:1730\n"
- " Bypass local names: Yes",
+ " <local>",
ProxyConfigToString(config));
}
// Proxy-per scheme (HTTP and HTTPS)
{
ProxyConfig config;
- config.auto_detect = false;
- config.proxy_rules.ParseFromString(
+ config.set_auto_detect(false);
+ config.proxy_rules().ParseFromString(
"http=proxy-for-http:1801; https=proxy-for-https:1802");
EXPECT_EQ("Automatic settings:\n"
@@ -360,16 +326,15 @@
" Proxy server: \n"
" HTTP: proxy-for-http:1801\n"
" HTTPS: proxy-for-https:1802\n"
- " Bypass list: [None]\n"
- " Bypass local names: No",
+ " Bypass list: [None]",
ProxyConfigToString(config));
}
// Proxy-per scheme (HTTP and SOCKS)
{
ProxyConfig config;
- config.auto_detect = false;
- config.proxy_rules.ParseFromString(
+ config.set_auto_detect(false);
+ config.proxy_rules().ParseFromString(
"http=http://proxy-for-http:1801; socks=socks-server:6083");
EXPECT_EQ("Automatic settings:\n"
@@ -379,23 +344,43 @@
" Proxy server: \n"
" HTTP: proxy-for-http:1801\n"
" SOCKS: socks4://socks-server:6083\n"
- " Bypass list: [None]\n"
- " Bypass local names: No",
+ " Bypass list: [None]",
ProxyConfigToString(config));
}
// No proxy.
{
ProxyConfig config;
- config.auto_detect = false;
+ config.set_auto_detect(false);
EXPECT_EQ("Automatic settings:\n"
" Auto-detect: No\n"
" Custom PAC script: [None]\n"
"Manual settings:\n"
" Proxy server: [None]\n"
- " Bypass list: [None]\n"
- " Bypass local names: No",
+ " Bypass list: [None]",
+ ProxyConfigToString(config));
+ }
+
+ // Manual proxy with bypass list + bypass local, list reversed.
+ {
+ ProxyConfig config;
+ config.set_auto_detect(false);
+ config.proxy_rules().ParseFromString("http://single-proxy:81");
+ config.proxy_rules().bypass_rules.AddRuleFromString("google.com");
+ config.proxy_rules().bypass_rules.AddRuleFromString("bypass2.net:1730");
+ config.proxy_rules().bypass_rules.AddRuleToBypassLocal();
+ config.proxy_rules().reverse_bypass = true;
+
+ EXPECT_EQ("Automatic settings:\n"
+ " Auto-detect: No\n"
+ " Custom PAC script: [None]\n"
+ "Manual settings:\n"
+ " Proxy server: single-proxy:81\n"
+ " Only use proxy for: \n"
+ " google.com\n"
+ " bypass2.net:1730\n"
+ " <local>",
ProxyConfigToString(config));
}
}
@@ -407,21 +392,47 @@
}
{
ProxyConfig config;
- config.auto_detect = true;
+ config.set_auto_detect(true);
EXPECT_TRUE(config.MayRequirePACResolver());
}
{
ProxyConfig config;
- config.pac_url = GURL("http://custom/pac.js");
+ config.set_pac_url(GURL("http://custom/pac.js"));
EXPECT_TRUE(config.MayRequirePACResolver());
}
{
ProxyConfig config;
- config.pac_url = GURL("notvalid");
+ config.set_pac_url(GURL("notvalid"));
EXPECT_FALSE(config.MayRequirePACResolver());
}
}
+TEST(ProxyConfigTest, ReversedBypassList) {
+ {
+ ProxyConfig config;
+ config.set_auto_detect(false);
+ config.proxy_rules().ParseFromString("http://single-proxy:81");
+ config.proxy_rules().bypass_rules.AddRuleFromString("google.com");
+ config.proxy_rules().bypass_rules.AddRuleFromString("bypass2.net:1730");
+ config.proxy_rules().bypass_rules.AddRuleToBypassLocal();
+ config.proxy_rules().reverse_bypass = true;
+
+ ProxyInfo info[3];
+ GURL url0("http://google.com");
+ GURL url1("http://www.webkit.com");
+ GURL url2("http://bypass2.net:1730");
+
+ config.proxy_rules().Apply(url0, &info[0]);
+ EXPECT_EQ("single-proxy:81", info[0].proxy_server().ToURI());
+
+ config.proxy_rules().Apply(url1, &info[1]);
+ EXPECT_TRUE(info[1].is_direct());
+
+ config.proxy_rules().Apply(url2, &info[2]);
+ EXPECT_EQ("single-proxy:81", info[2].proxy_server().ToURI());
+ }
+}
+
} // namespace
} // namespace net
diff --git a/net/proxy/proxy_info.h b/net/proxy/proxy_info.h
index b18feb8..2531c6d 100644
--- a/net/proxy/proxy_info.h
+++ b/net/proxy/proxy_info.h
@@ -22,22 +22,22 @@
ProxyInfo();
// Default copy-constructor and assignment operator are OK!
- // Use the same proxy server as the given |proxy_info|.
+ // Uses the same proxy server as the given |proxy_info|.
void Use(const ProxyInfo& proxy_info);
- // Use a direct connection.
+ // Uses a direct connection.
void UseDirect();
- // Use a specific proxy server, of the form:
+ // Uses a specific proxy server, of the form:
// proxy-uri = [<scheme> "://"] <hostname> [":" <port>]
// This may optionally be a semi-colon delimited list of <proxy-uri>.
// It is OK to have LWS between entries.
void UseNamedProxy(const std::string& proxy_uri_list);
- // Set the proxy list to a single entry, |proxy_server|.
+ // Sets the proxy list to a single entry, |proxy_server|.
void UseProxyServer(const ProxyServer& proxy_server);
- // Parse from the given PAC result.
+ // Parses from the given PAC result.
void UsePacString(const std::string& pac_string) {
proxy_list_.SetFromPacString(pac_string);
}
@@ -50,6 +50,27 @@
return proxy_list_.Get().is_direct();
}
+ // Returns true if the first valid proxy server is an https proxy.
+ bool is_https() const {
+ if (is_empty())
+ return false;
+ return proxy_server().is_https();
+ }
+
+ // Returns true if the first valid proxy server is an http proxy.
+ bool is_http() const {
+ if (is_empty())
+ return false;
+ return proxy_server().is_http();
+ }
+
+ // Returns true if the first valid proxy server is a socks server.
+ bool is_socks() const {
+ if (is_empty())
+ return false;
+ return proxy_server().is_socks();
+ }
+
// Returns true if this proxy info has no proxies left to try.
bool is_empty() const {
return proxy_list_.IsEmpty();
@@ -74,7 +95,7 @@
proxy_list_.DeprioritizeBadProxies(proxy_retry_info);
}
- // Delete any entry which doesn't have one of the specified proxy schemes.
+ // Deletes any entry which doesn't have one of the specified proxy schemes.
void RemoveProxiesWithoutScheme(int scheme_bit_field) {
proxy_list_.RemoveProxiesWithoutScheme(scheme_bit_field);
}
diff --git a/net/proxy/proxy_list.h b/net/proxy/proxy_list.h
index 1afd1d9..5df1e0a 100644
--- a/net/proxy/proxy_list.h
+++ b/net/proxy/proxy_list.h
@@ -40,11 +40,12 @@
// this if !IsEmpty().
const ProxyServer& Get() const;
- // Set the list by parsing the pac result |pac_string|.
+ // Sets the list by parsing the pac result |pac_string|.
// Some examples for |pac_string|:
// "DIRECT"
// "PROXY foopy1"
// "PROXY foopy1; SOCKS4 foopy2:1188"
+ // Does a best-effort parse, and silently discards any errors.
void SetFromPacString(const std::string& pac_string);
// Returns a PAC-style semicolon-separated list of valid proxy servers.
diff --git a/net/proxy/proxy_resolver.h b/net/proxy/proxy_resolver.h
index 2e83097..003fa84 100644
--- a/net/proxy/proxy_resolver.h
+++ b/net/proxy/proxy_resolver.h
@@ -5,15 +5,16 @@
#ifndef NET_PROXY_PROXY_RESOLVER_H_
#define NET_PROXY_PROXY_RESOLVER_H_
-#include <string>
-
#include "base/logging.h"
+#include "base/ref_counted.h"
+#include "base/string16.h"
#include "googleurl/src/gurl.h"
#include "net/base/completion_callback.h"
+#include "net/proxy/proxy_resolver_script_data.h"
namespace net {
-class LoadLog;
+class BoundNetLog;
class ProxyInfo;
// Interface for "proxy resolvers". A ProxyResolver fills in a list of proxies
@@ -41,30 +42,17 @@
ProxyInfo* results,
CompletionCallback* callback,
RequestHandle* request,
- LoadLog* load_log) = 0;
+ const BoundNetLog& net_log) = 0;
// Cancels |request|.
virtual void CancelRequest(RequestHandle request) = 0;
// The PAC script backend can be specified to the ProxyResolver either via
// URL, or via the javascript text itself. If |expects_pac_bytes| is true,
- // then PAC scripts should be specified using SetPacScriptByData(). Otherwise
- // they should be specified using SetPacScriptByUrl().
+ // then the ProxyResolverScriptData passed to SetPacScript() should
+ // contain the actual script bytes rather than just the URL.
bool expects_pac_bytes() const { return expects_pac_bytes_; }
- // Sets the PAC script backend to use for this proxy resolver (by URL).
- int SetPacScriptByUrl(const GURL& url, CompletionCallback* callback) {
- DCHECK(!expects_pac_bytes());
- return SetPacScript(url, std::string(), callback);
- }
-
- // Sets the PAC script backend to use for this proxy resolver (by contents).
- int SetPacScriptByData(const std::string& bytes_utf8,
- CompletionCallback* callback) {
- DCHECK(expects_pac_bytes());
- return SetPacScript(GURL(), bytes_utf8, callback);
- }
-
// TODO(eroman): Make this =0.
virtual void CancelSetPacScript() {
NOTREACHED();
@@ -75,16 +63,18 @@
// no-op implementation.
virtual void PurgeMemory() {}
- private:
- // Called to set the PAC script backend to use. If |pac_url| is invalid,
- // this is a request to use WPAD (auto detect). |bytes_utf8| may be empty if
- // the fetch failed, or if the fetch returned no content.
+ // Called to set the PAC script backend to use.
// Returns ERR_IO_PENDING in the case of asynchronous completion, and notifies
// the result through |callback|.
- virtual int SetPacScript(const GURL& pac_url,
- const std::string& bytes_utf8,
- CompletionCallback* callback) = 0;
+ virtual int SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& pac_script,
+ CompletionCallback* callback) = 0;
+ // Optional shutdown code to be run before destruction. This is only used
+ // by the multithreaded runner to signal cleanup from origin thread
+ virtual void Shutdown() {}
+
+ private:
const bool expects_pac_bytes_;
DISALLOW_COPY_AND_ASSIGN(ProxyResolver);
diff --git a/net/proxy/proxy_resolver_js_bindings.cc b/net/proxy/proxy_resolver_js_bindings.cc
index eb7e4f8..56604cd 100644
--- a/net/proxy/proxy_resolver_js_bindings.cc
+++ b/net/proxy/proxy_resolver_js_bindings.cc
@@ -1,153 +1,210 @@
-// Copyright (c) 2009 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.
+// 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 "net/proxy/proxy_resolver_js_bindings.h"
-#include "base/compiler_specific.h"
#include "base/logging.h"
-#include "base/message_loop.h"
-#include "base/waitable_event.h"
+#include "base/string_util.h"
+#include "base/values.h"
#include "net/base/address_list.h"
+#include "net/base/host_cache.h"
#include "net/base/host_resolver.h"
#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
#include "net/base/net_util.h"
#include "net/base/sys_addrinfo.h"
+#include "net/proxy/proxy_resolver_request_context.h"
namespace net {
+
namespace {
-// Wrapper around HostResolver to give a sync API while running the resolve
-// in async mode on |host_resolver_loop|. If |host_resolver_loop| is NULL,
-// runs sync on the current thread (this mode is just used by testing).
-class SyncHostResolverBridge
- : public base::RefCountedThreadSafe<SyncHostResolverBridge> {
+// Event parameters for a PAC error message (line number + message).
+class ErrorNetlogParams : public NetLog::EventParameters {
public:
- SyncHostResolverBridge(HostResolver* host_resolver,
- MessageLoop* host_resolver_loop)
- : host_resolver_(host_resolver),
- host_resolver_loop_(host_resolver_loop),
- event_(false, false),
- ALLOW_THIS_IN_INITIALIZER_LIST(
- callback_(this, &SyncHostResolverBridge::OnResolveCompletion)) {
+ ErrorNetlogParams(int line_number,
+ const string16& message)
+ : line_number_(line_number),
+ message_(message) {
}
- // Run the resolve on host_resolver_loop, and wait for result.
- int Resolve(const std::string& hostname,
- AddressFamily address_family,
- net::AddressList* addresses) {
- // Port number doesn't matter.
- HostResolver::RequestInfo info(hostname, 80);
- info.set_address_family(address_family);
-
- // Hack for tests -- run synchronously on current thread.
- if (!host_resolver_loop_)
- return host_resolver_->Resolve(info, addresses, NULL, NULL, NULL);
-
- // Otherwise start an async resolve on the resolver's thread.
- host_resolver_loop_->PostTask(FROM_HERE, NewRunnableMethod(this,
- &SyncHostResolverBridge::StartResolve, info, addresses));
-
- // Wait for the resolve to complete in the resolver's thread.
- event_.Wait();
- return err_;
+ virtual Value* ToValue() const {
+ DictionaryValue* dict = new DictionaryValue();
+ dict->SetInteger(L"line_number", line_number_);
+ dict->SetStringFromUTF16(L"message", message_);
+ return dict;
}
private:
- friend class base::RefCountedThreadSafe<SyncHostResolverBridge>;
+ const int line_number_;
+ const string16 message_;
- ~SyncHostResolverBridge() {}
+ DISALLOW_COPY_AND_ASSIGN(ErrorNetlogParams);
+};
- // Called on host_resolver_loop_.
- void StartResolve(const HostResolver::RequestInfo& info,
- net::AddressList* addresses) {
- DCHECK_EQ(host_resolver_loop_, MessageLoop::current());
- int error = host_resolver_->Resolve(
- info, addresses, &callback_, NULL, NULL);
- if (error != ERR_IO_PENDING)
- OnResolveCompletion(error); // Completed synchronously.
+// Event parameters for a PAC alert().
+class AlertNetlogParams : public NetLog::EventParameters {
+ public:
+ explicit AlertNetlogParams(const string16& message) : message_(message) {
}
- // Called on host_resolver_loop_.
- void OnResolveCompletion(int result) {
- DCHECK_EQ(host_resolver_loop_, MessageLoop::current());
- err_ = result;
- event_.Signal();
+ virtual Value* ToValue() const {
+ DictionaryValue* dict = new DictionaryValue();
+ dict->SetStringFromUTF16(L"message", message_);
+ return dict;
}
- scoped_refptr<HostResolver> host_resolver_;
- MessageLoop* host_resolver_loop_;
+ private:
+ const string16 message_;
- // Event to notify completion of resolve request.
- base::WaitableEvent event_;
-
- // Callback for when the resolve completes on host_resolver_loop_.
- net::CompletionCallbackImpl<SyncHostResolverBridge> callback_;
-
- // The result from the result request (set by in host_resolver_loop_).
- int err_;
+ DISALLOW_COPY_AND_ASSIGN(AlertNetlogParams);
};
// ProxyResolverJSBindings implementation.
class DefaultJSBindings : public ProxyResolverJSBindings {
public:
- DefaultJSBindings(HostResolver* host_resolver,
- MessageLoop* host_resolver_loop)
- : host_resolver_(new SyncHostResolverBridge(
- host_resolver, host_resolver_loop)) {}
+ DefaultJSBindings(HostResolver* host_resolver, NetLog* net_log)
+ : host_resolver_(host_resolver),
+ net_log_(net_log) {
+ }
// Handler for "alert(message)".
- virtual void Alert(const std::string& message) {
+ virtual void Alert(const string16& message) {
LOG(INFO) << "PAC-alert: " << message;
+
+ // Send to the NetLog.
+ LogEventToCurrentRequestAndGlobally(NetLog::TYPE_PAC_JAVASCRIPT_ALERT,
+ new AlertNetlogParams(message));
}
- // Handler for "myIpAddress()". Returns empty string on failure.
+ // Handler for "myIpAddress()".
// TODO(eroman): Perhaps enumerate the interfaces directly, using
// getifaddrs().
- virtual std::string MyIpAddress() {
- // DnsResolve("") returns "", so no need to check for failure.
- return DnsResolve(GetHostName());
+ virtual bool MyIpAddress(std::string* first_ip_address) {
+ LogEventToCurrentRequest(NetLog::PHASE_BEGIN,
+ NetLog::TYPE_PAC_JAVASCRIPT_MY_IP_ADDRESS,
+ NULL);
+
+ bool ok = MyIpAddressImpl(first_ip_address);
+
+ LogEventToCurrentRequest(NetLog::PHASE_END,
+ NetLog::TYPE_PAC_JAVASCRIPT_MY_IP_ADDRESS,
+ NULL);
+ return ok;
}
- // Handler for "myIpAddressEx()". Returns empty string on failure.
- virtual std::string MyIpAddressEx() {
- return DnsResolveEx(GetHostName());
+ // Handler for "myIpAddressEx()".
+ virtual bool MyIpAddressEx(std::string* ip_address_list) {
+ LogEventToCurrentRequest(NetLog::PHASE_BEGIN,
+ NetLog::TYPE_PAC_JAVASCRIPT_MY_IP_ADDRESS_EX,
+ NULL);
+
+ bool ok = MyIpAddressExImpl(ip_address_list);
+
+ LogEventToCurrentRequest(NetLog::PHASE_END,
+ NetLog::TYPE_PAC_JAVASCRIPT_MY_IP_ADDRESS_EX,
+ NULL);
+ return ok;
}
- // Handler for "dnsResolve(host)". Returns empty string on failure.
- virtual std::string DnsResolve(const std::string& host) {
+ // Handler for "dnsResolve(host)".
+ virtual bool DnsResolve(const std::string& host,
+ std::string* first_ip_address) {
+ LogEventToCurrentRequest(NetLog::PHASE_BEGIN,
+ NetLog::TYPE_PAC_JAVASCRIPT_DNS_RESOLVE,
+ NULL);
+
+ bool ok = DnsResolveImpl(host, first_ip_address);
+
+ LogEventToCurrentRequest(NetLog::PHASE_END,
+ NetLog::TYPE_PAC_JAVASCRIPT_DNS_RESOLVE,
+ NULL);
+ return ok;
+ }
+
+ // Handler for "dnsResolveEx(host)".
+ virtual bool DnsResolveEx(const std::string& host,
+ std::string* ip_address_list) {
+ LogEventToCurrentRequest(NetLog::PHASE_BEGIN,
+ NetLog::TYPE_PAC_JAVASCRIPT_DNS_RESOLVE_EX,
+ NULL);
+
+ bool ok = DnsResolveExImpl(host, ip_address_list);
+
+ LogEventToCurrentRequest(NetLog::PHASE_END,
+ NetLog::TYPE_PAC_JAVASCRIPT_DNS_RESOLVE_EX,
+ NULL);
+ return ok;
+ }
+
+ // Handler for when an error is encountered. |line_number| may be -1.
+ virtual void OnError(int line_number, const string16& message) {
+ // Send to the chrome log.
+ if (line_number == -1)
+ LOG(INFO) << "PAC-error: " << message;
+ else
+ LOG(INFO) << "PAC-error: " << "line: " << line_number << ": " << message;
+
+ // Send the error to the NetLog.
+ LogEventToCurrentRequestAndGlobally(
+ NetLog::TYPE_PAC_JAVASCRIPT_ERROR,
+ new ErrorNetlogParams(line_number, message));
+ }
+
+ virtual void Shutdown() {
+ host_resolver_->Shutdown();
+ }
+
+ private:
+ bool MyIpAddressImpl(std::string* first_ip_address) {
+ std::string my_hostname = GetHostName();
+ if (my_hostname.empty())
+ return false;
+ return DnsResolveImpl(my_hostname, first_ip_address);
+ }
+
+ bool MyIpAddressExImpl(std::string* ip_address_list) {
+ std::string my_hostname = GetHostName();
+ if (my_hostname.empty())
+ return false;
+ return DnsResolveExImpl(my_hostname, ip_address_list);
+ }
+
+ bool DnsResolveImpl(const std::string& host,
+ std::string* first_ip_address) {
// Do a sync resolve of the hostname.
// Disable IPv6 results. We do this because the PAC specification isn't
// really IPv6 friendly, and Internet Explorer also restricts to IPv4.
// Consequently a lot of existing PAC scripts assume they will only get
// IPv4 results, and will misbehave if they get an IPv6 result.
// See http://crbug.com/24641 for more details.
- net::AddressList address_list;
- int result = host_resolver_->Resolve(host,
- ADDRESS_FAMILY_IPV4,
- &address_list);
+ HostResolver::RequestInfo info(host, 80); // Port doesn't matter.
+ info.set_address_family(ADDRESS_FAMILY_IPV4);
+ AddressList address_list;
+ int result = DnsResolveHelper(info, &address_list);
if (result != OK)
- return std::string(); // Failed.
-
- if (!address_list.head())
- return std::string();
+ return false;
// There may be multiple results; we will just use the first one.
// This returns empty string on failure.
- return net::NetAddressToString(address_list.head());
+ *first_ip_address = net::NetAddressToString(address_list.head());
+ if (first_ip_address->empty())
+ return false;
+
+ return true;
}
- // Handler for "dnsResolveEx(host)". Returns empty string on failure.
- virtual std::string DnsResolveEx(const std::string& host) {
+ bool DnsResolveExImpl(const std::string& host,
+ std::string* ip_address_list) {
// Do a sync resolve of the hostname.
- net::AddressList address_list;
- int result = host_resolver_->Resolve(host,
- ADDRESS_FAMILY_UNSPECIFIED,
- &address_list);
+ HostResolver::RequestInfo info(host, 80); // Port doesn't matter.
+ AddressList address_list;
+ int result = DnsResolveHelper(info, &address_list);
if (result != OK)
- return std::string(); // Failed.
+ return false;
// Stringify all of the addresses in the address list, separated
// by semicolons.
@@ -156,31 +213,88 @@
while (current_address) {
if (!address_list_str.empty())
address_list_str += ";";
- address_list_str += net::NetAddressToString(current_address);
+ const std::string address_string = NetAddressToString(current_address);
+ if (address_string.empty())
+ return false;
+ address_list_str += address_string;
current_address = current_address->ai_next;
}
- return address_list_str;
+ *ip_address_list = address_list_str;
+ return true;
}
- // Handler for when an error is encountered. |line_number| may be -1.
- virtual void OnError(int line_number, const std::string& message) {
- if (line_number == -1)
- LOG(INFO) << "PAC-error: " << message;
- else
- LOG(INFO) << "PAC-error: " << "line: " << line_number << ": " << message;
+ // Helper to execute a synchronous DNS resolve, using the per-request
+ // DNS cache if there is one.
+ int DnsResolveHelper(const HostResolver::RequestInfo& info,
+ AddressList* address_list) {
+ HostCache::Key cache_key(info.hostname(),
+ info.address_family(),
+ info.host_resolver_flags());
+
+ HostCache* host_cache = current_request_context() ?
+ current_request_context()->host_cache : NULL;
+
+ // First try to service this request from the per-request DNS cache.
+ // (we cache DNS failures much more aggressively within the context
+ // of a FindProxyForURL() request).
+ if (host_cache) {
+ const HostCache::Entry* entry =
+ host_cache->Lookup(cache_key, base::TimeTicks::Now());
+ if (entry) {
+ if (entry->error == OK)
+ *address_list = entry->addrlist;
+ return entry->error;
+ }
+ }
+
+ // Otherwise ask the resolver.
+ int result = host_resolver_->Resolve(info, address_list, NULL, NULL,
+ BoundNetLog());
+
+ // Save the result back to the per-request DNS cache.
+ if (host_cache) {
+ host_cache->Set(cache_key, result, *address_list,
+ base::TimeTicks::Now());
+ }
+
+ return result;
}
- private:
- scoped_refptr<SyncHostResolverBridge> host_resolver_;
+ void LogEventToCurrentRequest(
+ NetLog::EventPhase phase,
+ NetLog::EventType type,
+ scoped_refptr<NetLog::EventParameters> params) {
+ if (current_request_context() && current_request_context()->net_log)
+ current_request_context()->net_log->AddEntry(type, phase, params);
+ }
+
+ void LogEventToCurrentRequestAndGlobally(
+ NetLog::EventType type,
+ scoped_refptr<NetLog::EventParameters> params) {
+ LogEventToCurrentRequest(NetLog::PHASE_NONE, type, params);
+
+ // Emit to the global NetLog event stream.
+ if (net_log_) {
+ net_log_->AddEntry(
+ type,
+ base::TimeTicks::Now(),
+ NetLog::Source(),
+ NetLog::PHASE_NONE,
+ params);
+ }
+ }
+
+ scoped_refptr<HostResolver> host_resolver_;
+ NetLog* net_log_;
};
} // namespace
// static
ProxyResolverJSBindings* ProxyResolverJSBindings::CreateDefault(
- HostResolver* host_resolver, MessageLoop* host_resolver_loop) {
- return new DefaultJSBindings(host_resolver, host_resolver_loop);
+ HostResolver* host_resolver, NetLog* net_log) {
+ return new DefaultJSBindings(host_resolver, net_log);
}
} // namespace net
diff --git a/net/proxy/proxy_resolver_js_bindings.h b/net/proxy/proxy_resolver_js_bindings.h
index 03ad61a..ae13584 100644
--- a/net/proxy/proxy_resolver_js_bindings.h
+++ b/net/proxy/proxy_resolver_js_bindings.h
@@ -1,60 +1,87 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
-#ifndef NET_PROXY_PROXY_JS_BINDINGS_H
-#define NET_PROXY_PROXY_JS_BINDINGS_H
+#ifndef NET_PROXY_PROXY_RESOLVER_JS_BINDINGS_H_
+#define NET_PROXY_PROXY_RESOLVER_JS_BINDINGS_H_
#include <string>
+#include "base/string16.h"
+
class MessageLoop;
namespace net {
class HostResolver;
+class NetLog;
+struct ProxyResolverRequestContext;
// Interface for the javascript bindings.
class ProxyResolverJSBindings {
public:
+ ProxyResolverJSBindings() : current_request_context_(NULL) {}
+
virtual ~ProxyResolverJSBindings() {}
// Handler for "alert(message)"
- virtual void Alert(const std::string& message) = 0;
+ virtual void Alert(const string16& message) = 0;
- // Handler for "myIpAddress()". Returns empty string on failure.
- virtual std::string MyIpAddress() = 0;
+ // Handler for "myIpAddress()". Returns true on success and fills
+ // |*first_ip_address| with the result.
+ virtual bool MyIpAddress(std::string* first_ip_address) = 0;
- // Handler for "myIpAddressEx()". Returns empty string on failure.
+ // Handler for "myIpAddressEx()". Returns true on success and fills
+ // |*ip_address_list| with the result.
//
// This is a Microsoft extension to PAC for IPv6, see:
// http://blogs.msdn.com/wndp/articles/IPV6_PAC_Extensions_v0_9.aspx
- virtual std::string MyIpAddressEx() = 0;
+ virtual bool MyIpAddressEx(std::string* ip_address_list) = 0;
- // Handler for "dnsResolve(host)". Returns empty string on failure.
- virtual std::string DnsResolve(const std::string& host) = 0;
+ // Handler for "dnsResolve(host)". Returns true on success and fills
+ // |*first_ip_address| with the result.
+ virtual bool DnsResolve(const std::string& host,
+ std::string* first_ip_address) = 0;
- // Handler for "dnsResolveEx(host)". Returns empty string on failure.
+ // Handler for "dnsResolveEx(host)". Returns true on success and fills
+ // |*ip_address_list| with the result.
//
// This is a Microsoft extension to PAC for IPv6, see:
// http://blogs.msdn.com/wndp/articles/IPV6_PAC_Extensions_v0_9.aspx
- virtual std::string DnsResolveEx(const std::string& host) = 0;
+ virtual bool DnsResolveEx(const std::string& host,
+ std::string* ip_address_list) = 0;
// Handler for when an error is encountered. |line_number| may be -1
// if a line number is not applicable to this error.
- virtual void OnError(int line_number, const std::string& error) = 0;
+ virtual void OnError(int line_number, const string16& error) = 0;
+
+ // Called before the thread running the proxy resolver is stopped.
+ virtual void Shutdown() = 0;
// Creates a default javascript bindings implementation that will:
- // - Send script error messages to LOG(INFO)
- // - Send script alert()s to LOG(INFO)
+ // - Send script error messages to both LOG(INFO), and the NetLog.
+ // - Send script alert()s to both LOG(INFO), and the NetLog.
// - Use the provided host resolver to service dnsResolve().
//
- // |host_resolver| will be used in async mode on |host_resolver_loop|. If
- // |host_resolver_loop| is NULL, then |host_resolver| will be used in sync
- // mode on the PAC thread.
- static ProxyResolverJSBindings* CreateDefault(
- HostResolver* host_resolver, MessageLoop* host_resolver_loop);
+ // Note that |host_resolver| will be used in sync mode mode.
+ static ProxyResolverJSBindings* CreateDefault(HostResolver* host_resolver,
+ NetLog* net_log);
+
+ // Sets details about the currently executing FindProxyForURL() request.
+ void set_current_request_context(
+ ProxyResolverRequestContext* current_request_context) {
+ current_request_context_ = current_request_context;
+ }
+
+ // Retrieves details about the currently executing FindProxyForURL() request.
+ ProxyResolverRequestContext* current_request_context() {
+ return current_request_context_;
+ }
+
+ private:
+ ProxyResolverRequestContext* current_request_context_;
};
} // namespace net
-#endif // NET_PROXY_PROXY_JS_BINDINGS_H
+#endif // NET_PROXY_PROXY_RESOLVER_JS_BINDINGS_H_
diff --git a/net/proxy/proxy_resolver_js_bindings_unittest.cc b/net/proxy/proxy_resolver_js_bindings_unittest.cc
index 5035f3e..0124469 100644
--- a/net/proxy/proxy_resolver_js_bindings_unittest.cc
+++ b/net/proxy/proxy_resolver_js_bindings_unittest.cc
@@ -1,17 +1,23 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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 "net/proxy/proxy_resolver_js_bindings.h"
+
#include "base/scoped_ptr.h"
+#include "base/string_util.h"
#include "net/base/address_list.h"
#include "net/base/mock_host_resolver.h"
#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
#include "net/base/net_util.h"
#include "net/base/sys_addrinfo.h"
-#include "net/proxy/proxy_resolver_js_bindings.h"
+#include "net/proxy/proxy_resolver_request_context.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace net {
+
namespace {
// This is a HostResolver that synchronously resolves all hosts to the
@@ -26,7 +32,7 @@
AddressList* addresses,
CompletionCallback* callback,
RequestHandle* out_req,
- LoadLog* load_log) {
+ const BoundNetLog& net_log) {
// Build up the result list (in reverse).
AddressList temp_list = ResolveIPLiteral("200.100.1.2");
temp_list = PrependAddressToList("172.22.34.1", temp_list);
@@ -47,7 +53,8 @@
AddressList result;
int rv = SystemHostResolverProc(ip_literal,
ADDRESS_FAMILY_UNSPECIFIED,
- &result);
+ 0,
+ &result, NULL);
EXPECT_EQ(OK, rv);
EXPECT_EQ(NULL, result.head()->ai_next);
return result;
@@ -67,7 +74,7 @@
// Make a copy of the concatenated list.
AddressList concatenated;
- concatenated.Copy(result.head());
+ concatenated.Copy(result.head(), true);
// Restore |result| (so it is freed properly).
result_head->ai_next = NULL;
@@ -76,6 +83,33 @@
}
};
+class MockFailingHostResolver : public HostResolver {
+ public:
+ MockFailingHostResolver() : count_(0) {}
+
+ // HostResolver methods:
+ virtual int Resolve(const RequestInfo& info,
+ AddressList* addresses,
+ CompletionCallback* callback,
+ RequestHandle* out_req,
+ const BoundNetLog& net_log) {
+ count_++;
+ return ERR_NAME_NOT_RESOLVED;
+ }
+
+ virtual void CancelRequest(RequestHandle req) {}
+ virtual void AddObserver(Observer* observer) {}
+ virtual void RemoveObserver(Observer* observer) {}
+ virtual void Shutdown() {}
+
+ // Returns the number of times Resolve() has been called.
+ int count() const { return count_; }
+ void ResetCount() { count_ = 0; }
+
+ private:
+ int count_;
+};
+
TEST(ProxyResolverJSBindingsTest, DnsResolve) {
scoped_refptr<MockHostResolver> host_resolver(new MockHostResolver);
@@ -83,18 +117,21 @@
scoped_ptr<ProxyResolverJSBindings> bindings(
ProxyResolverJSBindings::CreateDefault(host_resolver, NULL));
+ std::string ip_address;
+
// Empty string is not considered a valid host (even though on some systems
// requesting this will resolve to localhost).
host_resolver->rules()->AddSimulatedFailure("");
- EXPECT_EQ("", bindings->DnsResolve(""));
+ EXPECT_FALSE(bindings->DnsResolve("", &ip_address));
// Should call through to the HostResolver.
host_resolver->rules()->AddRule("google.com", "192.168.1.1");
- EXPECT_EQ("192.168.1.1", bindings->DnsResolve("google.com"));
+ EXPECT_TRUE(bindings->DnsResolve("google.com", &ip_address));
+ EXPECT_EQ("192.168.1.1", ip_address);
// Resolve failures should give empty string.
host_resolver->rules()->AddSimulatedFailure("fail");
- EXPECT_EQ("", bindings->DnsResolve("fail"));
+ EXPECT_FALSE(bindings->DnsResolve("fail", &ip_address));
// TODO(eroman): would be nice to have an IPV6 test here too, but that
// won't work on all systems.
@@ -103,12 +140,12 @@
TEST(ProxyResolverJSBindingsTest, MyIpAddress) {
// Get a hold of a DefaultJSBindings* (it is a hidden impl class).
scoped_ptr<ProxyResolverJSBindings> bindings(
- ProxyResolverJSBindings::CreateDefault(
- new MockHostResolver, NULL));
+ ProxyResolverJSBindings::CreateDefault(new MockHostResolver, NULL));
// Our IP address is always going to be 127.0.0.1, since we are using a
// mock host resolver.
- std::string my_ip_address = bindings->MyIpAddress();
+ std::string my_ip_address;
+ EXPECT_TRUE(bindings->MyIpAddress(&my_ip_address));
EXPECT_EQ("127.0.0.1", my_ip_address);
}
@@ -130,8 +167,7 @@
// Get a hold of a DefaultJSBindings* (it is a hidden impl class).
scoped_ptr<ProxyResolverJSBindings> bindings(
- ProxyResolverJSBindings::CreateDefault(
- host_resolver, NULL));
+ ProxyResolverJSBindings::CreateDefault(host_resolver, NULL));
// Make it so requests resolve to particular address patterns based on family:
// IPV4_ONLY --> 192.168.1.*
@@ -149,21 +185,34 @@
// depending if the address family was IPV4_ONLY or not.
HostResolver::RequestInfo info("foo", 80);
AddressList address_list;
- EXPECT_EQ(OK, host_resolver->Resolve(info, &address_list, NULL, NULL, NULL));
+ EXPECT_EQ(OK, host_resolver->Resolve(info, &address_list, NULL, NULL,
+ BoundNetLog()));
EXPECT_EQ("192.168.2.1", NetAddressToString(address_list.head()));
info.set_address_family(ADDRESS_FAMILY_IPV4);
- EXPECT_EQ(OK, host_resolver->Resolve(info, &address_list, NULL, NULL, NULL));
+ EXPECT_EQ(OK, host_resolver->Resolve(info, &address_list, NULL, NULL,
+ BoundNetLog()));
EXPECT_EQ("192.168.1.1", NetAddressToString(address_list.head()));
+ std::string ip_address;
// Now the actual test.
- EXPECT_EQ("192.168.1.2", bindings->MyIpAddress()); // IPv4 restricted.
- EXPECT_EQ("192.168.1.1", bindings->DnsResolve("foo")); // IPv4 restricted.
- EXPECT_EQ("192.168.1.2", bindings->DnsResolve("foo2")); // IPv4 restricted.
+ EXPECT_TRUE(bindings->MyIpAddress(&ip_address));
+ EXPECT_EQ("192.168.1.2", ip_address); // IPv4 restricted.
- EXPECT_EQ("192.168.2.2", bindings->MyIpAddressEx()); // Unrestricted.
- EXPECT_EQ("192.168.2.1", bindings->DnsResolveEx("foo")); // Unrestricted.
- EXPECT_EQ("192.168.2.2", bindings->DnsResolveEx("foo2")); // Unrestricted.
+ EXPECT_TRUE(bindings->DnsResolve("foo", &ip_address));
+ EXPECT_EQ("192.168.1.1", ip_address); // IPv4 restricted.
+
+ EXPECT_TRUE(bindings->DnsResolve("foo2", &ip_address));
+ EXPECT_EQ("192.168.1.2", ip_address); // IPv4 restricted.
+
+ EXPECT_TRUE(bindings->MyIpAddressEx(&ip_address));
+ EXPECT_EQ("192.168.2.2", ip_address); // Unrestricted.
+
+ EXPECT_TRUE(bindings->DnsResolveEx("foo", &ip_address));
+ EXPECT_EQ("192.168.2.1", ip_address); // Unrestricted.
+
+ EXPECT_TRUE(bindings->DnsResolveEx("foo2", &ip_address));
+ EXPECT_EQ("192.168.2.2", ip_address); // Unrestricted.
}
// Test that myIpAddressEx() and dnsResolveEx() both return a semi-colon
@@ -177,12 +226,144 @@
scoped_ptr<ProxyResolverJSBindings> bindings(
ProxyResolverJSBindings::CreateDefault(host_resolver, NULL));
- EXPECT_EQ("192.168.1.1;172.22.34.1;200.100.1.2",
- bindings->MyIpAddressEx());
+ std::string ip_addresses;
- EXPECT_EQ("192.168.1.1;172.22.34.1;200.100.1.2",
- bindings->DnsResolveEx("FOO"));
+ EXPECT_TRUE(bindings->MyIpAddressEx(&ip_addresses));
+ EXPECT_EQ("192.168.1.1;172.22.34.1;200.100.1.2", ip_addresses);
+
+ EXPECT_TRUE(bindings->DnsResolveEx("FOO", &ip_addresses));
+ EXPECT_EQ("192.168.1.1;172.22.34.1;200.100.1.2", ip_addresses);
+}
+
+TEST(ProxyResolverJSBindingsTest, PerRequestDNSCache) {
+ scoped_refptr<MockFailingHostResolver> host_resolver(
+ new MockFailingHostResolver);
+
+ // Get a hold of a DefaultJSBindings* (it is a hidden impl class).
+ scoped_ptr<ProxyResolverJSBindings> bindings(
+ ProxyResolverJSBindings::CreateDefault(host_resolver, NULL));
+
+ std::string ip_address;
+
+ // Call DnsResolve() 4 times for the same hostname -- this should issue
+ // 4 separate calls to the underlying host resolver, since there is no
+ // current request context.
+ EXPECT_FALSE(bindings->DnsResolve("foo", &ip_address));
+ EXPECT_FALSE(bindings->DnsResolve("foo", &ip_address));
+ EXPECT_FALSE(bindings->DnsResolve("foo", &ip_address));
+ EXPECT_FALSE(bindings->DnsResolve("foo", &ip_address));
+ EXPECT_EQ(4, host_resolver->count());
+
+ host_resolver->ResetCount();
+
+ // Now setup a per-request context, and try the same experiment -- we
+ // expect the underlying host resolver to receive only 1 request this time,
+ // since it will service the others from the per-request DNS cache.
+ HostCache cache(50,
+ base::TimeDelta::FromMinutes(10),
+ base::TimeDelta::FromMinutes(10));
+ ProxyResolverRequestContext context(NULL, &cache);
+ bindings->set_current_request_context(&context);
+
+ EXPECT_FALSE(bindings->DnsResolve("foo", &ip_address));
+ EXPECT_FALSE(bindings->DnsResolve("foo", &ip_address));
+ EXPECT_FALSE(bindings->DnsResolve("foo", &ip_address));
+ EXPECT_FALSE(bindings->DnsResolve("foo", &ip_address));
+ EXPECT_EQ(1, host_resolver->count());
+
+ host_resolver->ResetCount();
+
+ // The "Ex" version shares this same cache, however since the flags
+ // are different it won't reuse this particular entry.
+ EXPECT_FALSE(bindings->DnsResolveEx("foo", &ip_address));
+ EXPECT_EQ(1, host_resolver->count());
+ EXPECT_FALSE(bindings->DnsResolveEx("foo", &ip_address));
+ EXPECT_FALSE(bindings->DnsResolveEx("foo", &ip_address));
+ EXPECT_EQ(1, host_resolver->count());
+
+ bindings->set_current_request_context(NULL);
+}
+
+// Test that when a binding is called, it logs to the per-request NetLog.
+TEST(ProxyResolverJSBindingsTest, NetLog) {
+ scoped_refptr<MockFailingHostResolver> host_resolver(
+ new MockFailingHostResolver);
+
+ CapturingNetLog global_log(CapturingNetLog::kUnbounded);
+
+ // Get a hold of a DefaultJSBindings* (it is a hidden impl class).
+ scoped_ptr<ProxyResolverJSBindings> bindings(
+ ProxyResolverJSBindings::CreateDefault(host_resolver, &global_log));
+
+ // Attach a capturing NetLog as the current request's log stream.
+ CapturingNetLog log(CapturingNetLog::kUnbounded);
+ BoundNetLog bound_log(NetLog::Source(NetLog::SOURCE_NONE, 0), &log);
+ ProxyResolverRequestContext context(&bound_log, NULL);
+ bindings->set_current_request_context(&context);
+
+ std::string ip_address;
+
+ ASSERT_EQ(0u, log.entries().size());
+
+ // Call all the bindings. Each call should be logging something to
+ // our NetLog.
+
+ bindings->MyIpAddress(&ip_address);
+ EXPECT_EQ(2u, log.entries().size());
+ EXPECT_TRUE(LogContainsBeginEvent(
+ log.entries(), 0, NetLog::TYPE_PAC_JAVASCRIPT_MY_IP_ADDRESS));
+ EXPECT_TRUE(LogContainsEndEvent(
+ log.entries(), 1, NetLog::TYPE_PAC_JAVASCRIPT_MY_IP_ADDRESS));
+
+ bindings->MyIpAddressEx(&ip_address);
+ EXPECT_EQ(4u, log.entries().size());
+ EXPECT_TRUE(LogContainsBeginEvent(
+ log.entries(), 2, NetLog::TYPE_PAC_JAVASCRIPT_MY_IP_ADDRESS_EX));
+ EXPECT_TRUE(LogContainsEndEvent(
+ log.entries(), 3, NetLog::TYPE_PAC_JAVASCRIPT_MY_IP_ADDRESS_EX));
+
+ bindings->DnsResolve("foo", &ip_address);
+ EXPECT_EQ(6u, log.entries().size());
+ EXPECT_TRUE(LogContainsBeginEvent(
+ log.entries(), 4, NetLog::TYPE_PAC_JAVASCRIPT_DNS_RESOLVE));
+ EXPECT_TRUE(LogContainsEndEvent(
+ log.entries(), 5, NetLog::TYPE_PAC_JAVASCRIPT_DNS_RESOLVE));
+
+ bindings->DnsResolveEx("foo", &ip_address);
+ EXPECT_EQ(8u, log.entries().size());
+ EXPECT_TRUE(LogContainsBeginEvent(
+ log.entries(), 6, NetLog::TYPE_PAC_JAVASCRIPT_DNS_RESOLVE_EX));
+ EXPECT_TRUE(LogContainsEndEvent(
+ log.entries(), 7, NetLog::TYPE_PAC_JAVASCRIPT_DNS_RESOLVE_EX));
+
+ // Nothing has been emitted globally yet.
+ EXPECT_EQ(0u, global_log.entries().size());
+
+ bindings->OnError(30, string16());
+ EXPECT_EQ(9u, log.entries().size());
+ EXPECT_TRUE(LogContainsEvent(
+ log.entries(), 8, NetLog::TYPE_PAC_JAVASCRIPT_ERROR,
+ NetLog::PHASE_NONE));
+
+ // We also emit errors to the top-level log stream.
+ EXPECT_EQ(1u, global_log.entries().size());
+ EXPECT_TRUE(LogContainsEvent(
+ global_log.entries(), 0, NetLog::TYPE_PAC_JAVASCRIPT_ERROR,
+ NetLog::PHASE_NONE));
+
+ bindings->Alert(string16());
+ EXPECT_EQ(10u, log.entries().size());
+ EXPECT_TRUE(LogContainsEvent(
+ log.entries(), 9, NetLog::TYPE_PAC_JAVASCRIPT_ALERT,
+ NetLog::PHASE_NONE));
+
+ // We also emit javascript alerts to the top-level log stream.
+ EXPECT_EQ(2u, global_log.entries().size());
+ EXPECT_TRUE(LogContainsEvent(
+ global_log.entries(), 1, NetLog::TYPE_PAC_JAVASCRIPT_ALERT,
+ NetLog::PHASE_NONE));
}
} // namespace
+
} // namespace net
diff --git a/net/proxy/proxy_resolver_mac.cc b/net/proxy/proxy_resolver_mac.cc
index 6baf78f..8e8ef20 100644
--- a/net/proxy/proxy_resolver_mac.cc
+++ b/net/proxy/proxy_resolver_mac.cc
@@ -59,7 +59,7 @@
ProxyInfo* results,
CompletionCallback* /*callback*/,
RequestHandle* /*request*/,
- LoadLog* load_log) {
+ const BoundNetLog& net_log) {
scoped_cftyperef<CFStringRef> query_ref(
base::SysUTF8ToCFStringRef(query_url.spec()));
scoped_cftyperef<CFURLRef> query_url_ref(
@@ -69,7 +69,9 @@
if (!query_url_ref.get())
return ERR_FAILED;
scoped_cftyperef<CFStringRef> pac_ref(
- base::SysUTF8ToCFStringRef(pac_url_.spec()));
+ base::SysUTF8ToCFStringRef(
+ script_data_->type() == ProxyResolverScriptData::TYPE_AUTO_DETECT ?
+ std::string() : script_data_->url().spec()));
scoped_cftyperef<CFURLRef> pac_url_ref(
CFURLCreateWithString(kCFAllocatorDefault,
pac_ref.get(),
diff --git a/net/proxy/proxy_resolver_mac.h b/net/proxy/proxy_resolver_mac.h
index 6676b2f..cacf6c8 100644
--- a/net/proxy/proxy_resolver_mac.h
+++ b/net/proxy/proxy_resolver_mac.h
@@ -24,21 +24,21 @@
ProxyInfo* results,
CompletionCallback* callback,
RequestHandle* request,
- LoadLog* load_log);
+ const BoundNetLog& net_log);
virtual void CancelRequest(RequestHandle request) {
NOTREACHED();
}
- private:
- virtual int SetPacScript(const GURL& pac_url,
- const std::string& /*pac_bytes*/,
- CompletionCallback* /*callback*/) {
- pac_url_ = pac_url;
+ virtual int SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ CompletionCallback* /*callback*/) {
+ script_data_ = script_data_;
return OK;
}
- GURL pac_url_;
+ private:
+ scoped_refptr<ProxyResolverScriptData> script_data_;
};
} // namespace net
diff --git a/net/proxy/proxy_resolver_perftest.cc b/net/proxy/proxy_resolver_perftest.cc
index 6e336e9..0c57955 100644
--- a/net/proxy/proxy_resolver_perftest.cc
+++ b/net/proxy/proxy_resolver_perftest.cc
@@ -1,8 +1,9 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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/perftimer.h"
+#include "base/string_util.h"
#include "net/base/mock_host_resolver.h"
#include "net/proxy/proxy_resolver_js_bindings.h"
#include "net/proxy/proxy_resolver_v8.h"
@@ -97,7 +98,8 @@
InitHttpServer();
GURL pac_url =
server_->TestServerPage(std::string("files/") + script_name);
- int rv = resolver_->SetPacScriptByUrl(pac_url, NULL);
+ int rv = resolver_->SetPacScript(
+ net::ProxyResolverScriptData::FromURL(pac_url), NULL);
EXPECT_EQ(net::OK, rv);
} else {
LoadPacScriptIntoResolver(script_name);
@@ -109,7 +111,8 @@
{
net::ProxyInfo proxy_info;
int result = resolver_->GetProxyForURL(
- GURL("http://www.warmup.com"), &proxy_info, NULL, NULL, NULL);
+ GURL("http://www.warmup.com"), &proxy_info, NULL, NULL,
+ net::BoundNetLog());
ASSERT_EQ(net::OK, result);
}
@@ -124,7 +127,8 @@
// Resolve.
net::ProxyInfo proxy_info;
int result = resolver_->GetProxyForURL(GURL(query.query_url),
- &proxy_info, NULL, NULL, NULL);
+ &proxy_info, NULL, NULL,
+ net::BoundNetLog());
// Check that the result was correct. Note that ToPacString() and
// ASSERT_EQ() are fast, so they won't skew the results.
@@ -164,7 +168,8 @@
ASSERT_TRUE(ok);
// Load the PAC script into the ProxyResolver.
- int rv = resolver_->SetPacScriptByData(file_contents, NULL);
+ int rv = resolver_->SetPacScript(
+ net::ProxyResolverScriptData::FromUTF8(file_contents), NULL);
EXPECT_EQ(net::OK, rv);
}
@@ -196,3 +201,4 @@
PacPerfSuiteRunner runner(&resolver, "ProxyResolverV8");
runner.RunAllTests();
}
+
diff --git a/net/proxy/proxy_resolver_request_context.h b/net/proxy/proxy_resolver_request_context.h
new file mode 100644
index 0000000..fdcced1
--- /dev/null
+++ b/net/proxy/proxy_resolver_request_context.h
@@ -0,0 +1,31 @@
+// 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.
+
+#ifndef NET_PROXY_PROXY_RESOLVER_REQUEST_CONTEXT_H_
+#define NET_PROXY_PROXY_RESOLVER_REQUEST_CONTEXT_H_
+
+namespace net {
+
+class HostCache;
+class BoundNetLog;
+
+// This data structure holds state related to an invocation of
+// "FindProxyForURL()". It is used to associate per-request
+// data that can be retrieved by the bindings.
+struct ProxyResolverRequestContext {
+ // All of these pointers are expected to remain valid for duration of
+ // this instance's lifetime.
+ ProxyResolverRequestContext(const BoundNetLog* net_log,
+ HostCache* host_cache)
+ : net_log(net_log),
+ host_cache(host_cache) {
+ }
+
+ const BoundNetLog* net_log;
+ HostCache* host_cache;
+};
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_RESOLVER_REQUEST_CONTEXT_H_
diff --git a/net/proxy/proxy_resolver_script_data.cc b/net/proxy/proxy_resolver_script_data.cc
new file mode 100644
index 0000000..fd8c399
--- /dev/null
+++ b/net/proxy/proxy_resolver_script_data.cc
@@ -0,0 +1,48 @@
+// 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 "net/proxy/proxy_resolver_script_data.h"
+
+#include "base/logging.h"
+#include "base/utf_string_conversions.h"
+
+namespace net {
+
+// static
+scoped_refptr<ProxyResolverScriptData> ProxyResolverScriptData::FromUTF8(
+ const std::string& utf8) {
+ return new ProxyResolverScriptData(TYPE_SCRIPT_CONTENTS,
+ GURL(),
+ UTF8ToUTF16(utf8));
+}
+
+// static
+scoped_refptr<ProxyResolverScriptData> ProxyResolverScriptData::FromUTF16(
+ const string16& utf16) {
+ return new ProxyResolverScriptData(TYPE_SCRIPT_CONTENTS, GURL(), utf16);
+}
+
+// static
+scoped_refptr<ProxyResolverScriptData> ProxyResolverScriptData::FromURL(
+ const GURL& url) {
+ return new ProxyResolverScriptData(TYPE_SCRIPT_URL, url, string16());
+}
+
+// static
+scoped_refptr<ProxyResolverScriptData>
+ProxyResolverScriptData::ForAutoDetect() {
+ return new ProxyResolverScriptData(TYPE_AUTO_DETECT, GURL(), string16());
+}
+
+const string16& ProxyResolverScriptData::utf16() const {
+ DCHECK_EQ(TYPE_SCRIPT_CONTENTS, type_);
+ return utf16_;
+}
+
+const GURL& ProxyResolverScriptData::url() const {
+ DCHECK_EQ(TYPE_SCRIPT_URL, type_);
+ return url_;
+}
+
+} // namespace net
diff --git a/net/proxy/proxy_resolver_script_data.h b/net/proxy/proxy_resolver_script_data.h
new file mode 100644
index 0000000..f0bb2ee
--- /dev/null
+++ b/net/proxy/proxy_resolver_script_data.h
@@ -0,0 +1,71 @@
+// 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.
+
+#ifndef NET_PROXY_PROXY_RESOLVER_SCRIPT_DATA_H_
+#define NET_PROXY_PROXY_RESOLVER_SCRIPT_DATA_H_
+
+#include "base/ref_counted.h"
+#include "base/string16.h"
+#include "googleurl/src/gurl.h"
+
+namespace net {
+
+// Reference-counted wrapper for passing around a PAC script specification.
+// The PAC script can be either specified via a URL, a deferred URL for
+// auto-detect, or the actual javascript program text.
+//
+// This is thread-safe so it can be used by multi-threaded implementations of
+// ProxyResolver to share the data between threads.
+class ProxyResolverScriptData
+ : public base::RefCountedThreadSafe<ProxyResolverScriptData> {
+ public:
+ enum Type {
+ TYPE_SCRIPT_CONTENTS,
+ TYPE_SCRIPT_URL,
+ TYPE_AUTO_DETECT,
+ };
+
+ // Creates a script data given the UTF8 bytes of the content.
+ static scoped_refptr<ProxyResolverScriptData> FromUTF8(
+ const std::string& utf8);
+
+ // Creates a script data given the UTF16 bytes of the content.
+ static scoped_refptr<ProxyResolverScriptData> FromUTF16(
+ const string16& utf16);
+
+ // Creates a script data given a URL to the PAC script.
+ static scoped_refptr<ProxyResolverScriptData> FromURL(const GURL& url);
+
+ // Creates a script data for using an automatically detected PAC URL.
+ static scoped_refptr<ProxyResolverScriptData> ForAutoDetect();
+
+ Type type() const {
+ return type_;
+ }
+
+ // Returns the contents of the script as UTF16.
+ // (only valid for type() == TYPE_SCRIPT_CONTENTS).
+ const string16& utf16() const;
+
+ // Returns the URL of the script.
+ // (only valid for type() == TYPE_SCRIPT_URL).
+ const GURL& url() const;
+
+ private:
+ ProxyResolverScriptData(Type type,
+ const GURL& url,
+ const string16& utf16)
+ : type_(type),
+ url_(url),
+ utf16_(utf16) {
+ }
+
+ const Type type_;
+ const GURL url_;
+ const string16 utf16_;
+};
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_RESOLVER_SCRIPT_DATA_H_
diff --git a/net/proxy/proxy_resolver_v8.cc b/net/proxy/proxy_resolver_v8.cc
index 4db6bc3..495bbe9 100644
--- a/net/proxy/proxy_resolver_v8.cc
+++ b/net/proxy/proxy_resolver_v8.cc
@@ -1,16 +1,21 @@
-// Copyright (c) 2009 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.
+// 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 "net/proxy/proxy_resolver_v8.h"
+#include "base/basictypes.h"
#include "base/logging.h"
#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
#include "googleurl/src/gurl.h"
-#include "net/base/load_log.h"
+#include "googleurl/src/url_canon.h"
+#include "net/base/host_cache.h"
#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
#include "net/proxy/proxy_info.h"
#include "net/proxy/proxy_resolver_js_bindings.h"
+#include "net/proxy/proxy_resolver_request_context.h"
#include "net/proxy/proxy_resolver_script.h"
#include "v8/include/v8.h"
@@ -68,22 +73,100 @@
// Pseudo-name for the PAC utility script.
const char kPacUtilityResourceName[] = "proxy-pac-utility-script.js";
-// Convert a V8 String to a std::string.
-std::string V8StringToStdString(v8::Handle<v8::String> s) {
- int len = s->Utf8Length();
- std::string result;
- s->WriteUtf8(WriteInto(&result, len + 1), len);
+// External string wrapper so V8 can access the UTF16 string wrapped by
+// ProxyResolverScriptData.
+class V8ExternalStringFromScriptData
+ : public v8::String::ExternalStringResource {
+ public:
+ explicit V8ExternalStringFromScriptData(
+ const scoped_refptr<ProxyResolverScriptData>& script_data)
+ : script_data_(script_data) {}
+
+ virtual const uint16_t* data() const {
+ return reinterpret_cast<const uint16*>(script_data_->utf16().data());
+ }
+
+ virtual size_t length() const {
+ return script_data_->utf16().size();
+ }
+
+ private:
+ const scoped_refptr<ProxyResolverScriptData> script_data_;
+ DISALLOW_COPY_AND_ASSIGN(V8ExternalStringFromScriptData);
+};
+
+// External string wrapper so V8 can access a string literal.
+class V8ExternalASCIILiteral : public v8::String::ExternalAsciiStringResource {
+ public:
+ // |ascii| must be a NULL-terminated C string, and must remain valid
+ // throughout this object's lifetime.
+ V8ExternalASCIILiteral(const char* ascii, size_t length)
+ : ascii_(ascii), length_(length) {
+ DCHECK(IsStringASCII(ascii));
+ }
+
+ virtual const char* data() const {
+ return ascii_;
+ }
+
+ virtual size_t length() const {
+ return length_;
+ }
+
+ private:
+ const char* ascii_;
+ size_t length_;
+ DISALLOW_COPY_AND_ASSIGN(V8ExternalASCIILiteral);
+};
+
+// When creating a v8::String from a C++ string we have two choices: create
+// a copy, or create a wrapper that shares the same underlying storage.
+// For small strings it is better to just make a copy, whereas for large
+// strings there are savings by sharing the storage. This number identifies
+// the cutoff length for when to start wrapping rather than creating copies.
+const size_t kMaxStringBytesForCopy = 256;
+
+// Converts a V8 String to a UTF16 string16.
+string16 V8StringToUTF16(v8::Handle<v8::String> s) {
+ int len = s->Length();
+ string16 result;
+ // Note that the reinterpret cast is because on Windows string16 is an alias
+ // to wstring, and hence has character type wchar_t not uint16_t.
+ s->Write(reinterpret_cast<uint16_t*>(WriteInto(&result, len + 1)), 0, len);
return result;
}
-// Convert a std::string (UTF8) to a V8 string.
-v8::Local<v8::String> StdStringToV8String(const std::string& s) {
+// Converts an ASCII std::string to a V8 string.
+v8::Local<v8::String> ASCIIStringToV8String(const std::string& s) {
+ DCHECK(IsStringASCII(s));
return v8::String::New(s.data(), s.size());
}
-// String-ize a V8 object by calling its toString() method. Returns true
+// Converts a UTF16 string16 (warpped by a ProxyResolverScriptData) to a
+// V8 string.
+v8::Local<v8::String> ScriptDataToV8String(
+ const scoped_refptr<ProxyResolverScriptData>& s) {
+ if (s->utf16().size() * 2 <= kMaxStringBytesForCopy) {
+ return v8::String::New(
+ reinterpret_cast<const uint16_t*>(s->utf16().data()),
+ s->utf16().size());
+ }
+ return v8::String::NewExternal(new V8ExternalStringFromScriptData(s));
+}
+
+// Converts an ASCII string literal to a V8 string.
+v8::Local<v8::String> ASCIILiteralToV8String(const char* ascii) {
+ DCHECK(IsStringASCII(ascii));
+ size_t length = strlen(ascii);
+ if (length <= kMaxStringBytesForCopy)
+ return v8::String::New(ascii, length);
+ return v8::String::NewExternal(new V8ExternalASCIILiteral(ascii, length));
+}
+
+// Stringizes a V8 object by calling its toString() method. Returns true
// on success. This may fail if the toString() throws an exception.
-bool V8ObjectToString(v8::Handle<v8::Value> object, std::string* result) {
+bool V8ObjectToUTF16String(v8::Handle<v8::Value> object,
+ string16* utf16_result) {
if (object.IsEmpty())
return false;
@@ -91,10 +174,45 @@
v8::Local<v8::String> str_object = object->ToString();
if (str_object.IsEmpty())
return false;
- *result = V8StringToStdString(str_object);
+ *utf16_result = V8StringToUTF16(str_object);
return true;
}
+// Extracts an hostname argument from |args|. On success returns true
+// and fills |*hostname| with the result.
+bool GetHostnameArgument(const v8::Arguments& args, std::string* hostname) {
+ // The first argument should be a string.
+ if (args.Length() == 0 || args[0].IsEmpty() || !args[0]->IsString())
+ return false;
+
+ const string16 hostname_utf16 = V8StringToUTF16(args[0]->ToString());
+
+ // If the hostname is already in ASCII, simply return it as is.
+ if (IsStringASCII(hostname_utf16)) {
+ *hostname = UTF16ToASCII(hostname_utf16);
+ return true;
+ }
+
+ // Otherwise try to convert it from IDN to punycode.
+ const int kInitialBufferSize = 256;
+ url_canon::RawCanonOutputT<char16, kInitialBufferSize> punycode_output;
+ if (!url_canon::IDNToASCII(hostname_utf16.data(),
+ hostname_utf16.length(),
+ &punycode_output)) {
+ return false;
+ }
+
+ // |punycode_output| should now be ASCII; convert it to a std::string.
+ // (We could use UTF16ToASCII() instead, but that requires an extra string
+ // copy. Since ASCII is a subset of UTF8 the following is equivalent).
+ bool success = UTF16ToUTF8(punycode_output.data(),
+ punycode_output.length(),
+ hostname);
+ DCHECK(success);
+ DCHECK(IsStringASCII(*hostname));
+ return success;
+}
+
} // namespace
// ProxyResolverV8::Context ---------------------------------------------------
@@ -102,7 +220,7 @@
class ProxyResolverV8::Context {
public:
explicit Context(ProxyResolverJSBindings* js_bindings)
- : js_bindings_(js_bindings), current_request_load_log_(NULL) {
+ : js_bindings_(js_bindings) {
DCHECK(js_bindings != NULL);
}
@@ -111,6 +229,12 @@
v8_this_.Dispose();
v8_context_.Dispose();
+
+ // Run the V8 garbage collector. We do this to be sure the
+ // ExternalStringResource objects we allocated get properly disposed.
+ // Otherwise when running the unit-tests they may get leaked.
+ // See crbug.com/48145.
+ PurgeMemory();
}
int ResolveProxy(const GURL& query_url, ProxyInfo* results) {
@@ -121,13 +245,14 @@
v8::Local<v8::Value> function;
if (!GetFindProxyForURL(&function)) {
- js_bindings_->OnError(-1, "FindProxyForURL() is undefined.");
+ js_bindings_->OnError(
+ -1, ASCIIToUTF16("FindProxyForURL() is undefined."));
return ERR_PAC_SCRIPT_FAILED;
}
v8::Handle<v8::Value> argv[] = {
- StdStringToV8String(query_url.spec()),
- StdStringToV8String(query_url.host()),
+ ASCIIStringToV8String(query_url.spec()),
+ ASCIIStringToV8String(query_url.host()),
};
v8::TryCatch try_catch;
@@ -140,18 +265,30 @@
}
if (!ret->IsString()) {
- js_bindings_->OnError(-1, "FindProxyForURL() did not return a string.");
+ js_bindings_->OnError(
+ -1, ASCIIToUTF16("FindProxyForURL() did not return a string."));
return ERR_PAC_SCRIPT_FAILED;
}
- std::string ret_str = V8StringToStdString(ret->ToString());
+ string16 ret_str = V8StringToUTF16(ret->ToString());
- results->UsePacString(ret_str);
+ if (!IsStringASCII(ret_str)) {
+ // TODO(eroman): Rather than failing when a wide string is returned, we
+ // could extend the parsing to handle IDNA hostnames by
+ // converting them to ASCII punycode.
+ // crbug.com/47234
+ string16 error_message =
+ ASCIIToUTF16("FindProxyForURL() returned a non-ASCII string "
+ "(crbug.com/47234): ") + ret_str;
+ js_bindings_->OnError(-1, error_message);
+ return ERR_PAC_SCRIPT_FAILED;
+ }
+ results->UsePacString(UTF16ToASCII(ret_str));
return OK;
}
- int InitV8(const std::string& pac_data_utf8) {
+ int InitV8(const scoped_refptr<ProxyResolverScriptData>& pac_script) {
v8::Locker locked;
v8::HandleScope scope;
@@ -161,28 +298,28 @@
// Attach the javascript bindings.
v8::Local<v8::FunctionTemplate> alert_template =
v8::FunctionTemplate::New(&AlertCallback, v8_this_);
- global_template->Set(v8::String::New("alert"), alert_template);
+ global_template->Set(ASCIILiteralToV8String("alert"), alert_template);
v8::Local<v8::FunctionTemplate> my_ip_address_template =
v8::FunctionTemplate::New(&MyIpAddressCallback, v8_this_);
- global_template->Set(v8::String::New("myIpAddress"),
+ global_template->Set(ASCIILiteralToV8String("myIpAddress"),
my_ip_address_template);
v8::Local<v8::FunctionTemplate> dns_resolve_template =
v8::FunctionTemplate::New(&DnsResolveCallback, v8_this_);
- global_template->Set(v8::String::New("dnsResolve"),
+ global_template->Set(ASCIILiteralToV8String("dnsResolve"),
dns_resolve_template);
// Microsoft's PAC extensions (incomplete):
v8::Local<v8::FunctionTemplate> dns_resolve_ex_template =
v8::FunctionTemplate::New(&DnsResolveExCallback, v8_this_);
- global_template->Set(v8::String::New("dnsResolveEx"),
+ global_template->Set(ASCIILiteralToV8String("dnsResolveEx"),
dns_resolve_ex_template);
v8::Local<v8::FunctionTemplate> my_ip_address_ex_template =
v8::FunctionTemplate::New(&MyIpAddressExCallback, v8_this_);
- global_template->Set(v8::String::New("myIpAddressEx"),
+ global_template->Set(ASCIILiteralToV8String("myIpAddressEx"),
my_ip_address_ex_template);
v8_context_ = v8::Context::New(NULL, global_template);
@@ -192,16 +329,18 @@
// Add the PAC utility functions to the environment.
// (This script should never fail, as it is a string literal!)
// Note that the two string literals are concatenated.
- int rv = RunScript(PROXY_RESOLVER_SCRIPT
- PROXY_RESOLVER_SCRIPT_EX,
- kPacUtilityResourceName);
+ int rv = RunScript(
+ ASCIILiteralToV8String(
+ PROXY_RESOLVER_SCRIPT
+ PROXY_RESOLVER_SCRIPT_EX),
+ kPacUtilityResourceName);
if (rv != OK) {
NOTREACHED();
return rv;
}
// Add the user's PAC code to the environment.
- rv = RunScript(pac_data_utf8, kPacResourceName);
+ rv = RunScript(ScriptDataToV8String(pac_script), kPacResourceName);
if (rv != OK)
return rv;
@@ -214,8 +353,8 @@
return OK;
}
- void SetCurrentRequestLoadLog(LoadLog* load_log) {
- current_request_load_log_ = load_log;
+ void SetCurrentRequestContext(ProxyResolverRequestContext* context) {
+ js_bindings_->set_current_request_context(context);
}
void PurgeMemory() {
@@ -230,7 +369,8 @@
private:
bool GetFindProxyForURL(v8::Local<v8::Value>* function) {
- *function = v8_context_->Global()->Get(v8::String::New("FindProxyForURL"));
+ *function = v8_context_->Global()->Get(
+ ASCIILiteralToV8String("FindProxyForURL"));
return (*function)->IsFunction();
}
@@ -241,20 +381,20 @@
// Otherwise dispatch to the bindings.
int line_number = message->GetLineNumber();
- std::string error_message;
- V8ObjectToString(message->Get(), &error_message);
+ string16 error_message;
+ V8ObjectToUTF16String(message->Get(), &error_message);
js_bindings_->OnError(line_number, error_message);
}
- // Compiles and runs |script_utf8| in the current V8 context.
+ // Compiles and runs |script| in the current V8 context.
// Returns OK on success, otherwise an error code.
- int RunScript(const std::string& script_utf8, const char* script_name) {
+ int RunScript(v8::Handle<v8::String> script, const char* script_name) {
v8::TryCatch try_catch;
// Compile the script.
- v8::Local<v8::String> text = StdStringToV8String(script_utf8);
- v8::ScriptOrigin origin = v8::ScriptOrigin(v8::String::New(script_name));
- v8::Local<v8::Script> code = v8::Script::Compile(text, &origin);
+ v8::ScriptOrigin origin =
+ v8::ScriptOrigin(ASCIILiteralToV8String(script_name));
+ v8::Local<v8::Script> code = v8::Script::Compile(script, &origin);
// Execute.
if (!code.IsEmpty())
@@ -276,11 +416,11 @@
// Like firefox we assume "undefined" if no argument was specified, and
// disregard any arguments beyond the first.
- std::string message;
+ string16 message;
if (args.Length() == 0) {
- message = "undefined";
+ message = ASCIIToUTF16("undefined");
} else {
- if (!V8ObjectToString(args[0], &message))
+ if (!V8ObjectToUTF16String(args[0], &message))
return v8::Undefined(); // toString() threw an exception.
}
@@ -293,19 +433,20 @@
Context* context =
static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
- LoadLog::BeginEvent(context->current_request_load_log_,
- LoadLog::TYPE_PROXY_RESOLVER_V8_MY_IP_ADDRESS);
+ std::string result;
+ bool success;
- // We shouldn't be called with any arguments, but will not complain if
- // we are.
- std::string result = context->js_bindings_->MyIpAddress();
+ {
+ v8::Unlocker unlocker;
- LoadLog::EndEvent(context->current_request_load_log_,
- LoadLog::TYPE_PROXY_RESOLVER_V8_MY_IP_ADDRESS);
+ // We shouldn't be called with any arguments, but will not complain if
+ // we are.
+ success = context->js_bindings_->MyIpAddress(&result);
+ }
- if (result.empty())
- result = "127.0.0.1";
- return StdStringToV8String(result);
+ if (!success)
+ return ASCIILiteralToV8String("127.0.0.1");
+ return ASCIIStringToV8String(result);
}
// V8 callback for when "myIpAddressEx()" is invoked by the PAC script.
@@ -314,17 +455,20 @@
Context* context =
static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
- LoadLog::BeginEvent(context->current_request_load_log_,
- LoadLog::TYPE_PROXY_RESOLVER_V8_MY_IP_ADDRESS_EX);
+ std::string ip_address_list;
+ bool success;
- // We shouldn't be called with any arguments, but will not complain if
- // we are.
- std::string result = context->js_bindings_->MyIpAddressEx();
+ {
+ v8::Unlocker unlocker;
- LoadLog::EndEvent(context->current_request_load_log_,
- LoadLog::TYPE_PROXY_RESOLVER_V8_MY_IP_ADDRESS_EX);
+ // We shouldn't be called with any arguments, but will not complain if
+ // we are.
+ success = context->js_bindings_->MyIpAddressEx(&ip_address_list);
+ }
- return StdStringToV8String(result);
+ if (!success)
+ ip_address_list = std::string();
+ return ASCIIStringToV8String(ip_address_list);
}
// V8 callback for when "dnsResolve()" is invoked by the PAC script.
@@ -332,25 +476,20 @@
Context* context =
static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
- // We need at least one argument.
- std::string host;
- if (args.Length() == 0) {
- host = "undefined";
- } else {
- if (!V8ObjectToString(args[0], &host))
- return v8::Undefined();
+ // We need at least one string argument.
+ std::string hostname;
+ if (!GetHostnameArgument(args, &hostname))
+ return v8::Null();
+
+ std::string ip_address;
+ bool success;
+
+ {
+ v8::Unlocker unlocker;
+ success = context->js_bindings_->DnsResolve(hostname, &ip_address);
}
- LoadLog::BeginEvent(context->current_request_load_log_,
- LoadLog::TYPE_PROXY_RESOLVER_V8_DNS_RESOLVE);
-
- std::string result = context->js_bindings_->DnsResolve(host);
-
- LoadLog::EndEvent(context->current_request_load_log_,
- LoadLog::TYPE_PROXY_RESOLVER_V8_DNS_RESOLVE);
-
- // DnsResolve() returns empty string on failure.
- return result.empty() ? v8::Null() : StdStringToV8String(result);
+ return success ? ASCIIStringToV8String(ip_address) : v8::Null();
}
// V8 callback for when "dnsResolveEx()" is invoked by the PAC script.
@@ -358,28 +497,27 @@
Context* context =
static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
- // We need at least one argument.
- std::string host;
- if (args.Length() == 0) {
- host = "undefined";
- } else {
- if (!V8ObjectToString(args[0], &host))
- return v8::Undefined();
+ // We need at least one string argument.
+ std::string hostname;
+ if (!GetHostnameArgument(args, &hostname))
+ return v8::Undefined();
+
+ std::string ip_address_list;
+ bool success;
+
+ {
+ v8::Unlocker unlocker;
+ success = context->js_bindings_->DnsResolveEx(hostname,
+ &ip_address_list);
}
- LoadLog::BeginEvent(context->current_request_load_log_,
- LoadLog::TYPE_PROXY_RESOLVER_V8_DNS_RESOLVE_EX);
+ if (!success)
+ ip_address_list = std::string();
- std::string result = context->js_bindings_->DnsResolveEx(host);
-
- LoadLog::EndEvent(context->current_request_load_log_,
- LoadLog::TYPE_PROXY_RESOLVER_V8_DNS_RESOLVE_EX);
-
- return StdStringToV8String(result);
+ return ASCIIStringToV8String(ip_address_list);
}
ProxyResolverJSBindings* js_bindings_;
- LoadLog* current_request_load_log_;
v8::Persistent<v8::External> v8_this_;
v8::Persistent<v8::Context> v8_context_;
};
@@ -398,16 +536,29 @@
ProxyInfo* results,
CompletionCallback* /*callback*/,
RequestHandle* /*request*/,
- LoadLog* load_log) {
+ const BoundNetLog& net_log) {
// If the V8 instance has not been initialized (either because
// SetPacScript() wasn't called yet, or because it failed.
if (!context_.get())
return ERR_FAILED;
+ // Associate some short-lived context with this request. This context will be
+ // available to any of the javascript "bindings" that are subsequently invoked
+ // from the javascript.
+ //
+ // In particular, we create a HostCache that is aggressive about caching
+ // failed DNS resolves.
+ HostCache host_cache(
+ 50,
+ base::TimeDelta::FromMinutes(5),
+ base::TimeDelta::FromMinutes(5));
+
+ ProxyResolverRequestContext request_context(&net_log, &host_cache);
+
// Otherwise call into V8.
- context_->SetCurrentRequestLoadLog(load_log);
+ context_->SetCurrentRequestContext(&request_context);
int rv = context_->ResolveProxy(query_url, results);
- context_->SetCurrentRequestLoadLog(NULL);
+ context_->SetCurrentRequestContext(NULL);
return rv;
}
@@ -421,16 +572,21 @@
context_->PurgeMemory();
}
-int ProxyResolverV8::SetPacScript(const GURL& /*url*/,
- const std::string& bytes_utf8,
- CompletionCallback* /*callback*/) {
+void ProxyResolverV8::Shutdown() {
+ js_bindings_->Shutdown();
+}
+
+int ProxyResolverV8::SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ CompletionCallback* /*callback*/) {
+ DCHECK(script_data.get());
context_.reset();
- if (bytes_utf8.empty())
+ if (script_data->utf16().empty())
return ERR_PAC_SCRIPT_FAILED;
// Try parsing the PAC script.
scoped_ptr<Context> context(new Context(js_bindings_.get()));
- int rv = context->InitV8(bytes_utf8);
+ int rv = context->InitV8(script_data);
if (rv == OK)
context_.reset(context.release());
return rv;
diff --git a/net/proxy/proxy_resolver_v8.h b/net/proxy/proxy_resolver_v8.h
index 236beab..8d7a231 100644
--- a/net/proxy/proxy_resolver_v8.h
+++ b/net/proxy/proxy_resolver_v8.h
@@ -5,8 +5,6 @@
#ifndef NET_PROXY_PROXY_RESOLVER_V8_H_
#define NET_PROXY_PROXY_RESOLVER_V8_H_
-#include <string>
-
#include "base/scoped_ptr.h"
#include "net/proxy/proxy_resolver.h"
@@ -49,9 +47,13 @@
ProxyInfo* results,
CompletionCallback* /*callback*/,
RequestHandle* /*request*/,
- LoadLog* load_log);
+ const BoundNetLog& net_log);
virtual void CancelRequest(RequestHandle request);
virtual void PurgeMemory();
+ virtual void Shutdown();
+ virtual int SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ CompletionCallback* /*callback*/);
ProxyResolverJSBindings* js_bindings() const { return js_bindings_.get(); }
@@ -60,11 +62,6 @@
// script. It corresponds with the data from the last call to
// SetPacScript().
class Context;
-
- // ProxyResolver implementation:
- virtual int SetPacScript(const GURL& /*pac_url*/,
- const std::string& bytes_utf8,
- CompletionCallback* /*callback*/);
scoped_ptr<Context> context_;
scoped_ptr<ProxyResolverJSBindings> js_bindings_;
diff --git a/net/proxy/proxy_resolver_v8_unittest.cc b/net/proxy/proxy_resolver_v8_unittest.cc
index ee536b0..f0bfa1d 100644
--- a/net/proxy/proxy_resolver_v8_unittest.cc
+++ b/net/proxy/proxy_resolver_v8_unittest.cc
@@ -3,11 +3,12 @@
// found in the LICENSE file.
#include "base/file_util.h"
-#include "base/string_util.h"
#include "base/path_service.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
#include "googleurl/src/gurl.h"
-#include "net/base/load_log_unittest.h"
#include "net/base/net_errors.h"
+#include "net/base/net_log_unittest.h"
#include "net/proxy/proxy_info.h"
#include "net/proxy/proxy_resolver_js_bindings.h"
#include "net/proxy/proxy_resolver_v8.h"
@@ -23,39 +24,46 @@
public:
MockJSBindings() : my_ip_address_count(0), my_ip_address_ex_count(0) {}
- virtual void Alert(const std::string& message) {
+ virtual void Alert(const string16& message) {
LOG(INFO) << "PAC-alert: " << message; // Helpful when debugging.
- alerts.push_back(message);
+ alerts.push_back(UTF16ToUTF8(message));
}
- virtual std::string MyIpAddress() {
+ virtual bool MyIpAddress(std::string* ip_address) {
my_ip_address_count++;
- return my_ip_address_result;
+ *ip_address = my_ip_address_result;
+ return !my_ip_address_result.empty();
}
- virtual std::string MyIpAddressEx() {
+ virtual bool MyIpAddressEx(std::string* ip_address_list) {
my_ip_address_ex_count++;
- return my_ip_address_ex_result;
+ *ip_address_list = my_ip_address_ex_result;
+ return !my_ip_address_ex_result.empty();
}
- virtual std::string DnsResolve(const std::string& host) {
+ virtual bool DnsResolve(const std::string& host, std::string* ip_address) {
dns_resolves.push_back(host);
- return dns_resolve_result;
+ *ip_address = dns_resolve_result;
+ return !dns_resolve_result.empty();
}
- virtual std::string DnsResolveEx(const std::string& host) {
+ virtual bool DnsResolveEx(const std::string& host,
+ std::string* ip_address_list) {
dns_resolves_ex.push_back(host);
- return dns_resolve_ex_result;
+ *ip_address_list = dns_resolve_ex_result;
+ return !dns_resolve_ex_result.empty();
}
- virtual void OnError(int line_number, const std::string& message) {
+ virtual void OnError(int line_number, const string16& message) {
// Helpful when debugging.
LOG(INFO) << "PAC-error: [" << line_number << "] " << message;
- errors.push_back(message);
+ errors.push_back(UTF16ToUTF8(message));
errors_line_number.push_back(line_number);
}
+ virtual void Shutdown() {}
+
// Mock values to return.
std::string my_ip_address_result;
std::string my_ip_address_ex_result;
@@ -103,7 +111,8 @@
}
// Load the PAC script into the ProxyResolver.
- return SetPacScriptByData(file_contents, NULL);
+ return SetPacScript(ProxyResolverScriptData::FromUTF8(file_contents),
+ NULL);
}
};
@@ -118,8 +127,9 @@
EXPECT_EQ(OK, result);
ProxyInfo proxy_info;
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
- result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, log);
+ CapturingBoundNetLog log(CapturingNetLog::kUnbounded);
+ result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL,
+ log.bound());
EXPECT_EQ(OK, result);
EXPECT_TRUE(proxy_info.is_direct());
@@ -128,7 +138,7 @@
EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size());
// No bindings were called, so no log entries.
- EXPECT_EQ(0u, log->entries().size());
+ EXPECT_EQ(0u, log.entries().size());
}
TEST(ProxyResolverV8Test, ReturnEmptyString) {
@@ -137,7 +147,8 @@
EXPECT_EQ(OK, result);
ProxyInfo proxy_info;
- result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL);
+ result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL,
+ BoundNetLog());
EXPECT_EQ(OK, result);
EXPECT_TRUE(proxy_info.is_direct());
@@ -157,7 +168,7 @@
{
ProxyInfo proxy_info;
result = resolver.GetProxyForURL(GURL("http://query.com/path"),
- &proxy_info, NULL, NULL, NULL);
+ &proxy_info, NULL, NULL, BoundNetLog());
EXPECT_EQ(OK, result);
EXPECT_EQ("http.query.com.path.query.com:80",
proxy_info.proxy_server().ToURI());
@@ -165,7 +176,8 @@
{
ProxyInfo proxy_info;
int result = resolver.GetProxyForURL(GURL("ftp://query.com:90/path"),
- &proxy_info, NULL, NULL, NULL);
+ &proxy_info, NULL, NULL,
+ BoundNetLog());
EXPECT_EQ(OK, result);
// Note that FindProxyForURL(url, host) does not expect |host| to contain
// the port number.
@@ -203,7 +215,8 @@
EXPECT_EQ(OK, result);
ProxyInfo proxy_info;
- result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL);
+ result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL,
+ BoundNetLog());
EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result);
@@ -223,7 +236,8 @@
EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result);
ProxyInfo proxy_info;
- result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL);
+ result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL,
+ BoundNetLog());
EXPECT_EQ(ERR_FAILED, result);
}
@@ -235,7 +249,8 @@
EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result);
ProxyInfo proxy_info;
- result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL);
+ result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL,
+ BoundNetLog());
EXPECT_EQ(ERR_FAILED, result);
@@ -247,7 +262,7 @@
EXPECT_EQ("Uncaught SyntaxError: Unexpected end of input",
bindings->errors[0]);
- EXPECT_EQ(-1, bindings->errors_line_number[0]);
+ EXPECT_EQ(0, bindings->errors_line_number[0]);
}
// Run a PAC script several times, which has side-effects.
@@ -258,7 +273,8 @@
// The PAC script increments a counter each time we invoke it.
for (int i = 0; i < 3; ++i) {
ProxyInfo proxy_info;
- result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL);
+ result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL,
+ BoundNetLog());
EXPECT_EQ(OK, result);
EXPECT_EQ(StringPrintf("sideffect_%d:80", i),
proxy_info.proxy_server().ToURI());
@@ -271,7 +287,8 @@
for (int i = 0; i < 3; ++i) {
ProxyInfo proxy_info;
- result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL);
+ result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL,
+ BoundNetLog());
EXPECT_EQ(OK, result);
EXPECT_EQ(StringPrintf("sideffect_%d:80", i),
proxy_info.proxy_server().ToURI());
@@ -285,7 +302,8 @@
EXPECT_EQ(OK, result);
ProxyInfo proxy_info;
- result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL);
+ result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL,
+ BoundNetLog());
EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result);
@@ -297,18 +315,17 @@
EXPECT_EQ(3, bindings->errors_line_number[0]);
}
-// TODO(eroman): This test is disabed right now, since the parsing of
-// host/port doesn't check for non-ascii characters.
-TEST(ProxyResolverV8Test, DISABLED_ReturnUnicode) {
+TEST(ProxyResolverV8Test, ReturnUnicode) {
ProxyResolverV8WithMockBindings resolver;
int result = resolver.SetPacScriptFromDisk("return_unicode.js");
EXPECT_EQ(OK, result);
ProxyInfo proxy_info;
- result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL);
+ result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL,
+ BoundNetLog());
// The result from this resolve was unparseable, because it
- // wasn't ascii.
+ // wasn't ASCII.
EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result);
}
@@ -319,7 +336,8 @@
EXPECT_EQ(OK, result);
ProxyInfo proxy_info;
- result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL);
+ result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL,
+ BoundNetLog());
// If the javascript side of this unit-test fails, it will throw a javascript
// exception. Otherwise it will return "PROXY success:80".
@@ -337,7 +355,8 @@
ProxyInfo proxy_info;
// Resolve should fail, as we are not yet initialized with a script.
- int result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL);
+ int result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL,
+ BoundNetLog());
EXPECT_EQ(ERR_FAILED, result);
// Initialize it.
@@ -345,20 +364,24 @@
EXPECT_EQ(OK, result);
// Resolve should now succeed.
- result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL);
+ result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL,
+ BoundNetLog());
EXPECT_EQ(OK, result);
// Clear it, by initializing with an empty string.
- resolver.SetPacScriptByData(std::string(), NULL);
+ resolver.SetPacScript(
+ ProxyResolverScriptData::FromUTF16(string16()), NULL);
// Resolve should fail again now.
- result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL);
+ result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL,
+ BoundNetLog());
EXPECT_EQ(ERR_FAILED, result);
// Load a good script once more.
result = resolver.SetPacScriptFromDisk("direct.js");
EXPECT_EQ(OK, result);
- result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL);
+ result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL,
+ BoundNetLog());
EXPECT_EQ(OK, result);
EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size());
@@ -368,16 +391,18 @@
// Test marshalling/un-marshalling of values between C++/V8.
TEST(ProxyResolverV8Test, V8Bindings) {
ProxyResolverV8WithMockBindings resolver;
+ MockJSBindings* bindings = resolver.mock_js_bindings();
+ bindings->dns_resolve_result = "127.0.0.1";
int result = resolver.SetPacScriptFromDisk("bindings.js");
EXPECT_EQ(OK, result);
ProxyInfo proxy_info;
- result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL);
+ result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL,
+ BoundNetLog());
EXPECT_EQ(OK, result);
EXPECT_TRUE(proxy_info.is_direct());
- MockJSBindings* bindings = resolver.mock_js_bindings();
EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size());
// Alert was called 5 times.
@@ -388,20 +413,11 @@
EXPECT_EQ("[object Object]", bindings->alerts[3]);
EXPECT_EQ("exception from calling toString()", bindings->alerts[4]);
- // DnsResolve was called 8 times.
- ASSERT_EQ(8U, bindings->dns_resolves.size());
- EXPECT_EQ("undefined", bindings->dns_resolves[0]);
- EXPECT_EQ("null", bindings->dns_resolves[1]);
- EXPECT_EQ("undefined", bindings->dns_resolves[2]);
- EXPECT_EQ("", bindings->dns_resolves[3]);
- EXPECT_EQ("[object Object]", bindings->dns_resolves[4]);
- EXPECT_EQ("function fn() {}", bindings->dns_resolves[5]);
-
- // TODO(eroman): This isn't quite right... should probably stringize
- // to something like "['3']".
- EXPECT_EQ("3", bindings->dns_resolves[6]);
-
- EXPECT_EQ("arg1", bindings->dns_resolves[7]);
+ // DnsResolve was called 8 times, however only 2 of those were string
+ // parameters. (so 6 of them failed immediately).
+ ASSERT_EQ(2U, bindings->dns_resolves.size());
+ EXPECT_EQ("", bindings->dns_resolves[0]);
+ EXPECT_EQ("arg1", bindings->dns_resolves[1]);
// MyIpAddress was called two times.
EXPECT_EQ(2, bindings->my_ip_address_count);
@@ -415,38 +431,33 @@
EXPECT_EQ("foobar", bindings->dns_resolves_ex[1]);
}
-// Test that calls to the myIpAddress() and dnsResolve() bindings get
-// logged to the LoadLog parameter.
-TEST(ProxyResolverV8Test, LoadLog) {
+// Test calling a binding (myIpAddress()) from the script's global scope.
+// http://crbug.com/40026
+TEST(ProxyResolverV8Test, BindingCalledDuringInitialization) {
ProxyResolverV8WithMockBindings resolver;
- int result = resolver.SetPacScriptFromDisk("simple.js");
+
+ int result = resolver.SetPacScriptFromDisk("binding_from_global.js");
EXPECT_EQ(OK, result);
+ MockJSBindings* bindings = resolver.mock_js_bindings();
+
+ // myIpAddress() got called during initialization of the script.
+ EXPECT_EQ(1, bindings->my_ip_address_count);
+
ProxyInfo proxy_info;
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
- result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, log);
+ result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL,
+ BoundNetLog());
EXPECT_EQ(OK, result);
EXPECT_FALSE(proxy_info.is_direct());
- EXPECT_EQ("c:100", proxy_info.proxy_server().ToURI());
+ EXPECT_EQ("127.0.0.1:80", proxy_info.proxy_server().ToURI());
- // Note that dnsResolve() was never called directly, but it appears
- // in the LoadLog. This is because it gets called indirectly by
- // isInNet() and isResolvable().
-
- EXPECT_EQ(6u, log->entries().size());
- EXPECT_TRUE(LogContainsBeginEvent(
- *log, 0, LoadLog::TYPE_PROXY_RESOLVER_V8_MY_IP_ADDRESS));
- EXPECT_TRUE(LogContainsEndEvent(
- *log, 1, LoadLog::TYPE_PROXY_RESOLVER_V8_MY_IP_ADDRESS));
- EXPECT_TRUE(LogContainsBeginEvent(
- *log, 2, LoadLog::TYPE_PROXY_RESOLVER_V8_DNS_RESOLVE));
- EXPECT_TRUE(LogContainsEndEvent(
- *log, 3, LoadLog::TYPE_PROXY_RESOLVER_V8_DNS_RESOLVE));
- EXPECT_TRUE(LogContainsBeginEvent(
- *log, 4, LoadLog::TYPE_PROXY_RESOLVER_V8_DNS_RESOLVE));
- EXPECT_TRUE(LogContainsEndEvent(
- *log, 5, LoadLog::TYPE_PROXY_RESOLVER_V8_DNS_RESOLVE));
+ // Check that no other bindings were called.
+ EXPECT_EQ(0U, bindings->errors.size());
+ ASSERT_EQ(0U, bindings->alerts.size());
+ ASSERT_EQ(0U, bindings->dns_resolves.size());
+ EXPECT_EQ(0, bindings->my_ip_address_ex_count);
+ ASSERT_EQ(0U, bindings->dns_resolves_ex.size());
}
// Try loading a PAC script that ends with a comment and has no terminal
@@ -459,8 +470,9 @@
EXPECT_EQ(OK, result);
ProxyInfo proxy_info;
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
- result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, log);
+ CapturingBoundNetLog log(CapturingNetLog::kUnbounded);
+ result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL,
+ log.bound());
EXPECT_EQ(OK, result);
EXPECT_FALSE(proxy_info.is_direct());
@@ -478,8 +490,9 @@
EXPECT_EQ(OK, result);
ProxyInfo proxy_info;
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
- result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, log);
+ CapturingBoundNetLog log(CapturingNetLog::kUnbounded);
+ result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL,
+ log.bound());
EXPECT_EQ(OK, result);
EXPECT_FALSE(proxy_info.is_direct());
@@ -496,12 +509,37 @@
EXPECT_EQ(OK, result);
ProxyInfo proxy_info;
- result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL);
+ result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL,
+ BoundNetLog());
EXPECT_EQ(OK, result);
EXPECT_FALSE(proxy_info.is_direct());
EXPECT_EQ("success:80", proxy_info.proxy_server().ToURI());
}
+TEST(ProxyResolverV8Test, DNSResolutionOfInternationDomainName) {
+ ProxyResolverV8WithMockBindings resolver;
+ int result = resolver.SetPacScriptFromDisk("international_domain_names.js");
+ EXPECT_EQ(OK, result);
+
+ // Execute FindProxyForURL().
+ ProxyInfo proxy_info;
+ result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL,
+ BoundNetLog());
+
+ EXPECT_EQ(OK, result);
+ EXPECT_TRUE(proxy_info.is_direct());
+
+ // Check that the international domain name was converted to punycode
+ // before passing it onto the bindings layer.
+ MockJSBindings* bindings = resolver.mock_js_bindings();
+
+ ASSERT_EQ(1u, bindings->dns_resolves.size());
+ EXPECT_EQ("xn--bcher-kva.ch", bindings->dns_resolves[0]);
+
+ ASSERT_EQ(1u, bindings->dns_resolves_ex.size());
+ EXPECT_EQ("xn--bcher-kva.ch", bindings->dns_resolves_ex[0]);
+}
+
} // namespace
} // namespace net
diff --git a/net/proxy/proxy_resolver_winhttp.cc b/net/proxy/proxy_resolver_winhttp.cc
index fd9efe3..d7aaae3 100644
--- a/net/proxy/proxy_resolver_winhttp.cc
+++ b/net/proxy/proxy_resolver_winhttp.cc
@@ -56,7 +56,7 @@
ProxyInfo* results,
CompletionCallback* /*callback*/,
RequestHandle* /*request*/,
- LoadLog* /*load_log*/) {
+ const BoundNetLog& /*net_log*/) {
// If we don't have a WinHTTP session, then create a new one.
if (!session_handle_ && !OpenWinHttpSession())
return ERR_FAILED;
@@ -140,10 +140,14 @@
NOTREACHED();
}
-int ProxyResolverWinHttp::SetPacScript(const GURL& pac_url,
- const std::string& /*pac_bytes*/,
- CompletionCallback* /*callback*/) {
- pac_url_ = pac_url.is_valid() ? pac_url : GURL("http://wpad/wpad.dat");
+int ProxyResolverWinHttp::SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ CompletionCallback* /*callback*/) {
+ if (script_data->type() == ProxyResolverScriptData::TYPE_AUTO_DETECT) {
+ pac_url_ = GURL("http://wpad/wpad.dat");
+ } else {
+ pac_url_ = script_data->url();
+ }
return OK;
}
diff --git a/net/proxy/proxy_resolver_winhttp.h b/net/proxy/proxy_resolver_winhttp.h
index ab1df0f..7ad40dc 100644
--- a/net/proxy/proxy_resolver_winhttp.h
+++ b/net/proxy/proxy_resolver_winhttp.h
@@ -26,14 +26,13 @@
ProxyInfo* results,
CompletionCallback* /*callback*/,
RequestHandle* /*request*/,
- LoadLog* /*load_log*/);
+ const BoundNetLog& /*net_log*/);
virtual void CancelRequest(RequestHandle request);
+ virtual int SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ CompletionCallback* /*callback*/);
private:
- // ProxyResolver implementation:
- virtual int SetPacScript(const GURL& pac_url,
- const std::string& /*pac_bytes*/,
- CompletionCallback* /*callback*/);
bool OpenWinHttpSession();
void CloseWinHttpSession();
diff --git a/net/proxy/proxy_script_fetcher.cc b/net/proxy/proxy_script_fetcher.cc
index 7dc800e..719c380 100644
--- a/net/proxy/proxy_script_fetcher.cc
+++ b/net/proxy/proxy_script_fetcher.cc
@@ -1,6 +1,6 @@
-// Copyright (c) 2008 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.
+// 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 "net/proxy/proxy_script_fetcher.h"
@@ -45,9 +45,12 @@
return false;
}
-// Convert |bytes| (which is encoded by |charset|) in place to UTF8.
+// Converts |bytes| (which is encoded by |charset|) to UTF16, saving the resul
+// to |*utf16|.
// If |charset| is empty, then we don't know what it was and guess.
-void ConvertResponseToUTF8(const std::string& charset, std::string* bytes) {
+void ConvertResponseToUTF16(const std::string& charset,
+ const std::string& bytes,
+ string16* utf16) {
const char* codepage;
if (charset.empty()) {
@@ -61,12 +64,9 @@
// We will be generous in the conversion -- if any characters lie
// outside of |charset| (i.e. invalid), then substitute them with
// U+FFFD rather than failing.
- std::wstring tmp_wide;
- base::CodepageToWide(*bytes, codepage,
- base::OnStringConversionError::SUBSTITUTE,
- &tmp_wide);
- // TODO(eroman): would be nice to have a CodepageToUTF8() function.
- *bytes = WideToUTF8(tmp_wide);
+ base::CodepageToUTF16(bytes, codepage,
+ base::OnStringConversionError::SUBSTITUTE,
+ utf16);
}
} // namespace
@@ -83,7 +83,7 @@
// ProxyScriptFetcher methods:
- virtual int Fetch(const GURL& url, std::string* bytes,
+ virtual int Fetch(const GURL& url, string16* text,
CompletionCallback* callback);
virtual void Cancel();
virtual URLRequestContext* GetRequestContext();
@@ -103,7 +103,7 @@
void ReadBody(URLRequest* request);
// Called once the request has completed to notify the caller of
- // |response_code_| and |response_bytes_|.
+ // |response_code_| and |response_text_|.
void FetchCompleted();
// Clear out the state for the current request.
@@ -140,9 +140,12 @@
// Holds the error condition that was hit on the current request, or OK.
int result_code_;
- // Holds the bytes read so far. Will not exceed |max_response_bytes|. This
- // buffer is owned by the owner of |callback|.
- std::string* result_bytes_;
+ // Holds the bytes read so far. Will not exceed |max_response_bytes|.
+ std::string bytes_read_so_far_;
+
+ // This buffer is owned by the owner of |callback|, and will be filled with
+ // UTF16 response on completion.
+ string16* result_text_;
};
ProxyScriptFetcherImpl::ProxyScriptFetcherImpl(
@@ -155,7 +158,7 @@
cur_request_id_(0),
callback_(NULL),
result_code_(OK),
- result_bytes_(NULL) {
+ result_text_(NULL) {
DCHECK(url_request_context);
}
@@ -165,13 +168,13 @@
}
int ProxyScriptFetcherImpl::Fetch(const GURL& url,
- std::string* bytes,
+ string16* text,
CompletionCallback* callback) {
// It is invalid to call Fetch() while a request is already in progress.
DCHECK(!cur_request_.get());
DCHECK(callback);
- DCHECK(bytes);
+ DCHECK(text);
cur_request_.reset(new URLRequest(url, this));
cur_request_->set_context(url_request_context_);
@@ -186,8 +189,9 @@
// Save the caller's info for notification on completion.
callback_ = callback;
- result_bytes_ = bytes;
- result_bytes_->clear();
+ result_text_ = text;
+
+ bytes_read_so_far_.clear();
// Post a task to timeout this request if it takes too long.
cur_request_id_ = ++next_id_;
@@ -269,13 +273,13 @@
DCHECK(request == cur_request_.get());
if (num_bytes > 0) {
// Enforce maximum size bound.
- if (num_bytes + result_bytes_->size() >
+ if (num_bytes + bytes_read_so_far_.size() >
static_cast<size_t>(max_response_bytes)) {
result_code_ = ERR_FILE_TOO_BIG;
request->Cancel();
return;
}
- result_bytes_->append(buf_->data(), num_bytes);
+ bytes_read_so_far_.append(buf_->data(), num_bytes);
ReadBody(request);
} else { // Error while reading, or EOF
OnResponseCompleted(request);
@@ -305,13 +309,13 @@
void ProxyScriptFetcherImpl::FetchCompleted() {
if (result_code_ == OK) {
- // The caller expects the response to be encoded as UTF8.
+ // The caller expects the response to be encoded as UTF16.
std::string charset;
cur_request_->GetCharset(&charset);
- ConvertResponseToUTF8(charset, result_bytes_);
+ ConvertResponseToUTF16(charset, bytes_read_so_far_, result_text_);
} else {
// On error, the caller expects empty string for bytes.
- result_bytes_->clear();
+ result_text_->clear();
}
int result_code = result_code_;
@@ -327,7 +331,7 @@
cur_request_id_ = 0;
callback_ = NULL;
result_code_ = OK;
- result_bytes_ = NULL;
+ result_text_ = NULL;
}
void ProxyScriptFetcherImpl::OnTimeout(int id) {
diff --git a/net/proxy/proxy_script_fetcher.h b/net/proxy/proxy_script_fetcher.h
index 09ac84e..8899cbb 100644
--- a/net/proxy/proxy_script_fetcher.h
+++ b/net/proxy/proxy_script_fetcher.h
@@ -1,6 +1,6 @@
-// Copyright (c) 2008 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.
+// 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.
// ProxyScriptFetcher is an async interface for fetching a proxy auto config
// script. It is specific to fetching a PAC script; enforces timeout, max-size,
@@ -9,8 +9,7 @@
#ifndef NET_PROXY_PROXY_SCRIPT_FETCHER_H_
#define NET_PROXY_PROXY_SCRIPT_FETCHER_H_
-#include <string>
-
+#include "base/string16.h"
#include "net/base/completion_callback.h"
#include "testing/gtest/include/gtest/gtest_prod.h"
@@ -25,8 +24,8 @@
virtual ~ProxyScriptFetcher() {}
// Downloads the given PAC URL, and invokes |callback| on completion.
- // On success |callback| is executed with a result code of OK, and a
- // string of the response bytes (as UTF8). On failure, the result bytes is
+ // On success |callback| is executed with a result code of OK, |*utf16_text|
+ // is filled with the response. On failure, the result text is
// an empty string, and the result code is a network error. Some special
// network errors that may occur are:
//
@@ -39,7 +38,7 @@
// deleting |this|), then no callback is invoked.
//
// Only one fetch is allowed to be outstanding at a time.
- virtual int Fetch(const GURL& url, std::string* utf8_bytes,
+ virtual int Fetch(const GURL& url, string16* utf16_text,
CompletionCallback* callback) = 0;
// Aborts the in-progress fetch (if any).
diff --git a/net/proxy/proxy_script_fetcher_unittest.cc b/net/proxy/proxy_script_fetcher_unittest.cc
index 741e7d4..e0e64c9 100644
--- a/net/proxy/proxy_script_fetcher_unittest.cc
+++ b/net/proxy/proxy_script_fetcher_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -24,7 +24,7 @@
struct FetchResult {
int code;
- std::string bytes;
+ string16 text;
};
// A non-mock URL request which can access http:// and file:// urls.
@@ -32,14 +32,15 @@
public:
RequestContext() {
net::ProxyConfig no_proxy;
- host_resolver_ = net::CreateSystemHostResolver(NULL);
+ host_resolver_ =
+ net::CreateSystemHostResolver(net::HostResolver::kDefaultParallelism);
proxy_service_ = net::ProxyService::CreateFixed(no_proxy);
ssl_config_service_ = new net::SSLConfigServiceDefaults;
- http_transaction_factory_ =
- new net::HttpCache(net::HttpNetworkLayer::CreateFactory(
- NULL, host_resolver_, proxy_service_, ssl_config_service_),
- disk_cache::CreateInMemoryCacheBackend(0));
+ http_transaction_factory_ = new net::HttpCache(
+ net::HttpNetworkLayer::CreateFactory(host_resolver_, proxy_service_,
+ ssl_config_service_, NULL, NULL, NULL),
+ net::HttpCache::DefaultBackend::InMemory(0));
}
private:
@@ -70,22 +71,22 @@
ProxyScriptFetcher::Create(context));
{ // Fetch a non-existent file.
- std::string bytes;
+ string16 text;
TestCompletionCallback callback;
int result = pac_fetcher->Fetch(GetTestFileUrl("does-not-exist"),
- &bytes, &callback);
+ &text, &callback);
EXPECT_EQ(ERR_IO_PENDING, result);
EXPECT_EQ(ERR_FILE_NOT_FOUND, callback.WaitForResult());
- EXPECT_TRUE(bytes.empty());
+ EXPECT_TRUE(text.empty());
}
{ // Fetch a file that exists.
- std::string bytes;
+ string16 text;
TestCompletionCallback callback;
int result = pac_fetcher->Fetch(GetTestFileUrl("pac.txt"),
- &bytes, &callback);
+ &text, &callback);
EXPECT_EQ(ERR_IO_PENDING, result);
EXPECT_EQ(OK, callback.WaitForResult());
- EXPECT_EQ("-pac.txt-\n", bytes);
+ EXPECT_EQ(ASCIIToUTF16("-pac.txt-\n"), text);
}
}
@@ -101,30 +102,30 @@
{ // Fetch a PAC with mime type "text/plain"
GURL url = server->TestServerPage("files/pac.txt");
- std::string bytes;
+ string16 text;
TestCompletionCallback callback;
- int result = pac_fetcher->Fetch(url, &bytes, &callback);
+ int result = pac_fetcher->Fetch(url, &text, &callback);
EXPECT_EQ(ERR_IO_PENDING, result);
EXPECT_EQ(OK, callback.WaitForResult());
- EXPECT_EQ("-pac.txt-\n", bytes);
+ EXPECT_EQ(ASCIIToUTF16("-pac.txt-\n"), text);
}
{ // Fetch a PAC with mime type "text/html"
GURL url = server->TestServerPage("files/pac.html");
- std::string bytes;
+ string16 text;
TestCompletionCallback callback;
- int result = pac_fetcher->Fetch(url, &bytes, &callback);
+ int result = pac_fetcher->Fetch(url, &text, &callback);
EXPECT_EQ(ERR_IO_PENDING, result);
EXPECT_EQ(OK, callback.WaitForResult());
- EXPECT_EQ("-pac.html-\n", bytes);
+ EXPECT_EQ(ASCIIToUTF16("-pac.html-\n"), text);
}
{ // Fetch a PAC with mime type "application/x-ns-proxy-autoconfig"
GURL url = server->TestServerPage("files/pac.nsproxy");
- std::string bytes;
+ string16 text;
TestCompletionCallback callback;
- int result = pac_fetcher->Fetch(url, &bytes, &callback);
+ int result = pac_fetcher->Fetch(url, &text, &callback);
EXPECT_EQ(ERR_IO_PENDING, result);
EXPECT_EQ(OK, callback.WaitForResult());
- EXPECT_EQ("-pac.nsproxy-\n", bytes);
+ EXPECT_EQ(ASCIIToUTF16("-pac.nsproxy-\n"), text);
}
}
@@ -138,21 +139,21 @@
{ // Fetch a PAC which gives a 500 -- FAIL
GURL url = server->TestServerPage("files/500.pac");
- std::string bytes;
+ string16 text;
TestCompletionCallback callback;
- int result = pac_fetcher->Fetch(url, &bytes, &callback);
+ int result = pac_fetcher->Fetch(url, &text, &callback);
EXPECT_EQ(ERR_IO_PENDING, result);
EXPECT_EQ(ERR_PAC_STATUS_NOT_OK, callback.WaitForResult());
- EXPECT_TRUE(bytes.empty());
+ EXPECT_TRUE(text.empty());
}
{ // Fetch a PAC which gives a 404 -- FAIL
GURL url = server->TestServerPage("files/404.pac");
- std::string bytes;
+ string16 text;
TestCompletionCallback callback;
- int result = pac_fetcher->Fetch(url, &bytes, &callback);
+ int result = pac_fetcher->Fetch(url, &text, &callback);
EXPECT_EQ(ERR_IO_PENDING, result);
EXPECT_EQ(ERR_PAC_STATUS_NOT_OK, callback.WaitForResult());
- EXPECT_TRUE(bytes.empty());
+ EXPECT_TRUE(text.empty());
}
}
@@ -167,12 +168,12 @@
// Fetch PAC scripts via HTTP with a Content-Disposition header -- should
// have no effect.
GURL url = server->TestServerPage("files/downloadable.pac");
- std::string bytes;
+ string16 text;
TestCompletionCallback callback;
- int result = pac_fetcher->Fetch(url, &bytes, &callback);
+ int result = pac_fetcher->Fetch(url, &text, &callback);
EXPECT_EQ(ERR_IO_PENDING, result);
EXPECT_EQ(OK, callback.WaitForResult());
- EXPECT_EQ("-downloadable.pac-\n", bytes);
+ EXPECT_EQ(ASCIIToUTF16("-downloadable.pac-\n"), text);
}
TEST_F(ProxyScriptFetcherTest, NoCache) {
@@ -186,12 +187,12 @@
// Fetch a PAC script whose HTTP headers make it cacheable for 1 hour.
GURL url = server->TestServerPage("files/cacheable_1hr.pac");
{
- std::string bytes;
+ string16 text;
TestCompletionCallback callback;
- int result = pac_fetcher->Fetch(url, &bytes, &callback);
+ int result = pac_fetcher->Fetch(url, &text, &callback);
EXPECT_EQ(ERR_IO_PENDING, result);
EXPECT_EQ(OK, callback.WaitForResult());
- EXPECT_EQ("-cacheable_1hr.pac-\n", bytes);
+ EXPECT_EQ(ASCIIToUTF16("-cacheable_1hr.pac-\n"), text);
}
// Now kill the HTTP server.
@@ -202,9 +203,9 @@
// running anymore. (If it were instead being loaded from cache, we would
// get a success.
{
- std::string bytes;
+ string16 text;
TestCompletionCallback callback;
- int result = pac_fetcher->Fetch(url, &bytes, &callback);
+ int result = pac_fetcher->Fetch(url, &text, &callback);
EXPECT_EQ(ERR_IO_PENDING, result);
EXPECT_EQ(ERR_CONNECTION_REFUSED, callback.WaitForResult());
}
@@ -231,12 +232,12 @@
// after 50 bytes have been read, and fail with a too large error.
for (size_t i = 0; i < arraysize(urls); ++i) {
const GURL& url = urls[i];
- std::string bytes;
+ string16 text;
TestCompletionCallback callback;
- int result = pac_fetcher->Fetch(url, &bytes, &callback);
+ int result = pac_fetcher->Fetch(url, &text, &callback);
EXPECT_EQ(ERR_IO_PENDING, result);
EXPECT_EQ(ERR_FILE_TOO_BIG, callback.WaitForResult());
- EXPECT_TRUE(bytes.empty());
+ EXPECT_TRUE(text.empty());
}
// Restore the original size bound.
@@ -244,12 +245,12 @@
{ // Make sure we can still fetch regular URLs.
GURL url = server->TestServerPage("files/pac.nsproxy");
- std::string bytes;
+ string16 text;
TestCompletionCallback callback;
- int result = pac_fetcher->Fetch(url, &bytes, &callback);
+ int result = pac_fetcher->Fetch(url, &text, &callback);
EXPECT_EQ(ERR_IO_PENDING, result);
EXPECT_EQ(OK, callback.WaitForResult());
- EXPECT_EQ("-pac.nsproxy-\n", bytes);
+ EXPECT_EQ(ASCIIToUTF16("-pac.nsproxy-\n"), text);
}
}
@@ -268,12 +269,12 @@
// Try fetching a URL which takes 1.2 seconds. We should abort the request
// after 500 ms, and fail with a timeout error.
{ GURL url = server->TestServerPage("slow/proxy.pac?1.2");
- std::string bytes;
+ string16 text;
TestCompletionCallback callback;
- int result = pac_fetcher->Fetch(url, &bytes, &callback);
+ int result = pac_fetcher->Fetch(url, &text, &callback);
EXPECT_EQ(ERR_IO_PENDING, result);
EXPECT_EQ(ERR_TIMED_OUT, callback.WaitForResult());
- EXPECT_TRUE(bytes.empty());
+ EXPECT_TRUE(text.empty());
}
// Restore the original timeout period.
@@ -281,12 +282,12 @@
{ // Make sure we can still fetch regular URLs.
GURL url = server->TestServerPage("files/pac.nsproxy");
- std::string bytes;
+ string16 text;
TestCompletionCallback callback;
- int result = pac_fetcher->Fetch(url, &bytes, &callback);
+ int result = pac_fetcher->Fetch(url, &text, &callback);
EXPECT_EQ(ERR_IO_PENDING, result);
EXPECT_EQ(OK, callback.WaitForResult());
- EXPECT_EQ("-pac.nsproxy-\n", bytes);
+ EXPECT_EQ(ASCIIToUTF16("-pac.nsproxy-\n"), text);
}
}
@@ -304,24 +305,24 @@
// Test a response that is gzip-encoded -- should get inflated.
{
GURL url = server->TestServerPage("files/gzipped_pac");
- std::string bytes;
+ string16 text;
TestCompletionCallback callback;
- int result = pac_fetcher->Fetch(url, &bytes, &callback);
+ int result = pac_fetcher->Fetch(url, &text, &callback);
EXPECT_EQ(ERR_IO_PENDING, result);
EXPECT_EQ(OK, callback.WaitForResult());
- EXPECT_EQ("This data was gzipped.\n", bytes);
+ EXPECT_EQ(ASCIIToUTF16("This data was gzipped.\n"), text);
}
// Test a response that was served as UTF-16 (BE). It should
// be converted to UTF8.
{
GURL url = server->TestServerPage("files/utf16be_pac");
- std::string bytes;
+ string16 text;
TestCompletionCallback callback;
- int result = pac_fetcher->Fetch(url, &bytes, &callback);
+ int result = pac_fetcher->Fetch(url, &text, &callback);
EXPECT_EQ(ERR_IO_PENDING, result);
EXPECT_EQ(OK, callback.WaitForResult());
- EXPECT_EQ("This was encoded as UTF-16BE.\n", bytes);
+ EXPECT_EQ(ASCIIToUTF16("This was encoded as UTF-16BE.\n"), text);
}
}
diff --git a/net/proxy/proxy_server.cc b/net/proxy/proxy_server.cc
index 6c47fee..b78c347 100644
--- a/net/proxy/proxy_server.cc
+++ b/net/proxy/proxy_server.cc
@@ -34,6 +34,8 @@
return ProxyServer::SCHEME_SOCKS5;
if (LowerCaseEqualsASCII(begin, end, "direct"))
return ProxyServer::SCHEME_DIRECT;
+ if (LowerCaseEqualsASCII(begin, end, "https"))
+ return ProxyServer::SCHEME_HTTPS;
return ProxyServer::SCHEME_INVALID;
}
@@ -53,6 +55,8 @@
return ProxyServer::SCHEME_SOCKS5;
if (LowerCaseEqualsASCII(begin, end, "direct"))
return ProxyServer::SCHEME_DIRECT;
+ if (LowerCaseEqualsASCII(begin, end, "https"))
+ return ProxyServer::SCHEME_HTTPS;
return ProxyServer::SCHEME_INVALID;
}
@@ -84,6 +88,13 @@
return host_ + ":" + IntToString(port_);
}
+HostPortPair ProxyServer::host_port_pair() const {
+ // Doesn't make sense to call this if the URI scheme doesn't
+ // have concept of a host.
+ DCHECK(is_valid() && !is_direct());
+ return HostPortPair(host_, port_);
+}
+
// static
ProxyServer ProxyServer::FromURI(const std::string& uri,
Scheme default_scheme) {
@@ -125,6 +136,8 @@
return std::string("socks4://") + host_and_port();
case SCHEME_SOCKS5:
return std::string("socks5://") + host_and_port();
+ case SCHEME_HTTPS:
+ return std::string("https://") + host_and_port();
default:
// Got called with an invalid scheme.
NOTREACHED();
@@ -137,6 +150,7 @@
return FromPacString(pac_string.begin(), pac_string.end());
}
+// static
ProxyServer ProxyServer::FromPacString(std::string::const_iterator begin,
std::string::const_iterator end) {
// Trim the leading/trailing whitespace.
@@ -172,6 +186,8 @@
return std::string("SOCKS ") + host_and_port();
case SCHEME_SOCKS5:
return std::string("SOCKS5 ") + host_and_port();
+ case SCHEME_HTTPS:
+ return std::string("HTTPS ") + host_and_port();
default:
// Got called with an invalid scheme.
NOTREACHED();
@@ -187,6 +203,8 @@
case SCHEME_SOCKS4:
case SCHEME_SOCKS5:
return 1080;
+ case SCHEME_HTTPS:
+ return 443;
default:
return -1;
}
diff --git a/net/proxy/proxy_server.h b/net/proxy/proxy_server.h
index d079396..3d5593c 100644
--- a/net/proxy/proxy_server.h
+++ b/net/proxy/proxy_server.h
@@ -12,6 +12,7 @@
#endif
#include <string>
+#include "net/base/host_port_pair.h"
namespace net {
@@ -28,6 +29,7 @@
SCHEME_HTTP = 1 << 2,
SCHEME_SOCKS4 = 1 << 3,
SCHEME_SOCKS5 = 1 << 4,
+ SCHEME_HTTPS = 1 << 5,
};
// Default copy-constructor and assignment operator are OK!
@@ -51,6 +53,9 @@
// Returns true if this ProxyServer is an HTTP proxy.
bool is_http() const { return scheme_ == SCHEME_HTTP; }
+ // Returns true if this ProxyServer is an HTTPS proxy.
+ bool is_https() const { return scheme_ == SCHEME_HTTPS; }
+
// Returns true if this ProxyServer is a SOCKS proxy.
bool is_socks() const {
return scheme_ == SCHEME_SOCKS4 || scheme_ == SCHEME_SOCKS5;
@@ -65,9 +70,14 @@
int port() const;
// Returns the <host>":"<port> string for the proxy server.
+ // TODO(willchan): Remove in favor of host_port_pair().
std::string host_and_port() const;
- // Parse from an input with format:
+ // TODO(willchan): Change to const HostPortPair& after refactoring |host_| and
+ // |port_| here.
+ HostPortPair host_port_pair() const;
+
+ // Parses from an input with format:
// [<scheme>"://"]<server>[":"<port>]
//
// Both <scheme> and <port> are optional. If <scheme> is omitted, it will be
@@ -81,6 +91,7 @@
// "socks4://foopy" {scheme=SOCKS4, host="foopy", port=1080}
// "socks5://foopy" {scheme=SOCKS5, host="foopy", port=1080}
// "http://foopy:17" {scheme=HTTP, host="foopy", port=17}
+ // "https://foopy:17" {scheme=HTTPS, host="foopy", port=17}
// "direct://" {scheme=DIRECT}
// "foopy:X" INVALID -- bad port.
static ProxyServer FromURI(const std::string& uri, Scheme default_scheme);
@@ -88,7 +99,7 @@
std::string::const_iterator uri_end,
Scheme default_scheme);
- // Format as a URI string. This does the reverse of FromURI.
+ // Formats as a URI string. This does the reverse of FromURI.
std::string ToURI() const;
// Parses from a PAC string result.
@@ -102,6 +113,7 @@
// "PROXY foopy:19" {scheme=HTTP, host="foopy", port=19}
// "DIRECT" {scheme=DIRECT}
// "SOCKS5 foopy" {scheme=SOCKS5, host="foopy", port=1080}
+ // "HTTPS foopy:123" {scheme=HTTPS, host="foopy", port=123}
// "BLAH xxx:xx" INVALID
static ProxyServer FromPacString(const std::string& pac_string);
static ProxyServer FromPacString(std::string::const_iterator pac_string_begin,
@@ -124,8 +136,7 @@
CFStringRef port_key);
#endif
-
- // Format as a PAC result entry. This does the reverse of FromPacString().
+ // Formats as a PAC result entry. This does the reverse of FromPacString().
std::string ToPacString() const;
// Returns the default port number for a proxy server with the specified
@@ -139,7 +150,7 @@
}
private:
- // Create a ProxyServer given a scheme, and host/port string. If parsing the
+ // Creates a ProxyServer given a scheme, and host/port string. If parsing the
// host/port string fails, the returned instance will be invalid.
static ProxyServer FromSchemeHostAndPort(
Scheme scheme,
diff --git a/net/proxy/proxy_server_unittest.cc b/net/proxy/proxy_server_unittest.cc
index 0b06152..f91ad66 100644
--- a/net/proxy/proxy_server_unittest.cc
+++ b/net/proxy/proxy_server_unittest.cc
@@ -145,6 +145,35 @@
"foopy:10",
"SOCKS foopy:10"
},
+
+ // HTTPS proxy URIs:
+ {
+ "https://foopy", // No port
+ "https://foopy:443",
+ net::ProxyServer::SCHEME_HTTPS,
+ "foopy",
+ 443,
+ "foopy:443",
+ "HTTPS foopy:443"
+ },
+ {
+ "https://foopy:10", // Non-standard port
+ "https://foopy:10",
+ net::ProxyServer::SCHEME_HTTPS,
+ "foopy",
+ 10,
+ "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,
+ "1.2.3.4:10",
+ "HTTPS 1.2.3.4:10"
+ },
};
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
@@ -189,7 +218,6 @@
"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.
- "https://blah", // "https" is not a valid proxy scheme.
};
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
@@ -259,6 +287,14 @@
" direct ",
"direct://",
},
+ {
+ "https foopy",
+ "https://foopy:443",
+ },
+ {
+ "https foopy:10",
+ "https://foopy:10",
+ },
};
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
@@ -272,6 +308,7 @@
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.
};
diff --git a/net/proxy/proxy_service.cc b/net/proxy/proxy_service.cc
index fb607ec..8f9d28c 100644
--- a/net/proxy/proxy_service.cc
+++ b/net/proxy/proxy_service.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -8,13 +8,16 @@
#include "base/compiler_specific.h"
#include "base/logging.h"
+#include "base/histogram.h"
#include "base/message_loop.h"
#include "base/string_util.h"
#include "googleurl/src/gurl.h"
-#include "net/base/load_log.h"
+#include "net/base/forwarding_net_log.h"
+#include "net/base/net_log.h"
#include "net/base/net_errors.h"
#include "net/base/net_util.h"
#include "net/proxy/init_proxy_resolver.h"
+#include "net/proxy/multi_threaded_proxy_resolver.h"
#include "net/proxy/proxy_config_service_fixed.h"
#include "net/proxy/proxy_script_fetcher.h"
#if defined(OS_WIN)
@@ -29,7 +32,7 @@
#include "net/proxy/proxy_resolver.h"
#include "net/proxy/proxy_resolver_js_bindings.h"
#include "net/proxy/proxy_resolver_v8.h"
-#include "net/proxy/single_threaded_proxy_resolver.h"
+#include "net/proxy/sync_host_resolver_bridge.h"
#include "net/url_request/url_request_context.h"
using base::TimeDelta;
@@ -37,7 +40,8 @@
namespace net {
-static const size_t kMaxNumLoadLogEntries = 100;
+static const size_t kMaxNumNetLogEntries = 100;
+static const size_t kDefaultNumPacThreads = 4;
// Config getter that fails every time.
class ProxyConfigServiceNull : public ProxyConfigService {
@@ -58,7 +62,7 @@
ProxyInfo* results,
CompletionCallback* callback,
RequestHandle* request,
- LoadLog* load_log) {
+ const BoundNetLog& net_log) {
return ERR_NOT_IMPLEMENTED;
}
@@ -66,14 +70,72 @@
NOTREACHED();
}
- private:
- virtual int SetPacScript(const GURL& /*pac_url*/,
- const std::string& /*pac_bytes*/,
- CompletionCallback* /*callback*/) {
+ virtual int SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& /*script_data*/,
+ CompletionCallback* /*callback*/) {
return ERR_NOT_IMPLEMENTED;
}
};
+// This factory creates V8ProxyResolvers with appropriate javascript bindings.
+class ProxyResolverFactoryForV8 : public ProxyResolverFactory {
+ public:
+ // |async_host_resolver|, |io_loop| and |net_log| must remain
+ // valid for the duration of our lifetime.
+ // Both |async_host_resolver| and |net_log| will only be operated on
+ // |io_loop|.
+ ProxyResolverFactoryForV8(HostResolver* async_host_resolver,
+ MessageLoop* io_loop,
+ NetLog* net_log)
+ : ProxyResolverFactory(true /*expects_pac_bytes*/),
+ async_host_resolver_(async_host_resolver),
+ io_loop_(io_loop),
+ forwarding_net_log_(
+ net_log ? new ForwardingNetLog(net_log, io_loop) : NULL) {
+ }
+
+ virtual ProxyResolver* CreateProxyResolver() {
+ // Create a synchronous host resolver wrapper that operates
+ // |async_host_resolver_| on |io_loop_|.
+ SyncHostResolverBridge* sync_host_resolver =
+ new SyncHostResolverBridge(async_host_resolver_, io_loop_);
+
+ ProxyResolverJSBindings* js_bindings =
+ ProxyResolverJSBindings::CreateDefault(sync_host_resolver,
+ forwarding_net_log_.get());
+
+ // ProxyResolverV8 takes ownership of |js_bindings|.
+ return new ProxyResolverV8(js_bindings);
+ }
+
+ private:
+ scoped_refptr<HostResolver> async_host_resolver_;
+ MessageLoop* io_loop_;
+
+ // Thread-safe wrapper around a non-threadsafe NetLog implementation. This
+ // enables the proxy resolver to emit log messages from the PAC thread.
+ scoped_ptr<ForwardingNetLog> forwarding_net_log_;
+};
+
+// Creates ProxyResolvers using a non-V8 implementation.
+class ProxyResolverFactoryForNonV8 : public ProxyResolverFactory {
+ public:
+ ProxyResolverFactoryForNonV8()
+ : ProxyResolverFactory(false /*expects_pac_bytes*/) {}
+
+ virtual ProxyResolver* CreateProxyResolver() {
+#if defined(OS_WIN)
+ return new ProxyResolverWinHttp();
+#elif defined(OS_MACOSX)
+ return new ProxyResolverMac();
+#else
+ LOG(WARNING) << "PAC support disabled because there is no fallback "
+ "non-V8 implementation";
+ return new ProxyResolverNull();
+#endif
+ }
+};
+
// ProxyService::PacRequest ---------------------------------------------------
class ProxyService::PacRequest
@@ -83,7 +145,7 @@
const GURL& url,
ProxyInfo* results,
CompletionCallback* user_callback,
- LoadLog* load_log)
+ const BoundNetLog& net_log)
: service_(service),
user_callback_(user_callback),
ALLOW_THIS_IN_INITIALIZER_LIST(io_callback_(
@@ -92,7 +154,7 @@
url_(url),
resolve_job_(NULL),
config_id_(ProxyConfig::INVALID_ID),
- load_log_(load_log) {
+ net_log_(net_log) {
DCHECK(user_callback);
}
@@ -104,7 +166,7 @@
config_id_ = service_->config_.id();
return resolver()->GetProxyForURL(
- url_, results_, &io_callback_, &resolve_job_, load_log_);
+ url_, results_, &io_callback_, &resolve_job_, net_log_);
}
bool is_started() const {
@@ -129,7 +191,7 @@
}
void Cancel() {
- LoadLog::AddEvent(load_log_, LoadLog::TYPE_CANCELLED);
+ net_log_.AddEvent(NetLog::TYPE_CANCELLED, NULL);
if (is_started())
CancelResolveJob();
@@ -139,7 +201,7 @@
user_callback_ = NULL;
results_ = NULL;
- LoadLog::EndEvent(load_log_, LoadLog::TYPE_PROXY_SERVICE);
+ net_log_.EndEvent(NetLog::TYPE_PROXY_SERVICE, NULL);
}
// Returns true if Cancel() has been called.
@@ -158,10 +220,10 @@
resolve_job_ = NULL;
config_id_ = ProxyConfig::INVALID_ID;
- return service_->DidFinishResolvingProxy(results_, result_code, load_log_);
+ return service_->DidFinishResolvingProxy(results_, result_code, net_log_);
}
- LoadLog* load_log() const { return load_log_; }
+ BoundNetLog* net_log() { return &net_log_; }
private:
friend class base::RefCounted<ProxyService::PacRequest>;
@@ -192,52 +254,51 @@
GURL url_;
ProxyResolver::RequestHandle resolve_job_;
ProxyConfig::ID config_id_; // The config id when the resolve was started.
- scoped_refptr<LoadLog> load_log_;
+ BoundNetLog net_log_;
};
// ProxyService ---------------------------------------------------------------
ProxyService::ProxyService(ProxyConfigService* config_service,
ProxyResolver* resolver,
- NetworkChangeNotifier* network_change_notifier)
+ NetLog* net_log)
: config_service_(config_service),
resolver_(resolver),
next_config_id_(1),
should_use_proxy_resolver_(false),
ALLOW_THIS_IN_INITIALIZER_LIST(init_proxy_resolver_callback_(
this, &ProxyService::OnInitProxyResolverComplete)),
- network_change_notifier_(network_change_notifier) {
- // Register to receive network change notifications.
- if (network_change_notifier_)
- network_change_notifier_->AddObserver(this);
+ net_log_(net_log) {
+ NetworkChangeNotifier::AddObserver(this);
}
// static
ProxyService* ProxyService::Create(
ProxyConfigService* proxy_config_service,
bool use_v8_resolver,
+ size_t num_pac_threads,
URLRequestContext* url_request_context,
- NetworkChangeNotifier* network_change_notifier,
+ NetLog* net_log,
MessageLoop* io_loop) {
- ProxyResolver* proxy_resolver;
+ if (num_pac_threads == 0)
+ num_pac_threads = kDefaultNumPacThreads;
+ ProxyResolverFactory* sync_resolver_factory;
if (use_v8_resolver) {
- // Send javascript errors and alerts to LOG(INFO).
- HostResolver* host_resolver = url_request_context->host_resolver();
- ProxyResolverJSBindings* js_bindings =
- ProxyResolverJSBindings::CreateDefault(host_resolver, io_loop);
-
- proxy_resolver = new ProxyResolverV8(js_bindings);
+ sync_resolver_factory =
+ new ProxyResolverFactoryForV8(
+ url_request_context->host_resolver(),
+ io_loop,
+ net_log);
} else {
- proxy_resolver = CreateNonV8ProxyResolver();
+ sync_resolver_factory = new ProxyResolverFactoryForNonV8();
}
- // Wrap the (synchronous) ProxyResolver implementation in a single-threaded
- // runner. This will dispatch requests to a threadpool of size 1.
- proxy_resolver = new SingleThreadedProxyResolver(proxy_resolver);
+ ProxyResolver* proxy_resolver =
+ new MultiThreadedProxyResolver(sync_resolver_factory, num_pac_threads);
- ProxyService* proxy_service = new ProxyService(
- proxy_config_service, proxy_resolver, network_change_notifier);
+ ProxyService* proxy_service =
+ new ProxyService(proxy_config_service, proxy_resolver, net_log);
if (proxy_resolver->expects_pac_bytes()) {
// Configure PAC script downloads to be issued using |url_request_context|.
@@ -251,14 +312,15 @@
// static
ProxyService* ProxyService::CreateFixed(const ProxyConfig& pc) {
- return Create(new ProxyConfigServiceFixed(pc), false, NULL, NULL, NULL);
+ // TODO(eroman): This isn't quite right, won't work if |pc| specifies
+ // a PAC script.
+ return Create(new ProxyConfigServiceFixed(pc), false, 0, NULL, NULL, NULL);
}
// static
ProxyService* ProxyService::CreateNull() {
// Use a configuration fetcher and proxy resolver which always fail.
- return new ProxyService(new ProxyConfigServiceNull,
- new ProxyResolverNull,
+ return new ProxyService(new ProxyConfigServiceNull, new ProxyResolverNull,
NULL);
}
@@ -266,10 +328,10 @@
ProxyInfo* result,
CompletionCallback* callback,
PacRequest** pac_request,
- LoadLog* load_log) {
+ const BoundNetLog& net_log) {
DCHECK(callback);
- LoadLog::BeginEvent(load_log, LoadLog::TYPE_PROXY_SERVICE);
+ net_log.BeginEvent(NetLog::TYPE_PROXY_SERVICE, NULL);
// Strip away any reference fragments and the username/password, as they
// are not relevant to proxy resolution.
@@ -277,13 +339,13 @@
// Check if the request can be completed right away. This is the case when
// using a direct connection, or when the config is bad.
- UpdateConfigIfOld(load_log);
+ UpdateConfigIfOld(net_log);
int rv = TryToCompleteSynchronously(url, result);
if (rv != ERR_IO_PENDING)
- return DidFinishResolvingProxy(result, rv, load_log);
+ return DidFinishResolvingProxy(result, rv, net_log);
scoped_refptr<PacRequest> req =
- new PacRequest(this, url, result, callback, load_log);
+ new PacRequest(this, url, result, callback, net_log);
bool resolver_is_ready = !IsInitializingProxyResolver();
@@ -293,8 +355,8 @@
if (rv != ERR_IO_PENDING)
return req->QueryDidComplete(rv);
} else {
- LoadLog::BeginEvent(req->load_log(),
- LoadLog::TYPE_PROXY_SERVICE_WAITING_FOR_INIT_PAC);
+ req->net_log()->BeginEvent(NetLog::TYPE_PROXY_SERVICE_WAITING_FOR_INIT_PAC,
+ NULL);
}
DCHECK_EQ(ERR_IO_PENDING, rv);
@@ -319,52 +381,13 @@
return ERR_IO_PENDING;
}
- if (!config_.proxy_rules.empty()) {
- ApplyProxyRules(url, config_.proxy_rules, result);
- return OK;
- }
-
- // otherwise, we have no proxy config
- result->UseDirect();
+ // Use the manual proxy settings.
+ config_.proxy_rules().Apply(url, result);
return OK;
}
-void ProxyService::ApplyProxyRules(const GURL& url,
- const ProxyConfig::ProxyRules& proxy_rules,
- ProxyInfo* result) {
- DCHECK(!proxy_rules.empty());
-
- if (ShouldBypassProxyForURL(url)) {
- result->UseDirect();
- return;
- }
-
- switch (proxy_rules.type) {
- case ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY:
- result->UseProxyServer(proxy_rules.single_proxy);
- break;
- case ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME: {
- const ProxyServer* entry = proxy_rules.MapUrlSchemeToProxy(url.scheme());
- if (entry) {
- result->UseProxyServer(*entry);
- } else {
- // We failed to find a matching proxy server for the current URL
- // scheme. Default to direct.
- result->UseDirect();
- }
- break;
- }
- default:
- result->UseDirect();
- NOTREACHED();
- break;
- }
-}
-
ProxyService::~ProxyService() {
- // Unregister to receive network change notifications.
- if (network_change_notifier_)
- network_change_notifier_->RemoveObserver(this);
+ NetworkChangeNotifier::RemoveObserver(this);
// Cancel any inprogress requests.
for (PendingRequests::iterator it = pending_requests_.begin();
@@ -372,6 +395,10 @@
++it) {
(*it)->Cancel();
}
+
+ // Make sure that InitProxyResolver gets destroyed BEFORE the
+ // CapturingNetLog it is using is deleted.
+ init_proxy_resolver_.reset();
}
void ProxyService::SuspendAllPendingRequests() {
@@ -382,8 +409,8 @@
if (req->is_started()) {
req->CancelResolveJob();
- LoadLog::BeginEvent(req->load_log(),
- LoadLog::TYPE_PROXY_SERVICE_WAITING_FOR_INIT_PAC);
+ req->net_log()->BeginEvent(
+ NetLog::TYPE_PROXY_SERVICE_WAITING_FOR_INIT_PAC, NULL);
}
}
}
@@ -401,8 +428,8 @@
++it) {
PacRequest* req = it->get();
if (!req->is_started() && !req->was_cancelled()) {
- LoadLog::EndEvent(req->load_log(),
- LoadLog::TYPE_PROXY_SERVICE_WAITING_FOR_INIT_PAC);
+ req->net_log()->EndEvent(NetLog::TYPE_PROXY_SERVICE_WAITING_FOR_INIT_PAC,
+ NULL);
// Note that we re-check for synchronous completion, in case we are
// no longer using a ProxyResolver (can happen if we fell-back to manual).
@@ -433,14 +460,14 @@
ProxyInfo* result,
CompletionCallback* callback,
PacRequest** pac_request,
- LoadLog* load_log) {
+ const BoundNetLog& net_log) {
// Check to see if we have a new config since ResolveProxy was called. We
// want to re-run ResolveProxy in two cases: 1) we have a new config, or 2) a
// direct connection failed and we never tried the current config.
bool re_resolve = result->config_id_ != config_.id();
if (!re_resolve) {
- UpdateConfig(load_log);
+ UpdateConfig(net_log);
if (result->config_id_ != config_.id()) {
// A new configuration!
re_resolve = true;
@@ -450,7 +477,7 @@
// If we have a new config or the config was never tried, we delete the
// list of bad proxies and we try again.
proxy_retry_info_.clear();
- return ResolveProxy(url, result, callback, pac_request, load_log);
+ return ResolveProxy(url, result, callback, pac_request, net_log);
}
// We don't have new proxy settings to try, try to fallback to the next proxy
@@ -483,24 +510,33 @@
int ProxyService::DidFinishResolvingProxy(ProxyInfo* result,
int result_code,
- LoadLog* load_log) {
+ const BoundNetLog& net_log) {
// Log the result of the proxy resolution.
if (result_code == OK) {
// When full logging is enabled, dump the proxy list.
- if (LoadLog::IsUnbounded(load_log)) {
- LoadLog::AddString(
- load_log,
- std::string("Resolved proxy list: ") + result->ToPacString());
+ if (net_log.HasListener()) {
+ net_log.AddEvent(
+ NetLog::TYPE_PROXY_SERVICE_RESOLVED_PROXY_LIST,
+ new NetLogStringParameter("pac_string", result->ToPacString()));
}
+ result->DeprioritizeBadProxies(proxy_retry_info_);
} else {
- LoadLog::AddErrorCode(load_log, result_code);
+ net_log.AddEvent(
+ NetLog::TYPE_PROXY_SERVICE_RESOLVED_PROXY_LIST,
+ new NetLogIntegerParameter("net_error", result_code));
+
+ // Fall-back to direct when the proxy resolver fails. This corresponds
+ // with a javascript runtime error in the PAC script.
+ //
+ // This implicit fall-back to direct matches Firefox 3.5 and
+ // Internet Explorer 8. For more information, see:
+ //
+ // http://www.chromium.org/developers/design-documents/proxy-settings-fallback
+ result->UseDirect();
+ result_code = OK;
}
- // Clean up the results list.
- if (result_code == OK)
- result->DeprioritizeBadProxies(proxy_retry_info_);
-
- LoadLog::EndEvent(load_log, LoadLog::TYPE_PROXY_SERVICE);
+ net_log.EndEvent(NetLog::TYPE_PROXY_SERVICE, NULL);
return result_code;
}
@@ -528,7 +564,7 @@
void ProxyService::ResetConfigService(
ProxyConfigService* new_proxy_config_service) {
config_service_.reset(new_proxy_config_service);
- UpdateConfig(NULL);
+ UpdateConfig(BoundNetLog());
}
void ProxyService::PurgeMemory() {
@@ -541,7 +577,7 @@
// start updating (normally this would happen lazily during the next
// call to ResolveProxy()).
config_.set_id(ProxyConfig::INVALID_ID);
- UpdateConfig(NULL);
+ UpdateConfig(BoundNetLog());
}
// static
@@ -578,31 +614,18 @@
#endif
}
-// static
-ProxyResolver* ProxyService::CreateNonV8ProxyResolver() {
-#if defined(OS_WIN)
- return new ProxyResolverWinHttp();
-#elif defined(OS_MACOSX)
- return new ProxyResolverMac();
-#else
- LOG(WARNING) << "PAC support disabled because there is no fallback "
- "non-V8 implementation";
- return new ProxyResolverNull();
-#endif
-}
-
-void ProxyService::UpdateConfig(LoadLog* load_log) {
+void ProxyService::UpdateConfig(const BoundNetLog& net_log) {
bool is_first_update = !config_has_been_initialized();
ProxyConfig latest;
// Fetch the proxy settings.
TimeTicks start_time = TimeTicks::Now();
- LoadLog::BeginEvent(load_log,
- LoadLog::TYPE_PROXY_SERVICE_POLL_CONFIG_SERVICE_FOR_CHANGES);
+ net_log.BeginEvent(
+ NetLog::TYPE_PROXY_SERVICE_POLL_CONFIG_SERVICE_FOR_CHANGES, NULL);
int rv = config_service_->GetProxyConfig(&latest);
- LoadLog::EndEvent(load_log,
- LoadLog::TYPE_PROXY_SERVICE_POLL_CONFIG_SERVICE_FOR_CHANGES);
+ net_log.EndEvent(NetLog::TYPE_PROXY_SERVICE_POLL_CONFIG_SERVICE_FOR_CHANGES,
+ NULL);
TimeTicks end_time = TimeTicks::Now();
// Record how long the call to config_service_->GetConfig() above took.
@@ -661,115 +684,28 @@
DCHECK(!init_proxy_resolver_.get());
init_proxy_resolver_.reset(
- new InitProxyResolver(resolver_.get(), proxy_script_fetcher_.get()));
-
- init_proxy_resolver_log_ = new LoadLog(kMaxNumLoadLogEntries);
+ new InitProxyResolver(resolver_.get(), proxy_script_fetcher_.get(),
+ net_log_));
int rv = init_proxy_resolver_->Init(
- config_, &init_proxy_resolver_callback_, init_proxy_resolver_log_);
+ config_, &init_proxy_resolver_callback_);
if (rv != ERR_IO_PENDING)
OnInitProxyResolverComplete(rv);
}
-void ProxyService::UpdateConfigIfOld(LoadLog* load_log) {
+void ProxyService::UpdateConfigIfOld(const BoundNetLog& net_log) {
// The overhead of calling ProxyConfigService::GetProxyConfig is very low.
const TimeDelta kProxyConfigMaxAge = TimeDelta::FromSeconds(5);
// Periodically check for a new config.
if (!config_has_been_initialized() ||
(TimeTicks::Now() - config_last_update_time_) > kProxyConfigMaxAge)
- UpdateConfig(load_log);
+ UpdateConfig(net_log);
}
-bool ProxyService::ShouldBypassProxyForURL(const GURL& url) {
- std::string url_domain = url.scheme();
- if (!url_domain.empty())
- url_domain += "://";
-
- url_domain += url.host();
- // This isn't superfluous; GURL case canonicalization doesn't hit the embedded
- // percent-encoded characters.
- StringToLowerASCII(&url_domain);
-
- // TODO(eroman): use GetHostAndPort().
- std::string url_domain_and_port = url_domain + ":"
- + IntToString(url.EffectiveIntPort());
-
- if (config_.proxy_bypass_local_names && IsLocalName(url))
- return true;
-
- for(std::vector<std::string>::const_iterator i = config_.proxy_bypass.begin();
- i != config_.proxy_bypass.end(); ++i) {
- std::string bypass_url_domain = *i;
-
- // The proxy server bypass list can contain entities with http/https
- // If no scheme is specified then it indicates that all schemes are
- // allowed for the current entry. For matching this we just use
- // the protocol scheme of the url passed in.
- size_t scheme_colon = bypass_url_domain.find("://");
- if (scheme_colon == std::string::npos) {
- std::string bypass_url_domain_with_scheme = url.scheme();
- scheme_colon = bypass_url_domain_with_scheme.length();
- bypass_url_domain_with_scheme += "://";
- bypass_url_domain_with_scheme += bypass_url_domain;
-
- bypass_url_domain = bypass_url_domain_with_scheme;
- }
- std::string* url_compare_reference = &url_domain;
- size_t port_colon = bypass_url_domain.rfind(":");
- if (port_colon > scheme_colon) {
- // If our match pattern includes a colon followed by a digit,
- // and either it's preceded by ']' (IPv6 with port)
- // or has no other colon (IPv4),
- // then match against <domain>:<port>.
- // TODO(sdoyon): straighten this out, in particular the IPv6 brackets,
- // and do the parsing in ProxyConfig when we do the CIDR matching
- // mentioned below.
- std::string::const_iterator domain_begin =
- bypass_url_domain.begin() + scheme_colon + 3; // after ://
- std::string::const_iterator port_iter =
- bypass_url_domain.begin() + port_colon;
- std::string::const_iterator end = bypass_url_domain.end();
- if ((port_iter + 1) < end && IsAsciiDigit(*(port_iter + 1)) &&
- (*(port_iter - 1) == ']' ||
- std::find(domain_begin, port_iter, ':') == port_iter))
- url_compare_reference = &url_domain_and_port;
- }
-
- StringToLowerASCII(&bypass_url_domain);
-
- if (MatchPatternASCII(*url_compare_reference, bypass_url_domain))
- return true;
-
- // Some systems (the Mac, for example) allow CIDR-style specification of
- // proxy bypass for IP-specified hosts (e.g. "10.0.0.0/8"; see
- // http://www.tcd.ie/iss/internet/osx_proxy.php for a real-world example).
- // That's kinda cool so we'll provide that for everyone.
- // TODO(avi): implement here. See: http://crbug.com/9835.
- // IP addresses ought to be canonicalized for comparison (whether
- // with CIDR, port, or IP address alone).
- }
-
- return false;
-}
-
-// This matches IE's interpretation of the
-// "Bypass proxy server for local addresses" settings checkbox. Fully
-// qualified domain names or IP addresses are considered non-local,
-// regardless of what they map to.
-//
-// static
-bool ProxyService::IsLocalName(const GURL& url) {
- const std::string& host = url.host();
- if (host == "127.0.0.1" || host == "[::1]")
- return true;
- return host.find('.') == std::string::npos;
-}
void ProxyService::OnIPAddressChanged() {
- DCHECK(network_change_notifier_);
-
// Mark the current configuration as being un-initialized.
//
// This will force us to re-fetch the configuration (and re-run all of
@@ -790,11 +726,11 @@
int SyncProxyServiceHelper::ResolveProxy(const GURL& url,
ProxyInfo* proxy_info,
- LoadLog* load_log) {
+ const BoundNetLog& net_log) {
DCHECK(io_message_loop_ != MessageLoop::current());
io_message_loop_->PostTask(FROM_HERE, NewRunnableMethod(
- this, &SyncProxyServiceHelper::StartAsyncResolve, url, load_log));
+ this, &SyncProxyServiceHelper::StartAsyncResolve, url, net_log));
event_.Wait();
@@ -805,11 +741,11 @@
}
int SyncProxyServiceHelper::ReconsiderProxyAfterError(
- const GURL& url, ProxyInfo* proxy_info, LoadLog* load_log) {
+ const GURL& url, ProxyInfo* proxy_info, const BoundNetLog& net_log) {
DCHECK(io_message_loop_ != MessageLoop::current());
io_message_loop_->PostTask(FROM_HERE, NewRunnableMethod(
- this, &SyncProxyServiceHelper::StartAsyncReconsider, url, load_log));
+ this, &SyncProxyServiceHelper::StartAsyncReconsider, url, net_log));
event_.Wait();
@@ -820,18 +756,18 @@
}
void SyncProxyServiceHelper::StartAsyncResolve(const GURL& url,
- LoadLog* load_log) {
+ const BoundNetLog& net_log) {
result_ = proxy_service_->ResolveProxy(
- url, &proxy_info_, &callback_, NULL, load_log);
+ url, &proxy_info_, &callback_, NULL, net_log);
if (result_ != net::ERR_IO_PENDING) {
OnCompletion(result_);
}
}
void SyncProxyServiceHelper::StartAsyncReconsider(const GURL& url,
- LoadLog* load_log) {
+ const BoundNetLog& net_log) {
result_ = proxy_service_->ReconsiderProxyAfterError(
- url, &proxy_info_, &callback_, NULL, load_log);
+ url, &proxy_info_, &callback_, NULL, net_log);
if (result_ != net::ERR_IO_PENDING) {
OnCompletion(result_);
}
diff --git a/net/proxy/proxy_service.h b/net/proxy/proxy_service.h
index 8614d55..a702b38 100644
--- a/net/proxy/proxy_service.h
+++ b/net/proxy/proxy_service.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -13,6 +13,7 @@
#include "base/waitable_event.h"
#include "net/base/completion_callback.h"
#include "net/base/network_change_notifier.h"
+#include "net/base/net_log.h"
#include "net/proxy/proxy_server.h"
#include "net/proxy/proxy_info.h"
#include "testing/gtest/include/gtest/gtest_prod.h"
@@ -24,7 +25,6 @@
namespace net {
class InitProxyResolver;
-class LoadLog;
class ProxyConfigService;
class ProxyResolver;
class ProxyScriptFetcher;
@@ -36,11 +36,11 @@
public NetworkChangeNotifier::Observer {
public:
// The instance takes ownership of |config_service| and |resolver|.
- // If |network_change_notifier| is non-NULL, the proxy service will register
- // with it to detect when the network setup has changed. This is used to
- // decide when to re-configure the proxy discovery.
- ProxyService(ProxyConfigService* config_service, ProxyResolver* resolver,
- NetworkChangeNotifier* network_change_notifier);
+ // |net_log| is a possibly NULL destination to send log events to. It must
+ // remain alive for the lifetime of this ProxyService.
+ ProxyService(ProxyConfigService* config_service,
+ ProxyResolver* resolver,
+ NetLog* net_log);
// Used internally to handle PAC queries.
// TODO(eroman): consider naming this simply "Request".
@@ -64,12 +64,12 @@
// 2. PAC URL
// 3. named proxy
//
- // Profiling information for the request is saved to |load_log| if non-NULL.
+ // Profiling information for the request is saved to |net_log| if non-NULL.
int ResolveProxy(const GURL& url,
ProxyInfo* results,
CompletionCallback* callback,
PacRequest** pac_request,
- LoadLog* load_log);
+ const BoundNetLog& net_log);
// This method is called after a failure to connect or resolve a host name.
// It gives the proxy service an opportunity to reconsider the proxy to use.
@@ -82,12 +82,12 @@
//
// Returns ERR_FAILED if there is not another proxy config to try.
//
- // Profiling information for the request is saved to |load_log| if non-NULL.
+ // Profiling information for the request is saved to |net_log| if non-NULL.
int ReconsiderProxyAfterError(const GURL& url,
ProxyInfo* results,
CompletionCallback* callback,
PacRequest** pac_request,
- LoadLog* load_log);
+ const BoundNetLog& net_log);
// Call this method with a non-null |pac_request| to cancel the PAC request.
void CancelPacRequest(PacRequest* pac_request);
@@ -108,13 +108,6 @@
// Tells the resolver to purge any memory it does not need.
void PurgeMemory();
- // Returns the log for the most recent WPAD + PAC initialization.
- // (This shows how much time was spent downloading and parsing the
- // PAC scripts for the current configuration).
- LoadLog* init_proxy_resolver_log() const {
- return init_proxy_resolver_log_;
- }
-
// Returns true if we have called UpdateConfig() at least once.
bool config_has_been_initialized() const {
return config_.id() != ProxyConfig::INVALID_ID;
@@ -144,11 +137,25 @@
// the proxy settings change. We take ownership of |proxy_config_service|.
// Iff |use_v8_resolver| is true, then the V8 implementation is
// used.
+ //
+ // |num_pac_threads| specifies the maximum number of threads to use for
+ // executing PAC scripts. Threads are created lazily on demand.
+ // If |0| is specified, then a default number of threads will be selected.
+ //
+ // Having more threads avoids stalling proxy resolve requests when the
+ // PAC script takes a while to run. This is particularly a problem when PAC
+ // scripts do synchronous DNS resolutions, since that can take on the order
+ // of seconds.
+ //
+ // However, the disadvantages of using more than 1 thread are:
+ // (a) can cause compatibility issues for scripts that rely on side effects
+ // between runs (such scripts should not be common though).
+ // (b) increases the memory used by proxy resolving, as each thread will
+ // duplicate its own script context.
+
// |url_request_context| is only used when use_v8_resolver is true:
// it specifies the URL request context that will be used if a PAC
// script needs to be fetched.
- // |network_change_notifier| may be NULL. Otherwise it will be used to
- // signal the ProxyService when the network setup has changed.
// |io_loop| points to the IO thread's message loop. It is only used
// when pc is NULL.
// ##########################################################################
@@ -159,8 +166,9 @@
static ProxyService* Create(
ProxyConfigService* proxy_config_service,
bool use_v8_resolver,
+ size_t num_pac_threads,
URLRequestContext* url_request_context,
- NetworkChangeNotifier* network_change_notifier,
+ NetLog* net_log,
MessageLoop* io_loop);
// Convenience method that creates a proxy service using the
@@ -178,7 +186,6 @@
private:
friend class base::RefCountedThreadSafe<ProxyService>;
- FRIEND_TEST(ProxyServiceTest, IsLocalName);
FRIEND_TEST(ProxyServiceTest, UpdateConfigAfterFailedAutodetect);
FRIEND_TEST(ProxyServiceTest, UpdateConfigFromPACToDirect);
friend class PacRequest;
@@ -191,16 +198,12 @@
~ProxyService();
- // Creates a proxy resolver appropriate for this platform that doesn't rely
- // on V8.
- static ProxyResolver* CreateNonV8ProxyResolver();
-
// Identifies the proxy configuration.
ProxyConfig::ID config_id() const { return config_.id(); }
// Checks to see if the proxy configuration changed, and then updates config_
// to reference the new configuration.
- void UpdateConfig(LoadLog* load_log);
+ void UpdateConfig(const BoundNetLog& net_log);
// Assign |config| as the current configuration.
void SetConfig(const ProxyConfig& config);
@@ -210,7 +213,7 @@
void StartInitProxyResolver();
// Tries to update the configuration if it hasn't been checked in a while.
- void UpdateConfigIfOld(LoadLog* load_log);
+ void UpdateConfigIfOld(const BoundNetLog& net_log);
// Returns true if the proxy resolver is being initialized for PAC
// (downloading PAC script(s) + testing).
@@ -228,12 +231,7 @@
// Completing synchronously means we don't need to query ProxyResolver.
int TryToCompleteSynchronously(const GURL& url, ProxyInfo* result);
- // Set |result| with the proxy to use for |url|, based on |rules|.
- void ApplyProxyRules(const GURL& url,
- const ProxyConfig::ProxyRules& rules,
- ProxyInfo* result);
-
- // Cancel all of the requests sent to the ProxyResolver. These will be
+ // Cancels all of the requests sent to the ProxyResolver. These will be
// restarted when calling ResumeAllPendingRequests().
void SuspendAllPendingRequests();
@@ -251,18 +249,10 @@
// bad entries from the results list.
int DidFinishResolvingProxy(ProxyInfo* result,
int result_code,
- LoadLog* load_log);
+ const BoundNetLog& net_log);
- // Returns true if the URL passed in should not go through the proxy server.
- // 1. If the proxy settings say to bypass local names, and |IsLocalName(url)|.
- // 2. The URL matches one of the entities in the proxy bypass list.
- bool ShouldBypassProxyForURL(const GURL& url);
-
- // Returns true if |url| is to an intranet site (using non-FQDN as the
- // heuristic).
- static bool IsLocalName(const GURL& url);
-
- // NetworkChangeNotifier::Observer methods:
+ // NetworkChangeNotifier::Observer
+ // When this is called, we re-fetch PAC scripts and re-run WPAD.
virtual void OnIPAddressChanged();
scoped_ptr<ProxyConfigService> config_service_;
@@ -301,12 +291,9 @@
// |proxy_resolver_| must outlive |init_proxy_resolver_|.
scoped_ptr<InitProxyResolver> init_proxy_resolver_;
- // Log from the *last* time |init_proxy_resolver_.Init()| was called, or NULL.
- scoped_refptr<LoadLog> init_proxy_resolver_log_;
-
- // The (possibly NULL) network change notifier that we use to decide when
- // to refetch PAC scripts or re-run WPAD.
- NetworkChangeNotifier* const network_change_notifier_;
+ // This is the log where any events generated by |init_proxy_resolver_| are
+ // sent to.
+ NetLog* net_log_;
DISALLOW_COPY_AND_ASSIGN(ProxyService);
};
@@ -318,17 +305,20 @@
SyncProxyServiceHelper(MessageLoop* io_message_loop,
ProxyService* proxy_service);
- int ResolveProxy(const GURL& url, ProxyInfo* proxy_info, LoadLog* load_log);
+ int ResolveProxy(const GURL& url,
+ ProxyInfo* proxy_info,
+ const BoundNetLog& net_log);
int ReconsiderProxyAfterError(const GURL& url,
- ProxyInfo* proxy_info, LoadLog* load_log);
+ ProxyInfo* proxy_info,
+ const BoundNetLog& net_log);
private:
friend class base::RefCountedThreadSafe<SyncProxyServiceHelper>;
~SyncProxyServiceHelper() {}
- void StartAsyncResolve(const GURL& url, LoadLog* load_log);
- void StartAsyncReconsider(const GURL& url, LoadLog* load_log);
+ void StartAsyncResolve(const GURL& url, const BoundNetLog& net_log);
+ void StartAsyncReconsider(const GURL& url, const BoundNetLog& net_log);
void OnCompletion(int result);
diff --git a/net/proxy/proxy_service_unittest.cc b/net/proxy/proxy_service_unittest.cc
index 2e57e51..a69b66e 100644
--- a/net/proxy/proxy_service_unittest.cc
+++ b/net/proxy/proxy_service_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -10,9 +10,8 @@
#include "base/logging.h"
#include "base/string_util.h"
#include "googleurl/src/gurl.h"
-#include "net/base/load_log.h"
-#include "net/base/load_log_unittest.h"
-#include "net/base/mock_network_change_notifier.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
#include "net/base/net_errors.h"
#include "net/base/test_completion_callback.h"
#include "net/proxy/mock_proxy_resolver.h"
@@ -31,7 +30,7 @@
MockProxyConfigService() {} // Direct connect.
explicit MockProxyConfigService(const ProxyConfig& pc) : config(pc) {}
explicit MockProxyConfigService(const std::string& pac_url) {
- config.pac_url = GURL(pac_url);
+ config.set_pac_url(GURL(pac_url));
}
virtual int GetProxyConfig(ProxyConfig* results) {
@@ -49,25 +48,25 @@
class MockProxyScriptFetcher : public ProxyScriptFetcher {
public:
MockProxyScriptFetcher()
- : pending_request_callback_(NULL), pending_request_bytes_(NULL) {
+ : pending_request_callback_(NULL), pending_request_text_(NULL) {
}
// ProxyScriptFetcher implementation.
virtual int Fetch(const GURL& url,
- std::string* bytes,
+ string16* text,
CompletionCallback* callback) {
DCHECK(!has_pending_request());
// Save the caller's information, and have them wait.
pending_request_url_ = url;
pending_request_callback_ = callback;
- pending_request_bytes_ = bytes;
+ pending_request_text_ = text;
return ERR_IO_PENDING;
}
- void NotifyFetchCompletion(int result, const std::string& bytes) {
+ void NotifyFetchCompletion(int result, const std::string& ascii_text) {
DCHECK(has_pending_request());
- *pending_request_bytes_ = bytes;
+ *pending_request_text_ = ASCIIToUTF16(ascii_text);
CompletionCallback* callback = pending_request_callback_;
pending_request_callback_ = NULL;
callback->Run(result);
@@ -86,7 +85,7 @@
private:
GURL pending_request_url_;
CompletionCallback* pending_request_callback_;
- std::string* pending_request_bytes_;
+ string16* pending_request_text_;
};
TEST(ProxyServiceTest, Direct) {
@@ -98,18 +97,19 @@
ProxyInfo info;
TestCompletionCallback callback;
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
- int rv = service->ResolveProxy(url, &info, &callback, NULL, log);
+ CapturingBoundNetLog log(CapturingNetLog::kUnbounded);
+ int rv = service->ResolveProxy(url, &info, &callback, NULL, log.bound());
EXPECT_EQ(OK, rv);
EXPECT_TRUE(resolver->pending_requests().empty());
- EXPECT_TRUE(NULL == service->init_proxy_resolver_log());
EXPECT_TRUE(info.is_direct());
- // Check the LoadLog was filled correctly.
- EXPECT_EQ(5u, log->entries().size());
- EXPECT_TRUE(LogContainsBeginEvent(*log, 0, LoadLog::TYPE_PROXY_SERVICE));
- EXPECT_TRUE(LogContainsEndEvent(*log, 4, LoadLog::TYPE_PROXY_SERVICE));
+ // Check the NetLog was filled correctly.
+ EXPECT_EQ(5u, log.entries().size());
+ EXPECT_TRUE(LogContainsBeginEvent(
+ log.entries(), 0, NetLog::TYPE_PROXY_SERVICE));
+ EXPECT_TRUE(LogContainsEndEvent(
+ log.entries(), 4, NetLog::TYPE_PROXY_SERVICE));
}
TEST(ProxyServiceTest, PAC) {
@@ -125,13 +125,13 @@
ProxyInfo info;
TestCompletionCallback callback;
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
- int rv = service->ResolveProxy(url, &info, &callback, NULL, log);
+ CapturingBoundNetLog log(CapturingNetLog::kUnbounded);
+
+ int rv = service->ResolveProxy(url, &info, &callback, NULL, log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(GURL("http://foopy/proxy.pac"),
- resolver->pending_set_pac_script_request()->pac_url());
- EXPECT_FALSE(NULL == service->init_proxy_resolver_log());
+ resolver->pending_set_pac_script_request()->script_data()->url());
resolver->pending_set_pac_script_request()->CompleteNow(OK);
ASSERT_EQ(1u, resolver->pending_requests().size());
@@ -145,14 +145,16 @@
EXPECT_FALSE(info.is_direct());
EXPECT_EQ("foopy:80", info.proxy_server().ToURI());
- // Check the LoadLog was filled correctly.
- EXPECT_EQ(7u, log->entries().size());
- EXPECT_TRUE(LogContainsBeginEvent(*log, 0, LoadLog::TYPE_PROXY_SERVICE));
+ // Check the NetLog was filled correctly.
+ EXPECT_EQ(7u, log.entries().size());
EXPECT_TRUE(LogContainsBeginEvent(
- *log, 3, LoadLog::TYPE_PROXY_SERVICE_WAITING_FOR_INIT_PAC));
+ log.entries(), 0, NetLog::TYPE_PROXY_SERVICE));
+ EXPECT_TRUE(LogContainsBeginEvent(
+ log.entries(), 3, NetLog::TYPE_PROXY_SERVICE_WAITING_FOR_INIT_PAC));
EXPECT_TRUE(LogContainsEndEvent(
- *log, 4, LoadLog::TYPE_PROXY_SERVICE_WAITING_FOR_INIT_PAC));
- EXPECT_TRUE(LogContainsEndEvent(*log, 6, LoadLog::TYPE_PROXY_SERVICE));
+ log.entries(), 4, NetLog::TYPE_PROXY_SERVICE_WAITING_FOR_INIT_PAC));
+ EXPECT_TRUE(LogContainsEndEvent(
+ log.entries(), 6, NetLog::TYPE_PROXY_SERVICE));
}
// Test that the proxy resolver does not see the URL's username/password
@@ -170,11 +172,11 @@
ProxyInfo info;
TestCompletionCallback callback;
- int rv = service->ResolveProxy(url, &info, &callback, NULL, NULL);
+ int rv = service->ResolveProxy(url, &info, &callback, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(GURL("http://foopy/proxy.pac"),
- resolver->pending_set_pac_script_request()->pac_url());
+ resolver->pending_set_pac_script_request()->script_data()->url());
resolver->pending_set_pac_script_request()->CompleteNow(OK);
ASSERT_EQ(1u, resolver->pending_requests().size());
@@ -198,11 +200,11 @@
ProxyInfo info;
TestCompletionCallback callback1;
- int rv = service->ResolveProxy(url, &info, &callback1, NULL, NULL);
+ int rv = service->ResolveProxy(url, &info, &callback1, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(GURL("http://foopy/proxy.pac"),
- resolver->pending_set_pac_script_request()->pac_url());
+ resolver->pending_set_pac_script_request()->script_data()->url());
resolver->pending_set_pac_script_request()->CompleteNow(OK);
ASSERT_EQ(1u, resolver->pending_requests().size());
@@ -220,7 +222,8 @@
// left to fallback to, since our proxy list was NOT terminated by
// DIRECT.
TestCompletionCallback callback2;
- rv = service->ReconsiderProxyAfterError(url, &info, &callback2, NULL, NULL);
+ rv = service->ReconsiderProxyAfterError(url, &info, &callback2, NULL,
+ BoundNetLog());
// ReconsiderProxyAfterError returns error indicating nothing left.
EXPECT_EQ(ERR_FAILED, rv);
EXPECT_TRUE(info.is_empty());
@@ -255,11 +258,11 @@
ProxyInfo info;
TestCompletionCallback callback1;
- int rv = service->ResolveProxy(url, &info, &callback1, NULL, NULL);
+ int rv = service->ResolveProxy(url, &info, &callback1, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(GURL("http://foopy/proxy.pac"),
- resolver->pending_set_pac_script_request()->pac_url());
+ resolver->pending_set_pac_script_request()->script_data()->url());
resolver->pending_set_pac_script_request()->CompleteNow(OK);
ASSERT_EQ(1u, resolver->pending_requests().size());
@@ -275,27 +278,31 @@
// Fallback 1.
TestCompletionCallback callback2;
- rv = service->ReconsiderProxyAfterError(url, &info, &callback2, NULL, NULL);
+ rv = service->ReconsiderProxyAfterError(url, &info, &callback2, NULL,
+ BoundNetLog());
EXPECT_EQ(OK, rv);
EXPECT_FALSE(info.is_direct());
EXPECT_EQ("foobar:10", info.proxy_server().ToURI());
// Fallback 2.
TestCompletionCallback callback3;
- rv = service->ReconsiderProxyAfterError(url, &info, &callback3, NULL, NULL);
+ rv = service->ReconsiderProxyAfterError(url, &info, &callback3, NULL,
+ BoundNetLog());
EXPECT_EQ(OK, rv);
EXPECT_TRUE(info.is_direct());
// Fallback 3.
TestCompletionCallback callback4;
- rv = service->ReconsiderProxyAfterError(url, &info, &callback4, NULL, NULL);
+ rv = service->ReconsiderProxyAfterError(url, &info, &callback4, NULL,
+ BoundNetLog());
EXPECT_EQ(OK, rv);
EXPECT_FALSE(info.is_direct());
EXPECT_EQ("foobar:20", info.proxy_server().ToURI());
// Fallback 4 -- Nothing to fall back to!
TestCompletionCallback callback5;
- rv = service->ReconsiderProxyAfterError(url, &info, &callback5, NULL, NULL);
+ rv = service->ReconsiderProxyAfterError(url, &info, &callback5, NULL,
+ BoundNetLog());
EXPECT_EQ(ERR_FAILED, rv);
EXPECT_TRUE(info.is_empty());
}
@@ -317,11 +324,11 @@
GURL url("http://www.google.com/");
ProxyInfo info;
TestCompletionCallback callback1;
- int rv = service->ResolveProxy(url, &info, &callback1, NULL, NULL);
+ int rv = service->ResolveProxy(url, &info, &callback1, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(GURL("http://foopy/proxy.pac"),
- resolver->pending_set_pac_script_request()->pac_url());
+ resolver->pending_set_pac_script_request()->script_data()->url());
resolver->pending_set_pac_script_request()->CompleteNow(OK);
ASSERT_EQ(1u, resolver->pending_requests().size());
@@ -330,12 +337,15 @@
// Fail the first resolve request in MockAsyncProxyResolver.
resolver->pending_requests()[0]->CompleteNow(ERR_FAILED);
- EXPECT_EQ(ERR_FAILED, callback1.WaitForResult());
+ // Although the proxy resolver failed the request, ProxyService implicitly
+ // falls-back to DIRECT.
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_TRUE(info.is_direct());
// The second resolve request will try to run through the proxy resolver,
// regardless of whether the first request failed in it.
TestCompletionCallback callback2;
- rv = service->ResolveProxy(url, &info, &callback2, NULL, NULL);
+ rv = service->ResolveProxy(url, &info, &callback2, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
ASSERT_EQ(1u, resolver->pending_requests().size());
@@ -368,11 +378,11 @@
// Get the proxy information.
ProxyInfo info;
TestCompletionCallback callback1;
- int rv = service->ResolveProxy(url, &info, &callback1, NULL, NULL);
+ int rv = service->ResolveProxy(url, &info, &callback1, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(GURL("http://foopy/proxy.pac"),
- resolver->pending_set_pac_script_request()->pac_url());
+ resolver->pending_set_pac_script_request()->script_data()->url());
resolver->pending_set_pac_script_request()->CompleteNow(OK);
ASSERT_EQ(1u, resolver->pending_requests().size());
@@ -390,14 +400,15 @@
// Fake an error on the proxy.
TestCompletionCallback callback2;
- rv = service->ReconsiderProxyAfterError(url, &info, &callback2, NULL, NULL);
+ rv = service->ReconsiderProxyAfterError(url, &info, &callback2, NULL,
+ BoundNetLog());
EXPECT_EQ(OK, rv);
// The second proxy should be specified.
EXPECT_EQ("foopy2:9090", info.proxy_server().ToURI());
TestCompletionCallback callback3;
- rv = service->ResolveProxy(url, &info, &callback3, NULL, NULL);
+ rv = service->ResolveProxy(url, &info, &callback3, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
ASSERT_EQ(1u, resolver->pending_requests().size());
@@ -415,7 +426,8 @@
// We fake another error. It should now try the third one.
TestCompletionCallback callback4;
- rv = service->ReconsiderProxyAfterError(url, &info, &callback4, NULL, NULL);
+ rv = service->ReconsiderProxyAfterError(url, &info, &callback4, NULL,
+ BoundNetLog());
EXPECT_EQ(OK, rv);
EXPECT_EQ("foopy2:9090", info.proxy_server().ToURI());
@@ -423,14 +435,16 @@
// proxy servers we thought were valid; next we try the proxy server
// that was in our bad proxies map (foopy1:8080).
TestCompletionCallback callback5;
- rv = service->ReconsiderProxyAfterError(url, &info, &callback5, NULL, NULL);
+ rv = service->ReconsiderProxyAfterError(url, &info, &callback5, NULL,
+ BoundNetLog());
EXPECT_EQ(OK, rv);
EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI());
// Fake another error, the last proxy is gone, the list should now be empty,
// so there is nothing left to try.
TestCompletionCallback callback6;
- rv = service->ReconsiderProxyAfterError(url, &info, &callback6, NULL, NULL);
+ rv = service->ReconsiderProxyAfterError(url, &info, &callback6, NULL,
+ BoundNetLog());
EXPECT_EQ(ERR_FAILED, rv);
EXPECT_FALSE(info.is_direct());
EXPECT_TRUE(info.is_empty());
@@ -454,11 +468,11 @@
// Get the proxy information.
ProxyInfo info;
TestCompletionCallback callback1;
- int rv = service->ResolveProxy(url, &info, &callback1, NULL, NULL);
+ int rv = service->ResolveProxy(url, &info, &callback1, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(GURL("http://foopy/proxy.pac"),
- resolver->pending_set_pac_script_request()->pac_url());
+ resolver->pending_set_pac_script_request()->script_data()->url());
resolver->pending_set_pac_script_request()->CompleteNow(OK);
ASSERT_EQ(1u, resolver->pending_requests().size());
@@ -476,7 +490,8 @@
// Fake an error on the proxy.
TestCompletionCallback callback2;
- rv = service->ReconsiderProxyAfterError(url, &info, &callback2, NULL, NULL);
+ rv = service->ReconsiderProxyAfterError(url, &info, &callback2, NULL,
+ BoundNetLog());
EXPECT_EQ(OK, rv);
// Now we get back the second proxy.
@@ -484,7 +499,8 @@
// Fake an error on this proxy as well.
TestCompletionCallback callback3;
- rv = service->ReconsiderProxyAfterError(url, &info, &callback3, NULL, NULL);
+ rv = service->ReconsiderProxyAfterError(url, &info, &callback3, NULL,
+ BoundNetLog());
EXPECT_EQ(OK, rv);
// Finally, we get back DIRECT.
@@ -492,7 +508,8 @@
// Now we tell the proxy service that even DIRECT failed.
TestCompletionCallback callback4;
- rv = service->ReconsiderProxyAfterError(url, &info, &callback4, NULL, NULL);
+ rv = service->ReconsiderProxyAfterError(url, &info, &callback4, NULL,
+ BoundNetLog());
// There was nothing left to try after DIRECT, so we are out of
// choices.
EXPECT_EQ(ERR_FAILED, rv);
@@ -514,11 +531,11 @@
// Get the proxy information.
ProxyInfo info;
TestCompletionCallback callback1;
- int rv = service->ResolveProxy(url, &info, &callback1, NULL, NULL);
+ int rv = service->ResolveProxy(url, &info, &callback1, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(GURL("http://foopy/proxy.pac"),
- resolver->pending_set_pac_script_request()->pac_url());
+ resolver->pending_set_pac_script_request()->script_data()->url());
resolver->pending_set_pac_script_request()->CompleteNow(OK);
ASSERT_EQ(1u, resolver->pending_requests().size());
@@ -536,14 +553,15 @@
// Fake an error on the proxy, and also a new configuration on the proxy.
config_service->config = ProxyConfig();
- config_service->config.pac_url = GURL("http://foopy-new/proxy.pac");
+ config_service->config.set_pac_url(GURL("http://foopy-new/proxy.pac"));
TestCompletionCallback callback2;
- rv = service->ReconsiderProxyAfterError(url, &info, &callback2, NULL, NULL);
+ rv = service->ReconsiderProxyAfterError(url, &info, &callback2, NULL,
+ BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(GURL("http://foopy-new/proxy.pac"),
- resolver->pending_set_pac_script_request()->pac_url());
+ resolver->pending_set_pac_script_request()->script_data()->url());
resolver->pending_set_pac_script_request()->CompleteNow(OK);
ASSERT_EQ(1u, resolver->pending_requests().size());
@@ -559,21 +577,23 @@
// We fake another error. It should now ignore the first one.
TestCompletionCallback callback3;
- rv = service->ReconsiderProxyAfterError(url, &info, &callback3, NULL, NULL);
+ rv = service->ReconsiderProxyAfterError(url, &info, &callback3, NULL,
+ BoundNetLog());
EXPECT_EQ(OK, rv);
EXPECT_EQ("foopy2:9090", info.proxy_server().ToURI());
// We simulate a new configuration.
config_service->config = ProxyConfig();
- config_service->config.pac_url = GURL("http://foopy-new2/proxy.pac");
+ config_service->config.set_pac_url(GURL("http://foopy-new2/proxy.pac"));
// We fake another error. It should go back to the first proxy.
TestCompletionCallback callback4;
- rv = service->ReconsiderProxyAfterError(url, &info, &callback4, NULL, NULL);
+ rv = service->ReconsiderProxyAfterError(url, &info, &callback4, NULL,
+ BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(GURL("http://foopy-new2/proxy.pac"),
- resolver->pending_set_pac_script_request()->pac_url());
+ resolver->pending_set_pac_script_request()->script_data()->url());
resolver->pending_set_pac_script_request()->CompleteNow(OK);
ASSERT_EQ(1u, resolver->pending_requests().size());
@@ -603,11 +623,11 @@
// Get the proxy information.
ProxyInfo info;
TestCompletionCallback callback1;
- int rv = service->ResolveProxy(url, &info, &callback1, NULL, NULL);
+ int rv = service->ResolveProxy(url, &info, &callback1, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(GURL("http://foopy/proxy.pac"),
- resolver->pending_set_pac_script_request()->pac_url());
+ resolver->pending_set_pac_script_request()->script_data()->url());
resolver->pending_set_pac_script_request()->CompleteNow(OK);
ASSERT_EQ(1u, resolver->pending_requests().size());
EXPECT_EQ(url, resolver->pending_requests()[0]->url());
@@ -623,7 +643,8 @@
// Fake a proxy error.
TestCompletionCallback callback2;
- rv = service->ReconsiderProxyAfterError(url, &info, &callback2, NULL, NULL);
+ rv = service->ReconsiderProxyAfterError(url, &info, &callback2, NULL,
+ BoundNetLog());
EXPECT_EQ(OK, rv);
// The first proxy is ignored, and the second one is selected.
@@ -633,7 +654,7 @@
// Fake a PAC failure.
ProxyInfo info2;
TestCompletionCallback callback3;
- rv = service->ResolveProxy(url, &info2, &callback3, NULL, NULL);
+ rv = service->ResolveProxy(url, &info2, &callback3, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
ASSERT_EQ(1u, resolver->pending_requests().size());
@@ -642,17 +663,19 @@
// This simulates a javascript runtime error in the PAC script.
resolver->pending_requests()[0]->CompleteNow(ERR_FAILED);
- // No proxy servers are returned, since we failed.
- EXPECT_EQ(ERR_FAILED, callback3.WaitForResult());
- EXPECT_FALSE(info2.is_direct());
- EXPECT_TRUE(info2.is_empty());
+ // Although the resolver failed, the ProxyService will implicitly fall-back
+ // to a DIRECT connection.
+ EXPECT_EQ(OK, callback3.WaitForResult());
+ EXPECT_TRUE(info2.is_direct());
+ EXPECT_FALSE(info2.is_empty());
// The PAC script will work properly next time and successfully return a
// proxy list. Since we have not marked the configuration as bad, it should
// "just work" the next time we call it.
ProxyInfo info3;
TestCompletionCallback callback4;
- rv = service->ReconsiderProxyAfterError(url, &info3, &callback4, NULL, NULL);
+ rv = service->ReconsiderProxyAfterError(url, &info3, &callback4, NULL,
+ BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
ASSERT_EQ(1u, resolver->pending_requests().size());
@@ -670,224 +693,46 @@
}
TEST(ProxyServiceTest, ProxyBypassList) {
- // Test what happens when a proxy bypass list is specified.
+ // Test that the proxy bypass rules are consulted.
- ProxyInfo info;
+ TestCompletionCallback callback[2];
+ ProxyInfo info[2];
ProxyConfig config;
- config.proxy_rules.ParseFromString("foopy1:8080;foopy2:9090");
- config.auto_detect = false;
- config.proxy_bypass_local_names = true;
+ config.proxy_rules().ParseFromString("foopy1:8080;foopy2:9090");
+ config.set_auto_detect(false);
+ config.proxy_rules().bypass_rules.ParseFromString("*.org");
- {
- scoped_refptr<ProxyService> service(new ProxyService(
- new MockProxyConfigService(config),
- new MockAsyncProxyResolver(),
- NULL));
- GURL url("http://www.google.com/");
- // Get the proxy information.
- TestCompletionCallback callback;
- int rv = service->ResolveProxy(url, &info, &callback, NULL, NULL);
- EXPECT_EQ(OK, rv);
- EXPECT_FALSE(info.is_direct());
- }
+ scoped_refptr<ProxyService> service(new ProxyService(
+ new MockProxyConfigService(config), new MockAsyncProxyResolver, NULL));
- {
- scoped_refptr<ProxyService> service(new ProxyService(
- new MockProxyConfigService(config),
- new MockAsyncProxyResolver(),
- NULL));
- GURL test_url("http://local");
- TestCompletionCallback callback;
- int rv = service->ResolveProxy(test_url, &info, &callback, NULL, NULL);
- EXPECT_EQ(OK, rv);
- EXPECT_TRUE(info.is_direct());
- }
+ int rv;
+ GURL url1("http://www.webkit.org");
+ GURL url2("http://www.webkit.com");
- config.proxy_bypass.clear();
- config.proxy_bypass.push_back("*.org");
- config.proxy_bypass_local_names = true;
- {
- scoped_refptr<ProxyService> service(new ProxyService(
- new MockProxyConfigService(config), new MockAsyncProxyResolver, NULL));
- GURL test_url("http://www.webkit.org");
- TestCompletionCallback callback;
- int rv = service->ResolveProxy(test_url, &info, &callback, NULL, NULL);
- EXPECT_EQ(OK, rv);
- EXPECT_TRUE(info.is_direct());
- }
+ // Request for a .org domain should bypass proxy.
+ rv = service->ResolveProxy(url1, &info[0], &callback[0], NULL, BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(info[0].is_direct());
- config.proxy_bypass.clear();
- config.proxy_bypass.push_back("*.org");
- config.proxy_bypass.push_back("7*");
- config.proxy_bypass_local_names = true;
- {
- scoped_refptr<ProxyService> service(new ProxyService(
- new MockProxyConfigService(config), new MockAsyncProxyResolver, NULL));
- GURL test_url("http://74.125.19.147");
- TestCompletionCallback callback;
- int rv = service->ResolveProxy(test_url, &info, &callback, NULL, NULL);
- EXPECT_EQ(OK, rv);
- EXPECT_TRUE(info.is_direct());
- }
-
- config.proxy_bypass.clear();
- config.proxy_bypass.push_back("*.org");
- config.proxy_bypass_local_names = true;
- {
- scoped_refptr<ProxyService> service(new ProxyService(
- new MockProxyConfigService(config), new MockAsyncProxyResolver, NULL));
- GURL test_url("http://www.msn.com");
- TestCompletionCallback callback;
- int rv = service->ResolveProxy(test_url, &info, &callback, NULL, NULL);
- EXPECT_EQ(OK, rv);
- EXPECT_FALSE(info.is_direct());
- }
-
- config.proxy_bypass.clear();
- config.proxy_bypass.push_back("*.MSN.COM");
- config.proxy_bypass_local_names = true;
- {
- scoped_refptr<ProxyService> service(new ProxyService(
- new MockProxyConfigService(config), new MockAsyncProxyResolver, NULL));
- GURL test_url("http://www.msnbc.msn.com");
- TestCompletionCallback callback;
- int rv = service->ResolveProxy(test_url, &info, &callback, NULL, NULL);
- EXPECT_EQ(OK, rv);
- EXPECT_TRUE(info.is_direct());
- }
-
- config.proxy_bypass.clear();
- config.proxy_bypass.push_back("*.msn.com");
- config.proxy_bypass_local_names = true;
- {
- scoped_refptr<ProxyService> service(new ProxyService(
- new MockProxyConfigService(config), new MockAsyncProxyResolver, NULL));
- GURL test_url("HTTP://WWW.MSNBC.MSN.COM");
- TestCompletionCallback callback;
- int rv = service->ResolveProxy(test_url, &info, &callback, NULL, NULL);
- EXPECT_EQ(OK, rv);
- EXPECT_TRUE(info.is_direct());
- }
+ // Request for a .com domain hits the proxy.
+ rv = service->ResolveProxy(url2, &info[1], &callback[1], NULL, BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("foopy1:8080", info[1].proxy_server().ToURI());
}
-TEST(ProxyServiceTest, ProxyBypassListWithPorts) {
- // Test port specification in bypass list entries.
- ProxyInfo info;
- ProxyConfig config;
- config.proxy_rules.ParseFromString("foopy1:8080;foopy2:9090");
- config.auto_detect = false;
- config.proxy_bypass_local_names = false;
-
- config.proxy_bypass.clear();
- config.proxy_bypass.push_back("*.example.com:99");
- {
- scoped_refptr<ProxyService> service(new ProxyService(
- new MockProxyConfigService(config), new MockAsyncProxyResolver, NULL));
- {
- GURL test_url("http://www.example.com:99");
- TestCompletionCallback callback;
- int rv = service->ResolveProxy(test_url, &info, &callback, NULL, NULL);
- EXPECT_EQ(OK, rv);
- EXPECT_TRUE(info.is_direct());
- }
- {
- GURL test_url("http://www.example.com:100");
- TestCompletionCallback callback;
- int rv = service->ResolveProxy(test_url, &info, &callback, NULL, NULL);
- EXPECT_EQ(OK, rv);
- EXPECT_FALSE(info.is_direct());
- }
- {
- GURL test_url("http://www.example.com");
- TestCompletionCallback callback;
- int rv = service->ResolveProxy(test_url, &info, &callback, NULL, NULL);
- EXPECT_EQ(OK, rv);
- EXPECT_FALSE(info.is_direct());
- }
- }
-
- config.proxy_bypass.clear();
- config.proxy_bypass.push_back("*.example.com:80");
- {
- scoped_refptr<ProxyService> service(new ProxyService(
- new MockProxyConfigService(config), new MockAsyncProxyResolver, NULL));
- GURL test_url("http://www.example.com");
- TestCompletionCallback callback;
- int rv = service->ResolveProxy(test_url, &info, &callback, NULL, NULL);
- EXPECT_EQ(OK, rv);
- EXPECT_TRUE(info.is_direct());
- }
-
- config.proxy_bypass.clear();
- config.proxy_bypass.push_back("*.example.com");
- {
- scoped_refptr<ProxyService> service(new ProxyService(
- new MockProxyConfigService(config), new MockAsyncProxyResolver, NULL));
- GURL test_url("http://www.example.com:99");
- TestCompletionCallback callback;
- int rv = service->ResolveProxy(test_url, &info, &callback, NULL, NULL);
- EXPECT_EQ(OK, rv);
- EXPECT_TRUE(info.is_direct());
- }
-
- // IPv6 with port.
- config.proxy_bypass.clear();
- config.proxy_bypass.push_back("[3ffe:2a00:100:7031::1]:99");
- {
- scoped_refptr<ProxyService> service(new ProxyService(
- new MockProxyConfigService(config), new MockAsyncProxyResolver, NULL));
- {
- GURL test_url("http://[3ffe:2a00:100:7031::1]:99/");
- TestCompletionCallback callback;
- int rv = service->ResolveProxy(test_url, &info, &callback, NULL, NULL);
- EXPECT_EQ(OK, rv);
- EXPECT_TRUE(info.is_direct());
- }
- {
- GURL test_url("http://[3ffe:2a00:100:7031::1]/");
- TestCompletionCallback callback;
- int rv = service->ResolveProxy(test_url, &info, &callback, NULL, NULL);
- EXPECT_EQ(OK, rv);
- EXPECT_FALSE(info.is_direct());
- }
- }
-
- // IPv6 without port. The bypass entry ought to work without the
- // brackets, but the bypass matching logic in ProxyService is
- // currently limited.
- config.proxy_bypass.clear();
- config.proxy_bypass.push_back("[3ffe:2a00:100:7031::1]");
- {
- scoped_refptr<ProxyService> service(new ProxyService(
- new MockProxyConfigService(config), new MockAsyncProxyResolver, NULL));
- {
- GURL test_url("http://[3ffe:2a00:100:7031::1]:99/");
- TestCompletionCallback callback;
- int rv = service->ResolveProxy(test_url, &info, &callback, NULL, NULL);
- EXPECT_EQ(OK, rv);
- EXPECT_TRUE(info.is_direct());
- }
- {
- GURL test_url("http://[3ffe:2a00:100:7031::1]/");
- TestCompletionCallback callback;
- int rv = service->ResolveProxy(test_url, &info, &callback, NULL, NULL);
- EXPECT_EQ(OK, rv);
- EXPECT_TRUE(info.is_direct());
- }
- }
-}
TEST(ProxyServiceTest, PerProtocolProxyTests) {
ProxyConfig config;
- config.proxy_rules.ParseFromString("http=foopy1:8080;https=foopy2:8080");
- config.auto_detect = false;
+ config.proxy_rules().ParseFromString("http=foopy1:8080;https=foopy2:8080");
+ config.set_auto_detect(false);
{
scoped_refptr<ProxyService> service(new ProxyService(
new MockProxyConfigService(config), new MockAsyncProxyResolver, NULL));
GURL test_url("http://www.msn.com");
ProxyInfo info;
TestCompletionCallback callback;
- int rv = service->ResolveProxy(test_url, &info, &callback, NULL, NULL);
+ int rv = service->ResolveProxy(test_url, &info, &callback, NULL,
+ BoundNetLog());
EXPECT_EQ(OK, rv);
EXPECT_FALSE(info.is_direct());
EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI());
@@ -898,7 +743,8 @@
GURL test_url("ftp://ftp.google.com");
ProxyInfo info;
TestCompletionCallback callback;
- int rv = service->ResolveProxy(test_url, &info, &callback, NULL, NULL);
+ int rv = service->ResolveProxy(test_url, &info, &callback, NULL,
+ BoundNetLog());
EXPECT_EQ(OK, rv);
EXPECT_TRUE(info.is_direct());
EXPECT_EQ("direct://", info.proxy_server().ToURI());
@@ -909,19 +755,21 @@
GURL test_url("https://webbranch.techcu.com");
ProxyInfo info;
TestCompletionCallback callback;
- int rv = service->ResolveProxy(test_url, &info, &callback, NULL, NULL);
+ int rv = service->ResolveProxy(test_url, &info, &callback, NULL,
+ BoundNetLog());
EXPECT_EQ(OK, rv);
EXPECT_FALSE(info.is_direct());
EXPECT_EQ("foopy2:8080", info.proxy_server().ToURI());
}
{
- config.proxy_rules.ParseFromString("foopy1:8080");
+ config.proxy_rules().ParseFromString("foopy1:8080");
scoped_refptr<ProxyService> service(new ProxyService(
new MockProxyConfigService(config), new MockAsyncProxyResolver, NULL));
GURL test_url("http://www.microsoft.com");
ProxyInfo info;
TestCompletionCallback callback;
- int rv = service->ResolveProxy(test_url, &info, &callback, NULL, NULL);
+ int rv = service->ResolveProxy(test_url, &info, &callback, NULL,
+ BoundNetLog());
EXPECT_EQ(OK, rv);
EXPECT_FALSE(info.is_direct());
EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI());
@@ -932,10 +780,10 @@
// fall back to the SOCKS proxy.
TEST(ProxyServiceTest, DefaultProxyFallbackToSOCKS) {
ProxyConfig config;
- config.proxy_rules.ParseFromString("http=foopy1:8080;socks=foopy2:1080");
- config.auto_detect = false;
+ config.proxy_rules().ParseFromString("http=foopy1:8080;socks=foopy2:1080");
+ config.set_auto_detect(false);
EXPECT_EQ(ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME,
- config.proxy_rules.type);
+ config.proxy_rules().type);
{
scoped_refptr<ProxyService> service(new ProxyService(
@@ -943,7 +791,8 @@
GURL test_url("http://www.msn.com");
ProxyInfo info;
TestCompletionCallback callback;
- int rv = service->ResolveProxy(test_url, &info, &callback, NULL, NULL);
+ int rv = service->ResolveProxy(test_url, &info, &callback, NULL,
+ BoundNetLog());
EXPECT_EQ(OK, rv);
EXPECT_FALSE(info.is_direct());
EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI());
@@ -954,7 +803,8 @@
GURL test_url("ftp://ftp.google.com");
ProxyInfo info;
TestCompletionCallback callback;
- int rv = service->ResolveProxy(test_url, &info, &callback, NULL, NULL);
+ int rv = service->ResolveProxy(test_url, &info, &callback, NULL,
+ BoundNetLog());
EXPECT_EQ(OK, rv);
EXPECT_FALSE(info.is_direct());
EXPECT_EQ("socks4://foopy2:1080", info.proxy_server().ToURI());
@@ -965,7 +815,8 @@
GURL test_url("https://webbranch.techcu.com");
ProxyInfo info;
TestCompletionCallback callback;
- int rv = service->ResolveProxy(test_url, &info, &callback, NULL, NULL);
+ int rv = service->ResolveProxy(test_url, &info, &callback, NULL,
+ BoundNetLog());
EXPECT_EQ(OK, rv);
EXPECT_FALSE(info.is_direct());
EXPECT_EQ("socks4://foopy2:1080", info.proxy_server().ToURI());
@@ -976,7 +827,8 @@
GURL test_url("unknown://www.microsoft.com");
ProxyInfo info;
TestCompletionCallback callback;
- int rv = service->ResolveProxy(test_url, &info, &callback, NULL, NULL);
+ int rv = service->ResolveProxy(test_url, &info, &callback, NULL,
+ BoundNetLog());
EXPECT_EQ(OK, rv);
EXPECT_FALSE(info.is_direct());
EXPECT_EQ("socks4://foopy2:1080", info.proxy_server().ToURI());
@@ -998,7 +850,7 @@
ProxyInfo info1;
TestCompletionCallback callback1;
int rv = service->ResolveProxy(
- GURL("http://request1"), &info1, &callback1, NULL, NULL);
+ GURL("http://request1"), &info1, &callback1, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
// Nothing has been sent to the proxy resolver yet, since the proxy
@@ -1007,7 +859,7 @@
// Successfully initialize the PAC script.
EXPECT_EQ(GURL("http://foopy/proxy.pac"),
- resolver->pending_set_pac_script_request()->pac_url());
+ resolver->pending_set_pac_script_request()->script_data()->url());
resolver->pending_set_pac_script_request()->CompleteNow(OK);
ASSERT_EQ(1u, resolver->pending_requests().size());
@@ -1017,7 +869,7 @@
TestCompletionCallback callback2;
ProxyService::PacRequest* request2;
rv = service->ResolveProxy(
- GURL("http://request2"), &info2, &callback2, &request2, NULL);
+ GURL("http://request2"), &info2, &callback2, &request2, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
ASSERT_EQ(2u, resolver->pending_requests().size());
EXPECT_EQ(GURL("http://request2"), resolver->pending_requests()[1]->url());
@@ -1025,7 +877,7 @@
ProxyInfo info3;
TestCompletionCallback callback3;
rv = service->ResolveProxy(
- GURL("http://request3"), &info3, &callback3, NULL, NULL);
+ GURL("http://request3"), &info3, &callback3, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
ASSERT_EQ(3u, resolver->pending_requests().size());
EXPECT_EQ(GURL("http://request3"), resolver->pending_requests()[2]->url());
@@ -1076,7 +928,7 @@
ProxyInfo info1;
TestCompletionCallback callback1;
int rv = service->ResolveProxy(
- GURL("http://request1"), &info1, &callback1, NULL, NULL);
+ GURL("http://request1"), &info1, &callback1, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
// The first request should have triggered download of PAC script.
@@ -1086,13 +938,13 @@
ProxyInfo info2;
TestCompletionCallback callback2;
rv = service->ResolveProxy(
- GURL("http://request2"), &info2, &callback2, NULL, NULL);
+ GURL("http://request2"), &info2, &callback2, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
ProxyInfo info3;
TestCompletionCallback callback3;
rv = service->ResolveProxy(
- GURL("http://request3"), &info3, &callback3, NULL, NULL);
+ GURL("http://request3"), &info3, &callback3, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
// Nothing has been sent to the resolver yet.
@@ -1105,7 +957,8 @@
// Now that the PAC script is downloaded, it will have been sent to the proxy
// resolver.
- EXPECT_EQ("pac-v1", resolver->pending_set_pac_script_request()->pac_bytes());
+ EXPECT_EQ(ASCIIToUTF16("pac-v1"),
+ resolver->pending_set_pac_script_request()->script_data()->utf16());
resolver->pending_set_pac_script_request()->CompleteNow(OK);
ASSERT_EQ(3u, resolver->pending_requests().size());
@@ -1155,7 +1008,7 @@
ProxyInfo info1;
TestCompletionCallback callback1;
int rv = service->ResolveProxy(
- GURL("http://request1"), &info1, &callback1, NULL, NULL);
+ GURL("http://request1"), &info1, &callback1, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
// The first request should have triggered download of PAC script.
@@ -1165,7 +1018,7 @@
ProxyInfo info2;
TestCompletionCallback callback2;
rv = service->ResolveProxy(
- GURL("http://request2"), &info2, &callback2, NULL, NULL);
+ GURL("http://request2"), &info2, &callback2, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
// At this point the ProxyService should be waiting for the
@@ -1185,7 +1038,8 @@
// Now that the PAC script is downloaded, it will have been sent to the proxy
// resolver.
- EXPECT_EQ("pac-v1", resolver->pending_set_pac_script_request()->pac_bytes());
+ EXPECT_EQ(ASCIIToUTF16("pac-v1"),
+ resolver->pending_set_pac_script_request()->script_data()->utf16());
resolver->pending_set_pac_script_request()->CompleteNow(OK);
ASSERT_EQ(2u, resolver->pending_requests().size());
@@ -1211,9 +1065,9 @@
ProxyInfo info1;
TestCompletionCallback callback1;
ProxyService::PacRequest* request1;
- scoped_refptr<LoadLog> log1(new LoadLog(LoadLog::kUnbounded));
+ CapturingBoundNetLog log1(CapturingNetLog::kUnbounded);
int rv = service->ResolveProxy(
- GURL("http://request1"), &info1, &callback1, &request1, log1);
+ GURL("http://request1"), &info1, &callback1, &request1, log1.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
// The first request should have triggered download of PAC script.
@@ -1224,13 +1078,13 @@
TestCompletionCallback callback2;
ProxyService::PacRequest* request2;
rv = service->ResolveProxy(
- GURL("http://request2"), &info2, &callback2, &request2, NULL);
+ GURL("http://request2"), &info2, &callback2, &request2, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
ProxyInfo info3;
TestCompletionCallback callback3;
rv = service->ResolveProxy(
- GURL("http://request3"), &info3, &callback3, NULL, NULL);
+ GURL("http://request3"), &info3, &callback3, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
// Nothing has been sent to the resolver yet.
@@ -1247,7 +1101,8 @@
// Now that the PAC script is downloaded, it will have been sent to the
// proxy resolver.
- EXPECT_EQ("pac-v1", resolver->pending_set_pac_script_request()->pac_bytes());
+ EXPECT_EQ(ASCIIToUTF16("pac-v1"),
+ resolver->pending_set_pac_script_request()->script_data()->utf16());
resolver->pending_set_pac_script_request()->CompleteNow(OK);
ASSERT_EQ(1u, resolver->pending_requests().size());
@@ -1265,24 +1120,26 @@
EXPECT_FALSE(callback1.have_result()); // Cancelled.
EXPECT_FALSE(callback2.have_result()); // Cancelled.
- // Check the LoadLog for request 1 (which was cancelled) got filled properly.
- EXPECT_EQ(6u, log1->entries().size());
- EXPECT_TRUE(LogContainsBeginEvent(*log1, 0, LoadLog::TYPE_PROXY_SERVICE));
+ // Check the NetLog for request 1 (which was cancelled) got filled properly.
+ EXPECT_EQ(6u, log1.entries().size());
EXPECT_TRUE(LogContainsBeginEvent(
- *log1, 3, LoadLog::TYPE_PROXY_SERVICE_WAITING_FOR_INIT_PAC));
+ log1.entries(), 0, NetLog::TYPE_PROXY_SERVICE));
+ EXPECT_TRUE(LogContainsBeginEvent(
+ log1.entries(), 3, NetLog::TYPE_PROXY_SERVICE_WAITING_FOR_INIT_PAC));
// Note that TYPE_PROXY_SERVICE_WAITING_FOR_INIT_PAC is never completed before
// the cancellation occured.
EXPECT_TRUE(LogContainsEvent(
- *log1, 4, LoadLog::TYPE_CANCELLED, LoadLog::PHASE_NONE));
- EXPECT_TRUE(LogContainsEndEvent(*log1, 5, LoadLog::TYPE_PROXY_SERVICE));
+ log1.entries(), 4, NetLog::TYPE_CANCELLED, NetLog::PHASE_NONE));
+ EXPECT_TRUE(LogContainsEndEvent(
+ log1.entries(), 5, NetLog::TYPE_PROXY_SERVICE));
}
// Test that if auto-detect fails, we fall-back to the custom pac.
TEST(ProxyServiceTest, FallbackFromAutodetectToCustomPac) {
ProxyConfig config;
- config.auto_detect = true;
- config.pac_url = GURL("http://foopy/proxy.pac");
- config.proxy_rules.ParseFromString("http=foopy:80"); // Won't be used.
+ config.set_auto_detect(true);
+ config.set_pac_url(GURL("http://foopy/proxy.pac"));
+ config.proxy_rules().ParseFromString("http=foopy:80"); // Won't be used.
MockProxyConfigService* config_service = new MockProxyConfigService(config);
MockAsyncProxyResolverExpectsBytes* resolver =
@@ -1298,14 +1155,14 @@
ProxyInfo info1;
TestCompletionCallback callback1;
int rv = service->ResolveProxy(
- GURL("http://request1"), &info1, &callback1, NULL, NULL);
+ GURL("http://request1"), &info1, &callback1, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
ProxyInfo info2;
TestCompletionCallback callback2;
ProxyService::PacRequest* request2;
rv = service->ResolveProxy(
- GURL("http://request2"), &info2, &callback2, &request2, NULL);
+ GURL("http://request2"), &info2, &callback2, &request2, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
// Check that nothing has been sent to the proxy resolver yet.
@@ -1322,8 +1179,8 @@
EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url());
fetcher->NotifyFetchCompletion(OK, "custom-pac-script");
- EXPECT_EQ("custom-pac-script",
- resolver->pending_set_pac_script_request()->pac_bytes());
+ EXPECT_EQ(ASCIIToUTF16("custom-pac-script"),
+ resolver->pending_set_pac_script_request()->script_data()->utf16());
resolver->pending_set_pac_script_request()->CompleteNow(OK);
// Now finally, the pending requests should have been sent to the resolver
@@ -1351,9 +1208,9 @@
// the auto-detect script fails parsing rather than downloading.
TEST(ProxyServiceTest, FallbackFromAutodetectToCustomPac2) {
ProxyConfig config;
- config.auto_detect = true;
- config.pac_url = GURL("http://foopy/proxy.pac");
- config.proxy_rules.ParseFromString("http=foopy:80"); // Won't be used.
+ config.set_auto_detect(true);
+ config.set_pac_url(GURL("http://foopy/proxy.pac"));
+ config.proxy_rules().ParseFromString("http=foopy:80"); // Won't be used.
MockProxyConfigService* config_service = new MockProxyConfigService(config);
MockAsyncProxyResolverExpectsBytes* resolver =
@@ -1369,14 +1226,14 @@
ProxyInfo info1;
TestCompletionCallback callback1;
int rv = service->ResolveProxy(
- GURL("http://request1"), &info1, &callback1, NULL, NULL);
+ GURL("http://request1"), &info1, &callback1, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
ProxyInfo info2;
TestCompletionCallback callback2;
ProxyService::PacRequest* request2;
rv = service->ResolveProxy(
- GURL("http://request2"), &info2, &callback2, &request2, NULL);
+ GURL("http://request2"), &info2, &callback2, &request2, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
// Check that nothing has been sent to the proxy resolver yet.
@@ -1388,8 +1245,8 @@
fetcher->NotifyFetchCompletion(OK, "invalid-script-contents");
// Simulate a parse error.
- EXPECT_EQ("invalid-script-contents",
- resolver->pending_set_pac_script_request()->pac_bytes());
+ EXPECT_EQ(ASCIIToUTF16("invalid-script-contents"),
+ resolver->pending_set_pac_script_request()->script_data()->utf16());
resolver->pending_set_pac_script_request()->CompleteNow(
ERR_PAC_SCRIPT_FAILED);
@@ -1398,8 +1255,8 @@
EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url());
fetcher->NotifyFetchCompletion(OK, "custom-pac-script");
- EXPECT_EQ("custom-pac-script",
- resolver->pending_set_pac_script_request()->pac_bytes());
+ EXPECT_EQ(ASCIIToUTF16("custom-pac-script"),
+ resolver->pending_set_pac_script_request()->script_data()->utf16());
resolver->pending_set_pac_script_request()->CompleteNow(OK);
// Now finally, the pending requests should have been sent to the resolver
@@ -1427,9 +1284,9 @@
// are given, then we will try them in that order.
TEST(ProxyServiceTest, FallbackFromAutodetectToCustomToManual) {
ProxyConfig config;
- config.auto_detect = true;
- config.pac_url = GURL("http://foopy/proxy.pac");
- config.proxy_rules.ParseFromString("http=foopy:80");
+ config.set_auto_detect(true);
+ config.set_pac_url(GURL("http://foopy/proxy.pac"));
+ config.proxy_rules().ParseFromString("http=foopy:80");
MockProxyConfigService* config_service = new MockProxyConfigService(config);
MockAsyncProxyResolverExpectsBytes* resolver =
@@ -1445,14 +1302,14 @@
ProxyInfo info1;
TestCompletionCallback callback1;
int rv = service->ResolveProxy(
- GURL("http://request1"), &info1, &callback1, NULL, NULL);
+ GURL("http://request1"), &info1, &callback1, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
ProxyInfo info2;
TestCompletionCallback callback2;
ProxyService::PacRequest* request2;
rv = service->ResolveProxy(
- GURL("http://request2"), &info2, &callback2, &request2, NULL);
+ GURL("http://request2"), &info2, &callback2, &request2, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
// Check that nothing has been sent to the proxy resolver yet.
@@ -1484,10 +1341,10 @@
// Test that the bypass rules are NOT applied when using autodetect.
TEST(ProxyServiceTest, BypassDoesntApplyToPac) {
ProxyConfig config;
- config.auto_detect = true;
- config.pac_url = GURL("http://foopy/proxy.pac");
- config.proxy_rules.ParseFromString("http=foopy:80"); // Not used.
- config.proxy_bypass.push_back("www.google.com");
+ config.set_auto_detect(true);
+ config.set_pac_url(GURL("http://foopy/proxy.pac"));
+ config.proxy_rules().ParseFromString("http=foopy:80"); // Not used.
+ config.proxy_rules().bypass_rules.ParseFromString("www.google.com");
MockProxyConfigService* config_service = new MockProxyConfigService(config);
MockAsyncProxyResolverExpectsBytes* resolver =
@@ -1503,7 +1360,7 @@
ProxyInfo info1;
TestCompletionCallback callback1;
int rv = service->ResolveProxy(
- GURL("http://www.google.com"), &info1, &callback1, NULL, NULL);
+ GURL("http://www.google.com"), &info1, &callback1, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
// Check that nothing has been sent to the proxy resolver yet.
@@ -1514,8 +1371,8 @@
EXPECT_EQ(GURL("http://wpad/wpad.dat"), fetcher->pending_request_url());
fetcher->NotifyFetchCompletion(OK, "auto-detect");
- EXPECT_EQ("auto-detect",
- resolver->pending_set_pac_script_request()->pac_bytes());
+ EXPECT_EQ(ASCIIToUTF16("auto-detect"),
+ resolver->pending_set_pac_script_request()->script_data()->utf16());
resolver->pending_set_pac_script_request()->CompleteNow(OK);
ASSERT_EQ(1u, resolver->pending_requests().size());
@@ -1534,7 +1391,7 @@
ProxyInfo info2;
TestCompletionCallback callback2;
rv = service->ResolveProxy(
- GURL("http://www.google.com"), &info2, &callback2, NULL, NULL);
+ GURL("http://www.google.com"), &info2, &callback2, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
ASSERT_EQ(1u, resolver->pending_requests().size());
@@ -1555,7 +1412,7 @@
// being deleted prior to the InitProxyResolver).
TEST(ProxyServiceTest, DeleteWhileInitProxyResolverHasOutstandingFetch) {
ProxyConfig config;
- config.pac_url = GURL("http://foopy/proxy.pac");
+ config.set_pac_url(GURL("http://foopy/proxy.pac"));
MockProxyConfigService* config_service = new MockProxyConfigService(config);
MockAsyncProxyResolverExpectsBytes* resolver =
@@ -1571,7 +1428,7 @@
ProxyInfo info1;
TestCompletionCallback callback1;
int rv = service->ResolveProxy(
- GURL("http://www.google.com"), &info1, &callback1, NULL, NULL);
+ GURL("http://www.google.com"), &info1, &callback1, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
// Check that nothing has been sent to the proxy resolver yet.
@@ -1603,11 +1460,11 @@
ProxyInfo info;
TestCompletionCallback callback;
- int rv = service->ResolveProxy(url, &info, &callback, NULL, NULL);
+ int rv = service->ResolveProxy(url, &info, &callback, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(GURL("http://foopy/proxy.pac"),
- resolver->pending_set_pac_script_request()->pac_url());
+ resolver->pending_set_pac_script_request()->script_data()->url());
// Delete the ProxyService.
service = NULL;
@@ -1615,72 +1472,30 @@
TEST(ProxyServiceTest, ResetProxyConfigService) {
ProxyConfig config1;
- config1.proxy_rules.ParseFromString("foopy1:8080");
- config1.auto_detect = false;
+ config1.proxy_rules().ParseFromString("foopy1:8080");
+ config1.set_auto_detect(false);
scoped_refptr<ProxyService> service(new ProxyService(
new MockProxyConfigService(config1),
- new MockAsyncProxyResolverExpectsBytes,
- NULL));
+ new MockAsyncProxyResolverExpectsBytes, NULL));
ProxyInfo info;
TestCompletionCallback callback1;
int rv = service->ResolveProxy(
- GURL("http://request1"), &info, &callback1, NULL, NULL);
+ GURL("http://request1"), &info, &callback1, NULL, BoundNetLog());
EXPECT_EQ(OK, rv);
EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI());
ProxyConfig config2;
- config2.proxy_rules.ParseFromString("foopy2:8080");
- config2.auto_detect = false;
+ config2.proxy_rules().ParseFromString("foopy2:8080");
+ config2.set_auto_detect(false);
service->ResetConfigService(new MockProxyConfigService(config2));
TestCompletionCallback callback2;
rv = service->ResolveProxy(
- GURL("http://request2"), &info, &callback2, NULL, NULL);
+ GURL("http://request2"), &info, &callback2, NULL, BoundNetLog());
EXPECT_EQ(OK, rv);
EXPECT_EQ("foopy2:8080", info.proxy_server().ToURI());
}
-TEST(ProxyServiceTest, IsLocalName) {
- const struct {
- const char* url;
- bool expected_is_local;
- } tests[] = {
- // Single-component hostnames are considered local.
- {"http://localhost/x", true},
- {"http://www", true},
-
- // IPv4 loopback interface.
- {"http://127.0.0.1/x", true},
- {"http://127.0.0.1:80/x", true},
-
- // IPv6 loopback interface.
- {"http://[::1]:80/x", true},
- {"http://[0:0::1]:6233/x", true},
- {"http://[0:0:0:0:0:0:0:1]/x", true},
-
- // Non-local URLs.
- {"http://foo.com/", false},
- {"http://localhost.i/", false},
- {"http://www.google.com/", false},
- {"http://192.168.0.1/", false},
-
- // Try with different protocols.
- {"ftp://127.0.0.1/x", true},
- {"ftp://foobar.com/x", false},
-
- // This is a bit of a gray-area, but GURL does not strip trailing dots
- // in host-names, so the following are considered non-local.
- {"http://www./x", false},
- {"http://localhost./x", false},
- };
-
- for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
- SCOPED_TRACE(StringPrintf("Test[%" PRIuS "]: %s", i, tests[i].url));
- bool is_local = ProxyService::IsLocalName(GURL(tests[i].url));
- EXPECT_EQ(tests[i].expected_is_local, is_local);
- }
-}
-
// Check that after we have done the auto-detect test, and the configuration
// is updated (with no change), we don't re-try the autodetect test.
// Regression test for http://crbug.com/18526 -- the configuration was being
@@ -1688,7 +1503,7 @@
// thought it had received a new configuration.
TEST(ProxyServiceTest, UpdateConfigAfterFailedAutodetect) {
ProxyConfig config;
- config.auto_detect = true;
+ config.set_auto_detect(true);
MockProxyConfigService* config_service = new MockProxyConfigService(config);
MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver;
@@ -1700,14 +1515,15 @@
ProxyInfo info1;
TestCompletionCallback callback1;
int rv = service->ResolveProxy(
- GURL("http://www.google.com"), &info1, &callback1, NULL, NULL);
+ GURL("http://www.google.com"), &info1, &callback1, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
// Check that nothing has been sent to the proxy resolver yet.
ASSERT_EQ(0u, resolver->pending_requests().size());
// Fail the setting of autodetect script.
- EXPECT_EQ(GURL(), resolver->pending_set_pac_script_request()->pac_url());
+ EXPECT_EQ(ProxyResolverScriptData::TYPE_AUTO_DETECT,
+ resolver->pending_set_pac_script_request()->script_data()->type());
resolver->pending_set_pac_script_request()->CompleteNow(ERR_FAILED);
// Verify that request ran as expected -- should have fallen back to direct.
@@ -1716,7 +1532,7 @@
// Force the ProxyService to pull down a new proxy configuration.
// (Even though the configuration isn't old/bad).
- service->UpdateConfig(NULL);
+ service->UpdateConfig(BoundNetLog());
// Start another request -- the effective configuration has not
// changed, so we shouldn't re-run the autodetect step.
@@ -1724,7 +1540,7 @@
ProxyInfo info2;
TestCompletionCallback callback2;
rv = service->ResolveProxy(
- GURL("http://www.google.com"), &info2, &callback2, NULL, NULL);
+ GURL("http://www.google.com"), &info2, &callback2, NULL, BoundNetLog());
EXPECT_EQ(OK, rv);
EXPECT_TRUE(info2.is_direct());
@@ -1734,7 +1550,7 @@
// that does NOT, we unset the variable |should_use_proxy_resolver_|.
TEST(ProxyServiceTest, UpdateConfigFromPACToDirect) {
ProxyConfig config;
- config.auto_detect = true;
+ config.set_auto_detect(true);
MockProxyConfigService* config_service = new MockProxyConfigService(config);
MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver;
@@ -1746,14 +1562,15 @@
ProxyInfo info1;
TestCompletionCallback callback1;
int rv = service->ResolveProxy(
- GURL("http://www.google.com"), &info1, &callback1, NULL, NULL);
+ GURL("http://www.google.com"), &info1, &callback1, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
// Check that nothing has been sent to the proxy resolver yet.
ASSERT_EQ(0u, resolver->pending_requests().size());
// Successfully set the autodetect script.
- EXPECT_EQ(GURL(), resolver->pending_set_pac_script_request()->pac_url());
+ EXPECT_EQ(ProxyResolverScriptData::TYPE_AUTO_DETECT,
+ resolver->pending_set_pac_script_request()->script_data()->type());
resolver->pending_set_pac_script_request()->CompleteNow(OK);
// Complete the pending request.
@@ -1770,15 +1587,15 @@
//
// This new configuration no longer has auto_detect set, so
// requests should complete synchronously now as direct-connect.
- config.auto_detect = false;
+ config.set_auto_detect(false);
config_service->config = config;
- service->UpdateConfig(NULL);
+ service->UpdateConfig(BoundNetLog());
// Start another request -- the effective configuration has changed.
ProxyInfo info2;
TestCompletionCallback callback2;
rv = service->ResolveProxy(
- GURL("http://www.google.com"), &info2, &callback2, NULL, NULL);
+ GURL("http://www.google.com"), &info2, &callback2, NULL, BoundNetLog());
EXPECT_EQ(OK, rv);
EXPECT_TRUE(info2.is_direct());
@@ -1791,10 +1608,8 @@
MockAsyncProxyResolverExpectsBytes* resolver =
new MockAsyncProxyResolverExpectsBytes;
- MockNetworkChangeNotifier network_change_notifier;
-
scoped_refptr<ProxyService> service(
- new ProxyService(config_service, resolver, &network_change_notifier));
+ new ProxyService(config_service, resolver, NULL));
MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher;
service->SetProxyScriptFetcher(fetcher);
@@ -1804,7 +1619,7 @@
ProxyInfo info1;
TestCompletionCallback callback1;
int rv = service->ResolveProxy(
- GURL("http://request1"), &info1, &callback1, NULL, NULL);
+ GURL("http://request1"), &info1, &callback1, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
// The first request should have triggered initial download of PAC script.
@@ -1821,7 +1636,8 @@
// Now that the PAC script is downloaded, the request will have been sent to
// the proxy resolver.
- EXPECT_EQ("pac-v1", resolver->pending_set_pac_script_request()->pac_bytes());
+ EXPECT_EQ(ASCIIToUTF16("pac-v1"),
+ resolver->pending_set_pac_script_request()->script_data()->utf16());
resolver->pending_set_pac_script_request()->CompleteNow(OK);
ASSERT_EQ(1u, resolver->pending_requests().size());
@@ -1838,14 +1654,14 @@
// Now simluate a change in the network. The ProxyConfigService is still
// going to return the same PAC URL as before, but this URL needs to be
// refetched on the new network.
-
- network_change_notifier.NotifyIPAddressChange();
+ NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
+ MessageLoop::current()->RunAllPending(); // Notification happens async.
// Start a second request.
ProxyInfo info2;
TestCompletionCallback callback2;
rv = service->ResolveProxy(
- GURL("http://request2"), &info2, &callback2, NULL, NULL);
+ GURL("http://request2"), &info2, &callback2, NULL, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
// This second request should have triggered the re-download of the PAC
@@ -1862,7 +1678,8 @@
// Now that the PAC script is downloaded, the second request will have been
// sent to the proxy resolver.
- EXPECT_EQ("pac-v2", resolver->pending_set_pac_script_request()->pac_bytes());
+ EXPECT_EQ(ASCIIToUTF16("pac-v2"),
+ resolver->pending_set_pac_script_request()->script_data()->utf16());
resolver->pending_set_pac_script_request()->CompleteNow(OK);
ASSERT_EQ(1u, resolver->pending_requests().size());
diff --git a/net/proxy/sync_host_resolver_bridge.cc b/net/proxy/sync_host_resolver_bridge.cc
new file mode 100644
index 0000000..6c62c7d
--- /dev/null
+++ b/net/proxy/sync_host_resolver_bridge.cc
@@ -0,0 +1,191 @@
+// 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 "net/proxy/sync_host_resolver_bridge.h"
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/lock.h"
+#include "base/message_loop.h"
+#include "base/waitable_event.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+
+namespace net {
+
+// SyncHostResolverBridge::Core ----------------------------------------------
+
+class SyncHostResolverBridge::Core
+ : public base::RefCountedThreadSafe<SyncHostResolverBridge::Core> {
+ public:
+ Core(HostResolver* resolver, MessageLoop* host_resolver_loop);
+
+ int ResolveSynchronously(const HostResolver::RequestInfo& info,
+ AddressList* addresses);
+
+ // Returns true if Shutdown() has been called.
+ bool HasShutdown() const {
+ AutoLock l(lock_);
+ return HasShutdownLocked();
+ }
+
+ // Called on |host_resolver_loop_|.
+ void Shutdown();
+
+ private:
+ friend class base::RefCountedThreadSafe<SyncHostResolverBridge::Core>;
+
+ bool HasShutdownLocked() const {
+ return has_shutdown_;
+ }
+
+ // Called on |host_resolver_loop_|.
+ void StartResolve(const HostResolver::RequestInfo& info,
+ AddressList* addresses);
+
+ // Called on |host_resolver_loop_|.
+ void OnResolveCompletion(int result);
+
+ // Not called on |host_resolver_loop_|.
+ int WaitForResolveCompletion();
+
+ const scoped_refptr<HostResolver> host_resolver_;
+ MessageLoop* const host_resolver_loop_;
+ net::CompletionCallbackImpl<Core> callback_;
+ // The result from the current request (set on |host_resolver_loop_|).
+ int err_;
+ // The currently outstanding request to |host_resolver_|, or NULL.
+ HostResolver::RequestHandle outstanding_request_;
+
+ // Event to notify completion of resolve request. We always Signal() on
+ // |host_resolver_loop_| and Wait() on a different thread.
+ base::WaitableEvent event_;
+
+ // True if Shutdown() has been called. Must hold |lock_| to access it.
+ bool has_shutdown_;
+
+ // Mutex to guard accesses to |has_shutdown_|.
+ mutable Lock lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(Core);
+};
+
+SyncHostResolverBridge::Core::Core(HostResolver* host_resolver,
+ MessageLoop* host_resolver_loop)
+ : host_resolver_(host_resolver),
+ host_resolver_loop_(host_resolver_loop),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ callback_(this, &Core::OnResolveCompletion)),
+ err_(0),
+ outstanding_request_(NULL),
+ event_(true, false),
+ has_shutdown_(false) {}
+
+int SyncHostResolverBridge::Core::ResolveSynchronously(
+ const HostResolver::RequestInfo& info,
+ net::AddressList* addresses) {
+ // Otherwise start an async resolve on the resolver's thread.
+ host_resolver_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &Core::StartResolve,
+ info, addresses));
+
+ return WaitForResolveCompletion();
+}
+
+void SyncHostResolverBridge::Core::StartResolve(
+ const HostResolver::RequestInfo& info,
+ net::AddressList* addresses) {
+ DCHECK_EQ(MessageLoop::current(), host_resolver_loop_);
+ DCHECK(!outstanding_request_);
+
+ if (HasShutdown())
+ return;
+
+ int error = host_resolver_->Resolve(
+ info, addresses, &callback_, &outstanding_request_, BoundNetLog());
+ if (error != ERR_IO_PENDING)
+ OnResolveCompletion(error); // Completed synchronously.
+}
+
+void SyncHostResolverBridge::Core::OnResolveCompletion(int result) {
+ DCHECK_EQ(MessageLoop::current(), host_resolver_loop_);
+ err_ = result;
+ outstanding_request_ = NULL;
+ event_.Signal();
+}
+
+int SyncHostResolverBridge::Core::WaitForResolveCompletion() {
+ DCHECK_NE(MessageLoop::current(), host_resolver_loop_);
+ event_.Wait();
+
+ {
+ AutoLock l(lock_);
+ if (HasShutdownLocked())
+ return ERR_ABORTED;
+ event_.Reset();
+ }
+
+ return err_;
+}
+
+void SyncHostResolverBridge::Core::Shutdown() {
+ DCHECK_EQ(MessageLoop::current(), host_resolver_loop_);
+
+ if (outstanding_request_) {
+ host_resolver_->CancelRequest(outstanding_request_);
+ outstanding_request_ = NULL;
+ }
+
+ {
+ AutoLock l(lock_);
+ has_shutdown_ = true;
+ }
+
+ // Wake up the PAC thread in case it was waiting for resolve completion.
+ event_.Signal();
+}
+
+// SyncHostResolverBridge -----------------------------------------------------
+
+SyncHostResolverBridge::SyncHostResolverBridge(HostResolver* host_resolver,
+ MessageLoop* host_resolver_loop)
+ : host_resolver_loop_(host_resolver_loop),
+ core_(new Core(host_resolver, host_resolver_loop)) {
+ DCHECK(host_resolver_loop_);
+}
+
+SyncHostResolverBridge::~SyncHostResolverBridge() {
+ DCHECK(core_->HasShutdown());
+}
+
+int SyncHostResolverBridge::Resolve(const RequestInfo& info,
+ AddressList* addresses,
+ CompletionCallback* callback,
+ RequestHandle* out_req,
+ const BoundNetLog& net_log) {
+ DCHECK(!callback);
+ DCHECK(!out_req);
+
+ return core_->ResolveSynchronously(info, addresses);
+}
+
+void SyncHostResolverBridge::CancelRequest(RequestHandle req) {
+ NOTREACHED();
+}
+
+void SyncHostResolverBridge::AddObserver(Observer* observer) {
+ NOTREACHED();
+}
+
+void SyncHostResolverBridge::RemoveObserver(Observer* observer) {
+ NOTREACHED();
+}
+
+void SyncHostResolverBridge::Shutdown() {
+ DCHECK_EQ(MessageLoop::current(), host_resolver_loop_);
+ core_->Shutdown();
+}
+
+} // namespace net
diff --git a/net/proxy/sync_host_resolver_bridge.h b/net/proxy/sync_host_resolver_bridge.h
new file mode 100644
index 0000000..b02d496
--- /dev/null
+++ b/net/proxy/sync_host_resolver_bridge.h
@@ -0,0 +1,48 @@
+// 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.
+
+#ifndef NET_PROXY_SYNC_HOST_RESOLVER_BRIDGE_H_
+#define NET_PROXY_SYNC_HOST_RESOLVER_BRIDGE_H_
+
+#include "base/scoped_ptr.h"
+#include "net/base/host_resolver.h"
+
+class MessageLoop;
+
+namespace net {
+
+// Wrapper around HostResolver to give a sync API while running the resolver
+// in async mode on |host_resolver_loop|.
+class SyncHostResolverBridge : public HostResolver {
+ public:
+ SyncHostResolverBridge(HostResolver* host_resolver,
+ MessageLoop* host_resolver_loop);
+
+ virtual ~SyncHostResolverBridge();
+
+ // HostResolver methods:
+ virtual int Resolve(const RequestInfo& info,
+ AddressList* addresses,
+ CompletionCallback* callback,
+ RequestHandle* out_req,
+ const BoundNetLog& net_log);
+ virtual void CancelRequest(RequestHandle req);
+ virtual void AddObserver(Observer* observer);
+ virtual void RemoveObserver(Observer* observer);
+
+ // The Shutdown() method should be called prior to destruction, from
+ // |host_resolver_loop_|. It aborts any in progress synchronous resolves, to
+ // prevent deadlocks from happening.
+ virtual void Shutdown();
+
+ private:
+ class Core;
+
+ MessageLoop* const host_resolver_loop_;
+ scoped_refptr<Core> core_;
+};
+
+} // namespace net
+
+#endif // NET_PROXY_SYNC_HOST_RESOLVER_BRIDGE_H_
diff --git a/net/proxy/sync_host_resolver_bridge_unittest.cc b/net/proxy/sync_host_resolver_bridge_unittest.cc
new file mode 100644
index 0000000..95ba446
--- /dev/null
+++ b/net/proxy/sync_host_resolver_bridge_unittest.cc
@@ -0,0 +1,231 @@
+// 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 "net/proxy/sync_host_resolver_bridge.h"
+
+#include "base/thread.h"
+#include "base/waitable_event.h"
+#include "net/base/address_list.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/proxy/multi_threaded_proxy_resolver.h"
+#include "net/base/test_completion_callback.h"
+#include "net/proxy/proxy_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// TODO(eroman): This test should be moved into
+// multi_threaded_proxy_resolver_unittest.cc.
+
+namespace net {
+
+namespace {
+
+// This implementation of HostResolver allows blocking until a resolve request
+// has been received. The resolve requests it receives will never be completed.
+class BlockableHostResolver : public HostResolver {
+ public:
+ BlockableHostResolver()
+ : event_(true, false),
+ was_request_cancelled_(false) {
+ }
+
+ virtual int Resolve(const RequestInfo& info,
+ AddressList* addresses,
+ CompletionCallback* callback,
+ RequestHandle* out_req,
+ const BoundNetLog& net_log) {
+ EXPECT_TRUE(callback);
+ EXPECT_TRUE(out_req);
+ *out_req = reinterpret_cast<RequestHandle*>(1); // Magic value.
+
+ // Indicate to the caller that a request was received.
+ event_.Signal();
+
+ // We return ERR_IO_PENDING, as this request will NEVER be completed.
+ // Expectation is for the caller to later cancel the request.
+ return ERR_IO_PENDING;
+ }
+
+ virtual void CancelRequest(RequestHandle req) {
+ EXPECT_EQ(reinterpret_cast<RequestHandle*>(1), req);
+ was_request_cancelled_ = true;
+ }
+
+ virtual void AddObserver(Observer* observer) {
+ NOTREACHED();
+ }
+
+ virtual void RemoveObserver(Observer* observer) {
+ NOTREACHED();
+ }
+
+ // Waits until Resolve() has been called.
+ void WaitUntilRequestIsReceived() {
+ event_.Wait();
+ }
+
+ bool was_request_cancelled() const {
+ return was_request_cancelled_;
+ }
+
+ private:
+ // Event to notify when a resolve request was received.
+ base::WaitableEvent event_;
+ bool was_request_cancelled_;
+};
+
+// This implementation of ProxyResolver simply does a synchronous resolve
+// on |host_resolver| in response to GetProxyForURL().
+class SyncProxyResolver : public ProxyResolver {
+ public:
+ explicit SyncProxyResolver(SyncHostResolverBridge* host_resolver)
+ : ProxyResolver(false), host_resolver_(host_resolver) {}
+
+ virtual int GetProxyForURL(const GURL& url,
+ ProxyInfo* results,
+ CompletionCallback* callback,
+ RequestHandle* request,
+ const BoundNetLog& net_log) {
+ EXPECT_FALSE(callback);
+ EXPECT_FALSE(request);
+
+ // Do a synchronous host resolve.
+ HostResolver::RequestInfo info(url.host(), 80);
+ AddressList addresses;
+ int rv =
+ host_resolver_->Resolve(info, &addresses, NULL, NULL, BoundNetLog());
+
+ EXPECT_EQ(ERR_ABORTED, rv);
+
+ return rv;
+ }
+
+ virtual void CancelRequest(RequestHandle request) {
+ NOTREACHED();
+ }
+
+ virtual void Shutdown() {
+ host_resolver_->Shutdown();
+ }
+
+ virtual int SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ CompletionCallback* callback) {
+ return OK;
+ }
+
+ private:
+ scoped_refptr<SyncHostResolverBridge> host_resolver_;
+};
+
+class SyncProxyResolverFactory : public ProxyResolverFactory {
+ public:
+ explicit SyncProxyResolverFactory(SyncHostResolverBridge* sync_host_resolver)
+ : ProxyResolverFactory(false),
+ sync_host_resolver_(sync_host_resolver) {
+ }
+
+ virtual ProxyResolver* CreateProxyResolver() {
+ return new SyncProxyResolver(sync_host_resolver_);
+ }
+
+ private:
+ scoped_refptr<SyncHostResolverBridge> sync_host_resolver_;
+};
+
+// This helper thread is used to create the circumstances for the deadlock.
+// It is analagous to the "IO thread" which would be main thread running the
+// network stack.
+class IOThread : public base::Thread {
+ public:
+ IOThread() : base::Thread("IO-thread") {}
+
+ virtual ~IOThread() {
+ Stop();
+ }
+
+ const scoped_refptr<BlockableHostResolver>& async_resolver() {
+ return async_resolver_;
+ }
+
+ protected:
+ virtual void Init() {
+ async_resolver_ = new BlockableHostResolver();
+
+ // Create a synchronous host resolver that operates the async host
+ // resolver on THIS thread.
+ scoped_refptr<SyncHostResolverBridge> sync_resolver =
+ new SyncHostResolverBridge(async_resolver_, message_loop());
+
+ proxy_resolver_.reset(
+ new MultiThreadedProxyResolver(
+ new SyncProxyResolverFactory(sync_resolver),
+ 1u));
+
+ // Initialize the resolver.
+ TestCompletionCallback callback;
+ proxy_resolver_->SetPacScript(ProxyResolverScriptData::FromURL(GURL()),
+ &callback);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ // Start an asynchronous request to the proxy resolver
+ // (note that it will never complete).
+ proxy_resolver_->GetProxyForURL(GURL("http://test/"), &results_,
+ &callback_, &request_, BoundNetLog());
+ }
+
+ virtual void CleanUp() {
+ // Cancel the outstanding request (note however that this will not
+ // unblock the PAC thread though).
+ proxy_resolver_->CancelRequest(request_);
+
+ // Delete the single threaded proxy resolver.
+ proxy_resolver_.reset();
+
+ // (There may have been a completion posted back to origin thread, avoid
+ // leaking it by running).
+ MessageLoop::current()->RunAllPending();
+
+ // During the teardown sequence of the single threaded proxy resolver,
+ // the outstanding host resolve should have been cancelled.
+ EXPECT_TRUE(async_resolver_->was_request_cancelled());
+
+ async_resolver_ = NULL;
+ }
+
+ private:
+ // This (async) host resolver will outlive the thread that is operating it
+ // synchronously.
+ scoped_refptr<BlockableHostResolver> async_resolver_;
+
+ scoped_ptr<ProxyResolver> proxy_resolver_;
+
+ // Data for the outstanding request to the single threaded proxy resolver.
+ TestCompletionCallback callback_;
+ ProxyInfo results_;
+ ProxyResolver::RequestHandle request_;
+};
+
+// Test that a deadlock does not happen during shutdown when a host resolve
+// is outstanding on the SyncHostResolverBridge.
+// This is a regression test for http://crbug.com/41244.
+TEST(MultiThreadedProxyResolverTest, ShutdownIsCalledBeforeThreadJoin) {
+ IOThread io_thread;
+ base::Thread::Options options;
+ options.message_loop_type = MessageLoop::TYPE_IO;
+ ASSERT_TRUE(io_thread.StartWithOptions(options));
+
+ io_thread.async_resolver()->WaitUntilRequestIsReceived();
+
+ // Now upon exitting this scope, the IOThread is destroyed -- this will
+ // stop the IOThread, which will in turn delete the
+ // SingleThreadedProxyResolver, which in turn will stop its internal
+ // PAC thread (which is currently blocked waiting on the host resolve which
+ // is running on IOThread). The IOThread::Cleanup() will verify that after
+ // the PAC thread is stopped, it cancels the request on the HostResolver.
+}
+
+} // namespace
+
+} // namespace net
diff --git a/net/run_testserver.target.mk b/net/run_testserver.target.mk
new file mode 100644
index 0000000..8de959b
--- /dev/null
+++ b/net/run_testserver.target.mk
@@ -0,0 +1,189 @@
+# This file is generated by gyp; do not edit.
+
+TOOLSET := target
+TARGET := run_testserver
+DEFS_Debug := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-DUNIT_TEST' \
+ '-DGTEST_HAS_RTTI=0' \
+ '-D__STDC_FORMAT_MACROS' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=1' \
+ '-D_DEBUG'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Debug := -Werror \
+ -pthread \
+ -fno-exceptions \
+ -Wall \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/atk-1.0 \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -O0 \
+ -g
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Debug :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Debug := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Debug := -I. \
+ -Itesting/gtest/include
+
+DEFS_Release := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-DUNIT_TEST' \
+ '-DGTEST_HAS_RTTI=0' \
+ '-D__STDC_FORMAT_MACROS' \
+ '-DNDEBUG' \
+ '-DNVALGRIND' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=0'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Release := -Werror \
+ -pthread \
+ -fno-exceptions \
+ -Wall \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/atk-1.0 \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -O2 \
+ -fno-ident \
+ -fdata-sections \
+ -ffunction-sections
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Release :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Release := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Release := -I. \
+ -Itesting/gtest/include
+
+OBJS := $(obj).target/$(TARGET)/net/tools/testserver/run_testserver.o
+
+# Add to the list of files we specially track dependencies for.
+all_deps += $(OBJS)
+
+# Make sure our dependencies are built before any of us.
+$(OBJS): | $(obj).target/net/libnet.a $(obj).target/net/libnet_test_support.a $(obj).target/base/libbase.a $(obj).target/testing/libgtest.a $(obj).target/third_party/modp_b64/libmodp_b64.a $(obj).target/base/third_party/dynamic_annotations/libdynamic_annotations.a $(obj).target/base/libsymbolize.a $(obj).target/net/third_party/nss/libssl.a $(obj).target/third_party/zlib/libzlib.a $(obj).target/base/libxdg_mime.a $(obj).target/base/allocator/liballocator.a $(obj).target/third_party/libevent/libevent.a $(obj).target/base/libbase_i18n.a $(obj).target/third_party/icu/libicui18n.a $(obj).target/third_party/icu/libicuuc.a $(obj).target/third_party/icu/libicudata.a $(obj).target/build/temp_gyp/libgoogleurl.a $(obj).target/sdch/libsdch.a $(obj).target/net/libnet_base.a $(obj).target/v8/tools/gyp/libv8_snapshot.a $(obj).target/v8/tools/gyp/libv8_base.a
+
+# CFLAGS et al overrides must be target-local.
+# See "Target-specific Variable Values" in the GNU Make manual.
+$(OBJS): TOOLSET := $(TOOLSET)
+$(OBJS): GYP_CFLAGS := $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_C_$(BUILDTYPE)) $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE))
+$(OBJS): GYP_CXXFLAGS := $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_CC_$(BUILDTYPE)) $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE))
+
+# Suffix rules, putting all outputs into $(obj).
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(srcdir)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+# Try building from generated source, too.
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj).$(TOOLSET)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+# End of this set of suffix rules
+### Rules for final target.
+LDFLAGS_Debug := -pthread \
+ -Wl,-z,noexecstack \
+ -Wl,-uIsHeapProfilerRunning,-uProfilerStart \
+ -Wl,-u_Z21InitialMallocHook_NewPKvj,-u_Z22InitialMallocHook_MMapPKvS0_jiiix,-u_Z22InitialMallocHook_SbrkPKvi \
+ -Wl,-u_Z21InitialMallocHook_NewPKvm,-u_Z22InitialMallocHook_MMapPKvS0_miiil,-u_Z22InitialMallocHook_SbrkPKvl \
+ -rdynamic
+
+LDFLAGS_Release := -pthread \
+ -Wl,-z,noexecstack \
+ -Wl,-uIsHeapProfilerRunning,-uProfilerStart \
+ -Wl,-u_Z21InitialMallocHook_NewPKvj,-u_Z22InitialMallocHook_MMapPKvS0_jiiix,-u_Z22InitialMallocHook_SbrkPKvi \
+ -Wl,-u_Z21InitialMallocHook_NewPKvm,-u_Z22InitialMallocHook_MMapPKvS0_miiil,-u_Z22InitialMallocHook_SbrkPKvl \
+ -Wl,--gc-sections
+
+LIBS := -lrt \
+ -ldl \
+ -lgtk-x11-2.0 \
+ -lgdk-x11-2.0 \
+ -latk-1.0 \
+ -lgio-2.0 \
+ -lpangoft2-1.0 \
+ -lgdk_pixbuf-2.0 \
+ -lm \
+ -lpangocairo-1.0 \
+ -lcairo \
+ -lpango-1.0 \
+ -lfreetype \
+ -lfontconfig \
+ -lgobject-2.0 \
+ -lgmodule-2.0 \
+ -lgthread-2.0 \
+ -lglib-2.0 \
+ -lnss3 \
+ -lnssutil3 \
+ -lsmime3 \
+ -lplds4 \
+ -lplc4 \
+ -lnspr4 \
+ -lpthread \
+ -lz \
+ -lgconf-2
+
+$(builddir)/run_testserver: GYP_LDFLAGS := $(LDFLAGS_$(BUILDTYPE))
+$(builddir)/run_testserver: LIBS := $(LIBS)
+$(builddir)/run_testserver: TOOLSET := $(TOOLSET)
+$(builddir)/run_testserver: $(OBJS) $(obj).target/net/libnet.a $(obj).target/net/libnet_test_support.a $(obj).target/base/libbase.a $(obj).target/testing/libgtest.a $(obj).target/third_party/modp_b64/libmodp_b64.a $(obj).target/base/third_party/dynamic_annotations/libdynamic_annotations.a $(obj).target/base/libsymbolize.a $(obj).target/net/third_party/nss/libssl.a $(obj).target/third_party/zlib/libzlib.a $(obj).target/base/libxdg_mime.a $(obj).target/base/allocator/liballocator.a $(obj).target/third_party/libevent/libevent.a $(obj).target/base/libbase_i18n.a $(obj).target/third_party/icu/libicui18n.a $(obj).target/third_party/icu/libicuuc.a $(obj).target/third_party/icu/libicudata.a $(obj).target/build/temp_gyp/libgoogleurl.a $(obj).target/sdch/libsdch.a $(obj).target/net/libnet_base.a $(obj).target/v8/tools/gyp/libv8_snapshot.a $(obj).target/v8/tools/gyp/libv8_base.a FORCE_DO_CMD
+ $(call do_cmd,link)
+
+all_deps += $(builddir)/run_testserver
+# Add target alias
+.PHONY: run_testserver
+run_testserver: $(builddir)/run_testserver
+
+# Add executable to "all" target.
+.PHONY: all
+all: $(builddir)/run_testserver
+
diff --git a/net/server/http_listen_socket.cc b/net/server/http_listen_socket.cc
new file mode 100644
index 0000000..16c664e
--- /dev/null
+++ b/net/server/http_listen_socket.cc
@@ -0,0 +1,318 @@
+// 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.
+
+#ifdef _WIN32
+#include <winsock2.h>
+#else
+#include <arpa/inet.h>
+#endif
+
+#include <map>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/md5.h"
+#include "base/string_util.h"
+#include "net/server/http_listen_socket.h"
+#include "net/server/http_server_request_info.h"
+
+// must run in the IO thread
+HttpListenSocket::HttpListenSocket(SOCKET s,
+ HttpListenSocket::Delegate* delegate)
+ : ALLOW_THIS_IN_INITIALIZER_LIST(ListenSocket(s, this)),
+ delegate_(delegate),
+ is_web_socket_(false) {
+}
+
+// must run in the IO thread
+HttpListenSocket::~HttpListenSocket() {
+}
+
+void HttpListenSocket::Accept() {
+ SOCKET conn = ListenSocket::Accept(socket_);
+ DCHECK_NE(conn, ListenSocket::kInvalidSocket);
+ if (conn == ListenSocket::kInvalidSocket) {
+ // TODO
+ } else {
+ scoped_refptr<HttpListenSocket> sock =
+ new HttpListenSocket(conn, delegate_);
+ // it's up to the delegate to AddRef if it wants to keep it around
+ DidAccept(this, sock);
+ }
+}
+
+HttpListenSocket* HttpListenSocket::Listen(
+ const std::string& ip,
+ int port,
+ HttpListenSocket::Delegate* delegate) {
+ SOCKET s = ListenSocket::Listen(ip, port);
+ if (s == ListenSocket::kInvalidSocket) {
+ // TODO (ibrar): error handling
+ } else {
+ HttpListenSocket *serv = new HttpListenSocket(s, delegate);
+ serv->Listen();
+ return serv;
+ }
+ return NULL;
+}
+
+std::string GetHeaderValue(
+ HttpServerRequestInfo* request,
+ const std::string& header_name) {
+ HttpServerRequestInfo::HeadersMap::iterator it =
+ request->headers.find(header_name);
+ if (it != request->headers.end())
+ return it->second;
+ return "";
+}
+
+uint32 WebSocketKeyFingerprint(const std::string& str) {
+ std::string result;
+ const char* pChar = str.c_str();
+ int length = str.length();
+ int spaces = 0;
+ for (int i = 0; i < length; ++i) {
+ if (pChar[i] >= '0' && pChar[i] <= '9')
+ result.append(&pChar[i], 1);
+ else if (pChar[i] == ' ')
+ spaces++;
+ }
+ if (spaces == 0)
+ return 0;
+ int64 number = 0;
+ if (!StringToInt64(result, &number))
+ return 0;
+ return htonl(static_cast<uint32>(number / spaces));
+}
+
+void HttpListenSocket::AcceptWebSocket(HttpServerRequestInfo* request) {
+ std::string key1 = GetHeaderValue(request, "Sec-WebSocket-Key1");
+ std::string key2 = GetHeaderValue(request, "Sec-WebSocket-Key2");
+
+ uint32 fp1 = WebSocketKeyFingerprint(key1);
+ uint32 fp2 = WebSocketKeyFingerprint(key2);
+
+ char data[16];
+ memcpy(data, &fp1, 4);
+ memcpy(data + 4, &fp2, 4);
+ memcpy(data + 8, &request->data[0], 8);
+
+ MD5Digest digest;
+ MD5Sum(data, 16, &digest);
+
+ std::string origin = GetHeaderValue(request, "Origin");
+ std::string host = GetHeaderValue(request, "Host");
+ std::string location = "ws://" + host + request->path;
+ is_web_socket_ = true;
+ Send(StringPrintf("HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Origin: %s\r\n"
+ "Sec-WebSocket-Location: %s\r\n"
+ "\r\n",
+ origin.c_str(),
+ location.c_str()));
+ Send(reinterpret_cast<char*>(digest.a), 16);
+}
+
+void HttpListenSocket::SendOverWebSocket(const std::string& data) {
+ DCHECK(is_web_socket_);
+ char message_start = 0;
+ char message_end = -1;
+ Send(&message_start, 1);
+ Send(data);
+ Send(&message_end, 1);
+}
+
+//
+// HTTP Request Parser
+// This HTTP request parser uses a simple state machine to quickly parse
+// through the headers. The parser is not 100% complete, as it is designed
+// for use in this simple test driver.
+//
+// Known issues:
+// - does not handle whitespace on first HTTP line correctly. Expects
+// a single space between the method/url and url/protocol.
+
+// Input character types.
+enum header_parse_inputs {
+ INPUT_SPACE,
+ INPUT_CR,
+ INPUT_LF,
+ INPUT_COLON,
+ INPUT_00,
+ INPUT_FF,
+ INPUT_DEFAULT,
+ MAX_INPUTS,
+};
+
+// Parser states.
+enum header_parse_states {
+ ST_METHOD, // Receiving the method
+ ST_URL, // Receiving the URL
+ ST_PROTO, // Receiving the protocol
+ ST_HEADER, // Starting a Request Header
+ ST_NAME, // Receiving a request header name
+ ST_SEPARATOR, // Receiving the separator between header name and value
+ ST_VALUE, // Receiving a request header value
+ ST_WS_READY, // Ready to receive web socket frame
+ ST_WS_FRAME, // Receiving WebSocket frame
+ ST_WS_CLOSE, // Closing the connection WebSocket connection
+ ST_DONE, // Parsing is complete and successful
+ ST_ERR, // Parsing encountered invalid syntax.
+ MAX_STATES
+};
+
+// State transition table
+int parser_state[MAX_STATES][MAX_INPUTS] = {
+/* METHOD */ { ST_URL, ST_ERR, ST_ERR, ST_ERR, ST_ERR, ST_ERR, ST_METHOD },
+/* URL */ { ST_PROTO, ST_ERR, ST_ERR, ST_URL, ST_ERR, ST_ERR, ST_URL },
+/* PROTOCOL */ { ST_ERR, ST_HEADER, ST_NAME, ST_ERR, ST_ERR, ST_ERR, ST_PROTO },
+/* HEADER */ { ST_ERR, ST_ERR, ST_NAME, ST_ERR, ST_ERR, ST_ERR, ST_ERR },
+/* NAME */ { ST_SEPARATOR, ST_DONE, ST_ERR, ST_SEPARATOR, ST_ERR, ST_ERR, ST_NAME },
+/* SEPARATOR */ { ST_SEPARATOR, ST_ERR, ST_ERR, ST_SEPARATOR, ST_ERR, ST_ERR, ST_VALUE },
+/* VALUE */ { ST_VALUE, ST_HEADER, ST_NAME, ST_VALUE, ST_ERR, ST_ERR, ST_VALUE },
+/* WS_READY */ { ST_ERR, ST_ERR, ST_ERR, ST_ERR, ST_WS_FRAME, ST_WS_CLOSE, ST_ERR},
+/* WS_FRAME */ { ST_WS_FRAME, ST_WS_FRAME, ST_WS_FRAME, ST_WS_FRAME, ST_ERR, ST_WS_READY, ST_WS_FRAME },
+/* WS_CLOSE */ { ST_ERR, ST_ERR, ST_ERR, ST_ERR, ST_WS_CLOSE, ST_ERR, ST_ERR },
+/* DONE */ { ST_DONE, ST_DONE, ST_DONE, ST_DONE, ST_DONE, ST_DONE, ST_DONE },
+/* ERR */ { ST_ERR, ST_ERR, ST_ERR, ST_ERR, ST_ERR, ST_ERR, ST_ERR }
+};
+
+// Convert an input character to the parser's input token.
+int charToInput(char ch) {
+ switch(ch) {
+ case ' ':
+ return INPUT_SPACE;
+ case '\r':
+ return INPUT_CR;
+ case '\n':
+ return INPUT_LF;
+ case ':':
+ return INPUT_COLON;
+ case 0x0:
+ return INPUT_00;
+ case static_cast<char>(-1):
+ return INPUT_FF;
+ }
+ return INPUT_DEFAULT;
+}
+
+HttpServerRequestInfo* HttpListenSocket::ParseHeaders() {
+ int pos = 0;
+ int data_len = recv_data_.length();
+ int state = is_web_socket_ ? ST_WS_READY : ST_METHOD;
+ scoped_ptr<HttpServerRequestInfo> info(new HttpServerRequestInfo());
+ std::string buffer;
+ std::string header_name;
+ std::string header_value;
+ while (pos < data_len) {
+ char ch = recv_data_[pos++];
+ int input = charToInput(ch);
+ int next_state = parser_state[state][input];
+
+ bool transition = (next_state != state);
+ if (transition) {
+ // Do any actions based on state transitions.
+ switch (state) {
+ case ST_METHOD:
+ info->method = buffer;
+ buffer.clear();
+ break;
+ case ST_URL:
+ info->path = buffer;
+ buffer.clear();
+ break;
+ case ST_PROTO:
+ // TODO(mbelshe): Deal better with parsing protocol.
+ DCHECK(buffer == "HTTP/1.1");
+ buffer.clear();
+ break;
+ case ST_NAME:
+ header_name = buffer;
+ buffer.clear();
+ break;
+ case ST_VALUE:
+ header_value = buffer;
+ // TODO(mbelshe): Deal better with duplicate headers
+ DCHECK(info->headers.find(header_name) == info->headers.end());
+ info->headers[header_name] = header_value;
+ buffer.clear();
+ break;
+ case ST_SEPARATOR:
+ buffer.append(&ch, 1);
+ break;
+ case ST_WS_FRAME:
+ recv_data_ = recv_data_.substr(pos);
+ info->data = buffer;
+ buffer.clear();
+ return info.release();
+ break;
+ }
+ state = next_state;
+ } else {
+ // Do any actions based on current state
+ switch (state) {
+ case ST_METHOD:
+ case ST_URL:
+ case ST_PROTO:
+ case ST_VALUE:
+ case ST_NAME:
+ case ST_WS_FRAME:
+ buffer.append(&ch, 1);
+ break;
+ case ST_DONE:
+ recv_data_ = recv_data_.substr(pos);
+ info->data = recv_data_;
+ recv_data_.clear();
+ return info.release();
+ case ST_WS_CLOSE:
+ is_web_socket_ = false;
+ return NULL;
+ case ST_ERR:
+ return NULL;
+ }
+ }
+ }
+ // No more characters, but we haven't finished parsing yet.
+ return NULL;
+}
+
+void HttpListenSocket::DidAccept(ListenSocket* server,
+ ListenSocket* connection) {
+ connection->AddRef();
+}
+
+void HttpListenSocket::DidRead(ListenSocket*,
+ const char* data,
+ int len) {
+ recv_data_.append(data, len);
+ while (recv_data_.length()) {
+ scoped_ptr<HttpServerRequestInfo> request(ParseHeaders());
+ if (!request.get())
+ break;
+
+ if (is_web_socket_) {
+ delegate_->OnWebSocketMessage(this, request->data);
+ continue;
+ }
+
+ std::string connection = GetHeaderValue(request.get(), "Connection");
+ if (connection == "Upgrade") {
+ // Is this WebSocket and if yes, upgrade the connection.
+ std::string key1 = GetHeaderValue(request.get(), "Sec-WebSocket-Key1");
+ std::string key2 = GetHeaderValue(request.get(), "Sec-WebSocket-Key2");
+ if (!key1.empty() && !key2.empty()) {
+ delegate_->OnWebSocketRequest(this, request.get());
+ continue;
+ }
+ }
+ delegate_->OnHttpRequest(this, request.get());
+ }
+}
+
+void HttpListenSocket::DidClose(ListenSocket* sock) {
+ sock->Release();
+ delegate_->OnClose(this);
+}
diff --git a/net/server/http_listen_socket.h b/net/server/http_listen_socket.h
new file mode 100644
index 0000000..5517af0
--- /dev/null
+++ b/net/server/http_listen_socket.h
@@ -0,0 +1,65 @@
+// 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.
+
+#ifndef NET_SERVER_HTTP_LISTEN_SOCKET_H_
+#define NET_SERVER_HTTP_LISTEN_SOCKET_H_
+
+#include "net/base/listen_socket.h"
+
+class HttpServerRequestInfo;
+
+// Implements a simple HTTP listen socket on top of the raw socket interface.
+class HttpListenSocket : public ListenSocket,
+ public ListenSocket::ListenSocketDelegate {
+ public:
+ class Delegate {
+ public:
+ virtual void OnHttpRequest(HttpListenSocket* socket,
+ HttpServerRequestInfo* info) = 0;
+
+ virtual void OnWebSocketRequest(HttpListenSocket* socket,
+ HttpServerRequestInfo* info) = 0;
+
+ virtual void OnWebSocketMessage(HttpListenSocket* socket,
+ const std::string& data) = 0;
+
+ virtual void OnClose(HttpListenSocket* socket) = 0;
+ protected:
+ virtual ~Delegate() {}
+ };
+
+ static HttpListenSocket* Listen(const std::string& ip,
+ int port,
+ HttpListenSocket::Delegate* delegate);
+
+ void AcceptWebSocket(HttpServerRequestInfo* request);
+
+ void SendOverWebSocket(const std::string& data);
+
+ void Listen() { ListenSocket::Listen(); }
+ virtual void Accept();
+
+ // ListenSocketDelegate
+ virtual void DidAccept(ListenSocket* server, ListenSocket* connection);
+ virtual void DidRead(ListenSocket* connection, const char* data, int len);
+ virtual void DidClose(ListenSocket* sock);
+
+ private:
+ static const int kReadBufSize = 16 * 1024;
+ HttpListenSocket(SOCKET s, HttpListenSocket::Delegate* del);
+ virtual ~HttpListenSocket();
+
+ // Expects the raw data to be stored in recv_data_. If parsing is successful,
+ // will remove the data parsed from recv_data_, leaving only the unused
+ // recv data.
+ HttpServerRequestInfo* ParseHeaders();
+
+ HttpListenSocket::Delegate* delegate_;
+ bool is_web_socket_;
+ std::string recv_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpListenSocket);
+};
+
+#endif // NET_SERVER_HTTP_LISTEN_SOCKET_H_
diff --git a/net/server/http_server_request_info.h b/net/server/http_server_request_info.h
new file mode 100644
index 0000000..84f767f
--- /dev/null
+++ b/net/server/http_server_request_info.h
@@ -0,0 +1,35 @@
+// 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.
+
+#ifndef NET_SERVER_HTTP_SERVER_REQUEST_INFO_H_
+#define NET_SERVER_HTTP_SERVER_REQUEST_INFO_H_
+
+#include <string>
+#include <map>
+
+#include "net/http/http_request_info.h"
+
+// Meta information about an HTTP request.
+// This is geared toward servers in that it keeps a map of the headers and
+// values rather than just a list of header strings (which net::HttpRequestInfo
+// does).
+class HttpServerRequestInfo {
+ public:
+ HttpServerRequestInfo() {}
+
+ // Request method.
+ std::string method;
+
+ // Request line.
+ std::string path;
+
+ // Request data.
+ std::string data;
+
+ // A map of the names -> values for HTTP headers.
+ typedef std::map<std::string, std::string> HeadersMap;
+ HeadersMap headers;
+};
+
+#endif // NET_SERVER_HTTP_SERVER_REQUEST_INFO_H_
diff --git a/net/socket/client_socket.h b/net/socket/client_socket.h
index 28c7b4d..2bc1adb 100644
--- a/net/socket/client_socket.h
+++ b/net/socket/client_socket.h
@@ -1,25 +1,16 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// 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.
#ifndef NET_SOCKET_CLIENT_SOCKET_H_
#define NET_SOCKET_CLIENT_SOCKET_H_
-#include "build/build_config.h"
-
-// For struct sockaddr and socklen_t.
-#if defined(OS_POSIX)
-#include <sys/types.h>
-#include <sys/socket.h>
-#elif defined(OS_WIN)
-#include <ws2tcpip.h>
-#endif
-
#include "net/socket/socket.h"
namespace net {
-class LoadLog;
+class AddressList;
+class BoundNetLog;
class ClientSocket : public Socket {
public:
@@ -37,7 +28,7 @@
//
// Connect may also be called again after a call to the Disconnect method.
//
- virtual int Connect(CompletionCallback* callback, LoadLog* load_log) = 0;
+ virtual int Connect(CompletionCallback* callback) = 0;
// Called to disconnect a socket. Does nothing if the socket is already
// disconnected. After calling Disconnect it is possible to call Connect
@@ -57,9 +48,11 @@
// have been received.
virtual bool IsConnectedAndIdle() const = 0;
- // Identical to BSD socket call getpeername().
- // Needed by ssl_client_socket_nss and ssl_client_socket_mac.
- virtual int GetPeerName(struct sockaddr* name, socklen_t* namelen) = 0;
+ // Copies the peer address to |address| and returns a network error code.
+ virtual int GetPeerAddress(AddressList* address) const = 0;
+
+ // Gets the NetLog for this socket.
+ virtual const BoundNetLog& NetLog() const = 0;
};
} // namespace net
diff --git a/net/socket/client_socket_factory.cc b/net/socket/client_socket_factory.cc
index 6a3a4cc..fbccfcb 100644
--- a/net/socket/client_socket_factory.cc
+++ b/net/socket/client_socket_factory.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -6,12 +6,14 @@
#include "base/singleton.h"
#include "build/build_config.h"
+#include "net/socket/client_socket_handle.h"
#if defined(OS_WIN)
#include "net/socket/ssl_client_socket_win.h"
#elif defined(USE_NSS)
#include "net/socket/ssl_client_socket_nss.h"
#elif defined(OS_MACOSX)
#include "net/socket/ssl_client_socket_mac.h"
+#include "net/socket/ssl_client_socket_nss.h"
#endif
#include "net/socket/tcp_client_socket.h"
@@ -20,7 +22,7 @@
namespace {
SSLClientSocket* DefaultSSLClientSocketFactory(
- ClientSocket* transport_socket,
+ ClientSocketHandle* transport_socket,
const std::string& hostname,
const SSLConfig& ssl_config) {
#if defined(OS_WIN)
@@ -28,25 +30,30 @@
#elif defined(USE_NSS)
return new SSLClientSocketNSS(transport_socket, hostname, ssl_config);
#elif defined(OS_MACOSX)
- return new SSLClientSocketMac(transport_socket, hostname, ssl_config);
+ // TODO(wtc): SSLClientSocketNSS can't do SSL client authentication using
+ // Mac OS X CDSA/CSSM yet (http://crbug.com/45369), so fall back on
+ // SSLClientSocketMac.
+ if (ssl_config.client_cert)
+ return new SSLClientSocketMac(transport_socket, hostname, ssl_config);
+
+ return new SSLClientSocketNSS(transport_socket, hostname, ssl_config);
#else
NOTIMPLEMENTED();
return NULL;
#endif
}
-// True if we should use NSS instead of the system SSL library for SSL.
SSLClientSocketFactory g_ssl_factory = DefaultSSLClientSocketFactory;
class DefaultClientSocketFactory : public ClientSocketFactory {
public:
virtual ClientSocket* CreateTCPClientSocket(
- const AddressList& addresses) {
- return new TCPClientSocket(addresses);
+ const AddressList& addresses, NetLog* net_log) {
+ return new TCPClientSocket(addresses, net_log);
}
virtual SSLClientSocket* CreateSSLClientSocket(
- ClientSocket* transport_socket,
+ ClientSocketHandle* transport_socket,
const std::string& hostname,
const SSLConfig& ssl_config) {
return g_ssl_factory(transport_socket, hostname, ssl_config);
@@ -66,4 +73,14 @@
g_ssl_factory = factory;
}
+// Deprecated function (http://crbug.com/37810) that takes a ClientSocket.
+SSLClientSocket* ClientSocketFactory::CreateSSLClientSocket(
+ ClientSocket* transport_socket,
+ const std::string& hostname,
+ const SSLConfig& ssl_config) {
+ ClientSocketHandle* socket_handle = new ClientSocketHandle();
+ socket_handle->set_socket(transport_socket);
+ return CreateSSLClientSocket(socket_handle, hostname, ssl_config);
+}
+
} // namespace net
diff --git a/net/socket/client_socket_factory.h b/net/socket/client_socket_factory.h
index 988cf97..dddf1de 100644
--- a/net/socket/client_socket_factory.h
+++ b/net/socket/client_socket_factory.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -11,18 +11,14 @@
class AddressList;
class ClientSocket;
+class ClientSocketHandle;
+class NetLog;
class SSLClientSocket;
struct SSLConfig;
// Callback function to create new SSLClientSocket objects.
typedef SSLClientSocket* (*SSLClientSocketFactory)(
- ClientSocket* transport_socket,
- const std::string& hostname,
- const SSLConfig& ssl_config);
-
-// Creates SSLClientSocketNSS objects.
-SSLClientSocket* SSLClientSocketNSSFactory(
- ClientSocket* transport_socket,
+ ClientSocketHandle* transport_socket,
const std::string& hostname,
const SSLConfig& ssl_config);
@@ -33,13 +29,19 @@
virtual ~ClientSocketFactory() {}
virtual ClientSocket* CreateTCPClientSocket(
- const AddressList& addresses) = 0;
+ const AddressList& addresses, NetLog* net_log) = 0;
virtual SSLClientSocket* CreateSSLClientSocket(
- ClientSocket* transport_socket,
+ ClientSocketHandle* transport_socket,
const std::string& hostname,
const SSLConfig& ssl_config) = 0;
+
+ // Deprecated function (http://crbug.com/37810) that takes a ClientSocket.
+ virtual SSLClientSocket* CreateSSLClientSocket(ClientSocket* transport_socket,
+ const std::string& hostname,
+ const SSLConfig& ssl_config);
+
// Returns the default ClientSocketFactory.
static ClientSocketFactory* GetDefaultFactory();
diff --git a/net/socket/client_socket_handle.cc b/net/socket/client_socket_handle.cc
index d283dd6..de2fd94 100644
--- a/net/socket/client_socket_handle.cc
+++ b/net/socket/client_socket_handle.cc
@@ -1,21 +1,24 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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 "net/socket/client_socket_handle.h"
#include "base/compiler_specific.h"
+#include "base/histogram.h"
#include "base/logging.h"
#include "net/base/net_errors.h"
#include "net/socket/client_socket_pool.h"
+#include "net/socket/client_socket_pool_histograms.h"
namespace net {
ClientSocketHandle::ClientSocketHandle()
- : socket_(NULL),
+ : is_initialized_(false),
is_reused_(false),
ALLOW_THIS_IN_INITIALIZER_LIST(
- callback_(this, &ClientSocketHandle::OnIOComplete)) {}
+ callback_(this, &ClientSocketHandle::OnIOComplete)),
+ is_ssl_error_(false) {}
ClientSocketHandle::~ClientSocketHandle() {
Reset();
@@ -23,31 +26,48 @@
void ClientSocketHandle::Reset() {
ResetInternal(true);
+ ResetErrorState();
}
void ClientSocketHandle::ResetInternal(bool cancel) {
if (group_name_.empty()) // Was Init called?
return;
- if (socket_.get()) {
- // If we've still got a socket, release it back to the ClientSocketPool so
- // it can be deleted or reused.
- pool_->ReleaseSocket(group_name_, release_socket());
+ if (is_initialized()) {
+ // Because of http://crbug.com/37810 we may not have a pool, but have
+ // just a raw socket.
+ socket_->NetLog().EndEvent(NetLog::TYPE_SOCKET_IN_USE, NULL);
+ if (pool_)
+ // If we've still got a socket, release it back to the ClientSocketPool so
+ // it can be deleted or reused.
+ pool_->ReleaseSocket(group_name_, release_socket(), pool_id_);
} else if (cancel) {
- // If we did not get initialized yet, so we've got a socket request pending.
+ // If we did not get initialized yet, we've got a socket request pending.
// Cancel it.
pool_->CancelRequest(group_name_, this);
}
+ is_initialized_ = false;
group_name_.clear();
is_reused_ = false;
user_callback_ = NULL;
pool_ = NULL;
idle_time_ = base::TimeDelta();
init_time_ = base::TimeTicks();
+ setup_time_ = base::TimeDelta();
+ pool_id_ = -1;
+}
+
+void ClientSocketHandle::ResetErrorState() {
+ is_ssl_error_ = false;
+ ssl_error_response_info_ = HttpResponseInfo();
}
LoadState ClientSocketHandle::GetLoadState() const {
CHECK(!is_initialized());
CHECK(!group_name_.empty());
+ // Because of http://crbug.com/37810 we may not have a pool, but have
+ // just a raw socket.
+ if (!pool_)
+ return LOAD_STATE_IDLE;
return pool_->GetLoadState(group_name_, this);
}
@@ -59,9 +79,43 @@
}
void ClientSocketHandle::HandleInitCompletion(int result) {
- CHECK(ERR_IO_PENDING != result);
- if (result != OK)
- ResetInternal(false); // The request failed, so there's nothing to cancel.
+ CHECK_NE(ERR_IO_PENDING, result);
+ if (result != OK) {
+ if (!socket_.get())
+ ResetInternal(false); // Nothing to cancel since the request failed.
+ else
+ is_initialized_ = true;
+ return;
+ }
+ is_initialized_ = true;
+ CHECK_NE(-1, pool_id_) << "Pool should have set |pool_id_| to a valid value.";
+ setup_time_ = base::TimeTicks::Now() - init_time_;
+
+ scoped_refptr<ClientSocketPoolHistograms> histograms = pool_->histograms();
+ histograms->AddSocketType(reuse_type());
+ switch (reuse_type()) {
+ case ClientSocketHandle::UNUSED:
+ histograms->AddRequestTime(setup_time());
+ break;
+ case ClientSocketHandle::UNUSED_IDLE:
+ histograms->AddUnusedIdleTime(idle_time());
+ break;
+ case ClientSocketHandle::REUSED_IDLE:
+ histograms->AddReusedIdleTime(idle_time());
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+
+ // Broadcast that the socket has been acquired.
+ // TODO(eroman): This logging is not complete, in particular set_socket() and
+ // release() socket. It ends up working though, since those methods are being
+ // used to layer sockets (and the destination sources are the same).
+ DCHECK(socket_.get());
+ socket_->NetLog().BeginEvent(
+ NetLog::TYPE_SOCKET_IN_USE,
+ new NetLogSourceParameter("source_dependency", requesting_source_));
}
} // namespace net
diff --git a/net/socket/client_socket_handle.h b/net/socket/client_socket_handle.h
index bf7bf8a..7fdf784 100644
--- a/net/socket/client_socket_handle.h
+++ b/net/socket/client_socket_handle.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -14,7 +14,9 @@
#include "net/base/completion_callback.h"
#include "net/base/load_states.h"
#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
#include "net/base/request_priority.h"
+#include "net/http/http_response_info.h"
#include "net/socket/client_socket.h"
#include "net/socket/client_socket_pool.h"
@@ -53,17 +55,26 @@
// This method returns ERR_IO_PENDING if it cannot complete synchronously, in
// which case the consumer will be notified of completion via |callback|.
//
+ // If the pool was not able to reuse an existing socket, the new socket
+ // may report a recoverable error. In this case, the return value will
+ // indicate an error and the socket member will be set. If it is determined
+ // that the error is not recoverable, the Disconnect method should be used
+ // on the socket, so that it does not get reused.
+ //
+ // A non-recoverable error may set additional state in the ClientSocketHandle
+ // to allow the caller to determine what went wrong.
+ //
// Init may be called multiple times.
//
- // Profiling information for the request is saved to |load_log| if non-NULL.
+ // Profiling information for the request is saved to |net_log| if non-NULL.
//
template <typename SocketParams, typename PoolType>
int Init(const std::string& group_name,
- const SocketParams& socket_params,
+ const scoped_refptr<SocketParams>& socket_params,
RequestPriority priority,
CompletionCallback* callback,
- PoolType* pool,
- LoadLog* load_log);
+ const scoped_refptr<PoolType>& pool,
+ const BoundNetLog& net_log);
// An initialized handle can be reset, which causes it to return to the
// un-initialized state. This releases the underlying socket, which in the
@@ -80,18 +91,39 @@
LoadState GetLoadState() const;
// Returns true when Init() has completed successfully.
- bool is_initialized() const { return socket_ != NULL; }
+ bool is_initialized() const { return is_initialized_; }
// Returns the time tick when Init() was called.
base::TimeTicks init_time() const { return init_time_; }
+ // Returns the time between Init() and when is_initialized() becomes true.
+ base::TimeDelta setup_time() const { return setup_time_; }
+
// Used by ClientSocketPool to initialize the ClientSocketHandle.
void set_is_reused(bool is_reused) { is_reused_ = is_reused; }
void set_socket(ClientSocket* s) { socket_.reset(s); }
void set_idle_time(base::TimeDelta idle_time) { idle_time_ = idle_time; }
+ void set_pool_id(int id) { pool_id_ = id; }
+ void set_is_ssl_error(bool is_ssl_error) { is_ssl_error_ = is_ssl_error; }
+ void set_ssl_error_response_info(const HttpResponseInfo& ssl_error_state) {
+ ssl_error_response_info_ = ssl_error_state;
+ }
+
+ // Only valid if there is no |socket_|.
+ bool is_ssl_error() const {
+ DCHECK(socket_.get() == NULL);
+ return is_ssl_error_;
+ }
+ // On an ERR_PROXY_AUTH_REQUESTED error, the |headers| and |auth_challenge|
+ // fields are filled in. On an ERR_SSL_CLIENT_AUTH_CERT_NEEDED error,
+ // the |cert_request_info| field is set.
+ const HttpResponseInfo& ssl_error_response_info() const {
+ return ssl_error_response_info_;
+ }
// These may only be used if is_initialized() is true.
const std::string& group_name() const { return group_name_; }
+ int id() const { return pool_id_; }
ClientSocket* socket() { return socket_.get(); }
ClientSocket* release_socket() { return socket_.release(); }
bool is_reused() const { return is_reused_; }
@@ -128,9 +160,14 @@
void HandleInitCompletion(int result);
// Resets the state of the ClientSocketHandle. |cancel| indicates whether or
- // not to try to cancel the request with the ClientSocketPool.
+ // not to try to cancel the request with the ClientSocketPool. Does not
+ // reset the supplemental error state.
void ResetInternal(bool cancel);
+ // Resets the supplemental error state.
+ void ResetErrorState();
+
+ bool is_initialized_;
scoped_refptr<ClientSocketPool> pool_;
scoped_ptr<ClientSocket> socket_;
std::string group_name_;
@@ -138,7 +175,13 @@
CompletionCallbackImpl<ClientSocketHandle> callback_;
CompletionCallback* user_callback_;
base::TimeDelta idle_time_;
+ int pool_id_; // See ClientSocketPool::ReleaseSocket() for an explanation.
+ bool is_ssl_error_;
+ HttpResponseInfo ssl_error_response_info_;
base::TimeTicks init_time_;
+ base::TimeDelta setup_time_;
+
+ NetLog::Source requesting_source_;
DISALLOW_COPY_AND_ASSIGN(ClientSocketHandle);
};
@@ -146,22 +189,25 @@
// Template function implementation:
template <typename SocketParams, typename PoolType>
int ClientSocketHandle::Init(const std::string& group_name,
- const SocketParams& socket_params,
+ const scoped_refptr<SocketParams>& socket_params,
RequestPriority priority,
CompletionCallback* callback,
- PoolType* pool,
- LoadLog* load_log) {
+ const scoped_refptr<PoolType>& pool,
+ const BoundNetLog& net_log) {
+ requesting_source_ = net_log.source();
+
CHECK(!group_name.empty());
- // Note that this will result in a link error if the SocketParams has not been
- // registered for the PoolType via REGISTER_SOCKET_PARAMS_FOR_POOL (defined in
- // client_socket_pool.h).
+ // Note that this will result in a compile error if the SocketParams has not
+ // been registered for the PoolType via REGISTER_SOCKET_PARAMS_FOR_POOL
+ // (defined in client_socket_pool.h).
CheckIsValidSocketParamsForPool<PoolType, SocketParams>();
ResetInternal(true);
+ ResetErrorState();
pool_ = pool;
group_name_ = group_name;
init_time_ = base::TimeTicks::Now();
int rv = pool_->RequestSocket(
- group_name, &socket_params, priority, this, &callback_, load_log);
+ group_name, &socket_params, priority, this, &callback_, net_log);
if (rv == ERR_IO_PENDING) {
user_callback_ = callback;
} else {
diff --git a/net/socket/client_socket_pool.cc b/net/socket/client_socket_pool.cc
new file mode 100644
index 0000000..4cefe8d
--- /dev/null
+++ b/net/socket/client_socket_pool.cc
@@ -0,0 +1,29 @@
+// 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 "net/socket/client_socket_pool.h"
+
+namespace {
+
+// The maximum duration, in seconds, to keep used idle persistent sockets
+// alive.
+// TODO(ziadh): Change this timeout after getting histogram data on how long it
+// should be.
+int g_unused_idle_socket_timeout = 10;
+
+} // namespace
+
+namespace net {
+
+// static
+int ClientSocketPool::unused_idle_socket_timeout() {
+ return g_unused_idle_socket_timeout;
+}
+
+// static
+void ClientSocketPool::set_unused_idle_socket_timeout(int timeout) {
+ g_unused_idle_socket_timeout = timeout;
+}
+
+} // namespace net
diff --git a/net/socket/client_socket_pool.h b/net/socket/client_socket_pool.h
index 4bd1a58..646b5ed 100644
--- a/net/socket/client_socket_pool.h
+++ b/net/socket/client_socket_pool.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -9,7 +9,10 @@
#include <map>
#include <string>
+#include "base/basictypes.h"
#include "base/ref_counted.h"
+#include "base/time.h"
+#include "base/template_util.h"
#include "net/base/completion_callback.h"
#include "net/base/host_resolver.h"
#include "net/base/load_states.h"
@@ -19,6 +22,7 @@
class ClientSocket;
class ClientSocketHandle;
+class ClientSocketPoolHistograms;
// A ClientSocketPool is used to restrict the number of sockets open at a time.
// It also maintains a list of idle persistent sockets.
@@ -27,47 +31,64 @@
public:
// Requests a connected socket for a group_name.
//
- // There are four possible results from calling this function:
+ // There are five possible results from calling this function:
// 1) RequestSocket returns OK and initializes |handle| with a reused socket.
// 2) RequestSocket returns OK with a newly connected socket.
// 3) RequestSocket returns ERR_IO_PENDING. The handle will be added to a
// wait list until a socket is available to reuse or a new socket finishes
// connecting. |priority| will determine the placement into the wait list.
// 4) An error occurred early on, so RequestSocket returns an error code.
+ // 5) A recoverable error occurred while setting up the socket. An error
+ // code is returned, but the |handle| is initialized with the new socket.
+ // The caller must recover from the error before using the connection, or
+ // Disconnect the socket before releasing or resetting the |handle|.
+ // The current recoverable errors are: the errors accepted by
+ // IsCertificateError(err) and PROXY_AUTH_REQUESTED when reported by
+ // HttpProxyClientSocketPool.
//
// If this function returns OK, then |handle| is initialized upon return.
// The |handle|'s is_initialized method will return true in this case. If a
// ClientSocket was reused, then ClientSocketPool will call
// |handle|->set_reused(true). In either case, the socket will have been
// allocated and will be connected. A client might want to know whether or
- // not the socket is reused in order to know whether or not he needs to
- // perform SSL connection or tunnel setup or to request a new socket if he
- // encounters an error with the reused socket.
+ // not the socket is reused in order to request a new socket if he encounters
+ // an error with the reused socket.
//
// If ERR_IO_PENDING is returned, then the callback will be used to notify the
// client of completion.
//
- // Profiling information for the request is saved to |load_log| if non-NULL.
+ // Profiling information for the request is saved to |net_log| if non-NULL.
virtual int RequestSocket(const std::string& group_name,
const void* params,
RequestPriority priority,
ClientSocketHandle* handle,
CompletionCallback* callback,
- LoadLog* load_log) = 0;
+ const BoundNetLog& net_log) = 0;
// Called to cancel a RequestSocket call that returned ERR_IO_PENDING. The
// same handle parameter must be passed to this method as was passed to the
// RequestSocket call being cancelled. The associated CompletionCallback is
- // not run.
+ // not run. However, for performance, we will let one ConnectJob complete
+ // and go idle.
virtual void CancelRequest(const std::string& group_name,
- const ClientSocketHandle* handle) = 0;
+ ClientSocketHandle* handle) = 0;
// Called to release a socket once the socket is no longer needed. If the
// socket still has an established connection, then it will be added to the
// set of idle sockets to be used to satisfy future RequestSocket calls.
- // Otherwise, the ClientSocket is destroyed.
+ // Otherwise, the ClientSocket is destroyed. |id| is used to differentiate
+ // between updated versions of the same pool instance. The pool's id will
+ // change when it flushes, so it can use this |id| to discard sockets with
+ // mismatched ids.
virtual void ReleaseSocket(const std::string& group_name,
- ClientSocket* socket) = 0;
+ ClientSocket* socket,
+ int id) = 0;
+
+ // This flushes all state from the ClientSocketPool. This means that all
+ // idle and connecting sockets are discarded. Active sockets being
+ // held by ClientSocketPool clients will be discarded when released back to
+ // the pool.
+ virtual void Flush() = 0;
// Called to close any idle connections held by the connection manager.
virtual void CloseIdleSockets() = 0;
@@ -82,10 +103,23 @@
virtual LoadState GetLoadState(const std::string& group_name,
const ClientSocketHandle* handle) const = 0;
+ // Returns the maximum amount of time to wait before retrying a connect.
+ static const int kMaxConnectRetryIntervalMs = 250;
+
+ // The set of histograms specific to this pool. We can't use the standard
+ // UMA_HISTOGRAM_* macros because they are callsite static.
+ virtual scoped_refptr<ClientSocketPoolHistograms> histograms() const = 0;
+
+ static int unused_idle_socket_timeout();
+ static void set_unused_idle_socket_timeout(int timeout);
+
protected:
ClientSocketPool() {}
virtual ~ClientSocketPool() {}
+ // Return the connection timeout for this pool.
+ virtual base::TimeDelta ConnectionTimeout() const = 0;
+
private:
friend class base::RefCounted<ClientSocketPool>;
@@ -97,16 +131,27 @@
// will provide a definition of CheckIsValidSocketParamsForPool for the
// ClientSocketPool subtype and SocketParams pair. Trying to use a SocketParams
// type that has not been registered with the corresponding ClientSocketPool
-// subtype will result in a link time error stating that
-// CheckIsValidSocketParamsForPool with those template parameters is undefined.
+// subtype will result in a compile-time error.
template <typename PoolType, typename SocketParams>
-void CheckIsValidSocketParamsForPool();
+struct SocketParamTraits : public base::false_type {
+};
+
+template <typename PoolType, typename SocketParams>
+void CheckIsValidSocketParamsForPool() {
+ COMPILE_ASSERT(!base::is_pointer<scoped_refptr<SocketParams> >::value,
+ socket_params_cannot_be_pointer);
+ COMPILE_ASSERT((SocketParamTraits<PoolType,
+ scoped_refptr<SocketParams> >::value),
+ invalid_socket_params_for_pool);
+}
// Provides an empty definition for CheckIsValidSocketParamsForPool() which
// should be optimized out by the compiler.
-#define REGISTER_SOCKET_PARAMS_FOR_POOL(pool_type, socket_params) \
-template<> \
-inline void CheckIsValidSocketParamsForPool<pool_type, socket_params>() {}
+#define REGISTER_SOCKET_PARAMS_FOR_POOL(pool_type, socket_params) \
+template<> \
+struct SocketParamTraits<pool_type, scoped_refptr<socket_params> > \
+ : public base::true_type { \
+}
} // namespace net
diff --git a/net/socket/client_socket_pool_base.cc b/net/socket/client_socket_pool_base.cc
index 4a5c88c..e4a611b 100644
--- a/net/socket/client_socket_pool_base.cc
+++ b/net/socket/client_socket_pool_base.cc
@@ -1,14 +1,17 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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 "net/socket/client_socket_pool_base.h"
#include "base/compiler_specific.h"
+#include "base/format_macros.h"
#include "base/message_loop.h"
+#include "base/stats_counters.h"
#include "base/stl_util-inl.h"
+#include "base/string_util.h"
#include "base/time.h"
-#include "net/base/load_log.h"
+#include "net/base/net_log.h"
#include "net/base/net_errors.h"
#include "net/socket/client_socket_handle.h"
@@ -24,9 +27,6 @@
// some conditions. See http://crbug.com/4606.
const int kCleanupInterval = 10; // DO NOT INCREASE THIS TIMEOUT.
-// The maximum size of the ConnectJob's LoadLog.
-const int kMaxNumLoadLogEntries = 50;
-
} // namespace
namespace net {
@@ -34,69 +34,101 @@
ConnectJob::ConnectJob(const std::string& group_name,
base::TimeDelta timeout_duration,
Delegate* delegate,
- LoadLog* load_log)
+ const BoundNetLog& net_log)
: group_name_(group_name),
timeout_duration_(timeout_duration),
delegate_(delegate),
- load_log_(load_log) {
+ net_log_(net_log),
+ idle_(true) {
DCHECK(!group_name.empty());
DCHECK(delegate);
+ net_log.BeginEvent(NetLog::TYPE_SOCKET_POOL_CONNECT_JOB, NULL);
}
ConnectJob::~ConnectJob() {
- if (delegate_) {
- // If the delegate was not NULLed, then NotifyDelegateOfCompletion has
- // not been called yet (hence we are cancelling).
- LoadLog::AddEvent(load_log_, LoadLog::TYPE_CANCELLED);
- LoadLog::EndEvent(load_log_, LoadLog::TYPE_SOCKET_POOL_CONNECT_JOB);
- }
+ net_log().EndEvent(NetLog::TYPE_SOCKET_POOL_CONNECT_JOB, NULL);
}
int ConnectJob::Connect() {
if (timeout_duration_ != base::TimeDelta())
timer_.Start(timeout_duration_, this, &ConnectJob::OnTimeout);
- LoadLog::BeginEvent(load_log_, LoadLog::TYPE_SOCKET_POOL_CONNECT_JOB);
+ idle_ = false;
+
+ LogConnectStart();
int rv = ConnectInternal();
if (rv != ERR_IO_PENDING) {
+ LogConnectCompletion(rv);
delegate_ = NULL;
- LoadLog::EndEvent(load_log_, LoadLog::TYPE_SOCKET_POOL_CONNECT_JOB);
}
return rv;
}
+void ConnectJob::set_socket(ClientSocket* socket) {
+ if (socket) {
+ net_log().AddEvent(NetLog::TYPE_CONNECT_JOB_SET_SOCKET,
+ new NetLogSourceParameter("source_dependency",
+ socket->NetLog().source()));
+ }
+ socket_.reset(socket);
+}
+
void ConnectJob::NotifyDelegateOfCompletion(int rv) {
// The delegate will delete |this|.
Delegate *delegate = delegate_;
delegate_ = NULL;
- LoadLog::EndEvent(load_log_, LoadLog::TYPE_SOCKET_POOL_CONNECT_JOB);
-
+ LogConnectCompletion(rv);
delegate->OnConnectJobComplete(rv, this);
}
+void ConnectJob::ResetTimer(base::TimeDelta remaining_time) {
+ timer_.Stop();
+ timer_.Start(remaining_time, this, &ConnectJob::OnTimeout);
+}
+
+void ConnectJob::LogConnectStart() {
+ net_log().BeginEvent(NetLog::TYPE_SOCKET_POOL_CONNECT_JOB_CONNECT,
+ new NetLogStringParameter("group_name", group_name_));
+}
+
+void ConnectJob::LogConnectCompletion(int net_error) {
+ scoped_refptr<NetLog::EventParameters> params;
+ if (net_error != OK)
+ params = new NetLogIntegerParameter("net_error", net_error);
+ net_log().EndEvent(NetLog::TYPE_SOCKET_POOL_CONNECT_JOB_CONNECT, params);
+}
+
void ConnectJob::OnTimeout() {
// Make sure the socket is NULL before calling into |delegate|.
set_socket(NULL);
- LoadLog::AddEvent(load_log_,
- LoadLog::TYPE_SOCKET_POOL_CONNECT_JOB_TIMED_OUT);
+ net_log_.AddEvent(NetLog::TYPE_SOCKET_POOL_CONNECT_JOB_TIMED_OUT, NULL);
NotifyDelegateOfCompletion(ERR_TIMED_OUT);
}
namespace internal {
+ClientSocketPoolBaseHelper::Request::Request(
+ ClientSocketHandle* handle,
+ CompletionCallback* callback,
+ RequestPriority priority,
+ const BoundNetLog& net_log)
+ : handle_(handle), callback_(callback), priority_(priority),
+ net_log_(net_log) {}
+
+ClientSocketPoolBaseHelper::Request::~Request() {}
+
ClientSocketPoolBaseHelper::ClientSocketPoolBaseHelper(
int max_sockets,
int max_sockets_per_group,
base::TimeDelta unused_idle_socket_timeout,
base::TimeDelta used_idle_socket_timeout,
- ConnectJobFactory* connect_job_factory,
- NetworkChangeNotifier* network_change_notifier)
+ ConnectJobFactory* connect_job_factory)
: idle_socket_count_(0),
connecting_socket_count_(0),
handed_out_socket_count_(0),
@@ -104,14 +136,14 @@
max_sockets_per_group_(max_sockets_per_group),
unused_idle_socket_timeout_(unused_idle_socket_timeout),
used_idle_socket_timeout_(used_idle_socket_timeout),
- may_have_stalled_group_(false),
connect_job_factory_(connect_job_factory),
- network_change_notifier_(network_change_notifier) {
+ backup_jobs_enabled_(false),
+ ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)),
+ pool_generation_number_(0) {
DCHECK_LE(0, max_sockets_per_group);
DCHECK_LE(max_sockets_per_group, max_sockets);
- if (network_change_notifier_)
- network_change_notifier_->AddObserver(this);
+ NetworkChangeNotifier::AddObserver(this);
}
ClientSocketPoolBaseHelper::~ClientSocketPoolBaseHelper() {
@@ -121,11 +153,11 @@
// sockets or pending requests. They should have all been cleaned up prior
// to the manager being destroyed.
CloseIdleSockets();
- DCHECK(group_map_.empty());
+ CHECK(group_map_.empty());
+ DCHECK(pending_callback_map_.empty());
DCHECK_EQ(0, connecting_socket_count_);
- if (network_change_notifier_)
- network_change_notifier_->RemoveObserver(this);
+ NetworkChangeNotifier::RemoveObserver(this);
}
// InsertRequestIntoQueue inserts the request into the queue based on
@@ -135,9 +167,6 @@
// static
void ClientSocketPoolBaseHelper::InsertRequestIntoQueue(
const Request* r, RequestQueue* pending_requests) {
- LoadLog::BeginEvent(r->load_log(),
- LoadLog::TYPE_SOCKET_POOL_WAITING_IN_QUEUE);
-
RequestQueue::iterator it = pending_requests->begin();
while (it != pending_requests->end() && r->priority() >= (*it)->priority())
++it;
@@ -149,10 +178,6 @@
ClientSocketPoolBaseHelper::RemoveRequestFromQueue(
RequestQueue::iterator it, RequestQueue* pending_requests) {
const Request* req = *it;
-
- LoadLog::EndEvent(req->load_log(),
- LoadLog::TYPE_SOCKET_POOL_WAITING_IN_QUEUE);
-
pending_requests->erase(it);
return req;
}
@@ -160,6 +185,23 @@
int ClientSocketPoolBaseHelper::RequestSocket(
const std::string& group_name,
const Request* request) {
+ request->net_log().BeginEvent(NetLog::TYPE_SOCKET_POOL, NULL);
+ Group& group = group_map_[group_name];
+
+ int rv = RequestSocketInternal(group_name, request);
+ if (rv != ERR_IO_PENDING) {
+ request->net_log().EndEvent(NetLog::TYPE_SOCKET_POOL, NULL);
+ CHECK(!request->handle()->is_initialized());
+ delete request;
+ } else {
+ InsertRequestIntoQueue(request, &group.pending_requests);
+ }
+ return rv;
+}
+
+int ClientSocketPoolBaseHelper::RequestSocketInternal(
+ const std::string& group_name,
+ const Request* request) {
DCHECK_GE(request->priority(), 0);
CompletionCallback* const callback = request->callback();
CHECK(callback);
@@ -167,77 +209,167 @@
CHECK(handle);
Group& group = group_map_[group_name];
- // Can we make another active socket now?
- if (ReachedMaxSocketsLimit() ||
- !group.HasAvailableSocketSlot(max_sockets_per_group_)) {
- if (ReachedMaxSocketsLimit()) {
- // We could check if we really have a stalled group here, but it requires
- // a scan of all groups, so just flip a flag here, and do the check later.
- may_have_stalled_group_ = true;
+ // Try to reuse a socket.
+ if (AssignIdleSocketToGroup(&group, request))
+ return OK;
- LoadLog::AddEvent(request->load_log(),
- LoadLog::TYPE_SOCKET_POOL_STALLED_MAX_SOCKETS);
- } else {
- LoadLog::AddEvent(request->load_log(),
- LoadLog::TYPE_SOCKET_POOL_STALLED_MAX_SOCKETS_PER_GROUP);
- }
- InsertRequestIntoQueue(request, &group.pending_requests);
+ // Can we make another active socket now?
+ if (!group.HasAvailableSocketSlot(max_sockets_per_group_)) {
+ request->net_log().AddEvent(
+ NetLog::TYPE_SOCKET_POOL_STALLED_MAX_SOCKETS_PER_GROUP, NULL);
return ERR_IO_PENDING;
}
- // Try to reuse a socket.
- while (!group.idle_sockets.empty()) {
- IdleSocket idle_socket = group.idle_sockets.back();
- group.idle_sockets.pop_back();
+ if (ReachedMaxSocketsLimit()) {
+ if (idle_socket_count() > 0) {
+ CloseOneIdleSocket();
+ } else {
+ // We could check if we really have a stalled group here, but it requires
+ // a scan of all groups, so just flip a flag here, and do the check later.
+ request->net_log().AddEvent(
+ NetLog::TYPE_SOCKET_POOL_STALLED_MAX_SOCKETS, NULL);
+ return ERR_IO_PENDING;
+ }
+ }
+
+ // We couldn't find a socket to reuse, so allocate and connect a new one.
+ scoped_ptr<ConnectJob> connect_job(
+ connect_job_factory_->NewConnectJob(group_name, *request, this));
+
+ int rv = connect_job->Connect();
+ if (rv == OK) {
+ LogBoundConnectJobToRequest(connect_job->net_log().source(), request);
+ HandOutSocket(connect_job->ReleaseSocket(), false /* not reused */,
+ handle, base::TimeDelta(), &group, request->net_log());
+ } else if (rv == ERR_IO_PENDING) {
+ // If we don't have any sockets in this group, set a timer for potentially
+ // creating a new one. If the SYN is lost, this backup socket may complete
+ // before the slow socket, improving end user latency.
+ if (group.IsEmpty() && !group.backup_job && backup_jobs_enabled_) {
+ group.backup_job = connect_job_factory_->NewConnectJob(group_name,
+ *request,
+ this);
+ StartBackupSocketTimer(group_name);
+ }
+
+ connecting_socket_count_++;
+
+ ConnectJob* job = connect_job.release();
+ group.jobs.insert(job);
+ } else {
+ LogBoundConnectJobToRequest(connect_job->net_log().source(), request);
+ connect_job->GetAdditionalErrorState(handle);
+ ClientSocket* error_socket = connect_job->ReleaseSocket();
+ if (error_socket) {
+ HandOutSocket(error_socket, false /* not reused */, handle,
+ base::TimeDelta(), &group, request->net_log());
+ } else if (group.IsEmpty()) {
+ group_map_.erase(group_name);
+ }
+ }
+
+ return rv;
+}
+
+bool ClientSocketPoolBaseHelper::AssignIdleSocketToGroup(
+ Group* group, const Request* request) {
+ // Iterate through the list of idle sockets until we find one or exhaust
+ // the list.
+ while (!group->idle_sockets.empty()) {
+ IdleSocket idle_socket = group->idle_sockets.back();
+ group->idle_sockets.pop_back();
DecrementIdleCount();
if (idle_socket.socket->IsConnectedAndIdle()) {
// We found one we can reuse!
base::TimeDelta idle_time =
base::TimeTicks::Now() - idle_socket.start_time;
HandOutSocket(
- idle_socket.socket, idle_socket.used, handle, idle_time, &group);
- return OK;
+ idle_socket.socket, idle_socket.used, request->handle(), idle_time,
+ group, request->net_log());
+ return true;
}
delete idle_socket.socket;
}
+ return false;
+}
- // See if we already have enough connect jobs or sockets that will be released
- // soon.
- if (group.HasReleasingSockets()) {
- InsertRequestIntoQueue(request, &group.pending_requests);
- return ERR_IO_PENDING;
+// static
+void ClientSocketPoolBaseHelper::LogBoundConnectJobToRequest(
+ const NetLog::Source& connect_job_source, const Request* request) {
+ request->net_log().AddEvent(
+ NetLog::TYPE_SOCKET_POOL_BOUND_TO_CONNECT_JOB,
+ new NetLogSourceParameter("source_dependency", connect_job_source));
+}
+
+void ClientSocketPoolBaseHelper::StartBackupSocketTimer(
+ const std::string& group_name) {
+ CHECK(ContainsKey(group_map_, group_name));
+ Group& group = group_map_[group_name];
+
+ // Only allow one timer pending to create a backup socket.
+ if (group.backup_task)
+ return;
+
+ group.backup_task = method_factory_.NewRunnableMethod(
+ &ClientSocketPoolBaseHelper::OnBackupSocketTimerFired, group_name);
+ MessageLoop::current()->PostDelayedTask(FROM_HERE, group.backup_task,
+ ConnectRetryIntervalMs());
+}
+
+void ClientSocketPoolBaseHelper::OnBackupSocketTimerFired(
+ const std::string& group_name) {
+ CHECK(ContainsKey(group_map_, group_name));
+
+ Group& group = group_map_[group_name];
+
+ CHECK(group.backup_task);
+ group.backup_task = NULL;
+
+ CHECK(group.backup_job);
+
+ // If there are no more jobs pending, there is no work to do.
+ // If we've done our cleanups correctly, this should not happen.
+ if (group.jobs.empty()) {
+ NOTREACHED();
+ return;
}
- // We couldn't find a socket to reuse, so allocate and connect a new one.
- scoped_refptr<LoadLog> job_load_log = new LoadLog(kMaxNumLoadLogEntries);
-
- scoped_ptr<ConnectJob> connect_job(
- connect_job_factory_->NewConnectJob(group_name, *request, this,
- job_load_log));
-
- int rv = connect_job->Connect();
-
- if (rv != ERR_IO_PENDING && request->load_log())
- request->load_log()->Append(job_load_log);
-
- if (rv == OK) {
- HandOutSocket(connect_job->ReleaseSocket(), false /* not reused */,
- handle, base::TimeDelta(), &group);
- } else if (rv == ERR_IO_PENDING) {
- connecting_socket_count_++;
-
- ConnectJob* job = connect_job.release();
- InsertRequestIntoQueue(request, &group.pending_requests);
- group.jobs.insert(job);
- } else if (group.IsEmpty()) {
- group_map_.erase(group_name);
+ // If our backup job is waiting on DNS, or if we can't create any sockets
+ // right now due to limits, just reset the timer.
+ if (ReachedMaxSocketsLimit() ||
+ !group.HasAvailableSocketSlot(max_sockets_per_group_) ||
+ (*group.jobs.begin())->GetLoadState() == LOAD_STATE_RESOLVING_HOST) {
+ StartBackupSocketTimer(group_name);
+ return;
}
- return rv;
+ group.backup_job->net_log().AddEvent(NetLog::TYPE_SOCKET_BACKUP_CREATED,
+ NULL);
+ SIMPLE_STATS_COUNTER("socket.backup_created");
+ int rv = group.backup_job->Connect();
+ connecting_socket_count_++;
+ group.jobs.insert(group.backup_job);
+ ConnectJob* job = group.backup_job;
+ group.backup_job = NULL;
+ if (rv != ERR_IO_PENDING)
+ OnConnectJobComplete(rv, job);
}
void ClientSocketPoolBaseHelper::CancelRequest(
- const std::string& group_name, const ClientSocketHandle* handle) {
+ const std::string& group_name, ClientSocketHandle* handle) {
+ PendingCallbackMap::iterator callback_it = pending_callback_map_.find(handle);
+ if (callback_it != pending_callback_map_.end()) {
+ int result = callback_it->second.result;
+ pending_callback_map_.erase(callback_it);
+ ClientSocket* socket = handle->release_socket();
+ if (socket) {
+ if (result != OK)
+ socket->Disconnect();
+ ReleaseSocket(handle->group_name(), socket, handle->id());
+ }
+ return;
+ }
+
CHECK(ContainsKey(group_map_, group_name));
Group& group = group_map_[group_name];
@@ -247,31 +379,20 @@
for (; it != group.pending_requests.end(); ++it) {
if ((*it)->handle() == handle) {
const Request* req = RemoveRequestFromQueue(it, &group.pending_requests);
- LoadLog::AddEvent(req->load_log(), LoadLog::TYPE_CANCELLED);
- LoadLog::EndEvent(req->load_log(), LoadLog::TYPE_SOCKET_POOL);
+ req->net_log().AddEvent(NetLog::TYPE_CANCELLED, NULL);
+ req->net_log().EndEvent(NetLog::TYPE_SOCKET_POOL, NULL);
delete req;
- if (group.jobs.size() > group.pending_requests.size() + 1) {
- // TODO(willchan): Cancel the job in the earliest LoadState.
+
+ // We let the job run, unless we're at the socket limit.
+ if (group.jobs.size() && ReachedMaxSocketsLimit()) {
RemoveConnectJob(*group.jobs.begin(), &group);
- OnAvailableSocketSlot(group_name, &group);
+ CheckForStalledSocketGroups();
}
- return;
+ break;
}
}
}
-void ClientSocketPoolBaseHelper::ReleaseSocket(const std::string& group_name,
- ClientSocket* socket) {
- Group& group = group_map_[group_name];
- group.num_releasing_sockets++;
- DCHECK_LE(group.num_releasing_sockets, group.active_socket_count);
- // Run this asynchronously to allow the caller to finish before we let
- // another to begin doing work. This also avoids nasty recursion issues.
- // NOTE: We cannot refer to the handle argument after this method returns.
- MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
- this, &ClientSocketPoolBaseHelper::DoReleaseSocket, group_name, socket));
-}
-
void ClientSocketPoolBaseHelper::CloseIdleSockets() {
CleanupIdleSockets(true);
}
@@ -287,6 +408,9 @@
LoadState ClientSocketPoolBaseHelper::GetLoadState(
const std::string& group_name,
const ClientSocketHandle* handle) const {
+ if (ContainsKey(pending_callback_map_, handle))
+ return LOAD_STATE_CONNECTING;
+
if (!ContainsKey(group_map_, group_name)) {
NOTREACHED() << "ClientSocketPool does not contain group: " << group_name
<< " for handle: " << handle;
@@ -372,93 +496,88 @@
timer_.Stop();
}
-void ClientSocketPoolBaseHelper::DoReleaseSocket(const std::string& group_name,
- ClientSocket* socket) {
+void ClientSocketPoolBaseHelper::ReleaseSocket(const std::string& group_name,
+ ClientSocket* socket,
+ int id) {
GroupMap::iterator i = group_map_.find(group_name);
CHECK(i != group_map_.end());
Group& group = i->second;
- group.num_releasing_sockets--;
- DCHECK_GE(group.num_releasing_sockets, 0);
-
- CHECK(handed_out_socket_count_ > 0);
+ CHECK_GT(handed_out_socket_count_, 0);
handed_out_socket_count_--;
- CHECK(group.active_socket_count > 0);
+ CHECK_GT(group.active_socket_count, 0);
group.active_socket_count--;
- const bool can_reuse = socket->IsConnectedAndIdle();
+ const bool can_reuse = socket->IsConnectedAndIdle() &&
+ id == pool_generation_number_;
if (can_reuse) {
+ // Add it to the idle list.
AddIdleSocket(socket, true /* used socket */, &group);
+ OnAvailableSocketSlot(group_name, &group);
} else {
delete socket;
}
- const bool more_releasing_sockets = group.num_releasing_sockets > 0;
+ CheckForStalledSocketGroups();
+}
- OnAvailableSocketSlot(group_name, &group);
-
- // If there are no more releasing sockets, then we might have to process
- // multiple available socket slots, since we stalled their processing until
- // all sockets have been released.
- if (more_releasing_sockets)
+void ClientSocketPoolBaseHelper::CheckForStalledSocketGroups() {
+ // If we have idle sockets, see if we can give one to the top-stalled group.
+ std::string top_group_name;
+ Group* top_group = NULL;
+ if (!FindTopStalledGroup(&top_group, &top_group_name))
return;
- while (true) {
- // We can't activate more sockets since we're already at our global limit.
- if (ReachedMaxSocketsLimit())
+ if (ReachedMaxSocketsLimit()) {
+ if (idle_socket_count() > 0) {
+ CloseOneIdleSocket();
+ } else {
+ // We can't activate more sockets since we're already at our global
+ // limit.
return;
-
- // |group| might now be deleted.
- i = group_map_.find(group_name);
- if (i == group_map_.end())
- return;
-
- group = i->second;
-
- // If we already have enough ConnectJobs to satisfy the pending requests,
- // don't bother starting up more.
- if (group.pending_requests.size() <= group.jobs.size())
- return;
-
- if (!group.HasAvailableSocketSlot(max_sockets_per_group_))
- return;
-
- OnAvailableSocketSlot(group_name, &group);
+ }
}
+
+ // Note: we don't loop on waking stalled groups. If the stalled group is at
+ // its limit, may be left with other stalled groups that could be
+ // woken. This isn't optimal, but there is no starvation, so to avoid
+ // the looping we leave it at this.
+ OnAvailableSocketSlot(top_group_name, top_group);
}
// Search for the highest priority pending request, amongst the groups that
// are not at the |max_sockets_per_group_| limit. Note: for requests with
// the same priority, the winner is based on group hash ordering (and not
// insertion order).
-int ClientSocketPoolBaseHelper::FindTopStalledGroup(Group** group,
- std::string* group_name) {
+bool ClientSocketPoolBaseHelper::FindTopStalledGroup(Group** group,
+ std::string* group_name) {
Group* top_group = NULL;
const std::string* top_group_name = NULL;
- int stalled_group_count = 0;
+ bool has_stalled_group = false;
for (GroupMap::iterator i = group_map_.begin();
i != group_map_.end(); ++i) {
Group& group = i->second;
const RequestQueue& queue = group.pending_requests;
if (queue.empty())
continue;
- bool has_slot = group.HasAvailableSocketSlot(max_sockets_per_group_);
- if (has_slot)
- stalled_group_count++;
- bool has_higher_priority = !top_group ||
- group.TopPendingPriority() < top_group->TopPendingPriority();
- if (has_slot && has_higher_priority) {
- top_group = &group;
- top_group_name = &i->first;
+ if (group.IsStalled(max_sockets_per_group_)) {
+ has_stalled_group = true;
+ bool has_higher_priority = !top_group ||
+ group.TopPendingPriority() < top_group->TopPendingPriority();
+ if (has_higher_priority) {
+ top_group = &group;
+ top_group_name = &i->first;
+ }
}
}
+
if (top_group) {
*group = top_group;
*group_name = *top_group_name;
}
- return stalled_group_count;
+ return has_stalled_group;
}
void ClientSocketPoolBaseHelper::OnConnectJobComplete(
@@ -471,104 +590,104 @@
scoped_ptr<ClientSocket> socket(job->ReleaseSocket());
- scoped_refptr<LoadLog> job_load_log(job->load_log());
- RemoveConnectJob(job, &group);
-
- LoadLog::EndEvent(job_load_log, LoadLog::TYPE_SOCKET_POOL);
+ BoundNetLog job_log = job->net_log();
if (result == OK) {
DCHECK(socket.get());
+ RemoveConnectJob(job, &group);
if (!group.pending_requests.empty()) {
scoped_ptr<const Request> r(RemoveRequestFromQueue(
group.pending_requests.begin(), &group.pending_requests));
- if (r->load_log())
- r->load_log()->Append(job_load_log);
+ LogBoundConnectJobToRequest(job_log.source(), r.get());
HandOutSocket(
socket.release(), false /* unused socket */, r->handle(),
- base::TimeDelta(), &group);
- r->callback()->Run(result);
+ base::TimeDelta(), &group, r->net_log());
+ r->net_log().EndEvent(NetLog::TYPE_SOCKET_POOL, NULL);
+ InvokeUserCallbackLater(r->handle(), r->callback(), result);
} else {
AddIdleSocket(socket.release(), false /* unused socket */, &group);
OnAvailableSocketSlot(group_name, &group);
+ CheckForStalledSocketGroups();
}
} else {
- DCHECK(!socket.get());
+ // If we got a socket, it must contain error information so pass that
+ // up so that the caller can retrieve it.
+ bool handed_out_socket = false;
if (!group.pending_requests.empty()) {
scoped_ptr<const Request> r(RemoveRequestFromQueue(
group.pending_requests.begin(), &group.pending_requests));
- if (r->load_log())
- r->load_log()->Append(job_load_log);
- r->callback()->Run(result);
+ LogBoundConnectJobToRequest(job_log.source(), r.get());
+ job->GetAdditionalErrorState(r->handle());
+ RemoveConnectJob(job, &group);
+ if (socket.get()) {
+ handed_out_socket = true;
+ HandOutSocket(socket.release(), false /* unused socket */, r->handle(),
+ base::TimeDelta(), &group, r->net_log());
+ }
+ r->net_log().EndEvent(NetLog::TYPE_SOCKET_POOL,
+ new NetLogIntegerParameter("net_error", result));
+ InvokeUserCallbackLater(r->handle(), r->callback(), result);
+ } else {
+ RemoveConnectJob(job, &group);
}
- MaybeOnAvailableSocketSlot(group_name);
+ if (!handed_out_socket) {
+ OnAvailableSocketSlot(group_name, &group);
+ CheckForStalledSocketGroups();
+ }
}
}
void ClientSocketPoolBaseHelper::OnIPAddressChanged() {
+ Flush();
+}
+
+void ClientSocketPoolBaseHelper::Flush() {
+ pool_generation_number_++;
+ CancelAllConnectJobs();
CloseIdleSockets();
}
-void ClientSocketPoolBaseHelper::RemoveConnectJob(const ConnectJob *job,
+void ClientSocketPoolBaseHelper::RemoveConnectJob(const ConnectJob* job,
Group* group) {
- CHECK(connecting_socket_count_ > 0);
+ CHECK_GT(connecting_socket_count_, 0);
connecting_socket_count_--;
+ DCHECK(group);
+ DCHECK(ContainsKey(group->jobs, job));
+ group->jobs.erase(job);
+
+ // If we've got no more jobs for this group, then we no longer need a
+ // backup job either.
+ if (group->jobs.empty())
+ group->CleanupBackupJob();
+
DCHECK(job);
delete job;
-
- if (group) {
- DCHECK(ContainsKey(group->jobs, job));
- group->jobs.erase(job);
- }
-}
-
-void ClientSocketPoolBaseHelper::MaybeOnAvailableSocketSlot(
- const std::string& group_name) {
- GroupMap::iterator it = group_map_.find(group_name);
- if (it != group_map_.end()) {
- Group& group = it->second;
- if (group.HasAvailableSocketSlot(max_sockets_per_group_))
- OnAvailableSocketSlot(group_name, &group);
- }
}
void ClientSocketPoolBaseHelper::OnAvailableSocketSlot(
const std::string& group_name, Group* group) {
- if (may_have_stalled_group_) {
- std::string top_group_name;
- Group* top_group = NULL;
- int stalled_group_count = FindTopStalledGroup(&top_group, &top_group_name);
- if (stalled_group_count <= 1)
- may_have_stalled_group_ = false;
- if (stalled_group_count >= 1)
- ProcessPendingRequest(top_group_name, top_group);
- } else if (!group->pending_requests.empty()) {
+ if (!group->pending_requests.empty())
ProcessPendingRequest(group_name, group);
- // |group| may no longer be valid after this point. Be careful not to
- // access it again.
- } else if (group->IsEmpty()) {
- // Delete |group| if no longer needed. |group| will no longer be valid.
+
+ if (group->IsEmpty())
group_map_.erase(group_name);
- }
}
void ClientSocketPoolBaseHelper::ProcessPendingRequest(
const std::string& group_name, Group* group) {
- scoped_ptr<const Request> r(RemoveRequestFromQueue(
- group->pending_requests.begin(), &group->pending_requests));
-
- int rv = RequestSocket(group_name, r.get());
-
+ int rv = RequestSocketInternal(group_name,
+ *group->pending_requests.begin());
if (rv != ERR_IO_PENDING) {
- LoadLog::EndEvent(r->load_log(), LoadLog::TYPE_SOCKET_POOL);
- r->callback()->Run(rv);
- if (rv != OK) {
- // |group| may be invalid after the callback, we need to search
- // |group_map_| again.
- MaybeOnAvailableSocketSlot(group_name);
- }
- } else {
- r.release();
+ scoped_ptr<const Request> request(RemoveRequestFromQueue(
+ group->pending_requests.begin(), &group->pending_requests));
+
+ scoped_refptr<NetLog::EventParameters> params;
+ if (rv != OK)
+ params = new NetLogIntegerParameter("net_error", rv);
+ request->net_log().EndEvent(NetLog::TYPE_SOCKET_POOL, params);
+ InvokeUserCallbackLater(
+ request->handle(), request->callback(), rv);
}
}
@@ -577,11 +696,24 @@
bool reused,
ClientSocketHandle* handle,
base::TimeDelta idle_time,
- Group* group) {
+ Group* group,
+ const BoundNetLog& net_log) {
DCHECK(socket);
handle->set_socket(socket);
handle->set_is_reused(reused);
handle->set_idle_time(idle_time);
+ handle->set_pool_id(pool_generation_number_);
+
+ if (reused) {
+ net_log.AddEvent(
+ NetLog::TYPE_SOCKET_POOL_REUSED_AN_EXISTING_SOCKET,
+ new NetLogIntegerParameter(
+ "idle_ms", static_cast<int>(idle_time.InMilliseconds())));
+ }
+
+ net_log.AddEvent(NetLog::TYPE_SOCKET_POOL_BOUND_TO_SOCKET,
+ new NetLogSourceParameter(
+ "source_dependency", socket->NetLog().source()));
handed_out_socket_count_++;
group->active_socket_count++;
@@ -605,6 +737,11 @@
connecting_socket_count_ -= group.jobs.size();
STLDeleteElements(&group.jobs);
+ if (group.backup_task) {
+ group.backup_task->Cancel();
+ group.backup_task = NULL;
+ }
+
// Delete group if no longer needed.
if (group.IsEmpty()) {
group_map_.erase(i++);
@@ -616,9 +753,61 @@
bool ClientSocketPoolBaseHelper::ReachedMaxSocketsLimit() const {
// Each connecting socket will eventually connect and be handed out.
- int total = handed_out_socket_count_ + connecting_socket_count_;
+ int total = handed_out_socket_count_ + connecting_socket_count_ +
+ idle_socket_count();
DCHECK_LE(total, max_sockets_);
- return total == max_sockets_;
+ if (total < max_sockets_)
+ return false;
+ LOG(WARNING) << "ReachedMaxSocketsLimit: " << total << "/" << max_sockets_;
+ return true;
+}
+
+void ClientSocketPoolBaseHelper::CloseOneIdleSocket() {
+ CHECK_GT(idle_socket_count(), 0);
+
+ for (GroupMap::iterator i = group_map_.begin(); i != group_map_.end(); ++i) {
+ Group& group = i->second;
+
+ if (!group.idle_sockets.empty()) {
+ std::deque<IdleSocket>::iterator j = group.idle_sockets.begin();
+ delete j->socket;
+ group.idle_sockets.erase(j);
+ DecrementIdleCount();
+ if (group.IsEmpty())
+ group_map_.erase(i);
+
+ return;
+ }
+ }
+
+ LOG(DFATAL) << "No idle socket found to close!.";
+}
+
+void ClientSocketPoolBaseHelper::InvokeUserCallbackLater(
+ ClientSocketHandle* handle, CompletionCallback* callback, int rv) {
+ CHECK(!ContainsKey(pending_callback_map_, handle));
+ pending_callback_map_[handle] = CallbackResultPair(callback, rv);
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(
+ this,
+ &ClientSocketPoolBaseHelper::InvokeUserCallback,
+ handle));
+}
+
+void ClientSocketPoolBaseHelper::InvokeUserCallback(
+ ClientSocketHandle* handle) {
+ PendingCallbackMap::iterator it = pending_callback_map_.find(handle);
+
+ // Exit if the request has already been cancelled.
+ if (it == pending_callback_map_.end())
+ return;
+
+ CHECK(!handle->is_initialized());
+ CompletionCallback* callback = it->second.callback;
+ int result = it->second.result;
+ pending_callback_map_.erase(it);
+ callback->Run(result);
}
} // namespace internal
diff --git a/net/socket/client_socket_pool_base.h b/net/socket/client_socket_pool_base.h
index d4e9deb..ef4b115 100644
--- a/net/socket/client_socket_pool_base.h
+++ b/net/socket/client_socket_pool_base.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
//
@@ -28,15 +28,17 @@
#include <string>
#include "base/basictypes.h"
+#include "base/compiler_specific.h"
#include "base/ref_counted.h"
#include "base/scoped_ptr.h"
+#include "base/task.h"
#include "base/time.h"
#include "base/timer.h"
#include "net/base/address_list.h"
#include "net/base/completion_callback.h"
-#include "net/base/load_log.h"
#include "net/base/load_states.h"
#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
#include "net/base/network_change_notifier.h"
#include "net/base/request_priority.h"
#include "net/socket/client_socket.h"
@@ -67,12 +69,12 @@
ConnectJob(const std::string& group_name,
base::TimeDelta timeout_duration,
Delegate* delegate,
- LoadLog* load_log);
+ const BoundNetLog& net_log);
virtual ~ConnectJob();
// Accessors
const std::string& group_name() const { return group_name_; }
- LoadLog* load_log() { return load_log_; }
+ const BoundNetLog& net_log() { return net_log_; }
// Releases |socket_| to the client. On connection error, this should return
// NULL.
@@ -88,14 +90,23 @@
virtual LoadState GetLoadState() const = 0;
+ // If Connect returns an error (or OnConnectJobComplete reports an error
+ // result) this method will be called, allowing the pool to add
+ // additional error state to the ClientSocketHandle (post late-binding).
+ virtual void GetAdditionalErrorState(ClientSocketHandle* handle) {}
+
protected:
- void set_socket(ClientSocket* socket) { socket_.reset(socket); }
+ void set_socket(ClientSocket* socket);
ClientSocket* socket() { return socket_.get(); }
void NotifyDelegateOfCompletion(int rv);
+ void ResetTimer(base::TimeDelta remainingTime);
private:
virtual int ConnectInternal() = 0;
+ void LogConnectStart();
+ void LogConnectCompletion(int net_error);
+
// Alerts the delegate that the ConnectJob has timed out.
void OnTimeout();
@@ -105,7 +116,9 @@
base::OneShotTimer<ConnectJob> timer_;
Delegate* delegate_;
scoped_ptr<ClientSocket> socket_;
- scoped_refptr<LoadLog> load_log_;
+ BoundNetLog net_log_;
+ // A ConnectJob is idle until Connect() has been called.
+ bool idle_;
DISALLOW_COPY_AND_ASSIGN(ConnectJob);
};
@@ -127,22 +140,20 @@
Request(ClientSocketHandle* handle,
CompletionCallback* callback,
RequestPriority priority,
- LoadLog* load_log)
- : handle_(handle), callback_(callback), priority_(priority),
- load_log_(load_log) {}
+ const BoundNetLog& net_log);
- virtual ~Request() {}
+ virtual ~Request();
ClientSocketHandle* handle() const { return handle_; }
CompletionCallback* callback() const { return callback_; }
RequestPriority priority() const { return priority_; }
- LoadLog* load_log() const { return load_log_.get(); }
+ const BoundNetLog& net_log() const { return net_log_; }
private:
ClientSocketHandle* const handle_;
CompletionCallback* const callback_;
const RequestPriority priority_;
- const scoped_refptr<LoadLog> load_log_;
+ BoundNetLog net_log_;
DISALLOW_COPY_AND_ASSIGN(Request);
};
@@ -155,8 +166,9 @@
virtual ConnectJob* NewConnectJob(
const std::string& group_name,
const Request& request,
- ConnectJob::Delegate* delegate,
- LoadLog* load_log) const = 0;
+ ConnectJob::Delegate* delegate) const = 0;
+
+ virtual base::TimeDelta ConnectionTimeout() const = 0;
private:
DISALLOW_COPY_AND_ASSIGN(ConnectJobFactory);
@@ -167,21 +179,24 @@
int max_sockets_per_group,
base::TimeDelta unused_idle_socket_timeout,
base::TimeDelta used_idle_socket_timeout,
- ConnectJobFactory* connect_job_factory,
- NetworkChangeNotifier* network_change_notifier);
+ ConnectJobFactory* connect_job_factory);
// See ClientSocketPool::RequestSocket for documentation on this function.
- // Note that |request| must be heap allocated. If ERR_IO_PENDING is returned,
- // then ClientSocketPoolBaseHelper takes ownership of |request|.
+ // ClientSocketPoolBaseHelper takes ownership of |request|, which must be
+ // heap allocated.
int RequestSocket(const std::string& group_name, const Request* request);
// See ClientSocketPool::CancelRequest for documentation on this function.
void CancelRequest(const std::string& group_name,
- const ClientSocketHandle* handle);
+ ClientSocketHandle* handle);
// See ClientSocketPool::ReleaseSocket for documentation on this function.
void ReleaseSocket(const std::string& group_name,
- ClientSocket* socket);
+ ClientSocket* socket,
+ int id);
+
+ // See ClientSocketPool::Flush for documentation on this function.
+ void Flush();
// See ClientSocketPool::CloseIdleSockets for documentation on this function.
void CloseIdleSockets();
@@ -199,15 +214,18 @@
LoadState GetLoadState(const std::string& group_name,
const ClientSocketHandle* handle) const;
+ int ConnectRetryIntervalMs() const {
+ // TODO(mbelshe): Make this tuned dynamically based on measured RTT.
+ // For now, just use the max retry interval.
+ return ClientSocketPool::kMaxConnectRetryIntervalMs;
+ }
+
// ConnectJob::Delegate methods:
virtual void OnConnectJobComplete(int result, ConnectJob* job);
// NetworkChangeNotifier::Observer methods:
virtual void OnIPAddressChanged();
- // For testing.
- bool may_have_stalled_group() const { return may_have_stalled_group_; }
-
int NumConnectJobsInGroup(const std::string& group_name) const {
return group_map_.find(group_name)->second.jobs.size();
}
@@ -216,11 +234,15 @@
// sockets that timed out or can't be reused. Made public for testing.
void CleanupIdleSockets(bool force);
+ base::TimeDelta ConnectionTimeout() const {
+ return connect_job_factory_->ConnectionTimeout();
+ }
+
+ void EnableBackupJobs() { backup_jobs_enabled_ = true; }
+
private:
friend class base::RefCounted<ClientSocketPoolBaseHelper>;
- ~ClientSocketPoolBaseHelper();
-
// Entry for a persistent socket which became idle at time |start_time|.
struct IdleSocket {
IdleSocket() : socket(NULL), used(false) {}
@@ -244,14 +266,17 @@
// A Group is allocated per group_name when there are idle sockets or pending
// requests. Otherwise, the Group object is removed from the map.
- // |active_socket_count| tracks the number of sockets held by clients. Of
- // this number of sockets held by clients, some of them may be released soon,
- // since ReleaseSocket() was called of them, but the DoReleaseSocket() task
- // has not run yet for them. |num_releasing_sockets| tracks these values,
- // which is useful for not starting up new ConnectJobs when sockets may become
- // available really soon.
+ // |active_socket_count| tracks the number of sockets held by clients.
struct Group {
- Group() : active_socket_count(0), num_releasing_sockets(0) {}
+ Group()
+ : active_socket_count(0),
+ backup_job(NULL),
+ backup_task(NULL) {
+ }
+
+ ~Group() {
+ CleanupBackupJob();
+ }
bool IsEmpty() const {
return active_socket_count == 0 && idle_sockets.empty() && jobs.empty() &&
@@ -263,26 +288,53 @@
max_sockets_per_group;
}
- bool HasReleasingSockets() const {
- return num_releasing_sockets > 0;
+ bool IsStalled(int max_sockets_per_group) const {
+ return HasAvailableSocketSlot(max_sockets_per_group) &&
+ pending_requests.size() > jobs.size();
}
RequestPriority TopPendingPriority() const {
return pending_requests.front()->priority();
}
+ void CleanupBackupJob() {
+ if (backup_job) {
+ delete backup_job;
+ backup_job = NULL;
+ }
+ if (backup_task) {
+ backup_task->Cancel();
+ backup_task = NULL;
+ }
+ }
+
std::deque<IdleSocket> idle_sockets;
std::set<const ConnectJob*> jobs;
RequestQueue pending_requests;
int active_socket_count; // number of active sockets used by clients
- // Number of sockets being released within one loop through the MessageLoop.
- int num_releasing_sockets;
+ // A backup job in case the connect for this group takes too long.
+ ConnectJob* backup_job;
+ CancelableTask* backup_task;
};
typedef std::map<std::string, Group> GroupMap;
typedef std::set<const ConnectJob*> ConnectJobSet;
+ struct CallbackResultPair {
+ CallbackResultPair() : callback(NULL), result(OK) {}
+ CallbackResultPair(CompletionCallback* callback_in, int result_in)
+ : callback(callback_in), result(result_in) {}
+
+ CompletionCallback* callback;
+ int result;
+ };
+
+ typedef std::map<const ClientSocketHandle*, CallbackResultPair>
+ PendingCallbackMap;
+
+ ~ClientSocketPoolBaseHelper();
+
static void InsertRequestIntoQueue(const Request* r,
RequestQueue* pending_requests);
static const Request* RemoveRequestFromQueue(RequestQueue::iterator it,
@@ -292,14 +344,11 @@
void IncrementIdleCount();
void DecrementIdleCount();
- // Called via PostTask by ReleaseSocket.
- void DoReleaseSocket(const std::string& group_name, ClientSocket* socket);
-
// Scans the group map for groups which have an available socket slot and
- // at least one pending request. Returns number of groups found, and if found
- // at least one, fills |group| and |group_name| with data of the stalled group
+ // at least one pending request. Returns true if any groups are stalled, and
+ // if so, fills |group| and |group_name| with data of the stalled group
// having highest priority.
- int FindTopStalledGroup(Group** group, std::string* group_name);
+ bool FindTopStalledGroup(Group** group, std::string* group_name);
// Called when timer_ fires. This method scans the idle sockets removing
// sockets that timed out or can't be reused.
@@ -310,14 +359,10 @@
// Removes |job| from |connect_job_set_|. Also updates |group| if non-NULL.
void RemoveConnectJob(const ConnectJob* job, Group* group);
- // Same as OnAvailableSocketSlot except it looks up the Group first to see if
- // it's there.
- void MaybeOnAvailableSocketSlot(const std::string& group_name);
-
- // Might delete the Group from |group_map_|.
+ // Tries to see if we can handle any more requests for |group|.
void OnAvailableSocketSlot(const std::string& group_name, Group* group);
- // Process a request from a group's pending_requests queue.
+ // Process a pending socket request for a group.
void ProcessPendingRequest(const std::string& group_name, Group* group);
// Assigns |socket| to |handle| and updates |group|'s counters appropriately.
@@ -325,7 +370,8 @@
bool reused,
ClientSocketHandle* handle,
base::TimeDelta time_idle,
- Group* group);
+ Group* group,
+ const BoundNetLog& net_log);
// Adds |socket| to the list of idle sockets for |group|. |used| indicates
// whether or not the socket has previously been used.
@@ -337,11 +383,57 @@
void CancelAllConnectJobs();
// Returns true if we can't create any more sockets due to the total limit.
- // TODO(phajdan.jr): Also take idle sockets into account.
bool ReachedMaxSocketsLimit() const;
+ // This is the internal implementation of RequestSocket(). It differs in that
+ // it does not handle logging into NetLog of the queueing status of
+ // |request|.
+ int RequestSocketInternal(const std::string& group_name,
+ const Request* request);
+
+ // Assigns an idle socket for the group to the request.
+ // Returns |true| if an idle socket is available, false otherwise.
+ bool AssignIdleSocketToGroup(Group* group, const Request* request);
+
+ static void LogBoundConnectJobToRequest(
+ const NetLog::Source& connect_job_source, const Request* request);
+
+ // Set a timer to create a backup socket if it takes too long to create one.
+ void StartBackupSocketTimer(const std::string& group_name);
+
+ // Called when the backup socket timer fires.
+ void OnBackupSocketTimerFired(const std::string& group_name);
+
+ // Closes one idle socket. Picks the first one encountered.
+ // TODO(willchan): Consider a better algorithm for doing this. Perhaps we
+ // should keep an ordered list of idle sockets, and close them in order.
+ // Requires maintaining more state. It's not clear if it's worth it since
+ // I'm not sure if we hit this situation often.
+ void CloseOneIdleSocket();
+
+ // Checks if there are stalled socket groups that should be notified
+ // for possible wakeup.
+ void CheckForStalledSocketGroups();
+
+ // Posts a task to call InvokeUserCallback() on the next iteration through the
+ // current message loop. Inserts |callback| into |pending_callback_map_|,
+ // keyed by |handle|.
+ void InvokeUserCallbackLater(
+ ClientSocketHandle* handle, CompletionCallback* callback, int rv);
+
+ // Invokes the user callback for |handle|. By the time this task has run,
+ // it's possible that the request has been cancelled, so |handle| may not
+ // exist in |pending_callback_map_|. We look up the callback and result code
+ // in |pending_callback_map_|.
+ void InvokeUserCallback(ClientSocketHandle* handle);
+
GroupMap group_map_;
+ // Map of the ClientSocketHandles for which we have a pending Task to invoke a
+ // callback. This is necessary since, before we invoke said callback, it's
+ // possible that the request is cancelled.
+ PendingCallbackMap pending_callback_map_;
+
// Timer used to periodically prune idle sockets that timed out or can't be
// reused.
base::RepeatingTimer<ClientSocketPoolBaseHelper> timer_;
@@ -365,34 +457,22 @@
const base::TimeDelta unused_idle_socket_timeout_;
const base::TimeDelta used_idle_socket_timeout_;
- // Until the maximum number of sockets limit is reached, a group can only
- // have pending requests if it exceeds the "max sockets per group" limit.
- //
- // This means when a socket is released, the only pending requests that can
- // be started next belong to the same group.
- //
- // However once the |max_sockets_| limit is reached, this stops being true:
- // groups can now have pending requests without having first reached the
- // |max_sockets_per_group_| limit. So choosing the next request involves
- // selecting the highest priority request across *all* groups.
- //
- // Since reaching the maximum number of sockets is an edge case, we make note
- // of when it happens, and thus avoid doing the slower "scan all groups"
- // in the common case.
- bool may_have_stalled_group_;
-
const scoped_ptr<ConnectJobFactory> connect_job_factory_;
- NetworkChangeNotifier* const network_change_notifier_;
+ // TODO(vandebo) Remove when backup jobs move to TCPClientSocketPool
+ bool backup_jobs_enabled_;
+
+ // A factory to pin the backup_job tasks.
+ ScopedRunnableMethodFactory<ClientSocketPoolBaseHelper> method_factory_;
+
+ // A unique id for the pool. It gets incremented every time we Flush() the
+ // pool. This is so that when sockets get released back to the pool, we can
+ // make sure that they are discarded rather than reused.
+ int pool_generation_number_;
};
} // namespace internal
-// The maximum duration, in seconds, to keep unused idle persistent sockets
-// alive.
-// TODO(willchan): Change this timeout after getting histogram data on how
-// long it should be.
-static const int kUnusedIdleSocketTimeout = 10;
// The maximum duration, in seconds, to keep used idle persistent sockets alive.
static const int kUsedIdleSocketTimeout = 300; // 5 minutes
@@ -404,16 +484,16 @@
Request(ClientSocketHandle* handle,
CompletionCallback* callback,
RequestPriority priority,
- const SocketParams& params,
- LoadLog* load_log)
+ const scoped_refptr<SocketParams>& params,
+ const BoundNetLog& net_log)
: internal::ClientSocketPoolBaseHelper::Request(
- handle, callback, priority, load_log),
+ handle, callback, priority, net_log),
params_(params) {}
- const SocketParams& params() const { return params_; }
+ const scoped_refptr<SocketParams>& params() const { return params_; }
private:
- SocketParams params_;
+ scoped_refptr<SocketParams> params_;
};
class ConnectJobFactory {
@@ -424,8 +504,9 @@
virtual ConnectJob* NewConnectJob(
const std::string& group_name,
const Request& request,
- ConnectJob::Delegate* delegate,
- LoadLog* load_log) const = 0;
+ ConnectJob::Delegate* delegate) const = 0;
+
+ virtual base::TimeDelta ConnectionTimeout() const = 0;
private:
DISALLOW_COPY_AND_ASSIGN(ConnectJobFactory);
@@ -440,15 +521,15 @@
ClientSocketPoolBase(
int max_sockets,
int max_sockets_per_group,
+ const scoped_refptr<ClientSocketPoolHistograms>& histograms,
base::TimeDelta unused_idle_socket_timeout,
base::TimeDelta used_idle_socket_timeout,
- ConnectJobFactory* connect_job_factory,
- NetworkChangeNotifier* network_change_notifier)
- : helper_(new internal::ClientSocketPoolBaseHelper(
+ ConnectJobFactory* connect_job_factory)
+ : histograms_(histograms),
+ helper_(new internal::ClientSocketPoolBaseHelper(
max_sockets, max_sockets_per_group,
unused_idle_socket_timeout, used_idle_socket_timeout,
- new ConnectJobFactoryAdaptor(connect_job_factory),
- network_change_notifier)) {}
+ new ConnectJobFactoryAdaptor(connect_job_factory))) {}
virtual ~ClientSocketPoolBase() {}
@@ -458,29 +539,23 @@
// ClientSocketPoolBaseHelper::RequestSocket(). Note that the memory
// ownership is transferred in the asynchronous (ERR_IO_PENDING) case.
int RequestSocket(const std::string& group_name,
- const SocketParams& params,
+ const scoped_refptr<SocketParams>& params,
RequestPriority priority,
ClientSocketHandle* handle,
CompletionCallback* callback,
- LoadLog* load_log) {
- scoped_ptr<Request> request(
- new Request(handle, callback, priority, params, load_log));
- LoadLog::BeginEvent(load_log, LoadLog::TYPE_SOCKET_POOL);
- int rv = helper_->RequestSocket(group_name, request.get());
- if (rv == ERR_IO_PENDING)
- request.release();
- else
- LoadLog::EndEvent(load_log, LoadLog::TYPE_SOCKET_POOL);
- return rv;
+ const BoundNetLog& net_log) {
+ Request* request = new Request(handle, callback, priority, params, net_log);
+ return helper_->RequestSocket(group_name, request);
}
void CancelRequest(const std::string& group_name,
- const ClientSocketHandle* handle) {
+ ClientSocketHandle* handle) {
return helper_->CancelRequest(group_name, handle);
}
- void ReleaseSocket(const std::string& group_name, ClientSocket* socket) {
- return helper_->ReleaseSocket(group_name, socket);
+ void ReleaseSocket(const std::string& group_name, ClientSocket* socket,
+ int id) {
+ return helper_->ReleaseSocket(group_name, socket, id);
}
void CloseIdleSockets() { return helper_->CloseIdleSockets(); }
@@ -500,11 +575,6 @@
return helper_->OnConnectJobComplete(result, job);
}
- // For testing.
- bool may_have_stalled_group() const {
- return helper_->may_have_stalled_group();
- }
-
int NumConnectJobsInGroup(const std::string& group_name) const {
return helper_->NumConnectJobsInGroup(group_name);
}
@@ -513,6 +583,18 @@
return helper_->CleanupIdleSockets(force);
}
+ base::TimeDelta ConnectionTimeout() const {
+ return helper_->ConnectionTimeout();
+ }
+
+ scoped_refptr<ClientSocketPoolHistograms> histograms() const {
+ return histograms_;
+ }
+
+ void EnableBackupJobs() { helper_->EnableBackupJobs(); }
+
+ void Flush() { helper_->Flush(); }
+
private:
// This adaptor class exists to bridge the
// internal::ClientSocketPoolBaseHelper::ConnectJobFactory and
@@ -533,21 +615,26 @@
virtual ConnectJob* NewConnectJob(
const std::string& group_name,
const internal::ClientSocketPoolBaseHelper::Request& request,
- ConnectJob::Delegate* delegate,
- LoadLog* load_log) const {
+ ConnectJob::Delegate* delegate) const {
const Request* casted_request = static_cast<const Request*>(&request);
return connect_job_factory_->NewConnectJob(
- group_name, *casted_request, delegate, load_log);
+ group_name, *casted_request, delegate);
+ }
+
+ virtual base::TimeDelta ConnectionTimeout() const {
+ return connect_job_factory_->ConnectionTimeout();
}
const scoped_ptr<ConnectJobFactory> connect_job_factory_;
};
- // One might ask why ClientSocketPoolBaseHelper is also refcounted if its
- // containing ClientSocketPool is already refcounted. The reason is because
- // DoReleaseSocket() posts a task. If ClientSocketPool gets deleted between
- // the posting of the task and the execution, then we'll hit the DCHECK that
- // |ClientSocketPoolBaseHelper::group_map_| is empty.
+ // Histograms for the pool
+ const scoped_refptr<ClientSocketPoolHistograms> histograms_;
+
+ // The reason for reference counting here is because the operations on
+ // the ClientSocketPoolBaseHelper which release sockets can cause the
+ // ClientSocketPoolBase<T> reference to drop to zero. While we're deep
+ // in cleanup code, we'll often hold a reference to |self|.
scoped_refptr<internal::ClientSocketPoolBaseHelper> helper_;
DISALLOW_COPY_AND_ASSIGN(ClientSocketPoolBase);
diff --git a/net/socket/client_socket_pool_base_unittest.cc b/net/socket/client_socket_pool_base_unittest.cc
index 9f875ad..55f1949 100644
--- a/net/socket/client_socket_pool_base_unittest.cc
+++ b/net/socket/client_socket_pool_base_unittest.cc
@@ -1,21 +1,25 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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 "net/socket/client_socket_pool_base.h"
+#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/message_loop.h"
#include "base/platform_thread.h"
+#include "base/ref_counted.h"
#include "base/scoped_vector.h"
-#include "net/base/load_log.h"
-#include "net/base/load_log_unittest.h"
+#include "base/string_util.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
#include "net/base/net_errors.h"
#include "net/base/request_priority.h"
#include "net/base/test_completion_callback.h"
#include "net/socket/client_socket.h"
#include "net/socket/client_socket_factory.h"
#include "net/socket/client_socket_handle.h"
+#include "net/socket/client_socket_pool_histograms.h"
#include "net/socket/socket_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -27,7 +31,12 @@
const int kDefaultMaxSocketsPerGroup = 2;
const net::RequestPriority kDefaultPriority = MEDIUM;
-typedef ClientSocketPoolBase<const void*> TestClientSocketPoolBase;
+class TestSocketParams : public base::RefCounted<TestSocketParams> {
+ private:
+ friend class base::RefCounted<TestSocketParams>;
+ ~TestSocketParams() {}
+};
+typedef ClientSocketPoolBase<TestSocketParams> TestClientSocketPoolBase;
class MockClientSocket : public ClientSocket {
public:
@@ -43,12 +52,12 @@
IOBuffer* /* buf */, int /* len */, CompletionCallback* /* callback */) {
return ERR_UNEXPECTED;
}
- virtual bool SetReceiveBufferSize(int32 size) { return true; };
- virtual bool SetSendBufferSize(int32 size) { return true; };
+ virtual bool SetReceiveBufferSize(int32 size) { return true; }
+ virtual bool SetSendBufferSize(int32 size) { return true; }
// ClientSocket methods:
- virtual int Connect(CompletionCallback* callback, LoadLog* load_log) {
+ virtual int Connect(CompletionCallback* callback) {
connected_ = true;
return OK;
}
@@ -57,13 +66,17 @@
virtual bool IsConnected() const { return connected_; }
virtual bool IsConnectedAndIdle() const { return connected_; }
- virtual int GetPeerName(struct sockaddr* /* name */,
- socklen_t* /* namelen */) {
+ virtual int GetPeerAddress(AddressList* /* address */) const {
return ERR_UNEXPECTED;
}
+ virtual const BoundNetLog& NetLog() const {
+ return net_log_;
+ }
+
private:
bool connected_;
+ BoundNetLog net_log_;
DISALLOW_COPY_AND_ASSIGN(MockClientSocket);
};
@@ -74,13 +87,14 @@
public:
MockClientSocketFactory() : allocation_count_(0) {}
- virtual ClientSocket* CreateTCPClientSocket(const AddressList& addresses) {
+ virtual ClientSocket* CreateTCPClientSocket(const AddressList& addresses,
+ NetLog* /* net_log */) {
allocation_count_++;
return NULL;
}
virtual SSLClientSocket* CreateSSLClientSocket(
- ClientSocket* transport_socket,
+ ClientSocketHandle* transport_socket,
const std::string& hostname,
const SSLConfig& ssl_config) {
NOTIMPLEMENTED();
@@ -106,39 +120,61 @@
kMockPendingFailingJob,
kMockWaitingJob,
kMockAdvancingLoadStateJob,
+ kMockRecoverableJob,
+ kMockPendingRecoverableJob,
+ kMockAdditionalErrorStateJob,
+ kMockPendingAdditionalErrorStateJob,
};
+ // The kMockPendingJob uses a slight delay before allowing the connect
+ // to complete.
+ static const int kPendingConnectDelay = 2;
+
TestConnectJob(JobType job_type,
const std::string& group_name,
const TestClientSocketPoolBase::Request& request,
base::TimeDelta timeout_duration,
ConnectJob::Delegate* delegate,
MockClientSocketFactory* client_socket_factory,
- LoadLog* load_log)
- : ConnectJob(group_name, timeout_duration, delegate, load_log),
+ NetLog* net_log)
+ : ConnectJob(group_name, timeout_duration, delegate,
+ BoundNetLog::Make(net_log, NetLog::SOURCE_CONNECT_JOB)),
job_type_(job_type),
client_socket_factory_(client_socket_factory),
method_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)),
- load_state_(LOAD_STATE_IDLE) {}
+ load_state_(LOAD_STATE_IDLE),
+ store_additional_error_state_(false) {}
void Signal() {
- DoConnect(waiting_success_, true /* async */);
+ DoConnect(waiting_success_, true /* async */, false /* recoverable */);
}
virtual LoadState GetLoadState() const { return load_state_; }
+ virtual void GetAdditionalErrorState(ClientSocketHandle* handle) {
+ if (store_additional_error_state_) {
+ // Set all of the additional error state fields in some way.
+ handle->set_is_ssl_error(true);
+ HttpResponseInfo info;
+ info.headers = new HttpResponseHeaders("");
+ handle->set_ssl_error_response_info(info);
+ }
+ }
+
private:
// ConnectJob methods:
virtual int ConnectInternal() {
AddressList ignored;
- client_socket_factory_->CreateTCPClientSocket(ignored);
+ client_socket_factory_->CreateTCPClientSocket(ignored, NULL);
set_socket(new MockClientSocket());
switch (job_type_) {
case kMockJob:
- return DoConnect(true /* successful */, false /* sync */);
+ return DoConnect(true /* successful */, false /* sync */,
+ false /* recoverable */);
case kMockFailingJob:
- return DoConnect(false /* error */, false /* sync */);
+ return DoConnect(false /* error */, false /* sync */,
+ false /* recoverable */);
case kMockPendingJob:
set_load_state(LOAD_STATE_CONNECTING);
@@ -158,8 +194,9 @@
method_factory_.NewRunnableMethod(
&TestConnectJob::DoConnect,
true /* successful */,
- true /* async */),
- 2);
+ true /* async */,
+ false /* recoverable */),
+ kPendingConnectDelay);
return ERR_IO_PENDING;
case kMockPendingFailingJob:
set_load_state(LOAD_STATE_CONNECTING);
@@ -168,7 +205,8 @@
method_factory_.NewRunnableMethod(
&TestConnectJob::DoConnect,
false /* error */,
- true /* async */),
+ true /* async */,
+ false /* recoverable */),
2);
return ERR_IO_PENDING;
case kMockWaitingJob:
@@ -176,10 +214,39 @@
waiting_success_ = true;
return ERR_IO_PENDING;
case kMockAdvancingLoadStateJob:
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ method_factory_.NewRunnableMethod(
+ &TestConnectJob::AdvanceLoadState, load_state_));
+ return ERR_IO_PENDING;
+ case kMockRecoverableJob:
+ return DoConnect(false /* error */, false /* sync */,
+ true /* recoverable */);
+ case kMockPendingRecoverableJob:
+ set_load_state(LOAD_STATE_CONNECTING);
MessageLoop::current()->PostDelayedTask(
FROM_HERE,
method_factory_.NewRunnableMethod(
- &TestConnectJob::AdvanceLoadState, load_state_),
+ &TestConnectJob::DoConnect,
+ false /* error */,
+ true /* async */,
+ true /* recoverable */),
+ 2);
+ return ERR_IO_PENDING;
+ case kMockAdditionalErrorStateJob:
+ store_additional_error_state_ = true;
+ return DoConnect(false /* error */, false /* sync */,
+ false /* recoverable */);
+ case kMockPendingAdditionalErrorStateJob:
+ set_load_state(LOAD_STATE_CONNECTING);
+ store_additional_error_state_ = true;
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ method_factory_.NewRunnableMethod(
+ &TestConnectJob::DoConnect,
+ false /* error */,
+ true /* async */,
+ false /* recoverable */),
2);
return ERR_IO_PENDING;
default:
@@ -191,12 +258,14 @@
void set_load_state(LoadState load_state) { load_state_ = load_state; }
- int DoConnect(bool succeed, bool was_async) {
- int result = ERR_CONNECTION_FAILED;
+ int DoConnect(bool succeed, bool was_async, bool recoverable) {
+ int result = OK;
if (succeed) {
- result = OK;
- socket()->Connect(NULL, NULL);
+ socket()->Connect(NULL);
+ } else if (recoverable) {
+ result = ERR_PROXY_AUTH_REQUESTED;
} else {
+ result = ERR_CONNECTION_FAILED;
set_socket(NULL);
}
@@ -205,17 +274,21 @@
return result;
}
+ // This function helps simulate the progress of load states on a ConnectJob.
+ // Each time it is called it advances the load state and posts a task to be
+ // called again. It stops at the last connecting load state (the one
+ // before LOAD_STATE_SENDING_REQUEST).
void AdvanceLoadState(LoadState state) {
int tmp = state;
tmp++;
- state = static_cast<LoadState>(tmp);
- set_load_state(state);
- // Post a delayed task so RunAllPending() won't run it.
- MessageLoop::current()->PostDelayedTask(
- FROM_HERE,
- method_factory_.NewRunnableMethod(&TestConnectJob::AdvanceLoadState,
- state),
- 1 /* 1ms delay */);
+ if (tmp < LOAD_STATE_SENDING_REQUEST) {
+ state = static_cast<LoadState>(tmp);
+ set_load_state(state);
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ method_factory_.NewRunnableMethod(&TestConnectJob::AdvanceLoadState,
+ state));
+ }
}
bool waiting_success_;
@@ -223,6 +296,7 @@
MockClientSocketFactory* const client_socket_factory_;
ScopedRunnableMethodFactory<TestConnectJob> method_factory_;
LoadState load_state_;
+ bool store_additional_error_state_;
DISALLOW_COPY_AND_ASSIGN(TestConnectJob);
};
@@ -247,15 +321,18 @@
virtual ConnectJob* NewConnectJob(
const std::string& group_name,
const TestClientSocketPoolBase::Request& request,
- ConnectJob::Delegate* delegate,
- LoadLog* load_log) const {
+ ConnectJob::Delegate* delegate) const {
return new TestConnectJob(job_type_,
group_name,
request,
timeout_duration_,
delegate,
client_socket_factory_,
- load_log);
+ NULL);
+ }
+
+ virtual base::TimeDelta ConnectionTimeout() const {
+ return timeout_duration_;
}
private:
@@ -271,12 +348,13 @@
TestClientSocketPool(
int max_sockets,
int max_sockets_per_group,
+ const scoped_refptr<ClientSocketPoolHistograms>& histograms,
base::TimeDelta unused_idle_socket_timeout,
base::TimeDelta used_idle_socket_timeout,
TestClientSocketPoolBase::ConnectJobFactory* connect_job_factory)
- : base_(max_sockets, max_sockets_per_group,
+ : base_(max_sockets, max_sockets_per_group, histograms,
unused_idle_socket_timeout, used_idle_socket_timeout,
- connect_job_factory, NULL) {}
+ connect_job_factory) {}
virtual int RequestSocket(
const std::string& group_name,
@@ -284,21 +362,28 @@
net::RequestPriority priority,
ClientSocketHandle* handle,
CompletionCallback* callback,
- LoadLog* load_log) {
- return base_.RequestSocket(
- group_name, params, priority, handle, callback, load_log);
+ const BoundNetLog& net_log) {
+ const scoped_refptr<TestSocketParams>* casted_socket_params =
+ static_cast<const scoped_refptr<TestSocketParams>*>(params);
+ return base_.RequestSocket(group_name, *casted_socket_params, priority,
+ handle, callback, net_log);
}
virtual void CancelRequest(
const std::string& group_name,
- const ClientSocketHandle* handle) {
+ ClientSocketHandle* handle) {
base_.CancelRequest(group_name, handle);
}
virtual void ReleaseSocket(
const std::string& group_name,
- ClientSocket* socket) {
- base_.ReleaseSocket(group_name, socket);
+ ClientSocket* socket,
+ int id) {
+ base_.ReleaseSocket(group_name, socket, id);
+ }
+
+ virtual void Flush() {
+ base_.Flush();
}
virtual void CloseIdleSockets() {
@@ -316,6 +401,14 @@
return base_.GetLoadState(group_name, handle);
}
+ virtual base::TimeDelta ConnectionTimeout() const {
+ return base_.ConnectionTimeout();
+ }
+
+ virtual scoped_refptr<ClientSocketPoolHistograms> histograms() const {
+ return base_.histograms();
+ }
+
const TestClientSocketPoolBase* base() const { return &base_; }
int NumConnectJobsInGroup(const std::string& group_name) const {
@@ -324,6 +417,8 @@
void CleanupTimedOutIdleSockets() { base_.CleanupIdleSockets(false); }
+ void EnableBackupJobs() { base_.EnableBackupJobs(); }
+
private:
~TestClientSocketPool() {}
@@ -334,7 +429,7 @@
} // namespace
-REGISTER_SOCKET_PARAMS_FOR_POOL(TestClientSocketPool, const void*);
+REGISTER_SOCKET_PARAMS_FOR_POOL(TestClientSocketPool, TestSocketParams);
namespace {
@@ -382,13 +477,16 @@
class ClientSocketPoolBaseTest : public ClientSocketPoolTest {
protected:
- ClientSocketPoolBaseTest() {}
+ ClientSocketPoolBaseTest()
+ : params_(new TestSocketParams()),
+ histograms_(new ClientSocketPoolHistograms("ClientSocketPoolTest")) {}
void CreatePool(int max_sockets, int max_sockets_per_group) {
CreatePoolWithIdleTimeouts(
max_sockets,
max_sockets_per_group,
- base::TimeDelta::FromSeconds(kUnusedIdleSocketTimeout),
+ base::TimeDelta::FromSeconds(
+ ClientSocketPool::unused_idle_socket_timeout()),
base::TimeDelta::FromSeconds(kUsedIdleSocketTimeout));
}
@@ -400,6 +498,7 @@
connect_job_factory_ = new TestConnectJobFactory(&client_socket_factory_);
pool_ = new TestClientSocketPool(max_sockets,
max_sockets_per_group,
+ histograms_,
unused_idle_socket_timeout,
used_idle_socket_timeout,
connect_job_factory_);
@@ -407,8 +506,8 @@
int StartRequest(const std::string& group_name,
net::RequestPriority priority) {
- return StartRequestUsingPool<TestClientSocketPool, const void*>(
- pool_.get(), group_name, priority, NULL);
+ return StartRequestUsingPool<TestClientSocketPool, TestSocketParams>(
+ pool_, group_name, priority, params_);
}
virtual void TearDown() {
@@ -423,36 +522,28 @@
// to delete |requests_| because the pool is reference counted and requests
// keep reference to it.
// TODO(willchan): Remove this part when late binding becomes the default.
+ TestClientSocketPool* pool = pool_.get();
pool_ = NULL;
requests_.reset();
+ pool = NULL;
ClientSocketPoolTest::TearDown();
}
MockClientSocketFactory client_socket_factory_;
TestConnectJobFactory* connect_job_factory_;
+ scoped_refptr<TestSocketParams> params_;
scoped_refptr<TestClientSocketPool> pool_;
+ scoped_refptr<ClientSocketPoolHistograms> histograms_;
};
-// Helper function which explicitly specifies the template parameters, since
-// the compiler will infer (in this case, incorrectly) that NULL is of type int.
-int InitHandle(ClientSocketHandle* handle,
- const std::string& group_name,
- net::RequestPriority priority,
- CompletionCallback* callback,
- TestClientSocketPool* pool,
- LoadLog* load_log) {
- return handle->Init<const void*, TestClientSocketPool>(
- group_name, NULL, priority, callback, pool, load_log);
-}
-
// Even though a timeout is specified, it doesn't time out on a synchronous
// completion.
TEST_F(ClientSocketPoolBaseTest, ConnectJob_NoTimeoutOnSynchronousCompletion) {
TestConnectJobDelegate delegate;
ClientSocketHandle ignored;
TestClientSocketPoolBase::Request request(
- &ignored, NULL, kDefaultPriority, NULL, NULL);
+ &ignored, NULL, kDefaultPriority, params_, BoundNetLog());
scoped_ptr<TestConnectJob> job(
new TestConnectJob(TestConnectJob::kMockJob,
"a",
@@ -467,9 +558,10 @@
TEST_F(ClientSocketPoolBaseTest, ConnectJob_TimedOut) {
TestConnectJobDelegate delegate;
ClientSocketHandle ignored;
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
+ CapturingNetLog log(CapturingNetLog::kUnbounded);
+
TestClientSocketPoolBase::Request request(
- &ignored, NULL, kDefaultPriority, NULL, NULL);
+ &ignored, NULL, kDefaultPriority, params_, BoundNetLog());
// Deleted by TestConnectJobDelegate.
TestConnectJob* job =
new TestConnectJob(TestConnectJob::kMockPendingJob,
@@ -478,19 +570,26 @@
base::TimeDelta::FromMicroseconds(1),
&delegate,
&client_socket_factory_,
- log);
+ &log);
ASSERT_EQ(ERR_IO_PENDING, job->Connect());
PlatformThread::Sleep(1);
EXPECT_EQ(ERR_TIMED_OUT, delegate.WaitForResult());
- EXPECT_EQ(3u, log->entries().size());
+ EXPECT_EQ(6u, log.entries().size());
EXPECT_TRUE(LogContainsBeginEvent(
- *log, 0, LoadLog::TYPE_SOCKET_POOL_CONNECT_JOB));
+ log.entries(), 0, NetLog::TYPE_SOCKET_POOL_CONNECT_JOB));
+ EXPECT_TRUE(LogContainsBeginEvent(
+ log.entries(), 1, NetLog::TYPE_SOCKET_POOL_CONNECT_JOB_CONNECT));
EXPECT_TRUE(LogContainsEvent(
- *log, 1, LoadLog::TYPE_SOCKET_POOL_CONNECT_JOB_TIMED_OUT,
- LoadLog::PHASE_NONE));
+ log.entries(), 2, NetLog::TYPE_CONNECT_JOB_SET_SOCKET,
+ NetLog::PHASE_NONE));
+ EXPECT_TRUE(LogContainsEvent(
+ log.entries(), 3, NetLog::TYPE_SOCKET_POOL_CONNECT_JOB_TIMED_OUT,
+ NetLog::PHASE_NONE));
EXPECT_TRUE(LogContainsEndEvent(
- *log, 2, LoadLog::TYPE_SOCKET_POOL_CONNECT_JOB));
+ log.entries(), 4, NetLog::TYPE_SOCKET_POOL_CONNECT_JOB_CONNECT));
+ EXPECT_TRUE(LogContainsEndEvent(
+ log.entries(), 5, NetLog::TYPE_SOCKET_POOL_CONNECT_JOB));
}
TEST_F(ClientSocketPoolBaseTest, BasicSynchronous) {
@@ -498,45 +597,60 @@
TestCompletionCallback callback;
ClientSocketHandle handle;
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
- EXPECT_EQ(OK, InitHandle(&handle, "a", kDefaultPriority,
- &callback, pool_.get(), log));
+ CapturingBoundNetLog log(CapturingNetLog::kUnbounded);
+
+ EXPECT_EQ(OK, handle.Init("a", params_, kDefaultPriority, &callback, pool_,
+ log.bound()));
EXPECT_TRUE(handle.is_initialized());
EXPECT_TRUE(handle.socket());
handle.Reset();
- EXPECT_EQ(4u, log->entries().size());
- EXPECT_TRUE(LogContainsBeginEvent(*log, 0, LoadLog::TYPE_SOCKET_POOL));
+ EXPECT_EQ(4u, log.entries().size());
EXPECT_TRUE(LogContainsBeginEvent(
- *log, 1, LoadLog::TYPE_SOCKET_POOL_CONNECT_JOB));
+ log.entries(), 0, NetLog::TYPE_SOCKET_POOL));
+ EXPECT_TRUE(LogContainsEvent(
+ log.entries(), 1, NetLog::TYPE_SOCKET_POOL_BOUND_TO_CONNECT_JOB,
+ NetLog::PHASE_NONE));
+ EXPECT_TRUE(LogContainsEvent(
+ log.entries(), 2, NetLog::TYPE_SOCKET_POOL_BOUND_TO_SOCKET,
+ NetLog::PHASE_NONE));
EXPECT_TRUE(LogContainsEndEvent(
- *log, 2, LoadLog::TYPE_SOCKET_POOL_CONNECT_JOB));
- EXPECT_TRUE(LogContainsEndEvent(*log, 3, LoadLog::TYPE_SOCKET_POOL));
+ log.entries(), 3, NetLog::TYPE_SOCKET_POOL));
}
TEST_F(ClientSocketPoolBaseTest, InitConnectionFailure) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
connect_job_factory_->set_job_type(TestConnectJob::kMockFailingJob);
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
- TestSocketRequest req(&request_order_, &completion_count_);
- EXPECT_EQ(ERR_CONNECTION_FAILED,
- InitHandle(req.handle(), "a", kDefaultPriority, &req,
- pool_.get(), log));
+ CapturingBoundNetLog log(CapturingNetLog::kUnbounded);
- EXPECT_EQ(4u, log->entries().size());
- EXPECT_TRUE(LogContainsBeginEvent(*log, 0, LoadLog::TYPE_SOCKET_POOL));
+ TestSocketRequest req(&request_order_, &completion_count_);
+ // Set the additional error state members to ensure that they get cleared.
+ req.handle()->set_is_ssl_error(true);
+ HttpResponseInfo info;
+ info.headers = new HttpResponseHeaders("");
+ req.handle()->set_ssl_error_response_info(info);
+ EXPECT_EQ(ERR_CONNECTION_FAILED, req.handle()->Init("a", params_,
+ kDefaultPriority, &req,
+ pool_, log.bound()));
+ EXPECT_FALSE(req.handle()->socket());
+ EXPECT_FALSE(req.handle()->is_ssl_error());
+ EXPECT_TRUE(req.handle()->ssl_error_response_info().headers.get() == NULL);
+
+ EXPECT_EQ(3u, log.entries().size());
EXPECT_TRUE(LogContainsBeginEvent(
- *log, 1, LoadLog::TYPE_SOCKET_POOL_CONNECT_JOB));
+ log.entries(), 0, NetLog::TYPE_SOCKET_POOL));
+ EXPECT_TRUE(LogContainsEvent(
+ log.entries(), 1, NetLog::TYPE_SOCKET_POOL_BOUND_TO_CONNECT_JOB,
+ NetLog::PHASE_NONE));
EXPECT_TRUE(LogContainsEndEvent(
- *log, 2, LoadLog::TYPE_SOCKET_POOL_CONNECT_JOB));
- EXPECT_TRUE(LogContainsEndEvent(*log, 3, LoadLog::TYPE_SOCKET_POOL));
+ log.entries(), 2, NetLog::TYPE_SOCKET_POOL));
}
TEST_F(ClientSocketPoolBaseTest, TotalLimit) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
- // TODO(eroman): Check that the LoadLog contains this event.
+ // TODO(eroman): Check that the NetLog contains this event.
EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
EXPECT_EQ(OK, StartRequest("b", kDefaultPriority));
@@ -551,7 +665,7 @@
EXPECT_EQ(ERR_IO_PENDING, StartRequest("f", kDefaultPriority));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("g", kDefaultPriority));
- ReleaseAllConnections(KEEP_ALIVE);
+ ReleaseAllConnections(NO_KEEP_ALIVE);
EXPECT_EQ(static_cast<int>(requests_.size()),
client_socket_factory_.allocation_count());
@@ -572,7 +686,7 @@
TEST_F(ClientSocketPoolBaseTest, TotalLimitReachedNewGroup) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
- // TODO(eroman): Check that the LoadLog contains this event.
+ // TODO(eroman): Check that the NetLog contains this event.
// Reach all limits: max total sockets, and max sockets per group.
EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
@@ -587,7 +701,7 @@
// Now create a new group and verify that we don't starve it.
EXPECT_EQ(ERR_IO_PENDING, StartRequest("c", kDefaultPriority));
- ReleaseAllConnections(KEEP_ALIVE);
+ ReleaseAllConnections(NO_KEEP_ALIVE);
EXPECT_EQ(static_cast<int>(requests_.size()),
client_socket_factory_.allocation_count());
@@ -618,11 +732,8 @@
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", MEDIUM));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("b", HIGHEST));
- ReleaseAllConnections(KEEP_ALIVE);
+ ReleaseAllConnections(NO_KEEP_ALIVE);
- // We're re-using one socket for group "a", and one for "b".
- EXPECT_EQ(static_cast<int>(requests_.size()) - 2,
- client_socket_factory_.allocation_count());
EXPECT_EQ(requests_.size() - kDefaultMaxSockets, completion_count_);
// First 4 requests don't have to wait, and finish in order.
@@ -656,10 +767,9 @@
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOW));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("b", HIGHEST));
- ReleaseAllConnections(KEEP_ALIVE);
+ ReleaseAllConnections(NO_KEEP_ALIVE);
- // We're re-using one socket for group "a", and one for "b".
- EXPECT_EQ(static_cast<int>(requests_.size()) - 2,
+ EXPECT_EQ(static_cast<int>(requests_.size()),
client_socket_factory_.allocation_count());
EXPECT_EQ(requests_.size() - kDefaultMaxSockets, completion_count_);
@@ -704,7 +814,7 @@
connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
EXPECT_EQ(ERR_IO_PENDING, StartRequest("e", kDefaultPriority));
- ReleaseAllConnections(KEEP_ALIVE);
+ ReleaseAllConnections(NO_KEEP_ALIVE);
EXPECT_EQ(static_cast<int>(requests_.size()),
client_socket_factory_.allocation_count());
@@ -719,56 +829,196 @@
EXPECT_EQ(kIndexOutOfBounds, GetOrderOfRequest(6));
}
-// Inside ClientSocketPoolBase we have a may_have_stalled_group flag,
-// which tells it to use more expensive, but accurate, group selection
-// algorithm. Make sure it doesn't get stuck in the "on" state.
-TEST_F(ClientSocketPoolBaseTest, MayHaveStalledGroupReset) {
+TEST_F(ClientSocketPoolBaseTest, CorrectlyCountStalledGroups) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSockets);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
+
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+
+ connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob);
+
+ EXPECT_EQ(kDefaultMaxSockets, client_socket_factory_.allocation_count());
+
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("b", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("c", kDefaultPriority));
+
+ EXPECT_EQ(kDefaultMaxSockets, client_socket_factory_.allocation_count());
+
+ EXPECT_TRUE(ReleaseOneConnection(KEEP_ALIVE));
+ EXPECT_EQ(kDefaultMaxSockets + 1, client_socket_factory_.allocation_count());
+ EXPECT_TRUE(ReleaseOneConnection(KEEP_ALIVE));
+ EXPECT_EQ(kDefaultMaxSockets + 2, client_socket_factory_.allocation_count());
+ EXPECT_TRUE(ReleaseOneConnection(KEEP_ALIVE));
+ EXPECT_TRUE(ReleaseOneConnection(KEEP_ALIVE));
+ EXPECT_EQ(kDefaultMaxSockets + 2, client_socket_factory_.allocation_count());
+}
+
+TEST_F(ClientSocketPoolBaseTest, StallAndThenCancelAndTriggerAvailableSocket) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSockets);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING, handle.Init("a", params_, kDefaultPriority,
+ &callback, pool_, BoundNetLog()));
+
+ ClientSocketHandle handles[4];
+ for (size_t i = 0; i < arraysize(handles); ++i) {
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING, handles[i].Init("b", params_, kDefaultPriority,
+ &callback, pool_, BoundNetLog()));
+ }
+
+ // One will be stalled, cancel all the handles now.
+ // This should hit the OnAvailableSocketSlot() code where we previously had
+ // stalled groups, but no longer have any.
+ for (size_t i = 0; i < arraysize(handles); ++i)
+ handles[i].Reset();
+}
+
+TEST_F(ClientSocketPoolBaseTest, CancelStalledSocketAtSocketLimit) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
- EXPECT_FALSE(pool_->base()->may_have_stalled_group());
+ {
+ ClientSocketHandle handles[kDefaultMaxSockets];
+ TestCompletionCallback callbacks[kDefaultMaxSockets];
+ for (int i = 0; i < kDefaultMaxSockets; ++i) {
+ EXPECT_EQ(OK, handles[i].Init(IntToString(i), params_, kDefaultPriority,
+ &callbacks[i], pool_, BoundNetLog()));
+ }
- // Reach group socket limit.
- EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
- EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
- EXPECT_FALSE(pool_->base()->may_have_stalled_group());
+ // Force a stalled group.
+ ClientSocketHandle stalled_handle;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING, stalled_handle.Init("foo", params_,
+ kDefaultPriority, &callback,
+ pool_, BoundNetLog()));
- // Reach total limit, but don't request more sockets.
- EXPECT_EQ(OK, StartRequest("b", kDefaultPriority));
- EXPECT_EQ(OK, StartRequest("b", kDefaultPriority));
- EXPECT_FALSE(pool_->base()->may_have_stalled_group());
+ // Cancel the stalled request.
+ stalled_handle.Reset();
- // Request one more socket while we are at the maximum sockets limit.
- // This should flip the may_have_stalled_group flag.
- EXPECT_EQ(ERR_IO_PENDING, StartRequest("c", kDefaultPriority));
- EXPECT_TRUE(pool_->base()->may_have_stalled_group());
+ EXPECT_EQ(kDefaultMaxSockets, client_socket_factory_.allocation_count());
+ EXPECT_EQ(0, pool_->IdleSocketCount());
- // After releasing first connection for "a", we're still at the
- // maximum sockets limit, but every group's pending queue is empty,
- // so we reset the flag.
- EXPECT_TRUE(ReleaseOneConnection(KEEP_ALIVE));
- EXPECT_FALSE(pool_->base()->may_have_stalled_group());
+ // Dropping out of scope will close all handles and return them to idle.
+ }
- // Requesting additional socket while at the total limit should
- // flip the flag back to "on".
- EXPECT_EQ(ERR_IO_PENDING, StartRequest("c", kDefaultPriority));
- EXPECT_TRUE(pool_->base()->may_have_stalled_group());
+ EXPECT_EQ(kDefaultMaxSockets, client_socket_factory_.allocation_count());
+ EXPECT_EQ(kDefaultMaxSockets, pool_->IdleSocketCount());
+}
- // We'll request one more socket to verify that we don't reset the flag
- // too eagerly.
- EXPECT_EQ(ERR_IO_PENDING, StartRequest("d", kDefaultPriority));
- EXPECT_TRUE(pool_->base()->may_have_stalled_group());
+TEST_F(ClientSocketPoolBaseTest, CancelPendingSocketAtSocketLimit) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob);
- // We're at the maximum socket limit, and still have one request pending
- // for "d". Flag should be "on".
- EXPECT_TRUE(ReleaseOneConnection(KEEP_ALIVE));
- EXPECT_TRUE(pool_->base()->may_have_stalled_group());
+ {
+ ClientSocketHandle handles[kDefaultMaxSockets];
+ for (int i = 0; i < kDefaultMaxSockets; ++i) {
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING, handles[i].Init(IntToString(i), params_,
+ kDefaultPriority, &callback,
+ pool_, BoundNetLog()));
+ }
- // Now every group's pending queue should be empty again.
- EXPECT_TRUE(ReleaseOneConnection(KEEP_ALIVE));
- EXPECT_FALSE(pool_->base()->may_have_stalled_group());
+ // Force a stalled group.
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+ ClientSocketHandle stalled_handle;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING, stalled_handle.Init("foo", params_,
+ kDefaultPriority, &callback,
+ pool_, BoundNetLog()));
- ReleaseAllConnections(KEEP_ALIVE);
- EXPECT_FALSE(pool_->base()->may_have_stalled_group());
+ // Since it is stalled, it should have no connect jobs.
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("foo"));
+
+ // Cancel the stalled request.
+ handles[0].Reset();
+
+ // Now we should have a connect job.
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("foo"));
+
+ // The stalled socket should connect.
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ EXPECT_EQ(kDefaultMaxSockets + 1,
+ client_socket_factory_.allocation_count());
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("foo"));
+
+ // Dropping out of scope will close all handles and return them to idle.
+ }
+
+ EXPECT_EQ(1, pool_->IdleSocketCount());
+}
+
+TEST_F(ClientSocketPoolBaseTest, WaitForStalledSocketAtSocketLimit) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
+
+ ClientSocketHandle stalled_handle;
+ TestCompletionCallback callback;
+ {
+ ClientSocketHandle handles[kDefaultMaxSockets];
+ for (int i = 0; i < kDefaultMaxSockets; ++i) {
+ TestCompletionCallback callback;
+ EXPECT_EQ(OK, handles[i].Init(StringPrintf("Take 2: %d", i), params_,
+ kDefaultPriority, &callback, pool_,
+ BoundNetLog()));
+ }
+
+ EXPECT_EQ(kDefaultMaxSockets, client_socket_factory_.allocation_count());
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+
+ // Now we will hit the socket limit.
+ EXPECT_EQ(ERR_IO_PENDING, stalled_handle.Init("foo", params_,
+ kDefaultPriority, &callback,
+ pool_, BoundNetLog()));
+
+ // Dropping out of scope will close all handles and return them to idle.
+ }
+
+ // But if we wait for it, the released idle sockets will be closed in
+ // preference of the waiting request.
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ EXPECT_EQ(kDefaultMaxSockets + 1, client_socket_factory_.allocation_count());
+ EXPECT_EQ(3, pool_->IdleSocketCount());
+}
+
+// Regression test for http://crbug.com/40952.
+TEST_F(ClientSocketPoolBaseTest, CloseIdleSocketAtSocketLimitDeleteGroup) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ pool_->EnableBackupJobs();
+ connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
+
+ for (int i = 0; i < kDefaultMaxSockets; ++i) {
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ EXPECT_EQ(OK, handle.Init(IntToString(i), params_, kDefaultPriority,
+ &callback, pool_, BoundNetLog()));
+ }
+
+ // Flush all the DoReleaseSocket tasks.
+ MessageLoop::current()->RunAllPending();
+
+ // Stall a group. Set a pending job so it'll trigger a backup job if we don't
+ // reuse a socket.
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+
+ // "0" is special here, since it should be the first entry in the sorted map,
+ // which is the one which we would close an idle socket for. We shouldn't
+ // close an idle socket though, since we should reuse the idle socket.
+ EXPECT_EQ(OK, handle.Init("0", params_, kDefaultPriority, &callback, pool_,
+ BoundNetLog()));
+
+ EXPECT_EQ(kDefaultMaxSockets, client_socket_factory_.allocation_count());
+ EXPECT_EQ(kDefaultMaxSockets - 1, pool_->IdleSocketCount());
}
TEST_F(ClientSocketPoolBaseTest, PendingRequests) {
@@ -829,9 +1079,8 @@
connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
TestSocketRequest req(&request_order_, &completion_count_);
- EXPECT_EQ(ERR_IO_PENDING,
- InitHandle(req.handle(), "a", kDefaultPriority, &req,
- pool_.get(), NULL));
+ EXPECT_EQ(ERR_IO_PENDING, req.handle()->Init("a", params_, kDefaultPriority,
+ &req, pool_, BoundNetLog()));
req.handle()->Reset();
}
@@ -843,16 +1092,14 @@
TestCompletionCallback callback;
TestSocketRequest req(&request_order_, &completion_count_);
- EXPECT_EQ(ERR_IO_PENDING,
- InitHandle(&handle, "a", kDefaultPriority, &callback,
- pool_.get(), NULL));
+ EXPECT_EQ(ERR_IO_PENDING, handle.Init("a", params_, kDefaultPriority,
+ &callback, pool_, BoundNetLog()));
handle.Reset();
TestCompletionCallback callback2;
- EXPECT_EQ(ERR_IO_PENDING,
- InitHandle(&handle, "a", kDefaultPriority, &callback2,
- pool_.get(), NULL));
+ EXPECT_EQ(ERR_IO_PENDING, handle.Init("a", params_, kDefaultPriority,
+ &callback2, pool_, BoundNetLog()));
EXPECT_EQ(OK, callback2.WaitForResult());
EXPECT_FALSE(callback.have_result());
@@ -925,9 +1172,9 @@
}
within_callback_ = true;
TestCompletionCallback next_job_callback;
- int rv = InitHandle(
- handle_, "a", kDefaultPriority, &next_job_callback, pool_.get(),
- NULL);
+ scoped_refptr<TestSocketParams> params = new TestSocketParams();
+ int rv = handle_->Init("a", params, kDefaultPriority, &next_job_callback,
+ pool_, BoundNetLog());
switch (next_job_type_) {
case TestConnectJob::kMockJob:
EXPECT_EQ(OK, rv);
@@ -976,8 +1223,8 @@
RequestSocketCallback callback(
&handle, pool_.get(), connect_job_factory_,
TestConnectJob::kMockPendingJob);
- int rv = InitHandle(&handle, "a", kDefaultPriority, &callback,
- pool_.get(), NULL);
+ int rv = handle.Init("a", params_, kDefaultPriority, &callback, pool_,
+ BoundNetLog());
ASSERT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(OK, callback.WaitForResult());
@@ -990,8 +1237,8 @@
ClientSocketHandle handle;
RequestSocketCallback callback(
&handle, pool_.get(), connect_job_factory_, TestConnectJob::kMockJob);
- int rv = InitHandle(&handle, "a", kDefaultPriority, &callback,
- pool_.get(), NULL);
+ int rv = handle.Init("a", params_, kDefaultPriority, &callback, pool_,
+ BoundNetLog());
ASSERT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(OK, callback.WaitForResult());
@@ -1052,15 +1299,15 @@
connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
TestSocketRequest req(&request_order_, &completion_count_);
- int rv = InitHandle(req.handle(), "a", kDefaultPriority, &req,
- pool_.get(), NULL);
+ int rv = req.handle()->Init("a", params_, kDefaultPriority, &req, pool_,
+ BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
// Cancel the active request.
req.handle()->Reset();
- rv = InitHandle(req.handle(), "a", kDefaultPriority, &req,
- pool_.get(), NULL);
+ rv = req.handle()->Init("a", params_, kDefaultPriority, &req, pool_,
+ BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(OK, req.WaitForResult());
@@ -1089,35 +1336,29 @@
// Create a stalled group with high priorities.
EXPECT_EQ(ERR_IO_PENDING, StartRequest("c", kHighPriority));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("c", kHighPriority));
- EXPECT_TRUE(pool_->base()->may_have_stalled_group());
- // Release the first two sockets from "a", which will make room
- // for requests from "c". After that "a" will have no active sockets
- // and one pending request.
+ // Release the first two sockets from "a". Because this is a keepalive,
+ // the first release will unblock the pending request for "a". The
+ // second release will unblock a request for "c", becaue it is the next
+ // high priority socket.
EXPECT_TRUE(ReleaseOneConnection(KEEP_ALIVE));
EXPECT_TRUE(ReleaseOneConnection(KEEP_ALIVE));
// Closing idle sockets should not get us into trouble, but in the bug
// we were hitting a CHECK here.
- EXPECT_EQ(2, pool_->IdleSocketCountInGroup("a"));
- pool_->CloseIdleSockets();
EXPECT_EQ(0, pool_->IdleSocketCountInGroup("a"));
-}
+ pool_->CloseIdleSockets();
-class ClientSocketPoolBaseTest_LateBinding : public ClientSocketPoolBaseTest {
- protected:
- virtual void SetUp() {
- ClientSocketPoolBaseTest::SetUp();
- }
-};
+ MessageLoop::current()->RunAllPending(); // Run the released socket wakeups
+}
TEST_F(ClientSocketPoolBaseTest, BasicAsynchronous) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
TestSocketRequest req(&request_order_, &completion_count_);
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
- int rv = InitHandle(req.handle(), "a", LOWEST, &req, pool_.get(), log);
+ CapturingBoundNetLog log(CapturingNetLog::kUnbounded);
+ int rv = req.handle()->Init("a", params_, LOWEST, &req, pool_, log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState("a", req.handle()));
EXPECT_EQ(OK, req.WaitForResult());
@@ -1125,18 +1366,17 @@
EXPECT_TRUE(req.handle()->socket());
req.handle()->Reset();
- EXPECT_EQ(6u, log->entries().size());
- EXPECT_TRUE(LogContainsBeginEvent(*log, 0, LoadLog::TYPE_SOCKET_POOL));
+ EXPECT_EQ(4u, log.entries().size());
EXPECT_TRUE(LogContainsBeginEvent(
- *log, 1, LoadLog::TYPE_SOCKET_POOL_WAITING_IN_QUEUE));
+ log.entries(), 0, NetLog::TYPE_SOCKET_POOL));
+ EXPECT_TRUE(LogContainsEvent(
+ log.entries(), 1, NetLog::TYPE_SOCKET_POOL_BOUND_TO_CONNECT_JOB,
+ NetLog::PHASE_NONE));
+ EXPECT_TRUE(LogContainsEvent(
+ log.entries(), 2, NetLog::TYPE_SOCKET_POOL_BOUND_TO_SOCKET,
+ NetLog::PHASE_NONE));
EXPECT_TRUE(LogContainsEndEvent(
- *log, 2, LoadLog::TYPE_SOCKET_POOL_WAITING_IN_QUEUE));
- EXPECT_TRUE(LogContainsBeginEvent(
- *log, 3, LoadLog::TYPE_SOCKET_POOL_CONNECT_JOB));
- EXPECT_TRUE(LogContainsEndEvent(
- *log, 4, LoadLog::TYPE_SOCKET_POOL_CONNECT_JOB));
- EXPECT_TRUE(LogContainsEndEvent(
- *log, 5, LoadLog::TYPE_SOCKET_POOL));
+ log.entries(), 3, NetLog::TYPE_SOCKET_POOL));
}
TEST_F(ClientSocketPoolBaseTest,
@@ -1145,76 +1385,54 @@
connect_job_factory_->set_job_type(TestConnectJob::kMockPendingFailingJob);
TestSocketRequest req(&request_order_, &completion_count_);
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
- EXPECT_EQ(ERR_IO_PENDING,
- InitHandle(req.handle(), "a", kDefaultPriority, &req,
- pool_.get(), log));
+ CapturingBoundNetLog log(CapturingNetLog::kUnbounded);
+ // Set the additional error state members to ensure that they get cleared.
+ req.handle()->set_is_ssl_error(true);
+ HttpResponseInfo info;
+ info.headers = new HttpResponseHeaders("");
+ req.handle()->set_ssl_error_response_info(info);
+ EXPECT_EQ(ERR_IO_PENDING, req.handle()->Init("a", params_, kDefaultPriority,
+ &req, pool_, log.bound()));
EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState("a", req.handle()));
EXPECT_EQ(ERR_CONNECTION_FAILED, req.WaitForResult());
+ EXPECT_FALSE(req.handle()->is_ssl_error());
+ EXPECT_TRUE(req.handle()->ssl_error_response_info().headers.get() == NULL);
- EXPECT_EQ(6u, log->entries().size());
- EXPECT_TRUE(LogContainsBeginEvent(*log, 0, LoadLog::TYPE_SOCKET_POOL));
+ EXPECT_EQ(3u, log.entries().size());
EXPECT_TRUE(LogContainsBeginEvent(
- *log, 1, LoadLog::TYPE_SOCKET_POOL_WAITING_IN_QUEUE));
+ log.entries(), 0, NetLog::TYPE_SOCKET_POOL));
+ EXPECT_TRUE(LogContainsEvent(
+ log.entries(), 1, NetLog::TYPE_SOCKET_POOL_BOUND_TO_CONNECT_JOB,
+ NetLog::PHASE_NONE));
EXPECT_TRUE(LogContainsEndEvent(
- *log, 2, LoadLog::TYPE_SOCKET_POOL_WAITING_IN_QUEUE));
- EXPECT_TRUE(LogContainsBeginEvent(
- *log, 3, LoadLog::TYPE_SOCKET_POOL_CONNECT_JOB));
- EXPECT_TRUE(LogContainsEndEvent(
- *log, 4, LoadLog::TYPE_SOCKET_POOL_CONNECT_JOB));
- EXPECT_TRUE(LogContainsEndEvent(*log, 5, LoadLog::TYPE_SOCKET_POOL));
+ log.entries(), 2, NetLog::TYPE_SOCKET_POOL));
}
TEST_F(ClientSocketPoolBaseTest, TwoRequestsCancelOne) {
+ // TODO(eroman): Add back the log expectations! Removed them because the
+ // ordering is difficult, and some may fire during destructor.
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
TestSocketRequest req(&request_order_, &completion_count_);
TestSocketRequest req2(&request_order_, &completion_count_);
- scoped_refptr<LoadLog> log1(new LoadLog(LoadLog::kUnbounded));
- EXPECT_EQ(ERR_IO_PENDING,
- InitHandle(req.handle(), "a", kDefaultPriority, &req,
- pool_.get(), log1));
- scoped_refptr<LoadLog> log2(new LoadLog(LoadLog::kUnbounded));
- EXPECT_EQ(ERR_IO_PENDING,
- InitHandle(req2.handle(), "a", kDefaultPriority, &req2,
- pool_.get(), log2));
+ EXPECT_EQ(ERR_IO_PENDING, req.handle()->Init("a", params_, kDefaultPriority,
+ &req, pool_, BoundNetLog()));
+ CapturingBoundNetLog log2(CapturingNetLog::kUnbounded);
+ EXPECT_EQ(ERR_IO_PENDING, req2.handle()->Init("a", params_, kDefaultPriority,
+ &req2, pool_, BoundNetLog()));
req.handle()->Reset();
- EXPECT_EQ(5u, log1->entries().size());
- EXPECT_TRUE(LogContainsBeginEvent(*log1, 0, LoadLog::TYPE_SOCKET_POOL));
- EXPECT_TRUE(LogContainsBeginEvent(
- *log1, 1, LoadLog::TYPE_SOCKET_POOL_WAITING_IN_QUEUE));
- EXPECT_TRUE(LogContainsEndEvent(
- *log1, 2, LoadLog::TYPE_SOCKET_POOL_WAITING_IN_QUEUE));
- EXPECT_TRUE(LogContainsEvent(
- *log1, 3, LoadLog::TYPE_CANCELLED, LoadLog::PHASE_NONE));
- EXPECT_TRUE(LogContainsEndEvent(*log1, 4, LoadLog::TYPE_SOCKET_POOL));
// At this point, request 2 is just waiting for the connect job to finish.
- EXPECT_EQ(2u, log2->entries().size());
- EXPECT_TRUE(LogContainsBeginEvent(*log2, 0, LoadLog::TYPE_SOCKET_POOL));
- EXPECT_TRUE(LogContainsBeginEvent(
- *log2, 1, LoadLog::TYPE_SOCKET_POOL_WAITING_IN_QUEUE));
EXPECT_EQ(OK, req2.WaitForResult());
req2.handle()->Reset();
// Now request 2 has actually finished.
- EXPECT_EQ(6u, log2->entries().size());
- EXPECT_TRUE(LogContainsBeginEvent(*log2, 0, LoadLog::TYPE_SOCKET_POOL));
- EXPECT_TRUE(LogContainsBeginEvent(
- *log2, 1, LoadLog::TYPE_SOCKET_POOL_WAITING_IN_QUEUE));
- EXPECT_TRUE(LogContainsEndEvent(
- *log1, 2, LoadLog::TYPE_SOCKET_POOL_WAITING_IN_QUEUE));
- EXPECT_TRUE(LogContainsBeginEvent(
- *log2, 3, LoadLog::TYPE_SOCKET_POOL_CONNECT_JOB));
- EXPECT_TRUE(LogContainsEndEvent(
- *log2, 4, LoadLog::TYPE_SOCKET_POOL_CONNECT_JOB));
- EXPECT_TRUE(LogContainsEndEvent(*log2, 5, LoadLog::TYPE_SOCKET_POOL));
-
+ // TODO(eroman): Add back log expectations.
}
TEST_F(ClientSocketPoolBaseTest, CancelRequestLimitsJobs) {
@@ -1236,7 +1454,7 @@
EXPECT_EQ(kDefaultMaxSocketsPerGroup, pool_->NumConnectJobsInGroup("a"));
requests_[0]->handle()->Reset();
- EXPECT_EQ(kDefaultMaxSocketsPerGroup - 1, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(kDefaultMaxSocketsPerGroup, pool_->NumConnectJobsInGroup("a"));
}
// When requests and ConnectJobs are not coupled, the request will get serviced
@@ -1248,8 +1466,8 @@
connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
TestSocketRequest req1(&request_order_, &completion_count_);
- int rv = InitHandle(req1.handle(), "a", kDefaultPriority,
- &req1, pool_.get(), NULL);
+ int rv = req1.handle()->Init("a", params_, kDefaultPriority, &req1, pool_,
+ BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(OK, req1.WaitForResult());
@@ -1258,18 +1476,18 @@
connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob);
TestSocketRequest req2(&request_order_, &completion_count_);
- rv = InitHandle(req2.handle(), "a", kDefaultPriority, &req2,
- pool_.get(), NULL);
+ rv = req2.handle()->Init("a", params_, kDefaultPriority, &req2, pool_,
+ BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
TestSocketRequest req3(&request_order_, &completion_count_);
- rv = InitHandle(
- req3.handle(), "a", kDefaultPriority, &req3, pool_.get(), NULL);
+ rv = req3.handle()->Init("a", params_, kDefaultPriority, &req3, pool_,
+ BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
// Both Requests 2 and 3 are pending. We release socket 1 which should
// service request 2. Request 3 should still be waiting.
req1.handle()->Reset();
- MessageLoop::current()->RunAllPending(); // Run the DoReleaseSocket()
+ MessageLoop::current()->RunAllPending(); // Run the released socket wakeups
ASSERT_TRUE(req2.handle()->socket());
EXPECT_EQ(OK, req2.WaitForResult());
EXPECT_FALSE(req3.handle()->socket());
@@ -1294,21 +1512,21 @@
connect_job_factory_->set_job_type(TestConnectJob::kMockPendingFailingJob);
TestSocketRequest req1(&request_order_, &completion_count_);
- int rv = InitHandle(
- req1.handle(), "a", kDefaultPriority, &req1, pool_.get(), NULL);
+ int rv = req1.handle()->Init("a", params_, kDefaultPriority, &req1, pool_,
+ BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
TestSocketRequest req2(&request_order_, &completion_count_);
- rv = InitHandle(req2.handle(), "a", kDefaultPriority, &req2,
- pool_.get(), NULL);
+ rv = req2.handle()->Init("a", params_, kDefaultPriority, &req2, pool_,
+ BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
// The pending job is sync.
connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
TestSocketRequest req3(&request_order_, &completion_count_);
- rv = InitHandle(
- req3.handle(), "a", kDefaultPriority, &req3, pool_.get(), NULL);
+ rv = req3.handle()->Init("a", params_, kDefaultPriority, &req3, pool_,
+ BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(ERR_CONNECTION_FAILED, req1.WaitForResult());
@@ -1321,25 +1539,87 @@
EXPECT_EQ(&req3, request_order_[2]);
}
-TEST_F(ClientSocketPoolBaseTest, DISABLED_LoadState) {
+TEST_F(ClientSocketPoolBaseTest, LoadState) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
connect_job_factory_->set_job_type(
TestConnectJob::kMockAdvancingLoadStateJob);
TestSocketRequest req1(&request_order_, &completion_count_);
- int rv = InitHandle(
- req1.handle(), "a", kDefaultPriority, &req1, pool_.get(), NULL);
+ int rv = req1.handle()->Init("a", params_, kDefaultPriority, &req1, pool_,
+ BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(LOAD_STATE_IDLE, req1.handle()->GetLoadState());
MessageLoop::current()->RunAllPending();
TestSocketRequest req2(&request_order_, &completion_count_);
- rv = InitHandle(req2.handle(), "a", kDefaultPriority, &req2,
- pool_.get(), NULL);
+ rv = req2.handle()->Init("a", params_, kDefaultPriority, &req2, pool_,
+ BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
- EXPECT_EQ(LOAD_STATE_WAITING_FOR_CACHE, req1.handle()->GetLoadState());
- EXPECT_EQ(LOAD_STATE_WAITING_FOR_CACHE, req2.handle()->GetLoadState());
+ EXPECT_NE(LOAD_STATE_IDLE, req1.handle()->GetLoadState());
+ EXPECT_NE(LOAD_STATE_IDLE, req2.handle()->GetLoadState());
+}
+
+TEST_F(ClientSocketPoolBaseTest, Recoverable) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockRecoverableJob);
+
+ TestSocketRequest req(&request_order_, &completion_count_);
+ EXPECT_EQ(ERR_PROXY_AUTH_REQUESTED, req.handle()->Init("a", params_,
+ kDefaultPriority,
+ &req, pool_,
+ BoundNetLog()));
+ EXPECT_TRUE(req.handle()->is_initialized());
+ EXPECT_TRUE(req.handle()->socket());
+ req.handle()->Reset();
+}
+
+TEST_F(ClientSocketPoolBaseTest, AsyncRecoverable) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ connect_job_factory_->set_job_type(
+ TestConnectJob::kMockPendingRecoverableJob);
+ TestSocketRequest req(&request_order_, &completion_count_);
+ EXPECT_EQ(ERR_IO_PENDING, req.handle()->Init("a", params_, kDefaultPriority,
+ &req, pool_, BoundNetLog()));
+ EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState("a", req.handle()));
+ EXPECT_EQ(ERR_PROXY_AUTH_REQUESTED, req.WaitForResult());
+ EXPECT_TRUE(req.handle()->is_initialized());
+ EXPECT_TRUE(req.handle()->socket());
+ req.handle()->Reset();
+}
+
+TEST_F(ClientSocketPoolBaseTest, AdditionalErrorStateSynchronous) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(
+ TestConnectJob::kMockAdditionalErrorStateJob);
+
+ TestSocketRequest req(&request_order_, &completion_count_);
+ EXPECT_EQ(ERR_CONNECTION_FAILED, req.handle()->Init("a", params_,
+ kDefaultPriority, &req,
+ pool_, BoundNetLog()));
+ EXPECT_FALSE(req.handle()->is_initialized());
+ EXPECT_FALSE(req.handle()->socket());
+ EXPECT_TRUE(req.handle()->is_ssl_error());
+ EXPECT_FALSE(req.handle()->ssl_error_response_info().headers.get() == NULL);
+ req.handle()->Reset();
+}
+
+TEST_F(ClientSocketPoolBaseTest, AdditionalErrorStateAsynchronous) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ connect_job_factory_->set_job_type(
+ TestConnectJob::kMockPendingAdditionalErrorStateJob);
+ TestSocketRequest req(&request_order_, &completion_count_);
+ EXPECT_EQ(ERR_IO_PENDING, req.handle()->Init("a", params_, kDefaultPriority,
+ &req, pool_, BoundNetLog()));
+ EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState("a", req.handle()));
+ EXPECT_EQ(ERR_CONNECTION_FAILED, req.WaitForResult());
+ EXPECT_FALSE(req.handle()->is_initialized());
+ EXPECT_FALSE(req.handle()->socket());
+ EXPECT_TRUE(req.handle()->is_ssl_error());
+ EXPECT_FALSE(req.handle()->ssl_error_response_info().headers.get() == NULL);
+ req.handle()->Reset();
}
TEST_F(ClientSocketPoolBaseTest, CleanupTimedOutIdleSockets) {
@@ -1353,12 +1633,12 @@
// Startup two mock pending connect jobs, which will sit in the MessageLoop.
TestSocketRequest req(&request_order_, &completion_count_);
- int rv = InitHandle(req.handle(), "a", LOWEST, &req, pool_.get(), NULL);
+ int rv = req.handle()->Init("a", params_, LOWEST, &req, pool_, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState("a", req.handle()));
TestSocketRequest req2(&request_order_, &completion_count_);
- rv = InitHandle(req2.handle(), "a", LOWEST, &req2, pool_.get(), NULL);
+ rv = req2.handle()->Init("a", params_, LOWEST, &req2, pool_, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState("a", req2.handle()));
@@ -1384,12 +1664,15 @@
// used socket. Request it to make sure that it's used.
pool_->CleanupTimedOutIdleSockets();
- rv = InitHandle(req.handle(), "a", LOWEST, &req, pool_.get(), NULL);
+ CapturingBoundNetLog log(CapturingNetLog::kUnbounded);
+ rv = req.handle()->Init("a", params_, LOWEST, &req, pool_, log.bound());
EXPECT_EQ(OK, rv);
EXPECT_TRUE(req.handle()->is_reused());
+ EXPECT_TRUE(LogContainsEntryWithType(
+ log.entries(), 1, NetLog::TYPE_SOCKET_POOL_REUSED_AN_EXISTING_SOCKET));
}
-// Make sure that we process all pending requests even when we're stalling
+// Make sure that we process all pending requests even when we're stalling
// because of multiple releasing disconnected sockets.
TEST_F(ClientSocketPoolBaseTest, MultipleReleasingDisconnectedSockets) {
CreatePoolWithIdleTimeouts(
@@ -1402,19 +1685,19 @@
// Startup 4 connect jobs. Two of them will be pending.
TestSocketRequest req(&request_order_, &completion_count_);
- int rv = InitHandle(req.handle(), "a", LOWEST, &req, pool_.get(), NULL);
+ int rv = req.handle()->Init("a", params_, LOWEST, &req, pool_, BoundNetLog());
EXPECT_EQ(OK, rv);
TestSocketRequest req2(&request_order_, &completion_count_);
- rv = InitHandle(req2.handle(), "a", LOWEST, &req2, pool_.get(), NULL);
+ rv = req2.handle()->Init("a", params_, LOWEST, &req2, pool_, BoundNetLog());
EXPECT_EQ(OK, rv);
TestSocketRequest req3(&request_order_, &completion_count_);
- rv = InitHandle(req3.handle(), "a", LOWEST, &req3, pool_.get(), NULL);
+ rv = req3.handle()->Init("a", params_, LOWEST, &req3, pool_, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
TestSocketRequest req4(&request_order_, &completion_count_);
- rv = InitHandle(req4.handle(), "a", LOWEST, &req4, pool_.get(), NULL);
+ rv = req4.handle()->Init("a", params_, LOWEST, &req4, pool_, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
// Release two disconnected sockets.
@@ -1430,6 +1713,390 @@
EXPECT_FALSE(req4.handle()->is_reused());
}
+// Regression test for http://crbug.com/42267.
+// When DoReleaseSocket() is processed for one socket, it is blocked because the
+// other stalled groups all have releasing sockets, so no progress can be made.
+TEST_F(ClientSocketPoolBaseTest, SocketLimitReleasingSockets) {
+ CreatePoolWithIdleTimeouts(
+ 4 /* socket limit */, 4 /* socket limit per group */,
+ base::TimeDelta(), // Time out unused sockets immediately.
+ base::TimeDelta::FromDays(1)); // Don't time out used sockets.
+
+ connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
+
+ // Max out the socket limit with 2 per group.
+
+ scoped_ptr<TestSocketRequest> req_a[4];
+ scoped_ptr<TestSocketRequest> req_b[4];
+
+ for (int i = 0; i < 2; ++i) {
+ req_a[i].reset(new TestSocketRequest(&request_order_, &completion_count_));
+ req_b[i].reset(new TestSocketRequest(&request_order_, &completion_count_));
+ EXPECT_EQ(OK, req_a[i]->handle()->Init("a", params_, LOWEST, req_a[i].get(),
+ pool_, BoundNetLog()));
+ EXPECT_EQ(OK, req_b[i]->handle()->Init("b", params_, LOWEST, req_b[i].get(),
+ pool_, BoundNetLog()));
+ }
+
+ // Make 4 pending requests, 2 per group.
+
+ for (int i = 2; i < 4; ++i) {
+ req_a[i].reset(new TestSocketRequest(&request_order_, &completion_count_));
+ req_b[i].reset(new TestSocketRequest(&request_order_, &completion_count_));
+ EXPECT_EQ(ERR_IO_PENDING, req_a[i]->handle()->Init("a", params_, LOWEST,
+ req_a[i].get(), pool_,
+ BoundNetLog()));
+ EXPECT_EQ(ERR_IO_PENDING, req_b[i]->handle()->Init("b", params_, LOWEST,
+ req_b[i].get(), pool_,
+ BoundNetLog()));
+ }
+
+ // Release b's socket first. The order is important, because in
+ // DoReleaseSocket(), we'll process b's released socket, and since both b and
+ // a are stalled, but 'a' is lower lexicographically, we'll process group 'a'
+ // first, which has a releasing socket, so it refuses to start up another
+ // ConnectJob. So, we used to infinite loop on this.
+ req_b[0]->handle()->socket()->Disconnect();
+ req_b[0]->handle()->Reset();
+ req_a[0]->handle()->socket()->Disconnect();
+ req_a[0]->handle()->Reset();
+
+ // Used to get stuck here.
+ MessageLoop::current()->RunAllPending();
+
+ req_b[1]->handle()->socket()->Disconnect();
+ req_b[1]->handle()->Reset();
+ req_a[1]->handle()->socket()->Disconnect();
+ req_a[1]->handle()->Reset();
+
+ for (int i = 2; i < 4; ++i) {
+ EXPECT_EQ(OK, req_b[i]->WaitForResult());
+ EXPECT_EQ(OK, req_a[i]->WaitForResult());
+ }
+}
+
+TEST_F(ClientSocketPoolBaseTest,
+ ReleasingDisconnectedSocketsMaintainsPriorityOrder) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+
+ EXPECT_EQ(OK, requests_[0]->WaitForResult());
+ EXPECT_EQ(OK, requests_[1]->WaitForResult());
+ EXPECT_EQ(2u, completion_count_);
+
+ // Releases one connection.
+ EXPECT_TRUE(ReleaseOneConnection(NO_KEEP_ALIVE));
+ EXPECT_EQ(OK, requests_[2]->WaitForResult());
+
+ EXPECT_TRUE(ReleaseOneConnection(NO_KEEP_ALIVE));
+ EXPECT_EQ(OK, requests_[3]->WaitForResult());
+ EXPECT_EQ(4u, completion_count_);
+
+ EXPECT_EQ(1, GetOrderOfRequest(1));
+ EXPECT_EQ(2, GetOrderOfRequest(2));
+ EXPECT_EQ(3, GetOrderOfRequest(3));
+ EXPECT_EQ(4, GetOrderOfRequest(4));
+
+ // Make sure we test order of all requests made.
+ EXPECT_EQ(kIndexOutOfBounds, GetOrderOfRequest(5));
+}
+
+class TestReleasingSocketRequest : public CallbackRunner< Tuple1<int> > {
+ public:
+ TestReleasingSocketRequest(TestClientSocketPool* pool, int expected_result,
+ bool reset_releasing_handle)
+ : pool_(pool),
+ expected_result_(expected_result),
+ reset_releasing_handle_(reset_releasing_handle) {}
+
+ ClientSocketHandle* handle() { return &handle_; }
+
+ int WaitForResult() {
+ return callback_.WaitForResult();
+ }
+
+ virtual void RunWithParams(const Tuple1<int>& params) {
+ callback_.RunWithParams(params);
+ if (reset_releasing_handle_)
+ handle_.Reset();
+ scoped_refptr<TestSocketParams> con_params = new TestSocketParams();
+ EXPECT_EQ(expected_result_, handle2_.Init("a", con_params, kDefaultPriority,
+ &callback2_, pool_,
+ BoundNetLog()));
+ }
+
+ private:
+ scoped_refptr<TestClientSocketPool> pool_;
+ int expected_result_;
+ bool reset_releasing_handle_;
+ ClientSocketHandle handle_;
+ ClientSocketHandle handle2_;
+ TestCompletionCallback callback_;
+ TestCompletionCallback callback2_;
+};
+
+
+TEST_F(ClientSocketPoolBaseTest, AdditionalErrorSocketsDontUseSlot) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ EXPECT_EQ(OK, StartRequest("b", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("b", kDefaultPriority));
+
+ EXPECT_EQ(static_cast<int>(requests_.size()),
+ client_socket_factory_.allocation_count());
+
+ connect_job_factory_->set_job_type(
+ TestConnectJob::kMockPendingAdditionalErrorStateJob);
+ TestReleasingSocketRequest req(pool_.get(), OK, false);
+ EXPECT_EQ(ERR_IO_PENDING, req.handle()->Init("a", params_, kDefaultPriority,
+ &req, pool_, BoundNetLog()));
+ // The next job should complete synchronously
+ connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
+
+ EXPECT_EQ(ERR_CONNECTION_FAILED, req.WaitForResult());
+ EXPECT_FALSE(req.handle()->is_initialized());
+ EXPECT_FALSE(req.handle()->socket());
+ EXPECT_TRUE(req.handle()->is_ssl_error());
+ EXPECT_FALSE(req.handle()->ssl_error_response_info().headers.get() == NULL);
+}
+
+// http://crbug.com/44724 regression test.
+// We start releasing the pool when we flush on network change. When that
+// happens, the only active references are in the ClientSocketHandles. When a
+// ConnectJob completes and calls back into the last ClientSocketHandle, that
+// callback can release the last reference and delete the pool. After the
+// callback finishes, we go back to the stack frame within the now-deleted pool.
+// Executing any code that refers to members of the now-deleted pool can cause
+// crashes.
+TEST_F(ClientSocketPoolBaseTest, CallbackThatReleasesPool) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingFailingJob);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING, handle.Init("a", params_, kDefaultPriority,
+ &callback, pool_, BoundNetLog()));
+
+ // Simulate flushing the pool.
+ pool_ = NULL;
+
+ // We'll call back into this now.
+ callback.WaitForResult();
+}
+
+TEST_F(ClientSocketPoolBaseTest, DoNotReuseSocketAfterFlush) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING, handle.Init("a", params_, kDefaultPriority,
+ &callback, pool_, BoundNetLog()));
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_EQ(ClientSocketHandle::UNUSED, handle.reuse_type());
+
+ pool_->Flush();
+
+ handle.Reset();
+ MessageLoop::current()->RunAllPending();
+
+ EXPECT_EQ(ERR_IO_PENDING, handle.Init("a", params_, kDefaultPriority,
+ &callback, pool_, BoundNetLog()));
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_EQ(ClientSocketHandle::UNUSED, handle.reuse_type());
+}
+
+// Cancel a pending socket request while we're at max sockets,
+// and verify that the backup socket firing doesn't cause a crash.
+TEST_F(ClientSocketPoolBaseTest, BackupSocketCancelAtMaxSockets) {
+ // Max 4 sockets globally, max 4 sockets per group.
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSockets);
+ pool_->EnableBackupJobs();
+
+ // Create the first socket and set to ERR_IO_PENDING. This creates a
+ // backup job.
+ connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob);
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING, handle.Init("bar", params_, kDefaultPriority,
+ &callback, pool_, BoundNetLog()));
+
+ // Start (MaxSockets - 1) connected sockets to reach max sockets.
+ connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
+ ClientSocketHandle handles[kDefaultMaxSockets];
+ for (int i = 1; i < kDefaultMaxSockets; ++i) {
+ TestCompletionCallback callback;
+ EXPECT_EQ(OK, handles[i].Init("bar", params_, kDefaultPriority, &callback,
+ pool_, BoundNetLog()));
+ }
+
+ MessageLoop::current()->RunAllPending();
+
+ // Cancel the pending request.
+ handle.Reset();
+
+ // Wait for the backup timer to fire (add some slop to ensure it fires)
+ PlatformThread::Sleep(ClientSocketPool::kMaxConnectRetryIntervalMs / 2 * 3);
+
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(kDefaultMaxSockets, client_socket_factory_.allocation_count());
+}
+
+// Test delayed socket binding for the case where we have two connects,
+// and while one is waiting on a connect, the other frees up.
+// The socket waiting on a connect should switch immediately to the freed
+// up socket.
+TEST_F(ClientSocketPoolBaseTest, DelayedSocketBindingWaitingForConnect) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ ClientSocketHandle handle1;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING, handle1.Init("a", params_, kDefaultPriority,
+ &callback, pool_, BoundNetLog()));
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ // No idle sockets, no pending jobs.
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+
+ // Create a second socket to the same host, but this one will wait.
+ connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob);
+ ClientSocketHandle handle2;
+ EXPECT_EQ(ERR_IO_PENDING, handle2.Init("a", params_, kDefaultPriority,
+ &callback, pool_, BoundNetLog()));
+ // No idle sockets, and one connecting job.
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+
+ // Return the first handle to the pool. This will initiate the delayed
+ // binding.
+ handle1.Reset();
+
+ MessageLoop::current()->RunAllPending();
+
+ // Still no idle sockets, still one pending connect job.
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+
+ // The second socket connected, even though it was a Waiting Job.
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ // And we can see there is still one job waiting.
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+
+ // Finally, signal the waiting Connect.
+ client_socket_factory_.SignalJobs();
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+
+ MessageLoop::current()->RunAllPending();
+}
+
+// Test delayed socket binding when a group is at capacity and one
+// of the group's sockets frees up.
+TEST_F(ClientSocketPoolBaseTest, DelayedSocketBindingAtGroupCapacity) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ ClientSocketHandle handle1;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING, handle1.Init("a", params_, kDefaultPriority,
+ &callback, pool_, BoundNetLog()));
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ // No idle sockets, no pending jobs.
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+
+ // Create a second socket to the same host, but this one will wait.
+ connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob);
+ ClientSocketHandle handle2;
+ EXPECT_EQ(ERR_IO_PENDING, handle2.Init("a", params_, kDefaultPriority,
+ &callback, pool_, BoundNetLog()));
+ // No idle sockets, and one connecting job.
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+
+ // Return the first handle to the pool. This will initiate the delayed
+ // binding.
+ handle1.Reset();
+
+ MessageLoop::current()->RunAllPending();
+
+ // Still no idle sockets, still one pending connect job.
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+
+ // The second socket connected, even though it was a Waiting Job.
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ // And we can see there is still one job waiting.
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+
+ // Finally, signal the waiting Connect.
+ client_socket_factory_.SignalJobs();
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+
+ MessageLoop::current()->RunAllPending();
+}
+
+// Test out the case where we have one socket connected, one
+// connecting, when the first socket finishes and goes idle.
+// Although the second connection is pending, th second request
+// should complete, by taking the first socket's idle socket.
+TEST_F(ClientSocketPoolBaseTest, DelayedSocketBindingAtStall) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ ClientSocketHandle handle1;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING, handle1.Init("a", params_, kDefaultPriority,
+ &callback, pool_, BoundNetLog()));
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ // No idle sockets, no pending jobs.
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+
+ // Create a second socket to the same host, but this one will wait.
+ connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob);
+ ClientSocketHandle handle2;
+ EXPECT_EQ(ERR_IO_PENDING, handle2.Init("a", params_, kDefaultPriority,
+ &callback, pool_, BoundNetLog()));
+ // No idle sockets, and one connecting job.
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+
+ // Return the first handle to the pool. This will initiate the delayed
+ // binding.
+ handle1.Reset();
+
+ MessageLoop::current()->RunAllPending();
+
+ // Still no idle sockets, still one pending connect job.
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+
+ // The second socket connected, even though it was a Waiting Job.
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ // And we can see there is still one job waiting.
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+
+ // Finally, signal the waiting Connect.
+ client_socket_factory_.SignalJobs();
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+
+ MessageLoop::current()->RunAllPending();
+}
+
} // namespace
} // namespace net
diff --git a/net/socket/client_socket_pool_histograms.cc b/net/socket/client_socket_pool_histograms.cc
new file mode 100644
index 0000000..e86543d
--- /dev/null
+++ b/net/socket/client_socket_pool_histograms.cc
@@ -0,0 +1,56 @@
+// 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 "net/socket/client_socket_pool_histograms.h"
+
+#include <string>
+
+#include "base/histogram.h"
+#include "net/socket/client_socket_handle.h"
+
+namespace net {
+
+ClientSocketPoolHistograms::ClientSocketPoolHistograms(
+ const std::string& pool_name) {
+ // UMA_HISTOGRAM_ENUMERATION
+ socket_type_ = LinearHistogram::FactoryGet("Net.SocketType_" + pool_name, 1,
+ ClientSocketHandle::NUM_TYPES, ClientSocketHandle::NUM_TYPES + 1,
+ Histogram::kUmaTargetedHistogramFlag);
+ // UMA_HISTOGRAM_CUSTOM_TIMES
+ request_time_ = Histogram::FactoryTimeGet(
+ "Net.SocketRequestTime_" + pool_name,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10),
+ 100, Histogram::kUmaTargetedHistogramFlag);
+ // UMA_HISTOGRAM_CUSTOM_TIMES
+ unused_idle_time_ = Histogram::FactoryTimeGet(
+ "Net.SocketIdleTimeBeforeNextUse_UnusedSocket_" + pool_name,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(6),
+ 100, Histogram::kUmaTargetedHistogramFlag);
+ // UMA_HISTOGRAM_CUSTOM_TIMES
+ reused_idle_time_ = Histogram::FactoryTimeGet(
+ "Net.SocketIdleTimeBeforeNextUse_ReusedSocket_" + pool_name,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(6),
+ 100, Histogram::kUmaTargetedHistogramFlag);
+}
+
+void ClientSocketPoolHistograms::AddSocketType(int type) const {
+ socket_type_->Add(type);
+}
+
+void ClientSocketPoolHistograms::AddRequestTime(base::TimeDelta time) const {
+ request_time_->AddTime(time);
+}
+
+void ClientSocketPoolHistograms::AddUnusedIdleTime(base::TimeDelta time) const {
+ unused_idle_time_->AddTime(time);
+}
+
+void ClientSocketPoolHistograms::AddReusedIdleTime(base::TimeDelta time) const {
+ reused_idle_time_->AddTime(time);
+}
+
+} // namespace net
diff --git a/net/socket/client_socket_pool_histograms.h b/net/socket/client_socket_pool_histograms.h
new file mode 100644
index 0000000..1aea112
--- /dev/null
+++ b/net/socket/client_socket_pool_histograms.h
@@ -0,0 +1,37 @@
+// 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.
+
+#ifndef NET_SOCKET_CLIENT_SOCKET_POOL_HISTOGRAMS_H_
+#define NET_SOCKET_CLIENT_SOCKET_POOL_HISTOGRAMS_H_
+
+#include <string>
+
+#include "base/histogram.h"
+#include "base/ref_counted.h"
+
+namespace net {
+
+class ClientSocketPoolHistograms
+ : public base::RefCounted<ClientSocketPoolHistograms> {
+ public:
+ ClientSocketPoolHistograms(const std::string& pool_name);
+
+ void AddSocketType(int socket_reuse_type) const;
+ void AddRequestTime(base::TimeDelta time) const;
+ void AddUnusedIdleTime(base::TimeDelta time) const;
+ void AddReusedIdleTime(base::TimeDelta time) const;
+
+ private:
+ friend class base::RefCounted<ClientSocketPoolHistograms>;
+ ~ClientSocketPoolHistograms() {}
+
+ scoped_refptr<Histogram> socket_type_;
+ scoped_refptr<Histogram> request_time_;
+ scoped_refptr<Histogram> unused_idle_time_;
+ scoped_refptr<Histogram> reused_idle_time_;
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_CLIENT_SOCKET_POOL_HISTOGRAMS_H_
diff --git a/net/socket/socket_test_util.cc b/net/socket/socket_test_util.cc
index 283ae35..0d75516 100644
--- a/net/socket/socket_test_util.cc
+++ b/net/socket/socket_test_util.cc
@@ -1,23 +1,124 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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 "net/socket/socket_test_util.h"
#include <algorithm>
+#include <vector>
+
#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "base/message_loop.h"
+#include "base/time.h"
+#include "net/base/address_family.h"
+#include "net/base/auth.h"
+#include "net/base/host_resolver_proc.h"
#include "net/base/ssl_info.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_response_headers.h"
+#include "net/socket/client_socket_pool_histograms.h"
#include "net/socket/socket.h"
#include "testing/gtest/include/gtest/gtest.h"
+#define NET_TRACE(level, s) DLOG(level) << s << __FUNCTION__ << "() "
+
namespace net {
-MockClientSocket::MockClientSocket()
+namespace {
+
+inline char AsciifyHigh(char x) {
+ char nybble = static_cast<char>((x >> 4) & 0x0F);
+ return nybble + ((nybble < 0x0A) ? '0' : 'A' - 10);
+}
+
+inline char AsciifyLow(char x) {
+ char nybble = static_cast<char>((x >> 0) & 0x0F);
+ return nybble + ((nybble < 0x0A) ? '0' : 'A' - 10);
+}
+
+inline char Asciify(char x) {
+ if ((x < 0) || !isprint(x))
+ return '.';
+ return x;
+}
+
+void DumpData(const char* data, int data_len) {
+ if (logging::LOG_INFO < logging::GetMinLogLevel())
+ return;
+ DLOG(INFO) << "Length: " << data_len;
+ const char* pfx = "Data: ";
+ if (!data || (data_len <= 0)) {
+ DLOG(INFO) << pfx << "<None>";
+ } else {
+ int i;
+ for (i = 0; i <= (data_len - 4); i += 4) {
+ DLOG(INFO) << pfx
+ << AsciifyHigh(data[i + 0]) << AsciifyLow(data[i + 0])
+ << AsciifyHigh(data[i + 1]) << AsciifyLow(data[i + 1])
+ << AsciifyHigh(data[i + 2]) << AsciifyLow(data[i + 2])
+ << AsciifyHigh(data[i + 3]) << AsciifyLow(data[i + 3])
+ << " '"
+ << Asciify(data[i + 0])
+ << Asciify(data[i + 1])
+ << Asciify(data[i + 2])
+ << Asciify(data[i + 3])
+ << "'";
+ pfx = " ";
+ }
+ // Take care of any 'trailing' bytes, if data_len was not a multiple of 4.
+ switch (data_len - i) {
+ case 3:
+ DLOG(INFO) << pfx
+ << AsciifyHigh(data[i + 0]) << AsciifyLow(data[i + 0])
+ << AsciifyHigh(data[i + 1]) << AsciifyLow(data[i + 1])
+ << AsciifyHigh(data[i + 2]) << AsciifyLow(data[i + 2])
+ << " '"
+ << Asciify(data[i + 0])
+ << Asciify(data[i + 1])
+ << Asciify(data[i + 2])
+ << " '";
+ break;
+ case 2:
+ DLOG(INFO) << pfx
+ << AsciifyHigh(data[i + 0]) << AsciifyLow(data[i + 0])
+ << AsciifyHigh(data[i + 1]) << AsciifyLow(data[i + 1])
+ << " '"
+ << Asciify(data[i + 0])
+ << Asciify(data[i + 1])
+ << " '";
+ break;
+ case 1:
+ DLOG(INFO) << pfx
+ << AsciifyHigh(data[i + 0]) << AsciifyLow(data[i + 0])
+ << " '"
+ << Asciify(data[i + 0])
+ << " '";
+ break;
+ }
+ }
+}
+
+void DumpMockRead(const MockRead& r) {
+ if (logging::LOG_INFO < logging::GetMinLogLevel())
+ return;
+ DLOG(INFO) << "Async: " << r.async;
+ DLOG(INFO) << "Result: " << r.result;
+ DumpData(r.data, r.data_len);
+ const char* stop = (r.sequence_number & MockRead::STOPLOOP) ? " (STOP)" : "";
+ DLOG(INFO) << "Stage: " << (r.sequence_number & ~MockRead::STOPLOOP)
+ << stop;
+ DLOG(INFO) << "Time: " << r.time_stamp.ToInternalValue();
+}
+
+} // namespace
+
+MockClientSocket::MockClientSocket(net::NetLog* net_log)
: ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)),
- connected_(false) {
+ connected_(false),
+ net_log_(NetLog::Source(), net_log) {
}
void MockClientSocket::GetSSLInfo(net::SSLInfo* ssl_info) {
@@ -47,9 +148,9 @@
return connected_;
}
-int MockClientSocket::GetPeerName(struct sockaddr* name, socklen_t* namelen) {
- memset(reinterpret_cast<char *>(name), 0, *namelen);
- return net::OK;
+int MockClientSocket::GetPeerAddress(AddressList* address) const {
+ return net::SystemHostResolverProc("localhost", ADDRESS_FAMILY_UNSPECIFIED,
+ 0, address, NULL);
}
void MockClientSocket::RunCallbackAsync(net::CompletionCallback* callback,
@@ -66,8 +167,10 @@
}
MockTCPClientSocket::MockTCPClientSocket(const net::AddressList& addresses,
+ net::NetLog* net_log,
net::SocketDataProvider* data)
- : addresses_(addresses),
+ : MockClientSocket(net_log),
+ addresses_(addresses),
data_(data),
read_offset_(0),
read_data_(false, net::ERR_UNEXPECTED),
@@ -80,8 +183,7 @@
data_->Reset();
}
-int MockTCPClientSocket::Connect(net::CompletionCallback* callback,
- LoadLog* load_log) {
+int MockTCPClientSocket::Connect(net::CompletionCallback* callback) {
if (connected_)
return net::OK;
connected_ = true;
@@ -92,6 +194,11 @@
return data_->connect_data().result;
}
+void MockTCPClientSocket::Disconnect() {
+ MockClientSocket::Disconnect();
+ pending_callback_ = NULL;
+}
+
bool MockTCPClientSocket::IsConnected() const {
return connected_ && !peer_closed_connection_;
}
@@ -232,12 +339,14 @@
};
MockSSLClientSocket::MockSSLClientSocket(
- net::ClientSocket* transport_socket,
+ net::ClientSocketHandle* transport_socket,
const std::string& hostname,
const net::SSLConfig& ssl_config,
net::SSLSocketDataProvider* data)
- : transport_(transport_socket),
- data_(data) {
+ : MockClientSocket(transport_socket->socket()->NetLog().net_log()),
+ transport_(transport_socket),
+ data_(data),
+ is_npn_state_set_(false) {
DCHECK(data_);
}
@@ -245,15 +354,10 @@
Disconnect();
}
-void MockSSLClientSocket::GetSSLInfo(net::SSLInfo* ssl_info) {
- ssl_info->Reset();
-}
-
-int MockSSLClientSocket::Connect(net::CompletionCallback* callback,
- LoadLog* load_log) {
+int MockSSLClientSocket::Connect(net::CompletionCallback* callback) {
ConnectCallback* connect_callback = new ConnectCallback(
this, callback, data_->connect.result);
- int rv = transport_->Connect(connect_callback, load_log);
+ int rv = transport_->socket()->Connect(connect_callback);
if (rv == net::OK) {
delete connect_callback;
if (data_->connect.async) {
@@ -269,26 +373,45 @@
void MockSSLClientSocket::Disconnect() {
MockClientSocket::Disconnect();
- if (transport_ != NULL)
- transport_->Disconnect();
+ if (transport_->socket() != NULL)
+ transport_->socket()->Disconnect();
}
int MockSSLClientSocket::Read(net::IOBuffer* buf, int buf_len,
net::CompletionCallback* callback) {
- return transport_->Read(buf, buf_len, callback);
+ return transport_->socket()->Read(buf, buf_len, callback);
}
int MockSSLClientSocket::Write(net::IOBuffer* buf, int buf_len,
net::CompletionCallback* callback) {
- return transport_->Write(buf, buf_len, callback);
+ return transport_->socket()->Write(buf, buf_len, callback);
+}
+
+void MockSSLClientSocket::GetSSLInfo(net::SSLInfo* ssl_info) {
+ ssl_info->Reset();
+}
+
+SSLClientSocket::NextProtoStatus MockSSLClientSocket::GetNextProto(
+ std::string* proto) {
+ *proto = data_->next_proto;
+ return data_->next_proto_status;
+}
+
+bool MockSSLClientSocket::wasNpnNegotiated() const {
+ if (is_npn_state_set_)
+ return new_npn_value_;
+ return data_->was_npn_negotiated;
+}
+
+bool MockSSLClientSocket::setWasNpnNegotiated(bool negotiated) {
+ is_npn_state_set_ = true;
+ return new_npn_value_ = negotiated;
}
MockRead StaticSocketDataProvider::GetNextRead() {
- MockRead rv = reads_[read_index_];
- if (reads_[read_index_].result != OK ||
- reads_[read_index_].data_len != 0)
- read_index_++; // Don't advance past an EOF.
- return rv;
+ DCHECK(!at_read_eof());
+ reads_[read_index_].time_stamp = base::Time::Now();
+ return reads_[read_index_++];
}
MockWriteResult StaticSocketDataProvider::OnWrite(const std::string& data) {
@@ -297,9 +420,12 @@
return MockWriteResult(false, data.length());
}
+ DCHECK(!at_write_eof());
+
// Check that what we are writing matches the expectation.
// Then give the mocked return value.
net::MockWrite* w = &writes_[write_index_++];
+ w->time_stamp = base::Time::Now();
int result = w->result;
if (w->data) {
// Note - we can simulate a partial write here. If the expected data
@@ -320,6 +446,26 @@
return MockWriteResult(w->async, result);
}
+const MockRead& StaticSocketDataProvider::PeekRead() const {
+ DCHECK(!at_read_eof());
+ return reads_[read_index_];
+}
+
+const MockWrite& StaticSocketDataProvider::PeekWrite() const {
+ DCHECK(!at_write_eof());
+ return writes_[write_index_];
+}
+
+const MockRead& StaticSocketDataProvider::PeekRead(size_t index) const {
+ DCHECK_LT(index, read_count_);
+ return reads_[index];
+}
+
+const MockWrite& StaticSocketDataProvider::PeekWrite(size_t index) const {
+ DCHECK_LT(index, write_count_);
+ return writes_[index];
+}
+
void StaticSocketDataProvider::Reset() {
read_index_ = 0;
write_index_ = 0;
@@ -348,11 +494,153 @@
reads_.clear();
}
-void DynamicSocketDataProvider::SimulateRead(const char* data) {
+void DynamicSocketDataProvider::SimulateRead(const char* data,
+ const size_t length) {
if (!allow_unconsumed_reads_) {
EXPECT_TRUE(reads_.empty()) << "Unconsumed read: " << reads_.front().data;
}
- reads_.push_back(MockRead(data));
+ reads_.push_back(MockRead(true, data, length));
+}
+
+DelayedSocketData::DelayedSocketData(
+ int write_delay, MockRead* reads, size_t reads_count,
+ MockWrite* writes, size_t writes_count)
+ : StaticSocketDataProvider(reads, reads_count, writes, writes_count),
+ write_delay_(write_delay),
+ ALLOW_THIS_IN_INITIALIZER_LIST(factory_(this)) {
+ DCHECK_GE(write_delay_, 0);
+}
+
+DelayedSocketData::DelayedSocketData(
+ const MockConnect& connect, int write_delay, MockRead* reads,
+ size_t reads_count, MockWrite* writes, size_t writes_count)
+ : StaticSocketDataProvider(reads, reads_count, writes, writes_count),
+ write_delay_(write_delay),
+ ALLOW_THIS_IN_INITIALIZER_LIST(factory_(this)) {
+ DCHECK_GE(write_delay_, 0);
+ set_connect_data(connect);
+}
+
+MockRead DelayedSocketData::GetNextRead() {
+ if (write_delay_)
+ return MockRead(true, ERR_IO_PENDING);
+ return StaticSocketDataProvider::GetNextRead();
+}
+
+MockWriteResult DelayedSocketData::OnWrite(const std::string& data) {
+ MockWriteResult rv = StaticSocketDataProvider::OnWrite(data);
+ // Now that our write has completed, we can allow reads to continue.
+ if (!--write_delay_)
+ MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ factory_.NewRunnableMethod(&DelayedSocketData::CompleteRead), 100);
+ return rv;
+}
+
+void DelayedSocketData::Reset() {
+ set_socket(NULL);
+ factory_.RevokeAll();
+ StaticSocketDataProvider::Reset();
+}
+
+void DelayedSocketData::CompleteRead() {
+ if (socket())
+ socket()->OnReadComplete(GetNextRead());
+}
+
+OrderedSocketData::OrderedSocketData(
+ MockRead* reads, size_t reads_count, MockWrite* writes, size_t writes_count)
+ : StaticSocketDataProvider(reads, reads_count, writes, writes_count),
+ sequence_number_(0), loop_stop_stage_(0), callback_(NULL),
+ blocked_(false), ALLOW_THIS_IN_INITIALIZER_LIST(factory_(this)) {
+}
+
+OrderedSocketData::OrderedSocketData(
+ const MockConnect& connect,
+ MockRead* reads, size_t reads_count,
+ MockWrite* writes, size_t writes_count)
+ : StaticSocketDataProvider(reads, reads_count, writes, writes_count),
+ sequence_number_(0), loop_stop_stage_(0), callback_(NULL),
+ blocked_(false), ALLOW_THIS_IN_INITIALIZER_LIST(factory_(this)) {
+ set_connect_data(connect);
+}
+
+MockRead OrderedSocketData::GetNextRead() {
+ factory_.RevokeAll();
+ blocked_ = false;
+ const MockRead& next_read = StaticSocketDataProvider::PeekRead();
+ if (next_read.sequence_number & MockRead::STOPLOOP)
+ EndLoop();
+ if ((next_read.sequence_number & ~MockRead::STOPLOOP) <=
+ sequence_number_++) {
+ NET_TRACE(INFO, " *** ") << "Stage " << sequence_number_ - 1
+ << ": Read " << read_index();
+ DumpMockRead(next_read);
+ return StaticSocketDataProvider::GetNextRead();
+ }
+ NET_TRACE(INFO, " *** ") << "Stage " << sequence_number_ - 1
+ << ": I/O Pending";
+ MockRead result = MockRead(true, ERR_IO_PENDING);
+ DumpMockRead(result);
+ blocked_ = true;
+ return result;
+}
+
+MockWriteResult OrderedSocketData::OnWrite(const std::string& data) {
+ NET_TRACE(INFO, " *** ") << "Stage " << sequence_number_
+ << ": Write " << write_index();
+ DumpMockRead(PeekWrite());
+ ++sequence_number_;
+ if (blocked_) {
+ // TODO(willchan): This 100ms delay seems to work around some weirdness. We
+ // should probably fix the weirdness. One example is in SpdyStream,
+ // DoSendRequest() will return ERR_IO_PENDING, and there's a race. If the
+ // SYN_REPLY causes OnResponseReceived() to get called before
+ // SpdyStream::ReadResponseHeaders() is called, we hit a NOTREACHED().
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ factory_.NewRunnableMethod(&OrderedSocketData::CompleteRead), 100);
+ }
+ return StaticSocketDataProvider::OnWrite(data);
+}
+
+void OrderedSocketData::Reset() {
+ NET_TRACE(INFO, " *** ") << "Stage "
+ << sequence_number_ << ": Reset()";
+ sequence_number_ = 0;
+ loop_stop_stage_ = 0;
+ set_socket(NULL);
+ factory_.RevokeAll();
+ StaticSocketDataProvider::Reset();
+}
+
+void OrderedSocketData::EndLoop() {
+ // If we've already stopped the loop, don't do it again until we've advanced
+ // to the next sequence_number.
+ NET_TRACE(INFO, " *** ") << "Stage " << sequence_number_ << ": EndLoop()";
+ if (loop_stop_stage_ > 0) {
+ const MockRead& next_read = StaticSocketDataProvider::PeekRead();
+ if ((next_read.sequence_number & ~MockRead::STOPLOOP) >
+ loop_stop_stage_) {
+ NET_TRACE(INFO, " *** ") << "Stage " << sequence_number_
+ << ": Clearing stop index";
+ loop_stop_stage_ = 0;
+ } else {
+ return;
+ }
+ }
+ // Record the sequence_number at which we stopped the loop.
+ NET_TRACE(INFO, " *** ") << "Stage " << sequence_number_
+ << ": Posting Quit at read " << read_index();
+ loop_stop_stage_ = sequence_number_;
+ if (callback_)
+ callback_->RunWithParams(Tuple1<int>(ERR_IO_PENDING));
+}
+
+void OrderedSocketData::CompleteRead() {
+ if (socket()) {
+ NET_TRACE(INFO, " *** ") << "Stage " << sequence_number_;
+ socket()->OnReadComplete(GetNextRead());
+ }
}
void MockClientSocketFactory::AddSocketDataProvider(
@@ -371,27 +659,29 @@
}
MockTCPClientSocket* MockClientSocketFactory::GetMockTCPClientSocket(
- int index) const {
+ size_t index) const {
+ DCHECK_LT(index, tcp_client_sockets_.size());
return tcp_client_sockets_[index];
}
MockSSLClientSocket* MockClientSocketFactory::GetMockSSLClientSocket(
- int index) const {
+ size_t index) const {
+ DCHECK_LT(index, ssl_client_sockets_.size());
return ssl_client_sockets_[index];
}
ClientSocket* MockClientSocketFactory::CreateTCPClientSocket(
- const AddressList& addresses) {
+ const AddressList& addresses, net::NetLog* net_log) {
SocketDataProvider* data_provider = mock_data_.GetNext();
MockTCPClientSocket* socket =
- new MockTCPClientSocket(addresses, data_provider);
+ new MockTCPClientSocket(addresses, net_log, data_provider);
data_provider->set_socket(socket);
tcp_client_sockets_.push_back(socket);
return socket;
}
SSLClientSocket* MockClientSocketFactory::CreateSSLClientSocket(
- ClientSocket* transport_socket,
+ ClientSocketHandle* transport_socket,
const std::string& hostname,
const SSLConfig& ssl_config) {
MockSSLClientSocket* socket =
@@ -469,4 +759,192 @@
} while (released_one);
}
+MockTCPClientSocketPool::MockConnectJob::MockConnectJob(
+ ClientSocket* socket,
+ ClientSocketHandle* handle,
+ CompletionCallback* callback)
+ : socket_(socket),
+ handle_(handle),
+ user_callback_(callback),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ connect_callback_(this, &MockConnectJob::OnConnect)) {
+}
+
+int MockTCPClientSocketPool::MockConnectJob::Connect() {
+ int rv = socket_->Connect(&connect_callback_);
+ if (rv == OK) {
+ user_callback_ = NULL;
+ OnConnect(OK);
+ }
+ return rv;
+}
+
+bool MockTCPClientSocketPool::MockConnectJob::CancelHandle(
+ const ClientSocketHandle* handle) {
+ if (handle != handle_)
+ return false;
+ socket_.reset();
+ handle_ = NULL;
+ user_callback_ = NULL;
+ return true;
+}
+
+void MockTCPClientSocketPool::MockConnectJob::OnConnect(int rv) {
+ if (!socket_.get())
+ return;
+ if (rv == OK) {
+ handle_->set_socket(socket_.release());
+ } else {
+ socket_.reset();
+ }
+
+ handle_ = NULL;
+
+ if (user_callback_) {
+ CompletionCallback* callback = user_callback_;
+ user_callback_ = NULL;
+ callback->Run(rv);
+ }
+}
+
+MockTCPClientSocketPool::MockTCPClientSocketPool(
+ int max_sockets,
+ int max_sockets_per_group,
+ const scoped_refptr<ClientSocketPoolHistograms>& histograms,
+ ClientSocketFactory* socket_factory)
+ : TCPClientSocketPool(max_sockets, max_sockets_per_group, histograms,
+ NULL, NULL, NULL),
+ client_socket_factory_(socket_factory),
+ release_count_(0),
+ cancel_count_(0) {
+}
+
+int MockTCPClientSocketPool::RequestSocket(const std::string& group_name,
+ const void* socket_params,
+ RequestPriority priority,
+ ClientSocketHandle* handle,
+ CompletionCallback* callback,
+ const BoundNetLog& net_log) {
+ ClientSocket* socket = client_socket_factory_->CreateTCPClientSocket(
+ AddressList(), net_log.net_log());
+ MockConnectJob* job = new MockConnectJob(socket, handle, callback);
+ job_list_.push_back(job);
+ handle->set_pool_id(1);
+ return job->Connect();
+}
+
+void MockTCPClientSocketPool::CancelRequest(const std::string& group_name,
+ ClientSocketHandle* handle) {
+ std::vector<MockConnectJob*>::iterator i;
+ for (i = job_list_.begin(); i != job_list_.end(); ++i) {
+ if ((*i)->CancelHandle(handle)) {
+ cancel_count_++;
+ break;
+ }
+ }
+}
+
+void MockTCPClientSocketPool::ReleaseSocket(const std::string& group_name,
+ ClientSocket* socket, int id) {
+ EXPECT_EQ(1, id);
+ release_count_++;
+ delete socket;
+}
+
+MockTCPClientSocketPool::~MockTCPClientSocketPool() {}
+
+MockSOCKSClientSocketPool::MockSOCKSClientSocketPool(
+ int max_sockets,
+ int max_sockets_per_group,
+ const scoped_refptr<ClientSocketPoolHistograms>& histograms,
+ const scoped_refptr<TCPClientSocketPool>& tcp_pool)
+ : SOCKSClientSocketPool(max_sockets, max_sockets_per_group, histograms,
+ NULL, tcp_pool, NULL),
+ tcp_pool_(tcp_pool) {
+}
+
+int MockSOCKSClientSocketPool::RequestSocket(const std::string& group_name,
+ const void* socket_params,
+ RequestPriority priority,
+ ClientSocketHandle* handle,
+ CompletionCallback* callback,
+ const BoundNetLog& net_log) {
+ return tcp_pool_->RequestSocket(group_name, socket_params, priority, handle,
+ callback, net_log);
+}
+
+void MockSOCKSClientSocketPool::CancelRequest(
+ const std::string& group_name,
+ ClientSocketHandle* handle) {
+ return tcp_pool_->CancelRequest(group_name, handle);
+}
+
+void MockSOCKSClientSocketPool::ReleaseSocket(const std::string& group_name,
+ ClientSocket* socket, int id) {
+ return tcp_pool_->ReleaseSocket(group_name, socket, id);
+}
+
+MockSOCKSClientSocketPool::~MockSOCKSClientSocketPool() {}
+
+MockHttpAuthController::MockHttpAuthController()
+ : HttpAuthController(HttpAuth::AUTH_PROXY, GURL(),
+ scoped_refptr<HttpNetworkSession>(NULL)),
+ data_(NULL),
+ data_index_(0),
+ data_count_(0) {
+}
+
+void MockHttpAuthController::SetMockAuthControllerData(
+ struct MockHttpAuthControllerData* data, size_t count) {
+ data_ = data;
+ data_count_ = count;
+}
+
+int MockHttpAuthController::MaybeGenerateAuthToken(
+ const HttpRequestInfo* request,
+ CompletionCallback* callback,
+ const BoundNetLog& net_log) {
+ return OK;
+}
+
+void MockHttpAuthController::AddAuthorizationHeader(
+ HttpRequestHeaders* authorization_headers) {
+ authorization_headers->AddHeadersFromString(CurrentData().auth_header);
+}
+
+int MockHttpAuthController::HandleAuthChallenge(
+ scoped_refptr<HttpResponseHeaders> headers,
+ bool do_not_send_server_auth,
+ bool establishing_tunnel,
+ const BoundNetLog& net_log) {
+ return OK;
+}
+
+void MockHttpAuthController::ResetAuth(const std::wstring& username,
+ const std::wstring& password) {
+ data_index_++;
+}
+
+bool MockHttpAuthController::HaveAuth() const {
+ return CurrentData().auth_header.size() != 0;
+}
+
+bool MockHttpAuthController::HaveAuthHandler() const {
+ return HaveAuth();
+}
+
+const char kSOCKS5GreetRequest[] = { 0x05, 0x01, 0x00 };
+const int kSOCKS5GreetRequestLength = arraysize(kSOCKS5GreetRequest);
+
+const char kSOCKS5GreetResponse[] = { 0x05, 0x00 };
+const int kSOCKS5GreetResponseLength = arraysize(kSOCKS5GreetResponse);
+
+const char kSOCKS5OkRequest[] =
+ { 0x05, 0x01, 0x00, 0x03, 0x04, 'h', 'o', 's', 't', 0x00, 0x50 };
+const int kSOCKS5OkRequestLength = arraysize(kSOCKS5OkRequest);
+
+const char kSOCKS5OkResponse[] =
+ { 0x05, 0x00, 0x00, 0x01, 127, 0, 0, 1, 0x00, 0x50 };
+const int kSOCKS5OkResponseLength = arraysize(kSOCKS5OkResponse);
+
} // namespace net
diff --git a/net/socket/socket_test_util.h b/net/socket/socket_test_util.h
index 90b4019..2bd0d23 100644
--- a/net/socket/socket_test_util.h
+++ b/net/socket/socket_test_util.h
@@ -1,26 +1,33 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
#ifndef NET_SOCKET_SOCKET_TEST_UTIL_H_
#define NET_SOCKET_SOCKET_TEST_UTIL_H_
+#include <cstring>
#include <deque>
#include <string>
#include <vector>
#include "base/basictypes.h"
+#include "base/callback.h"
#include "base/logging.h"
#include "base/scoped_ptr.h"
#include "base/scoped_vector.h"
#include "net/base/address_list.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
#include "net/base/ssl_config_service.h"
#include "net/base/test_completion_callback.h"
+#include "net/http/http_auth_controller.h"
+#include "net/http/http_proxy_client_socket_pool.h"
#include "net/socket/client_socket_factory.h"
#include "net/socket/client_socket_handle.h"
+#include "net/socket/socks_client_socket_pool.h"
#include "net/socket/ssl_client_socket.h"
+#include "net/socket/tcp_client_socket_pool.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace net {
@@ -35,7 +42,8 @@
};
class ClientSocket;
-class LoadLog;
+class HttpRequestHeaders;
+class HttpResponseHeaders;
class MockClientSocket;
class SSLClientSocket;
@@ -49,29 +57,55 @@
};
struct MockRead {
+ // Flag to indicate that the message loop should be terminated.
+ enum {
+ STOPLOOP = 1 << 31
+ };
+
// Default
- MockRead() : async(false), result(0), data(NULL), data_len(0) {}
+ MockRead() : async(false), result(0), data(NULL), data_len(0),
+ sequence_number(0), time_stamp(base::Time::Now()) {}
// Read failure (no data).
MockRead(bool async, int result) : async(async) , result(result), data(NULL),
- data_len(0) { }
+ data_len(0), sequence_number(0), time_stamp(base::Time::Now()) { }
+
+ // Read failure (no data), with sequence information.
+ MockRead(bool async, int result, int seq) : async(async) , result(result),
+ data(NULL), data_len(0), sequence_number(seq),
+ time_stamp(base::Time::Now()) { }
// Asynchronous read success (inferred data length).
explicit MockRead(const char* data) : async(true), result(0), data(data),
- data_len(strlen(data)) { }
+ data_len(strlen(data)), sequence_number(0),
+ time_stamp(base::Time::Now()) { }
// Read success (inferred data length).
MockRead(bool async, const char* data) : async(async), result(0), data(data),
- data_len(strlen(data)) { }
+ data_len(strlen(data)), sequence_number(0),
+ time_stamp(base::Time::Now()) { }
// Read success.
MockRead(bool async, const char* data, int data_len) : async(async),
- result(0), data(data), data_len(data_len) { }
+ result(0), data(data), data_len(data_len), sequence_number(0),
+ time_stamp(base::Time::Now()) { }
+
+ // Read success with sequence information.
+ MockRead(bool async, const char* data, int data_len, int seq) : async(async),
+ result(0), data(data), data_len(data_len), sequence_number(seq),
+ time_stamp(base::Time::Now()) { }
bool async;
int result;
const char* data;
int data_len;
+
+ // For OrderedSocketData, which only allows reads to occur in a particular
+ // sequence. If a read occurs before the given |sequence_number| is reached,
+ // an ERR_IO_PENDING is returned.
+ int sequence_number; // The sequence number at which a read is allowed
+ // to occur.
+ base::Time time_stamp; // The time stamp at which the operation occurred.
};
// MockWrite uses the same member fields as MockRead, but with different
@@ -121,28 +155,44 @@
// writes.
class StaticSocketDataProvider : public SocketDataProvider {
public:
- StaticSocketDataProvider() : reads_(NULL), read_index_(0),
- writes_(NULL), write_index_(0) {}
- StaticSocketDataProvider(MockRead* r, MockWrite* w) : reads_(r),
- read_index_(0), writes_(w), write_index_(0) {}
+ StaticSocketDataProvider() : reads_(NULL), read_index_(0), read_count_(0),
+ writes_(NULL), write_index_(0), write_count_(0) {}
+ StaticSocketDataProvider(MockRead* reads, size_t reads_count,
+ MockWrite* writes, size_t writes_count)
+ : reads_(reads),
+ read_index_(0),
+ read_count_(reads_count),
+ writes_(writes),
+ write_index_(0),
+ write_count_(writes_count) {
+ }
// SocketDataProvider methods:
virtual MockRead GetNextRead();
virtual MockWriteResult OnWrite(const std::string& data);
virtual void Reset();
- // If the test wishes to verify that all data is consumed, it can include
- // a EOF MockRead or MockWrite, which is a zero-length Read or Write.
- // The test can then call at_read_eof() or at_write_eof() to verify that
- // all data has been consumed.
- bool at_read_eof() const { return reads_[read_index_].data_len == 0; }
- bool at_write_eof() const { return writes_[write_index_].data_len == 0; }
+ // These functions get access to the next available read and write data.
+ const MockRead& PeekRead() const;
+ const MockWrite& PeekWrite() const;
+ // These functions get random access to the read and write data, for timing.
+ const MockRead& PeekRead(size_t index) const;
+ const MockWrite& PeekWrite(size_t index) const;
+ size_t read_index() const { return read_index_; }
+ size_t write_index() const { return write_index_; }
+ size_t read_count() const { return read_count_; }
+ size_t write_count() const { return write_count_; }
+
+ bool at_read_eof() const { return read_index_ >= read_count_; }
+ bool at_write_eof() const { return write_index_ >= write_count_; }
private:
MockRead* reads_;
- int read_index_;
+ size_t read_index_;
+ size_t read_count_;
MockWrite* writes_;
- int write_index_;
+ size_t write_index_;
+ size_t write_count_;
DISALLOW_COPY_AND_ASSIGN(StaticSocketDataProvider);
};
@@ -168,7 +218,10 @@
protected:
// The next time there is a read from this socket, it will return |data|.
// Before calling SimulateRead next time, the previous data must be consumed.
- void SimulateRead(const char* data);
+ void SimulateRead(const char* data, size_t length);
+ void SimulateRead(const char* data) {
+ SimulateRead(data, std::strlen(data));
+ }
private:
std::deque<MockRead> reads_;
@@ -186,9 +239,105 @@
// SSLSocketDataProviders only need to keep track of the return code from calls
// to Connect().
struct SSLSocketDataProvider {
- SSLSocketDataProvider(bool async, int result) : connect(async, result) { }
+ SSLSocketDataProvider(bool async, int result)
+ : connect(async, result),
+ next_proto_status(SSLClientSocket::kNextProtoUnsupported),
+ was_npn_negotiated(false) { }
MockConnect connect;
+ SSLClientSocket::NextProtoStatus next_proto_status;
+ std::string next_proto;
+ bool was_npn_negotiated;
+};
+
+// A DataProvider where the client must write a request before the reads (e.g.
+// the response) will complete.
+class DelayedSocketData : public StaticSocketDataProvider,
+ public base::RefCounted<DelayedSocketData> {
+ public:
+ // |write_delay| the number of MockWrites to complete before allowing
+ // a MockRead to complete.
+ // |reads| the list of MockRead completions.
+ // |writes| the list of MockWrite completions.
+ // Note: All MockReads and MockWrites must be async.
+ // Note: The MockRead and MockWrite lists musts end with a EOF
+ // e.g. a MockRead(true, 0, 0);
+ DelayedSocketData(int write_delay,
+ MockRead* reads, size_t reads_count,
+ MockWrite* writes, size_t writes_count);
+
+ // |connect| the result for the connect phase.
+ // |reads| the list of MockRead completions.
+ // |write_delay| the number of MockWrites to complete before allowing
+ // a MockRead to complete.
+ // |writes| the list of MockWrite completions.
+ // Note: All MockReads and MockWrites must be async.
+ // Note: The MockRead and MockWrite lists musts end with a EOF
+ // e.g. a MockRead(true, 0, 0);
+ DelayedSocketData(const MockConnect& connect, int write_delay,
+ MockRead* reads, size_t reads_count,
+ MockWrite* writes, size_t writes_count);
+
+ virtual MockRead GetNextRead();
+ virtual MockWriteResult OnWrite(const std::string& data);
+ virtual void Reset();
+ void CompleteRead();
+
+ private:
+ int write_delay_;
+ ScopedRunnableMethodFactory<DelayedSocketData> factory_;
+};
+
+// A DataProvider where the reads are ordered.
+// If a read is requested before its sequence number is reached, we return an
+// ERR_IO_PENDING (that way we don't have to explicitly add a MockRead just to
+// wait).
+// The sequence number is incremented on every read and write operation.
+// The message loop may be interrupted by setting the high bit of the sequence
+// number in the MockRead's sequence number. When that MockRead is reached,
+// we post a Quit message to the loop. This allows us to interrupt the reading
+// of data before a complete message has arrived, and provides support for
+// testing server push when the request is issued while the response is in the
+// middle of being received.
+class OrderedSocketData : public StaticSocketDataProvider,
+ public base::RefCounted<OrderedSocketData> {
+ public:
+ // |reads| the list of MockRead completions.
+ // |writes| the list of MockWrite completions.
+ // Note: All MockReads and MockWrites must be async.
+ // Note: The MockRead and MockWrite lists musts end with a EOF
+ // e.g. a MockRead(true, 0, 0);
+ OrderedSocketData(MockRead* reads, size_t reads_count,
+ MockWrite* writes, size_t writes_count);
+
+ // |connect| the result for the connect phase.
+ // |reads| the list of MockRead completions.
+ // |writes| the list of MockWrite completions.
+ // Note: All MockReads and MockWrites must be async.
+ // Note: The MockRead and MockWrite lists musts end with a EOF
+ // e.g. a MockRead(true, 0, 0);
+ OrderedSocketData(const MockConnect& connect,
+ MockRead* reads, size_t reads_count,
+ MockWrite* writes, size_t writes_count);
+
+ virtual MockRead GetNextRead();
+ virtual MockWriteResult OnWrite(const std::string& data);
+ virtual void Reset();
+ void SetCompletionCallback(CompletionCallback* callback) {
+ callback_ = callback;
+ }
+
+ // Posts a quit message to the current message loop, if one is running.
+ void EndLoop();
+
+ void CompleteRead();
+
+ private:
+ int sequence_number_;
+ int loop_stop_stage_;
+ CompletionCallback* callback_;
+ bool blocked_;
+ ScopedRunnableMethodFactory<OrderedSocketData> factory_;
};
// Holds an array of SocketDataProvider elements. As Mock{TCP,SSL}ClientSocket
@@ -201,7 +350,7 @@
}
T* GetNext() {
- DCHECK(next_index_ < data_providers_.size());
+ DCHECK_LT(next_index_, data_providers_.size());
return data_providers_[next_index_++];
}
@@ -239,16 +388,17 @@
// Return |index|-th MockTCPClientSocket (starting from 0) that the factory
// created.
- MockTCPClientSocket* GetMockTCPClientSocket(int index) const;
+ MockTCPClientSocket* GetMockTCPClientSocket(size_t index) const;
// Return |index|-th MockSSLClientSocket (starting from 0) that the factory
// created.
- MockSSLClientSocket* GetMockSSLClientSocket(int index) const;
+ MockSSLClientSocket* GetMockSSLClientSocket(size_t index) const;
// ClientSocketFactory
- virtual ClientSocket* CreateTCPClientSocket(const AddressList& addresses);
+ virtual ClientSocket* CreateTCPClientSocket(const AddressList& addresses,
+ NetLog* net_log);
virtual SSLClientSocket* CreateSSLClientSocket(
- ClientSocket* transport_socket,
+ ClientSocketHandle* transport_socket,
const std::string& hostname,
const SSLConfig& ssl_config);
@@ -263,14 +413,15 @@
class MockClientSocket : public net::SSLClientSocket {
public:
- MockClientSocket();
+ explicit MockClientSocket(net::NetLog* net_log);
// ClientSocket methods:
- virtual int Connect(net::CompletionCallback* callback, LoadLog* load_log) = 0;
+ virtual int Connect(net::CompletionCallback* callback) = 0;
virtual void Disconnect();
virtual bool IsConnected() const;
virtual bool IsConnectedAndIdle() const;
- virtual int GetPeerName(struct sockaddr* name, socklen_t* namelen);
+ virtual int GetPeerAddress(AddressList* address) const;
+ virtual const BoundNetLog& NetLog() const { return net_log_;}
// SSLClientSocket methods:
virtual void GetSSLInfo(net::SSLInfo* ssl_info);
@@ -301,16 +452,18 @@
// True if Connect completed successfully and Disconnect hasn't been called.
bool connected_;
+
+ net::BoundNetLog net_log_;
};
class MockTCPClientSocket : public MockClientSocket {
public:
- MockTCPClientSocket(const net::AddressList& addresses,
+ MockTCPClientSocket(const net::AddressList& addresses, net::NetLog* net_log,
net::SocketDataProvider* socket);
// ClientSocket methods:
- virtual int Connect(net::CompletionCallback* callback,
- LoadLog* load_log);
+ virtual int Connect(net::CompletionCallback* callback);
+ virtual void Disconnect();
virtual bool IsConnected() const;
virtual bool IsConnectedAndIdle() const { return IsConnected(); }
@@ -348,15 +501,14 @@
class MockSSLClientSocket : public MockClientSocket {
public:
MockSSLClientSocket(
- net::ClientSocket* transport_socket,
+ net::ClientSocketHandle* transport_socket,
const std::string& hostname,
const net::SSLConfig& ssl_config,
net::SSLSocketDataProvider* socket);
~MockSSLClientSocket();
- virtual void GetSSLInfo(net::SSLInfo* ssl_info);
-
- virtual int Connect(net::CompletionCallback* callback, LoadLog* load_log);
+ // ClientSocket methods:
+ virtual int Connect(net::CompletionCallback* callback);
virtual void Disconnect();
// Socket methods:
@@ -365,14 +517,22 @@
virtual int Write(net::IOBuffer* buf, int buf_len,
net::CompletionCallback* callback);
+ // SSLClientSocket methods:
+ virtual void GetSSLInfo(net::SSLInfo* ssl_info);
+ virtual NextProtoStatus GetNextProto(std::string* proto);
+ virtual bool wasNpnNegotiated() const;
+ virtual bool setWasNpnNegotiated(bool negotiated);
+
// This MockSocket does not implement the manual async IO feature.
virtual void OnReadComplete(const MockRead& data) { NOTIMPLEMENTED(); }
private:
class ConnectCallback;
- scoped_ptr<ClientSocket> transport_;
+ scoped_ptr<ClientSocketHandle> transport_;
net::SSLSocketDataProvider* data_;
+ bool is_npn_state_set_;
+ bool new_npn_value_;
};
class TestSocketRequest : public CallbackRunner< Tuple1<int> > {
@@ -414,17 +574,17 @@
virtual void TearDown();
template <typename PoolType, typename SocketParams>
- int StartRequestUsingPool(PoolType* socket_pool,
+ int StartRequestUsingPool(const scoped_refptr<PoolType>& socket_pool,
const std::string& group_name,
RequestPriority priority,
- const SocketParams& socket_params) {
- DCHECK(socket_pool);
+ const scoped_refptr<SocketParams>& socket_params) {
+ DCHECK(socket_pool.get());
TestSocketRequest* request = new TestSocketRequest(&request_order_,
&completion_count_);
requests_.push_back(request);
int rv = request->handle()->Init(
group_name, socket_params, priority, request,
- socket_pool, NULL);
+ socket_pool, BoundNetLog());
if (rv != ERR_IO_PENDING)
request_order_.push_back(request);
return rv;
@@ -448,6 +608,143 @@
size_t completion_count_;
};
+class MockTCPClientSocketPool : public TCPClientSocketPool {
+ public:
+ class MockConnectJob {
+ public:
+ MockConnectJob(ClientSocket* socket, ClientSocketHandle* handle,
+ CompletionCallback* callback);
+
+ int Connect();
+ bool CancelHandle(const ClientSocketHandle* handle);
+
+ private:
+ void OnConnect(int rv);
+
+ scoped_ptr<ClientSocket> socket_;
+ ClientSocketHandle* handle_;
+ CompletionCallback* user_callback_;
+ CompletionCallbackImpl<MockConnectJob> connect_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockConnectJob);
+ };
+
+ MockTCPClientSocketPool(
+ int max_sockets,
+ int max_sockets_per_group,
+ const scoped_refptr<ClientSocketPoolHistograms>& histograms,
+ ClientSocketFactory* socket_factory);
+
+ int release_count() const { return release_count_; };
+ int cancel_count() const { return cancel_count_; };
+
+ // TCPClientSocketPool methods.
+ virtual int RequestSocket(const std::string& group_name,
+ const void* socket_params,
+ RequestPriority priority,
+ ClientSocketHandle* handle,
+ CompletionCallback* callback,
+ const BoundNetLog& net_log);
+
+ virtual void CancelRequest(const std::string& group_name,
+ ClientSocketHandle* handle);
+ virtual void ReleaseSocket(const std::string& group_name,
+ ClientSocket* socket, int id);
+
+ protected:
+ virtual ~MockTCPClientSocketPool();
+
+ private:
+ ClientSocketFactory* client_socket_factory_;
+ int release_count_;
+ int cancel_count_;
+ ScopedVector<MockConnectJob> job_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockTCPClientSocketPool);
+};
+
+class MockSOCKSClientSocketPool : public SOCKSClientSocketPool {
+ public:
+ MockSOCKSClientSocketPool(
+ int max_sockets,
+ int max_sockets_per_group,
+ const scoped_refptr<ClientSocketPoolHistograms>& histograms,
+ const scoped_refptr<TCPClientSocketPool>& tcp_pool);
+
+ // SOCKSClientSocketPool methods.
+ virtual int RequestSocket(const std::string& group_name,
+ const void* socket_params,
+ RequestPriority priority,
+ ClientSocketHandle* handle,
+ CompletionCallback* callback,
+ const BoundNetLog& net_log);
+
+ virtual void CancelRequest(const std::string& group_name,
+ ClientSocketHandle* handle);
+ virtual void ReleaseSocket(const std::string& group_name,
+ ClientSocket* socket, int id);
+
+ protected:
+ virtual ~MockSOCKSClientSocketPool();
+
+ private:
+ const scoped_refptr<TCPClientSocketPool> tcp_pool_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockSOCKSClientSocketPool);
+};
+
+struct MockHttpAuthControllerData {
+ MockHttpAuthControllerData(std::string header) : auth_header(header) {}
+
+ std::string auth_header;
+};
+
+class MockHttpAuthController : public HttpAuthController {
+ public:
+ MockHttpAuthController();
+ void SetMockAuthControllerData(struct MockHttpAuthControllerData* data,
+ size_t data_length);
+
+ // HttpAuthController methods.
+ virtual int MaybeGenerateAuthToken(const HttpRequestInfo* request,
+ CompletionCallback* callback,
+ const BoundNetLog& net_log);
+ virtual void AddAuthorizationHeader(
+ HttpRequestHeaders* authorization_headers);
+ virtual int HandleAuthChallenge(scoped_refptr<HttpResponseHeaders> headers,
+ bool do_not_send_server_auth,
+ bool establishing_tunnel,
+ const BoundNetLog& net_log);
+ virtual void ResetAuth(const std::wstring& username,
+ const std::wstring& password);
+ virtual bool HaveAuthHandler() const;
+ virtual bool HaveAuth() const;
+
+ private:
+ virtual ~MockHttpAuthController() {}
+ const struct MockHttpAuthControllerData& CurrentData() const {
+ DCHECK(data_index_ < data_count_);
+ return data_[data_index_];
+ }
+
+ MockHttpAuthControllerData* data_;
+ size_t data_index_;
+ size_t data_count_;
+};
+
+// Constants for a successful SOCKS v5 handshake.
+extern const char kSOCKS5GreetRequest[];
+extern const int kSOCKS5GreetRequestLength;
+
+extern const char kSOCKS5GreetResponse[];
+extern const int kSOCKS5GreetResponseLength;
+
+extern const char kSOCKS5OkRequest[];
+extern const int kSOCKS5OkRequestLength;
+
+extern const char kSOCKS5OkResponse[];
+extern const int kSOCKS5OkResponseLength;
+
} // namespace net
#endif // NET_SOCKET_SOCKET_TEST_UTIL_H_
diff --git a/net/socket/socks5_client_socket.cc b/net/socket/socks5_client_socket.cc
index a4ba814..997fb25 100644
--- a/net/socket/socks5_client_socket.cc
+++ b/net/socket/socks5_client_socket.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -10,32 +10,13 @@
#include "base/string_util.h"
#include "base/trace_event.h"
#include "net/base/io_buffer.h"
-#include "net/base/load_log.h"
+#include "net/base/net_log.h"
#include "net/base/net_util.h"
#include "net/base/sys_addrinfo.h"
+#include "net/socket/client_socket_handle.h"
namespace net {
-namespace {
-
-// Returns a string description of |socks_error|, or NULL if |socks_error| is
-// not a valid SOCKS reply.
-const char* MapSOCKSReplyToErrorString(char socks_error) {
- switch(socks_error) {
- case 1: return "(1) General SOCKS server failure";
- case 2: return "(2) Connection not allowed by ruleset";
- case 3: return "(3) Network unreachable";
- case 4: return "(4) Host unreachable";
- case 5: return "(5) Connection refused";
- case 6: return "(6) TTL expired";
- case 7: return "(7) Command not supported";
- case 8: return "(8) Address type not supported";
- default: return NULL;
- }
-}
-
-} // namespace
-
const unsigned int SOCKS5ClientSocket::kGreetReadHeaderSize = 2;
const unsigned int SOCKS5ClientSocket::kWriteHeaderSize = 10;
const unsigned int SOCKS5ClientSocket::kReadHeaderSize = 5;
@@ -46,7 +27,8 @@
COMPILE_ASSERT(sizeof(struct in_addr) == 4, incorrect_system_size_of_IPv4);
COMPILE_ASSERT(sizeof(struct in6_addr) == 16, incorrect_system_size_of_IPv6);
-SOCKS5ClientSocket::SOCKS5ClientSocket(ClientSocket* transport_socket,
+SOCKS5ClientSocket::SOCKS5ClientSocket(
+ ClientSocketHandle* transport_socket,
const HostResolver::RequestInfo& req_info)
: ALLOW_THIS_IN_INITIALIZER_LIST(
io_callback_(this, &SOCKS5ClientSocket::OnIOComplete)),
@@ -57,17 +39,35 @@
bytes_sent_(0),
bytes_received_(0),
read_header_size(kReadHeaderSize),
- host_request_info_(req_info) {
+ host_request_info_(req_info),
+ net_log_(transport_socket->socket()->NetLog()) {
+}
+
+SOCKS5ClientSocket::SOCKS5ClientSocket(
+ ClientSocket* transport_socket,
+ const HostResolver::RequestInfo& req_info)
+ : ALLOW_THIS_IN_INITIALIZER_LIST(
+ io_callback_(this, &SOCKS5ClientSocket::OnIOComplete)),
+ transport_(new ClientSocketHandle()),
+ next_state_(STATE_NONE),
+ user_callback_(NULL),
+ completed_handshake_(false),
+ bytes_sent_(0),
+ bytes_received_(0),
+ read_header_size(kReadHeaderSize),
+ host_request_info_(req_info),
+ net_log_(transport_socket->NetLog()) {
+ transport_->set_socket(transport_socket);
}
SOCKS5ClientSocket::~SOCKS5ClientSocket() {
Disconnect();
}
-int SOCKS5ClientSocket::Connect(CompletionCallback* callback,
- LoadLog* load_log) {
+int SOCKS5ClientSocket::Connect(CompletionCallback* callback) {
DCHECK(transport_.get());
- DCHECK(transport_->IsConnected());
+ DCHECK(transport_->socket());
+ DCHECK(transport_->socket()->IsConnected());
DCHECK_EQ(STATE_NONE, next_state_);
DCHECK(!user_callback_);
@@ -75,8 +75,7 @@
if (completed_handshake_)
return OK;
- load_log_ = load_log;
- LoadLog::BeginEvent(load_log, LoadLog::TYPE_SOCKS5_CONNECT);
+ net_log_.BeginEvent(NetLog::TYPE_SOCKS5_CONNECT, NULL);
next_state_ = STATE_GREET_WRITE;
buffer_.clear();
@@ -85,29 +84,27 @@
if (rv == ERR_IO_PENDING) {
user_callback_ = callback;
} else {
- LoadLog::EndEvent(load_log, LoadLog::TYPE_SOCKS5_CONNECT);
- load_log_ = NULL;
+ net_log_.EndEvent(NetLog::TYPE_SOCKS5_CONNECT, NULL);
}
return rv;
}
void SOCKS5ClientSocket::Disconnect() {
completed_handshake_ = false;
- transport_->Disconnect();
+ transport_->socket()->Disconnect();
// Reset other states to make sure they aren't mistakenly used later.
// These are the states initialized by Connect().
next_state_ = STATE_NONE;
user_callback_ = NULL;
- load_log_ = NULL;
}
bool SOCKS5ClientSocket::IsConnected() const {
- return completed_handshake_ && transport_->IsConnected();
+ return completed_handshake_ && transport_->socket()->IsConnected();
}
bool SOCKS5ClientSocket::IsConnectedAndIdle() const {
- return completed_handshake_ && transport_->IsConnectedAndIdle();
+ return completed_handshake_ && transport_->socket()->IsConnectedAndIdle();
}
// Read is called by the transport layer above to read. This can only be done
@@ -118,7 +115,7 @@
DCHECK_EQ(STATE_NONE, next_state_);
DCHECK(!user_callback_);
- return transport_->Read(buf, buf_len, callback);
+ return transport_->socket()->Read(buf, buf_len, callback);
}
// Write is called by the transport layer. This can only be done if the
@@ -129,15 +126,15 @@
DCHECK_EQ(STATE_NONE, next_state_);
DCHECK(!user_callback_);
- return transport_->Write(buf, buf_len, callback);
+ return transport_->socket()->Write(buf, buf_len, callback);
}
bool SOCKS5ClientSocket::SetReceiveBufferSize(int32 size) {
- return transport_->SetReceiveBufferSize(size);
+ return transport_->socket()->SetReceiveBufferSize(size);
}
bool SOCKS5ClientSocket::SetSendBufferSize(int32 size) {
- return transport_->SetSendBufferSize(size);
+ return transport_->socket()->SetSendBufferSize(size);
}
void SOCKS5ClientSocket::DoCallback(int result) {
@@ -155,8 +152,7 @@
DCHECK_NE(STATE_NONE, next_state_);
int rv = DoLoop(result);
if (rv != ERR_IO_PENDING) {
- LoadLog::EndEvent(load_log_, LoadLog::TYPE_SOCKS5_CONNECT);
- load_log_ = NULL;
+ net_log_.EndEvent(NetLog::TYPE_SOCKS5_CONNECT, NULL);
DoCallback(rv);
}
}
@@ -170,39 +166,39 @@
switch (state) {
case STATE_GREET_WRITE:
DCHECK_EQ(OK, rv);
- LoadLog::BeginEvent(load_log_, LoadLog::TYPE_SOCKS5_GREET_WRITE);
+ net_log_.BeginEvent(NetLog::TYPE_SOCKS5_GREET_WRITE, NULL);
rv = DoGreetWrite();
break;
case STATE_GREET_WRITE_COMPLETE:
rv = DoGreetWriteComplete(rv);
- LoadLog::EndEvent(load_log_, LoadLog::TYPE_SOCKS5_GREET_WRITE);
+ net_log_.EndEvent(NetLog::TYPE_SOCKS5_GREET_WRITE, NULL);
break;
case STATE_GREET_READ:
DCHECK_EQ(OK, rv);
- LoadLog::BeginEvent(load_log_, LoadLog::TYPE_SOCKS5_GREET_READ);
+ net_log_.BeginEvent(NetLog::TYPE_SOCKS5_GREET_READ, NULL);
rv = DoGreetRead();
break;
case STATE_GREET_READ_COMPLETE:
rv = DoGreetReadComplete(rv);
- LoadLog::EndEvent(load_log_, LoadLog::TYPE_SOCKS5_GREET_READ);
+ net_log_.EndEvent(NetLog::TYPE_SOCKS5_GREET_READ, NULL);
break;
case STATE_HANDSHAKE_WRITE:
DCHECK_EQ(OK, rv);
- LoadLog::BeginEvent(load_log_, LoadLog::TYPE_SOCKS5_HANDSHAKE_WRITE);
+ net_log_.BeginEvent(NetLog::TYPE_SOCKS5_HANDSHAKE_WRITE, NULL);
rv = DoHandshakeWrite();
break;
case STATE_HANDSHAKE_WRITE_COMPLETE:
rv = DoHandshakeWriteComplete(rv);
- LoadLog::EndEvent(load_log_, LoadLog::TYPE_SOCKS5_HANDSHAKE_WRITE);
+ net_log_.EndEvent(NetLog::TYPE_SOCKS5_HANDSHAKE_WRITE, NULL);
break;
case STATE_HANDSHAKE_READ:
DCHECK_EQ(OK, rv);
- LoadLog::BeginEvent(load_log_, LoadLog::TYPE_SOCKS5_HANDSHAKE_READ);
+ net_log_.BeginEvent(NetLog::TYPE_SOCKS5_HANDSHAKE_READ, NULL);
rv = DoHandshakeRead();
break;
case STATE_HANDSHAKE_READ_COMPLETE:
rv = DoHandshakeReadComplete(rv);
- LoadLog::EndEvent(load_log_, LoadLog::TYPE_SOCKS5_HANDSHAKE_READ);
+ net_log_.EndEvent(NetLog::TYPE_SOCKS5_HANDSHAKE_READ, NULL);
break;
default:
NOTREACHED() << "bad state";
@@ -220,10 +216,8 @@
// Since we only have 1 byte to send the hostname length in, if the
// URL has a hostname longer than 255 characters we can't send it.
if (0xFF < host_request_info_.hostname().size()) {
- LoadLog::AddStringLiteral(load_log_,
- "Failed sending request because hostname is "
- "longer than 255 characters");
- return ERR_INVALID_URL;
+ net_log_.AddEvent(NetLog::TYPE_SOCKS_HOSTNAME_TOO_BIG, NULL);
+ return ERR_SOCKS_CONNECTION_FAILED;
}
if (buffer_.empty()) {
@@ -237,7 +231,8 @@
handshake_buf_ = new IOBuffer(handshake_buf_len);
memcpy(handshake_buf_->data(), &buffer_.data()[bytes_sent_],
handshake_buf_len);
- return transport_->Write(handshake_buf_, handshake_buf_len, &io_callback_);
+ return transport_->socket()->Write(handshake_buf_, handshake_buf_len,
+ &io_callback_);
}
int SOCKS5ClientSocket::DoGreetWriteComplete(int result) {
@@ -259,15 +254,19 @@
next_state_ = STATE_GREET_READ_COMPLETE;
size_t handshake_buf_len = kGreetReadHeaderSize - bytes_received_;
handshake_buf_ = new IOBuffer(handshake_buf_len);
- return transport_->Read(handshake_buf_, handshake_buf_len, &io_callback_);
+ return transport_->socket()->Read(handshake_buf_, handshake_buf_len,
+ &io_callback_);
}
int SOCKS5ClientSocket::DoGreetReadComplete(int result) {
if (result < 0)
return result;
- if (result == 0)
- return ERR_CONNECTION_CLOSED; // Unexpected socket close
+ if (result == 0) {
+ net_log_.AddEvent(NetLog::TYPE_SOCKS_UNEXPECTEDLY_CLOSED_DURING_GREETING,
+ NULL);
+ return ERR_SOCKS_CONNECTION_FAILED;
+ }
bytes_received_ += result;
buffer_.append(handshake_buf_->data(), result);
@@ -278,16 +277,14 @@
// Got the greet data.
if (buffer_[0] != kSOCKS5Version) {
- LoadLog::AddStringLiteral(load_log_, "Unexpected SOCKS version");
- LoadLog::AddString(load_log_, StringPrintf(
- "buffer_[0] = 0x%x", static_cast<int>(buffer_[0])));
- return ERR_INVALID_RESPONSE;
+ net_log_.AddEvent(NetLog::TYPE_SOCKS_UNEXPECTED_VERSION,
+ new NetLogIntegerParameter("version", buffer_[0]));
+ return ERR_SOCKS_CONNECTION_FAILED;
}
if (buffer_[1] != 0x00) {
- LoadLog::AddStringLiteral(load_log_, "Unexpected authentication method");
- LoadLog::AddString(load_log_, StringPrintf(
- "buffer_[1] = 0x%x", static_cast<int>(buffer_[1])));
- return ERR_INVALID_RESPONSE; // Unknown error
+ net_log_.AddEvent(NetLog::TYPE_SOCKS_UNEXPECTED_AUTH,
+ new NetLogIntegerParameter("method", buffer_[1]));
+ return ERR_SOCKS_CONNECTION_FAILED;
}
buffer_.clear();
@@ -333,7 +330,8 @@
handshake_buf_ = new IOBuffer(handshake_buf_len);
memcpy(handshake_buf_->data(), &buffer_[bytes_sent_],
handshake_buf_len);
- return transport_->Write(handshake_buf_, handshake_buf_len, &io_callback_);
+ return transport_->socket()->Write(handshake_buf_, handshake_buf_len,
+ &io_callback_);
}
int SOCKS5ClientSocket::DoHandshakeWriteComplete(int result) {
@@ -366,7 +364,8 @@
int handshake_buf_len = read_header_size - bytes_received_;
handshake_buf_ = new IOBuffer(handshake_buf_len);
- return transport_->Read(handshake_buf_, handshake_buf_len, &io_callback_);
+ return transport_->socket()->Read(handshake_buf_, handshake_buf_len,
+ &io_callback_);
}
int SOCKS5ClientSocket::DoHandshakeReadComplete(int result) {
@@ -374,8 +373,11 @@
return result;
// The underlying socket closed unexpectedly.
- if (result == 0)
- return ERR_CONNECTION_CLOSED;
+ if (result == 0) {
+ net_log_.AddEvent(NetLog::TYPE_SOCKS_UNEXPECTEDLY_CLOSED_DURING_HANDSHAKE,
+ NULL);
+ return ERR_SOCKS_CONNECTION_FAILED;
+ }
buffer_.append(handshake_buf_->data(), result);
bytes_received_ += result;
@@ -384,24 +386,14 @@
// and accordingly increase them
if (bytes_received_ == kReadHeaderSize) {
if (buffer_[0] != kSOCKS5Version || buffer_[2] != kNullByte) {
- LoadLog::AddStringLiteral(load_log_, "Unexpected SOCKS version.");
- LoadLog::AddString(load_log_, StringPrintf(
- "buffer_[0] = 0x%x; buffer_[2] = 0x%x",
- static_cast<int>(buffer_[0]),
- static_cast<int>(buffer_[2])));
- return ERR_INVALID_RESPONSE;
+ net_log_.AddEvent(NetLog::TYPE_SOCKS_UNEXPECTED_VERSION,
+ new NetLogIntegerParameter("version", buffer_[0]));
+ return ERR_SOCKS_CONNECTION_FAILED;
}
if (buffer_[1] != 0x00) {
- LoadLog::AddStringLiteral(load_log_,
- "SOCKS server returned a failure code:");
- const char* error_string = MapSOCKSReplyToErrorString(buffer_[1]);
- if (error_string) {
- LoadLog::AddStringLiteral(load_log_, error_string);
- } else {
- LoadLog::AddString(load_log_, StringPrintf(
- "buffer_[1] = 0x%x", static_cast<int>(buffer_[1])));
- }
- return ERR_FAILED;
+ net_log_.AddEvent(NetLog::TYPE_SOCKS_SERVER_ERROR,
+ new NetLogIntegerParameter("error_code", buffer_[1]));
+ return ERR_SOCKS_CONNECTION_FAILED;
}
// We check the type of IP/Domain the server returns and accordingly
@@ -418,10 +410,9 @@
else if (address_type == kEndPointResolvedIPv6)
read_header_size += sizeof(struct in6_addr) - 1;
else {
- LoadLog::AddStringLiteral(load_log_, "Unknown address type in response");
- LoadLog::AddString(load_log_, StringPrintf(
- "buffer_[3] = 0x%x", static_cast<int>(buffer_[3])));
- return ERR_INVALID_RESPONSE;
+ net_log_.AddEvent(NetLog::TYPE_SOCKS_UNKNOWN_ADDRESS_TYPE,
+ new NetLogIntegerParameter("address_type", buffer_[3]));
+ return ERR_SOCKS_CONNECTION_FAILED;
}
read_header_size += 2; // for the port.
@@ -443,9 +434,8 @@
return OK;
}
-int SOCKS5ClientSocket::GetPeerName(struct sockaddr* name,
- socklen_t* namelen) {
- return transport_->GetPeerName(name, namelen);
+int SOCKS5ClientSocket::GetPeerAddress(AddressList* address) const {
+ return transport_->socket()->GetPeerAddress(address);
}
} // namespace net
diff --git a/net/socket/socks5_client_socket.h b/net/socket/socks5_client_socket.h
index dec1cc4..e6150f3 100644
--- a/net/socket/socks5_client_socket.h
+++ b/net/socket/socks5_client_socket.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -15,12 +15,14 @@
#include "net/base/completion_callback.h"
#include "net/base/host_resolver.h"
#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
#include "net/socket/client_socket.h"
#include "testing/gtest/include/gtest/gtest_prod.h"
namespace net {
-class LoadLog;
+class ClientSocketHandle;
+class BoundNetLog;
// This ClientSocket is used to setup a SOCKSv5 handshake with a socks proxy.
// Currently no SOCKSv5 authentication is supported.
@@ -35,6 +37,10 @@
// Although SOCKS 5 supports 3 different modes of addressing, we will
// always pass it a hostname. This means the DNS resolving is done
// proxy side.
+ SOCKS5ClientSocket(ClientSocketHandle* transport_socket,
+ const HostResolver::RequestInfo& req_info);
+
+ // Deprecated constructor (http://crbug.com/37810) that takes a ClientSocket.
SOCKS5ClientSocket(ClientSocket* transport_socket,
const HostResolver::RequestInfo& req_info);
@@ -44,10 +50,11 @@
// ClientSocket methods:
// Does the SOCKS handshake and completes the protocol.
- virtual int Connect(CompletionCallback* callback, LoadLog* load_log);
+ virtual int Connect(CompletionCallback* callback);
virtual void Disconnect();
virtual bool IsConnected() const;
virtual bool IsConnectedAndIdle() const;
+ virtual const BoundNetLog& NetLog() const { return net_log_; }
// Socket methods:
virtual int Read(IOBuffer* buf, int buf_len, CompletionCallback* callback);
@@ -56,7 +63,7 @@
virtual bool SetReceiveBufferSize(int32 size);
virtual bool SetSendBufferSize(int32 size);
- virtual int GetPeerName(struct sockaddr* name, socklen_t* namelen);
+ virtual int GetPeerAddress(AddressList* address) const;
private:
enum State {
@@ -105,7 +112,7 @@
CompletionCallbackImpl<SOCKS5ClientSocket> io_callback_;
// Stores the underlying socket.
- scoped_ptr<ClientSocket> transport_;
+ scoped_ptr<ClientSocketHandle> transport_;
State next_state_;
@@ -133,7 +140,7 @@
HostResolver::RequestInfo host_request_info_;
- scoped_refptr<LoadLog> load_log_;
+ BoundNetLog net_log_;
DISALLOW_COPY_AND_ASSIGN(SOCKS5ClientSocket);
};
diff --git a/net/socket/socks5_client_socket_unittest.cc b/net/socket/socks5_client_socket_unittest.cc
index b4eb2cf..736a725 100644
--- a/net/socket/socks5_client_socket_unittest.cc
+++ b/net/socket/socks5_client_socket_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -8,8 +8,8 @@
#include <map>
#include "net/base/address_list.h"
-#include "net/base/load_log.h"
-#include "net/base/load_log_unittest.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
#include "net/base/mock_host_resolver.h"
#include "net/base/sys_addrinfo.h"
#include "net/base/test_completion_callback.h"
@@ -32,14 +32,18 @@
SOCKS5ClientSocketTest();
// Create a SOCKSClientSocket on top of a MockSocket.
SOCKS5ClientSocket* BuildMockSocket(MockRead reads[],
+ size_t reads_count,
MockWrite writes[],
+ size_t writes_count,
const std::string& hostname,
- int port);
+ int port,
+ NetLog* net_log);
virtual void SetUp();
protected:
const uint16 kNwPort;
+ CapturingNetLog net_log_;
scoped_ptr<SOCKS5ClientSocket> user_sock_;
AddressList address_list_;
ClientSocket* tcp_sock_;
@@ -52,7 +56,9 @@
};
SOCKS5ClientSocketTest::SOCKS5ClientSocketTest()
- : kNwPort(htons(80)), host_resolver_(new MockHostResolver) {
+ : kNwPort(htons(80)),
+ net_log_(CapturingNetLog::kUnbounded),
+ host_resolver_(new MockHostResolver) {
}
// Set up platform before every test case
@@ -61,20 +67,25 @@
// Resolve the "localhost" AddressList used by the TCP connection to connect.
HostResolver::RequestInfo info("www.socks-proxy.com", 1080);
- int rv = host_resolver_->Resolve(info, &address_list_, NULL, NULL, NULL);
+ int rv = host_resolver_->Resolve(info, &address_list_, NULL, NULL,
+ BoundNetLog());
ASSERT_EQ(OK, rv);
}
SOCKS5ClientSocket* SOCKS5ClientSocketTest::BuildMockSocket(
MockRead reads[],
+ size_t reads_count,
MockWrite writes[],
+ size_t writes_count,
const std::string& hostname,
- int port) {
+ int port,
+ NetLog* net_log) {
TestCompletionCallback callback;
- data_.reset(new StaticSocketDataProvider(reads, writes));
- tcp_sock_ = new MockTCPClientSocket(address_list_, data_.get());
+ data_.reset(new StaticSocketDataProvider(reads, reads_count,
+ writes, writes_count));
+ tcp_sock_ = new MockTCPClientSocket(address_list_, net_log, data_.get());
- int rv = tcp_sock_->Connect(&callback, NULL);
+ int rv = tcp_sock_->Connect(&callback);
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(OK, rv);
@@ -84,18 +95,12 @@
HostResolver::RequestInfo(hostname, port));
}
-const char kSOCKS5GreetRequest[] = { 0x05, 0x01, 0x00 };
-const char kSOCKS5GreetResponse[] = { 0x05, 0x00 };
-const char kSOCKS5OkResponse[] =
- { 0x05, 0x00, 0x00, 0x01, 127, 0, 0, 1, 0x00, 0x50 };
-
-
// Tests a complete SOCKS5 handshake and the disconnection.
TEST_F(SOCKS5ClientSocketTest, CompleteHandshake) {
const std::string payload_write = "random data";
const std::string payload_read = "moar random data";
- const char kSOCKS5OkRequest[] = {
+ const char kOkRequest[] = {
0x05, // Version
0x01, // Command (CONNECT)
0x00, // Reserved.
@@ -107,31 +112,34 @@
};
MockWrite data_writes[] = {
- MockWrite(true, kSOCKS5GreetRequest, arraysize(kSOCKS5GreetRequest)),
- MockWrite(true, kSOCKS5OkRequest, arraysize(kSOCKS5OkRequest)),
+ MockWrite(true, kSOCKS5GreetRequest, kSOCKS5GreetRequestLength),
+ MockWrite(true, kOkRequest, arraysize(kOkRequest)),
MockWrite(true, payload_write.data(), payload_write.size()) };
MockRead data_reads[] = {
- MockRead(true, kSOCKS5GreetResponse, arraysize(kSOCKS5GreetResponse)),
- MockRead(true, kSOCKS5OkResponse, arraysize(kSOCKS5OkResponse)),
+ MockRead(true, kSOCKS5GreetResponse, kSOCKS5GreetResponseLength),
+ MockRead(true, kSOCKS5OkResponse, kSOCKS5OkResponseLength),
MockRead(true, payload_read.data(), payload_read.size()) };
- user_sock_.reset(BuildMockSocket(data_reads, data_writes, "localhost", 80));
+ user_sock_.reset(BuildMockSocket(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes),
+ "localhost", 80, &net_log_));
// At this state the TCP connection is completed but not the SOCKS handshake.
EXPECT_TRUE(tcp_sock_->IsConnected());
EXPECT_FALSE(user_sock_->IsConnected());
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
- int rv = user_sock_->Connect(&callback_, log);
+ int rv = user_sock_->Connect(&callback_);
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_FALSE(user_sock_->IsConnected());
- EXPECT_TRUE(LogContainsBeginEvent(*log, 0, LoadLog::TYPE_SOCKS5_CONNECT));
+ EXPECT_TRUE(LogContainsBeginEvent(net_log_.entries(), 0,
+ NetLog::TYPE_SOCKS5_CONNECT));
rv = callback_.WaitForResult();
EXPECT_EQ(OK, rv);
EXPECT_TRUE(user_sock_->IsConnected());
- EXPECT_TRUE(LogContainsEndEvent(*log, -1, LoadLog::TYPE_SOCKS5_CONNECT));
+ EXPECT_TRUE(LogContainsEndEvent(net_log_.entries(), -1,
+ NetLog::TYPE_SOCKS5_CONNECT));
scoped_refptr<IOBuffer> buffer = new IOBuffer(payload_write.size());
memcpy(buffer->data(), payload_write.data(), payload_write.size());
@@ -169,17 +177,19 @@
for (int i = 0; i < 2; ++i) {
MockWrite data_writes[] = {
- MockWrite(false, kSOCKS5GreetRequest, arraysize(kSOCKS5GreetRequest)),
+ MockWrite(false, kSOCKS5GreetRequest, kSOCKS5GreetRequestLength),
MockWrite(false, request.data(), request.size())
};
MockRead data_reads[] = {
- MockRead(false, kSOCKS5GreetResponse, arraysize(kSOCKS5GreetResponse)),
- MockRead(false, kSOCKS5OkResponse, arraysize(kSOCKS5OkResponse))
+ MockRead(false, kSOCKS5GreetResponse, kSOCKS5GreetResponseLength),
+ MockRead(false, kSOCKS5OkResponse, kSOCKS5OkResponseLength)
};
- user_sock_.reset(BuildMockSocket(data_reads, data_writes, hostname, 80));
+ user_sock_.reset(BuildMockSocket(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes),
+ hostname, 80, NULL));
- int rv = user_sock_->Connect(&callback_, NULL);
+ int rv = user_sock_->Connect(&callback_);
EXPECT_EQ(OK, rv);
EXPECT_TRUE(user_sock_->IsConnected());
@@ -197,20 +207,21 @@
// Create a SOCKS socket, with mock transport socket.
MockWrite data_writes[] = {MockWrite()};
MockRead data_reads[] = {MockRead()};
- user_sock_.reset(BuildMockSocket(data_reads, data_writes,
- large_host_name, 80));
+ user_sock_.reset(BuildMockSocket(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes),
+ large_host_name, 80, NULL));
// Try to connect -- should fail (without having read/written anything to
// the transport socket first) because the hostname is too long.
TestCompletionCallback callback;
- int rv = user_sock_->Connect(&callback, NULL);
- EXPECT_EQ(ERR_INVALID_URL, rv);
+ int rv = user_sock_->Connect(&callback);
+ EXPECT_EQ(ERR_SOCKS_CONNECTION_FAILED, rv);
}
TEST_F(SOCKS5ClientSocketTest, PartialReadWrites) {
const std::string hostname = "www.google.com";
- const char kSOCKS5OkRequest[] = {
+ const char kOkRequest[] = {
0x05, // Version
0x01, // Command (CONNECT)
0x00, // Reserved.
@@ -228,19 +239,22 @@
MockWrite data_writes[] = {
MockWrite(true, arraysize(partial1)),
MockWrite(true, partial2, arraysize(partial2)),
- MockWrite(true, kSOCKS5OkRequest, arraysize(kSOCKS5OkRequest)) };
+ MockWrite(true, kOkRequest, arraysize(kOkRequest)) };
MockRead data_reads[] = {
- MockRead(true, kSOCKS5GreetResponse, arraysize(kSOCKS5GreetResponse)),
- MockRead(true, kSOCKS5OkResponse, arraysize(kSOCKS5OkResponse)) };
- user_sock_.reset(BuildMockSocket(data_reads, data_writes, hostname, 80));
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
- int rv = user_sock_->Connect(&callback_, log);
+ MockRead(true, kSOCKS5GreetResponse, kSOCKS5GreetResponseLength),
+ MockRead(true, kSOCKS5OkResponse, kSOCKS5OkResponseLength) };
+ user_sock_.reset(BuildMockSocket(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes),
+ hostname, 80, &net_log_));
+ int rv = user_sock_->Connect(&callback_);
EXPECT_EQ(ERR_IO_PENDING, rv);
- EXPECT_TRUE(LogContainsBeginEvent(*log, 0, LoadLog::TYPE_SOCKS5_CONNECT));
+ EXPECT_TRUE(LogContainsBeginEvent(net_log_.entries(), 0,
+ NetLog::TYPE_SOCKS5_CONNECT));
rv = callback_.WaitForResult();
EXPECT_EQ(OK, rv);
EXPECT_TRUE(user_sock_->IsConnected());
- EXPECT_TRUE(LogContainsEndEvent(*log, -1, LoadLog::TYPE_SOCKS5_CONNECT));
+ EXPECT_TRUE(LogContainsEndEvent(net_log_.entries(), -1,
+ NetLog::TYPE_SOCKS5_CONNECT));
}
// Test for partial greet response read
@@ -248,69 +262,78 @@
const char partial1[] = { 0x05 };
const char partial2[] = { 0x00 };
MockWrite data_writes[] = {
- MockWrite(true, kSOCKS5GreetRequest, arraysize(kSOCKS5GreetRequest)),
- MockWrite(true, kSOCKS5OkRequest, arraysize(kSOCKS5OkRequest)) };
+ MockWrite(true, kSOCKS5GreetRequest, kSOCKS5GreetRequestLength),
+ MockWrite(true, kOkRequest, arraysize(kOkRequest)) };
MockRead data_reads[] = {
MockRead(true, partial1, arraysize(partial1)),
MockRead(true, partial2, arraysize(partial2)),
- MockRead(true, kSOCKS5OkResponse, arraysize(kSOCKS5OkResponse)) };
- user_sock_.reset(BuildMockSocket(data_reads, data_writes, hostname, 80));
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
- int rv = user_sock_->Connect(&callback_, log);
+ MockRead(true, kSOCKS5OkResponse, kSOCKS5OkResponseLength) };
+ user_sock_.reset(BuildMockSocket(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes),
+ hostname, 80, &net_log_));
+ int rv = user_sock_->Connect(&callback_);
EXPECT_EQ(ERR_IO_PENDING, rv);
- EXPECT_TRUE(LogContainsBeginEvent(*log, 0, LoadLog::TYPE_SOCKS5_CONNECT));
+ EXPECT_TRUE(LogContainsBeginEvent(net_log_.entries(), 0,
+ NetLog::TYPE_SOCKS5_CONNECT));
rv = callback_.WaitForResult();
EXPECT_EQ(OK, rv);
EXPECT_TRUE(user_sock_->IsConnected());
- EXPECT_TRUE(LogContainsEndEvent(*log, -1, LoadLog::TYPE_SOCKS5_CONNECT));
+ EXPECT_TRUE(LogContainsEndEvent(net_log_.entries(), -1,
+ NetLog::TYPE_SOCKS5_CONNECT));
}
// Test for partial handshake request write.
{
const int kSplitPoint = 3; // Break handshake write into two parts.
MockWrite data_writes[] = {
- MockWrite(true, kSOCKS5GreetRequest, arraysize(kSOCKS5GreetRequest)),
- MockWrite(true, kSOCKS5OkRequest, kSplitPoint),
- MockWrite(true, kSOCKS5OkRequest + kSplitPoint,
- arraysize(kSOCKS5OkRequest) - kSplitPoint)
+ MockWrite(true, kSOCKS5GreetRequest, kSOCKS5GreetRequestLength),
+ MockWrite(true, kOkRequest, kSplitPoint),
+ MockWrite(true, kOkRequest + kSplitPoint,
+ arraysize(kOkRequest) - kSplitPoint)
};
MockRead data_reads[] = {
- MockRead(true, kSOCKS5GreetResponse, arraysize(kSOCKS5GreetResponse)),
- MockRead(true, kSOCKS5OkResponse, arraysize(kSOCKS5OkResponse)) };
- user_sock_.reset(BuildMockSocket(data_reads, data_writes, hostname, 80));
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
- int rv = user_sock_->Connect(&callback_, log);
+ MockRead(true, kSOCKS5GreetResponse, kSOCKS5GreetResponseLength),
+ MockRead(true, kSOCKS5OkResponse, kSOCKS5OkResponseLength) };
+ user_sock_.reset(BuildMockSocket(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes),
+ hostname, 80, &net_log_));
+ int rv = user_sock_->Connect(&callback_);
EXPECT_EQ(ERR_IO_PENDING, rv);
- EXPECT_TRUE(LogContainsBeginEvent(*log, 0, LoadLog::TYPE_SOCKS5_CONNECT));
+ EXPECT_TRUE(LogContainsBeginEvent(net_log_.entries(), 0,
+ NetLog::TYPE_SOCKS5_CONNECT));
rv = callback_.WaitForResult();
EXPECT_EQ(OK, rv);
EXPECT_TRUE(user_sock_->IsConnected());
- EXPECT_TRUE(LogContainsEndEvent(*log, -1, LoadLog::TYPE_SOCKS5_CONNECT));
+ EXPECT_TRUE(LogContainsEndEvent(net_log_.entries(), -1,
+ NetLog::TYPE_SOCKS5_CONNECT));
}
// Test for partial handshake response read
{
const int kSplitPoint = 6; // Break the handshake read into two parts.
MockWrite data_writes[] = {
- MockWrite(true, kSOCKS5GreetRequest, arraysize(kSOCKS5GreetRequest)),
- MockWrite(true, kSOCKS5OkRequest, arraysize(kSOCKS5OkRequest))
+ MockWrite(true, kSOCKS5GreetRequest, kSOCKS5GreetRequestLength),
+ MockWrite(true, kOkRequest, arraysize(kOkRequest))
};
MockRead data_reads[] = {
- MockRead(true, kSOCKS5GreetResponse, arraysize(kSOCKS5GreetResponse)),
+ MockRead(true, kSOCKS5GreetResponse, kSOCKS5GreetResponseLength),
MockRead(true, kSOCKS5OkResponse, kSplitPoint),
MockRead(true, kSOCKS5OkResponse + kSplitPoint,
- arraysize(kSOCKS5OkResponse) - kSplitPoint)
+ kSOCKS5OkResponseLength - kSplitPoint)
};
- user_sock_.reset(BuildMockSocket(data_reads, data_writes, hostname, 80));
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
- int rv = user_sock_->Connect(&callback_, log);
+ user_sock_.reset(BuildMockSocket(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes),
+ hostname, 80, &net_log_));
+ int rv = user_sock_->Connect(&callback_);
EXPECT_EQ(ERR_IO_PENDING, rv);
- EXPECT_TRUE(LogContainsBeginEvent(*log, 0, LoadLog::TYPE_SOCKS5_CONNECT));
+ EXPECT_TRUE(LogContainsBeginEvent(net_log_.entries(), 0,
+ NetLog::TYPE_SOCKS5_CONNECT));
rv = callback_.WaitForResult();
EXPECT_EQ(OK, rv);
EXPECT_TRUE(user_sock_->IsConnected());
- EXPECT_TRUE(LogContainsEndEvent(*log, -1, LoadLog::TYPE_SOCKS5_CONNECT));
+ EXPECT_TRUE(LogContainsEndEvent(net_log_.entries(), -1,
+ NetLog::TYPE_SOCKS5_CONNECT));
}
}
diff --git a/net/socket/socks_client_socket.cc b/net/socket/socks_client_socket.cc
index 5850da3..32c4e3b 100644
--- a/net/socket/socks_client_socket.cc
+++ b/net/socket/socks_client_socket.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -8,9 +8,10 @@
#include "base/compiler_specific.h"
#include "base/trace_event.h"
#include "net/base/io_buffer.h"
-#include "net/base/load_log.h"
+#include "net/base/net_log.h"
#include "net/base/net_util.h"
#include "net/base/sys_addrinfo.h"
+#include "net/socket/client_socket_handle.h"
namespace net {
@@ -59,7 +60,7 @@
COMPILE_ASSERT(sizeof(SOCKS4ServerResponse) == kReadHeaderSize,
socks4_server_response_struct_wrong_size);
-SOCKSClientSocket::SOCKSClientSocket(ClientSocket* transport_socket,
+SOCKSClientSocket::SOCKSClientSocket(ClientSocketHandle* transport_socket,
const HostResolver::RequestInfo& req_info,
HostResolver* host_resolver)
: ALLOW_THIS_IN_INITIALIZER_LIST(
@@ -72,17 +73,36 @@
bytes_sent_(0),
bytes_received_(0),
host_resolver_(host_resolver),
- host_request_info_(req_info) {
+ host_request_info_(req_info),
+ net_log_(transport_socket->socket()->NetLog()) {
+}
+
+SOCKSClientSocket::SOCKSClientSocket(ClientSocket* transport_socket,
+ const HostResolver::RequestInfo& req_info,
+ HostResolver* host_resolver)
+ : ALLOW_THIS_IN_INITIALIZER_LIST(
+ io_callback_(this, &SOCKSClientSocket::OnIOComplete)),
+ transport_(new ClientSocketHandle()),
+ next_state_(STATE_NONE),
+ socks_version_(kSOCKS4Unresolved),
+ user_callback_(NULL),
+ completed_handshake_(false),
+ bytes_sent_(0),
+ bytes_received_(0),
+ host_resolver_(host_resolver),
+ host_request_info_(req_info),
+ net_log_(transport_socket->NetLog()) {
+ transport_->set_socket(transport_socket);
}
SOCKSClientSocket::~SOCKSClientSocket() {
Disconnect();
}
-int SOCKSClientSocket::Connect(CompletionCallback* callback,
- LoadLog* load_log) {
+int SOCKSClientSocket::Connect(CompletionCallback* callback) {
DCHECK(transport_.get());
- DCHECK(transport_->IsConnected());
+ DCHECK(transport_->socket());
+ DCHECK(transport_->socket()->IsConnected());
DCHECK_EQ(STATE_NONE, next_state_);
DCHECK(!user_callback_);
@@ -91,16 +111,14 @@
return OK;
next_state_ = STATE_RESOLVE_HOST;
- load_log_ = load_log;
- LoadLog::BeginEvent(load_log, LoadLog::TYPE_SOCKS_CONNECT);
+ net_log_.BeginEvent(NetLog::TYPE_SOCKS_CONNECT, NULL);
int rv = DoLoop(OK);
if (rv == ERR_IO_PENDING) {
user_callback_ = callback;
} else {
- LoadLog::EndEvent(load_log, LoadLog::TYPE_SOCKS_CONNECT);
- load_log_ = NULL;
+ net_log_.EndEvent(NetLog::TYPE_SOCKS_CONNECT, NULL);
}
return rv;
}
@@ -108,21 +126,20 @@
void SOCKSClientSocket::Disconnect() {
completed_handshake_ = false;
host_resolver_.Cancel();
- transport_->Disconnect();
+ transport_->socket()->Disconnect();
// Reset other states to make sure they aren't mistakenly used later.
// These are the states initialized by Connect().
next_state_ = STATE_NONE;
user_callback_ = NULL;
- load_log_ = NULL;
}
bool SOCKSClientSocket::IsConnected() const {
- return completed_handshake_ && transport_->IsConnected();
+ return completed_handshake_ && transport_->socket()->IsConnected();
}
bool SOCKSClientSocket::IsConnectedAndIdle() const {
- return completed_handshake_ && transport_->IsConnectedAndIdle();
+ return completed_handshake_ && transport_->socket()->IsConnectedAndIdle();
}
// Read is called by the transport layer above to read. This can only be done
@@ -133,7 +150,7 @@
DCHECK_EQ(STATE_NONE, next_state_);
DCHECK(!user_callback_);
- return transport_->Read(buf, buf_len, callback);
+ return transport_->socket()->Read(buf, buf_len, callback);
}
// Write is called by the transport layer. This can only be done if the
@@ -144,15 +161,15 @@
DCHECK_EQ(STATE_NONE, next_state_);
DCHECK(!user_callback_);
- return transport_->Write(buf, buf_len, callback);
+ return transport_->socket()->Write(buf, buf_len, callback);
}
bool SOCKSClientSocket::SetReceiveBufferSize(int32 size) {
- return transport_->SetReceiveBufferSize(size);
+ return transport_->socket()->SetReceiveBufferSize(size);
}
bool SOCKSClientSocket::SetSendBufferSize(int32 size) {
- return transport_->SetSendBufferSize(size);
+ return transport_->socket()->SetSendBufferSize(size);
}
void SOCKSClientSocket::DoCallback(int result) {
@@ -171,8 +188,7 @@
DCHECK_NE(STATE_NONE, next_state_);
int rv = DoLoop(result);
if (rv != ERR_IO_PENDING) {
- LoadLog::EndEvent(load_log_, LoadLog::TYPE_SOCKS_CONNECT);
- load_log_ = NULL;
+ net_log_.EndEvent(NetLog::TYPE_SOCKS_CONNECT, NULL);
DoCallback(rv);
}
}
@@ -219,7 +235,7 @@
next_state_ = STATE_RESOLVE_HOST_COMPLETE;
return host_resolver_.Resolve(
- host_request_info_, &addresses_, &io_callback_, load_log_);
+ host_request_info_, &addresses_, &io_callback_, net_log_);
}
int SOCKSClientSocket::DoResolveHostComplete(int result) {
@@ -306,7 +322,8 @@
handshake_buf_ = new IOBuffer(handshake_buf_len);
memcpy(handshake_buf_->data(), &buffer_[bytes_sent_],
handshake_buf_len);
- return transport_->Write(handshake_buf_, handshake_buf_len, &io_callback_);
+ return transport_->socket()->Write(handshake_buf_, handshake_buf_len,
+ &io_callback_);
}
int SOCKSClientSocket::DoHandshakeWriteComplete(int result) {
@@ -342,7 +359,8 @@
int handshake_buf_len = kReadHeaderSize - bytes_received_;
handshake_buf_ = new IOBuffer(handshake_buf_len);
- return transport_->Read(handshake_buf_, handshake_buf_len, &io_callback_);
+ return transport_->socket()->Read(handshake_buf_, handshake_buf_len,
+ &io_callback_);
}
int SOCKSClientSocket::DoHandshakeReadComplete(int result) {
@@ -355,8 +373,10 @@
if (result == 0)
return ERR_CONNECTION_CLOSED;
- if (bytes_received_ + result > kReadHeaderSize)
- return ERR_INVALID_RESPONSE;
+ if (bytes_received_ + result > kReadHeaderSize) {
+ // TODO(eroman): Describe failure in NetLog.
+ return ERR_SOCKS_CONNECTION_FAILED;
+ }
buffer_.append(handshake_buf_->data(), result);
bytes_received_ += result;
@@ -370,36 +390,34 @@
if (response->reserved_null != 0x00) {
LOG(ERROR) << "Unknown response from SOCKS server.";
- return ERR_INVALID_RESPONSE;
+ return ERR_SOCKS_CONNECTION_FAILED;
}
- // TODO(arindam): Add SOCKS specific failure codes in net_error_list.h
switch (response->code) {
case kServerResponseOk:
completed_handshake_ = true;
return OK;
case kServerResponseRejected:
LOG(ERROR) << "SOCKS request rejected or failed";
- return ERR_FAILED;
+ return ERR_SOCKS_CONNECTION_FAILED;
case kServerResponseNotReachable:
LOG(ERROR) << "SOCKS request failed because client is not running "
<< "identd (or not reachable from the server)";
- return ERR_NAME_NOT_RESOLVED;
+ return ERR_SOCKS_CONNECTION_HOST_UNREACHABLE;
case kServerResponseMismatchedUserId:
LOG(ERROR) << "SOCKS request failed because client's identd could "
<< "not confirm the user ID string in the request";
- return ERR_FAILED;
+ return ERR_SOCKS_CONNECTION_FAILED;
default:
LOG(ERROR) << "SOCKS server sent unknown response";
- return ERR_INVALID_RESPONSE;
+ return ERR_SOCKS_CONNECTION_FAILED;
}
// Note: we ignore the last 6 bytes as specified by the SOCKS protocol
}
-int SOCKSClientSocket::GetPeerName(struct sockaddr* name,
- socklen_t* namelen) {
- return transport_->GetPeerName(name, namelen);
+int SOCKSClientSocket::GetPeerAddress(AddressList* address) const {
+ return transport_->socket()->GetPeerAddress(address);
}
} // namespace net
diff --git a/net/socket/socks_client_socket.h b/net/socket/socks_client_socket.h
index dc0b287..f99bff0 100644
--- a/net/socket/socks_client_socket.h
+++ b/net/socket/socks_client_socket.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -15,12 +15,14 @@
#include "net/base/completion_callback.h"
#include "net/base/host_resolver.h"
#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
#include "net/socket/client_socket.h"
#include "testing/gtest/include/gtest/gtest_prod.h"
namespace net {
-class LoadLog;
+class ClientSocketHandle;
+class BoundNetLog;
// The SOCKS client socket implementation
class SOCKSClientSocket : public ClientSocket {
@@ -30,6 +32,11 @@
//
// |req_info| contains the hostname and port to which the socket above will
// communicate to via the socks layer. For testing the referrer is optional.
+ SOCKSClientSocket(ClientSocketHandle* transport_socket,
+ const HostResolver::RequestInfo& req_info,
+ HostResolver* host_resolver);
+
+ // Deprecated constructor (http://crbug.com/37810) that takes a ClientSocket.
SOCKSClientSocket(ClientSocket* transport_socket,
const HostResolver::RequestInfo& req_info,
HostResolver* host_resolver);
@@ -40,10 +47,11 @@
// ClientSocket methods:
// Does the SOCKS handshake and completes the protocol.
- virtual int Connect(CompletionCallback* callback, LoadLog* load_log);
+ virtual int Connect(CompletionCallback* callback);
virtual void Disconnect();
virtual bool IsConnected() const;
virtual bool IsConnectedAndIdle() const;
+ virtual const BoundNetLog& NetLog() const { return net_log_; }
// Socket methods:
virtual int Read(IOBuffer* buf, int buf_len, CompletionCallback* callback);
@@ -52,7 +60,7 @@
virtual bool SetReceiveBufferSize(int32 size);
virtual bool SetSendBufferSize(int32 size);
- virtual int GetPeerName(struct sockaddr* name, socklen_t* namelen);
+ virtual int GetPeerAddress(AddressList* address) const;
private:
FRIEND_TEST(SOCKSClientSocketTest, CompleteHandshake);
@@ -95,7 +103,7 @@
CompletionCallbackImpl<SOCKSClientSocket> io_callback_;
// Stores the underlying socket.
- scoped_ptr<ClientSocket> transport_;
+ scoped_ptr<ClientSocketHandle> transport_;
State next_state_;
SocksVersion socks_version_;
@@ -125,7 +133,7 @@
AddressList addresses_;
HostResolver::RequestInfo host_request_info_;
- scoped_refptr<LoadLog> load_log_;
+ BoundNetLog net_log_;
DISALLOW_COPY_AND_ASSIGN(SOCKSClientSocket);
};
diff --git a/net/socket/socks_client_socket_pool.cc b/net/socket/socks_client_socket_pool.cc
new file mode 100644
index 0000000..d1c75b6
--- /dev/null
+++ b/net/socket/socks_client_socket_pool.cc
@@ -0,0 +1,235 @@
+// 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 "net/socket/socks_client_socket_pool.h"
+
+#include "base/time.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/net_errors.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/client_socket_pool_base.h"
+#include "net/socket/socks5_client_socket.h"
+#include "net/socket/socks_client_socket.h"
+
+namespace net {
+
+SOCKSSocketParams::SOCKSSocketParams(
+ const scoped_refptr<TCPSocketParams>& proxy_server,
+ bool socks_v5,
+ const HostPortPair& host_port_pair,
+ RequestPriority priority,
+ const GURL& referrer)
+ : tcp_params_(proxy_server),
+ destination_(host_port_pair.host, host_port_pair.port),
+ socks_v5_(socks_v5) {
+ // The referrer is used by the DNS prefetch system to correlate resolutions
+ // with the page that triggered them. It doesn't impact the actual addresses
+ // that we resolve to.
+ destination_.set_referrer(referrer);
+ destination_.set_priority(priority);
+}
+
+SOCKSSocketParams::~SOCKSSocketParams() {}
+
+// SOCKSConnectJobs will time out after this many seconds. Note this is on
+// top of the timeout for the transport socket.
+static const int kSOCKSConnectJobTimeoutInSeconds = 30;
+
+SOCKSConnectJob::SOCKSConnectJob(
+ const std::string& group_name,
+ const scoped_refptr<SOCKSSocketParams>& socks_params,
+ const base::TimeDelta& timeout_duration,
+ const scoped_refptr<TCPClientSocketPool>& tcp_pool,
+ const scoped_refptr<HostResolver>& host_resolver,
+ Delegate* delegate,
+ NetLog* net_log)
+ : ConnectJob(group_name, timeout_duration, delegate,
+ BoundNetLog::Make(net_log, NetLog::SOURCE_CONNECT_JOB)),
+ socks_params_(socks_params),
+ tcp_pool_(tcp_pool),
+ resolver_(host_resolver),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ callback_(this, &SOCKSConnectJob::OnIOComplete)) {
+}
+
+SOCKSConnectJob::~SOCKSConnectJob() {
+ // We don't worry about cancelling the tcp socket since the destructor in
+ // scoped_ptr<ClientSocketHandle> tcp_socket_handle_ will take care of it.
+}
+
+LoadState SOCKSConnectJob::GetLoadState() const {
+ switch (next_state_) {
+ case kStateTCPConnect:
+ case kStateTCPConnectComplete:
+ return tcp_socket_handle_->GetLoadState();
+ case kStateSOCKSConnect:
+ case kStateSOCKSConnectComplete:
+ return LOAD_STATE_CONNECTING;
+ default:
+ NOTREACHED();
+ return LOAD_STATE_IDLE;
+ }
+}
+
+int SOCKSConnectJob::ConnectInternal() {
+ next_state_ = kStateTCPConnect;
+ return DoLoop(OK);
+}
+
+void SOCKSConnectJob::OnIOComplete(int result) {
+ int rv = DoLoop(result);
+ if (rv != ERR_IO_PENDING)
+ NotifyDelegateOfCompletion(rv); // Deletes |this|
+}
+
+int SOCKSConnectJob::DoLoop(int result) {
+ DCHECK_NE(next_state_, kStateNone);
+
+ int rv = result;
+ do {
+ State state = next_state_;
+ next_state_ = kStateNone;
+ switch (state) {
+ case kStateTCPConnect:
+ DCHECK_EQ(OK, rv);
+ rv = DoTCPConnect();
+ break;
+ case kStateTCPConnectComplete:
+ rv = DoTCPConnectComplete(rv);
+ break;
+ case kStateSOCKSConnect:
+ DCHECK_EQ(OK, rv);
+ rv = DoSOCKSConnect();
+ break;
+ case kStateSOCKSConnectComplete:
+ rv = DoSOCKSConnectComplete(rv);
+ break;
+ default:
+ NOTREACHED() << "bad state";
+ rv = ERR_FAILED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != kStateNone);
+
+ return rv;
+}
+
+int SOCKSConnectJob::DoTCPConnect() {
+ next_state_ = kStateTCPConnectComplete;
+ tcp_socket_handle_.reset(new ClientSocketHandle());
+ return tcp_socket_handle_->Init(group_name(), socks_params_->tcp_params(),
+ socks_params_->destination().priority(),
+ &callback_, tcp_pool_, net_log());
+}
+
+int SOCKSConnectJob::DoTCPConnectComplete(int result) {
+ if (result != OK)
+ return result;
+
+ // Reset the timer to just the length of time allowed for SOCKS handshake
+ // so that a fast TCP connection plus a slow SOCKS failure doesn't take
+ // longer to timeout than it should.
+ ResetTimer(base::TimeDelta::FromSeconds(kSOCKSConnectJobTimeoutInSeconds));
+ next_state_ = kStateSOCKSConnect;
+ return result;
+}
+
+int SOCKSConnectJob::DoSOCKSConnect() {
+ next_state_ = kStateSOCKSConnectComplete;
+
+ // Add a SOCKS connection on top of the tcp socket.
+ if (socks_params_->is_socks_v5()) {
+ socket_.reset(new SOCKS5ClientSocket(tcp_socket_handle_.release(),
+ socks_params_->destination()));
+ } else {
+ socket_.reset(new SOCKSClientSocket(tcp_socket_handle_.release(),
+ socks_params_->destination(),
+ resolver_));
+ }
+ return socket_->Connect(&callback_);
+}
+
+int SOCKSConnectJob::DoSOCKSConnectComplete(int result) {
+ if (result != OK) {
+ socket_->Disconnect();
+ return result;
+ }
+
+ set_socket(socket_.release());
+ return result;
+}
+
+ConnectJob* SOCKSClientSocketPool::SOCKSConnectJobFactory::NewConnectJob(
+ const std::string& group_name,
+ const PoolBase::Request& request,
+ ConnectJob::Delegate* delegate) const {
+ return new SOCKSConnectJob(group_name, request.params(), ConnectionTimeout(),
+ tcp_pool_, host_resolver_, delegate, net_log_);
+}
+
+base::TimeDelta
+SOCKSClientSocketPool::SOCKSConnectJobFactory::ConnectionTimeout() const {
+ return tcp_pool_->ConnectionTimeout() +
+ base::TimeDelta::FromSeconds(kSOCKSConnectJobTimeoutInSeconds);
+}
+
+SOCKSClientSocketPool::SOCKSClientSocketPool(
+ int max_sockets,
+ int max_sockets_per_group,
+ const scoped_refptr<ClientSocketPoolHistograms>& histograms,
+ const scoped_refptr<HostResolver>& host_resolver,
+ const scoped_refptr<TCPClientSocketPool>& tcp_pool,
+ NetLog* net_log)
+ : base_(max_sockets, max_sockets_per_group, histograms,
+ base::TimeDelta::FromSeconds(
+ ClientSocketPool::unused_idle_socket_timeout()),
+ base::TimeDelta::FromSeconds(kUsedIdleSocketTimeout),
+ new SOCKSConnectJobFactory(tcp_pool, host_resolver, net_log)) {
+}
+
+SOCKSClientSocketPool::~SOCKSClientSocketPool() {}
+
+int SOCKSClientSocketPool::RequestSocket(const std::string& group_name,
+ const void* socket_params,
+ RequestPriority priority,
+ ClientSocketHandle* handle,
+ CompletionCallback* callback,
+ const BoundNetLog& net_log) {
+ const scoped_refptr<SOCKSSocketParams>* casted_socket_params =
+ static_cast<const scoped_refptr<SOCKSSocketParams>*>(socket_params);
+
+ return base_.RequestSocket(group_name, *casted_socket_params, priority,
+ handle, callback, net_log);
+}
+
+void SOCKSClientSocketPool::CancelRequest(const std::string& group_name,
+ ClientSocketHandle* handle) {
+ base_.CancelRequest(group_name, handle);
+}
+
+void SOCKSClientSocketPool::ReleaseSocket(const std::string& group_name,
+ ClientSocket* socket, int id) {
+ base_.ReleaseSocket(group_name, socket, id);
+}
+
+void SOCKSClientSocketPool::Flush() {
+ base_.Flush();
+}
+
+void SOCKSClientSocketPool::CloseIdleSockets() {
+ base_.CloseIdleSockets();
+}
+
+int SOCKSClientSocketPool::IdleSocketCountInGroup(
+ const std::string& group_name) const {
+ return base_.IdleSocketCountInGroup(group_name);
+}
+
+LoadState SOCKSClientSocketPool::GetLoadState(
+ const std::string& group_name, const ClientSocketHandle* handle) const {
+ return base_.GetLoadState(group_name, handle);
+}
+
+} // namespace net
diff --git a/net/socket/socks_client_socket_pool.h b/net/socket/socks_client_socket_pool.h
new file mode 100644
index 0000000..4387aa7
--- /dev/null
+++ b/net/socket/socks_client_socket_pool.h
@@ -0,0 +1,193 @@
+// 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.
+
+#ifndef NET_SOCKET_SOCKS_CLIENT_SOCKET_POOL_H_
+#define NET_SOCKET_SOCKS_CLIENT_SOCKET_POOL_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "base/time.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/host_resolver.h"
+#include "net/proxy/proxy_server.h"
+#include "net/socket/client_socket_pool_base.h"
+#include "net/socket/client_socket_pool_histograms.h"
+#include "net/socket/client_socket_pool.h"
+#include "net/socket/tcp_client_socket_pool.h"
+
+namespace net {
+
+class ClientSocketFactory;
+class ConnectJobFactory;
+
+class SOCKSSocketParams : public base::RefCounted<SOCKSSocketParams> {
+ public:
+ SOCKSSocketParams(const scoped_refptr<TCPSocketParams>& proxy_server,
+ bool socks_v5, const HostPortPair& host_port_pair,
+ RequestPriority priority, const GURL& referrer);
+
+ const scoped_refptr<TCPSocketParams>& tcp_params() const {
+ return tcp_params_;
+ }
+ const HostResolver::RequestInfo& destination() const { return destination_; }
+ bool is_socks_v5() const { return socks_v5_; }
+
+ private:
+ friend class base::RefCounted<SOCKSSocketParams>;
+ ~SOCKSSocketParams();
+
+ // The tcp connection must point toward the proxy server.
+ const scoped_refptr<TCPSocketParams> tcp_params_;
+ // This is the HTTP destination.
+ HostResolver::RequestInfo destination_;
+ const bool socks_v5_;
+
+ DISALLOW_COPY_AND_ASSIGN(SOCKSSocketParams);
+};
+
+// SOCKSConnectJob handles the handshake to a socks server after setting up
+// an underlying transport socket.
+class SOCKSConnectJob : public ConnectJob {
+ public:
+ SOCKSConnectJob(const std::string& group_name,
+ const scoped_refptr<SOCKSSocketParams>& params,
+ const base::TimeDelta& timeout_duration,
+ const scoped_refptr<TCPClientSocketPool>& tcp_pool,
+ const scoped_refptr<HostResolver> &host_resolver,
+ Delegate* delegate,
+ NetLog* net_log);
+ virtual ~SOCKSConnectJob();
+
+ // ConnectJob methods.
+ virtual LoadState GetLoadState() const;
+
+ private:
+ enum State {
+ kStateTCPConnect,
+ kStateTCPConnectComplete,
+ kStateSOCKSConnect,
+ kStateSOCKSConnectComplete,
+ kStateNone,
+ };
+
+ // Begins the tcp connection and the SOCKS handshake. Returns OK on success
+ // and ERR_IO_PENDING if it cannot immediately service the request.
+ // Otherwise, it returns a net error code.
+ virtual int ConnectInternal();
+
+ void OnIOComplete(int result);
+
+ // Runs the state transition loop.
+ int DoLoop(int result);
+
+ int DoTCPConnect();
+ int DoTCPConnectComplete(int result);
+ int DoSOCKSConnect();
+ int DoSOCKSConnectComplete(int result);
+
+ scoped_refptr<SOCKSSocketParams> socks_params_;
+ const scoped_refptr<TCPClientSocketPool> tcp_pool_;
+ const scoped_refptr<HostResolver> resolver_;
+
+ State next_state_;
+ CompletionCallbackImpl<SOCKSConnectJob> callback_;
+ scoped_ptr<ClientSocketHandle> tcp_socket_handle_;
+ scoped_ptr<ClientSocket> socket_;
+
+ DISALLOW_COPY_AND_ASSIGN(SOCKSConnectJob);
+};
+
+class SOCKSClientSocketPool : public ClientSocketPool {
+ public:
+ SOCKSClientSocketPool(
+ int max_sockets,
+ int max_sockets_per_group,
+ const scoped_refptr<ClientSocketPoolHistograms>& histograms,
+ const scoped_refptr<HostResolver>& host_resolver,
+ const scoped_refptr<TCPClientSocketPool>& tcp_pool,
+ NetLog* net_log);
+
+ // ClientSocketPool methods:
+ virtual int RequestSocket(const std::string& group_name,
+ const void* connect_params,
+ RequestPriority priority,
+ ClientSocketHandle* handle,
+ CompletionCallback* callback,
+ const BoundNetLog& net_log);
+
+ virtual void CancelRequest(const std::string& group_name,
+ ClientSocketHandle* handle);
+
+ virtual void ReleaseSocket(const std::string& group_name,
+ ClientSocket* socket,
+ int id);
+
+ virtual void Flush();
+
+ virtual void CloseIdleSockets();
+
+ virtual int IdleSocketCount() const {
+ return base_.idle_socket_count();
+ }
+
+ virtual int IdleSocketCountInGroup(const std::string& group_name) const;
+
+ virtual LoadState GetLoadState(const std::string& group_name,
+ const ClientSocketHandle* handle) const;
+
+ virtual base::TimeDelta ConnectionTimeout() const {
+ return base_.ConnectionTimeout();
+ }
+
+ virtual scoped_refptr<ClientSocketPoolHistograms> histograms() const {
+ return base_.histograms();
+ };
+
+ protected:
+ virtual ~SOCKSClientSocketPool();
+
+ private:
+ typedef ClientSocketPoolBase<SOCKSSocketParams> PoolBase;
+
+ class SOCKSConnectJobFactory : public PoolBase::ConnectJobFactory {
+ public:
+ SOCKSConnectJobFactory(const scoped_refptr<TCPClientSocketPool>& tcp_pool,
+ HostResolver* host_resolver,
+ NetLog* net_log)
+ : tcp_pool_(tcp_pool),
+ host_resolver_(host_resolver),
+ net_log_(net_log) {}
+
+ virtual ~SOCKSConnectJobFactory() {}
+
+ // ClientSocketPoolBase::ConnectJobFactory methods.
+ virtual ConnectJob* NewConnectJob(
+ const std::string& group_name,
+ const PoolBase::Request& request,
+ ConnectJob::Delegate* delegate) const;
+
+ virtual base::TimeDelta ConnectionTimeout() const;
+
+ private:
+ const scoped_refptr<TCPClientSocketPool> tcp_pool_;
+ const scoped_refptr<HostResolver> host_resolver_;
+ NetLog* net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(SOCKSConnectJobFactory);
+ };
+
+ PoolBase base_;
+
+ DISALLOW_COPY_AND_ASSIGN(SOCKSClientSocketPool);
+};
+
+REGISTER_SOCKET_PARAMS_FOR_POOL(SOCKSClientSocketPool, SOCKSSocketParams);
+
+} // namespace net
+
+#endif // NET_SOCKET_SOCKS_CLIENT_SOCKET_POOL_H_
diff --git a/net/socket/socks_client_socket_pool_unittest.cc b/net/socket/socks_client_socket_pool_unittest.cc
new file mode 100644
index 0000000..14e2bee
--- /dev/null
+++ b/net/socket/socks_client_socket_pool_unittest.cc
@@ -0,0 +1,261 @@
+// 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 "net/socket/socks_client_socket_pool.h"
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/time.h"
+#include "net/base/mock_host_resolver.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/client_socket_pool_histograms.h"
+#include "net/socket/socket_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+const int kMaxSockets = 32;
+const int kMaxSocketsPerGroup = 6;
+
+class SOCKSClientSocketPoolTest : public ClientSocketPoolTest {
+ protected:
+ class SOCKS5MockData {
+ public:
+ explicit SOCKS5MockData(bool async) {
+ writes_.reset(new MockWrite[3]);
+ writes_[0] = MockWrite(async, kSOCKS5GreetRequest,
+ kSOCKS5GreetRequestLength);
+ writes_[1] = MockWrite(async, kSOCKS5OkRequest, kSOCKS5OkRequestLength);
+ writes_[2] = MockWrite(async, 0);
+
+ reads_.reset(new MockRead[3]);
+ reads_[0] = MockRead(async, kSOCKS5GreetResponse,
+ kSOCKS5GreetResponseLength);
+ reads_[1] = MockRead(async, kSOCKS5OkResponse, kSOCKS5OkResponseLength);
+ reads_[2] = MockRead(async, 0);
+
+ data_.reset(new StaticSocketDataProvider(reads_.get(), 3,
+ writes_.get(), 3));
+ }
+
+ SocketDataProvider* data_provider() { return data_.get(); }
+
+ private:
+ scoped_ptr<StaticSocketDataProvider> data_;
+ scoped_array<MockWrite> writes_;
+ scoped_array<MockWrite> reads_;
+ };
+
+ SOCKSClientSocketPoolTest()
+ : ignored_tcp_socket_params_(new TCPSocketParams(
+ HostPortPair("proxy", 80), MEDIUM, GURL(), false)),
+ tcp_histograms_(new ClientSocketPoolHistograms("MockTCP")),
+ tcp_socket_pool_(new MockTCPClientSocketPool(kMaxSockets,
+ kMaxSocketsPerGroup, tcp_histograms_, &tcp_client_socket_factory_)),
+ ignored_socket_params_(new SOCKSSocketParams(
+ ignored_tcp_socket_params_, true, HostPortPair("host", 80), MEDIUM,
+ GURL())),
+ socks_histograms_(new ClientSocketPoolHistograms("SOCKSUnitTest")),
+ pool_(new SOCKSClientSocketPool(kMaxSockets, kMaxSocketsPerGroup,
+ socks_histograms_, NULL, tcp_socket_pool_, NULL)) {
+ }
+
+ int StartRequest(const std::string& group_name, RequestPriority priority) {
+ return StartRequestUsingPool(
+ pool_, group_name, priority, ignored_socket_params_);
+ }
+
+ scoped_refptr<TCPSocketParams> ignored_tcp_socket_params_;
+ scoped_refptr<ClientSocketPoolHistograms> tcp_histograms_;
+ MockClientSocketFactory tcp_client_socket_factory_;
+ scoped_refptr<MockTCPClientSocketPool> tcp_socket_pool_;
+
+ scoped_refptr<SOCKSSocketParams> ignored_socket_params_;
+ scoped_refptr<ClientSocketPoolHistograms> socks_histograms_;
+ scoped_refptr<SOCKSClientSocketPool> pool_;
+};
+
+TEST_F(SOCKSClientSocketPoolTest, Simple) {
+ SOCKS5MockData data(false);
+ data.data_provider()->set_connect_data(MockConnect(false, 0));
+ tcp_client_socket_factory_.AddSocketDataProvider(data.data_provider());
+
+ ClientSocketHandle handle;
+ int rv = handle.Init("a", ignored_socket_params_, LOW, NULL, pool_,
+ BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+}
+
+TEST_F(SOCKSClientSocketPoolTest, Async) {
+ SOCKS5MockData data(true);
+ tcp_client_socket_factory_.AddSocketDataProvider(data.data_provider());
+
+ TestCompletionCallback callback;
+ ClientSocketHandle handle;
+ int rv = handle.Init("a", ignored_socket_params_, LOW, &callback, pool_,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+}
+
+TEST_F(SOCKSClientSocketPoolTest, TCPConnectError) {
+ scoped_ptr<SocketDataProvider> socket_data(new StaticSocketDataProvider());
+ socket_data->set_connect_data(MockConnect(false, ERR_CONNECTION_REFUSED));
+ tcp_client_socket_factory_.AddSocketDataProvider(socket_data.get());
+
+ ClientSocketHandle handle;
+ int rv = handle.Init("a", ignored_socket_params_, LOW, NULL, pool_,
+ BoundNetLog());
+ EXPECT_EQ(ERR_CONNECTION_REFUSED, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+}
+
+TEST_F(SOCKSClientSocketPoolTest, AsyncTCPConnectError) {
+ scoped_ptr<SocketDataProvider> socket_data(new StaticSocketDataProvider());
+ socket_data->set_connect_data(MockConnect(true, ERR_CONNECTION_REFUSED));
+ tcp_client_socket_factory_.AddSocketDataProvider(socket_data.get());
+
+ TestCompletionCallback callback;
+ ClientSocketHandle handle;
+ int rv = handle.Init("a", ignored_socket_params_, LOW, &callback, pool_,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(ERR_CONNECTION_REFUSED, callback.WaitForResult());
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+}
+
+TEST_F(SOCKSClientSocketPoolTest, SOCKSConnectError) {
+ MockRead failed_read[] = {
+ MockRead(false, 0),
+ };
+ scoped_ptr<SocketDataProvider> socket_data(new StaticSocketDataProvider(
+ failed_read, arraysize(failed_read), NULL, 0));
+ socket_data->set_connect_data(MockConnect(false, 0));
+ tcp_client_socket_factory_.AddSocketDataProvider(socket_data.get());
+
+ ClientSocketHandle handle;
+ EXPECT_EQ(0, tcp_socket_pool_->release_count());
+ int rv = handle.Init("a", ignored_socket_params_, LOW, NULL, pool_,
+ BoundNetLog());
+ EXPECT_EQ(ERR_SOCKS_CONNECTION_FAILED, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ EXPECT_EQ(1, tcp_socket_pool_->release_count());
+}
+
+TEST_F(SOCKSClientSocketPoolTest, AsyncSOCKSConnectError) {
+ MockRead failed_read[] = {
+ MockRead(true, 0),
+ };
+ scoped_ptr<SocketDataProvider> socket_data(new StaticSocketDataProvider(
+ failed_read, arraysize(failed_read), NULL, 0));
+ socket_data->set_connect_data(MockConnect(false, 0));
+ tcp_client_socket_factory_.AddSocketDataProvider(socket_data.get());
+
+ TestCompletionCallback callback;
+ ClientSocketHandle handle;
+ EXPECT_EQ(0, tcp_socket_pool_->release_count());
+ int rv = handle.Init("a", ignored_socket_params_, LOW, &callback, pool_,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(ERR_SOCKS_CONNECTION_FAILED, callback.WaitForResult());
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ EXPECT_EQ(1, tcp_socket_pool_->release_count());
+}
+
+TEST_F(SOCKSClientSocketPoolTest, CancelDuringTCPConnect) {
+ SOCKS5MockData data(false);
+ tcp_client_socket_factory_.AddSocketDataProvider(data.data_provider());
+ // We need two connections because the pool base lets one cancelled
+ // connect job proceed for potential future use.
+ SOCKS5MockData data2(false);
+ tcp_client_socket_factory_.AddSocketDataProvider(data2.data_provider());
+
+ EXPECT_EQ(0, tcp_socket_pool_->cancel_count());
+ int rv = StartRequest("a", LOW);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = StartRequest("a", LOW);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ pool_->CancelRequest("a", requests_[0]->handle());
+ pool_->CancelRequest("a", requests_[1]->handle());
+ // Requests in the connect phase don't actually get cancelled.
+ EXPECT_EQ(0, tcp_socket_pool_->cancel_count());
+
+ // Now wait for the TCP sockets to connect.
+ MessageLoop::current()->RunAllPending();
+
+ EXPECT_EQ(kRequestNotFound, GetOrderOfRequest(1));
+ EXPECT_EQ(kRequestNotFound, GetOrderOfRequest(2));
+ EXPECT_EQ(0, tcp_socket_pool_->cancel_count());
+ EXPECT_EQ(2, pool_->IdleSocketCount());
+
+ requests_[0]->handle()->Reset();
+ requests_[1]->handle()->Reset();
+}
+
+TEST_F(SOCKSClientSocketPoolTest, CancelDuringSOCKSConnect) {
+ SOCKS5MockData data(true);
+ data.data_provider()->set_connect_data(MockConnect(false, 0));
+ tcp_client_socket_factory_.AddSocketDataProvider(data.data_provider());
+ // We need two connections because the pool base lets one cancelled
+ // connect job proceed for potential future use.
+ SOCKS5MockData data2(true);
+ data2.data_provider()->set_connect_data(MockConnect(false, 0));
+ tcp_client_socket_factory_.AddSocketDataProvider(data2.data_provider());
+
+ EXPECT_EQ(0, tcp_socket_pool_->cancel_count());
+ EXPECT_EQ(0, tcp_socket_pool_->release_count());
+ int rv = StartRequest("a", LOW);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = StartRequest("a", LOW);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ pool_->CancelRequest("a", requests_[0]->handle());
+ pool_->CancelRequest("a", requests_[1]->handle());
+ EXPECT_EQ(0, tcp_socket_pool_->cancel_count());
+ // Requests in the connect phase don't actually get cancelled.
+ EXPECT_EQ(0, tcp_socket_pool_->release_count());
+
+ // Now wait for the async data to reach the SOCKS connect jobs.
+ MessageLoop::current()->RunAllPending();
+
+ EXPECT_EQ(kRequestNotFound, GetOrderOfRequest(1));
+ EXPECT_EQ(kRequestNotFound, GetOrderOfRequest(2));
+ EXPECT_EQ(0, tcp_socket_pool_->cancel_count());
+ EXPECT_EQ(0, tcp_socket_pool_->release_count());
+ EXPECT_EQ(2, pool_->IdleSocketCount());
+
+ requests_[0]->handle()->Reset();
+ requests_[1]->handle()->Reset();
+}
+
+// It would be nice to also test the timeouts in SOCKSClientSocketPool.
+
+} // namespace
+
+} // namespace net
diff --git a/net/socket/socks_client_socket_unittest.cc b/net/socket/socks_client_socket_unittest.cc
index dfa078f..aaabc4b 100644
--- a/net/socket/socks_client_socket_unittest.cc
+++ b/net/socket/socks_client_socket_unittest.cc
@@ -1,12 +1,12 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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 "net/socket/socks_client_socket.h"
#include "net/base/address_list.h"
-#include "net/base/load_log.h"
-#include "net/base/load_log_unittest.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
#include "net/base/mock_host_resolver.h"
#include "net/base/test_completion_callback.h"
#include "net/base/winsock_init.h"
@@ -29,9 +29,11 @@
public:
SOCKSClientSocketTest();
// Create a SOCKSClientSocket on top of a MockSocket.
- SOCKSClientSocket* BuildMockSocket(MockRead reads[], MockWrite writes[],
+ SOCKSClientSocket* BuildMockSocket(MockRead reads[], size_t reads_count,
+ MockWrite writes[], size_t writes_count,
HostResolver* host_resolver,
- const std::string& hostname, int port);
+ const std::string& hostname, int port,
+ NetLog* net_log);
virtual void SetUp();
protected:
@@ -57,16 +59,20 @@
SOCKSClientSocket* SOCKSClientSocketTest::BuildMockSocket(
MockRead reads[],
+ size_t reads_count,
MockWrite writes[],
+ size_t writes_count,
HostResolver* host_resolver,
const std::string& hostname,
- int port) {
+ int port,
+ NetLog* net_log) {
TestCompletionCallback callback;
- data_.reset(new StaticSocketDataProvider(reads, writes));
- tcp_sock_ = new MockTCPClientSocket(address_list_, data_.get());
+ data_.reset(new StaticSocketDataProvider(reads, reads_count,
+ writes, writes_count));
+ tcp_sock_ = new MockTCPClientSocket(address_list_, net_log, data_.get());
- int rv = tcp_sock_->Connect(&callback, NULL);
+ int rv = tcp_sock_->Connect(&callback);
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(OK, rv);
@@ -88,7 +94,7 @@
AddressList* addresses,
CompletionCallback* callback,
RequestHandle* out_req,
- LoadLog* load_log) {
+ const BoundNetLog& net_log) {
EXPECT_FALSE(HasOutstandingRequest());
outstanding_request_ = reinterpret_cast<RequestHandle>(1);
*out_req = outstanding_request_;
@@ -103,7 +109,6 @@
virtual void AddObserver(Observer* observer) {}
virtual void RemoveObserver(Observer* observer) {}
- virtual void Shutdown() {}
bool HasOutstandingRequest() {
return outstanding_request_ != NULL;
@@ -126,26 +131,28 @@
MockRead data_reads[] = {
MockRead(true, kSOCKSOkReply, arraysize(kSOCKSOkReply)),
MockRead(true, payload_read.data(), payload_read.size()) };
+ CapturingNetLog log(CapturingNetLog::kUnbounded);
- user_sock_.reset(BuildMockSocket(data_reads, data_writes, host_resolver_,
- "localhost", 80));
+ user_sock_.reset(BuildMockSocket(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes),
+ host_resolver_, "localhost", 80, &log));
// At this state the TCP connection is completed but not the SOCKS handshake.
EXPECT_TRUE(tcp_sock_->IsConnected());
EXPECT_FALSE(user_sock_->IsConnected());
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
- int rv = user_sock_->Connect(&callback_, log);
+ int rv = user_sock_->Connect(&callback_);
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_TRUE(
- LogContainsBeginEvent(*log, 0, LoadLog::TYPE_SOCKS_CONNECT));
+ LogContainsBeginEvent(log.entries(), 0, NetLog::TYPE_SOCKS_CONNECT));
EXPECT_FALSE(user_sock_->IsConnected());
rv = callback_.WaitForResult();
EXPECT_EQ(OK, rv);
EXPECT_TRUE(user_sock_->IsConnected());
EXPECT_EQ(SOCKSClientSocket::kSOCKS4, user_sock_->socks_version_);
- EXPECT_TRUE(LogContainsEndEvent(*log, -1, LoadLog::TYPE_SOCKS_CONNECT));
+ EXPECT_TRUE(LogContainsEndEvent(
+ log.entries(), -1, NetLog::TYPE_SOCKS_CONNECT));
scoped_refptr<IOBuffer> buffer = new IOBuffer(payload_write.size());
memcpy(buffer->data(), payload_write.data(), payload_write.size());
@@ -176,12 +183,12 @@
// Failure of the server response code
{
{ 0x01, 0x5A, 0x00, 0x00, 0, 0, 0, 0 },
- ERR_INVALID_RESPONSE,
+ ERR_SOCKS_CONNECTION_FAILED,
},
// Failure of the null byte
{
{ 0x00, 0x5B, 0x00, 0x00, 0, 0, 0, 0 },
- ERR_FAILED,
+ ERR_SOCKS_CONNECTION_FAILED,
},
};
@@ -192,19 +199,22 @@
MockWrite(false, kSOCKSOkRequest, arraysize(kSOCKSOkRequest)) };
MockRead data_reads[] = {
MockRead(false, tests[i].fail_reply, arraysize(tests[i].fail_reply)) };
+ CapturingNetLog log(CapturingNetLog::kUnbounded);
- user_sock_.reset(BuildMockSocket(data_reads, data_writes, host_resolver_,
- "localhost", 80));
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
+ user_sock_.reset(BuildMockSocket(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes),
+ host_resolver_, "localhost", 80, &log));
- int rv = user_sock_->Connect(&callback_, log);
+ int rv = user_sock_->Connect(&callback_);
EXPECT_EQ(ERR_IO_PENDING, rv);
- EXPECT_TRUE(LogContainsBeginEvent(*log, 0, LoadLog::TYPE_SOCKS_CONNECT));
+ EXPECT_TRUE(LogContainsBeginEvent(
+ log.entries(), 0, NetLog::TYPE_SOCKS_CONNECT));
rv = callback_.WaitForResult();
EXPECT_EQ(tests[i].fail_code, rv);
EXPECT_FALSE(user_sock_->IsConnected());
EXPECT_TRUE(tcp_sock_->IsConnected());
- EXPECT_TRUE(LogContainsEndEvent(*log, -1, LoadLog::TYPE_SOCKS_CONNECT));
+ EXPECT_TRUE(LogContainsEndEvent(
+ log.entries(), -1, NetLog::TYPE_SOCKS_CONNECT));
}
}
@@ -219,19 +229,21 @@
MockRead data_reads[] = {
MockRead(true, kSOCKSPartialReply1, arraysize(kSOCKSPartialReply1)),
MockRead(true, kSOCKSPartialReply2, arraysize(kSOCKSPartialReply2)) };
+ CapturingNetLog log(CapturingNetLog::kUnbounded);
- user_sock_.reset(BuildMockSocket(data_reads, data_writes, host_resolver_,
- "localhost", 80));
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
+ user_sock_.reset(BuildMockSocket(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes),
+ host_resolver_, "localhost", 80, &log));
- int rv = user_sock_->Connect(&callback_, log);
+ int rv = user_sock_->Connect(&callback_);
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_TRUE(LogContainsBeginEvent(
- *log, 0, LoadLog::TYPE_SOCKS_CONNECT));
+ log.entries(), 0, NetLog::TYPE_SOCKS_CONNECT));
rv = callback_.WaitForResult();
EXPECT_EQ(OK, rv);
EXPECT_TRUE(user_sock_->IsConnected());
- EXPECT_TRUE(LogContainsEndEvent(*log, -1, LoadLog::TYPE_SOCKS_CONNECT));
+ EXPECT_TRUE(LogContainsEndEvent(
+ log.entries(), -1, NetLog::TYPE_SOCKS_CONNECT));
}
// Tests scenario when the client sends the handshake request in
@@ -249,18 +261,21 @@
arraysize(kSOCKSPartialRequest2)) };
MockRead data_reads[] = {
MockRead(true, kSOCKSOkReply, arraysize(kSOCKSOkReply)) };
+ CapturingNetLog log(CapturingNetLog::kUnbounded);
- user_sock_.reset(BuildMockSocket(data_reads, data_writes, host_resolver_,
- "localhost", 80));
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
+ user_sock_.reset(BuildMockSocket(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes),
+ host_resolver_, "localhost", 80, &log));
- int rv = user_sock_->Connect(&callback_, log);
+ int rv = user_sock_->Connect(&callback_);
EXPECT_EQ(ERR_IO_PENDING, rv);
- EXPECT_TRUE(LogContainsBeginEvent(*log, 0, LoadLog::TYPE_SOCKS_CONNECT));
+ EXPECT_TRUE(LogContainsBeginEvent(
+ log.entries(), 0, NetLog::TYPE_SOCKS_CONNECT));
rv = callback_.WaitForResult();
EXPECT_EQ(OK, rv);
EXPECT_TRUE(user_sock_->IsConnected());
- EXPECT_TRUE(LogContainsEndEvent(*log, -1, LoadLog::TYPE_SOCKS_CONNECT));
+ EXPECT_TRUE(LogContainsEndEvent(
+ log.entries(), -1, NetLog::TYPE_SOCKS_CONNECT));
}
// Tests the case when the server sends a smaller sized handshake data
@@ -272,18 +287,21 @@
MockRead(true, kSOCKSOkReply, arraysize(kSOCKSOkReply) - 2),
// close connection unexpectedly
MockRead(false, 0) };
+ CapturingNetLog log(CapturingNetLog::kUnbounded);
- user_sock_.reset(BuildMockSocket(data_reads, data_writes, host_resolver_,
- "localhost", 80));
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
+ user_sock_.reset(BuildMockSocket(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes),
+ host_resolver_, "localhost", 80, &log));
- int rv = user_sock_->Connect(&callback_, log);
+ int rv = user_sock_->Connect(&callback_);
EXPECT_EQ(ERR_IO_PENDING, rv);
- EXPECT_TRUE(LogContainsBeginEvent(*log, 0, LoadLog::TYPE_SOCKS_CONNECT));
+ EXPECT_TRUE(LogContainsBeginEvent(
+ log.entries(), 0, NetLog::TYPE_SOCKS_CONNECT));
rv = callback_.WaitForResult();
EXPECT_EQ(ERR_CONNECTION_CLOSED, rv);
EXPECT_FALSE(user_sock_->IsConnected());
- EXPECT_TRUE(LogContainsEndEvent(*log, -1, LoadLog::TYPE_SOCKS_CONNECT));
+ EXPECT_TRUE(LogContainsEndEvent(
+ log.entries(), -1, NetLog::TYPE_SOCKS_CONNECT));
}
// Tries to connect to an unknown DNS and on failure should revert to SOCKS4A.
@@ -300,19 +318,22 @@
MockWrite(false, request.data(), request.size()) };
MockRead data_reads[] = {
MockRead(false, kSOCKSOkReply, arraysize(kSOCKSOkReply)) };
+ CapturingNetLog log(CapturingNetLog::kUnbounded);
- user_sock_.reset(BuildMockSocket(data_reads, data_writes, host_resolver_,
- hostname, 80));
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
+ user_sock_.reset(BuildMockSocket(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes),
+ host_resolver_, hostname, 80, &log));
- int rv = user_sock_->Connect(&callback_, log);
+ int rv = user_sock_->Connect(&callback_);
EXPECT_EQ(ERR_IO_PENDING, rv);
- EXPECT_TRUE(LogContainsBeginEvent(*log, 0, LoadLog::TYPE_SOCKS_CONNECT));
+ EXPECT_TRUE(LogContainsBeginEvent(
+ log.entries(), 0, NetLog::TYPE_SOCKS_CONNECT));
rv = callback_.WaitForResult();
EXPECT_EQ(OK, rv);
EXPECT_TRUE(user_sock_->IsConnected());
EXPECT_EQ(SOCKSClientSocket::kSOCKS4a, user_sock_->socks_version_);
- EXPECT_TRUE(LogContainsEndEvent(*log, -1, LoadLog::TYPE_SOCKS_CONNECT));
+ EXPECT_TRUE(LogContainsEndEvent(
+ log.entries(), -1, NetLog::TYPE_SOCKS_CONNECT));
}
// Tries to connect to a domain that resolves to IPv6.
@@ -320,7 +341,8 @@
TEST_F(SOCKSClientSocketTest, SOCKS4AIfDomainInIPv6) {
const char hostname[] = "an.ipv6.address";
- host_resolver_->rules()->AddIPv6Rule(hostname, "2001:db8:8714:3a90::12");
+ host_resolver_->rules()->AddIPLiteralRule(hostname,
+ "2001:db8:8714:3a90::12", "");
std::string request(kSOCKS4aInitialRequest,
arraysize(kSOCKS4aInitialRequest));
@@ -330,19 +352,22 @@
MockWrite(false, request.data(), request.size()) };
MockRead data_reads[] = {
MockRead(false, kSOCKSOkReply, arraysize(kSOCKSOkReply)) };
+ CapturingNetLog log(CapturingNetLog::kUnbounded);
- user_sock_.reset(BuildMockSocket(data_reads, data_writes, host_resolver_,
- hostname, 80));
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
+ user_sock_.reset(BuildMockSocket(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes),
+ host_resolver_, hostname, 80, &log));
- int rv = user_sock_->Connect(&callback_, log);
+ int rv = user_sock_->Connect(&callback_);
EXPECT_EQ(ERR_IO_PENDING, rv);
- EXPECT_TRUE(LogContainsBeginEvent(*log, 0, LoadLog::TYPE_SOCKS_CONNECT));
+ EXPECT_TRUE(LogContainsBeginEvent(
+ log.entries(), 0, NetLog::TYPE_SOCKS_CONNECT));
rv = callback_.WaitForResult();
EXPECT_EQ(OK, rv);
EXPECT_TRUE(user_sock_->IsConnected());
EXPECT_EQ(SOCKSClientSocket::kSOCKS4a, user_sock_->socks_version_);
- EXPECT_TRUE(LogContainsEndEvent(*log, -1, LoadLog::TYPE_SOCKS_CONNECT));
+ EXPECT_TRUE(LogContainsEndEvent(
+ log.entries(), -1, NetLog::TYPE_SOCKS_CONNECT));
}
// Calls Disconnect() while a host resolve is in progress. The outstanding host
@@ -355,11 +380,12 @@
MockWrite data_writes[] = { MockWrite(false, "", 0) };
MockRead data_reads[] = { MockRead(false, "", 0) };
- user_sock_.reset(BuildMockSocket(data_reads, data_writes, hanging_resolver,
- "foo", 80));
+ user_sock_.reset(BuildMockSocket(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes),
+ hanging_resolver, "foo", 80, NULL));
// Start connecting (will get stuck waiting for the host to resolve).
- int rv = user_sock_->Connect(&callback_, NULL);
+ int rv = user_sock_->Connect(&callback_);
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_FALSE(user_sock_->IsConnected());
diff --git a/net/socket/ssl_client_socket.h b/net/socket/ssl_client_socket.h
index 71184bc..9c34282 100644
--- a/net/socket/ssl_client_socket.h
+++ b/net/socket/ssl_client_socket.h
@@ -5,6 +5,10 @@
#ifndef NET_SOCKET_SSL_CLIENT_SOCKET_H_
#define NET_SOCKET_SSL_CLIENT_SOCKET_H_
+#include <string>
+
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
#include "net/socket/client_socket.h"
namespace net {
@@ -20,6 +24,8 @@
//
class SSLClientSocket : public ClientSocket {
public:
+ SSLClientSocket() : was_npn_negotiated_(false) {
+ }
// Next Protocol Negotiation (NPN) allows a TLS client and server to come to
// an agreement about the application level protocol to speak over a
// connection.
@@ -37,7 +43,7 @@
enum NextProto {
kProtoUnknown = 0,
kProtoHTTP11 = 1,
- kProtoSPDY = 2,
+ kProtoSPDY1 = 2,
};
// Gets the SSL connection information of the socket.
@@ -58,14 +64,43 @@
virtual NextProtoStatus GetNextProto(std::string* proto) = 0;
static NextProto NextProtoFromString(const std::string& proto_string) {
- if (proto_string == "http1.1") {
+ if (proto_string == "http1.1" || proto_string == "http/1.1") {
return kProtoHTTP11;
- } else if (proto_string == "spdy") {
- return kProtoSPDY;
+ } else if (proto_string == "spdy" || proto_string == "spdy/1") {
+ return kProtoSPDY1;
} else {
return kProtoUnknown;
}
}
+
+ static bool IgnoreCertError(int error, int load_flags) {
+ if (error == OK || load_flags & LOAD_IGNORE_ALL_CERT_ERRORS)
+ return true;
+
+ if (error == ERR_CERT_COMMON_NAME_INVALID &&
+ (load_flags & LOAD_IGNORE_CERT_COMMON_NAME_INVALID))
+ return true;
+ if(error == ERR_CERT_DATE_INVALID &&
+ (load_flags & LOAD_IGNORE_CERT_DATE_INVALID))
+ return true;
+ if(error == ERR_CERT_AUTHORITY_INVALID &&
+ (load_flags & LOAD_IGNORE_CERT_AUTHORITY_INVALID))
+ return true;
+
+ return false;
+ }
+
+ virtual bool wasNpnNegotiated() const {
+ return was_npn_negotiated_;
+ }
+
+ virtual bool setWasNpnNegotiated(bool negotiated) {
+ return was_npn_negotiated_ = negotiated;
+ }
+
+ private:
+ // True if NPN was responded to, independent of selecting SPDY or HTTP.
+ bool was_npn_negotiated_;
};
} // namespace net
diff --git a/net/socket/ssl_client_socket_mac.cc b/net/socket/ssl_client_socket_mac.cc
index 2fb43d1..c3c7d7a 100644
--- a/net/socket/ssl_client_socket_mac.cc
+++ b/net/socket/ssl_client_socket_mac.cc
@@ -1,19 +1,26 @@
-// Copyright (c) 2008-2009 The Chromium Authors. All rights reserved.
+// 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 "net/socket/ssl_client_socket_mac.h"
#include <CoreServices/CoreServices.h>
+#include <netdb.h>
+#include <sys/socket.h>
+#include <sys/types.h>
#include "base/scoped_cftyperef.h"
#include "base/singleton.h"
#include "base/string_util.h"
+#include "net/base/address_list.h"
#include "net/base/cert_verifier.h"
#include "net/base/io_buffer.h"
-#include "net/base/load_log.h"
#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/ssl_cert_request_info.h"
+#include "net/base/ssl_connection_status_flags.h"
#include "net/base/ssl_info.h"
+#include "net/socket/client_socket_handle.h"
// Welcome to Mac SSL. We've been waiting for you.
//
@@ -93,19 +100,28 @@
namespace {
+// Pause if we have 2MB of data in flight, resume once we're down below 1MB.
+const unsigned int kWriteSizePauseLimit = 2 * 1024 * 1024;
+const unsigned int kWriteSizeResumeLimit = 1 * 1024 * 1024;
+
+// You can change this to LOG(WARNING) during development.
+#define SSL_LOG LOG(INFO) << "SSL: "
+
+#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5
// Declarations needed to call the 10.5.7 and later SSLSetSessionOption()
// function when building with the 10.5.0 SDK.
typedef enum {
- kSSLSessionOptionBreakOnServerAuthFlag
-} SSLSetSessionOptionType;
+ kSSLSessionOptionBreakOnServerAuth,
+ kSSLSessionOptionBreakOnCertRequested,
+} SSLSessionOption;
enum {
- errSSLServerAuthCompletedFlag = -9841
+ errSSLServerAuthCompleted = -9841,
+ errSSLClientCertRequested = -9842,
};
// When compiled against the Mac OS X 10.5 SDK, define symbolic constants for
// cipher suites added in Mac OS X 10.6.
-#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5
enum {
// ECC cipher suites from RFC 4492.
TLS_ECDH_ECDSA_WITH_NULL_SHA = 0xC001,
@@ -137,7 +153,7 @@
#endif
typedef OSStatus (*SSLSetSessionOptionFuncPtr)(SSLContextRef,
- SSLSetSessionOptionType,
+ SSLSessionOption,
Boolean);
// For an explanation of the Mac OS X error codes, please refer to:
// http://developer.apple.com/mac/library/documentation/Security/Reference/secureTransportRef/Reference/reference.html
@@ -173,13 +189,18 @@
case errSSLXCertChainInvalid:
case errSSLBadCert:
return ERR_CERT_INVALID;
- case errSSLPeerCertRevoked:
- return ERR_CERT_REVOKED;
case errSSLClosedGraceful:
case noErr:
return OK;
+ case errSSLPeerCertUnknown...errSSLPeerBadCert:
+ case errSSLPeerInsufficientSecurity...errSSLPeerUnknownCA:
+ // (Note that all errSSLPeer* codes indicate errors reported by the
+ // peer, so the cert-related ones refer to my _client_ cert.)
+ LOG(WARNING) << "Server rejected client cert (OSStatus=" << status << ")";
+ return ERR_BAD_SSL_CLIENT_AUTH_CERT;
+
case errSSLBadRecordMac:
case errSSLBufferOverflow:
case errSSLDecryptionFail:
@@ -400,29 +421,31 @@
DCHECK_GT(CFArrayGetCount(certs), 0);
- SecCertificateRef server_cert = static_cast<SecCertificateRef>(
- const_cast<void*>(CFArrayGetValueAtIndex(certs, 0)));
- CFRetain(server_cert);
- X509Certificate *x509_cert = X509Certificate::CreateFromHandle(
- server_cert, X509Certificate::SOURCE_FROM_NETWORK);
- if (!x509_cert)
- return NULL;
-
// Add each of the intermediate certificates in the server's chain to the
// server's X509Certificate object. This makes them available to
// X509Certificate::Verify() for chain building.
- // TODO(wtc): Since X509Certificate::CreateFromHandle may return a cached
- // X509Certificate object, we may be adding intermediate CA certificates to
- // it repeatedly!
+ std::vector<SecCertificateRef> intermediate_ca_certs;
CFIndex certs_length = CFArrayGetCount(certs);
for (CFIndex i = 1; i < certs_length; ++i) {
SecCertificateRef cert_ref = reinterpret_cast<SecCertificateRef>(
const_cast<void*>(CFArrayGetValueAtIndex(certs, i)));
- CFRetain(cert_ref);
- x509_cert->AddIntermediateCertificate(cert_ref);
+ intermediate_ca_certs.push_back(cert_ref);
}
- return x509_cert;
+ SecCertificateRef server_cert = static_cast<SecCertificateRef>(
+ const_cast<void*>(CFArrayGetValueAtIndex(certs, 0)));
+ return X509Certificate::CreateFromHandle(
+ server_cert, X509Certificate::SOURCE_FROM_NETWORK, intermediate_ca_certs);
+}
+
+// Dynamically look up a pointer to a function exported by a bundle.
+template <typename FNTYPE>
+FNTYPE LookupFunction(CFStringRef bundleName, CFStringRef fnName) {
+ CFBundleRef bundle = CFBundleGetBundleWithIdentifier(bundleName);
+ if (!bundle)
+ return NULL;
+ return reinterpret_cast<FNTYPE>(
+ CFBundleGetFunctionPointerForName(bundle, fnName));
}
// A class that wraps an array of enabled cipher suites that can be passed to
@@ -475,7 +498,7 @@
//-----------------------------------------------------------------------------
-SSLClientSocketMac::SSLClientSocketMac(ClientSocket* transport_socket,
+SSLClientSocketMac::SSLClientSocketMac(ClientSocketHandle* transport_socket,
const std::string& hostname,
const SSLConfig& ssl_config)
: handshake_io_callback_(this, &SSLClientSocketMac::OnHandshakeIOComplete),
@@ -494,35 +517,35 @@
next_handshake_state_(STATE_NONE),
completed_handshake_(false),
handshake_interrupted_(false),
+ client_cert_requested_(false),
ssl_context_(NULL),
- pending_send_error_(OK) {
+ pending_send_error_(OK),
+ net_log_(transport_socket->socket()->NetLog()) {
}
SSLClientSocketMac::~SSLClientSocketMac() {
Disconnect();
}
-int SSLClientSocketMac::Connect(CompletionCallback* callback,
- LoadLog* load_log) {
+int SSLClientSocketMac::Connect(CompletionCallback* callback) {
DCHECK(transport_.get());
DCHECK(next_handshake_state_ == STATE_NONE);
DCHECK(!user_connect_callback_);
- LoadLog::BeginEvent(load_log, LoadLog::TYPE_SSL_CONNECT);
+ net_log_.BeginEvent(NetLog::TYPE_SSL_CONNECT, NULL);
int rv = InitializeSSLContext();
if (rv != OK) {
- LoadLog::EndEvent(load_log, LoadLog::TYPE_SSL_CONNECT);
+ net_log_.EndEvent(NetLog::TYPE_SSL_CONNECT, NULL);
return rv;
}
next_handshake_state_ = STATE_HANDSHAKE_START;
rv = DoHandshakeLoop(OK);
if (rv == ERR_IO_PENDING) {
- load_log_ = load_log;
user_connect_callback_ = callback;
} else {
- LoadLog::EndEvent(load_log, LoadLog::TYPE_SSL_CONNECT);
+ net_log_.EndEvent(NetLog::TYPE_SSL_CONNECT, NULL);
}
return rv;
}
@@ -534,11 +557,12 @@
SSLClose(ssl_context_);
SSLDisposeContext(ssl_context_);
ssl_context_ = NULL;
+ SSL_LOG << "----- Disposed SSLContext";
}
// Shut down anything that may call us back.
verifier_.reset();
- transport_->Disconnect();
+ transport_->socket()->Disconnect();
}
bool SSLClientSocketMac::IsConnected() const {
@@ -548,7 +572,7 @@
// layer (HttpNetworkTransaction) needs to handle a persistent connection
// closed by the server when we send a request anyway, a false positive in
// exchange for simpler code is a good trade-off.
- return completed_handshake_ && transport_->IsConnected();
+ return completed_handshake_ && transport_->socket()->IsConnected();
}
bool SSLClientSocketMac::IsConnectedAndIdle() const {
@@ -557,13 +581,14 @@
// Strictly speaking, we should check if we have received the close_notify
// alert message from the server, and return false in that case. Although
// the close_notify alert message means EOF in the SSL layer, it is just
- // bytes to the transport layer below, so transport_->IsConnectedAndIdle()
- // returns the desired false when we receive close_notify.
- return completed_handshake_ && transport_->IsConnectedAndIdle();
+ // bytes to the transport layer below, so
+ // transport_->socket()->IsConnectedAndIdle() returns the desired false
+ // when we receive close_notify.
+ return completed_handshake_ && transport_->socket()->IsConnectedAndIdle();
}
-int SSLClientSocketMac::GetPeerName(struct sockaddr* name, socklen_t* namelen) {
- return transport_->GetPeerName(name, namelen);
+int SSLClientSocketMac::GetPeerAddress(AddressList* address) const {
+ return transport_->socket()->GetPeerAddress(address);
}
int SSLClientSocketMac::Read(IOBuffer* buf, int buf_len,
@@ -605,11 +630,11 @@
}
bool SSLClientSocketMac::SetReceiveBufferSize(int32 size) {
- return transport_->SetReceiveBufferSize(size);
+ return transport_->socket()->SetReceiveBufferSize(size);
}
bool SSLClientSocketMac::SetSendBufferSize(int32 size) {
- return transport_->SetSendBufferSize(size);
+ return transport_->socket()->SetSendBufferSize(size);
}
void SSLClientSocketMac::GetSSLInfo(SSLInfo* ssl_info) {
@@ -630,11 +655,43 @@
OSStatus status = SSLGetNegotiatedCipher(ssl_context_, &suite);
if (!status)
ssl_info->security_bits = KeySizeOfCipherSuite(suite);
+
+ if (ssl_config_.ssl3_fallback)
+ ssl_info->connection_status |= SSL_CONNECTION_SSL3_FALLBACK;
}
void SSLClientSocketMac::GetSSLCertRequestInfo(
SSLCertRequestInfo* cert_request_info) {
- // TODO(wtc): implement this.
+ // I'm being asked for available client certs (identities).
+ // First, get the cert issuer names allowed by the server.
+ std::vector<CertPrincipal> valid_issuers;
+ CFArrayRef valid_issuer_names = NULL;
+ if (SSLCopyDistinguishedNames(ssl_context_, &valid_issuer_names) == noErr &&
+ valid_issuer_names != NULL) {
+ SSL_LOG << "Server has " << CFArrayGetCount(valid_issuer_names)
+ << " valid issuer names";
+ int n = CFArrayGetCount(valid_issuer_names);
+ for (int i = 0; i < n; i++) {
+ // Parse each name into a CertPrincipal object.
+ CFDataRef issuer = reinterpret_cast<CFDataRef>(
+ CFArrayGetValueAtIndex(valid_issuer_names, i));
+ CertPrincipal p;
+ if (p.ParseDistinguishedName(CFDataGetBytePtr(issuer),
+ CFDataGetLength(issuer))) {
+ valid_issuers.push_back(p);
+ }
+ }
+ CFRelease(valid_issuer_names);
+ }
+
+ // Now get the available client certs whose issuers are allowed by the server.
+ cert_request_info->host_and_port = hostname_;
+ cert_request_info->client_certs.clear();
+ X509Certificate::GetSSLClientCertificates(hostname_,
+ valid_issuers,
+ &cert_request_info->client_certs);
+ SSL_LOG << "Asking user to choose between "
+ << cert_request_info->client_certs.size() << " client certs...";
}
SSLClientSocket::NextProtoStatus
@@ -643,7 +700,36 @@
return kNextProtoUnsupported;
}
+OSStatus SSLClientSocketMac::EnableBreakOnAuth(bool enabled) {
+ // SSLSetSessionOption() was introduced in Mac OS X 10.5.7. It allows us
+ // to perform certificate validation during the handshake, which is
+ // required in order to properly enable session resumption.
+ //
+ // With the kSSLSessionOptionBreakOnServerAuth option set, SSLHandshake()
+ // will return errSSLServerAuthCompleted after receiving the server's
+ // Certificate during the handshake. That gives us an opportunity to verify
+ // the server certificate and then re-enter that handshake (assuming the
+ // certificate successfully validated).
+ // If the server also requests a client cert, SSLHandshake() will return
+ // errSSLClientCertRequested in addition to (or in some cases *instead of*)
+ // errSSLServerAuthCompleted.
+ static SSLSetSessionOptionFuncPtr ssl_set_session_options =
+ LookupFunction<SSLSetSessionOptionFuncPtr>(CFSTR("com.apple.security"),
+ CFSTR("SSLSetSessionOption"));
+ if (!ssl_set_session_options)
+ return unimpErr; // Return this if the API isn't available
+ OSStatus err = ssl_set_session_options(ssl_context_,
+ kSSLSessionOptionBreakOnServerAuth,
+ enabled);
+ if (err)
+ return err;
+ return ssl_set_session_options(ssl_context_,
+ kSSLSessionOptionBreakOnCertRequested,
+ enabled);
+}
+
int SSLClientSocketMac::InitializeSSLContext() {
+ SSL_LOG << "----- InitializeSSLContext";
OSStatus status = noErr;
status = SSLNewContext(false, &ssl_context_);
@@ -683,65 +769,63 @@
if (status)
return NetErrorFromOSStatus(status);
+ // Passing the domain name enables the server_name TLS extension (SNI).
+ status = SSLSetPeerDomainName(ssl_context_,
+ hostname_.data(),
+ hostname_.length());
+ if (status)
+ return NetErrorFromOSStatus(status);
+
// Disable certificate verification within Secure Transport; we'll
// be handling that ourselves.
status = SSLSetEnableCertVerify(ssl_context_, false);
if (status)
return NetErrorFromOSStatus(status);
- // SSLSetSessionOption() was introduced in Mac OS X 10.5.7. It allows us
- // to perform certificate validation during the handshake, which is
- // required in order to properly enable session resumption.
- //
- // With the kSSLSessionOptionBreakOnServerAuth option set, SSLHandshake()
- // will return errSSLServerAuthCompleted after receiving the server's
- // Certificate during the handshake. That gives us an opportunity to verify
- // the server certificate and then re-enter that handshake (assuming the
- // certificate successfully validated).
- //
- // If SSLSetSessionOption() is not present, we do not enable session
- // resumption, because in that case we are verifying the server's certificate
- // after the handshake completes (but before any application data is
- // exchanged). If we were to enable session resumption in this situation,
- // the session would be cached before we verified the certificate, leaving
- // the potential for a session in which the certificate failed to validate
- // to still be able to be resumed.
- CFBundleRef bundle =
- CFBundleGetBundleWithIdentifier(CFSTR("com.apple.security"));
- if (bundle) {
- SSLSetSessionOptionFuncPtr ssl_set_session_options =
- reinterpret_cast<SSLSetSessionOptionFuncPtr>(
- CFBundleGetFunctionPointerForName(bundle,
- CFSTR("SSLSetSessionOption")));
- if (ssl_set_session_options) {
- status = ssl_set_session_options(ssl_context_,
- kSSLSessionOptionBreakOnServerAuthFlag,
- true);
- if (status)
- return NetErrorFromOSStatus(status);
+ if (ssl_config_.send_client_cert) {
+ // Provide the client cert up-front if we have one, even though we'll get
+ // notified later when the server requests it, and set it again; this is
+ // seemingly redundant but works around a problem with SecureTransport
+ // and provides correct behavior on both 10.5 and 10.6:
+ // http://lists.apple.com/archives/apple-cdsa/2010/Feb/msg00058.html
+ // http://code.google.com/p/chromium/issues/detail?id=38905
+ SSL_LOG << "Setting client cert in advance because send_client_cert is set";
+ status = SetClientCert();
+ if (status)
+ return NetErrorFromOSStatus(status);
+ }
- // Concatenate the hostname and peer address to use as the peer ID. To
- // resume a session, we must connect to the same server on the same port
- // using the same hostname (i.e., localhost and 127.0.0.1 are considered
- // different peers, which puts us through certificate validation again
- // and catches hostname/certificate name mismatches.
- struct sockaddr_storage addr;
- socklen_t addr_length = sizeof(struct sockaddr_storage);
- memset(&addr, 0, sizeof(addr));
- if (!transport_->GetPeerName(reinterpret_cast<struct sockaddr*>(&addr),
- &addr_length)) {
- // Assemble the socket hostname and address into a single buffer.
- std::vector<char> peer_id(hostname_.begin(), hostname_.end());
- peer_id.insert(peer_id.end(), reinterpret_cast<char*>(&addr),
- reinterpret_cast<char*>(&addr) + addr_length);
+ status = EnableBreakOnAuth(true);
+ if (status == noErr) {
+ // Only enable session resumption if break-on-auth is available,
+ // because without break-on-auth we are verifying the server's certificate
+ // after the handshake completes (but before any application data is
+ // exchanged). If we were to enable session resumption in this situation,
+ // the session would be cached before we verified the certificate, leaving
+ // the potential for a session in which the certificate failed to validate
+ // to still be able to be resumed.
- // SSLSetPeerID() treats peer_id as a binary blob, and makes its
- // own copy.
- status = SSLSetPeerID(ssl_context_, &peer_id[0], peer_id.size());
- if (status)
- return NetErrorFromOSStatus(status);
- }
- }
+ // Concatenate the hostname and peer address to use as the peer ID. To
+ // resume a session, we must connect to the same server on the same port
+ // using the same hostname (i.e., localhost and 127.0.0.1 are considered
+ // different peers, which puts us through certificate validation again
+ // and catches hostname/certificate name mismatches.
+ AddressList address;
+ int rv = transport_->socket()->GetPeerAddress(&address);
+ if (rv != OK)
+ return rv;
+ const struct addrinfo* ai = address.head();
+ std::string peer_id(hostname_);
+ peer_id += std::string(reinterpret_cast<char*>(ai->ai_addr),
+ ai->ai_addrlen);
+
+ // SSLSetPeerID() treats peer_id as a binary blob, and makes its
+ // own copy.
+ status = SSLSetPeerID(ssl_context_, peer_id.data(), peer_id.length());
+ if (status)
+ return NetErrorFromOSStatus(status);
+ } else if (status != unimpErr) { // it's OK if the API isn't available
+ return NetErrorFromOSStatus(status);
}
return OK;
@@ -787,8 +871,7 @@
DCHECK(next_handshake_state_ != STATE_NONE);
int rv = DoHandshakeLoop(result);
if (rv != ERR_IO_PENDING) {
- LoadLog::EndEvent(load_log_, LoadLog::TYPE_SSL_CONNECT);
- load_log_ = NULL;
+ net_log_.EndEvent(NetLog::TYPE_SSL_CONNECT, NULL);
DoConnectCallback(rv);
}
}
@@ -804,8 +887,7 @@
if (next_handshake_state_ != STATE_NONE) {
int rv = DoHandshakeLoop(result);
if (rv != ERR_IO_PENDING) {
- LoadLog::EndEvent(load_log_, LoadLog::TYPE_SSL_CONNECT);
- load_log_ = NULL;
+ net_log_.EndEvent(NetLog::TYPE_SSL_CONNECT, NULL);
DoConnectCallback(rv);
}
return;
@@ -834,8 +916,13 @@
if (!send_buffer_.empty())
SSLWriteCallback(this, NULL, NULL);
- // Since SSLWriteCallback() lies to return noErr even if transport_->Write()
- // returns ERR_IO_PENDING, we don't need to call any callbacks here.
+ // If paused because too much data is in flight, try writing again and make
+ // the promised callback.
+ if (user_write_buf_ && send_buffer_.size() < kWriteSizeResumeLimit) {
+ int rv = DoPayloadWrite();
+ if (rv != ERR_IO_PENDING)
+ DoWriteCallback(rv);
+ }
}
// This is the main loop driving the state machine. Most calls coming from the
@@ -874,27 +961,48 @@
int SSLClientSocketMac::DoHandshakeStart() {
OSStatus status = SSLHandshake(ssl_context_);
- if (status == errSSLWouldBlock)
- next_handshake_state_ = STATE_HANDSHAKE_START;
- if (status == noErr || status == errSSLServerAuthCompletedFlag) {
- // TODO(hawk): we verify the certificate chain even on resumed sessions
- // so that we have the certificate status (valid, expired but overridden
- // by the user, EV, etc.) available. Eliminate this step once we have
- // a certificate validation result cache.
- next_handshake_state_ = STATE_VERIFY_CERT;
- if (status == errSSLServerAuthCompletedFlag) {
- // Override errSSLServerAuthCompletedFlag as it's not actually an error,
+ switch (status) {
+ case errSSLWouldBlock:
+ next_handshake_state_ = STATE_HANDSHAKE_START;
+ break;
+
+ case noErr:
+ // TODO(hawk): we verify the certificate chain even on resumed sessions
+ // so that we have the certificate status (valid, expired but overridden
+ // by the user, EV, etc.) available. Eliminate this step once we have
+ // a certificate validation result cache. (Also applies to the
+ // errSSLServerAuthCompleted case below.)
+ SSL_LOG << "Handshake completed (DoHandshakeStart), next verify cert";
+ next_handshake_state_ = STATE_VERIFY_CERT;
+ HandshakeFinished();
+ break;
+
+ case errSSLServerAuthCompleted:
+ // Override errSSLServerAuthCompleted as it's not actually an error,
// but rather an indication that we're only half way through the
// handshake.
+ SSL_LOG << "Server auth completed (DoHandshakeStart)";
+ next_handshake_state_ = STATE_VERIFY_CERT;
handshake_interrupted_ = true;
status = noErr;
- }
- }
+ break;
- if (status == errSSLClosedGraceful) {
- // The server unexpectedly closed on us.
- return ERR_SSL_PROTOCOL_ERROR;
+ case errSSLClientCertRequested:
+ SSL_LOG << "Received client cert request in DoHandshakeStart";
+ // If we get this instead of errSSLServerAuthCompleted, the latter is
+ // implicit, and we should begin verification as well.
+ next_handshake_state_ = STATE_VERIFY_CERT;
+ handshake_interrupted_ = true;
+ status = noErr;
+ // We don't want to send a client cert now, because we haven't
+ // verified the server's cert yet. Remember it for later.
+ client_cert_requested_ = true;
+ break;
+
+ case errSSLClosedGraceful:
+ // The server unexpectedly closed on us.
+ return ERR_SSL_PROTOCOL_ERROR;
}
int net_error = NetErrorFromOSStatus(status);
@@ -912,6 +1020,7 @@
if (!server_cert_)
return ERR_UNEXPECTED;
+ SSL_LOG << "DoVerifyCert...";
int flags = 0;
if (ssl_config_.rev_checking_enabled)
flags |= X509Certificate::VERIFY_REV_CHECKING_ENABLED;
@@ -927,9 +1036,20 @@
DCHECK(verifier_.get());
verifier_.reset();
+ SSL_LOG << "...DoVerifyCertComplete (result=" << result << ")";
if (IsCertificateError(result) && ssl_config_.IsAllowedBadCert(server_cert_))
result = OK;
+ if (result == OK && client_cert_requested_) {
+ if (!ssl_config_.send_client_cert) {
+ // Caller hasn't specified a client cert, so let it know the server's
+ // asking for one, and abort the connection.
+ return ERR_SSL_CLIENT_AUTH_CERT_NEEDED;
+ }
+ // (We already called SetClientCert during InitializeSSLContext;
+ // no need to do so again.)
+ }
+
if (handshake_interrupted_) {
// With session resumption enabled the full handshake (i.e., the handshake
// in a non-resumed session) occurs in two steps. Continue on to the second
@@ -939,6 +1059,7 @@
} else {
// If the session was resumed or session resumption was disabled, we're
// done with the handshake.
+ SSL_LOG << "Handshake finished! (DoVerifyCertComplete)";
completed_handshake_ = true;
DCHECK(next_handshake_state_ == STATE_NONE);
}
@@ -946,23 +1067,79 @@
return result;
}
+int SSLClientSocketMac::SetClientCert() {
+ if (!ssl_config_.send_client_cert || !ssl_config_.client_cert)
+ return noErr;
+
+ scoped_cftyperef<CFArrayRef> cert_refs(
+ ssl_config_.client_cert->CreateClientCertificateChain());
+ SSL_LOG << "SSLSetCertificate(" << CFArrayGetCount(cert_refs) << " certs)";
+ OSStatus result = SSLSetCertificate(ssl_context_, cert_refs);
+ if (result)
+ LOG(ERROR) << "SSLSetCertificate returned OSStatus " << result;
+ return result;
+}
+
int SSLClientSocketMac::DoHandshakeFinish() {
OSStatus status = SSLHandshake(ssl_context_);
- if (status == errSSLWouldBlock)
- next_handshake_state_ = STATE_HANDSHAKE_FINISH;
-
- if (status == errSSLClosedGraceful)
- return ERR_SSL_PROTOCOL_ERROR;
-
- if (status == noErr) {
- completed_handshake_ = true;
- DCHECK(next_handshake_state_ == STATE_NONE);
+ switch (status) {
+ case errSSLWouldBlock:
+ next_handshake_state_ = STATE_HANDSHAKE_FINISH;
+ break;
+ case errSSLClientCertRequested:
+ SSL_LOG << "Server requested client cert (DoHandshakeFinish)";
+ if (!ssl_config_.send_client_cert)
+ return ERR_SSL_CLIENT_AUTH_CERT_NEEDED;
+ // (We already called SetClientCert during InitializeSSLContext.)
+ status = noErr;
+ next_handshake_state_ = STATE_HANDSHAKE_FINISH;
+ break;
+ case errSSLClosedGraceful:
+ return ERR_SSL_PROTOCOL_ERROR;
+ case errSSLClosedAbort:
+ case errSSLPeerHandshakeFail: {
+ // See if the server aborted due to client cert checking.
+ SSLClientCertificateState client_state;
+ if (SSLGetClientCertificateState(ssl_context_, &client_state) == noErr &&
+ client_state > kSSLClientCertNone) {
+ if (client_state == kSSLClientCertRequested &&
+ !ssl_config_.send_client_cert) {
+ SSL_LOG << "Server requested SSL cert during handshake";
+ return ERR_SSL_CLIENT_AUTH_CERT_NEEDED;
+ }
+ LOG(WARNING) << "Server aborted SSL handshake; client_state="
+ << client_state;
+ return ERR_BAD_SSL_CLIENT_AUTH_CERT;
+ }
+ break;
+ }
+ case noErr:
+ SSL_LOG << "Handshake finished! (DoHandshakeFinish)";
+ HandshakeFinished();
+ completed_handshake_ = true;
+ DCHECK(next_handshake_state_ == STATE_NONE);
+ break;
+ default:
+ break;
}
return NetErrorFromOSStatus(status);
}
+void SSLClientSocketMac::HandshakeFinished() {
+ // After the handshake's finished, disable breaking on server or client
+ // auth. Otherwise it might be triggered during a subsequent renegotiation,
+ // and SecureTransport doesn't handle that very well (there's usually no way
+ // to proceed without aborting the connection, at least not on 10.5.)
+ SSL_LOG << "HandshakeFinished()";
+ OSStatus status = EnableBreakOnAuth(false);
+ if (status != noErr)
+ SSL_LOG << "EnableBreakOnAuth failed: " << status;
+ // Note- this will actually always return an error, up through OS 10.6.3,
+ // because the option can't be changed after the context opens.
+}
+
int SSLClientSocketMac::DoPayloadRead() {
size_t processed = 0;
OSStatus status = SSLRead(ssl_context_,
@@ -979,17 +1156,39 @@
if (processed > 0)
return processed;
- if (status == errSSLClosedNoNotify) {
- // TODO(wtc): Unless we have received the close_notify alert, we need to
- // return an error code indicating that the SSL connection ended
- // uncleanly, a potential truncation attack. See http://crbug.com/18586.
- return OK;
- }
+ switch (status) {
+ case errSSLClosedNoNotify:
+ // TODO(wtc): Unless we have received the close_notify alert, we need to
+ // return an error code indicating that the SSL connection ended
+ // uncleanly, a potential truncation attack. See http://crbug.com/18586.
+ return OK;
- return NetErrorFromOSStatus(status);
+ case errSSLServerAuthCompleted:
+ case errSSLClientCertRequested:
+ // Server wants to renegotiate, probably to ask for a client cert,
+ // but SecureTransport doesn't support renegotiation so we have to close.
+ if (ssl_config_.send_client_cert) {
+ // We already gave SecureTransport a client cert. At this point there's
+ // nothing we can do; the renegotiation will fail regardless, due to
+ // bugs in Apple's SecureTransport library.
+ SSL_LOG << "Server renegotiating (status=" << status
+ << "), but I've already set a client cert. Fatal error.";
+ return ERR_SSL_PROTOCOL_ERROR;
+ }
+ // Tell my caller the server wants a client cert so it can reconnect.
+ SSL_LOG << "Server renegotiating; assuming it wants a client cert...";
+ return ERR_SSL_CLIENT_AUTH_CERT_NEEDED;
+
+ default:
+ return NetErrorFromOSStatus(status);
+ }
}
int SSLClientSocketMac::DoPayloadWrite() {
+ // Too much data in flight?
+ if (send_buffer_.size() > kWriteSizePauseLimit)
+ return ERR_IO_PENDING;
+
size_t processed = 0;
OSStatus status = SSLWrite(ssl_context_,
user_write_buf_->data(),
@@ -1024,9 +1223,9 @@
int rv = 1; // any old value to spin the loop below
while (rv > 0 && total_read < *data_length) {
us->read_io_buf_ = new IOBuffer(*data_length - total_read);
- rv = us->transport_->Read(us->read_io_buf_,
- *data_length - total_read,
- &us->transport_read_callback_);
+ rv = us->transport_->socket()->Read(us->read_io_buf_,
+ *data_length - total_read,
+ &us->transport_read_callback_);
if (rv >= 0) {
us->recv_buffer_.insert(us->recv_buffer_.end(),
@@ -1086,9 +1285,9 @@
us->write_io_buf_ = new IOBuffer(us->send_buffer_.size());
memcpy(us->write_io_buf_->data(), &us->send_buffer_[0],
us->send_buffer_.size());
- rv = us->transport_->Write(us->write_io_buf_,
- us->send_buffer_.size(),
- &us->transport_write_callback_);
+ rv = us->transport_->socket()->Write(us->write_io_buf_,
+ us->send_buffer_.size(),
+ &us->transport_write_callback_);
if (rv > 0) {
us->send_buffer_.erase(us->send_buffer_.begin(),
us->send_buffer_.begin() + rv);
diff --git a/net/socket/ssl_client_socket_mac.h b/net/socket/ssl_client_socket_mac.h
index 3176116..dc2ed65 100644
--- a/net/socket/ssl_client_socket_mac.h
+++ b/net/socket/ssl_client_socket_mac.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -13,13 +13,14 @@
#include "base/scoped_ptr.h"
#include "net/base/cert_verify_result.h"
#include "net/base/completion_callback.h"
+#include "net/base/net_log.h"
#include "net/base/ssl_config_service.h"
#include "net/socket/ssl_client_socket.h"
namespace net {
class CertVerifier;
-class LoadLog;
+class ClientSocketHandle;
// An SSL client socket implemented with Secure Transport.
class SSLClientSocketMac : public SSLClientSocket {
@@ -28,7 +29,7 @@
// The given hostname will be compared with the name(s) in the server's
// certificate during the SSL handshake. ssl_config specifies the SSL
// settings.
- SSLClientSocketMac(ClientSocket* transport_socket,
+ SSLClientSocketMac(ClientSocketHandle* transport_socket,
const std::string& hostname,
const SSLConfig& ssl_config);
~SSLClientSocketMac();
@@ -39,11 +40,12 @@
virtual NextProtoStatus GetNextProto(std::string* proto);
// ClientSocket methods:
- virtual int Connect(CompletionCallback* callback, LoadLog* load_log);
+ virtual int Connect(CompletionCallback* callback);
virtual void Disconnect();
virtual bool IsConnected() const;
virtual bool IsConnectedAndIdle() const;
- virtual int GetPeerName(struct sockaddr* name, socklen_t* namelen);
+ virtual int GetPeerAddress(AddressList* address) const;
+ virtual const BoundNetLog& NetLog() const { return net_log_; }
// Socket methods:
virtual int Read(IOBuffer* buf, int buf_len, CompletionCallback* callback);
@@ -55,6 +57,8 @@
// Initializes the SSLContext. Returns a net error code.
int InitializeSSLContext();
+ OSStatus EnableBreakOnAuth(bool enabled);
+
void DoConnectCallback(int result);
void DoReadCallback(int result);
void DoWriteCallback(int result);
@@ -70,6 +74,9 @@
int DoVerifyCert();
int DoVerifyCertComplete(int result);
int DoHandshakeFinish();
+ void HandshakeFinished();
+
+ int SetClientCert();
static OSStatus SSLReadCallback(SSLConnectionRef connection,
void* data,
@@ -82,7 +89,7 @@
CompletionCallbackImpl<SSLClientSocketMac> transport_read_callback_;
CompletionCallbackImpl<SSLClientSocketMac> transport_write_callback_;
- scoped_ptr<ClientSocket> transport_;
+ scoped_ptr<ClientSocketHandle> transport_;
std::string hostname_;
SSLConfig ssl_config_;
@@ -113,6 +120,7 @@
bool completed_handshake_;
bool handshake_interrupted_;
+ bool client_cert_requested_;
SSLContextRef ssl_context_;
// These buffers hold data retrieved from/sent to the underlying transport
@@ -125,7 +133,7 @@
scoped_refptr<IOBuffer> read_io_buf_;
scoped_refptr<IOBuffer> write_io_buf_;
- scoped_refptr<LoadLog> load_log_;
+ BoundNetLog net_log_;
};
} // namespace net
diff --git a/net/socket/ssl_client_socket_mac_factory.cc b/net/socket/ssl_client_socket_mac_factory.cc
new file mode 100644
index 0000000..ec41345
--- /dev/null
+++ b/net/socket/ssl_client_socket_mac_factory.cc
@@ -0,0 +1,18 @@
+// 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 "net/socket/client_socket_factory.h"
+
+#include "net/socket/ssl_client_socket_mac.h"
+
+namespace net {
+
+SSLClientSocket* SSLClientSocketMacFactory(
+ ClientSocketHandle* transport_socket,
+ const std::string& hostname,
+ const SSLConfig& ssl_config) {
+ return new SSLClientSocketMac(transport_socket, hostname, ssl_config);
+}
+
+} // namespace net
diff --git a/net/socket/ssl_client_socket_mac_factory.h b/net/socket/ssl_client_socket_mac_factory.h
new file mode 100644
index 0000000..dafc40f
--- /dev/null
+++ b/net/socket/ssl_client_socket_mac_factory.h
@@ -0,0 +1,20 @@
+// 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.
+
+#ifndef NET_SOCKET_SSL_CLIENT_SOCKET_MAC_FACTORY_H_
+#define NET_SOCKET_SSL_CLIENT_SOCKET_MAC_FACTORY_H_
+
+#include "net/socket/client_socket_factory.h"
+
+namespace net {
+
+// Creates SSLClientSocketMac objects.
+SSLClientSocket* SSLClientSocketMacFactory(
+ ClientSocketHandle* transport_socket,
+ const std::string& hostname,
+ const SSLConfig& ssl_config);
+
+} // namespace net
+
+#endif // NET_SOCKET_SSL_CLIENT_SOCKET_MAC_FACTORY_H_
diff --git a/net/socket/ssl_client_socket_nss.cc b/net/socket/ssl_client_socket_nss.cc
index 10690ee..0a81a48 100644
--- a/net/socket/ssl_client_socket_nss.cc
+++ b/net/socket/ssl_client_socket_nss.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -60,17 +60,22 @@
#include <pk11pub.h>
#include "base/compiler_specific.h"
+#include "base/histogram.h"
#include "base/logging.h"
#include "base/nss_util.h"
#include "base/singleton.h"
#include "base/string_util.h"
+#include "net/base/address_list.h"
#include "net/base/cert_verifier.h"
#include "net/base/io_buffer.h"
-#include "net/base/load_log.h"
+#include "net/base/net_log.h"
#include "net/base/net_errors.h"
#include "net/base/ssl_cert_request_info.h"
+#include "net/base/ssl_connection_status_flags.h"
#include "net/base/ssl_info.h"
+#include "net/base/sys_addrinfo.h"
#include "net/ocsp/nss_ocsp.h"
+#include "net/socket/client_socket_handle.h"
static const int kRecvBufferSize = 4096;
@@ -111,6 +116,9 @@
// Use late binding to avoid scary but benign warning
// "Symbol `SSL_ImplementedCiphers' has different size in shared object,
// consider re-linking"
+ // TODO(wtc): Use the new SSL_GetImplementedCiphers and
+ // SSL_GetNumImplementedCiphers functions when we require NSS 3.12.6.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=496993.
const PRUint16* pSSL_ImplementedCiphers = static_cast<const PRUint16*>(
dlsym(RTLD_DEFAULT, "SSL_ImplementedCiphers"));
if (pSSL_ImplementedCiphers == NULL) {
@@ -175,11 +183,21 @@
case PR_ADDRESS_NOT_AVAILABLE_ERROR:
return ERR_ADDRESS_INVALID;
+ case SSL_ERROR_SSL_DISABLED:
+ return ERR_NO_SSL_VERSIONS_ENABLED;
case SSL_ERROR_NO_CYPHER_OVERLAP:
case SSL_ERROR_UNSUPPORTED_VERSION:
return ERR_SSL_VERSION_OR_CIPHER_MISMATCH;
case SSL_ERROR_HANDSHAKE_FAILURE_ALERT:
+ case SSL_ERROR_HANDSHAKE_UNEXPECTED_ALERT:
+ case SSL_ERROR_ILLEGAL_PARAMETER_ALERT:
return ERR_SSL_PROTOCOL_ERROR;
+ case SSL_ERROR_DECOMPRESSION_FAILURE_ALERT:
+ return ERR_SSL_DECOMPRESSION_FAILURE_ALERT;
+ case SSL_ERROR_BAD_MAC_ALERT:
+ return ERR_SSL_BAD_RECORD_MAC_ALERT;
+ case SSL_ERROR_UNSAFE_NEGOTIATION:
+ return ERR_SSL_UNSAFE_NEGOTIATION;
default: {
if (IS_SSL_ERROR(err)) {
@@ -211,6 +229,50 @@
}
}
+#if defined(OS_WIN)
+
+// A certificate for COMODO EV SGC CA, issued by AddTrust External CA Root,
+// causes CertGetCertificateChain to report CERT_TRUST_IS_NOT_VALID_FOR_USAGE.
+// It seems to be caused by the szOID_APPLICATION_CERT_POLICIES extension in
+// that certificate.
+//
+// This function is used in the workaround for http://crbug.com/43538
+bool IsProblematicComodoEVCACert(const CERTCertificate& cert) {
+ // Issuer:
+ // CN = AddTrust External CA Root
+ // OU = AddTrust External TTP Network
+ // O = AddTrust AB
+ // C = SE
+ static const uint8 kIssuer[] = {
+ 0x30, 0x6f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04,
+ 0x06, 0x13, 0x02, 0x53, 0x45, 0x31, 0x14, 0x30, 0x12, 0x06,
+ 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b, 0x41, 0x64, 0x64, 0x54,
+ 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31, 0x26, 0x30,
+ 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+ 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74,
+ 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20,
+ 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30,
+ 0x20, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x19, 0x41, 0x64,
+ 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74,
+ 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+ 0x6f, 0x6f, 0x74
+ };
+
+ // Serial number: 79:0A:83:4D:48:40:6B:AB:6C:35:2A:D5:1F:42:83:FE.
+ static const uint8 kSerialNumber[] = {
+ 0x79, 0x0a, 0x83, 0x4d, 0x48, 0x40, 0x6b, 0xab, 0x6c, 0x35,
+ 0x2a, 0xd5, 0x1f, 0x42, 0x83, 0xfe
+ };
+
+ return cert.derIssuer.len == sizeof(kIssuer) &&
+ memcmp(cert.derIssuer.data, kIssuer, cert.derIssuer.len) == 0 &&
+ cert.serialNumber.len == sizeof(kSerialNumber) &&
+ memcmp(cert.serialNumber.data, kSerialNumber,
+ cert.serialNumber.len) == 0;
+}
+
+#endif
+
} // namespace
#if defined(OS_WIN)
@@ -218,7 +280,7 @@
HCERTSTORE SSLClientSocketNSS::cert_store_ = NULL;
#endif
-SSLClientSocketNSS::SSLClientSocketNSS(ClientSocket* transport_socket,
+SSLClientSocketNSS::SSLClientSocketNSS(ClientSocketHandle* transport_socket,
const std::string& hostname,
const SSLConfig& ssl_config)
: ALLOW_THIS_IN_INITIALIZER_LIST(buffer_send_callback_(
@@ -239,10 +301,12 @@
user_write_buf_len_(0),
server_cert_nss_(NULL),
client_auth_cert_needed_(false),
+ handshake_callback_called_(false),
completed_handshake_(false),
next_handshake_state_(STATE_NONE),
nss_fd_(NULL),
- nss_bufs_(NULL) {
+ nss_bufs_(NULL),
+ net_log_(transport_socket->socket()->NetLog()) {
EnterFunction("");
}
@@ -257,7 +321,9 @@
// Initialize the NSS SSL library in a threadsafe way. This also
// initializes the NSS base library.
EnsureNSSSSLInit();
-#if !defined(OS_WIN)
+ if (!NSS_IsInitialized())
+ return ERR_UNEXPECTED;
+#if !defined(OS_MACOSX) && !defined(OS_WIN)
// We must call EnsureOCSPInit() here, on the IO thread, to get the IO loop
// by MessageLoopForIO::current().
// X509Certificate::Verify() runs on a worker thread of CertVerifier.
@@ -268,8 +334,7 @@
return OK;
}
-int SSLClientSocketNSS::Connect(CompletionCallback* callback,
- LoadLog* load_log) {
+int SSLClientSocketNSS::Connect(CompletionCallback* callback) {
EnterFunction("");
DCHECK(transport_.get());
DCHECK(next_handshake_state_ == STATE_NONE);
@@ -279,15 +344,17 @@
DCHECK(!user_read_buf_);
DCHECK(!user_write_buf_);
- LoadLog::BeginEvent(load_log, LoadLog::TYPE_SSL_CONNECT);
+ net_log_.BeginEvent(NetLog::TYPE_SSL_CONNECT, NULL);
- if (Init() != OK) {
- NOTREACHED() << "Couldn't initialize nss";
+ int rv = Init();
+ if (rv != OK) {
+ net_log_.EndEvent(NetLog::TYPE_SSL_CONNECT, NULL);
+ return rv;
}
- int rv = InitializeSSLOptions();
+ rv = InitializeSSLOptions();
if (rv != OK) {
- LoadLog::EndEvent(load_log, LoadLog::TYPE_SSL_CONNECT);
+ net_log_.EndEvent(NetLog::TYPE_SSL_CONNECT, NULL);
return rv;
}
@@ -295,9 +362,8 @@
rv = DoHandshakeLoop(OK);
if (rv == ERR_IO_PENDING) {
user_connect_callback_ = callback;
- load_log_ = load_log;
} else {
- LoadLog::EndEvent(load_log, LoadLog::TYPE_SSL_CONNECT);
+ net_log_.EndEvent(NetLog::TYPE_SSL_CONNECT, NULL);
}
LeaveFunction("");
@@ -313,14 +379,25 @@
}
// Tell NSS who we're connected to
+ AddressList peer_address;
+ int err = transport_->socket()->GetPeerAddress(&peer_address);
+ if (err != OK)
+ return err;
+
+ const struct addrinfo* ai = peer_address.head();
+
PRNetAddr peername;
- socklen_t len = sizeof(PRNetAddr);
- int err = transport_->GetPeerName((struct sockaddr *)&peername, &len);
- if (err) {
- DLOG(ERROR) << "GetPeerName failed";
- // TODO(wtc): Change GetPeerName to return a network error code.
- return ERR_UNEXPECTED;
- }
+ memset(&peername, 0, sizeof(peername));
+ DCHECK_LE(ai->ai_addrlen, sizeof(peername));
+ size_t len = std::min(static_cast<size_t>(ai->ai_addrlen), sizeof(peername));
+ memcpy(&peername, ai->ai_addr, len);
+
+ // Adjust the address family field for BSD, whose sockaddr
+ // structure has a one-byte length and one-byte address family
+ // field at the beginning. PRNetAddr has a two-byte address
+ // family field at the beginning.
+ peername.raw.family = ai->ai_addr->sa_family;
+
memio_SetPeerName(nss_fd_, &peername);
// Grab pointer to buffers
@@ -381,20 +458,31 @@
LOG(INFO) << "SSL_ENABLE_DEFLATE failed. Old system nss?";
#endif
+#ifdef SSL_ENABLE_FALSE_START
+ rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_FALSE_START, PR_TRUE);
+ if (rv != SECSuccess)
+ LOG(INFO) << "SSL_ENABLE_FALSE_START failed. Old system nss?";
+#endif
+
#ifdef SSL_ENABLE_RENEGOTIATION
- // We allow servers to request renegotiation. Since we're a client,
- // prohibiting this is rather a waste of time. Only servers are in a position
- // to prevent renegotiation attacks.
- // http://extendedsubset.com/?p=8
- //
- // This should be changed when NSS 3.12.6 comes out with support for the
- // renegotiation info extension.
- // http://code.google.com/p/chromium/issues/detail?id=31647
- rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_RENEGOTIATION,
- SSL_RENEGOTIATE_UNRESTRICTED);
+ if (SSLConfigService::IsKnownStrictTLSServer(hostname_)) {
+ rv = SSL_OptionSet(nss_fd_, SSL_REQUIRE_SAFE_NEGOTIATION, PR_TRUE);
+ if (rv != SECSuccess)
+ LOG(INFO) << "SSL_REQUIRE_SAFE_NEGOTIATION failed.";
+ rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_RENEGOTIATION,
+ SSL_RENEGOTIATE_REQUIRES_XTN);
+ } else {
+ // We allow servers to request renegotiation. Since we're a client,
+ // prohibiting this is rather a waste of time. Only servers are in a
+ // position to prevent renegotiation attacks.
+ // http://extendedsubset.com/?p=8
+
+ rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_RENEGOTIATION,
+ SSL_RENEGOTIATE_UNRESTRICTED);
+ }
if (rv != SECSuccess)
LOG(INFO) << "SSL_ENABLE_RENEGOTIATION failed.";
-#endif
+#endif // SSL_ENABLE_RENEGOTIATION
#ifdef SSL_NEXT_PROTO_NEGOTIATED
if (!ssl_config_.next_protos.empty()) {
@@ -429,9 +517,10 @@
// Set the peer ID for session reuse. This is necessary when we create an
// SSL tunnel through a proxy -- GetPeerName returns the proxy's address
// rather than the destination server's address in that case.
- // TODO(wtc): port in peername is not the server's port when a proxy is used.
+ // TODO(wtc): port in |peer_address| is not the server's port when a proxy is
+ // used.
std::string peer_id = StringPrintf("%s:%d", hostname_.c_str(),
- PR_ntohs(PR_NetAddrInetPort(&peername)));
+ peer_address.GetPort());
rv = SSL_SetSockPeerID(nss_fd_, const_cast<char*>(peer_id.c_str()));
if (rv != SECSuccess)
LOG(INFO) << "SSL_SetSockPeerID failed: peer_id=" << peer_id;
@@ -462,7 +551,7 @@
// Shut down anything that may call us back (through buffer_send_callback_,
// buffer_recv_callback, or handshake_io_callback_).
verifier_.reset();
- transport_->Disconnect();
+ transport_->socket()->Disconnect();
// Reset object state
transport_send_busy_ = false;
@@ -496,7 +585,7 @@
// closed by the server when we send a request anyway, a false positive in
// exchange for simpler code is a good trade-off.
EnterFunction("");
- bool ret = completed_handshake_ && transport_->IsConnected();
+ bool ret = completed_handshake_ && transport_->socket()->IsConnected();
LeaveFunction("");
return ret;
}
@@ -507,16 +596,17 @@
// Strictly speaking, we should check if we have received the close_notify
// alert message from the server, and return false in that case. Although
// the close_notify alert message means EOF in the SSL layer, it is just
- // bytes to the transport layer below, so transport_->IsConnectedAndIdle()
- // returns the desired false when we receive close_notify.
+ // bytes to the transport layer below, so
+ // transport_->socket()->IsConnectedAndIdle() returns the desired false
+ // when we receive close_notify.
EnterFunction("");
- bool ret = completed_handshake_ && transport_->IsConnectedAndIdle();
+ bool ret = completed_handshake_ && transport_->socket()->IsConnectedAndIdle();
LeaveFunction("");
return ret;
}
-int SSLClientSocketNSS::GetPeerName(struct sockaddr* name, socklen_t* namelen) {
- return transport_->GetPeerName(name, namelen);
+int SSLClientSocketNSS::GetPeerAddress(AddressList* address) const {
+ return transport_->socket()->GetPeerAddress(address);
}
int SSLClientSocketNSS::Read(IOBuffer* buf, int buf_len,
@@ -534,9 +624,9 @@
int rv = DoReadLoop(OK);
- if (rv == ERR_IO_PENDING)
+ if (rv == ERR_IO_PENDING) {
user_read_callback_ = callback;
- else {
+ } else {
user_read_buf_ = NULL;
user_read_buf_len_ = 0;
}
@@ -559,9 +649,9 @@
int rv = DoWriteLoop(OK);
- if (rv == ERR_IO_PENDING)
+ if (rv == ERR_IO_PENDING) {
user_write_callback_ = callback;
- else {
+ } else {
user_write_buf_ = NULL;
user_write_buf_len_ = 0;
}
@@ -570,13 +660,36 @@
}
bool SSLClientSocketNSS::SetReceiveBufferSize(int32 size) {
- return transport_->SetReceiveBufferSize(size);
+ return transport_->socket()->SetReceiveBufferSize(size);
}
bool SSLClientSocketNSS::SetSendBufferSize(int32 size) {
- return transport_->SetSendBufferSize(size);
+ return transport_->socket()->SetSendBufferSize(size);
}
+#if defined(OS_WIN)
+// static
+X509Certificate::OSCertHandle SSLClientSocketNSS::CreateOSCert(
+ const SECItem& der_cert) {
+ // TODO(wtc): close cert_store_ at shutdown.
+ if (!cert_store_)
+ cert_store_ = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, NULL, 0, NULL);
+
+ X509Certificate::OSCertHandle cert_handle = NULL;
+ BOOL ok = CertAddEncodedCertificateToStore(
+ cert_store_, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
+ der_cert.data, der_cert.len, CERT_STORE_ADD_USE_EXISTING, &cert_handle);
+ return ok ? cert_handle : NULL;
+}
+#elif defined(OS_MACOSX)
+// static
+X509Certificate::OSCertHandle SSLClientSocketNSS::CreateOSCert(
+ const SECItem& der_cert) {
+ return X509Certificate::CreateOSCertHandleFromBytes(
+ reinterpret_cast<char*>(der_cert.data), der_cert.len);
+}
+#endif
+
X509Certificate *SSLClientSocketNSS::UpdateServerCert() {
// We set the server_cert_ from OwnAuthCertHandler(), but this handler
// does not necessarily get called if we are continuing a cached SSL
@@ -584,59 +697,74 @@
if (server_cert_ == NULL) {
server_cert_nss_ = SSL_PeerCertificate(nss_fd_);
if (server_cert_nss_) {
-#if defined(OS_WIN)
- // TODO(wtc): close cert_store_ at shutdown.
- if (!cert_store_)
- cert_store_ = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, NULL, 0, NULL);
-
- PCCERT_CONTEXT cert_context = NULL;
- BOOL ok = CertAddEncodedCertificateToStore(
- cert_store_,
- X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
- server_cert_nss_->derCert.data,
- server_cert_nss_->derCert.len,
- CERT_STORE_ADD_USE_EXISTING,
- &cert_context);
- DCHECK(ok);
- server_cert_ = X509Certificate::CreateFromHandle(
- cert_context, X509Certificate::SOURCE_FROM_NETWORK);
-
- // Add each of the intermediate certificates in the server's chain to
- // the server's X509Certificate object. This makes them available to
- // X509Certificate::Verify() for chain building.
- // TODO(wtc): Since X509Certificate::CreateFromHandle may return a
- // cached X509Certificate object, we may be adding intermediate CA
- // certificates to it repeatedly!
+#if defined(OS_MACOSX) || defined(OS_WIN)
+ // Get each of the intermediate certificates in the server's chain.
+ // These will be added to the server's X509Certificate object, making
+ // them available to X509Certificate::Verify() for chain building.
+ X509Certificate::OSCertHandles intermediate_ca_certs;
+ X509Certificate::OSCertHandle cert_handle = NULL;
CERTCertList* cert_list = CERT_GetCertChainFromCert(
server_cert_nss_, PR_Now(), certUsageSSLCA);
if (cert_list) {
for (CERTCertListNode* node = CERT_LIST_HEAD(cert_list);
!CERT_LIST_END(node, cert_list);
node = CERT_LIST_NEXT(node)) {
- cert_context = NULL;
- ok = CertAddEncodedCertificateToStore(
- cert_store_,
- X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
- node->cert->derCert.data,
- node->cert->derCert.len,
- CERT_STORE_ADD_USE_EXISTING,
- &cert_context);
- DCHECK(ok);
- if (node->cert != server_cert_nss_)
- server_cert_->AddIntermediateCertificate(cert_context);
+ if (node->cert == server_cert_nss_)
+ continue;
+#if defined(OS_WIN)
+ // Work around http://crbug.com/43538 by not importing the
+ // problematic COMODO EV SGC CA certificate. CryptoAPI will
+ // download a good certificate for that CA, issued by COMODO
+ // Certification Authority, using the AIA extension in the server
+ // certificate.
+ if (IsProblematicComodoEVCACert(*node->cert))
+ continue;
+#endif
+ cert_handle = CreateOSCert(node->cert->derCert);
+ DCHECK(cert_handle);
+ intermediate_ca_certs.push_back(cert_handle);
}
CERT_DestroyCertList(cert_list);
}
+
+ // Finally create the X509Certificate object.
+ cert_handle = CreateOSCert(server_cert_nss_->derCert);
+ DCHECK(cert_handle);
+ server_cert_ = X509Certificate::CreateFromHandle(
+ cert_handle,
+ X509Certificate::SOURCE_FROM_NETWORK,
+ intermediate_ca_certs);
+ X509Certificate::FreeOSCertHandle(cert_handle);
+ for (size_t i = 0; i < intermediate_ca_certs.size(); ++i)
+ X509Certificate::FreeOSCertHandle(intermediate_ca_certs[i]);
#else
server_cert_ = X509Certificate::CreateFromHandle(
- CERT_DupCertificate(server_cert_nss_),
- X509Certificate::SOURCE_FROM_NETWORK);
+ server_cert_nss_,
+ X509Certificate::SOURCE_FROM_NETWORK,
+ X509Certificate::OSCertHandles());
#endif
}
}
return server_cert_;
}
+// Log an informational message if the server does not support secure
+// renegotiation (RFC 5746).
+void SSLClientSocketNSS::CheckSecureRenegotiation() const {
+ // SSL_HandshakeNegotiatedExtension was added in NSS 3.12.6.
+ // Since SSL_MAX_EXTENSIONS was added at the same time, we can test
+ // SSL_MAX_EXTENSIONS for the presence of SSL_HandshakeNegotiatedExtension.
+#if defined(SSL_MAX_EXTENSIONS)
+ PRBool received_renego_info;
+ if (SSL_HandshakeNegotiatedExtension(nss_fd_, ssl_renegotiation_info_xtn,
+ &received_renego_info) == SECSuccess &&
+ !received_renego_info) {
+ LOG(INFO) << "The server " << hostname_
+ << " does not support the TLS renegotiation_info extension.";
+ }
+#endif
+}
+
void SSLClientSocketNSS::GetSSLInfo(SSLInfo* ssl_info) {
EnterFunction("");
ssl_info->Reset();
@@ -659,19 +787,41 @@
LOG(DFATAL) << "SSL_GetCipherSuiteInfo returned " << PR_GetError()
<< " for cipherSuite " << channel_info.cipherSuite;
}
+ ssl_info->connection_status |=
+ (((int)channel_info.cipherSuite) & SSL_CONNECTION_CIPHERSUITE_MASK) <<
+ SSL_CONNECTION_CIPHERSUITE_SHIFT;
+
+ ssl_info->connection_status |=
+ (((int)channel_info.compressionMethod) &
+ SSL_CONNECTION_COMPRESSION_MASK) <<
+ SSL_CONNECTION_COMPRESSION_SHIFT;
+
UpdateServerCert();
}
ssl_info->cert_status = server_cert_verify_result_.cert_status;
DCHECK(server_cert_ != NULL);
ssl_info->cert = server_cert_;
+ PRBool peer_supports_renego_ext;
+ ok = SSL_HandshakeNegotiatedExtension(nss_fd_, ssl_renegotiation_info_xtn,
+ &peer_supports_renego_ext);
+ if (ok == SECSuccess) {
+ if (!peer_supports_renego_ext)
+ ssl_info->connection_status |= SSL_CONNECTION_NO_RENEGOTIATION_EXTENSION;
+ UMA_HISTOGRAM_ENUMERATION("Net.RenegotiationExtensionSupported",
+ (int)peer_supports_renego_ext, 2);
+ }
+
+ if (ssl_config_.ssl3_fallback)
+ ssl_info->connection_status |= SSL_CONNECTION_SSL3_FALLBACK;
+
LeaveFunction("");
}
void SSLClientSocketNSS::GetSSLCertRequestInfo(
SSLCertRequestInfo* cert_request_info) {
EnterFunction("");
- cert_request_info->host_and_port = hostname_;
+ cert_request_info->host_and_port = hostname_; // TODO(wtc): no port!
cert_request_info->client_certs = client_certs_;
LeaveFunction(cert_request_info->client_certs.size());
}
@@ -764,8 +914,7 @@
EnterFunction(result);
int rv = DoHandshakeLoop(result);
if (rv != ERR_IO_PENDING) {
- LoadLog::EndEvent(load_log_, net::LoadLog::TYPE_SSL_CONNECT);
- load_log_ = NULL;
+ net_log_.EndEvent(net::NetLog::TYPE_SSL_CONNECT, NULL);
DoConnectCallback(rv);
}
LeaveFunction("");
@@ -773,7 +922,7 @@
void SSLClientSocketNSS::OnSendComplete(int result) {
EnterFunction(result);
- if (next_handshake_state_ != STATE_NONE) {
+ if (next_handshake_state_ == STATE_HANDSHAKE) {
// In handshake phase.
OnHandshakeIOComplete(result);
LeaveFunction("");
@@ -805,7 +954,7 @@
void SSLClientSocketNSS::OnRecvComplete(int result) {
EnterFunction(result);
- if (next_handshake_state_ != STATE_NONE) {
+ if (next_handshake_state_ == STATE_HANDSHAKE) {
// In handshake phase.
OnHandshakeIOComplete(result);
LeaveFunction("");
@@ -853,6 +1002,8 @@
return PR_HOST_UNREACHABLE_ERROR; // Also PR_NETWORK_UNREACHABLE_ERROR.
case ERR_ADDRESS_INVALID:
return PR_ADDRESS_NOT_AVAILABLE_ERROR;
+ case ERR_NAME_NOT_RESOLVED:
+ return PR_DIRECTORY_LOOKUP_ERROR;
default:
LOG(WARNING) << "MapErrorToNSS " << result
<< " mapped to PR_UNKNOWN_ERROR";
@@ -892,7 +1043,8 @@
scoped_refptr<IOBuffer> send_buffer = new IOBuffer(nb);
memcpy(send_buffer->data(), buf, nb);
- int rv = transport_->Write(send_buffer, nb, &buffer_send_callback_);
+ int rv = transport_->socket()->Write(send_buffer, nb,
+ &buffer_send_callback_);
if (rv == ERR_IO_PENDING) {
transport_send_busy_ = true;
break;
@@ -932,7 +1084,7 @@
rv = ERR_IO_PENDING;
} else {
recv_buffer_ = new IOBuffer(nb);
- rv = transport_->Read(recv_buffer_, nb, &buffer_recv_callback_);
+ rv = transport_->socket()->Read(recv_buffer_, nb, &buffer_recv_callback_);
if (rv == ERR_IO_PENDING) {
transport_recv_busy_ = true;
} else {
@@ -1067,15 +1219,122 @@
CERTDistNames* ca_names,
CERTCertificate** result_certificate,
SECKEYPrivateKey** result_private_key) {
-#if defined(OS_WIN)
- // Not implemented. Send no client certificate.
- PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
- return SECFailure;
-#else
SSLClientSocketNSS* that = reinterpret_cast<SSLClientSocketNSS*>(arg);
that->client_auth_cert_needed_ = !that->ssl_config_.send_client_cert;
+#if defined(OS_WIN)
+ if (that->ssl_config_.send_client_cert) {
+ // TODO(wtc): SSLClientSocketNSS can't do SSL client authentication using
+ // CryptoAPI yet (http://crbug.com/37560), so client_cert must be NULL.
+ DCHECK(!that->ssl_config_.client_cert);
+ // Send no client certificate.
+ return SECFailure;
+ }
+
+ that->client_certs_.clear();
+
+ std::vector<CERT_NAME_BLOB> issuer_list(ca_names->nnames);
+ for (int i = 0; i < ca_names->nnames; ++i) {
+ issuer_list[i].cbData = ca_names->names[i].len;
+ issuer_list[i].pbData = ca_names->names[i].data;
+ }
+
+ // Client certificates of the user are in the "MY" system certificate store.
+ HCERTSTORE my_cert_store = CertOpenSystemStore(NULL, L"MY");
+ if (!my_cert_store) {
+ LOG(ERROR) << "Could not open the \"MY\" system certificate store: "
+ << GetLastError();
+ return SECFailure;
+ }
+
+ // Enumerate the client certificates.
+ CERT_CHAIN_FIND_BY_ISSUER_PARA find_by_issuer_para;
+ memset(&find_by_issuer_para, 0, sizeof(find_by_issuer_para));
+ find_by_issuer_para.cbSize = sizeof(find_by_issuer_para);
+ find_by_issuer_para.pszUsageIdentifier = szOID_PKIX_KP_CLIENT_AUTH;
+ find_by_issuer_para.cIssuer = ca_names->nnames;
+ find_by_issuer_para.rgIssuer = ca_names->nnames ? &issuer_list[0] : NULL;
+
+ PCCERT_CHAIN_CONTEXT chain_context = NULL;
+
+ // TODO(wtc): close cert_store_ at shutdown.
+ if (!cert_store_)
+ cert_store_ = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, NULL, 0, NULL);
+
+ for (;;) {
+ // Find a certificate chain.
+ chain_context = CertFindChainInStore(my_cert_store,
+ X509_ASN_ENCODING,
+ 0,
+ CERT_CHAIN_FIND_BY_ISSUER,
+ &find_by_issuer_para,
+ chain_context);
+ if (!chain_context) {
+ DWORD err = GetLastError();
+ if (err != CRYPT_E_NOT_FOUND)
+ DLOG(ERROR) << "CertFindChainInStore failed: " << err;
+ break;
+ }
+
+ // Get the leaf certificate.
+ PCCERT_CONTEXT cert_context =
+ chain_context->rgpChain[0]->rgpElement[0]->pCertContext;
+ // Copy it to our own certificate store, so that we can close the "MY"
+ // certificate store before returning from this function.
+ PCCERT_CONTEXT cert_context2;
+ BOOL ok = CertAddCertificateContextToStore(cert_store_, cert_context,
+ CERT_STORE_ADD_USE_EXISTING,
+ &cert_context2);
+ if (!ok) {
+ NOTREACHED();
+ continue;
+ }
+ scoped_refptr<X509Certificate> cert = X509Certificate::CreateFromHandle(
+ cert_context2, X509Certificate::SOURCE_LONE_CERT_IMPORT,
+ X509Certificate::OSCertHandles());
+ X509Certificate::FreeOSCertHandle(cert_context2);
+ that->client_certs_.push_back(cert);
+ }
+
+ BOOL ok = CertCloseStore(my_cert_store, CERT_CLOSE_STORE_CHECK_FLAG);
+ DCHECK(ok);
+
+ // Tell NSS to suspend the client authentication. We will then abort the
+ // handshake by returning ERR_SSL_CLIENT_AUTH_CERT_NEEDED.
+ return SECWouldBlock;
+#elif defined(OS_MACOSX)
+ if (that->ssl_config_.send_client_cert) {
+ // TODO(wtc): SSLClientSocketNSS can't do SSL client authentication using
+ // CDSA/CSSM yet (http://crbug.com/45369), so client_cert must be NULL.
+ DCHECK(!that->ssl_config_.client_cert);
+ // Send no client certificate.
+ return SECFailure;
+ }
+
+ that->client_certs_.clear();
+
+ // First, get the cert issuer names allowed by the server.
+ std::vector<CertPrincipal> valid_issuers;
+ int n = ca_names->nnames;
+ for (int i = 0; i < n; i++) {
+ // Parse each name into a CertPrincipal object.
+ CertPrincipal p;
+ if (p.ParseDistinguishedName(ca_names->names[i].data,
+ ca_names->names[i].len)) {
+ valid_issuers.push_back(p);
+ }
+ }
+
+ // Now get the available client certs whose issuers are allowed by the server.
+ X509Certificate::GetSSLClientCertificates(that->hostname_,
+ valid_issuers,
+ &that->client_certs_);
+
+ // Tell NSS to suspend the client authentication. We will then abort the
+ // handshake by returning ERR_SSL_CLIENT_AUTH_CERT_NEEDED.
+ return SECWouldBlock;
+#else
CERTCertificate* cert = NULL;
SECKEYPrivateKey* privkey = NULL;
void* wincx = SSL_RevealPinArg(socket);
@@ -1111,13 +1370,15 @@
continue;
// Only check unexpired certs.
if (CERT_CheckCertValidTimes(cert, PR_Now(), PR_TRUE) ==
- secCertTimeValid &&
- NSS_CmpCertChainWCANames(cert, ca_names) == SECSuccess) {
+ secCertTimeValid && (!ca_names->nnames ||
+ NSS_CmpCertChainWCANames(cert, ca_names) == SECSuccess)) {
privkey = PK11_FindKeyByAnyCert(cert, wincx);
if (privkey) {
X509Certificate* x509_cert = X509Certificate::CreateFromHandle(
- cert, X509Certificate::SOURCE_LONE_CERT_IMPORT);
+ cert, X509Certificate::SOURCE_LONE_CERT_IMPORT,
+ net::X509Certificate::OSCertHandles());
that->client_certs_.push_back(x509_cert);
+ CERT_DestroyCertificate(cert);
SECKEY_DestroyPrivateKey(privkey);
continue;
}
@@ -1127,7 +1388,9 @@
CERT_FreeNicknames(names);
}
- return SECFailure;
+ // Tell NSS to suspend the client authentication. We will then abort the
+ // handshake by returning ERR_SSL_CLIENT_AUTH_CERT_NEEDED.
+ return SECWouldBlock;
#endif
}
@@ -1139,7 +1402,11 @@
void* arg) {
SSLClientSocketNSS* that = reinterpret_cast<SSLClientSocketNSS*>(arg);
+ that->set_handshake_callback_called();
+
that->UpdateServerCert();
+
+ that->CheckSecureRenegotiation();
}
int SSLClientSocketNSS::DoHandshake() {
@@ -1158,9 +1425,15 @@
LOG(WARNING) << "Couldn't invalidate SSL session: " << PR_GetError();
}
} else if (rv == SECSuccess) {
- // SSL handshake is completed. Let's verify the certificate.
- GotoState(STATE_VERIFY_CERT);
- // Done!
+ if (handshake_callback_called_) {
+ // SSL handshake is completed. Let's verify the certificate.
+ GotoState(STATE_VERIFY_CERT);
+ // Done!
+ } else {
+ // SSL_ForceHandshake returned SECSuccess prematurely.
+ rv = SECFailure;
+ net_error = ERR_SSL_PROTOCOL_ERROR;
+ }
} else {
PRErrorCode prerr = PR_GetError();
net_error = MapHandshakeError(prerr);
@@ -1182,7 +1455,21 @@
DCHECK(server_cert_);
GotoState(STATE_VERIFY_CERT_COMPLETE);
int flags = 0;
- if (ssl_config_.rev_checking_enabled)
+
+ /* Disable revocation checking for SPDY. This is a hack, but we ignore
+ * certificate errors for SPDY anyway so it's no loss in security. This lets
+ * us benchmark as if we had OCSP stapling.
+ *
+ * http://crbug.com/32020
+ */
+ unsigned char buf[255];
+ int state;
+ unsigned int len;
+ SECStatus rv = SSL_GetNextProto(nss_fd_, &state, buf, &len, sizeof(buf));
+ bool spdy = (rv == SECSuccess && state == SSL_NEXT_PROTO_NEGOTIATED &&
+ len == 4 && memcmp(buf, "spdy", 4) == 0);
+
+ if (ssl_config_.rev_checking_enabled && !spdy)
flags |= X509Certificate::VERIFY_REV_CHECKING_ENABLED;
if (ssl_config_.verify_ev_cert)
flags |= X509Certificate::VERIFY_EV_CERT;
@@ -1247,7 +1534,7 @@
}
completed_handshake_ = true;
- // TODO(ukai): we may not need this call because it is now harmless to have an
+ // TODO(ukai): we may not need this call because it is now harmless to have a
// session with a bad cert.
InvalidateSessionIfBadCertificate();
// Exit DoHandshakeLoop and return the result to the caller to Connect.
@@ -1258,7 +1545,7 @@
int SSLClientSocketNSS::DoPayloadRead() {
EnterFunction(user_read_buf_len_);
DCHECK(user_read_buf_);
- DCHECK(user_read_buf_len_ > 0);
+ DCHECK_GT(user_read_buf_len_, 0);
int rv = PR_Read(nss_fd_, user_read_buf_->data(), user_read_buf_len_);
if (client_auth_cert_needed_) {
// We don't need to invalidate the non-client-authenticated SSL session
@@ -1291,6 +1578,7 @@
}
PRErrorCode prerr = PR_GetError();
if (prerr == PR_WOULD_BLOCK_ERROR) {
+ LeaveFunction("");
return ERR_IO_PENDING;
}
LeaveFunction("");
diff --git a/net/socket/ssl_client_socket_nss.h b/net/socket/ssl_client_socket_nss.h
index 7e59ea8..60544ea 100644
--- a/net/socket/ssl_client_socket_nss.h
+++ b/net/socket/ssl_client_socket_nss.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -16,14 +16,17 @@
#include "base/scoped_ptr.h"
#include "net/base/cert_verify_result.h"
#include "net/base/completion_callback.h"
+#include "net/base/net_log.h"
#include "net/base/nss_memio.h"
#include "net/base/ssl_config_service.h"
+#include "net/base/x509_certificate.h"
#include "net/socket/ssl_client_socket.h"
namespace net {
+class BoundNetLog;
class CertVerifier;
-class LoadLog;
+class ClientSocketHandle;
class X509Certificate;
// An SSL client socket implemented with Mozilla NSS.
@@ -33,7 +36,7 @@
// The given hostname will be compared with the name(s) in the server's
// certificate during the SSL handshake. ssl_config specifies the SSL
// settings.
- SSLClientSocketNSS(ClientSocket* transport_socket,
+ SSLClientSocketNSS(ClientSocketHandle* transport_socket,
const std::string& hostname,
const SSLConfig& ssl_config);
~SSLClientSocketNSS();
@@ -44,11 +47,12 @@
virtual NextProtoStatus GetNextProto(std::string* proto);
// ClientSocket methods:
- virtual int Connect(CompletionCallback* callback, LoadLog* load_log);
+ virtual int Connect(CompletionCallback* callback);
virtual void Disconnect();
virtual bool IsConnected() const;
virtual bool IsConnectedAndIdle() const;
- virtual int GetPeerName(struct sockaddr* name, socklen_t* namelen);
+ virtual int GetPeerAddress(AddressList* address) const;
+ virtual const BoundNetLog& NetLog() const { return net_log_; }
// Socket methods:
virtual int Read(IOBuffer* buf, int buf_len, CompletionCallback* callback);
@@ -56,12 +60,19 @@
virtual bool SetReceiveBufferSize(int32 size);
virtual bool SetSendBufferSize(int32 size);
+ void set_handshake_callback_called() { handshake_callback_called_ = true; }
+
private:
// Initializes NSS SSL options. Returns a net error code.
int InitializeSSLOptions();
void InvalidateSessionIfBadCertificate();
+#if defined(OS_MACOSX) || defined(OS_WIN)
+ // Creates an OS certificate from a DER-encoded certificate.
+ static X509Certificate::OSCertHandle CreateOSCert(const SECItem& der_cert);
+#endif
X509Certificate* UpdateServerCert();
+ void CheckSecureRenegotiation() const;
void DoReadCallback(int result);
void DoWriteCallback(int result);
void DoConnectCallback(int result);
@@ -107,7 +118,7 @@
scoped_refptr<IOBuffer> recv_buffer_;
CompletionCallbackImpl<SSLClientSocketNSS> handshake_io_callback_;
- scoped_ptr<ClientSocket> transport_;
+ scoped_ptr<ClientSocketHandle> transport_;
std::string hostname_;
SSLConfig ssl_config_;
@@ -137,6 +148,10 @@
scoped_ptr<CertVerifier> verifier_;
+ // True if NSS has called HandshakeCallback.
+ bool handshake_callback_called_;
+
+ // True if the SSL handshake has been completed.
bool completed_handshake_;
enum State {
@@ -153,12 +168,15 @@
// Buffers for the network end of the SSL state machine
memio_Private* nss_bufs_;
- scoped_refptr<LoadLog> load_log_;
+ BoundNetLog net_log_;
#if defined(OS_WIN)
- // A CryptoAPI in-memory certificate store that we import server
- // certificates into so that we can verify and display the certificates
- // using CryptoAPI.
+ // A CryptoAPI in-memory certificate store. We use it for two purposes:
+ // 1. Import server certificates into this store so that we can verify and
+ // display the certificates using CryptoAPI.
+ // 2. Copy client certificates from the "MY" system certificate store into
+ // this store so that we can close the system store when we finish
+ // searching for client certificates.
static HCERTSTORE cert_store_;
#endif
};
diff --git a/net/socket/ssl_client_socket_nss_factory.cc b/net/socket/ssl_client_socket_nss_factory.cc
index cb5333d..99fb632 100644
--- a/net/socket/ssl_client_socket_nss_factory.cc
+++ b/net/socket/ssl_client_socket_nss_factory.cc
@@ -4,7 +4,11 @@
#include "net/socket/client_socket_factory.h"
+#include "build/build_config.h"
#include "net/socket/ssl_client_socket_nss.h"
+#if defined(OS_WIN)
+#include "net/socket/ssl_client_socket_win.h"
+#endif
// This file is only used on platforms where NSS is not the system SSL
// library. When compiled, this file is the only object module that pulls
@@ -14,9 +18,17 @@
namespace net {
SSLClientSocket* SSLClientSocketNSSFactory(
- ClientSocket* transport_socket,
+ ClientSocketHandle* transport_socket,
const std::string& hostname,
const SSLConfig& ssl_config) {
+ // TODO(wtc): SSLClientSocketNSS can't do SSL client authentication using
+ // CryptoAPI yet (http://crbug.com/37560), so we fall back on
+ // SSLClientSocketWin.
+#if defined(OS_WIN)
+ if (ssl_config.client_cert)
+ return new SSLClientSocketWin(transport_socket, hostname, ssl_config);
+#endif
+
return new SSLClientSocketNSS(transport_socket, hostname, ssl_config);
}
diff --git a/net/socket/ssl_client_socket_nss_factory.h b/net/socket/ssl_client_socket_nss_factory.h
new file mode 100644
index 0000000..b3b99b9
--- /dev/null
+++ b/net/socket/ssl_client_socket_nss_factory.h
@@ -0,0 +1,20 @@
+// 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.
+
+#ifndef NET_SOCKET_SSL_CLIENT_SOCKET_NSS_FACTORY_H_
+#define NET_SOCKET_SSL_CLIENT_SOCKET_NSS_FACTORY_H_
+
+#include "net/socket/client_socket_factory.h"
+
+namespace net {
+
+// Creates SSLClientSocketNSS objects.
+SSLClientSocket* SSLClientSocketNSSFactory(
+ ClientSocketHandle* transport_socket,
+ const std::string& hostname,
+ const SSLConfig& ssl_config);
+
+} // namespace net
+
+#endif // NET_SOCKET_SSL_CLIENT_SOCKET_NSS_FACTORY_H_
diff --git a/net/socket/ssl_client_socket_pool.cc b/net/socket/ssl_client_socket_pool.cc
new file mode 100644
index 0000000..3fd960c
--- /dev/null
+++ b/net/socket/ssl_client_socket_pool.cc
@@ -0,0 +1,426 @@
+// 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 "net/socket/ssl_client_socket_pool.h"
+
+#include "net/base/net_errors.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/client_socket_handle.h"
+
+namespace net {
+
+SSLSocketParams::SSLSocketParams(
+ const scoped_refptr<TCPSocketParams>& tcp_params,
+ const scoped_refptr<HttpProxySocketParams>& http_proxy_params,
+ const scoped_refptr<SOCKSSocketParams>& socks_params,
+ ProxyServer::Scheme proxy,
+ const std::string& hostname,
+ const SSLConfig& ssl_config,
+ int load_flags,
+ bool want_spdy)
+ : tcp_params_(tcp_params),
+ http_proxy_params_(http_proxy_params),
+ socks_params_(socks_params),
+ proxy_(proxy),
+ hostname_(hostname),
+ ssl_config_(ssl_config),
+ load_flags_(load_flags),
+ want_spdy_(want_spdy) {
+ switch (proxy_) {
+ case ProxyServer::SCHEME_DIRECT:
+ DCHECK(tcp_params_.get() != NULL);
+ DCHECK(http_proxy_params_.get() == NULL);
+ DCHECK(socks_params_.get() == NULL);
+ break;
+ case ProxyServer::SCHEME_HTTP:
+ DCHECK(tcp_params_.get() == NULL);
+ DCHECK(http_proxy_params_.get() != NULL);
+ DCHECK(socks_params_.get() == NULL);
+ break;
+ case ProxyServer::SCHEME_SOCKS4:
+ case ProxyServer::SCHEME_SOCKS5:
+ DCHECK(tcp_params_.get() == NULL);
+ DCHECK(http_proxy_params_.get() == NULL);
+ DCHECK(socks_params_.get() != NULL);
+ break;
+ default:
+ LOG(DFATAL) << "unknown proxy type";
+ break;
+ }
+}
+
+SSLSocketParams::~SSLSocketParams() {}
+
+// Timeout for the SSL handshake portion of the connect.
+static const int kSSLHandshakeTimeoutInSeconds = 30;
+
+SSLConnectJob::SSLConnectJob(
+ const std::string& group_name,
+ const scoped_refptr<SSLSocketParams>& params,
+ const base::TimeDelta& timeout_duration,
+ const scoped_refptr<TCPClientSocketPool>& tcp_pool,
+ const scoped_refptr<HttpProxyClientSocketPool>& http_proxy_pool,
+ const scoped_refptr<SOCKSClientSocketPool>& socks_pool,
+ ClientSocketFactory* client_socket_factory,
+ const scoped_refptr<HostResolver>& host_resolver,
+ Delegate* delegate,
+ NetLog* net_log)
+ : ConnectJob(group_name, timeout_duration, delegate,
+ BoundNetLog::Make(net_log, NetLog::SOURCE_CONNECT_JOB)),
+ params_(params),
+ tcp_pool_(tcp_pool),
+ http_proxy_pool_(http_proxy_pool),
+ socks_pool_(socks_pool),
+ client_socket_factory_(client_socket_factory),
+ resolver_(host_resolver),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ callback_(this, &SSLConnectJob::OnIOComplete)) {}
+
+SSLConnectJob::~SSLConnectJob() {}
+
+LoadState SSLConnectJob::GetLoadState() const {
+ switch (next_state_) {
+ case STATE_TUNNEL_CONNECT_COMPLETE:
+ if (transport_socket_handle_->socket())
+ return LOAD_STATE_ESTABLISHING_PROXY_TUNNEL;
+ // else, fall through.
+ case STATE_TCP_CONNECT:
+ case STATE_TCP_CONNECT_COMPLETE:
+ case STATE_SOCKS_CONNECT:
+ case STATE_SOCKS_CONNECT_COMPLETE:
+ case STATE_TUNNEL_CONNECT:
+ return transport_socket_handle_->GetLoadState();
+ case STATE_SSL_CONNECT:
+ case STATE_SSL_CONNECT_COMPLETE:
+ return LOAD_STATE_SSL_HANDSHAKE;
+ default:
+ NOTREACHED();
+ return LOAD_STATE_IDLE;
+ }
+}
+
+int SSLConnectJob::ConnectInternal() {
+ DetermineFirstState();
+ return DoLoop(OK);
+}
+
+void SSLConnectJob::DetermineFirstState() {
+ switch (params_->proxy()) {
+ case ProxyServer::SCHEME_DIRECT:
+ next_state_ = STATE_TCP_CONNECT;
+ break;
+ case ProxyServer::SCHEME_HTTP:
+ next_state_ = STATE_TUNNEL_CONNECT;
+ break;
+ case ProxyServer::SCHEME_SOCKS4:
+ case ProxyServer::SCHEME_SOCKS5:
+ next_state_ = STATE_SOCKS_CONNECT;
+ break;
+ default:
+ NOTREACHED() << "unknown proxy type";
+ break;
+ }
+}
+
+void SSLConnectJob::OnIOComplete(int result) {
+ int rv = DoLoop(result);
+ if (rv != ERR_IO_PENDING)
+ NotifyDelegateOfCompletion(rv); // Deletes |this|.
+}
+
+int SSLConnectJob::DoLoop(int result) {
+ DCHECK_NE(next_state_, STATE_NONE);
+
+ int rv = result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_TCP_CONNECT:
+ DCHECK_EQ(OK, rv);
+ rv = DoTCPConnect();
+ break;
+ case STATE_TCP_CONNECT_COMPLETE:
+ rv = DoTCPConnectComplete(rv);
+ break;
+ case STATE_SOCKS_CONNECT:
+ DCHECK_EQ(OK, rv);
+ rv = DoSOCKSConnect();
+ break;
+ case STATE_SOCKS_CONNECT_COMPLETE:
+ rv = DoSOCKSConnectComplete(rv);
+ break;
+ case STATE_TUNNEL_CONNECT:
+ DCHECK_EQ(OK, rv);
+ rv = DoTunnelConnect();
+ break;
+ case STATE_TUNNEL_CONNECT_COMPLETE:
+ rv = DoTunnelConnectComplete(rv);
+ break;
+ case STATE_SSL_CONNECT:
+ DCHECK_EQ(OK, rv);
+ rv = DoSSLConnect();
+ break;
+ case STATE_SSL_CONNECT_COMPLETE:
+ rv = DoSSLConnectComplete(rv);
+ break;
+ default:
+ NOTREACHED() << "bad state";
+ rv = ERR_FAILED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+
+ return rv;
+}
+
+int SSLConnectJob::DoTCPConnect() {
+ DCHECK(tcp_pool_.get());
+ next_state_ = STATE_TCP_CONNECT_COMPLETE;
+ transport_socket_handle_.reset(new ClientSocketHandle());
+ scoped_refptr<TCPSocketParams> tcp_params = params_->tcp_params();
+ return transport_socket_handle_->Init(group_name(), tcp_params,
+ tcp_params->destination().priority(),
+ &callback_, tcp_pool_, net_log());
+}
+
+int SSLConnectJob::DoTCPConnectComplete(int result) {
+ if (result == OK)
+ next_state_ = STATE_SSL_CONNECT;
+
+ return result;
+}
+
+int SSLConnectJob::DoSOCKSConnect() {
+ DCHECK(socks_pool_.get());
+ next_state_ = STATE_SOCKS_CONNECT_COMPLETE;
+ transport_socket_handle_.reset(new ClientSocketHandle());
+ scoped_refptr<SOCKSSocketParams> socks_params = params_->socks_params();
+ return transport_socket_handle_->Init(group_name(), socks_params,
+ socks_params->destination().priority(),
+ &callback_, socks_pool_, net_log());
+}
+
+int SSLConnectJob::DoSOCKSConnectComplete(int result) {
+ if (result == OK)
+ next_state_ = STATE_SSL_CONNECT;
+
+ return result;
+}
+
+int SSLConnectJob::DoTunnelConnect() {
+ DCHECK(http_proxy_pool_.get());
+ next_state_ = STATE_TUNNEL_CONNECT_COMPLETE;
+ transport_socket_handle_.reset(new ClientSocketHandle());
+ scoped_refptr<HttpProxySocketParams> http_proxy_params =
+ params_->http_proxy_params();
+ return transport_socket_handle_->Init(
+ group_name(), http_proxy_params,
+ http_proxy_params->tcp_params()->destination().priority(), &callback_,
+ http_proxy_pool_, net_log());
+}
+
+int SSLConnectJob::DoTunnelConnectComplete(int result) {
+ ClientSocket* socket = transport_socket_handle_->socket();
+ HttpProxyClientSocket* tunnel_socket =
+ static_cast<HttpProxyClientSocket*>(socket);
+
+ if (result == ERR_RETRY_CONNECTION) {
+ DetermineFirstState();
+ transport_socket_handle_->socket()->Disconnect();
+ return OK;
+ }
+
+ // Extract the information needed to prompt for the proxy authentication.
+ // so that when ClientSocketPoolBaseHelper calls |GetAdditionalErrorState|,
+ // we can easily set the state.
+ if (result == ERR_PROXY_AUTH_REQUESTED)
+ error_response_info_ = *tunnel_socket->GetResponseInfo();
+
+ if (result < 0)
+ return result;
+
+ if (tunnel_socket->NeedsRestartWithAuth()) {
+ // We must have gotten an 'idle' tunnel socket that is waiting for auth.
+ // The HttpAuthController should have new credentials, we just need
+ // to retry.
+ next_state_ = STATE_TUNNEL_CONNECT_COMPLETE;
+ return tunnel_socket->RestartWithAuth(&callback_);
+ }
+
+ next_state_ = STATE_SSL_CONNECT;
+ return result;
+}
+
+void SSLConnectJob::GetAdditionalErrorState(ClientSocketHandle * handle) {
+ handle->set_ssl_error_response_info(error_response_info_);
+ if (!ssl_connect_start_time_.is_null())
+ handle->set_is_ssl_error(true);
+}
+
+int SSLConnectJob::DoSSLConnect() {
+ next_state_ = STATE_SSL_CONNECT_COMPLETE;
+ // Reset the timeout to just the time allowed for the SSL handshake.
+ ResetTimer(base::TimeDelta::FromSeconds(kSSLHandshakeTimeoutInSeconds));
+ ssl_connect_start_time_ = base::TimeTicks::Now();
+
+ ssl_socket_.reset(client_socket_factory_->CreateSSLClientSocket(
+ transport_socket_handle_.release(), params_->hostname(),
+ params_->ssl_config()));
+ return ssl_socket_->Connect(&callback_);
+}
+
+int SSLConnectJob::DoSSLConnectComplete(int result) {
+ SSLClientSocket::NextProtoStatus status =
+ SSLClientSocket::kNextProtoUnsupported;
+ std::string proto;
+ // GetNextProto will fail and and trigger a NOTREACHED if we pass in a socket
+ // that hasn't had SSL_ImportFD called on it. If we get a certificate error
+ // here, then we know that we called SSL_ImportFD.
+ if (result == OK || IsCertificateError(result))
+ status = ssl_socket_->GetNextProto(&proto);
+
+ bool using_spdy = false;
+ if (status == SSLClientSocket::kNextProtoNegotiated) {
+ ssl_socket_->setWasNpnNegotiated(true);
+ if (SSLClientSocket::NextProtoFromString(proto) ==
+ SSLClientSocket::kProtoSPDY1) {
+ using_spdy = true;
+ }
+ }
+ if (params_->want_spdy() && !using_spdy)
+ return ERR_NPN_NEGOTIATION_FAILED;
+
+ if (result == OK ||
+ ssl_socket_->IgnoreCertError(result, params_->load_flags())) {
+ DCHECK(ssl_connect_start_time_ != base::TimeTicks());
+ base::TimeDelta connect_duration =
+ base::TimeTicks::Now() - ssl_connect_start_time_;
+ if (using_spdy)
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.SpdyConnectionLatency",
+ connect_duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10),
+ 100);
+ else
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.SSL_Connection_Latency",
+ connect_duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10),
+ 100);
+ }
+
+ if (result == OK || IsCertificateError(result)) {
+ set_socket(ssl_socket_.release());
+ } else if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
+ error_response_info_.cert_request_info = new SSLCertRequestInfo;
+ ssl_socket_->GetSSLCertRequestInfo(error_response_info_.cert_request_info);
+ }
+
+ return result;
+}
+
+ConnectJob* SSLClientSocketPool::SSLConnectJobFactory::NewConnectJob(
+ const std::string& group_name,
+ const PoolBase::Request& request,
+ ConnectJob::Delegate* delegate) const {
+ return new SSLConnectJob(group_name, request.params(), ConnectionTimeout(),
+ tcp_pool_, http_proxy_pool_, socks_pool_,
+ client_socket_factory_, host_resolver_, delegate,
+ net_log_);
+}
+
+SSLClientSocketPool::SSLConnectJobFactory::SSLConnectJobFactory(
+ const scoped_refptr<TCPClientSocketPool>& tcp_pool,
+ const scoped_refptr<HttpProxyClientSocketPool>& http_proxy_pool,
+ const scoped_refptr<SOCKSClientSocketPool>& socks_pool,
+ ClientSocketFactory* client_socket_factory,
+ HostResolver* host_resolver,
+ NetLog* net_log)
+ : tcp_pool_(tcp_pool),
+ http_proxy_pool_(http_proxy_pool),
+ socks_pool_(socks_pool),
+ client_socket_factory_(client_socket_factory),
+ host_resolver_(host_resolver),
+ net_log_(net_log) {
+ base::TimeDelta max_transport_timeout = base::TimeDelta();
+ base::TimeDelta pool_timeout;
+ if (tcp_pool_)
+ max_transport_timeout = tcp_pool_->ConnectionTimeout();
+ if (socks_pool_) {
+ pool_timeout = socks_pool_->ConnectionTimeout();
+ if (pool_timeout > max_transport_timeout)
+ max_transport_timeout = pool_timeout;
+ }
+ if (http_proxy_pool_) {
+ pool_timeout = http_proxy_pool_->ConnectionTimeout();
+ if (pool_timeout > max_transport_timeout)
+ max_transport_timeout = pool_timeout;
+ }
+ timeout_ = max_transport_timeout +
+ base::TimeDelta::FromSeconds(kSSLHandshakeTimeoutInSeconds);
+}
+
+SSLClientSocketPool::SSLClientSocketPool(
+ int max_sockets,
+ int max_sockets_per_group,
+ const scoped_refptr<ClientSocketPoolHistograms>& histograms,
+ const scoped_refptr<HostResolver>& host_resolver,
+ ClientSocketFactory* client_socket_factory,
+ const scoped_refptr<TCPClientSocketPool>& tcp_pool,
+ const scoped_refptr<HttpProxyClientSocketPool>& http_proxy_pool,
+ const scoped_refptr<SOCKSClientSocketPool>& socks_pool,
+ NetLog* net_log)
+ : base_(max_sockets, max_sockets_per_group, histograms,
+ base::TimeDelta::FromSeconds(
+ ClientSocketPool::unused_idle_socket_timeout()),
+ base::TimeDelta::FromSeconds(kUsedIdleSocketTimeout),
+ new SSLConnectJobFactory(tcp_pool, http_proxy_pool, socks_pool,
+ client_socket_factory, host_resolver,
+ net_log)) {}
+
+SSLClientSocketPool::~SSLClientSocketPool() {}
+
+int SSLClientSocketPool::RequestSocket(const std::string& group_name,
+ const void* socket_params,
+ RequestPriority priority,
+ ClientSocketHandle* handle,
+ CompletionCallback* callback,
+ const BoundNetLog& net_log) {
+ const scoped_refptr<SSLSocketParams>* casted_socket_params =
+ static_cast<const scoped_refptr<SSLSocketParams>*>(socket_params);
+
+ return base_.RequestSocket(group_name, *casted_socket_params, priority,
+ handle, callback, net_log);
+}
+
+void SSLClientSocketPool::CancelRequest(const std::string& group_name,
+ ClientSocketHandle* handle) {
+ base_.CancelRequest(group_name, handle);
+}
+
+void SSLClientSocketPool::ReleaseSocket(const std::string& group_name,
+ ClientSocket* socket, int id) {
+ base_.ReleaseSocket(group_name, socket, id);
+}
+
+void SSLClientSocketPool::Flush() {
+ base_.Flush();
+}
+
+void SSLClientSocketPool::CloseIdleSockets() {
+ base_.CloseIdleSockets();
+}
+
+int SSLClientSocketPool::IdleSocketCountInGroup(
+ const std::string& group_name) const {
+ return base_.IdleSocketCountInGroup(group_name);
+}
+
+LoadState SSLClientSocketPool::GetLoadState(
+ const std::string& group_name, const ClientSocketHandle* handle) const {
+ return base_.GetLoadState(group_name, handle);
+}
+
+} // namespace net
diff --git a/net/socket/ssl_client_socket_pool.h b/net/socket/ssl_client_socket_pool.h
new file mode 100644
index 0000000..fd5bbb3
--- /dev/null
+++ b/net/socket/ssl_client_socket_pool.h
@@ -0,0 +1,247 @@
+// 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.
+
+#ifndef NET_SOCKET_SSL_CLIENT_SOCKET_POOL_H_
+#define NET_SOCKET_SSL_CLIENT_SOCKET_POOL_H_
+
+#include <string>
+
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "base/time.h"
+#include "net/base/host_resolver.h"
+#include "net/base/ssl_config_service.h"
+#include "net/http/http_proxy_client_socket.h"
+#include "net/http/http_proxy_client_socket_pool.h"
+#include "net/proxy/proxy_server.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/client_socket_pool_base.h"
+#include "net/socket/client_socket_pool_histograms.h"
+#include "net/socket/client_socket_pool.h"
+#include "net/socket/socks_client_socket_pool.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/socket/tcp_client_socket_pool.h"
+
+namespace net {
+
+class ClientSocketFactory;
+class ConnectJobFactory;
+
+// SSLSocketParams only needs the socket params for the transport socket
+// that will be used (denoted by |proxy|).
+class SSLSocketParams : public base::RefCounted<SSLSocketParams> {
+ public:
+ SSLSocketParams(const scoped_refptr<TCPSocketParams>& tcp_params,
+ const scoped_refptr<HttpProxySocketParams>& http_proxy_params,
+ const scoped_refptr<SOCKSSocketParams>& socks_params,
+ ProxyServer::Scheme proxy,
+ const std::string& hostname,
+ const SSLConfig& ssl_config,
+ int load_flags,
+ bool want_spdy);
+
+ const scoped_refptr<TCPSocketParams>& tcp_params() { return tcp_params_; }
+ const scoped_refptr<HttpProxySocketParams>& http_proxy_params () {
+ return http_proxy_params_;
+ }
+ const scoped_refptr<SOCKSSocketParams>& socks_params() {
+ return socks_params_;
+ }
+ ProxyServer::Scheme proxy() const { return proxy_; }
+ const std::string& hostname() const { return hostname_; }
+ const SSLConfig& ssl_config() const { return ssl_config_; }
+ int load_flags() const { return load_flags_; }
+ bool want_spdy() const { return want_spdy_; }
+
+ private:
+ friend class base::RefCounted<SSLSocketParams>;
+ ~SSLSocketParams();
+
+ const scoped_refptr<TCPSocketParams> tcp_params_;
+ const scoped_refptr<HttpProxySocketParams> http_proxy_params_;
+ const scoped_refptr<SOCKSSocketParams> socks_params_;
+ const ProxyServer::Scheme proxy_;
+ const std::string hostname_;
+ const SSLConfig ssl_config_;
+ const int load_flags_;
+ const bool want_spdy_;
+
+ DISALLOW_COPY_AND_ASSIGN(SSLSocketParams);
+};
+
+// SSLConnectJob handles the SSL handshake after setting up the underlying
+// connection as specified in the params.
+class SSLConnectJob : public ConnectJob {
+ public:
+ SSLConnectJob(
+ const std::string& group_name,
+ const scoped_refptr<SSLSocketParams>& params,
+ const base::TimeDelta& timeout_duration,
+ const scoped_refptr<TCPClientSocketPool>& tcp_pool,
+ const scoped_refptr<HttpProxyClientSocketPool>& http_proxy_pool,
+ const scoped_refptr<SOCKSClientSocketPool>& socks_pool,
+ ClientSocketFactory* client_socket_factory,
+ const scoped_refptr<HostResolver>& host_resolver,
+ Delegate* delegate,
+ NetLog* net_log);
+ virtual ~SSLConnectJob();
+
+ // ConnectJob methods.
+ virtual LoadState GetLoadState() const;
+
+ virtual void GetAdditionalErrorState(ClientSocketHandle * handle);
+
+ private:
+ enum State {
+ STATE_TCP_CONNECT,
+ STATE_TCP_CONNECT_COMPLETE,
+ STATE_SOCKS_CONNECT,
+ STATE_SOCKS_CONNECT_COMPLETE,
+ STATE_TUNNEL_CONNECT,
+ STATE_TUNNEL_CONNECT_COMPLETE,
+ STATE_SSL_CONNECT,
+ STATE_SSL_CONNECT_COMPLETE,
+ STATE_NONE,
+ };
+
+ // Starts the SSL connection process. Returns OK on success and
+ // ERR_IO_PENDING if it cannot immediately service the request.
+ // Otherwise, it returns a net error code.
+ virtual int ConnectInternal();
+
+ void DetermineFirstState();
+
+ void OnIOComplete(int result);
+
+ // Runs the state transition loop.
+ int DoLoop(int result);
+
+ int DoTCPConnect();
+ int DoTCPConnectComplete(int result);
+ int DoSOCKSConnect();
+ int DoSOCKSConnectComplete(int result);
+ int DoTunnelConnect();
+ int DoTunnelConnectComplete(int result);
+ int DoSSLConnect();
+ int DoSSLConnectComplete(int result);
+
+ scoped_refptr<SSLSocketParams> params_;
+ const scoped_refptr<TCPClientSocketPool> tcp_pool_;
+ const scoped_refptr<HttpProxyClientSocketPool> http_proxy_pool_;
+ const scoped_refptr<SOCKSClientSocketPool> socks_pool_;
+ ClientSocketFactory* const client_socket_factory_;
+ const scoped_refptr<HostResolver> resolver_;
+
+ State next_state_;
+ CompletionCallbackImpl<SSLConnectJob> callback_;
+ scoped_ptr<ClientSocketHandle> transport_socket_handle_;
+ scoped_ptr<SSLClientSocket> ssl_socket_;
+
+ // The time the DoSSLConnect() method was called.
+ base::TimeTicks ssl_connect_start_time_;
+
+ HttpResponseInfo error_response_info_;
+
+ DISALLOW_COPY_AND_ASSIGN(SSLConnectJob);
+};
+
+class SSLClientSocketPool : public ClientSocketPool {
+ public:
+ // Only the pools that will be used are required. i.e. if you never
+ // try to create an SSL over SOCKS socket, |socks_pool| may be NULL.
+ SSLClientSocketPool(
+ int max_sockets,
+ int max_sockets_per_group,
+ const scoped_refptr<ClientSocketPoolHistograms>& histograms,
+ const scoped_refptr<HostResolver>& host_resolver,
+ ClientSocketFactory* client_socket_factory,
+ const scoped_refptr<TCPClientSocketPool>& tcp_pool,
+ const scoped_refptr<HttpProxyClientSocketPool>& http_proxy_pool,
+ const scoped_refptr<SOCKSClientSocketPool>& socks_pool,
+ NetLog* net_log);
+
+ // ClientSocketPool methods:
+ virtual int RequestSocket(const std::string& group_name,
+ const void* connect_params,
+ RequestPriority priority,
+ ClientSocketHandle* handle,
+ CompletionCallback* callback,
+ const BoundNetLog& net_log);
+
+ virtual void CancelRequest(const std::string& group_name,
+ ClientSocketHandle* handle);
+
+ virtual void ReleaseSocket(const std::string& group_name,
+ ClientSocket* socket,
+ int id);
+
+ virtual void Flush();
+
+ virtual void CloseIdleSockets();
+
+ virtual int IdleSocketCount() const {
+ return base_.idle_socket_count();
+ }
+
+ virtual int IdleSocketCountInGroup(const std::string& group_name) const;
+
+ virtual LoadState GetLoadState(const std::string& group_name,
+ const ClientSocketHandle* handle) const;
+
+ virtual base::TimeDelta ConnectionTimeout() const {
+ return base_.ConnectionTimeout();
+ }
+
+ virtual scoped_refptr<ClientSocketPoolHistograms> histograms() const {
+ return base_.histograms();
+ };
+
+ protected:
+ virtual ~SSLClientSocketPool();
+
+ private:
+ typedef ClientSocketPoolBase<SSLSocketParams> PoolBase;
+
+ class SSLConnectJobFactory : public PoolBase::ConnectJobFactory {
+ public:
+ SSLConnectJobFactory(
+ const scoped_refptr<TCPClientSocketPool>& tcp_pool,
+ const scoped_refptr<HttpProxyClientSocketPool>& http_proxy_pool,
+ const scoped_refptr<SOCKSClientSocketPool>& socks_pool,
+ ClientSocketFactory* client_socket_factory,
+ HostResolver* host_resolver,
+ NetLog* net_log);
+
+ virtual ~SSLConnectJobFactory() {}
+
+ // ClientSocketPoolBase::ConnectJobFactory methods.
+ virtual ConnectJob* NewConnectJob(
+ const std::string& group_name,
+ const PoolBase::Request& request,
+ ConnectJob::Delegate* delegate) const;
+
+ virtual base::TimeDelta ConnectionTimeout() const { return timeout_; }
+
+ private:
+ const scoped_refptr<TCPClientSocketPool> tcp_pool_;
+ const scoped_refptr<HttpProxyClientSocketPool> http_proxy_pool_;
+ const scoped_refptr<SOCKSClientSocketPool> socks_pool_;
+ ClientSocketFactory* const client_socket_factory_;
+ const scoped_refptr<HostResolver> host_resolver_;
+ base::TimeDelta timeout_;
+ NetLog* net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(SSLConnectJobFactory);
+ };
+
+ PoolBase base_;
+
+ DISALLOW_COPY_AND_ASSIGN(SSLClientSocketPool);
+};
+
+REGISTER_SOCKET_PARAMS_FOR_POOL(SSLClientSocketPool, SSLSocketParams);
+
+} // namespace net
+
+#endif // NET_SOCKET_SSL_CLIENT_SOCKET_POOL_H_
diff --git a/net/socket/ssl_client_socket_pool_unittest.cc b/net/socket/ssl_client_socket_pool_unittest.cc
new file mode 100644
index 0000000..972f7d8
--- /dev/null
+++ b/net/socket/ssl_client_socket_pool_unittest.cc
@@ -0,0 +1,723 @@
+// 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 "net/http/http_proxy_client_socket_pool.h"
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/time.h"
+#include "net/base/auth.h"
+#include "net/base/mock_host_resolver.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/base/ssl_config_service_defaults.h"
+#include "net/http/http_auth_controller.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_response_headers.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/client_socket_pool_histograms.h"
+#include "net/socket/socket_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+const int kMaxSockets = 32;
+const int kMaxSocketsPerGroup = 6;
+
+class SSLClientSocketPoolTest : public ClientSocketPoolTest {
+ protected:
+ SSLClientSocketPoolTest()
+ : direct_tcp_socket_params_(new TCPSocketParams(
+ HostPortPair("host", 443), MEDIUM, GURL(), false)),
+ tcp_socket_pool_(new MockTCPClientSocketPool(
+ kMaxSockets,
+ kMaxSocketsPerGroup,
+ make_scoped_refptr(new ClientSocketPoolHistograms("MockTCP")),
+ &socket_factory_)),
+ proxy_tcp_socket_params_(new TCPSocketParams(
+ HostPortPair("proxy", 443), MEDIUM, GURL(), false)),
+ http_proxy_socket_pool_(new HttpProxyClientSocketPool(
+ kMaxSockets,
+ kMaxSocketsPerGroup,
+ make_scoped_refptr(new ClientSocketPoolHistograms("MockHttpProxy")),
+ new MockHostResolver,
+ tcp_socket_pool_,
+ NULL)),
+ socks_socket_params_(new SOCKSSocketParams(
+ proxy_tcp_socket_params_, true, HostPortPair("sockshost", 443),
+ MEDIUM, GURL())),
+ socks_socket_pool_(new MockSOCKSClientSocketPool(
+ kMaxSockets,
+ kMaxSocketsPerGroup,
+ make_scoped_refptr(new ClientSocketPoolHistograms("MockSOCKS")),
+ tcp_socket_pool_)) {
+ scoped_refptr<SSLConfigService> ssl_config_service(
+ new SSLConfigServiceDefaults);
+ ssl_config_service->GetSSLConfig(&ssl_config_);
+ }
+
+ void CreatePool(bool tcp_pool, bool http_proxy_pool, bool socks_pool) {
+ pool_ = new SSLClientSocketPool(
+ kMaxSockets,
+ kMaxSocketsPerGroup,
+ make_scoped_refptr(new ClientSocketPoolHistograms("SSLUnitTest")),
+ NULL,
+ &socket_factory_,
+ tcp_pool ? tcp_socket_pool_ : NULL,
+ http_proxy_pool ? http_proxy_socket_pool_ : NULL,
+ socks_pool ? socks_socket_pool_ : NULL,
+ NULL);
+ }
+
+ scoped_refptr<SSLSocketParams> SSLParams(
+ ProxyServer::Scheme proxy, struct MockHttpAuthControllerData* auth_data,
+ size_t auth_data_len, bool want_spdy) {
+
+ scoped_refptr<HttpProxySocketParams> http_proxy_params;
+ if (proxy == ProxyServer::SCHEME_HTTP) {
+ scoped_refptr<MockHttpAuthController> auth_controller =
+ new MockHttpAuthController();
+ auth_controller->SetMockAuthControllerData(auth_data, auth_data_len);
+ http_proxy_params = new HttpProxySocketParams(proxy_tcp_socket_params_,
+ GURL("http://host"),
+ HostPortPair("host", 80),
+ auth_controller, true);
+ }
+
+ return make_scoped_refptr(new SSLSocketParams(
+ proxy == ProxyServer::SCHEME_DIRECT ? direct_tcp_socket_params_ : NULL,
+ http_proxy_params,
+ proxy == ProxyServer::SCHEME_SOCKS5 ? socks_socket_params_ : NULL,
+ proxy,
+ "host",
+ ssl_config_,
+ 0,
+ want_spdy));
+ }
+
+ MockClientSocketFactory socket_factory_;
+
+ scoped_refptr<TCPSocketParams> direct_tcp_socket_params_;
+ scoped_refptr<MockTCPClientSocketPool> tcp_socket_pool_;
+
+ scoped_refptr<TCPSocketParams> proxy_tcp_socket_params_;
+ scoped_refptr<HttpProxySocketParams> http_proxy_socket_params_;
+ scoped_refptr<HttpProxyClientSocketPool> http_proxy_socket_pool_;
+
+ scoped_refptr<SOCKSSocketParams> socks_socket_params_;
+ scoped_refptr<MockSOCKSClientSocketPool> socks_socket_pool_;
+
+ SSLConfig ssl_config_;
+ scoped_refptr<SSLClientSocketPool> pool_;
+};
+
+TEST_F(SSLClientSocketPoolTest, TCPFail) {
+ StaticSocketDataProvider data;
+ data.set_connect_data(MockConnect(false, ERR_CONNECTION_FAILED));
+ socket_factory_.AddSocketDataProvider(&data);
+
+ CreatePool(true /* tcp pool */, false, false);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_DIRECT,
+ NULL, 0, false);
+
+ ClientSocketHandle handle;
+ int rv = handle.Init("a", params, MEDIUM, NULL, pool_, BoundNetLog());
+ EXPECT_EQ(ERR_CONNECTION_FAILED, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ EXPECT_FALSE(handle.is_ssl_error());
+}
+
+TEST_F(SSLClientSocketPoolTest, TCPFailAsync) {
+ StaticSocketDataProvider data;
+ data.set_connect_data(MockConnect(true, ERR_CONNECTION_FAILED));
+ socket_factory_.AddSocketDataProvider(&data);
+
+ CreatePool(true /* tcp pool */, false, false);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_DIRECT,
+ NULL, 0, false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a", params, MEDIUM, &callback, pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(ERR_CONNECTION_FAILED, callback.WaitForResult());
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ EXPECT_FALSE(handle.is_ssl_error());
+}
+
+TEST_F(SSLClientSocketPoolTest, BasicDirect) {
+ StaticSocketDataProvider data;
+ data.set_connect_data(MockConnect(false, OK));
+ socket_factory_.AddSocketDataProvider(&data);
+ SSLSocketDataProvider ssl(false, OK);
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreatePool(true /* tcp pool */, false, false);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_DIRECT,
+ NULL, 0, false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a", params, MEDIUM, &callback, pool_, BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+}
+
+TEST_F(SSLClientSocketPoolTest, BasicDirectAsync) {
+ StaticSocketDataProvider data;
+ socket_factory_.AddSocketDataProvider(&data);
+ SSLSocketDataProvider ssl(true, OK);
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreatePool(true /* tcp pool */, false, false);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_DIRECT,
+ NULL, 0, false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a", params, MEDIUM, &callback, pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+}
+
+TEST_F(SSLClientSocketPoolTest, DirectCertError) {
+ StaticSocketDataProvider data;
+ socket_factory_.AddSocketDataProvider(&data);
+ SSLSocketDataProvider ssl(true, ERR_CERT_COMMON_NAME_INVALID);
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreatePool(true /* tcp pool */, false, false);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_DIRECT,
+ NULL, 0, false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a", params, MEDIUM, &callback, pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(ERR_CERT_COMMON_NAME_INVALID, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+}
+
+TEST_F(SSLClientSocketPoolTest, DirectSSLError) {
+ StaticSocketDataProvider data;
+ socket_factory_.AddSocketDataProvider(&data);
+ SSLSocketDataProvider ssl(true, ERR_SSL_PROTOCOL_ERROR);
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreatePool(true /* tcp pool */, false, false);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_DIRECT,
+ NULL, 0, false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a", params, MEDIUM, &callback, pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(ERR_SSL_PROTOCOL_ERROR, callback.WaitForResult());
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ EXPECT_TRUE(handle.is_ssl_error());
+}
+
+TEST_F(SSLClientSocketPoolTest, DirectWithNPN) {
+ StaticSocketDataProvider data;
+ socket_factory_.AddSocketDataProvider(&data);
+ SSLSocketDataProvider ssl(true, OK);
+ ssl.next_proto_status = SSLClientSocket::kNextProtoNegotiated;
+ ssl.next_proto = "http/1.1";
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreatePool(true /* tcp pool */, false, false);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_DIRECT,
+ NULL, 0, false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a", params, MEDIUM, &callback, pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+ SSLClientSocket* ssl_socket = static_cast<SSLClientSocket*>(handle.socket());
+ EXPECT_TRUE(ssl_socket->wasNpnNegotiated());
+}
+
+TEST_F(SSLClientSocketPoolTest, DirectNoSPDY) {
+ StaticSocketDataProvider data;
+ socket_factory_.AddSocketDataProvider(&data);
+ SSLSocketDataProvider ssl(true, OK);
+ ssl.next_proto_status = SSLClientSocket::kNextProtoNegotiated;
+ ssl.next_proto = "http/1.1";
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreatePool(true /* tcp pool */, false, false);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_DIRECT,
+ NULL, 0, true);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a", params, MEDIUM, &callback, pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(ERR_NPN_NEGOTIATION_FAILED, callback.WaitForResult());
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ EXPECT_TRUE(handle.is_ssl_error());
+}
+
+TEST_F(SSLClientSocketPoolTest, DirectGotSPDY) {
+ StaticSocketDataProvider data;
+ socket_factory_.AddSocketDataProvider(&data);
+ SSLSocketDataProvider ssl(true, OK);
+ ssl.next_proto_status = SSLClientSocket::kNextProtoNegotiated;
+ ssl.next_proto = "spdy/1";
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreatePool(true /* tcp pool */, false, false);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_DIRECT,
+ NULL, 0, true);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a", params, MEDIUM, &callback, pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+
+ SSLClientSocket* ssl_socket = static_cast<SSLClientSocket*>(handle.socket());
+ EXPECT_TRUE(ssl_socket->wasNpnNegotiated());
+ std::string proto;
+ ssl_socket->GetNextProto(&proto);
+ EXPECT_EQ(SSLClientSocket::NextProtoFromString(proto),
+ SSLClientSocket::kProtoSPDY1);
+}
+
+TEST_F(SSLClientSocketPoolTest, DirectGotBonusSPDY) {
+ StaticSocketDataProvider data;
+ socket_factory_.AddSocketDataProvider(&data);
+ SSLSocketDataProvider ssl(true, OK);
+ ssl.next_proto_status = SSLClientSocket::kNextProtoNegotiated;
+ ssl.next_proto = "spdy/1";
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreatePool(true /* tcp pool */, false, false);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_DIRECT,
+ NULL, 0, false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a", params, MEDIUM, &callback, pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+
+ SSLClientSocket* ssl_socket = static_cast<SSLClientSocket*>(handle.socket());
+ EXPECT_TRUE(ssl_socket->wasNpnNegotiated());
+ std::string proto;
+ ssl_socket->GetNextProto(&proto);
+ EXPECT_EQ(SSLClientSocket::NextProtoFromString(proto),
+ SSLClientSocket::kProtoSPDY1);
+}
+
+TEST_F(SSLClientSocketPoolTest, SOCKSFail) {
+ StaticSocketDataProvider data;
+ data.set_connect_data(MockConnect(false, ERR_CONNECTION_FAILED));
+ socket_factory_.AddSocketDataProvider(&data);
+
+ CreatePool(false, true /* http proxy pool */, true /* socks pool */);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_SOCKS5,
+ NULL, 0, false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a", params, MEDIUM, &callback, pool_, BoundNetLog());
+ EXPECT_EQ(ERR_CONNECTION_FAILED, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ EXPECT_FALSE(handle.is_ssl_error());
+}
+
+TEST_F(SSLClientSocketPoolTest, SOCKSFailAsync) {
+ StaticSocketDataProvider data;
+ data.set_connect_data(MockConnect(true, ERR_CONNECTION_FAILED));
+ socket_factory_.AddSocketDataProvider(&data);
+
+ CreatePool(false, true /* http proxy pool */, true /* socks pool */);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_SOCKS5,
+ NULL, 0, false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a", params, MEDIUM, &callback, pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(ERR_CONNECTION_FAILED, callback.WaitForResult());
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ EXPECT_FALSE(handle.is_ssl_error());
+}
+
+TEST_F(SSLClientSocketPoolTest, SOCKSBasic) {
+ StaticSocketDataProvider data;
+ data.set_connect_data(MockConnect(false, OK));
+ socket_factory_.AddSocketDataProvider(&data);
+ SSLSocketDataProvider ssl(false, OK);
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreatePool(false, true /* http proxy pool */, true /* socks pool */);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_SOCKS5,
+ NULL, 0, false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a", params, MEDIUM, &callback, pool_, BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+}
+
+TEST_F(SSLClientSocketPoolTest, SOCKSBasicAsync) {
+ StaticSocketDataProvider data;
+ socket_factory_.AddSocketDataProvider(&data);
+ SSLSocketDataProvider ssl(true, OK);
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreatePool(false, true /* http proxy pool */, true /* socks pool */);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_SOCKS5,
+ NULL, 0, false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a", params, MEDIUM, &callback, pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+}
+
+TEST_F(SSLClientSocketPoolTest, HttpProxyFail) {
+ StaticSocketDataProvider data;
+ data.set_connect_data(MockConnect(false, ERR_CONNECTION_FAILED));
+ socket_factory_.AddSocketDataProvider(&data);
+
+ CreatePool(false, true /* http proxy pool */, true /* socks pool */);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_HTTP,
+ NULL, 0, false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a", params, MEDIUM, &callback, pool_, BoundNetLog());
+ EXPECT_EQ(ERR_CONNECTION_FAILED, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ EXPECT_FALSE(handle.is_ssl_error());
+}
+
+TEST_F(SSLClientSocketPoolTest, HttpProxyFailAsync) {
+ StaticSocketDataProvider data;
+ data.set_connect_data(MockConnect(true, ERR_CONNECTION_FAILED));
+ socket_factory_.AddSocketDataProvider(&data);
+
+ CreatePool(false, true /* http proxy pool */, true /* socks pool */);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_HTTP,
+ NULL, 0, false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a", params, MEDIUM, &callback, pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(ERR_CONNECTION_FAILED, callback.WaitForResult());
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ EXPECT_FALSE(handle.is_ssl_error());
+}
+
+TEST_F(SSLClientSocketPoolTest, HttpProxyBasic) {
+ MockWrite writes[] = {
+ MockWrite(false,
+ "CONNECT host:80 HTTP/1.1\r\n"
+ "Host: host\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: Basic Zm9vOmJheg==\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(false, "HTTP/1.1 200 Connection Established\r\n\r\n"),
+ };
+ StaticSocketDataProvider data(reads, arraysize(reads), writes,
+ arraysize(writes));
+ data.set_connect_data(MockConnect(false, OK));
+ socket_factory_.AddSocketDataProvider(&data);
+ MockHttpAuthControllerData auth_data[] = {
+ MockHttpAuthControllerData("Proxy-Authorization: Basic Zm9vOmJheg=="),
+ };
+ SSLSocketDataProvider ssl(false, OK);
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreatePool(false, true /* http proxy pool */, true /* socks pool */);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_HTTP,
+ auth_data,
+ arraysize(auth_data),
+ false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a", params, MEDIUM, &callback, pool_, BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+}
+
+TEST_F(SSLClientSocketPoolTest, HttpProxyBasicAsync) {
+ MockWrite writes[] = {
+ MockWrite("CONNECT host:80 HTTP/1.1\r\n"
+ "Host: host\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: Basic Zm9vOmJheg==\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"),
+ };
+ StaticSocketDataProvider data(reads, arraysize(reads), writes,
+ arraysize(writes));
+ socket_factory_.AddSocketDataProvider(&data);
+ MockHttpAuthControllerData auth_data[] = {
+ MockHttpAuthControllerData("Proxy-Authorization: Basic Zm9vOmJheg=="),
+ };
+ SSLSocketDataProvider ssl(true, OK);
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreatePool(false, true /* http proxy pool */, true /* socks pool */);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_HTTP,
+ auth_data,
+ arraysize(auth_data),
+ false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a", params, MEDIUM, &callback, pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+}
+
+TEST_F(SSLClientSocketPoolTest, NeedProxyAuth) {
+ MockWrite writes[] = {
+ MockWrite("CONNECT host:80 HTTP/1.1\r\n"
+ "Host: host\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"),
+ MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Content-Length: 10\r\n\r\n"),
+ MockRead("0123456789"),
+ };
+ StaticSocketDataProvider data(reads, arraysize(reads), writes,
+ arraysize(writes));
+ socket_factory_.AddSocketDataProvider(&data);
+ MockHttpAuthControllerData auth_data[] = {
+ MockHttpAuthControllerData(""),
+ };
+ SSLSocketDataProvider ssl(true, OK);
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreatePool(false, true /* http proxy pool */, true /* socks pool */);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_HTTP,
+ auth_data,
+ arraysize(auth_data),
+ false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a", params, MEDIUM, &callback, pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(ERR_PROXY_AUTH_REQUESTED, callback.WaitForResult());
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ EXPECT_FALSE(handle.is_ssl_error());
+ const HttpResponseInfo& tunnel_info = handle.ssl_error_response_info();
+ EXPECT_EQ(tunnel_info.headers->response_code(), 407);
+}
+
+TEST_F(SSLClientSocketPoolTest, DoProxyAuth) {
+ MockWrite writes[] = {
+ MockWrite("CONNECT host:80 HTTP/1.1\r\n"
+ "Host: host\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ MockWrite("CONNECT host:80 HTTP/1.1\r\n"
+ "Host: host\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: Basic Zm9vOmJheg==\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"),
+ MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Content-Length: 10\r\n\r\n"),
+ MockRead("0123456789"),
+ MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"),
+ };
+ StaticSocketDataProvider data(reads, arraysize(reads), writes,
+ arraysize(writes));
+ socket_factory_.AddSocketDataProvider(&data);
+ MockHttpAuthControllerData auth_data[] = {
+ MockHttpAuthControllerData(""),
+ MockHttpAuthControllerData("Proxy-Authorization: Basic Zm9vOmJheg=="),
+ };
+ SSLSocketDataProvider ssl(true, OK);
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreatePool(false, true /* http proxy pool */, true /* socks pool */);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_HTTP,
+ auth_data,
+ arraysize(auth_data),
+ false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a", params, MEDIUM, &callback, pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(ERR_PROXY_AUTH_REQUESTED, callback.WaitForResult());
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ EXPECT_FALSE(handle.is_ssl_error());
+ const HttpResponseInfo& tunnel_info = handle.ssl_error_response_info();
+ EXPECT_EQ(tunnel_info.headers->response_code(), 407);
+
+ params->http_proxy_params()->auth_controller()->ResetAuth(std::wstring(),
+ std::wstring());
+ rv = handle.Init("a", params, MEDIUM, &callback, pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ // Test that http://crbug.com/49325 doesn't regress.
+ EXPECT_EQ(handle.GetLoadState(), LOAD_STATE_ESTABLISHING_PROXY_TUNNEL);
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+}
+
+TEST_F(SSLClientSocketPoolTest, DoProxyAuthNoKeepAlive) {
+ MockWrite writes1[] = {
+ MockWrite("CONNECT host:80 HTTP/1.1\r\n"
+ "Host: host\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ };
+ MockWrite writes2[] = {
+ MockWrite("CONNECT host:80 HTTP/1.1\r\n"
+ "Host: host\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: Basic Zm9vOmJheg==\r\n\r\n"),
+ };
+ MockRead reads1[] = {
+ MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"),
+ MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n\r\n"),
+ MockRead("Content0123456789"),
+ };
+ MockRead reads2[] = {
+ MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"),
+ };
+ StaticSocketDataProvider data1(reads1, arraysize(reads1), writes1,
+ arraysize(writes1));
+ socket_factory_.AddSocketDataProvider(&data1);
+ StaticSocketDataProvider data2(reads2, arraysize(reads2), writes2,
+ arraysize(writes2));
+ socket_factory_.AddSocketDataProvider(&data2);
+ MockHttpAuthControllerData auth_data[] = {
+ MockHttpAuthControllerData(""),
+ MockHttpAuthControllerData("Proxy-Authorization: Basic Zm9vOmJheg=="),
+ };
+ SSLSocketDataProvider ssl(true, OK);
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreatePool(false, true /* http proxy pool */, true /* socks pool */);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_HTTP,
+ auth_data,
+ arraysize(auth_data),
+ false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a", params, MEDIUM, &callback, pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(ERR_PROXY_AUTH_REQUESTED, callback.WaitForResult());
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ EXPECT_FALSE(handle.is_ssl_error());
+ const HttpResponseInfo& tunnel_info = handle.ssl_error_response_info();
+ EXPECT_EQ(tunnel_info.headers->response_code(), 407);
+
+ params->http_proxy_params()->auth_controller()->ResetAuth(std::wstring(),
+ std::wstring());
+ rv = handle.Init("a", params, MEDIUM, &callback, pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+}
+
+// It would be nice to also test the timeouts in SSLClientSocketPool.
+
+} // namespace
+
+} // namespace net
diff --git a/net/socket/ssl_client_socket_unittest.cc b/net/socket/ssl_client_socket_unittest.cc
index f44f1ab..a62927e 100644
--- a/net/socket/ssl_client_socket_unittest.cc
+++ b/net/socket/ssl_client_socket_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -7,14 +7,15 @@
#include "net/base/address_list.h"
#include "net/base/host_resolver.h"
#include "net/base/io_buffer.h"
-#include "net/base/load_log.h"
-#include "net/base/load_log_unittest.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
#include "net/base/net_errors.h"
#include "net/base/ssl_config_service.h"
#include "net/base/test_completion_callback.h"
#include "net/socket/client_socket_factory.h"
-#include "net/socket/ssl_test_util.h"
+#include "net/socket/socket_test_util.h"
#include "net/socket/tcp_client_socket.h"
+#include "net/test/test_server.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
@@ -25,7 +26,8 @@
class SSLClientSocketTest : public PlatformTest {
public:
SSLClientSocketTest()
- : resolver_(net::CreateSystemHostResolver(NULL)),
+ : resolver_(net::CreateSystemHostResolver(
+ net::HostResolver::kDefaultParallelism)),
socket_factory_(net::ClientSocketFactory::GetDefaultFactory()) {
}
@@ -65,11 +67,12 @@
TestCompletionCallback callback;
net::HostResolver::RequestInfo info(server_.kHostName, server_.kOKHTTPSPort);
- int rv = resolver_->Resolve(info, &addr, NULL, NULL, NULL);
+ int rv = resolver_->Resolve(info, &addr, NULL, NULL, net::BoundNetLog());
EXPECT_EQ(net::OK, rv);
- net::ClientSocket *transport = new net::TCPClientSocket(addr);
- rv = transport->Connect(&callback, NULL);
+ net::CapturingNetLog log(net::CapturingNetLog::kUnbounded);
+ net::ClientSocket* transport = new net::TCPClientSocket(addr, &log);
+ rv = transport->Connect(&callback);
if (rv == net::ERR_IO_PENDING)
rv = callback.WaitForResult();
EXPECT_EQ(net::OK, rv);
@@ -80,15 +83,14 @@
EXPECT_FALSE(sock->IsConnected());
- scoped_refptr<net::LoadLog> log(new net::LoadLog(net::LoadLog::kUnbounded));
- rv = sock->Connect(&callback, log);
+ rv = sock->Connect(&callback);
EXPECT_TRUE(net::LogContainsBeginEvent(
- *log, 0, net::LoadLog::TYPE_SSL_CONNECT));
+ log.entries(), 5, net::NetLog::TYPE_SSL_CONNECT));
if (rv != net::OK) {
ASSERT_EQ(net::ERR_IO_PENDING, rv);
EXPECT_FALSE(sock->IsConnected());
EXPECT_FALSE(net::LogContainsEndEvent(
- *log, -1, net::LoadLog::TYPE_SSL_CONNECT));
+ log.entries(), -1, net::NetLog::TYPE_SSL_CONNECT));
rv = callback.WaitForResult();
EXPECT_EQ(net::OK, rv);
@@ -96,7 +98,7 @@
EXPECT_TRUE(sock->IsConnected());
EXPECT_TRUE(net::LogContainsEndEvent(
- *log, -1, net::LoadLog::TYPE_SSL_CONNECT));
+ log.entries(), -1, net::NetLog::TYPE_SSL_CONNECT));
sock->Disconnect();
EXPECT_FALSE(sock->IsConnected());
@@ -109,11 +111,12 @@
TestCompletionCallback callback;
net::HostResolver::RequestInfo info(server_.kHostName, server_.kBadHTTPSPort);
- int rv = resolver_->Resolve(info, &addr, NULL, NULL, NULL);
+ int rv = resolver_->Resolve(info, &addr, NULL, NULL, net::BoundNetLog());
EXPECT_EQ(net::OK, rv);
- net::ClientSocket *transport = new net::TCPClientSocket(addr);
- rv = transport->Connect(&callback, NULL);
+ net::CapturingNetLog log(net::CapturingNetLog::kUnbounded);
+ net::ClientSocket* transport = new net::TCPClientSocket(addr, &log);
+ rv = transport->Connect(&callback);
if (rv == net::ERR_IO_PENDING)
rv = callback.WaitForResult();
EXPECT_EQ(net::OK, rv);
@@ -124,15 +127,14 @@
EXPECT_FALSE(sock->IsConnected());
- scoped_refptr<net::LoadLog> log(new net::LoadLog(net::LoadLog::kUnbounded));
- rv = sock->Connect(&callback, log);
+ rv = sock->Connect(&callback);
EXPECT_TRUE(net::LogContainsBeginEvent(
- *log, 0, net::LoadLog::TYPE_SSL_CONNECT));
+ log.entries(), 5, net::NetLog::TYPE_SSL_CONNECT));
if (rv != net::OK) {
ASSERT_EQ(net::ERR_IO_PENDING, rv);
EXPECT_FALSE(sock->IsConnected());
EXPECT_FALSE(net::LogContainsEndEvent(
- *log, -1, net::LoadLog::TYPE_SSL_CONNECT));
+ log.entries(), -1, net::NetLog::TYPE_SSL_CONNECT));
rv = callback.WaitForResult();
EXPECT_EQ(net::ERR_CERT_DATE_INVALID, rv);
@@ -143,7 +145,7 @@
// leave it connected.
EXPECT_TRUE(net::LogContainsEndEvent(
- *log, -1, net::LoadLog::TYPE_SSL_CONNECT));
+ log.entries(), -1, net::NetLog::TYPE_SSL_CONNECT));
}
TEST_F(SSLClientSocketTest, ConnectMismatched) {
@@ -154,11 +156,12 @@
net::HostResolver::RequestInfo info(server_.kMismatchedHostName,
server_.kOKHTTPSPort);
- int rv = resolver_->Resolve(info, &addr, NULL, NULL, NULL);
+ int rv = resolver_->Resolve(info, &addr, NULL, NULL, net::BoundNetLog());
EXPECT_EQ(net::OK, rv);
- net::ClientSocket *transport = new net::TCPClientSocket(addr);
- rv = transport->Connect(&callback, NULL);
+ net::CapturingNetLog log(net::CapturingNetLog::kUnbounded);
+ net::ClientSocket* transport = new net::TCPClientSocket(addr, &log);
+ rv = transport->Connect(&callback);
if (rv == net::ERR_IO_PENDING)
rv = callback.WaitForResult();
EXPECT_EQ(net::OK, rv);
@@ -169,15 +172,15 @@
EXPECT_FALSE(sock->IsConnected());
- scoped_refptr<net::LoadLog> log(new net::LoadLog(net::LoadLog::kUnbounded));
- rv = sock->Connect(&callback, log);
+ rv = sock->Connect(&callback);
+
EXPECT_TRUE(net::LogContainsBeginEvent(
- *log, 0, net::LoadLog::TYPE_SSL_CONNECT));
+ log.entries(), 5, net::NetLog::TYPE_SSL_CONNECT));
if (rv != net::ERR_CERT_COMMON_NAME_INVALID) {
ASSERT_EQ(net::ERR_IO_PENDING, rv);
EXPECT_FALSE(sock->IsConnected());
EXPECT_FALSE(net::LogContainsEndEvent(
- *log, -1, net::LoadLog::TYPE_SSL_CONNECT));
+ log.entries(), -1, net::NetLog::TYPE_SSL_CONNECT));
rv = callback.WaitForResult();
EXPECT_EQ(net::ERR_CERT_COMMON_NAME_INVALID, rv);
@@ -188,7 +191,7 @@
// leave it connected.
EXPECT_TRUE(net::LogContainsEndEvent(
- *log, -1, net::LoadLog::TYPE_SSL_CONNECT));
+ log.entries(), -1, net::NetLog::TYPE_SSL_CONNECT));
}
// TODO(wtc): Add unit tests for IsConnectedAndIdle:
@@ -203,14 +206,14 @@
TestCompletionCallback callback;
net::HostResolver::RequestInfo info(server_.kHostName, server_.kOKHTTPSPort);
- int rv = resolver_->Resolve(info, &addr, &callback, NULL, NULL);
+ int rv = resolver_->Resolve(info, &addr, &callback, NULL, net::BoundNetLog());
EXPECT_EQ(net::ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(net::OK, rv);
- net::ClientSocket *transport = new net::TCPClientSocket(addr);
- rv = transport->Connect(&callback, NULL);
+ net::ClientSocket* transport = new net::TCPClientSocket(addr, NULL);
+ rv = transport->Connect(&callback);
if (rv == net::ERR_IO_PENDING)
rv = callback.WaitForResult();
EXPECT_EQ(net::OK, rv);
@@ -220,7 +223,7 @@
server_.kHostName,
kDefaultSSLConfig));
- rv = sock->Connect(&callback, NULL);
+ rv = sock->Connect(&callback);
if (rv != net::OK) {
ASSERT_EQ(net::ERR_IO_PENDING, rv);
@@ -265,14 +268,14 @@
TestCompletionCallback callback2; // Used for Write only.
net::HostResolver::RequestInfo info(server_.kHostName, server_.kOKHTTPSPort);
- int rv = resolver_->Resolve(info, &addr, &callback, NULL, NULL);
+ int rv = resolver_->Resolve(info, &addr, &callback, NULL, net::BoundNetLog());
EXPECT_EQ(net::ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(net::OK, rv);
- net::ClientSocket *transport = new net::TCPClientSocket(addr);
- rv = transport->Connect(&callback, NULL);
+ net::ClientSocket* transport = new net::TCPClientSocket(addr, NULL);
+ rv = transport->Connect(&callback);
if (rv == net::ERR_IO_PENDING)
rv = callback.WaitForResult();
EXPECT_EQ(net::OK, rv);
@@ -282,7 +285,7 @@
server_.kHostName,
kDefaultSSLConfig));
- rv = sock->Connect(&callback, NULL);
+ rv = sock->Connect(&callback);
if (rv != net::OK) {
ASSERT_EQ(net::ERR_IO_PENDING, rv);
@@ -327,11 +330,11 @@
TestCompletionCallback callback;
net::HostResolver::RequestInfo info(server_.kHostName, server_.kOKHTTPSPort);
- int rv = resolver_->Resolve(info, &addr, NULL, NULL, NULL);
+ int rv = resolver_->Resolve(info, &addr, NULL, NULL, net::BoundNetLog());
EXPECT_EQ(net::OK, rv);
- net::ClientSocket *transport = new net::TCPClientSocket(addr);
- rv = transport->Connect(&callback, NULL);
+ net::ClientSocket* transport = new net::TCPClientSocket(addr, NULL);
+ rv = transport->Connect(&callback);
if (rv == net::ERR_IO_PENDING)
rv = callback.WaitForResult();
EXPECT_EQ(net::OK, rv);
@@ -340,7 +343,7 @@
socket_factory_->CreateSSLClientSocket(transport,
server_.kHostName, kDefaultSSLConfig));
- rv = sock->Connect(&callback, NULL);
+ rv = sock->Connect(&callback);
if (rv != net::OK) {
ASSERT_EQ(net::ERR_IO_PENDING, rv);
@@ -381,11 +384,11 @@
TestCompletionCallback callback;
net::HostResolver::RequestInfo info(server_.kHostName, server_.kOKHTTPSPort);
- int rv = resolver_->Resolve(info, &addr, NULL, NULL, NULL);
+ int rv = resolver_->Resolve(info, &addr, NULL, NULL, net::BoundNetLog());
EXPECT_EQ(net::OK, rv);
- net::ClientSocket *transport = new net::TCPClientSocket(addr);
- rv = transport->Connect(&callback, NULL);
+ net::ClientSocket* transport = new net::TCPClientSocket(addr, NULL);
+ rv = transport->Connect(&callback);
if (rv == net::ERR_IO_PENDING)
rv = callback.WaitForResult();
EXPECT_EQ(net::OK, rv);
@@ -394,7 +397,7 @@
socket_factory_->CreateSSLClientSocket(transport,
server_.kHostName, kDefaultSSLConfig));
- rv = sock->Connect(&callback, NULL);
+ rv = sock->Connect(&callback);
if (rv != net::OK) {
ASSERT_EQ(net::ERR_IO_PENDING, rv);
@@ -424,3 +427,46 @@
EXPECT_GT(rv, 0);
}
+
+// Regression test for http://crbug.com/42538
+TEST_F(SSLClientSocketTest, PrematureApplicationData) {
+ net::AddressList addr;
+ TestCompletionCallback callback;
+
+ static const unsigned char application_data[] = {
+ 0x17, 0x03, 0x01, 0x00, 0x4a, 0x02, 0x00, 0x00, 0x46, 0x03, 0x01, 0x4b,
+ 0xc2, 0xf8, 0xb2, 0xc1, 0x56, 0x42, 0xb9, 0x57, 0x7f, 0xde, 0x87, 0x46,
+ 0xf7, 0xa3, 0x52, 0x42, 0x21, 0xf0, 0x13, 0x1c, 0x9c, 0x83, 0x88, 0xd6,
+ 0x93, 0x0c, 0xf6, 0x36, 0x30, 0x05, 0x7e, 0x20, 0xb5, 0xb5, 0x73, 0x36,
+ 0x53, 0x83, 0x0a, 0xfc, 0x17, 0x63, 0xbf, 0xa0, 0xe4, 0x42, 0x90, 0x0d,
+ 0x2f, 0x18, 0x6d, 0x20, 0xd8, 0x36, 0x3f, 0xfc, 0xe6, 0x01, 0xfa, 0x0f,
+ 0xa5, 0x75, 0x7f, 0x09, 0x00, 0x04, 0x00, 0x16, 0x03, 0x01, 0x11, 0x57,
+ 0x0b, 0x00, 0x11, 0x53, 0x00, 0x11, 0x50, 0x00, 0x06, 0x22, 0x30, 0x82,
+ 0x06, 0x1e, 0x30, 0x82, 0x05, 0x06, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02,
+ 0x0a
+ };
+
+ // All reads and writes complete synchronously (async=false).
+ net::MockRead data_reads[] = {
+ net::MockRead(false, reinterpret_cast<const char*>(application_data),
+ arraysize(application_data)),
+ net::MockRead(false, net::OK),
+ };
+
+ net::StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ NULL, 0);
+
+ net::ClientSocket* transport =
+ new net::MockTCPClientSocket(addr, NULL, &data);
+ int rv = transport->Connect(&callback);
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(net::OK, rv);
+
+ scoped_ptr<net::SSLClientSocket> sock(
+ socket_factory_->CreateSSLClientSocket(
+ transport, server_.kHostName, kDefaultSSLConfig));
+
+ rv = sock->Connect(&callback);
+ EXPECT_EQ(net::ERR_SSL_PROTOCOL_ERROR, rv);
+}
diff --git a/net/socket/ssl_client_socket_win.cc b/net/socket/ssl_client_socket_win.cc
index 6e8d86d..0484ebd 100644
--- a/net/socket/ssl_client_socket_win.cc
+++ b/net/socket/ssl_client_socket_win.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -14,10 +14,12 @@
#include "net/base/cert_verifier.h"
#include "net/base/connection_type_histograms.h"
#include "net/base/io_buffer.h"
-#include "net/base/load_log.h"
+#include "net/base/net_log.h"
#include "net/base/net_errors.h"
#include "net/base/ssl_cert_request_info.h"
+#include "net/base/ssl_connection_status_flags.h"
#include "net/base/ssl_info.h"
+#include "net/socket/client_socket_handle.h"
#pragma comment(lib, "secur32.lib")
@@ -58,6 +60,7 @@
case SEC_E_ALGORITHM_MISMATCH:
return ERR_SSL_VERSION_OR_CIPHER_MISMATCH;
case SEC_E_INVALID_HANDLE:
+ case SEC_E_INVALID_TOKEN:
return ERR_UNEXPECTED;
case SEC_E_OK:
return OK;
@@ -67,13 +70,6 @@
}
}
-// Returns true if the two CERT_CONTEXTs contain the same certificate.
-bool SameCert(PCCERT_CONTEXT a, PCCERT_CONTEXT b) {
- return a == b ||
- (a->cbCertEncoded == b->cbCertEncoded &&
- memcmp(a->pbCertEncoded, b->pbCertEncoded, b->cbCertEncoded) == 0);
-}
-
//-----------------------------------------------------------------------------
// A bitmask consisting of these bit flags encodes which versions of the SSL
@@ -298,7 +294,7 @@
// 64: >= SSL record trailer (16 or 20 have been observed)
static const int kRecvBufferSize = (5 + 16*1024 + 64);
-SSLClientSocketWin::SSLClientSocketWin(ClientSocket* transport_socket,
+SSLClientSocketWin::SSLClientSocketWin(ClientSocketHandle* transport_socket,
const std::string& hostname,
const SSLConfig& ssl_config)
: ALLOW_THIS_IN_INITIALIZER_LIST(
@@ -328,7 +324,8 @@
writing_first_token_(false),
ignore_ok_result_(false),
renegotiating_(false),
- need_more_data_(false) {
+ need_more_data_(false),
+ net_log_(transport_socket->socket()->NetLog()) {
memset(&stream_sizes_, 0, sizeof(stream_sizes_));
memset(in_buffers_, 0, sizeof(in_buffers_));
memset(&send_buffer_, 0, sizeof(send_buffer_));
@@ -340,6 +337,8 @@
}
void SSLClientSocketWin::GetSSLInfo(SSLInfo* ssl_info) {
+ ssl_info->Reset();
+
if (!server_cert_)
return;
@@ -354,6 +353,9 @@
// normalized.
ssl_info->security_bits = connection_info.dwCipherStrength;
}
+
+ if (ssl_config_.ssl3_fallback)
+ ssl_info->connection_status |= SSL_CONNECTION_SSL3_FALLBACK;
}
void SSLClientSocketWin::GetSSLCertRequestInfo(
@@ -376,6 +378,8 @@
// Client certificates of the user are in the "MY" system certificate store.
HCERTSTORE my_cert_store = CertOpenSystemStore(NULL, L"MY");
if (!my_cert_store) {
+ LOG(ERROR) << "Could not open the \"MY\" system certificate store: "
+ << GetLastError();
FreeContextBuffer(issuer_list.aIssuers);
return;
}
@@ -417,8 +421,10 @@
continue;
}
scoped_refptr<X509Certificate> cert = X509Certificate::CreateFromHandle(
- cert_context2, X509Certificate::SOURCE_LONE_CERT_IMPORT);
+ cert_context2, X509Certificate::SOURCE_LONE_CERT_IMPORT,
+ X509Certificate::OSCertHandles());
cert_request_info->client_certs.push_back(cert);
+ CertFreeCertificateContext(cert_context2);
}
FreeContextBuffer(issuer_list.aIssuers);
@@ -433,17 +439,16 @@
return kNextProtoUnsupported;
}
-int SSLClientSocketWin::Connect(CompletionCallback* callback,
- LoadLog* load_log) {
+int SSLClientSocketWin::Connect(CompletionCallback* callback) {
DCHECK(transport_.get());
DCHECK(next_state_ == STATE_NONE);
DCHECK(!user_connect_callback_);
- LoadLog::BeginEvent(load_log, LoadLog::TYPE_SSL_CONNECT);
+ net_log_.BeginEvent(NetLog::TYPE_SSL_CONNECT, NULL);
int rv = InitializeSSLContext();
if (rv != OK) {
- LoadLog::EndEvent(load_log, LoadLog::TYPE_SSL_CONNECT);
+ net_log_.EndEvent(NetLog::TYPE_SSL_CONNECT, NULL);
return rv;
}
@@ -452,9 +457,8 @@
rv = DoLoop(OK);
if (rv == ERR_IO_PENDING) {
user_connect_callback_ = callback;
- load_log_ = load_log;
} else {
- LoadLog::EndEvent(load_log, LoadLog::TYPE_SSL_CONNECT);
+ net_log_.EndEvent(NetLog::TYPE_SSL_CONNECT, NULL);
}
return rv;
}
@@ -526,7 +530,7 @@
// Shut down anything that may call us back.
verifier_.reset();
- transport_->Disconnect();
+ transport_->socket()->Disconnect();
if (send_buffer_.pvBuffer)
FreeSendBuffer();
@@ -552,7 +556,7 @@
// layer (HttpNetworkTransaction) needs to handle a persistent connection
// closed by the server when we send a request anyway, a false positive in
// exchange for simpler code is a good trade-off.
- return completed_handshake() && transport_->IsConnected();
+ return completed_handshake() && transport_->socket()->IsConnected();
}
bool SSLClientSocketWin::IsConnectedAndIdle() const {
@@ -561,13 +565,14 @@
// Strictly speaking, we should check if we have received the close_notify
// alert message from the server, and return false in that case. Although
// the close_notify alert message means EOF in the SSL layer, it is just
- // bytes to the transport layer below, so transport_->IsConnectedAndIdle()
- // returns the desired false when we receive close_notify.
- return completed_handshake() && transport_->IsConnectedAndIdle();
+ // bytes to the transport layer below, so
+ // transport_->socket()->IsConnectedAndIdle() returns the desired false
+ // when we receive close_notify.
+ return completed_handshake() && transport_->socket()->IsConnectedAndIdle();
}
-int SSLClientSocketWin::GetPeerName(struct sockaddr* name, socklen_t* namelen) {
- return transport_->GetPeerName(name, namelen);
+int SSLClientSocketWin::GetPeerAddress(AddressList* address) const {
+ return transport_->socket()->GetPeerAddress(address);
}
int SSLClientSocketWin::Read(IOBuffer* buf, int buf_len,
@@ -634,28 +639,23 @@
}
bool SSLClientSocketWin::SetReceiveBufferSize(int32 size) {
- return transport_->SetReceiveBufferSize(size);
+ return transport_->socket()->SetReceiveBufferSize(size);
}
bool SSLClientSocketWin::SetSendBufferSize(int32 size) {
- return transport_->SetSendBufferSize(size);
+ return transport_->socket()->SetSendBufferSize(size);
}
void SSLClientSocketWin::OnHandshakeIOComplete(int result) {
int rv = DoLoop(result);
- // The SSL handshake has some round trips. Any error, other than waiting
- // for IO, means that we've failed and need to notify the caller.
+ // The SSL handshake has some round trips. We need to notify the caller of
+ // success or any error, other than waiting for IO.
if (rv != ERR_IO_PENDING) {
- LoadLog::EndEvent(load_log_, LoadLog::TYPE_SSL_CONNECT);
- load_log_ = NULL;
-
- // If there is no connect callback available to call, it had better be
- // because we are renegotiating (which occurs because we are in the middle
- // of a Read when the renegotiation process starts). We need to inform the
- // caller of the SSL error, so we complete the Read here.
+ // If there is no connect callback available to call, we are renegotiating
+ // (which occurs because we are in the middle of a Read when the
+ // renegotiation process starts). So we complete the Read here.
if (!user_connect_callback_) {
- DCHECK(renegotiating_);
CompletionCallback* c = user_read_callback_;
user_read_callback_ = NULL;
user_read_buf_ = NULL;
@@ -663,6 +663,7 @@
c->Run(rv);
return;
}
+ net_log_.EndEvent(NetLog::TYPE_SSL_CONNECT, NULL);
CompletionCallback* c = user_connect_callback_;
user_connect_callback_ = NULL;
c->Run(rv);
@@ -757,8 +758,8 @@
DCHECK(!transport_read_buf_);
transport_read_buf_ = new IOBuffer(buf_len);
- return transport_->Read(transport_read_buf_, buf_len,
- &handshake_io_callback_);
+ return transport_->socket()->Read(transport_read_buf_, buf_len,
+ &handshake_io_callback_);
}
int SSLClientSocketWin::DoHandshakeReadComplete(int result) {
@@ -833,6 +834,12 @@
&out_flags,
&expiry);
+ if (isc_status_ == SEC_E_INVALID_TOKEN) {
+ // Peer sent us an SSL record type that's invalid during SSL handshake.
+ // TODO(wtc): move this to MapSecurityError after sufficient testing.
+ return ERR_SSL_PROTOCOL_ERROR;
+ }
+
if (send_buffer_.cbBuffer != 0 &&
(isc_status_ == SEC_E_OK ||
isc_status_ == SEC_I_CONTINUE_NEEDED ||
@@ -882,6 +889,13 @@
if (isc_status_ == SEC_I_INCOMPLETE_CREDENTIALS)
return ERR_SSL_CLIENT_AUTH_CERT_NEEDED;
+ if (isc_status_ == SEC_I_NO_RENEGOTIATION) {
+ // Received a no_renegotiation alert message. Although this is just a
+ // warning, SChannel doesn't seem to allow us to continue after this
+ // point, so we have to return an error. See http://crbug.com/36835.
+ return ERR_SSL_NO_RENEGOTIATION;
+ }
+
DCHECK(isc_status_ == SEC_I_CONTINUE_NEEDED);
if (in_buffers_[1].BufferType == SECBUFFER_EXTRA) {
memmove(recv_buffer_.get(),
@@ -911,8 +925,8 @@
transport_write_buf_ = new IOBuffer(buf_len);
memcpy(transport_write_buf_->data(), buf, buf_len);
- return transport_->Write(transport_write_buf_, buf_len,
- &handshake_io_callback_);
+ return transport_->socket()->Write(transport_write_buf_, buf_len,
+ &handshake_io_callback_);
}
int SSLClientSocketWin::DoHandshakeWriteComplete(int result) {
@@ -977,7 +991,8 @@
ssl_config_.IsAllowedBadCert(server_cert_))
result = OK;
- LogConnectionTypeMetrics(result >= 0);
+ if (result == OK)
+ LogConnectionTypeMetrics();
if (renegotiating_) {
DidCompleteRenegotiation();
return result;
@@ -1005,7 +1020,8 @@
DCHECK(!transport_read_buf_);
transport_read_buf_ = new IOBuffer(buf_len);
- rv = transport_->Read(transport_read_buf_, buf_len, &read_callback_);
+ rv = transport_->socket()->Read(transport_read_buf_, buf_len,
+ &read_callback_);
if (rv != ERR_IO_PENDING)
rv = DoPayloadReadComplete(rv);
if (rv <= 0)
@@ -1240,7 +1256,8 @@
transport_write_buf_ = new IOBuffer(buf_len);
memcpy(transport_write_buf_->data(), buf, buf_len);
- int rv = transport_->Write(transport_write_buf_, buf_len, &write_callback_);
+ int rv = transport_->socket()->Write(transport_write_buf_, buf_len,
+ &write_callback_);
if (rv != ERR_IO_PENDING)
rv = DoPayloadWriteComplete(rv);
return rv;
@@ -1276,7 +1293,8 @@
// The user had a read in progress, which was usurped by the renegotiation.
// Restart the read sequence.
next_state_ = STATE_COMPLETED_HANDSHAKE;
- DCHECK(result == OK);
+ if (result != OK)
+ return result;
return DoPayloadRead();
}
@@ -1296,39 +1314,43 @@
return MapSecurityError(status);
}
if (renegotiating_ &&
- SameCert(server_cert_->os_cert_handle(), server_cert_handle)) {
+ X509Certificate::IsSameOSCert(server_cert_->os_cert_handle(),
+ server_cert_handle)) {
// We already verified the server certificate. Either it is good or the
// user has accepted the certificate error.
- CertFreeCertificateContext(server_cert_handle);
DidCompleteRenegotiation();
} else {
server_cert_ = X509Certificate::CreateFromHandle(
- server_cert_handle, X509Certificate::SOURCE_FROM_NETWORK);
+ server_cert_handle, X509Certificate::SOURCE_FROM_NETWORK,
+ X509Certificate::OSCertHandles());
next_state_ = STATE_VERIFY_CERT;
}
+ CertFreeCertificateContext(server_cert_handle);
return OK;
}
// Called when a renegotiation is completed. |result| is the verification
// result of the server certificate received during renegotiation.
void SSLClientSocketWin::DidCompleteRenegotiation() {
+ DCHECK(!user_connect_callback_);
+ DCHECK(user_read_callback_);
renegotiating_ = false;
next_state_ = STATE_COMPLETED_RENEGOTIATION;
}
-void SSLClientSocketWin::LogConnectionTypeMetrics(bool success) const {
- UpdateConnectionTypeHistograms(CONNECTION_SSL, success);
+void SSLClientSocketWin::LogConnectionTypeMetrics() const {
+ UpdateConnectionTypeHistograms(CONNECTION_SSL);
if (server_cert_verify_result_.has_md5)
- UpdateConnectionTypeHistograms(CONNECTION_SSL_MD5, success);
+ UpdateConnectionTypeHistograms(CONNECTION_SSL_MD5);
if (server_cert_verify_result_.has_md2)
- UpdateConnectionTypeHistograms(CONNECTION_SSL_MD2, success);
+ UpdateConnectionTypeHistograms(CONNECTION_SSL_MD2);
if (server_cert_verify_result_.has_md4)
- UpdateConnectionTypeHistograms(CONNECTION_SSL_MD4, success);
+ UpdateConnectionTypeHistograms(CONNECTION_SSL_MD4);
if (server_cert_verify_result_.has_md5_ca)
- UpdateConnectionTypeHistograms(CONNECTION_SSL_MD5_CA, success);
+ UpdateConnectionTypeHistograms(CONNECTION_SSL_MD5_CA);
if (server_cert_verify_result_.has_md2_ca)
- UpdateConnectionTypeHistograms(CONNECTION_SSL_MD2_CA, success);
+ UpdateConnectionTypeHistograms(CONNECTION_SSL_MD2_CA);
}
void SSLClientSocketWin::FreeSendBuffer() {
diff --git a/net/socket/ssl_client_socket_win.h b/net/socket/ssl_client_socket_win.h
index c5d6cf7..b4a0bad 100644
--- a/net/socket/ssl_client_socket_win.h
+++ b/net/socket/ssl_client_socket_win.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -16,13 +16,15 @@
#include "base/scoped_ptr.h"
#include "net/base/cert_verify_result.h"
#include "net/base/completion_callback.h"
+#include "net/base/net_log.h"
#include "net/base/ssl_config_service.h"
#include "net/socket/ssl_client_socket.h"
namespace net {
class CertVerifier;
-class LoadLog;
+class ClientSocketHandle;
+class BoundNetLog;
// An SSL client socket implemented with the Windows Schannel.
class SSLClientSocketWin : public SSLClientSocket {
@@ -31,7 +33,7 @@
// The given hostname will be compared with the name(s) in the server's
// certificate during the SSL handshake. ssl_config specifies the SSL
// settings.
- SSLClientSocketWin(ClientSocket* transport_socket,
+ SSLClientSocketWin(ClientSocketHandle* transport_socket,
const std::string& hostname,
const SSLConfig& ssl_config);
~SSLClientSocketWin();
@@ -42,11 +44,12 @@
virtual NextProtoStatus GetNextProto(std::string* proto);
// ClientSocket methods:
- virtual int Connect(CompletionCallback* callback, LoadLog* load_log);
+ virtual int Connect(CompletionCallback* callback);
virtual void Disconnect();
virtual bool IsConnected() const;
virtual bool IsConnectedAndIdle() const;
- virtual int GetPeerName(struct sockaddr* name, socklen_t* namelen);
+ virtual int GetPeerAddress(AddressList* address) const;
+ virtual const BoundNetLog& NetLog() const { return net_log_; }
// Socket methods:
virtual int Read(IOBuffer* buf, int buf_len, CompletionCallback* callback);
@@ -86,7 +89,7 @@
int DidCallInitializeSecurityContext();
int DidCompleteHandshake();
void DidCompleteRenegotiation();
- void LogConnectionTypeMetrics(bool success) const;
+ void LogConnectionTypeMetrics() const;
void FreeSendBuffer();
// Internal callbacks as async operations complete.
@@ -94,7 +97,7 @@
CompletionCallbackImpl<SSLClientSocketWin> read_callback_;
CompletionCallbackImpl<SSLClientSocketWin> write_callback_;
- scoped_ptr<ClientSocket> transport_;
+ scoped_ptr<ClientSocketHandle> transport_;
std::string hostname_;
SSLConfig ssl_config_;
@@ -164,8 +167,6 @@
// state.
bool writing_first_token_;
- bool completed_handshake_;
-
// Only used in the STATE_HANDSHAKE_READ_COMPLETE and
// STATE_PAYLOAD_READ_COMPLETE states. True if a 'result' argument of OK
// should be ignored, to prevent it from being interpreted as EOF.
@@ -183,7 +184,7 @@
// True when the decrypter needs more data in order to decrypt.
bool need_more_data_;
- scoped_refptr<LoadLog> load_log_;
+ BoundNetLog net_log_;
};
} // namespace net
diff --git a/net/socket/tcp_client_socket_libevent.cc b/net/socket/tcp_client_socket_libevent.cc
index 2c1c73d..93593d9 100644
--- a/net/socket/tcp_client_socket_libevent.cc
+++ b/net/socket/tcp_client_socket_libevent.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -9,14 +9,20 @@
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
+#if defined(OS_POSIX)
+#include <netinet/in.h>
+#endif
#include "base/eintr_wrapper.h"
+#include "base/logging.h"
#include "base/message_loop.h"
+#include "base/stats_counters.h"
#include "base/string_util.h"
-#include "base/trace_event.h"
+#include "net/base/address_list_net_log_param.h"
#include "net/base/io_buffer.h"
-#include "net/base/load_log.h"
#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
#if defined(USE_SYSTEM_LIBEVENT)
#include <event.h>
#else
@@ -29,15 +35,6 @@
const int kInvalidSocket = -1;
-// Return 0 on success, -1 on failure.
-// Too small a function to bother putting in a library?
-int SetNonBlocking(int fd) {
- int flags = fcntl(fd, F_GETFL, 0);
- if (-1 == flags)
- return flags;
- return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
-}
-
// DisableNagle turns off buffering in the kernel. By default, TCP sockets will
// wait up to 200ms for more data to complete a packet before transmitting.
// After calling this function, the kernel will not wait. See TCP_NODELAY in
@@ -98,142 +95,180 @@
}
}
-// Given os_error, an errno from a connect() attempt, returns true if
-// connect() should be retried with another address.
-bool ShouldTryNextAddress(int os_error) {
- switch (os_error) {
- case EADDRNOTAVAIL:
- case EAFNOSUPPORT:
- case ECONNREFUSED:
- case ECONNRESET:
- case EACCES:
- case EPERM:
- case ENETUNREACH:
- case EHOSTUNREACH:
- case ENETDOWN:
- case ETIMEDOUT:
- return true;
- default:
- return false;
- }
-}
-
} // namespace
//-----------------------------------------------------------------------------
-TCPClientSocketLibevent::TCPClientSocketLibevent(const AddressList& addresses)
+TCPClientSocketLibevent::TCPClientSocketLibevent(const AddressList& addresses,
+ net::NetLog* net_log)
: socket_(kInvalidSocket),
addresses_(addresses),
- current_ai_(addresses_.head()),
- waiting_connect_(false),
+ current_ai_(NULL),
read_watcher_(this),
write_watcher_(this),
read_callback_(NULL),
- write_callback_(NULL) {
+ write_callback_(NULL),
+ next_connect_state_(CONNECT_STATE_NONE),
+ connect_os_error_(0),
+ net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_SOCKET)) {
+ net_log_.BeginEvent(NetLog::TYPE_SOCKET_ALIVE, NULL);
}
TCPClientSocketLibevent::~TCPClientSocketLibevent() {
Disconnect();
+ net_log_.EndEvent(NetLog::TYPE_SOCKET_ALIVE, NULL);
}
-int TCPClientSocketLibevent::Connect(CompletionCallback* callback,
- LoadLog* load_log) {
+int TCPClientSocketLibevent::Connect(CompletionCallback* callback) {
+ DCHECK(CalledOnValidThread());
+
// If already connected, then just return OK.
if (socket_ != kInvalidSocket)
return OK;
- DCHECK(!waiting_connect_);
- DCHECK(!load_log_);
+ static StatsCounter connects("tcp.connect");
+ connects.Increment();
- TRACE_EVENT_BEGIN("socket.connect", this, "");
+ DCHECK(!waiting_connect());
- LoadLog::BeginEvent(load_log, LoadLog::TYPE_TCP_CONNECT);
+ net_log_.BeginEvent(NetLog::TYPE_TCP_CONNECT,
+ new AddressListNetLogParam(addresses_));
- int rv = DoConnect();
+ // We will try to connect to each address in addresses_. Start with the
+ // first one in the list.
+ next_connect_state_ = CONNECT_STATE_CONNECT;
+ current_ai_ = addresses_.head();
+ int rv = DoConnectLoop(OK);
if (rv == ERR_IO_PENDING) {
// Synchronous operation not supported.
DCHECK(callback);
-
- load_log_ = load_log;
- waiting_connect_ = true;
write_callback_ = callback;
} else {
- TRACE_EVENT_END("socket.connect", this, "");
- LoadLog::EndEvent(load_log, LoadLog::TYPE_TCP_CONNECT);
+ LogConnectCompletion(rv);
}
return rv;
}
+int TCPClientSocketLibevent::DoConnectLoop(int result) {
+ DCHECK_NE(next_connect_state_, CONNECT_STATE_NONE);
+
+ int rv = result;
+ do {
+ ConnectState state = next_connect_state_;
+ next_connect_state_ = CONNECT_STATE_NONE;
+ switch (state) {
+ case CONNECT_STATE_CONNECT:
+ DCHECK_EQ(OK, rv);
+ rv = DoConnect();
+ break;
+ case CONNECT_STATE_CONNECT_COMPLETE:
+ rv = DoConnectComplete(rv);
+ break;
+ default:
+ LOG(DFATAL) << "bad state";
+ rv = ERR_UNEXPECTED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_connect_state_ != CONNECT_STATE_NONE);
+
+ return rv;
+}
+
int TCPClientSocketLibevent::DoConnect() {
- while (true) {
- DCHECK(current_ai_);
+ DCHECK(current_ai_);
- int rv = CreateSocket(current_ai_);
- if (rv != OK)
- return rv;
+ DCHECK_EQ(0, connect_os_error_);
- if (!HANDLE_EINTR(connect(socket_, current_ai_->ai_addr,
- static_cast<int>(current_ai_->ai_addrlen)))) {
- // Connected without waiting!
- return OK;
- }
+ net_log_.BeginEvent(NetLog::TYPE_TCP_CONNECT_ATTEMPT,
+ new NetLogStringParameter(
+ "address", NetAddressToStringWithPort(current_ai_)));
- int os_error = errno;
- if (os_error == EINPROGRESS)
- break;
+ next_connect_state_ = CONNECT_STATE_CONNECT_COMPLETE;
- close(socket_);
- socket_ = kInvalidSocket;
+ // Create a non-blocking socket.
+ connect_os_error_ = CreateSocket(current_ai_);
+ if (connect_os_error_)
+ return MapPosixError(connect_os_error_);
- if (current_ai_->ai_next && ShouldTryNextAddress(os_error)) {
- // connect() can fail synchronously for an address even on a
- // non-blocking socket. As an example, this can happen when there is
- // no route to the host. Retry using the next address in the list.
- current_ai_ = current_ai_->ai_next;
- } else {
- DLOG(INFO) << "connect failed: " << os_error;
- return MapConnectError(os_error);
- }
+ // Connect the socket.
+ if (!HANDLE_EINTR(connect(socket_, current_ai_->ai_addr,
+ static_cast<int>(current_ai_->ai_addrlen)))) {
+ // Connected without waiting!
+ return OK;
}
- // Initialize write_socket_watcher_ and link it to our MessagePump.
- // POLLOUT is set if the connection is established.
- // POLLIN is set if the connection fails.
+ // Check if the connect() failed synchronously.
+ connect_os_error_ = errno;
+ if (connect_os_error_ != EINPROGRESS)
+ return MapPosixError(connect_os_error_);
+
+ // Otherwise the connect() is going to complete asynchronously, so watch
+ // for its completion.
if (!MessageLoopForIO::current()->WatchFileDescriptor(
socket_, true, MessageLoopForIO::WATCH_WRITE, &write_socket_watcher_,
&write_watcher_)) {
- DLOG(INFO) << "WatchFileDescriptor failed: " << errno;
- close(socket_);
- socket_ = kInvalidSocket;
- return MapPosixError(errno);
+ connect_os_error_ = errno;
+ DLOG(INFO) << "WatchFileDescriptor failed: " << connect_os_error_;
+ return MapPosixError(connect_os_error_);
}
return ERR_IO_PENDING;
}
+int TCPClientSocketLibevent::DoConnectComplete(int result) {
+ // Log the end of this attempt (and any OS error it threw).
+ int os_error = connect_os_error_;
+ connect_os_error_ = 0;
+ scoped_refptr<NetLog::EventParameters> params;
+ if (result != OK)
+ params = new NetLogIntegerParameter("os_error", os_error);
+ net_log_.EndEvent(NetLog::TYPE_TCP_CONNECT_ATTEMPT, params);
+
+ write_socket_watcher_.StopWatchingFileDescriptor();
+
+ if (result == OK)
+ return OK; // Done!
+
+ // Close whatever partially connected socket we currently have.
+ DoDisconnect();
+
+ // Try to fall back to the next address in the list.
+ if (current_ai_->ai_next) {
+ next_connect_state_ = CONNECT_STATE_CONNECT;
+ current_ai_ = current_ai_->ai_next;
+ return OK;
+ }
+
+ // Otherwise there is nothing to fall back to, so give up.
+ return result;
+}
+
void TCPClientSocketLibevent::Disconnect() {
+ DCHECK(CalledOnValidThread());
+
+ DoDisconnect();
+ current_ai_ = NULL;
+}
+
+void TCPClientSocketLibevent::DoDisconnect() {
if (socket_ == kInvalidSocket)
return;
- TRACE_EVENT_INSTANT("socket.disconnect", this, "");
-
bool ok = read_socket_watcher_.StopWatchingFileDescriptor();
DCHECK(ok);
ok = write_socket_watcher_.StopWatchingFileDescriptor();
DCHECK(ok);
- close(socket_);
+ if (HANDLE_EINTR(close(socket_)) < 0)
+ PLOG(ERROR) << "close";
socket_ = kInvalidSocket;
- waiting_connect_ = false;
-
- // Reset for next time.
- current_ai_ = addresses_.head();
}
bool TCPClientSocketLibevent::IsConnected() const {
- if (socket_ == kInvalidSocket || waiting_connect_)
+ DCHECK(CalledOnValidThread());
+
+ if (socket_ == kInvalidSocket || waiting_connect())
return false;
// Check if connection is alive.
@@ -248,7 +283,9 @@
}
bool TCPClientSocketLibevent::IsConnectedAndIdle() const {
- if (socket_ == kInvalidSocket || waiting_connect_)
+ DCHECK(CalledOnValidThread());
+
+ if (socket_ == kInvalidSocket || waiting_connect())
return false;
// Check if connection is alive and we haven't received any data
@@ -266,17 +303,21 @@
int TCPClientSocketLibevent::Read(IOBuffer* buf,
int buf_len,
CompletionCallback* callback) {
+ DCHECK(CalledOnValidThread());
DCHECK_NE(kInvalidSocket, socket_);
- DCHECK(!waiting_connect_);
+ DCHECK(!waiting_connect());
DCHECK(!read_callback_);
// Synchronous operation not supported
DCHECK(callback);
DCHECK_GT(buf_len, 0);
- TRACE_EVENT_BEGIN("socket.read", this, "");
int nread = HANDLE_EINTR(read(socket_, buf->data(), buf_len));
if (nread >= 0) {
- TRACE_EVENT_END("socket.read", this, StringPrintf("%d bytes", nread));
+ static StatsCounter read_bytes("tcp.read_bytes");
+ read_bytes.Add(nread);
+
+ net_log_.AddEvent(NetLog::TYPE_SOCKET_BYTES_RECEIVED,
+ new NetLogIntegerParameter("num_bytes", nread));
return nread;
}
if (errno != EAGAIN && errno != EWOULDBLOCK) {
@@ -300,17 +341,20 @@
int TCPClientSocketLibevent::Write(IOBuffer* buf,
int buf_len,
CompletionCallback* callback) {
+ DCHECK(CalledOnValidThread());
DCHECK_NE(kInvalidSocket, socket_);
- DCHECK(!waiting_connect_);
+ DCHECK(!waiting_connect());
DCHECK(!write_callback_);
// Synchronous operation not supported
DCHECK(callback);
DCHECK_GT(buf_len, 0);
- TRACE_EVENT_BEGIN("socket.write", this, "");
int nwrite = HANDLE_EINTR(write(socket_, buf->data(), buf_len));
if (nwrite >= 0) {
- TRACE_EVENT_END("socket.write", this, StringPrintf("%d bytes", nwrite));
+ static StatsCounter write_bytes("tcp.write_bytes");
+ write_bytes.Add(nwrite);
+ net_log_.AddEvent(NetLog::TYPE_SOCKET_BYTES_SENT,
+ new NetLogIntegerParameter("num_bytes", nwrite));
return nwrite;
}
if (errno != EAGAIN && errno != EWOULDBLOCK)
@@ -323,7 +367,6 @@
return MapPosixError(errno);
}
-
write_buf_ = buf;
write_buf_len_ = buf_len;
write_callback_ = callback;
@@ -331,6 +374,7 @@
}
bool TCPClientSocketLibevent::SetReceiveBufferSize(int32 size) {
+ DCHECK(CalledOnValidThread());
int rv = setsockopt(socket_, SOL_SOCKET, SO_RCVBUF,
reinterpret_cast<const char*>(&size),
sizeof(size));
@@ -339,6 +383,7 @@
}
bool TCPClientSocketLibevent::SetSendBufferSize(int32 size) {
+ DCHECK(CalledOnValidThread());
int rv = setsockopt(socket_, SOL_SOCKET, SO_SNDBUF,
reinterpret_cast<const char*>(&size),
sizeof(size));
@@ -350,10 +395,10 @@
int TCPClientSocketLibevent::CreateSocket(const addrinfo* ai) {
socket_ = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (socket_ == kInvalidSocket)
- return MapPosixError(errno);
+ return errno;
if (SetNonBlocking(socket_)) {
- const int err = MapPosixError(errno);
+ const int err = errno;
close(socket_);
socket_ = kInvalidSocket;
return err;
@@ -363,7 +408,14 @@
// tcp_client_socket_win.cc after searching for "NODELAY".
DisableNagle(socket_); // If DisableNagle fails, we don't care.
- return OK;
+ return 0;
+}
+
+void TCPClientSocketLibevent::LogConnectCompletion(int net_error) {
+ scoped_refptr<NetLog::EventParameters> params;
+ if (net_error != OK)
+ params = new NetLogIntegerParameter("net_error", net_error);
+ net_log_.EndEvent(NetLog::TYPE_TCP_CONNECT, params);
}
void TCPClientSocketLibevent::DoReadCallback(int rv) {
@@ -387,39 +439,25 @@
}
void TCPClientSocketLibevent::DidCompleteConnect() {
- int result = ERR_UNEXPECTED;
+ DCHECK_EQ(next_connect_state_, CONNECT_STATE_CONNECT_COMPLETE);
- // Check to see if connect succeeded
+ // Get the error that connect() completed with.
int os_error = 0;
socklen_t len = sizeof(os_error);
if (getsockopt(socket_, SOL_SOCKET, SO_ERROR, &os_error, &len) < 0)
os_error = errno;
+ // TODO(eroman): Is this check really necessary?
if (os_error == EINPROGRESS || os_error == EALREADY) {
NOTREACHED(); // This indicates a bug in libevent or our code.
- result = ERR_IO_PENDING;
- } else if (current_ai_->ai_next && ShouldTryNextAddress(os_error)) {
- // This address failed, try next one in list.
- const addrinfo* next = current_ai_->ai_next;
- Disconnect();
- current_ai_ = next;
- scoped_refptr<LoadLog> load_log;
- load_log.swap(load_log_);
- TRACE_EVENT_END("socket.connect", this, "");
- LoadLog::EndEvent(load_log, LoadLog::TYPE_TCP_CONNECT);
- result = Connect(write_callback_, load_log);
- } else {
- result = MapConnectError(os_error);
- bool ok = write_socket_watcher_.StopWatchingFileDescriptor();
- DCHECK(ok);
- waiting_connect_ = false;
- TRACE_EVENT_END("socket.connect", this, "");
- LoadLog::EndEvent(load_log_, LoadLog::TYPE_TCP_CONNECT);
- load_log_ = NULL;
+ return;
}
- if (result != ERR_IO_PENDING) {
- DoWriteCallback(result);
+ connect_os_error_ = os_error;
+ int rv = DoConnectLoop(MapConnectError(os_error));
+ if (rv != ERR_IO_PENDING) {
+ LogConnectCompletion(rv);
+ DoWriteCallback(rv);
}
}
@@ -430,9 +468,9 @@
int result;
if (bytes_transferred >= 0) {
- TRACE_EVENT_END("socket.read", this,
- StringPrintf("%d bytes", bytes_transferred));
result = bytes_transferred;
+ net_log_.AddEvent(NetLog::TYPE_SOCKET_BYTES_RECEIVED,
+ new NetLogIntegerParameter("num_bytes", result));
} else {
result = MapPosixError(errno);
}
@@ -454,8 +492,8 @@
int result;
if (bytes_transferred >= 0) {
result = bytes_transferred;
- TRACE_EVENT_END("socket.write", this,
- StringPrintf("%d bytes", bytes_transferred));
+ net_log_.AddEvent(NetLog::TYPE_SOCKET_BYTES_SENT,
+ new NetLogIntegerParameter("num_bytes", result));
} else {
result = MapPosixError(errno);
}
@@ -468,9 +506,13 @@
}
}
-int TCPClientSocketLibevent::GetPeerName(struct sockaddr* name,
- socklen_t* namelen) {
- return ::getpeername(socket_, name, namelen);
+int TCPClientSocketLibevent::GetPeerAddress(AddressList* address) const {
+ DCHECK(CalledOnValidThread());
+ DCHECK(address);
+ if (!current_ai_)
+ return ERR_UNEXPECTED;
+ address->Copy(current_ai_, false);
+ return OK;
}
} // namespace net
diff --git a/net/socket/tcp_client_socket_libevent.h b/net/socket/tcp_client_socket_libevent.h
index b054805..f0bed43 100644
--- a/net/socket/tcp_client_socket_libevent.h
+++ b/net/socket/tcp_client_socket_libevent.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -6,34 +6,38 @@
#define NET_SOCKET_TCP_CLIENT_SOCKET_LIBEVENT_H_
#include "base/message_loop.h"
+#include "base/non_thread_safe.h"
#include "base/ref_counted.h"
#include "base/scoped_ptr.h"
#include "net/base/address_list.h"
#include "net/base/completion_callback.h"
+#include "net/base/net_log.h"
#include "net/socket/client_socket.h"
struct event; // From libevent
namespace net {
-class LoadLog;
+class BoundNetLog;
// A client socket that uses TCP as the transport layer.
-class TCPClientSocketLibevent : public ClientSocket {
+class TCPClientSocketLibevent : public ClientSocket, NonThreadSafe {
public:
// The IP address(es) and port number to connect to. The TCP socket will try
// each IP address in the list until it succeeds in establishing a
// connection.
- explicit TCPClientSocketLibevent(const AddressList& addresses);
+ explicit TCPClientSocketLibevent(const AddressList& addresses,
+ net::NetLog* net_log);
virtual ~TCPClientSocketLibevent();
// ClientSocket methods:
- virtual int Connect(CompletionCallback* callback, LoadLog* load_log);
+ virtual int Connect(CompletionCallback* callback);
virtual void Disconnect();
virtual bool IsConnected() const;
virtual bool IsConnectedAndIdle() const;
- virtual int GetPeerName(struct sockaddr* name, socklen_t* namelen);
+ virtual int GetPeerAddress(AddressList* address) const;
+ virtual const BoundNetLog& NetLog() const { return net_log_; }
// Socket methods:
// Multiple outstanding requests are not supported.
@@ -44,6 +48,13 @@
virtual bool SetSendBufferSize(int32 size);
private:
+ // State machine for connecting the socket.
+ enum ConnectState {
+ CONNECT_STATE_CONNECT,
+ CONNECT_STATE_CONNECT_COMPLETE,
+ CONNECT_STATE_NONE,
+ };
+
class ReadWatcher : public MessageLoopForIO::Watcher {
public:
explicit ReadWatcher(TCPClientSocketLibevent* socket) : socket_(socket) {}
@@ -72,7 +83,7 @@
virtual void OnFileCanReadWithoutBlocking(int /* fd */) {}
virtual void OnFileCanWriteWithoutBlocking(int /* fd */) {
- if (socket_->waiting_connect_) {
+ if (socket_->waiting_connect()) {
socket_->DidCompleteConnect();
} else if (socket_->write_callback_) {
socket_->DidCompleteWrite();
@@ -85,8 +96,14 @@
DISALLOW_COPY_AND_ASSIGN(WriteWatcher);
};
- // Performs the actual connect(). Returns a net error code.
+ // State machine used by Connect().
+ int DoConnectLoop(int result);
int DoConnect();
+ int DoConnectComplete(int result);
+
+ // Helper used by Disconnect(), which disconnects minus the logging and
+ // resetting of current_ai_.
+ void DoDisconnect();
void DoReadCallback(int rv);
void DoWriteCallback(int rv);
@@ -94,8 +111,17 @@
void DidCompleteWrite();
void DidCompleteConnect();
+ // Returns true if a Connect() is in progress.
+ bool waiting_connect() const {
+ return next_connect_state_ != CONNECT_STATE_NONE;
+ }
+
+ // Returns the OS error code (or 0 on success).
int CreateSocket(const struct addrinfo* ai);
+ // Helper to add a TCP_CONNECT (end) event to the NetLog.
+ void LogConnectCompletion(int net_error);
+
int socket_;
// The list of addresses we should try in order to establish a connection.
@@ -104,9 +130,6 @@
// Where we are in above list, or NULL if all addrinfos have been tried.
const struct addrinfo* current_ai_;
- // Whether we're currently waiting for connect() to complete
- bool waiting_connect_;
-
// The socket's libevent wrappers
MessageLoopForIO::FileDescriptorWatcher read_socket_watcher_;
MessageLoopForIO::FileDescriptorWatcher write_socket_watcher_;
@@ -129,7 +152,13 @@
// External callback; called when write is complete.
CompletionCallback* write_callback_;
- scoped_refptr<LoadLog> load_log_;
+ // The next state for the Connect() state machine.
+ ConnectState next_connect_state_;
+
+ // The OS error that CONNECT_STATE_CONNECT last completed with.
+ int connect_os_error_;
+
+ BoundNetLog net_log_;
DISALLOW_COPY_AND_ASSIGN(TCPClientSocketLibevent);
};
diff --git a/net/socket/tcp_client_socket_pool.cc b/net/socket/tcp_client_socket_pool.cc
index 3ea6700..2caa3fb 100644
--- a/net/socket/tcp_client_socket_pool.cc
+++ b/net/socket/tcp_client_socket_pool.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -9,7 +9,7 @@
#include "base/message_loop.h"
#include "base/string_util.h"
#include "base/time.h"
-#include "net/base/load_log.h"
+#include "net/base/net_log.h"
#include "net/base/net_errors.h"
#include "net/socket/client_socket_factory.h"
#include "net/socket/client_socket_handle.h"
@@ -20,6 +20,23 @@
namespace net {
+TCPSocketParams::TCPSocketParams(const HostPortPair& host_port_pair,
+ RequestPriority priority, const GURL& referrer,
+ bool disable_resolver_cache)
+ : destination_(host_port_pair.host, host_port_pair.port) {
+ Initialize(priority, referrer, disable_resolver_cache);
+}
+
+// TODO(willchan): Update all unittests so we don't need this.
+TCPSocketParams::TCPSocketParams(const std::string& host, int port,
+ RequestPriority priority, const GURL& referrer,
+ bool disable_resolver_cache)
+ : destination_(host, port) {
+ Initialize(priority, referrer, disable_resolver_cache);
+}
+
+TCPSocketParams::~TCPSocketParams() {}
+
// TCPConnectJobs will time out after this many seconds. Note this is the total
// time, including both host resolution and TCP connect() times.
//
@@ -29,18 +46,19 @@
// timeout. Even worse, the per-connect timeout threshold varies greatly
// between systems (anywhere from 20 seconds to 190 seconds).
// See comment #12 at http://crbug.com/23364 for specifics.
-static const int kTCPConnectJobTimeoutInSeconds = 240; // 4 minutes.
+static const int kTCPConnectJobTimeoutInSeconds = 240; // 4 minutes.
TCPConnectJob::TCPConnectJob(
const std::string& group_name,
- const HostResolver::RequestInfo& resolve_info,
+ const scoped_refptr<TCPSocketParams>& params,
base::TimeDelta timeout_duration,
ClientSocketFactory* client_socket_factory,
HostResolver* host_resolver,
Delegate* delegate,
- LoadLog* load_log)
- : ConnectJob(group_name, timeout_duration, delegate, load_log),
- resolve_info_(resolve_info),
+ NetLog* net_log)
+ : ConnectJob(group_name, timeout_duration, delegate,
+ BoundNetLog::Make(net_log, NetLog::SOURCE_CONNECT_JOB)),
+ params_(params),
client_socket_factory_(client_socket_factory),
ALLOW_THIS_IN_INITIALIZER_LIST(
callback_(this,
@@ -112,7 +130,8 @@
int TCPConnectJob::DoResolveHost() {
next_state_ = kStateResolveHostComplete;
- return resolver_.Resolve(resolve_info_, &addresses_, &callback_, load_log());
+ return resolver_.Resolve(params_->destination(), &addresses_, &callback_,
+ net_log());
}
int TCPConnectJob::DoResolveHostComplete(int result) {
@@ -123,9 +142,10 @@
int TCPConnectJob::DoTCPConnect() {
next_state_ = kStateTCPConnectComplete;
- set_socket(client_socket_factory_->CreateTCPClientSocket(addresses_));
+ set_socket(client_socket_factory_->CreateTCPClientSocket(
+ addresses_, net_log().net_log()));
connect_start_time_ = base::TimeTicks::Now();
- return socket()->Connect(&callback_, load_log());
+ return socket()->Connect(&callback_);
}
int TCPConnectJob::DoTCPConnectComplete(int result) {
@@ -158,60 +178,75 @@
ConnectJob* TCPClientSocketPool::TCPConnectJobFactory::NewConnectJob(
const std::string& group_name,
const PoolBase::Request& request,
- ConnectJob::Delegate* delegate,
- LoadLog* load_log) const {
- return new TCPConnectJob(
- group_name, request.params(),
- base::TimeDelta::FromSeconds(kTCPConnectJobTimeoutInSeconds),
- client_socket_factory_, host_resolver_, delegate, load_log);
+ ConnectJob::Delegate* delegate) const {
+ return new TCPConnectJob(group_name, request.params(), ConnectionTimeout(),
+ client_socket_factory_, host_resolver_, delegate,
+ net_log_);
+}
+
+base::TimeDelta
+ TCPClientSocketPool::TCPConnectJobFactory::ConnectionTimeout() const {
+ return base::TimeDelta::FromSeconds(kTCPConnectJobTimeoutInSeconds);
}
TCPClientSocketPool::TCPClientSocketPool(
int max_sockets,
int max_sockets_per_group,
+ const scoped_refptr<ClientSocketPoolHistograms>& histograms,
HostResolver* host_resolver,
ClientSocketFactory* client_socket_factory,
- NetworkChangeNotifier* network_change_notifier)
- : base_(max_sockets, max_sockets_per_group,
- base::TimeDelta::FromSeconds(kUnusedIdleSocketTimeout),
+ NetLog* net_log)
+ : base_(max_sockets, max_sockets_per_group, histograms,
+ base::TimeDelta::FromSeconds(
+ ClientSocketPool::unused_idle_socket_timeout()),
base::TimeDelta::FromSeconds(kUsedIdleSocketTimeout),
- new TCPConnectJobFactory(client_socket_factory, host_resolver),
- network_change_notifier) {}
+ new TCPConnectJobFactory(client_socket_factory,
+ host_resolver, net_log)) {
+ base_.EnableBackupJobs();
+}
TCPClientSocketPool::~TCPClientSocketPool() {}
int TCPClientSocketPool::RequestSocket(
const std::string& group_name,
- const void* resolve_info,
+ const void* params,
RequestPriority priority,
ClientSocketHandle* handle,
CompletionCallback* callback,
- LoadLog* load_log) {
- const HostResolver::RequestInfo* casted_resolve_info =
- static_cast<const HostResolver::RequestInfo*>(resolve_info);
+ const BoundNetLog& net_log) {
+ const scoped_refptr<TCPSocketParams>* casted_params =
+ static_cast<const scoped_refptr<TCPSocketParams>*>(params);
- if (LoadLog::IsUnbounded(load_log)) {
- LoadLog::AddString(
- load_log,
- StringPrintf("Requested TCP socket to: %s [port %d]",
- casted_resolve_info->hostname().c_str(),
- casted_resolve_info->port()));
+ if (net_log.HasListener()) {
+ // TODO(eroman): Split out the host and port parameters.
+ net_log.AddEvent(
+ NetLog::TYPE_TCP_CLIENT_SOCKET_POOL_REQUESTED_SOCKET,
+ new NetLogStringParameter(
+ "host_and_port",
+ StringPrintf("%s [port %d]",
+ casted_params->get()->destination().hostname().c_str(),
+ casted_params->get()->destination().port())));
}
- return base_.RequestSocket(
- group_name, *casted_resolve_info, priority, handle, callback, load_log);
+ return base_.RequestSocket(group_name, *casted_params, priority, handle,
+ callback, net_log);
}
void TCPClientSocketPool::CancelRequest(
const std::string& group_name,
- const ClientSocketHandle* handle) {
+ ClientSocketHandle* handle) {
base_.CancelRequest(group_name, handle);
}
void TCPClientSocketPool::ReleaseSocket(
const std::string& group_name,
- ClientSocket* socket) {
- base_.ReleaseSocket(group_name, socket);
+ ClientSocket* socket,
+ int id) {
+ base_.ReleaseSocket(group_name, socket, id);
+}
+
+void TCPClientSocketPool::Flush() {
+ base_.Flush();
}
void TCPClientSocketPool::CloseIdleSockets() {
diff --git a/net/socket/tcp_client_socket_pool.h b/net/socket/tcp_client_socket_pool.h
index fb1a93b..1ee50d2 100644
--- a/net/socket/tcp_client_socket_pool.h
+++ b/net/socket/tcp_client_socket_pool.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -12,25 +12,58 @@
#include "base/scoped_ptr.h"
#include "base/time.h"
#include "base/timer.h"
+#include "net/base/host_port_pair.h"
#include "net/base/host_resolver.h"
#include "net/socket/client_socket_pool_base.h"
+#include "net/socket/client_socket_pool_histograms.h"
#include "net/socket/client_socket_pool.h"
namespace net {
class ClientSocketFactory;
+class TCPSocketParams : public base::RefCounted<TCPSocketParams> {
+ public:
+ TCPSocketParams(const HostPortPair& host_port_pair, RequestPriority priority,
+ const GURL& referrer, bool disable_resolver_cache);
+
+ // TODO(willchan): Update all unittests so we don't need this.
+ TCPSocketParams(const std::string& host, int port, RequestPriority priority,
+ const GURL& referrer, bool disable_resolver_cache);
+
+ const HostResolver::RequestInfo& destination() const { return destination_; }
+
+ private:
+ friend class base::RefCounted<TCPSocketParams>;
+ ~TCPSocketParams();
+
+ void Initialize(RequestPriority priority, const GURL& referrer,
+ bool disable_resolver_cache) {
+ // The referrer is used by the DNS prefetch system to correlate resolutions
+ // with the page that triggered them. It doesn't impact the actual addresses
+ // that we resolve to.
+ destination_.set_referrer(referrer);
+ destination_.set_priority(priority);
+ if (disable_resolver_cache)
+ destination_.set_allow_cached_response(false);
+ }
+
+ HostResolver::RequestInfo destination_;
+
+ DISALLOW_COPY_AND_ASSIGN(TCPSocketParams);
+};
+
// TCPConnectJob handles the host resolution necessary for socket creation
// and the tcp connect.
class TCPConnectJob : public ConnectJob {
public:
TCPConnectJob(const std::string& group_name,
- const HostResolver::RequestInfo& resolve_info,
+ const scoped_refptr<TCPSocketParams>& params,
base::TimeDelta timeout_duration,
ClientSocketFactory* client_socket_factory,
HostResolver* host_resolver,
Delegate* delegate,
- LoadLog* load_log);
+ NetLog* net_log);
virtual ~TCPConnectJob();
// ConnectJob methods.
@@ -60,7 +93,7 @@
int DoTCPConnect();
int DoTCPConnectComplete(int result);
- const HostResolver::RequestInfo resolve_info_;
+ scoped_refptr<TCPSocketParams> params_;
ClientSocketFactory* const client_socket_factory_;
CompletionCallbackImpl<TCPConnectJob> callback_;
SingleRequestHostResolver resolver_;
@@ -81,9 +114,10 @@
TCPClientSocketPool(
int max_sockets,
int max_sockets_per_group,
+ const scoped_refptr<ClientSocketPoolHistograms>& histograms,
HostResolver* host_resolver,
ClientSocketFactory* client_socket_factory,
- NetworkChangeNotifier* network_change_notifier);
+ NetLog* net_log);
// ClientSocketPool methods:
@@ -92,13 +126,16 @@
RequestPriority priority,
ClientSocketHandle* handle,
CompletionCallback* callback,
- LoadLog* load_log);
+ const BoundNetLog& net_log);
virtual void CancelRequest(const std::string& group_name,
- const ClientSocketHandle* handle);
+ ClientSocketHandle* handle);
virtual void ReleaseSocket(const std::string& group_name,
- ClientSocket* socket);
+ ClientSocket* socket,
+ int id);
+
+ virtual void Flush();
virtual void CloseIdleSockets();
@@ -111,19 +148,29 @@
virtual LoadState GetLoadState(const std::string& group_name,
const ClientSocketHandle* handle) const;
+ virtual base::TimeDelta ConnectionTimeout() const {
+ return base_.ConnectionTimeout();
+ }
+
+ virtual scoped_refptr<ClientSocketPoolHistograms> histograms() const {
+ return base_.histograms();
+ }
+
protected:
virtual ~TCPClientSocketPool();
private:
- typedef ClientSocketPoolBase<HostResolver::RequestInfo> PoolBase;
+ typedef ClientSocketPoolBase<TCPSocketParams> PoolBase;
class TCPConnectJobFactory
: public PoolBase::ConnectJobFactory {
public:
TCPConnectJobFactory(ClientSocketFactory* client_socket_factory,
- HostResolver* host_resolver)
+ HostResolver* host_resolver,
+ NetLog* net_log)
: client_socket_factory_(client_socket_factory),
- host_resolver_(host_resolver) {}
+ host_resolver_(host_resolver),
+ net_log_(net_log) {}
virtual ~TCPConnectJobFactory() {}
@@ -132,12 +179,14 @@
virtual ConnectJob* NewConnectJob(
const std::string& group_name,
const PoolBase::Request& request,
- ConnectJob::Delegate* delegate,
- LoadLog* load_log) const;
+ ConnectJob::Delegate* delegate) const;
+
+ virtual base::TimeDelta ConnectionTimeout() const;
private:
ClientSocketFactory* const client_socket_factory_;
const scoped_refptr<HostResolver> host_resolver_;
+ NetLog* net_log_;
DISALLOW_COPY_AND_ASSIGN(TCPConnectJobFactory);
};
@@ -147,7 +196,7 @@
DISALLOW_COPY_AND_ASSIGN(TCPClientSocketPool);
};
-REGISTER_SOCKET_PARAMS_FOR_POOL(TCPClientSocketPool, HostResolver::RequestInfo)
+REGISTER_SOCKET_PARAMS_FOR_POOL(TCPClientSocketPool, TCPSocketParams);
} // namespace net
diff --git a/net/socket/tcp_client_socket_pool_unittest.cc b/net/socket/tcp_client_socket_pool_unittest.cc
index 719d280..9516f9f 100644
--- a/net/socket/tcp_client_socket_pool_unittest.cc
+++ b/net/socket/tcp_client_socket_pool_unittest.cc
@@ -1,18 +1,19 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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 "net/socket/tcp_client_socket_pool.h"
+#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/message_loop.h"
#include "net/base/mock_host_resolver.h"
-#include "net/base/mock_network_change_notifier.h"
#include "net/base/net_errors.h"
#include "net/base/test_completion_callback.h"
#include "net/socket/client_socket.h"
#include "net/socket/client_socket_factory.h"
#include "net/socket/client_socket_handle.h"
+#include "net/socket/client_socket_pool_histograms.h"
#include "net/socket/socket_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -29,7 +30,7 @@
MockClientSocket() : connected_(false) {}
// ClientSocket methods:
- virtual int Connect(CompletionCallback* callback, LoadLog* /* load_log */) {
+ virtual int Connect(CompletionCallback* callback) {
connected_ = true;
return OK;
}
@@ -42,9 +43,12 @@
virtual bool IsConnectedAndIdle() const {
return connected_;
}
- virtual int GetPeerName(struct sockaddr* name, socklen_t* namelen) {
+ virtual int GetPeerAddress(AddressList* address) const {
return ERR_UNEXPECTED;
}
+ virtual const BoundNetLog& NetLog() const {
+ return net_log_;
+ }
// Socket methods:
virtual int Read(IOBuffer* buf, int buf_len,
@@ -60,6 +64,7 @@
private:
bool connected_;
+ BoundNetLog net_log_;
};
class MockFailingClientSocket : public ClientSocket {
@@ -67,7 +72,7 @@
MockFailingClientSocket() {}
// ClientSocket methods:
- virtual int Connect(CompletionCallback* callback, LoadLog* /* load_log */) {
+ virtual int Connect(CompletionCallback* callback) {
return ERR_CONNECTION_FAILED;
}
@@ -79,9 +84,12 @@
virtual bool IsConnectedAndIdle() const {
return false;
}
- virtual int GetPeerName(struct sockaddr* name, socklen_t* namelen) {
+ virtual int GetPeerAddress(AddressList* address) const {
return ERR_UNEXPECTED;
}
+ virtual const BoundNetLog& NetLog() const {
+ return net_log_;
+ }
// Socket methods:
virtual int Read(IOBuffer* buf, int buf_len,
@@ -95,21 +103,30 @@
}
virtual bool SetReceiveBufferSize(int32 size) { return true; }
virtual bool SetSendBufferSize(int32 size) { return true; }
+
+ private:
+ BoundNetLog net_log_;
};
class MockPendingClientSocket : public ClientSocket {
public:
- MockPendingClientSocket(bool should_connect)
+ // |should_connect| indicates whether the socket should successfully complete
+ // or fail.
+ // |should_stall| indicates that this socket should never connect.
+ // |delay_ms| is the delay, in milliseconds, before simulating a connect.
+ MockPendingClientSocket(bool should_connect, bool should_stall, int delay_ms)
: method_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)),
should_connect_(should_connect),
+ should_stall_(should_stall),
+ delay_ms_(delay_ms),
is_connected_(false) {}
// ClientSocket methods:
- virtual int Connect(CompletionCallback* callback, LoadLog* /* load_log */) {
- MessageLoop::current()->PostTask(
+ virtual int Connect(CompletionCallback* callback) {
+ MessageLoop::current()->PostDelayedTask(
FROM_HERE,
method_factory_.NewRunnableMethod(
- &MockPendingClientSocket::DoCallback, callback));
+ &MockPendingClientSocket::DoCallback, callback), delay_ms_);
return ERR_IO_PENDING;
}
@@ -121,9 +138,12 @@
virtual bool IsConnectedAndIdle() const {
return is_connected_;
}
- virtual int GetPeerName(struct sockaddr* name, socklen_t* namelen) {
+ virtual int GetPeerAddress(AddressList* address) const{
return ERR_UNEXPECTED;
}
+ virtual const BoundNetLog& NetLog() const {
+ return net_log_;
+ }
// Socket methods:
virtual int Read(IOBuffer* buf, int buf_len,
@@ -140,6 +160,9 @@
private:
void DoCallback(CompletionCallback* callback) {
+ if (should_stall_)
+ return;
+
if (should_connect_) {
is_connected_ = true;
callback->Run(OK);
@@ -151,7 +174,10 @@
ScopedRunnableMethodFactory<MockPendingClientSocket> method_factory_;
bool should_connect_;
+ bool should_stall_;
+ int delay_ms_;
bool is_connected_;
+ BoundNetLog net_log_;
};
class MockClientSocketFactory : public ClientSocketFactory {
@@ -161,22 +187,41 @@
MOCK_FAILING_CLIENT_SOCKET,
MOCK_PENDING_CLIENT_SOCKET,
MOCK_PENDING_FAILING_CLIENT_SOCKET,
+ // A delayed socket will pause before connecting through the message loop.
+ MOCK_DELAYED_CLIENT_SOCKET,
+ // A stalled socket that never connects at all.
+ MOCK_STALLED_CLIENT_SOCKET,
};
MockClientSocketFactory()
- : allocation_count_(0), client_socket_type_(MOCK_CLIENT_SOCKET) {}
+ : allocation_count_(0), client_socket_type_(MOCK_CLIENT_SOCKET),
+ client_socket_types_(NULL), client_socket_index_(0),
+ client_socket_index_max_(0) {}
- virtual ClientSocket* CreateTCPClientSocket(const AddressList& addresses) {
+ virtual ClientSocket* CreateTCPClientSocket(const AddressList& addresses,
+ NetLog* /* net_log */) {
allocation_count_++;
- switch (client_socket_type_) {
+
+ ClientSocketType type = client_socket_type_;
+ if (client_socket_types_ &&
+ client_socket_index_ < client_socket_index_max_) {
+ type = client_socket_types_[client_socket_index_++];
+ }
+
+ switch (type) {
case MOCK_CLIENT_SOCKET:
return new MockClientSocket();
case MOCK_FAILING_CLIENT_SOCKET:
return new MockFailingClientSocket();
case MOCK_PENDING_CLIENT_SOCKET:
- return new MockPendingClientSocket(true);
+ return new MockPendingClientSocket(true, false, 0);
case MOCK_PENDING_FAILING_CLIENT_SOCKET:
- return new MockPendingClientSocket(false);
+ return new MockPendingClientSocket(false, false, 0);
+ case MOCK_DELAYED_CLIENT_SOCKET:
+ return new MockPendingClientSocket(true, false,
+ ClientSocketPool::kMaxConnectRetryIntervalMs);
+ case MOCK_STALLED_CLIENT_SOCKET:
+ return new MockPendingClientSocket(true, true, 0);
default:
NOTREACHED();
return new MockClientSocket();
@@ -184,7 +229,7 @@
}
virtual SSLClientSocket* CreateSSLClientSocket(
- ClientSocket* transport_socket,
+ ClientSocketHandle* transport_socket,
const std::string& hostname,
const SSLConfig& ssl_config) {
NOTIMPLEMENTED();
@@ -193,44 +238,62 @@
int allocation_count() const { return allocation_count_; }
+ // Set the default ClientSocketType.
void set_client_socket_type(ClientSocketType type) {
client_socket_type_ = type;
}
+ // Set a list of ClientSocketTypes to be used.
+ void set_client_socket_types(ClientSocketType* type_list, int num_types) {
+ DCHECK_GT(num_types, 0);
+ client_socket_types_ = type_list;
+ client_socket_index_ = 0;
+ client_socket_index_max_ = num_types;
+ }
+
private:
int allocation_count_;
ClientSocketType client_socket_type_;
+ ClientSocketType* client_socket_types_;
+ int client_socket_index_;
+ int client_socket_index_max_;
};
class TCPClientSocketPoolTest : public ClientSocketPoolTest {
protected:
TCPClientSocketPoolTest()
- : ignored_request_info_("ignored", 80),
+ : params_(new TCPSocketParams(HostPortPair("www.google.com", 80),
+ kDefaultPriority, GURL(), false)),
+ low_params_(new TCPSocketParams(HostPortPair("www.google.com", 80),
+ LOW, GURL(), false)),
+ histograms_(new ClientSocketPoolHistograms("TCPUnitTest")),
host_resolver_(new MockHostResolver),
pool_(new TCPClientSocketPool(kMaxSockets,
kMaxSocketsPerGroup,
+ histograms_,
host_resolver_,
&client_socket_factory_,
- ¬ifier_)) {
+ NULL)) {
}
int StartRequest(const std::string& group_name, RequestPriority priority) {
- return StartRequestUsingPool(
- pool_.get(), group_name, priority, ignored_request_info_);
+ scoped_refptr<TCPSocketParams> params = new TCPSocketParams(
+ HostPortPair("www.google.com", 80), MEDIUM, GURL(), false);
+ return StartRequestUsingPool(pool_, group_name, priority, params);
}
- HostResolver::RequestInfo ignored_request_info_;
+ scoped_refptr<TCPSocketParams> params_;
+ scoped_refptr<TCPSocketParams> low_params_;
+ scoped_refptr<ClientSocketPoolHistograms> histograms_;
scoped_refptr<MockHostResolver> host_resolver_;
MockClientSocketFactory client_socket_factory_;
- MockNetworkChangeNotifier notifier_;
scoped_refptr<TCPClientSocketPool> pool_;
};
TEST_F(TCPClientSocketPoolTest, Basic) {
TestCompletionCallback callback;
ClientSocketHandle handle;
- HostResolver::RequestInfo info("www.google.com", 80);
- int rv = handle.Init("a", info, LOW, &callback, pool_.get(), NULL);
+ int rv = handle.Init("a", low_params_, LOW, &callback, pool_, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_FALSE(handle.is_initialized());
EXPECT_FALSE(handle.socket());
@@ -245,10 +308,11 @@
TEST_F(TCPClientSocketPoolTest, InitHostResolutionFailure) {
host_resolver_->rules()->AddSimulatedFailure("unresolvable.host.name");
TestSocketRequest req(&request_order_, &completion_count_);
- HostResolver::RequestInfo info("unresolvable.host.name", 80);
+ scoped_refptr<TCPSocketParams> dest = new TCPSocketParams(
+ "unresolvable.host.name", 80, kDefaultPriority, GURL(), false);
EXPECT_EQ(ERR_IO_PENDING,
- req.handle()->Init(
- "a", info, kDefaultPriority, &req, pool_.get(), NULL));
+ req.handle()->Init("a", dest, kDefaultPriority, &req, pool_,
+ BoundNetLog()));
EXPECT_EQ(ERR_NAME_NOT_RESOLVED, req.WaitForResult());
}
@@ -256,17 +320,15 @@
client_socket_factory_.set_client_socket_type(
MockClientSocketFactory::MOCK_FAILING_CLIENT_SOCKET);
TestSocketRequest req(&request_order_, &completion_count_);
- HostResolver::RequestInfo info("a", 80);
- EXPECT_EQ(ERR_IO_PENDING,
- req.handle()->Init(
- "a", info, kDefaultPriority, &req, pool_.get(), NULL));
+ EXPECT_EQ(ERR_IO_PENDING, req.handle()->Init("a", params_, kDefaultPriority,
+ &req, pool_, BoundNetLog()));
EXPECT_EQ(ERR_CONNECTION_FAILED, req.WaitForResult());
// Make the host resolutions complete synchronously this time.
host_resolver_->set_synchronous_mode(true);
- EXPECT_EQ(ERR_CONNECTION_FAILED,
- req.handle()->Init(
- "a", info, kDefaultPriority, &req, pool_.get(), NULL));
+ EXPECT_EQ(ERR_CONNECTION_FAILED, req.handle()->Init("a", params_,
+ kDefaultPriority, &req,
+ pool_, BoundNetLog()));
}
TEST_F(TCPClientSocketPoolTest, PendingRequests) {
@@ -370,10 +432,8 @@
// ClientSocketPool which will crash if the group was not cleared properly.
TEST_F(TCPClientSocketPoolTest, CancelRequestClearGroup) {
TestSocketRequest req(&request_order_, &completion_count_);
- HostResolver::RequestInfo info("www.google.com", 80);
- EXPECT_EQ(ERR_IO_PENDING,
- req.handle()->Init(
- "a", info, kDefaultPriority, &req, pool_.get(), NULL));
+ EXPECT_EQ(ERR_IO_PENDING, req.handle()->Init("a", params_, kDefaultPriority,
+ &req, pool_, BoundNetLog()));
req.handle()->Reset();
// There is a race condition here. If the worker pool doesn't post the task
@@ -389,13 +449,10 @@
TestSocketRequest req(&request_order_, &completion_count_);
TestSocketRequest req2(&request_order_, &completion_count_);
- HostResolver::RequestInfo info("www.google.com", 80);
- EXPECT_EQ(ERR_IO_PENDING,
- req.handle()->Init(
- "a", info, kDefaultPriority, &req, pool_.get(), NULL));
- EXPECT_EQ(ERR_IO_PENDING,
- req2.handle()->Init(
- "a", info, kDefaultPriority, &req2, pool_.get(), NULL));
+ EXPECT_EQ(ERR_IO_PENDING, req.handle()->Init("a", params_, kDefaultPriority,
+ &req, pool_, BoundNetLog()));
+ EXPECT_EQ(ERR_IO_PENDING, req2.handle()->Init("a", params_, kDefaultPriority,
+ &req2, pool_, BoundNetLog()));
req.handle()->Reset();
@@ -410,17 +467,14 @@
TestCompletionCallback callback;
TestSocketRequest req(&request_order_, &completion_count_);
- HostResolver::RequestInfo info("www.google.com", 80);
- EXPECT_EQ(ERR_IO_PENDING,
- handle.Init(
- "a", info, kDefaultPriority, &callback, pool_.get(), NULL));
+ EXPECT_EQ(ERR_IO_PENDING, handle.Init("a", params_, kDefaultPriority,
+ &callback, pool_, BoundNetLog()));
handle.Reset();
TestCompletionCallback callback2;
- EXPECT_EQ(ERR_IO_PENDING,
- handle.Init(
- "a", info, kDefaultPriority, &callback2, pool_.get(), NULL));
+ EXPECT_EQ(ERR_IO_PENDING, handle.Init("a", params_, kDefaultPriority,
+ &callback2, pool_, BoundNetLog()));
host_resolver_->set_synchronous_mode(true);
// At this point, handle has two ConnectingSockets out for it. Due to the
@@ -518,9 +572,10 @@
MessageLoop::current()->RunAllPending();
}
within_callback_ = true;
- int rv = handle_->Init(
- "a", HostResolver::RequestInfo("www.google.com", 80), LOWEST,
- this, pool_.get(), NULL);
+ scoped_refptr<TCPSocketParams> dest = new TCPSocketParams(
+ HostPortPair("www.google.com", 80), LOWEST, GURL(), false);
+ int rv = handle_->Init("a", dest, LOWEST, this, pool_,
+ BoundNetLog());
EXPECT_EQ(OK, rv);
}
}
@@ -539,9 +594,10 @@
TEST_F(TCPClientSocketPoolTest, RequestTwice) {
ClientSocketHandle handle;
RequestSocketCallback callback(&handle, pool_.get());
- int rv = handle.Init(
- "a", HostResolver::RequestInfo("www.google.com", 80), LOWEST,
- &callback, pool_.get(), NULL);
+ scoped_refptr<TCPSocketParams> dest = new TCPSocketParams(
+ HostPortPair("www.google.com", 80), LOWEST, GURL(), false);
+ int rv = handle.Init("a", dest, LOWEST, &callback, pool_,
+ BoundNetLog());
ASSERT_EQ(ERR_IO_PENDING, rv);
// The callback is going to request "www.google.com". We want it to complete
@@ -603,8 +659,7 @@
TEST_F(TCPClientSocketPoolTest, ResetIdleSocketsOnIPAddressChange) {
TestCompletionCallback callback;
ClientSocketHandle handle;
- HostResolver::RequestInfo info("www.google.com", 80);
- int rv = handle.Init("a", info, LOW, &callback, pool_.get(), NULL);
+ int rv = handle.Init("a", low_params_, LOW, &callback, pool_, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_FALSE(handle.is_initialized());
EXPECT_FALSE(handle.socket());
@@ -622,10 +677,207 @@
EXPECT_EQ(1, pool_->IdleSocketCount());
// After an IP address change, we should have 0 idle sockets.
- notifier_.NotifyIPAddressChange();
+ NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
+ MessageLoop::current()->RunAllPending(); // Notification happens async.
+
EXPECT_EQ(0, pool_->IdleSocketCount());
}
+TEST_F(TCPClientSocketPoolTest, BackupSocketConnect) {
+ // Case 1 tests the first socket stalling, and the backup connecting.
+ MockClientSocketFactory::ClientSocketType case1_types[] = {
+ // The first socket will not connect.
+ MockClientSocketFactory::MOCK_STALLED_CLIENT_SOCKET,
+ // The second socket will connect more quickly.
+ MockClientSocketFactory::MOCK_CLIENT_SOCKET
+ };
+
+ // Case 2 tests the first socket being slow, so that we start the
+ // second connect, but the second connect stalls, and we still
+ // complete the first.
+ MockClientSocketFactory::ClientSocketType case2_types[] = {
+ // The first socket will connect, although delayed.
+ MockClientSocketFactory::MOCK_DELAYED_CLIENT_SOCKET,
+ // The second socket will not connect.
+ MockClientSocketFactory::MOCK_STALLED_CLIENT_SOCKET
+ };
+
+ MockClientSocketFactory::ClientSocketType* cases[2] = {
+ case1_types,
+ case2_types
+ };
+
+ for (size_t index = 0; index < arraysize(cases); ++index) {
+ client_socket_factory_.set_client_socket_types(cases[index], 2);
+
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+
+ TestCompletionCallback callback;
+ ClientSocketHandle handle;
+ int rv = handle.Init("b", low_params_, LOW, &callback, pool_,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ // Create the first socket, set the timer.
+ MessageLoop::current()->RunAllPending();
+
+ // Wait for the backup socket timer to fire.
+ PlatformThread::Sleep(ClientSocketPool::kMaxConnectRetryIntervalMs * 2);
+
+ // Let the appropriate socket connect.
+ MessageLoop::current()->RunAllPending();
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+
+ // One socket is stalled, the other is active.
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+ handle.Reset();
+
+ // Close all pending connect jobs and existing sockets.
+ pool_->Flush();
+
+ // TODO(mbelshe): Flush has a bug. UIt doesn't clean out pending connect
+ // jobs. When they complete, they become idle sockets. For now, continue
+ // to replace the pool_. But we really need to fix Flush().
+ pool_ = new TCPClientSocketPool(kMaxSockets, kMaxSocketsPerGroup,
+ histograms_, host_resolver_, &client_socket_factory_, NULL);
+ }
+}
+
+// Test the case where a socket took long enough to start the creation
+// of the backup socket, but then we cancelled the request after that.
+TEST_F(TCPClientSocketPoolTest, BackupSocketCancel) {
+ client_socket_factory_.set_client_socket_type(
+ MockClientSocketFactory::MOCK_STALLED_CLIENT_SOCKET);
+
+ enum { CANCEL_BEFORE_WAIT, CANCEL_AFTER_WAIT };
+
+ for (int index = CANCEL_BEFORE_WAIT; index < CANCEL_AFTER_WAIT; ++index) {
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+
+ TestCompletionCallback callback;
+ ClientSocketHandle handle;
+ int rv = handle.Init("c", low_params_, LOW, &callback, pool_,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ // Create the first socket, set the timer.
+ MessageLoop::current()->RunAllPending();
+
+ if (index == CANCEL_AFTER_WAIT) {
+ // Wait for the backup socket timer to fire.
+ PlatformThread::Sleep(ClientSocketPool::kMaxConnectRetryIntervalMs);
+ }
+
+ // Let the appropriate socket connect.
+ MessageLoop::current()->RunAllPending();
+
+ handle.Reset();
+
+ EXPECT_FALSE(callback.have_result());
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ // One socket is stalled, the other is active.
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+ }
+}
+
+// Test the case where a socket took long enough to start the creation
+// of the backup socket and never completes, and then the backup
+// connection fails.
+TEST_F(TCPClientSocketPoolTest, BackupSocketFailAfterStall) {
+ MockClientSocketFactory::ClientSocketType case_types[] = {
+ // The first socket will not connect.
+ MockClientSocketFactory::MOCK_STALLED_CLIENT_SOCKET,
+ // The second socket will fail immediately.
+ MockClientSocketFactory::MOCK_FAILING_CLIENT_SOCKET
+ };
+
+ client_socket_factory_.set_client_socket_types(case_types, 2);
+
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+
+ TestCompletionCallback callback;
+ ClientSocketHandle handle;
+ int rv = handle.Init("b", low_params_, LOW, &callback, pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ // Create the first socket, set the timer.
+ MessageLoop::current()->RunAllPending();
+
+ // Wait for the backup socket timer to fire.
+ PlatformThread::Sleep(ClientSocketPool::kMaxConnectRetryIntervalMs);
+
+ // Let the second connect be synchronous. Otherwise, the emulated
+ // host resolution takes an extra trip through the message loop.
+ host_resolver_->set_synchronous_mode(true);
+
+ // Let the appropriate socket connect.
+ MessageLoop::current()->RunAllPending();
+
+ EXPECT_EQ(ERR_CONNECTION_FAILED, callback.WaitForResult());
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+ handle.Reset();
+
+ // Reset for the next case.
+ host_resolver_->set_synchronous_mode(false);
+}
+
+// Test the case where a socket took long enough to start the creation
+// of the backup socket and eventually completes, but the backup socket
+// fails.
+TEST_F(TCPClientSocketPoolTest, BackupSocketFailAfterDelay) {
+ MockClientSocketFactory::ClientSocketType case_types[] = {
+ // The first socket will connect, although delayed.
+ MockClientSocketFactory::MOCK_DELAYED_CLIENT_SOCKET,
+ // The second socket will not connect.
+ MockClientSocketFactory::MOCK_FAILING_CLIENT_SOCKET
+ };
+
+ client_socket_factory_.set_client_socket_types(case_types, 2);
+
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+
+ TestCompletionCallback callback;
+ ClientSocketHandle handle;
+ int rv = handle.Init("b", low_params_, LOW, &callback, pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ // Create the first socket, set the timer.
+ MessageLoop::current()->RunAllPending();
+
+ // Wait for the backup socket timer to fire.
+ PlatformThread::Sleep(ClientSocketPool::kMaxConnectRetryIntervalMs);
+
+ // Let the second connect be synchronous. Otherwise, the emulated
+ // host resolution takes an extra trip through the message loop.
+ host_resolver_->set_synchronous_mode(true);
+
+ // Let the appropriate socket connect.
+ MessageLoop::current()->RunAllPending();
+
+ EXPECT_EQ(ERR_CONNECTION_FAILED, callback.WaitForResult());
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ handle.Reset();
+
+ // Reset for the next case.
+ host_resolver_->set_synchronous_mode(false);
+}
+
} // namespace
} // namespace net
diff --git a/net/socket/tcp_client_socket_unittest.cc b/net/socket/tcp_client_socket_unittest.cc
index e1e08ab..638e887 100644
--- a/net/socket/tcp_client_socket_unittest.cc
+++ b/net/socket/tcp_client_socket_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -9,8 +9,8 @@
#include "net/base/host_resolver.h"
#include "net/base/io_buffer.h"
#include "net/base/listen_socket.h"
-#include "net/base/load_log.h"
-#include "net/base/load_log_unittest.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
#include "net/base/net_errors.h"
#include "net/base/test_completion_callback.h"
#include "net/base/winsock_init.h"
@@ -26,14 +26,14 @@
class TCPClientSocketTest
: public PlatformTest, public ListenSocket::ListenSocketDelegate {
public:
- TCPClientSocketTest() {
+ TCPClientSocketTest() : net_log_(CapturingNetLog::kUnbounded) {
}
// Implement ListenSocketDelegate methods
virtual void DidAccept(ListenSocket* server, ListenSocket* connection) {
connected_sock_ = connection;
}
- virtual void DidRead(ListenSocket*, const std::string& s) {
+ virtual void DidRead(ListenSocket*, const char* str, int len) {
// TODO(dkegel): this might not be long enough to tickle some bugs.
connected_sock_->Send(kServerReply,
arraysize(kServerReply) - 1,
@@ -59,6 +59,7 @@
protected:
int listen_port_;
+ CapturingNetLog net_log_;
scoped_ptr<TCPClientSocket> sock_;
private:
@@ -88,33 +89,32 @@
listen_port_ = port;
AddressList addr;
- scoped_refptr<HostResolver> resolver(CreateSystemHostResolver(NULL));
+ scoped_refptr<HostResolver> resolver(
+ CreateSystemHostResolver(HostResolver::kDefaultParallelism));
HostResolver::RequestInfo info("localhost", listen_port_);
- int rv = resolver->Resolve(info, &addr, NULL, NULL, NULL);
- CHECK(rv == OK);
- sock_.reset(new TCPClientSocket(addr));
+ int rv = resolver->Resolve(info, &addr, NULL, NULL, BoundNetLog());
+ CHECK_EQ(rv, OK);
+ sock_.reset(new TCPClientSocket(addr, &net_log_));
}
TEST_F(TCPClientSocketTest, Connect) {
TestCompletionCallback callback;
EXPECT_FALSE(sock_->IsConnected());
- scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
- int rv = sock_->Connect(&callback, log);
+ int rv = sock_->Connect(&callback);
EXPECT_TRUE(net::LogContainsBeginEvent(
- *log, 0, net::LoadLog::TYPE_TCP_CONNECT));
+ net_log_.entries(), 0, net::NetLog::TYPE_SOCKET_ALIVE));
+ EXPECT_TRUE(net::LogContainsBeginEvent(
+ net_log_.entries(), 1, net::NetLog::TYPE_TCP_CONNECT));
if (rv != OK) {
ASSERT_EQ(rv, ERR_IO_PENDING);
- EXPECT_FALSE(net::LogContainsEndEvent(
- *log, -1, net::LoadLog::TYPE_TCP_CONNECT));
-
rv = callback.WaitForResult();
EXPECT_EQ(rv, OK);
}
EXPECT_TRUE(sock_->IsConnected());
EXPECT_TRUE(net::LogContainsEndEvent(
- *log, -1, net::LoadLog::TYPE_TCP_CONNECT));
+ net_log_.entries(), -1, net::NetLog::TYPE_TCP_CONNECT));
sock_->Disconnect();
EXPECT_FALSE(sock_->IsConnected());
@@ -126,7 +126,7 @@
TEST_F(TCPClientSocketTest, Read) {
TestCompletionCallback callback;
- int rv = sock_->Connect(&callback, NULL);
+ int rv = sock_->Connect(&callback);
if (rv != OK) {
ASSERT_EQ(rv, ERR_IO_PENDING);
@@ -171,7 +171,7 @@
TEST_F(TCPClientSocketTest, Read_SmallChunks) {
TestCompletionCallback callback;
- int rv = sock_->Connect(&callback, NULL);
+ int rv = sock_->Connect(&callback);
if (rv != OK) {
ASSERT_EQ(rv, ERR_IO_PENDING);
@@ -216,7 +216,7 @@
TEST_F(TCPClientSocketTest, Read_Interrupted) {
TestCompletionCallback callback;
- int rv = sock_->Connect(&callback, NULL);
+ int rv = sock_->Connect(&callback);
if (rv != OK) {
ASSERT_EQ(ERR_IO_PENDING, rv);
@@ -250,7 +250,7 @@
TEST_F(TCPClientSocketTest, DISABLED_FullDuplex_ReadFirst) {
TestCompletionCallback callback;
- int rv = sock_->Connect(&callback, NULL);
+ int rv = sock_->Connect(&callback);
if (rv != OK) {
ASSERT_EQ(rv, ERR_IO_PENDING);
@@ -292,7 +292,7 @@
TEST_F(TCPClientSocketTest, DISABLED_FullDuplex_WriteFirst) {
TestCompletionCallback callback;
- int rv = sock_->Connect(&callback, NULL);
+ int rv = sock_->Connect(&callback);
if (rv != OK) {
ASSERT_EQ(ERR_IO_PENDING, rv);
diff --git a/net/socket/tcp_client_socket_win.cc b/net/socket/tcp_client_socket_win.cc
index 3046659..3faf41b 100644
--- a/net/socket/tcp_client_socket_win.cc
+++ b/net/socket/tcp_client_socket_win.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -10,11 +10,13 @@
#include "base/stats_counters.h"
#include "base/string_util.h"
#include "base/sys_info.h"
-#include "base/trace_event.h"
+#include "net/base/address_list_net_log_param.h"
#include "net/base/connection_type_histograms.h"
#include "net/base/io_buffer.h"
-#include "net/base/load_log.h"
#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "net/base/sys_addrinfo.h"
#include "net/base/winsock_init.h"
namespace net {
@@ -34,7 +36,7 @@
DWORD wait_rv = WaitForSingleObject(hEvent, 0);
if (wait_rv == WAIT_TIMEOUT)
return false; // The event object is not signaled.
- CHECK(wait_rv == WAIT_OBJECT_0);
+ CHECK_EQ(WAIT_OBJECT_0, wait_rv);
BOOL ok = WSAResetEvent(hEvent);
CHECK(ok);
return true;
@@ -97,24 +99,6 @@
}
}
-// Given os_error, a WSAGetLastError() error code from a connect() attempt,
-// returns true if connect() should be retried with another address.
-bool ShouldTryNextAddress(int os_error) {
- switch (os_error) {
- case WSAEADDRNOTAVAIL:
- case WSAEAFNOSUPPORT:
- case WSAECONNREFUSED:
- case WSAEACCES:
- case WSAENETUNREACH:
- case WSAEHOSTUNREACH:
- case WSAENETDOWN:
- case WSAETIMEDOUT:
- return true;
- default:
- return false;
- }
-}
-
} // namespace
//-----------------------------------------------------------------------------
@@ -146,6 +130,7 @@
WSABUF write_buffer_;
scoped_refptr<IOBuffer> read_iobuffer_;
scoped_refptr<IOBuffer> write_iobuffer_;
+ int write_buffer_length_;
// Throttle the read size based on our current slow start state.
// Returns the throttled read size.
@@ -211,7 +196,8 @@
TCPClientSocketWin::Core::Core(
TCPClientSocketWin* socket)
- : socket_(socket),
+ : write_buffer_length_(0),
+ socket_(socket),
ALLOW_THIS_IN_INITIALIZER_LIST(reader_(this)),
ALLOW_THIS_IN_INITIALIZER_LIST(writer_(this)),
slow_start_throttle_(kInitialSlowStartThrottle) {
@@ -248,7 +234,7 @@
HANDLE object) {
DCHECK_EQ(object, core_->read_overlapped_.hEvent);
if (core_->socket_) {
- if (core_->socket_->waiting_connect_) {
+ if (core_->socket_->waiting_connect()) {
core_->socket_->DidCompleteConnect();
} else {
core_->socket_->DidCompleteRead();
@@ -269,62 +255,96 @@
//-----------------------------------------------------------------------------
-TCPClientSocketWin::TCPClientSocketWin(const AddressList& addresses)
+TCPClientSocketWin::TCPClientSocketWin(const AddressList& addresses,
+ net::NetLog* net_log)
: socket_(INVALID_SOCKET),
addresses_(addresses),
- current_ai_(addresses_.head()),
- waiting_connect_(false),
+ current_ai_(NULL),
waiting_read_(false),
waiting_write_(false),
read_callback_(NULL),
- write_callback_(NULL) {
+ write_callback_(NULL),
+ next_connect_state_(CONNECT_STATE_NONE),
+ connect_os_error_(0),
+ net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_SOCKET)) {
+ net_log_.BeginEvent(NetLog::TYPE_SOCKET_ALIVE, NULL);
EnsureWinsockInit();
}
TCPClientSocketWin::~TCPClientSocketWin() {
Disconnect();
+ net_log_.EndEvent(NetLog::TYPE_SOCKET_ALIVE, NULL);
}
-int TCPClientSocketWin::Connect(CompletionCallback* callback,
- LoadLog* load_log) {
+int TCPClientSocketWin::Connect(CompletionCallback* callback) {
+ DCHECK(CalledOnValidThread());
+
// If already connected, then just return OK.
if (socket_ != INVALID_SOCKET)
return OK;
- DCHECK(!load_log_);
-
static StatsCounter connects("tcp.connect");
connects.Increment();
- TRACE_EVENT_BEGIN("socket.connect", this, "");
+ net_log_.BeginEvent(NetLog::TYPE_TCP_CONNECT,
+ new AddressListNetLogParam(addresses_));
- LoadLog::BeginEvent(load_log, LoadLog::TYPE_TCP_CONNECT);
+ // We will try to connect to each address in addresses_. Start with the
+ // first one in the list.
+ next_connect_state_ = CONNECT_STATE_CONNECT;
+ current_ai_ = addresses_.head();
- int rv = DoConnect();
-
+ int rv = DoConnectLoop(OK);
if (rv == ERR_IO_PENDING) {
// Synchronous operation not supported.
DCHECK(callback);
-
- load_log_ = load_log;
- waiting_connect_ = true;
read_callback_ = callback;
} else {
- TRACE_EVENT_END("socket.connect", this, "");
- LoadLog::EndEvent(load_log, LoadLog::TYPE_TCP_CONNECT);
- UpdateConnectionTypeHistograms(CONNECTION_ANY, rv >= 0);
+ LogConnectCompletion(rv);
}
return rv;
}
+int TCPClientSocketWin::DoConnectLoop(int result) {
+ DCHECK_NE(next_connect_state_, CONNECT_STATE_NONE);
+
+ int rv = result;
+ do {
+ ConnectState state = next_connect_state_;
+ next_connect_state_ = CONNECT_STATE_NONE;
+ switch (state) {
+ case CONNECT_STATE_CONNECT:
+ DCHECK_EQ(OK, rv);
+ rv = DoConnect();
+ break;
+ case CONNECT_STATE_CONNECT_COMPLETE:
+ rv = DoConnectComplete(rv);
+ break;
+ default:
+ LOG(DFATAL) << "bad state";
+ rv = ERR_UNEXPECTED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_connect_state_ != CONNECT_STATE_NONE);
+
+ return rv;
+}
+
int TCPClientSocketWin::DoConnect() {
const struct addrinfo* ai = current_ai_;
DCHECK(ai);
+ DCHECK_EQ(0, connect_os_error_);
- int rv = CreateSocket(ai);
- if (rv != OK)
- return rv;
+ net_log_.BeginEvent(NetLog::TYPE_TCP_CONNECT_ATTEMPT,
+ new NetLogStringParameter(
+ "address", NetAddressToStringWithPort(current_ai_)));
+
+ next_connect_state_ = CONNECT_STATE_CONNECT_COMPLETE;
+
+ connect_os_error_ = CreateSocket(ai);
+ if (connect_os_error_ != 0)
+ return MapWinsockError(connect_os_error_);
DCHECK(!core_);
core_ = new Core(this);
@@ -356,6 +376,7 @@
int os_error = WSAGetLastError();
if (os_error != WSAEWOULDBLOCK) {
LOG(ERROR) << "connect failed: " << os_error;
+ connect_os_error_ = os_error;
return MapConnectError(os_error);
}
}
@@ -364,12 +385,43 @@
return ERR_IO_PENDING;
}
+int TCPClientSocketWin::DoConnectComplete(int result) {
+ // Log the end of this attempt (and any OS error it threw).
+ int os_error = connect_os_error_;
+ connect_os_error_ = 0;
+ scoped_refptr<NetLog::EventParameters> params;
+ if (result != OK)
+ params = new NetLogIntegerParameter("os_error", os_error);
+ net_log_.EndEvent(NetLog::TYPE_TCP_CONNECT_ATTEMPT, params);
+
+ if (result == OK)
+ return OK; // Done!
+
+ // Close whatever partially connected socket we currently have.
+ DoDisconnect();
+
+ // Try to fall back to the next address in the list.
+ if (current_ai_->ai_next) {
+ next_connect_state_ = CONNECT_STATE_CONNECT;
+ current_ai_ = current_ai_->ai_next;
+ return OK;
+ }
+
+ // Otherwise there is nothing to fall back to, so give up.
+ return result;
+}
+
void TCPClientSocketWin::Disconnect() {
+ DoDisconnect();
+ current_ai_ = NULL;
+}
+
+void TCPClientSocketWin::DoDisconnect() {
+ DCHECK(CalledOnValidThread());
+
if (socket_ == INVALID_SOCKET)
return;
- TRACE_EVENT_INSTANT("socket.disconnect", this, "");
-
// Note: don't use CancelIo to cancel pending IO because it doesn't work
// when there is a Winsock layered service provider.
@@ -383,10 +435,7 @@
closesocket(socket_);
socket_ = INVALID_SOCKET;
- // Reset for next time.
- current_ai_ = addresses_.head();
-
- if (waiting_connect_) {
+ if (waiting_connect()) {
// We closed the socket, so this notification will never come.
// From MSDN' WSAEventSelect documentation:
// "Closing a socket with closesocket also cancels the association and
@@ -396,14 +445,15 @@
waiting_read_ = false;
waiting_write_ = false;
- waiting_connect_ = false;
core_->Detach();
core_ = NULL;
}
bool TCPClientSocketWin::IsConnected() const {
- if (socket_ == INVALID_SOCKET || waiting_connect_)
+ DCHECK(CalledOnValidThread());
+
+ if (socket_ == INVALID_SOCKET || waiting_connect())
return false;
// Check if connection is alive.
@@ -418,7 +468,9 @@
}
bool TCPClientSocketWin::IsConnectedAndIdle() const {
- if (socket_ == INVALID_SOCKET || waiting_connect_)
+ DCHECK(CalledOnValidThread());
+
+ if (socket_ == INVALID_SOCKET || waiting_connect())
return false;
// Check if connection is alive and we haven't received any data
@@ -433,14 +485,19 @@
return true;
}
-int TCPClientSocketWin::GetPeerName(struct sockaddr* name,
- socklen_t* namelen) {
- return getpeername(socket_, name, namelen);
+int TCPClientSocketWin::GetPeerAddress(AddressList* address) const {
+ DCHECK(CalledOnValidThread());
+ DCHECK(address);
+ if (!current_ai_)
+ return ERR_FAILED;
+ address->Copy(current_ai_, false);
+ return OK;
}
int TCPClientSocketWin::Read(IOBuffer* buf,
int buf_len,
CompletionCallback* callback) {
+ DCHECK(CalledOnValidThread());
DCHECK_NE(socket_, INVALID_SOCKET);
DCHECK(!waiting_read_);
DCHECK(!read_callback_);
@@ -451,16 +508,14 @@
core_->read_buffer_.len = buf_len;
core_->read_buffer_.buf = buf->data();
- TRACE_EVENT_BEGIN("socket.read", this, "");
// TODO(wtc): Remove the CHECK after enough testing.
- CHECK(WaitForSingleObject(core_->read_overlapped_.hEvent, 0) == WAIT_TIMEOUT);
+ CHECK_EQ(static_cast<DWORD>(WAIT_TIMEOUT),
+ WaitForSingleObject(core_->read_overlapped_.hEvent, 0));
DWORD num, flags = 0;
int rv = WSARecv(socket_, &core_->read_buffer_, 1, &num, &flags,
&core_->read_overlapped_, NULL);
if (rv == 0) {
if (ResetEventIfSignaled(core_->read_overlapped_.hEvent)) {
- TRACE_EVENT_END("socket.read", this, StringPrintf("%d bytes", num));
-
// Because of how WSARecv fills memory when used asynchronously, Purify
// isn't able to detect that it's been initialized, so it scans for 0xcd
// in the buffer and reports UMRs (uninitialized memory reads) for those
@@ -470,6 +525,8 @@
base::MemoryDebug::MarkAsInitialized(core_->read_buffer_.buf, num);
static StatsCounter read_bytes("tcp.read_bytes");
read_bytes.Add(num);
+ net_log_.AddEvent(NetLog::TYPE_SOCKET_BYTES_RECEIVED,
+ new NetLogIntegerParameter("num_bytes", num));
return static_cast<int>(num);
}
} else {
@@ -487,6 +544,7 @@
int TCPClientSocketWin::Write(IOBuffer* buf,
int buf_len,
CompletionCallback* callback) {
+ DCHECK(CalledOnValidThread());
DCHECK_NE(socket_, INVALID_SOCKET);
DCHECK(!waiting_write_);
DCHECK(!write_callback_);
@@ -498,20 +556,29 @@
core_->write_buffer_.len = buf_len;
core_->write_buffer_.buf = buf->data();
+ core_->write_buffer_length_ = buf_len;
- TRACE_EVENT_BEGIN("socket.write", this, "");
// TODO(wtc): Remove the CHECK after enough testing.
- CHECK(
- WaitForSingleObject(core_->write_overlapped_.hEvent, 0) == WAIT_TIMEOUT);
+ CHECK_EQ(static_cast<DWORD>(WAIT_TIMEOUT),
+ WaitForSingleObject(core_->write_overlapped_.hEvent, 0));
DWORD num;
int rv = WSASend(socket_, &core_->write_buffer_, 1, &num, 0,
&core_->write_overlapped_, NULL);
if (rv == 0) {
if (ResetEventIfSignaled(core_->write_overlapped_.hEvent)) {
- TRACE_EVENT_END("socket.write", this, StringPrintf("%d bytes", num));
+ rv = static_cast<int>(num);
+ if (rv > buf_len || rv < 0) {
+ // It seems that some winsock interceptors report that more was written
+ // than was available. Treat this as an error. http://crbug.com/27870
+ LOG(ERROR) << "Detected broken LSP: Asked to write " << buf_len
+ << " bytes, but " << rv << " bytes reported.";
+ return ERR_WINSOCK_UNEXPECTED_WRITTEN_BYTES;
+ }
static StatsCounter write_bytes("tcp.write_bytes");
- write_bytes.Add(num);
- return static_cast<int>(num);
+ write_bytes.Add(rv);
+ net_log_.AddEvent(NetLog::TYPE_SOCKET_BYTES_SENT,
+ new NetLogIntegerParameter("num_bytes", rv));
+ return rv;
}
} else {
int os_error = WSAGetLastError();
@@ -526,6 +593,7 @@
}
bool TCPClientSocketWin::SetReceiveBufferSize(int32 size) {
+ DCHECK(CalledOnValidThread());
int rv = setsockopt(socket_, SOL_SOCKET, SO_RCVBUF,
reinterpret_cast<const char*>(&size), sizeof(size));
DCHECK(!rv) << "Could not set socket receive buffer size: " << GetLastError();
@@ -533,6 +601,7 @@
}
bool TCPClientSocketWin::SetSendBufferSize(int32 size) {
+ DCHECK(CalledOnValidThread());
int rv = setsockopt(socket_, SOL_SOCKET, SO_SNDBUF,
reinterpret_cast<const char*>(&size), sizeof(size));
DCHECK(!rv) << "Could not set socket send buffer size: " << GetLastError();
@@ -545,7 +614,7 @@
if (socket_ == INVALID_SOCKET) {
int os_error = WSAGetLastError();
LOG(ERROR) << "WSASocket failed: " << os_error;
- return MapWinsockError(os_error);
+ return os_error;
}
// Increase the socket buffer sizes from the default sizes for WinXP. In
@@ -594,7 +663,17 @@
reinterpret_cast<const char*>(&kDisableNagle), sizeof(kDisableNagle));
DCHECK(!rv) << "Could not disable nagle";
- return OK;
+ // Disregard any failure in disabling nagle.
+ return 0;
+}
+
+void TCPClientSocketWin::LogConnectCompletion(int net_error) {
+ scoped_refptr<NetLog::EventParameters> params;
+ if (net_error != OK)
+ params = new NetLogIntegerParameter("net_error", net_error);
+ net_log_.EndEvent(NetLog::TYPE_TCP_CONNECT, params);
+ if (net_error == OK)
+ UpdateConnectionTypeHistograms(CONNECTION_ANY);
}
void TCPClientSocketWin::DoReadCallback(int rv) {
@@ -624,46 +703,30 @@
}
void TCPClientSocketWin::DidCompleteConnect() {
- DCHECK(waiting_connect_);
+ DCHECK_EQ(next_connect_state_, CONNECT_STATE_CONNECT_COMPLETE);
int result;
- waiting_connect_ = false;
-
WSANETWORKEVENTS events;
int rv = WSAEnumNetworkEvents(socket_, core_->read_overlapped_.hEvent,
&events);
+ int os_error = 0;
if (rv == SOCKET_ERROR) {
NOTREACHED();
- result = MapWinsockError(WSAGetLastError());
+ os_error = WSAGetLastError();
+ result = MapWinsockError(os_error);
} else if (events.lNetworkEvents & FD_CONNECT) {
- int os_error = events.iErrorCode[FD_CONNECT_BIT];
- if (current_ai_->ai_next && ShouldTryNextAddress(os_error)) {
- // Try using the next address.
- const struct addrinfo* next = current_ai_->ai_next;
- Disconnect();
- current_ai_ = next;
- scoped_refptr<LoadLog> load_log;
- load_log.swap(load_log_);
- TRACE_EVENT_END("socket.connect", this, "");
- LoadLog::EndEvent(load_log, LoadLog::TYPE_TCP_CONNECT);
- result = Connect(read_callback_, load_log);
- } else {
- result = MapConnectError(os_error);
- TRACE_EVENT_END("socket.connect", this, "");
- LoadLog::EndEvent(load_log_, LoadLog::TYPE_TCP_CONNECT);
- load_log_ = NULL;
- }
+ os_error = events.iErrorCode[FD_CONNECT_BIT];
+ result = MapConnectError(os_error);
} else {
NOTREACHED();
result = ERR_UNEXPECTED;
- TRACE_EVENT_END("socket.connect", this, "");
- LoadLog::EndEvent(load_log_, LoadLog::TYPE_TCP_CONNECT);
- load_log_ = NULL;
}
- if (result != ERR_IO_PENDING) {
- UpdateConnectionTypeHistograms(CONNECTION_ANY, result >= 0);
- DoReadCallback(result);
+ connect_os_error_ = os_error;
+ rv = DoConnectLoop(result);
+ if (rv != ERR_IO_PENDING) {
+ LogConnectCompletion(rv);
+ DoReadCallback(rv);
}
}
@@ -673,9 +736,12 @@
BOOL ok = WSAGetOverlappedResult(socket_, &core_->read_overlapped_,
&num_bytes, FALSE, &flags);
WSAResetEvent(core_->read_overlapped_.hEvent);
- TRACE_EVENT_END("socket.read", this, StringPrintf("%d bytes", num_bytes));
waiting_read_ = false;
core_->read_iobuffer_ = NULL;
+ if (ok) {
+ net_log_.AddEvent(NetLog::TYPE_SOCKET_BYTES_RECEIVED,
+ new NetLogIntegerParameter("num_bytes", num_bytes));
+ }
DoReadCallback(ok ? num_bytes : MapWinsockError(WSAGetLastError()));
}
@@ -686,10 +752,26 @@
BOOL ok = WSAGetOverlappedResult(socket_, &core_->write_overlapped_,
&num_bytes, FALSE, &flags);
WSAResetEvent(core_->write_overlapped_.hEvent);
- TRACE_EVENT_END("socket.write", this, StringPrintf("%d bytes", num_bytes));
waiting_write_ = false;
+ int rv;
+ if (!ok) {
+ rv = MapWinsockError(WSAGetLastError());
+ } else {
+ rv = static_cast<int>(num_bytes);
+ if (rv > core_->write_buffer_length_ || rv < 0) {
+ // It seems that some winsock interceptors report that more was written
+ // than was available. Treat this as an error. http://crbug.com/27870
+ LOG(ERROR) << "Detected broken LSP: Asked to write "
+ << core_->write_buffer_length_ << " bytes, but " << rv
+ << " bytes reported.";
+ rv = ERR_WINSOCK_UNEXPECTED_WRITTEN_BYTES;
+ } else {
+ net_log_.AddEvent(NetLog::TYPE_SOCKET_BYTES_SENT,
+ new NetLogIntegerParameter("num_bytes", rv));
+ }
+ }
core_->write_iobuffer_ = NULL;
- DoWriteCallback(ok ? num_bytes : MapWinsockError(WSAGetLastError()));
+ DoWriteCallback(rv);
}
} // namespace net
diff --git a/net/socket/tcp_client_socket_win.h b/net/socket/tcp_client_socket_win.h
index 9ad1632..4ac57a8 100644
--- a/net/socket/tcp_client_socket_win.h
+++ b/net/socket/tcp_client_socket_win.h
@@ -1,34 +1,39 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// 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.
#ifndef NET_SOCKET_TCP_CLIENT_SOCKET_WIN_H_
#define NET_SOCKET_TCP_CLIENT_SOCKET_WIN_H_
+#include <winsock2.h>
+
+#include "base/non_thread_safe.h"
#include "base/object_watcher.h"
#include "net/base/address_list.h"
#include "net/base/completion_callback.h"
+#include "net/base/net_log.h"
#include "net/socket/client_socket.h"
namespace net {
-class LoadLog;
+class BoundNetLog;
-class TCPClientSocketWin : public ClientSocket {
+class TCPClientSocketWin : public ClientSocket, NonThreadSafe {
public:
// The IP address(es) and port number to connect to. The TCP socket will try
// each IP address in the list until it succeeds in establishing a
// connection.
- explicit TCPClientSocketWin(const AddressList& addresses);
+ TCPClientSocketWin(const AddressList& addresses, net::NetLog* net_log);
~TCPClientSocketWin();
// ClientSocket methods:
- virtual int Connect(CompletionCallback* callback, LoadLog* load_log);
+ virtual int Connect(CompletionCallback* callback);
virtual void Disconnect();
virtual bool IsConnected() const;
virtual bool IsConnectedAndIdle() const;
- virtual int GetPeerName(struct sockaddr* name, socklen_t* namelen);
+ virtual int GetPeerAddress(AddressList* address) const;
+ virtual const BoundNetLog& NetLog() const { return net_log_; }
// Socket methods:
// Multiple outstanding requests are not supported.
@@ -40,12 +45,35 @@
virtual bool SetSendBufferSize(int32 size);
private:
+ // State machine for connecting the socket.
+ enum ConnectState {
+ CONNECT_STATE_CONNECT,
+ CONNECT_STATE_CONNECT_COMPLETE,
+ CONNECT_STATE_NONE,
+ };
+
class Core;
- // Performs the actual connect(). Returns a net error code.
+ // State machine used by Connect().
+ int DoConnectLoop(int result);
int DoConnect();
+ int DoConnectComplete(int result);
+ // Helper used by Disconnect(), which disconnects minus the logging and
+ // resetting of current_ai_.
+ void DoDisconnect();
+
+ // Returns true if a Connect() is in progress.
+ bool waiting_connect() const {
+ return next_connect_state_ != CONNECT_STATE_NONE;
+ }
+
+ // Returns the OS error code (or 0 on success).
int CreateSocket(const struct addrinfo* ai);
+
+ // Called after Connect() has completed with |net_error|.
+ void LogConnectCompletion(int net_error);
+
void DoReadCallback(int rv);
void DoWriteCallback(int rv);
void DidCompleteConnect();
@@ -61,7 +89,6 @@
const struct addrinfo* current_ai_;
// The various states that the socket could be in.
- bool waiting_connect_;
bool waiting_read_;
bool waiting_write_;
@@ -76,7 +103,13 @@
// External callback; called when write is complete.
CompletionCallback* write_callback_;
- scoped_refptr<LoadLog> load_log_;
+ // The next state for the Connect() state machine.
+ ConnectState next_connect_state_;
+
+ // The OS error that CONNECT_STATE_CONNECT last completed with.
+ int connect_os_error_;
+
+ BoundNetLog net_log_;
DISALLOW_COPY_AND_ASSIGN(TCPClientSocketWin);
};
diff --git a/net/socket/tcp_pinger.h b/net/socket/tcp_pinger.h
index ae543c5..96fa4fd 100644
--- a/net/socket/tcp_pinger.h
+++ b/net/socket/tcp_pinger.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -9,6 +9,7 @@
#include "base/ref_counted.h"
#include "base/scoped_ptr.h"
#include "base/task.h"
+#include "base/third_party/dynamic_annotations/dynamic_annotations.h"
#include "base/thread.h"
#include "base/waitable_event.h"
#include "net/base/address_list.h"
@@ -85,8 +86,8 @@
}
void DoConnect() {
- sock_.reset(new TCPClientSocket(addr_));
- int rv = sock_->Connect(&connect_callback_, NULL);
+ sock_.reset(new TCPClientSocket(addr_, NULL));
+ int rv = sock_->Connect(&connect_callback_);
// Regardless of success or failure, if we're done now,
// signal the customer.
if (rv != ERR_IO_PENDING)
@@ -106,7 +107,10 @@
int TimedWaitForResult(base::TimeDelta tryTimeout) {
event_.TimedWait(tryTimeout);
- return net_error_;
+ // In case of timeout, the value of net_error_ should be ERR_IO_PENDING.
+ // However, a harmless data race can happen if TimedWait times out right
+ // before event_.Signal() is called in ConnectDone().
+ return ANNOTATE_UNPROTECTED_READ(net_error_);
}
int WaitForResult() {
diff --git a/net/socket/tcp_pinger_unittest.cc b/net/socket/tcp_pinger_unittest.cc
index 2cffa8a..c415ac3 100644
--- a/net/socket/tcp_pinger_unittest.cc
+++ b/net/socket/tcp_pinger_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -28,7 +28,7 @@
LOG(INFO) << "TCPPinger accepted connection";
connected_sock_ = connection;
}
- virtual void DidRead(ListenSocket*, const std::string& s) {
+ virtual void DidRead(ListenSocket*, const char* str, int len) {
// Not really needed yet, as TCPPinger doesn't support Read
connected_sock_->Send(std::string("HTTP/1.1 404 Not Found"), true);
connected_sock_ = NULL;
@@ -66,10 +66,10 @@
TEST_F(TCPPingerTest, Ping) {
net::AddressList addr;
scoped_refptr<net::HostResolver> resolver(
- net::CreateSystemHostResolver(NULL));
+ net::CreateSystemHostResolver(net::HostResolver::kDefaultParallelism));
net::HostResolver::RequestInfo info("localhost", listen_port_);
- int rv = resolver->Resolve(info, &addr, NULL, NULL, NULL);
+ int rv = resolver->Resolve(info, &addr, NULL, NULL, net::BoundNetLog());
EXPECT_EQ(rv, net::OK);
net::TCPPinger pinger(addr);
@@ -80,13 +80,13 @@
TEST_F(TCPPingerTest, PingFail) {
net::AddressList addr;
scoped_refptr<net::HostResolver> resolver(
- net::CreateSystemHostResolver(NULL));
+ net::CreateSystemHostResolver(net::HostResolver::kDefaultParallelism));
// "Kill" "server"
listen_sock_ = NULL;
net::HostResolver::RequestInfo info("localhost", listen_port_);
- int rv = resolver->Resolve(info, &addr, NULL, NULL, NULL);
+ int rv = resolver->Resolve(info, &addr, NULL, NULL, net::BoundNetLog());
EXPECT_EQ(rv, net::OK);
net::TCPPinger pinger(addr);
diff --git a/net/socket_stream/socket_stream.cc b/net/socket_stream/socket_stream.cc
index 2a01f17..d1ead09 100644
--- a/net/socket_stream/socket_stream.cc
+++ b/net/socket_stream/socket_stream.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
//
@@ -18,6 +18,8 @@
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/base/net_util.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_request_info.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
#include "net/socket/client_socket_factory.h"
@@ -26,7 +28,6 @@
#include "net/socket/socks_client_socket.h"
#include "net/socket/tcp_client_socket.h"
#include "net/socket_stream/socket_stream_metrics.h"
-#include "net/socket_stream/socket_stream_throttle.h"
#include "net/url_request/url_request.h"
static const int kMaxPendingSendAllowed = 32768; // 32 kilobytes.
@@ -34,15 +35,19 @@
namespace net {
+SocketStream::ResponseHeaders::ResponseHeaders() : IOBuffer() {}
+SocketStream::ResponseHeaders::~ResponseHeaders() { data_ = NULL; }
+
void SocketStream::ResponseHeaders::Realloc(size_t new_size) {
headers_.reset(static_cast<char*>(realloc(headers_.release(), new_size)));
}
SocketStream::SocketStream(const GURL& url, Delegate* delegate)
- : url_(url),
- delegate_(delegate),
+ : delegate_(delegate),
+ url_(url),
max_pending_send_allowed_(kMaxPendingSendAllowed),
next_state_(STATE_NONE),
+ http_auth_handler_factory_(NULL),
factory_(ClientSocketFactory::GetDefaultFactory()),
proxy_mode_(kDirectConnection),
proxy_url_(url),
@@ -58,23 +63,19 @@
current_write_buf_(NULL),
write_buf_offset_(0),
write_buf_size_(0),
- throttle_(
- SocketStreamThrottle::GetSocketStreamThrottleForScheme(
- url.scheme())),
- metrics_(new SocketStreamMetrics(url)),
- ALLOW_THIS_IN_INITIALIZER_LIST(
- request_tracker_node_(this)) {
+ closing_(false),
+ metrics_(new SocketStreamMetrics(url)) {
DCHECK(MessageLoop::current()) <<
"The current MessageLoop must exist";
DCHECK_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()) <<
"The current MessageLoop must be TYPE_IO";
DCHECK(delegate_);
- DCHECK(throttle_);
}
SocketStream::~SocketStream() {
set_context(NULL);
DCHECK(!delegate_);
+ DCHECK(!pac_request_);
}
SocketStream::UserData* SocketStream::GetUserData(
@@ -95,21 +96,27 @@
context_ = context;
if (prev_context != context) {
- if (prev_context)
- prev_context->socket_stream_tracker()->Remove(this);
+ if (prev_context && pac_request_) {
+ prev_context->proxy_service()->CancelPacRequest(pac_request_);
+ pac_request_ = NULL;
+ }
+
+ net_log_.EndEvent(NetLog::TYPE_REQUEST_ALIVE, NULL);
+ net_log_ = BoundNetLog();
+
if (context) {
- if (!load_log_) {
- // Create the LoadLog -- we waited until now to create it so we know
- // what constraints the URLRequestContext is enforcing on log levels.
- load_log_ = context->socket_stream_tracker()->CreateLoadLog();
- }
- context->socket_stream_tracker()->Add(this);
+ net_log_ = BoundNetLog::Make(
+ context->net_log(),
+ NetLog::SOURCE_SOCKET_STREAM);
+
+ net_log_.BeginEvent(NetLog::TYPE_REQUEST_ALIVE, NULL);
}
}
- if (context_)
+ if (context_) {
host_resolver_ = context_->host_resolver();
-
+ http_auth_handler_factory_ = context_->http_auth_handler_factory();
+ }
}
void SocketStream::Connect() {
@@ -125,7 +132,9 @@
// Open a connection asynchronously, so that delegate won't be called
// back before returning Connect().
next_state_ = STATE_RESOLVE_PROXY;
- LoadLog::BeginEvent(load_log_, LoadLog::TYPE_SOCKET_STREAM_CONNECT);
+ net_log_.BeginEvent(
+ NetLog::TYPE_SOCKET_STREAM_CONNECT,
+ new NetLogStringParameter("url", url_.possibly_invalid_spec()));
MessageLoop::current()->PostTask(
FROM_HERE,
NewRunnableMethod(this, &SocketStream::DoLoop, OK));
@@ -171,10 +180,13 @@
"The current MessageLoop must exist";
DCHECK_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()) <<
"The current MessageLoop must be TYPE_IO";
- if (!socket_.get() || !socket_->IsConnected() || next_state_ == STATE_NONE)
+ // If next_state_ is STATE_NONE, the socket was not opened, or already
+ // closed. So, return immediately.
+ // Otherwise, it might call Finish() more than once, so breaks balance
+ // of AddRef() and Release() in Connect() and Finish(), respectively.
+ if (next_state_ == STATE_NONE)
return;
- socket_->Disconnect();
- next_state_ = STATE_CLOSE;
+ closing_ = true;
// Close asynchronously, so that delegate won't be called
// back before returning Close().
MessageLoop::current()->PostTask(
@@ -188,7 +200,7 @@
"The current MessageLoop must exist";
DCHECK_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()) <<
"The current MessageLoop must be TYPE_IO";
- DCHECK(auth_handler_);
+ DCHECK(auth_handler_.get());
if (!socket_.get()) {
LOG(ERROR) << "Socket is closed before restarting with auth.";
return;
@@ -211,7 +223,9 @@
if (!delegate_)
return;
delegate_ = NULL;
- LoadLog::AddEvent(load_log_, LoadLog::TYPE_CANCELLED);
+ net_log_.AddEvent(NetLog::TYPE_CANCELLED, NULL);
+ // We don't need to send pending data when client detach the delegate.
+ pending_write_bufs_.clear();
Close();
}
@@ -234,7 +248,6 @@
if (delegate) {
delegate->OnClose(this);
}
- throttle_->OnClose(this);
Release();
}
@@ -250,7 +263,7 @@
}
void SocketStream::CopyAddrInfo(struct addrinfo* head) {
- addresses_.Copy(head);
+ addresses_.Copy(head, true);
}
int SocketStream::DidEstablishConnection() {
@@ -261,7 +274,7 @@
next_state_ = STATE_READ_WRITE;
metrics_->OnConnected();
- LoadLog::EndEvent(load_log_, LoadLog::TYPE_SOCKET_STREAM_CONNECT);
+ net_log_.EndEvent(NetLog::TYPE_SOCKET_STREAM_CONNECT, NULL);
if (delegate_)
delegate_->OnConnected(this, max_pending_send_allowed_);
@@ -271,25 +284,22 @@
int SocketStream::DidReceiveData(int result) {
DCHECK(read_buf_);
DCHECK_GT(result, 0);
- LoadLog::AddEvent(load_log_, LoadLog::TYPE_SOCKET_STREAM_RECEIVED);
+ net_log_.AddEvent(NetLog::TYPE_SOCKET_STREAM_RECEIVED, NULL);
int len = result;
metrics_->OnRead(len);
- result = throttle_->OnRead(this, read_buf_->data(), len, &io_callback_);
if (delegate_) {
// Notify recevied data to delegate.
delegate_->OnReceivedData(this, read_buf_->data(), len);
}
read_buf_ = NULL;
- return result;
+ return OK;
}
int SocketStream::DidSendData(int result) {
DCHECK_GT(result, 0);
- LoadLog::AddEvent(load_log_, LoadLog::TYPE_SOCKET_STREAM_SENT);
+ net_log_.AddEvent(NetLog::TYPE_SOCKET_STREAM_SENT, NULL);
int len = result;
metrics_->OnWrite(len);
- result = throttle_->OnWrite(this, current_write_buf_->data(), len,
- &io_callback_);
current_write_buf_ = NULL;
if (delegate_)
delegate_->OnSentData(this, len);
@@ -308,7 +318,7 @@
} else {
write_buf_offset_ += len;
}
- return result;
+ return OK;
}
void SocketStream::OnIOCompleted(int result) {
@@ -409,7 +419,8 @@
// close the connection.
if (state != STATE_READ_WRITE && result < ERR_IO_PENDING) {
DCHECK_EQ(next_state_, STATE_CLOSE);
- LoadLog::EndEvent(load_log_, LoadLog::TYPE_SOCKET_STREAM_CONNECT);
+ net_log_.EndEvent(NetLog::TYPE_SOCKET_STREAM_CONNECT,
+ new NetLogIntegerParameter("net_error", result));
}
} while (result != ERR_IO_PENDING);
}
@@ -424,12 +435,10 @@
}
return proxy_service()->ResolveProxy(
- proxy_url_, &proxy_info_, &io_callback_, &pac_request_, load_log_);
+ proxy_url_, &proxy_info_, &io_callback_, &pac_request_, net_log_);
}
int SocketStream::DoResolveProxyComplete(int result) {
- next_state_ = STATE_RESOLVE_HOST;
-
pac_request_ = NULL;
if (result != OK) {
LOG(ERROR) << "Failed to resolve proxy: " << result;
@@ -453,12 +462,20 @@
}
}
+ if (proxy_info_.is_empty()) {
+ // No proxies/direct to choose from. This happens when we don't support any
+ // of the proxies in the returned list.
+ return ERR_NO_SUPPORTED_PROXIES;
+ }
+
+ next_state_ = STATE_RESOLVE_HOST;
return OK;
}
int SocketStream::DoResolveHost() {
next_state_ = STATE_RESOLVE_HOST_COMPLETE;
+ DCHECK(!proxy_info_.is_empty());
if (proxy_info_.is_direct())
proxy_mode_ = kDirectConnection;
else if (proxy_info_.proxy_server().is_socks())
@@ -483,13 +500,13 @@
DCHECK(host_resolver_.get());
resolver_.reset(new SingleRequestHostResolver(host_resolver_.get()));
return resolver_->Resolve(resolve_info, &addresses_, &io_callback_,
- load_log_);
+ net_log_);
}
int SocketStream::DoResolveHostComplete(int result) {
if (result == OK) {
next_state_ = STATE_TCP_CONNECT;
- result = throttle_->OnStartOpenConnection(this, &io_callback_);
+ result = delegate_->OnStartOpenConnection(this, &io_callback_);
if (result == net::ERR_IO_PENDING)
metrics_->OnWaitConnection();
} else {
@@ -502,9 +519,10 @@
int SocketStream::DoTcpConnect() {
next_state_ = STATE_TCP_CONNECT_COMPLETE;
DCHECK(factory_);
- socket_.reset(factory_->CreateTCPClientSocket(addresses_));
+ socket_.reset(factory_->CreateTCPClientSocket(addresses_,
+ net_log_.net_log()));
metrics_->OnStartConnection();
- return socket_->Connect(&io_callback_, load_log_);
+ return socket_->Connect(&io_callback_);
}
int SocketStream::DoTcpConnectComplete(int result) {
@@ -540,15 +558,23 @@
std::string authorization_headers;
if (!auth_handler_.get()) {
- // First attempt. Find auth from the proxy address.
+ // Do preemptive authentication.
HttpAuthCache::Entry* entry = auth_cache_.LookupByPath(
ProxyAuthOrigin(), std::string());
- if (entry && !entry->handler()->is_connection_based()) {
- auth_identity_.source = HttpAuth::IDENT_SRC_PATH_LOOKUP;
- auth_identity_.invalid = false;
- auth_identity_.username = entry->username();
- auth_identity_.password = entry->password();
- auth_handler_ = entry->handler();
+ if (entry) {
+ scoped_ptr<HttpAuthHandler> handler_preemptive;
+ int rv_create = http_auth_handler_factory_->
+ CreatePreemptiveAuthHandlerFromString(
+ entry->auth_challenge(), HttpAuth::AUTH_PROXY,
+ ProxyAuthOrigin(), entry->IncrementNonceCount(),
+ net_log_, &handler_preemptive);
+ if (rv_create == OK) {
+ auth_identity_.source = HttpAuth::IDENT_SRC_PATH_LOOKUP;
+ auth_identity_.invalid = false;
+ auth_identity_.username = entry->username();
+ auth_identity_.password = entry->password();
+ auth_handler_.swap(handler_preemptive);
+ }
}
}
@@ -556,14 +582,21 @@
// HttpRequestInfo.
// TODO(ukai): Add support other authentication scheme.
if (auth_handler_.get() && auth_handler_->scheme() == "basic") {
- std::string credentials = auth_handler_->GenerateCredentials(
- auth_identity_.username,
- auth_identity_.password,
+ HttpRequestInfo request_info;
+ std::string auth_token;
+ int rv = auth_handler_->GenerateAuthToken(
+ &auth_identity_.username,
+ &auth_identity_.password,
+ &request_info,
NULL,
- &proxy_info_);
+ &auth_token);
+ // TODO(cbentzel): Support async auth handlers.
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ if (rv != OK)
+ return rv;
authorization_headers.append(
HttpAuth::GetAuthorizationHeaderName(HttpAuth::AUTH_PROXY) +
- ": " + credentials + "\r\n");
+ ": " + auth_token + "\r\n");
}
tunnel_request_headers_->headers_ = StringPrintf(
@@ -676,8 +709,9 @@
return OK;
case 407: // Proxy Authentication Required.
result = HandleAuthChallenge(headers.get());
- if (result == ERR_PROXY_AUTH_REQUESTED &&
+ if (result == ERR_PROXY_AUTH_UNSUPPORTED &&
auth_handler_.get() && delegate_) {
+ DCHECK(!proxy_info_.is_empty());
auth_info_ = new AuthChallengeInfo;
auth_info_->is_proxy = true;
auth_info_->host_and_port =
@@ -707,13 +741,14 @@
HostResolver::RequestInfo req_info(url_.HostNoBrackets(),
url_.EffectiveIntPort());
+ DCHECK(!proxy_info_.is_empty());
if (proxy_info_.proxy_server().scheme() == ProxyServer::SCHEME_SOCKS5)
s = new SOCKS5ClientSocket(s, req_info);
else
s = new SOCKSClientSocket(s, req_info, host_resolver_.get());
socket_.reset(s);
metrics_->OnSOCKSProxy();
- return socket_->Connect(&io_callback_, load_log_);
+ return socket_->Connect(&io_callback_);
}
int SocketStream::DoSOCKSConnectComplete(int result) {
@@ -736,7 +771,7 @@
socket_.release(), url_.HostNoBrackets(), ssl_config_));
next_state_ = STATE_SSL_CONNECT_COMPLETE;
metrics_->OnSSLConnection();
- return socket_->Connect(&io_callback_, load_log_);
+ return socket_->Connect(&io_callback_);
}
int SocketStream::DoSSLConnectComplete(int result) {
@@ -793,6 +828,15 @@
return ERR_CONNECTION_CLOSED;
}
+ // If client has requested close(), and there's nothing to write, then
+ // let's close the socket.
+ // We don't care about receiving data after the socket is closed.
+ if (closing_ && !write_buf_ && pending_write_bufs_.empty()) {
+ socket_->Disconnect();
+ next_state_ = STATE_CLOSE;
+ return OK;
+ }
+
next_state_ = STATE_READ_WRITE;
if (!read_buf_) {
@@ -840,6 +884,7 @@
}
GURL SocketStream::ProxyAuthOrigin() const {
+ DCHECK(!proxy_info_.is_empty());
return GURL("http://" + proxy_info_.proxy_server().host_and_port());
}
@@ -855,35 +900,36 @@
if (auth_identity_.source != HttpAuth::IDENT_SRC_PATH_LOOKUP)
auth_cache_.Remove(auth_origin,
auth_handler_->realm(),
+ auth_handler_->scheme(),
auth_identity_.username,
auth_identity_.password);
- auth_handler_ = NULL;
+ auth_handler_.reset();
auth_identity_ = HttpAuth::Identity();
}
auth_identity_.invalid = true;
- HttpAuth::ChooseBestChallenge(headers, HttpAuth::AUTH_PROXY, auth_origin,
- &auth_handler_);
- if (!auth_handler_) {
+ std::set<std::string> disabled_schemes;
+ HttpAuth::ChooseBestChallenge(http_auth_handler_factory_, headers,
+ HttpAuth::AUTH_PROXY,
+ auth_origin, disabled_schemes,
+ net_log_, &auth_handler_);
+ if (!auth_handler_.get()) {
LOG(ERROR) << "Can't perform auth to the proxy " << auth_origin;
return ERR_TUNNEL_CONNECTION_FAILED;
}
if (auth_handler_->NeedsIdentity()) {
- HttpAuthCache::Entry* entry = auth_cache_.LookupByRealm(
- auth_origin, auth_handler_->realm());
+ // We only support basic authentication scheme now.
+ // TODO(ukai): Support other authentication scheme.
+ HttpAuthCache::Entry* entry =
+ auth_cache_.Lookup(auth_origin, auth_handler_->realm(), "basic");
if (entry) {
- if (entry->handler()->scheme() != "basic") {
- // We only support basic authentication scheme now.
- // TODO(ukai): Support other authentication scheme.
- return ERR_TUNNEL_CONNECTION_FAILED;
- }
auth_identity_.source = HttpAuth::IDENT_SRC_REALM_LOOKUP;
auth_identity_.invalid = false;
auth_identity_.username = entry->username();
auth_identity_.password = entry->password();
// Restart with auth info.
}
- return ERR_PROXY_AUTH_REQUESTED;
+ return ERR_PROXY_AUTH_UNSUPPORTED;
} else {
auth_identity_.invalid = false;
}
@@ -899,8 +945,12 @@
void SocketStream::DoRestartWithAuth() {
DCHECK_EQ(next_state_, STATE_AUTH_REQUIRED);
- auth_cache_.Add(ProxyAuthOrigin(), auth_handler_,
- auth_identity_.username, auth_identity_.password,
+ auth_cache_.Add(ProxyAuthOrigin(),
+ auth_handler_->realm(),
+ auth_handler_->scheme(),
+ auth_handler_->challenge(),
+ auth_identity_.username,
+ auth_identity_.password,
std::string());
tunnel_request_headers_ = NULL;
@@ -939,10 +989,5 @@
return context_->proxy_service();
}
-void SocketStream::GetInfoForTracker(
- RequestTracker<SocketStream>::RecentRequestInfo* info) const {
- info->original_url = url_;
- info->load_log = load_log_;
-}
-
} // namespace net
+
diff --git a/net/socket_stream/socket_stream.h b/net/socket_stream/socket_stream.h
index cf6a6b0..d0e8b2e 100644
--- a/net/socket_stream/socket_stream.h
+++ b/net/socket_stream/socket_stream.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -17,12 +17,13 @@
#include "net/base/address_list.h"
#include "net/base/completion_callback.h"
#include "net/base/io_buffer.h"
+#include "net/base/net_log.h"
+#include "net/base/net_errors.h"
#include "net/http/http_auth.h"
#include "net/http/http_auth_cache.h"
#include "net/http/http_auth_handler.h"
#include "net/proxy/proxy_service.h"
#include "net/socket/tcp_client_socket.h"
-#include "net/url_request/request_tracker.h"
#include "net/url_request/url_request_context.h"
namespace net {
@@ -30,11 +31,10 @@
class AuthChallengeInfo;
class ClientSocketFactory;
class HostResolver;
-class LoadLog;
+class HttpAuthHandlerFactory;
class SSLConfigService;
class SingleRequestHostResolver;
class SocketStreamMetrics;
-class SocketStreamThrottle;
// SocketStream is used to implement Web Sockets.
// It provides plain full-duplex stream with proxy and SSL support.
@@ -57,6 +57,11 @@
public:
virtual ~Delegate() {}
+ virtual int OnStartOpenConnection(SocketStream* socket,
+ CompletionCallback* callback) {
+ return OK;
+ }
+
// Called when socket stream has been connected. The socket stream accepts
// at most |max_pending_send_allowed| so that a client of the socket stream
// should keep track of how much it has pending and shouldn't go over
@@ -100,6 +105,7 @@
void SetUserData(const void* key, UserData* data);
const GURL& url() const { return url_; }
+ bool is_secure() const;
const AddressList& address_list() const { return addresses_; }
Delegate* delegate() const { return delegate_; }
int max_pending_send_allowed() const { return max_pending_send_allowed_; }
@@ -107,32 +113,32 @@
URLRequestContext* context() const { return context_.get(); }
void set_context(URLRequestContext* context);
- LoadLog* load_log() const { return load_log_; }
+ BoundNetLog* net_log() { return &net_log_; }
// Opens the connection on the IO thread.
// Once the connection is established, calls delegate's OnConnected.
- void Connect();
+ virtual void Connect();
// Requests to send |len| bytes of |data| on the connection.
// Returns true if |data| is buffered in the job.
// Returns false if size of buffered data would exceeds
// |max_pending_send_allowed_| and |data| is not sent at all.
- bool SendData(const char* data, int len);
+ virtual bool SendData(const char* data, int len);
// Requests to close the connection.
// Once the connection is closed, calls delegate's OnClose.
- void Close();
+ virtual void Close();
// Restarts with authentication info.
// Should be used for response of OnAuthRequired.
- void RestartWithAuth(
+ virtual void RestartWithAuth(
const std::wstring& username,
const std::wstring& password);
// Detach delegate. Call before delegate is deleted.
// Once delegate is detached, close the socket stream and never call delegate
// back.
- void DetachDelegate();
+ virtual void DetachDelegate();
// Sets an alternative HostResolver. For testing purposes only.
void SetHostResolver(HostResolver* host_resolver);
@@ -141,6 +147,12 @@
// |factory|. For testing purposes only.
void SetClientSocketFactory(ClientSocketFactory* factory);
+ protected:
+ friend class base::RefCountedThreadSafe<SocketStream>;
+ virtual ~SocketStream();
+
+ Delegate* delegate_;
+
private:
class RequestHeaders : public IOBuffer {
public:
@@ -158,7 +170,7 @@
class ResponseHeaders : public IOBuffer {
public:
- ResponseHeaders() : IOBuffer() {}
+ ResponseHeaders();
void SetDataOffset(size_t offset) { data_ = headers_.get() + offset; }
char* headers() const { return headers_.get(); }
@@ -166,7 +178,7 @@
void Realloc(size_t new_size);
private:
- ~ResponseHeaders() { data_ = NULL; }
+ ~ResponseHeaders();
scoped_ptr_malloc<char> headers_;
};
@@ -199,9 +211,6 @@
};
typedef std::deque< scoped_refptr<IOBufferWithSize> > PendingDataQueue;
- friend class RequestTracker<SocketStream>;
- friend class base::RefCountedThreadSafe<SocketStream>;
- ~SocketStream();
friend class WebSocketThrottleTest;
@@ -247,17 +256,12 @@
int HandleCertificateError(int result);
- bool is_secure() const;
SSLConfigService* ssl_config_service() const;
ProxyService* proxy_service() const;
- void GetInfoForTracker(
- RequestTracker<SocketStream>::RecentRequestInfo* info) const;
-
- scoped_refptr<LoadLog> load_log_;
+ BoundNetLog net_log_;
GURL url_;
- Delegate* delegate_;
int max_pending_send_allowed_;
scoped_refptr<URLRequestContext> context_;
@@ -266,6 +270,7 @@
State next_state_;
scoped_refptr<HostResolver> host_resolver_;
+ HttpAuthHandlerFactory* http_auth_handler_factory_;
ClientSocketFactory* factory_;
ProxyMode proxy_mode_;
@@ -275,7 +280,7 @@
ProxyInfo proxy_info_;
HttpAuthCache auth_cache_;
- scoped_refptr<HttpAuthHandler> auth_handler_;
+ scoped_ptr<HttpAuthHandler> auth_handler_;
HttpAuth::Identity auth_identity_;
scoped_refptr<AuthChallengeInfo> auth_info_;
@@ -313,12 +318,10 @@
int write_buf_size_;
PendingDataQueue pending_write_bufs_;
- SocketStreamThrottle* throttle_;
+ bool closing_;
scoped_ptr<SocketStreamMetrics> metrics_;
- RequestTracker<SocketStream>::Node request_tracker_node_;
-
DISALLOW_COPY_AND_ASSIGN(SocketStream);
};
diff --git a/net/socket_stream/socket_stream_job.cc b/net/socket_stream/socket_stream_job.cc
new file mode 100644
index 0000000..c8849a5
--- /dev/null
+++ b/net/socket_stream/socket_stream_job.cc
@@ -0,0 +1,27 @@
+// 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 "net/socket_stream/socket_stream_job.h"
+
+#include "net/socket_stream/socket_stream_job_manager.h"
+
+namespace net {
+
+static SocketStreamJobManager* GetJobManager() {
+ return Singleton<SocketStreamJobManager>::get();
+}
+
+// static
+SocketStreamJob::ProtocolFactory* SocketStreamJob::RegisterProtocolFactory(
+ const std::string& scheme, ProtocolFactory* factory) {
+ return GetJobManager()->RegisterProtocolFactory(scheme, factory);
+}
+
+// static
+SocketStreamJob* SocketStreamJob::CreateSocketStreamJob(
+ const GURL& url, SocketStream::Delegate* delegate) {
+ return GetJobManager()->CreateJob(url, delegate);
+}
+
+} // namespace net
diff --git a/net/socket_stream/socket_stream_job.h b/net/socket_stream/socket_stream_job.h
new file mode 100644
index 0000000..618620c
--- /dev/null
+++ b/net/socket_stream/socket_stream_job.h
@@ -0,0 +1,87 @@
+// 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.
+
+#ifndef NET_SOCKET_STREAM_SOCKET_STREAM_JOB_H_
+#define NET_SOCKET_STREAM_SOCKET_STREAM_JOB_H_
+
+#include <string>
+
+#include "base/ref_counted.h"
+#include "net/socket_stream/socket_stream.h"
+
+class GURL;
+
+namespace net {
+
+// SocketStreamJob represents full-duplex communication over SocketStream.
+// If a protocol (e.g. WebSocket protocol) needs to inspect/modify data
+// over SocketStream, you can implement protocol specific job (e.g.
+// WebSocketJob) to do some work on data over SocketStream.
+// Registers the protocol specific SocketStreamJob by RegisterProtocolFactory
+// and call CreateSocketStreamJob to create SocketStreamJob for the URL.
+class SocketStreamJob : public base::RefCountedThreadSafe<SocketStreamJob> {
+ public:
+ // Callback function implemented by protocol handlers to create new jobs.
+ typedef SocketStreamJob* (ProtocolFactory)(const GURL& url,
+ SocketStream::Delegate* delegate);
+
+ static ProtocolFactory* RegisterProtocolFactory(const std::string& scheme,
+ ProtocolFactory* factory);
+
+ static SocketStreamJob* CreateSocketStreamJob(
+ const GURL& url, SocketStream::Delegate* delegate);
+
+ SocketStreamJob() {}
+ void InitSocketStream(SocketStream* socket) {
+ socket_ = socket;
+ }
+
+ virtual SocketStream::UserData *GetUserData(const void* key) const {
+ return socket_->GetUserData(key);
+ }
+ virtual void SetUserData(const void* key, SocketStream::UserData* data) {
+ socket_->SetUserData(key, data);
+ }
+
+ URLRequestContext* context() const {
+ return socket_->context();
+ }
+ void set_context(URLRequestContext* context) {
+ socket_->set_context(context);
+ }
+
+ virtual void Connect() {
+ socket_->Connect();
+ }
+
+ virtual bool SendData(const char* data, int len) {
+ return socket_->SendData(data, len);
+ }
+
+ virtual void Close() {
+ socket_->Close();
+ }
+
+ virtual void RestartWithAuth(
+ const std::wstring& username,
+ const std::wstring& password) {
+ socket_->RestartWithAuth(username, password);
+ }
+
+ virtual void DetachDelegate() {
+ socket_->DetachDelegate();
+ }
+
+ protected:
+ friend class base::RefCountedThreadSafe<SocketStreamJob>;
+ virtual ~SocketStreamJob() {}
+
+ scoped_refptr<SocketStream> socket_;
+
+ DISALLOW_COPY_AND_ASSIGN(SocketStreamJob);
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_STREAM_SOCKET_STREAM_JOB_H_
diff --git a/net/socket_stream/socket_stream_job_manager.cc b/net/socket_stream/socket_stream_job_manager.cc
new file mode 100644
index 0000000..7dd0d6b
--- /dev/null
+++ b/net/socket_stream/socket_stream_job_manager.cc
@@ -0,0 +1,59 @@
+// 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 "net/socket_stream/socket_stream_job_manager.h"
+
+namespace net {
+
+SocketStreamJobManager::SocketStreamJobManager() {
+}
+
+SocketStreamJobManager::~SocketStreamJobManager() {
+}
+
+SocketStreamJob* SocketStreamJobManager::CreateJob(
+ const GURL& url, SocketStream::Delegate* delegate) const {
+ // If url is invalid, create plain SocketStreamJob, which will close
+ // the socket immediately.
+ if (!url.is_valid()) {
+ SocketStreamJob* job = new SocketStreamJob();
+ job->InitSocketStream(new SocketStream(url, delegate));
+ return job;
+ }
+
+ const std::string& scheme = url.scheme(); // already lowercase
+
+ AutoLock locked(lock_);
+ FactoryMap::const_iterator found = factories_.find(scheme);
+ if (found != factories_.end()) {
+ SocketStreamJob* job = found->second(url, delegate);
+ if (job)
+ return job;
+ }
+ SocketStreamJob* job = new SocketStreamJob();
+ job->InitSocketStream(new SocketStream(url, delegate));
+ return job;
+}
+
+SocketStreamJob::ProtocolFactory*
+SocketStreamJobManager::RegisterProtocolFactory(
+ const std::string& scheme, SocketStreamJob::ProtocolFactory* factory) {
+ AutoLock locked(lock_);
+
+ SocketStreamJob::ProtocolFactory* old_factory;
+ FactoryMap::iterator found = factories_.find(scheme);
+ if (found != factories_.end()) {
+ old_factory = found->second;
+ } else {
+ old_factory = NULL;
+ }
+ if (factory) {
+ factories_[scheme] = factory;
+ } else if (found != factories_.end()) {
+ factories_.erase(found);
+ }
+ return old_factory;
+}
+
+} // namespace net
diff --git a/net/socket_stream/socket_stream_job_manager.h b/net/socket_stream/socket_stream_job_manager.h
new file mode 100644
index 0000000..17ff833
--- /dev/null
+++ b/net/socket_stream/socket_stream_job_manager.h
@@ -0,0 +1,40 @@
+// 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.
+
+#ifndef NET_SOCKET_STREAM_SOCKET_STREAM_JOB_MANAGER_H_
+#define NET_SOCKET_STREAM_SOCKET_STREAM_JOB_MANAGER_H_
+
+#include <map>
+#include <string>
+
+#include "net/socket_stream/socket_stream.h"
+#include "net/socket_stream/socket_stream_job.h"
+
+class GURL;
+
+namespace net {
+
+class SocketStreamJobManager {
+ public:
+ SocketStreamJobManager();
+ ~SocketStreamJobManager();
+
+ SocketStreamJob* CreateJob(
+ const GURL& url, SocketStream::Delegate* delegate) const;
+
+ SocketStreamJob::ProtocolFactory* RegisterProtocolFactory(
+ const std::string& scheme, SocketStreamJob::ProtocolFactory* factory);
+
+ private:
+ typedef std::map<std::string, SocketStreamJob::ProtocolFactory*> FactoryMap;
+
+ mutable Lock lock_;
+ FactoryMap factories_;
+
+ DISALLOW_COPY_AND_ASSIGN(SocketStreamJobManager);
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_STREAM_SOCKET_STREAM_JOB_MANAGER_H_
diff --git a/net/socket_stream/socket_stream_metrics.cc b/net/socket_stream/socket_stream_metrics.cc
index 625a491..71239af 100644
--- a/net/socket_stream/socket_stream_metrics.cc
+++ b/net/socket_stream/socket_stream_metrics.cc
@@ -70,16 +70,18 @@
void SocketStreamMetrics::OnClose() {
base::TimeTicks closed_time = base::TimeTicks::Now();
- UMA_HISTOGRAM_LONG_TIMES("Net.SocketStream.Duration",
- closed_time - connect_establish_time_);
- UMA_HISTOGRAM_COUNTS("Net.SocketStream.ReceivedBytes",
- received_bytes_);
- UMA_HISTOGRAM_COUNTS("Net.SocketStream.ReceivedCounts",
- received_counts_);
- UMA_HISTOGRAM_COUNTS("Net.SocketStream.SentBytes",
- sent_bytes_);
- UMA_HISTOGRAM_COUNTS("Net.SocketStream.SentCounts",
- sent_counts_);
+ if (!connect_establish_time_.is_null()) {
+ UMA_HISTOGRAM_LONG_TIMES("Net.SocketStream.Duration",
+ closed_time - connect_establish_time_);
+ UMA_HISTOGRAM_COUNTS("Net.SocketStream.ReceivedBytes",
+ received_bytes_);
+ UMA_HISTOGRAM_COUNTS("Net.SocketStream.ReceivedCounts",
+ received_counts_);
+ UMA_HISTOGRAM_COUNTS("Net.SocketStream.SentBytes",
+ sent_bytes_);
+ UMA_HISTOGRAM_COUNTS("Net.SocketStream.SentCounts",
+ sent_counts_);
+ }
}
void SocketStreamMetrics::CountProtocolType(ProtocolType protocol_type) {
diff --git a/net/socket_stream/socket_stream_unittest.cc b/net/socket_stream/socket_stream_unittest.cc
index 76d59e9..d32654a 100644
--- a/net/socket_stream/socket_stream_unittest.cc
+++ b/net/socket_stream/socket_stream_unittest.cc
@@ -5,9 +5,10 @@
#include <string>
#include <vector>
-#include "net/base/load_log.h"
-#include "net/base/load_log_unittest.h"
+#include "base/callback.h"
#include "net/base/mock_host_resolver.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
#include "net/base/test_completion_callback.h"
#include "net/socket/socket_test_util.h"
#include "net/socket_stream/socket_stream.h"
@@ -146,8 +147,140 @@
namespace net {
class SocketStreamTest : public PlatformTest {
+ public:
+ virtual ~SocketStreamTest() {}
+ virtual void SetUp() {
+ mock_socket_factory_.reset();
+ handshake_request_ = kWebSocketHandshakeRequest;
+ handshake_response_ = kWebSocketHandshakeResponse;
+ }
+ virtual void TearDown() {
+ mock_socket_factory_.reset();
+ }
+
+ virtual void SetWebSocketHandshakeMessage(
+ const char* request, const char* response) {
+ handshake_request_ = request;
+ handshake_response_ = response;
+ }
+ virtual void AddWebSocketMessage(const std::string& message) {
+ messages_.push_back(message);
+ }
+
+ virtual MockClientSocketFactory* GetMockClientSocketFactory() {
+ mock_socket_factory_.reset(new MockClientSocketFactory);
+ return mock_socket_factory_.get();
+ }
+
+ virtual void DoSendWebSocketHandshake(SocketStreamEvent* event) {
+ event->socket->SendData(
+ handshake_request_.data(), handshake_request_.size());
+ }
+
+ virtual void DoCloseFlushPendingWriteTest(SocketStreamEvent* event) {
+ // handshake response received.
+ for (size_t i = 0; i < messages_.size(); i++) {
+ std::vector<char> frame;
+ frame.push_back('\0');
+ frame.insert(frame.end(), messages_[i].begin(), messages_[i].end());
+ frame.push_back('\xff');
+ EXPECT_TRUE(event->socket->SendData(&frame[0], frame.size()));
+ }
+ // Actual ClientSocket close must happen after all frames queued by
+ // SendData above are sent out.
+ event->socket->Close();
+ }
+
+ static const char* kWebSocketHandshakeRequest;
+ static const char* kWebSocketHandshakeResponse;
+
+ private:
+ std::string handshake_request_;
+ std::string handshake_response_;
+ std::vector<std::string> messages_;
+
+ scoped_ptr<MockClientSocketFactory> mock_socket_factory_;
};
+const char* SocketStreamTest::kWebSocketHandshakeRequest =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n"
+ "Origin: http://example.com\r\n"
+ "\r\n"
+ "^n:ds[4U";
+
+const char* SocketStreamTest::kWebSocketHandshakeResponse =
+ "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Location: ws://example.com/demo\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "\r\n"
+ "8jKS'y:G*Co,Wxa-";
+
+TEST_F(SocketStreamTest, CloseFlushPendingWrite) {
+ TestCompletionCallback callback;
+
+ scoped_ptr<SocketStreamEventRecorder> delegate(
+ new SocketStreamEventRecorder(&callback));
+ // Necessary for NewCallback.
+ SocketStreamTest* test = this;
+ delegate->SetOnConnected(NewCallback(
+ test, &SocketStreamTest::DoSendWebSocketHandshake));
+ delegate->SetOnReceivedData(NewCallback(
+ test, &SocketStreamTest::DoCloseFlushPendingWriteTest));
+
+ scoped_refptr<SocketStream> socket_stream =
+ new SocketStream(GURL("ws://example.com/demo"), delegate.get());
+
+ socket_stream->set_context(new TestURLRequestContext());
+ socket_stream->SetHostResolver(new MockHostResolver());
+
+ MockWrite data_writes[] = {
+ MockWrite(SocketStreamTest::kWebSocketHandshakeRequest),
+ MockWrite(true, "\0message1\xff", 10),
+ MockWrite(true, "\0message2\xff", 10)
+ };
+ MockRead data_reads[] = {
+ MockRead(SocketStreamTest::kWebSocketHandshakeResponse),
+ // Server doesn't close the connection after handshake.
+ MockRead(true, ERR_IO_PENDING)
+ };
+ AddWebSocketMessage("message1");
+ AddWebSocketMessage("message2");
+
+ scoped_refptr<DelayedSocketData> data_provider(
+ new DelayedSocketData(1,
+ data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes)));
+
+ MockClientSocketFactory* mock_socket_factory =
+ GetMockClientSocketFactory();
+ mock_socket_factory->AddSocketDataProvider(data_provider.get());
+
+ socket_stream->SetClientSocketFactory(mock_socket_factory);
+
+ socket_stream->Connect();
+
+ callback.WaitForResult();
+
+ const std::vector<SocketStreamEvent>& events = delegate->GetSeenEvents();
+ EXPECT_EQ(6U, events.size());
+
+ EXPECT_EQ(SocketStreamEvent::EVENT_CONNECTED, events[0].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_SENT_DATA, events[1].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_RECEIVED_DATA, events[2].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_SENT_DATA, events[3].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_SENT_DATA, events[4].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_CLOSE, events[5].event_type);
+}
+
TEST_F(SocketStreamTest, BasicAuthProxy) {
MockClientSocketFactory mock_socket_factory;
MockWrite data_writes1[] = {
@@ -160,7 +293,8 @@
MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
MockRead("\r\n"),
};
- StaticSocketDataProvider data1(data_reads1, data_writes1);
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
mock_socket_factory.AddSocketDataProvider(&data1);
MockWrite data_writes2[] = {
@@ -174,7 +308,8 @@
MockRead("Proxy-agent: Apache/2.2.8\r\n"),
MockRead("\r\n"),
};
- StaticSocketDataProvider data2(data_reads2, data_writes2);
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
mock_socket_factory.AddSocketDataProvider(&data2);
TestCompletionCallback callback;
@@ -208,12 +343,7 @@
EXPECT_EQ(SocketStreamEvent::EVENT_CONNECTED, events[1].event_type);
EXPECT_EQ(SocketStreamEvent::EVENT_CLOSE, events[2].event_type);
- // The first and last entries of the LoadLog should be for
- // SOCKET_STREAM_CONNECT.
- EXPECT_TRUE(LogContainsBeginEvent(
- *socket_stream->load_log(), 0, LoadLog::TYPE_SOCKET_STREAM_CONNECT));
- EXPECT_TRUE(LogContainsEndEvent(
- *socket_stream->load_log(), -1, LoadLog::TYPE_SOCKET_STREAM_CONNECT));
+ // TODO(eroman): Add back NetLogTest here...
}
} // namespace net
diff --git a/net/spdy/spdy_bitmasks.h b/net/spdy/spdy_bitmasks.h
new file mode 100644
index 0000000..03752e3
--- /dev/null
+++ b/net/spdy/spdy_bitmasks.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2009 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.
+
+#ifndef NET_SPDY_SPDY_BITMASKS_H_
+#define NET_SPDY_SPDY_BITMASKS_H_
+
+namespace spdy {
+
+// StreamId mask from the SpdyHeader
+const unsigned int kStreamIdMask = 0x7fffffff;
+
+// Control flag mask from the SpdyHeader
+const unsigned int kControlFlagMask = 0x8000;
+
+// Priority mask from the SYN_FRAME
+const unsigned int kPriorityMask = 0xc0;
+
+// Mask the lower 24 bits.
+const unsigned int kLengthMask = 0xffffff;
+
+// Mask the Id from a SETTINGS id.
+const unsigned int kSettingsIdMask = 0xffffff;
+
+// Legal flags on data packets.
+const int kDataFlagsMask = 0x03;
+
+// Legal flags on control packets.
+const int kControlFlagsMask = 0x03;
+
+} // namespace spdy
+
+#endif // NET_SPDY_SPDY_BITMASKS_H_
diff --git a/net/spdy/spdy_frame_builder.cc b/net/spdy/spdy_frame_builder.cc
new file mode 100644
index 0000000..7d21b82
--- /dev/null
+++ b/net/spdy/spdy_frame_builder.cc
@@ -0,0 +1,181 @@
+// Copyright (c) 2009 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 <limits>
+
+#include "net/spdy/spdy_frame_builder.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace spdy {
+
+// We mark a read only SpdyFrameBuilder with a special capacity_.
+static const size_t kCapacityReadOnly = std::numeric_limits<size_t>::max();
+
+SpdyFrameBuilder::SpdyFrameBuilder()
+ : buffer_(NULL),
+ capacity_(0),
+ length_(0),
+ variable_buffer_offset_(0) {
+ Resize(kInitialPayload);
+}
+
+SpdyFrameBuilder::SpdyFrameBuilder(const char* data, int data_len)
+ : buffer_(const_cast<char*>(data)),
+ capacity_(kCapacityReadOnly),
+ length_(data_len),
+ variable_buffer_offset_(0) {
+}
+
+SpdyFrameBuilder::~SpdyFrameBuilder() {
+ if (capacity_ != kCapacityReadOnly)
+ delete[] buffer_;
+}
+
+bool SpdyFrameBuilder::ReadUInt16(void** iter, uint16* result) const {
+ DCHECK(iter);
+ if (!*iter)
+ *iter = const_cast<char*>(buffer_);
+
+ if (!IteratorHasRoomFor(*iter, sizeof(*result)))
+ return false;
+
+ *result = ntohs(*(reinterpret_cast<uint16*>(*iter)));
+
+ UpdateIter(iter, sizeof(*result));
+ return true;
+}
+
+bool SpdyFrameBuilder::ReadUInt32(void** iter, uint32* result) const {
+ DCHECK(iter);
+ if (!*iter)
+ *iter = const_cast<char*>(buffer_);
+
+ if (!IteratorHasRoomFor(*iter, sizeof(*result)))
+ return false;
+
+ *result = ntohl(*(reinterpret_cast<uint32*>(*iter)));
+
+ UpdateIter(iter, sizeof(*result));
+ return true;
+}
+
+bool SpdyFrameBuilder::ReadString(void** iter, std::string* result) const {
+ DCHECK(iter);
+
+ uint16 len;
+ if (!ReadUInt16(iter, &len))
+ return false;
+
+ if (!IteratorHasRoomFor(*iter, len))
+ return false;
+
+ char* chars = reinterpret_cast<char*>(*iter);
+ result->assign(chars, len);
+
+ UpdateIter(iter, len);
+ return true;
+}
+
+bool SpdyFrameBuilder::ReadBytes(void** iter, const char** data,
+ uint16 length) const {
+ DCHECK(iter);
+ DCHECK(data);
+
+ if (!IteratorHasRoomFor(*iter, length))
+ return false;
+
+ *data = reinterpret_cast<const char*>(*iter);
+
+ UpdateIter(iter, length);
+ return true;
+}
+
+bool SpdyFrameBuilder::ReadData(void** iter, const char** data,
+ uint16* length) const {
+ DCHECK(iter);
+ DCHECK(data);
+ DCHECK(length);
+
+ if (!ReadUInt16(iter, length))
+ return false;
+
+ return ReadBytes(iter, data, *length);
+}
+
+char* SpdyFrameBuilder::BeginWrite(size_t length) {
+ size_t offset = length_;
+ size_t needed_size = length_ + length;
+ if (needed_size > capacity_ && !Resize(std::max(capacity_ * 2, needed_size)))
+ return NULL;
+
+#ifdef ARCH_CPU_64_BITS
+ DCHECK_LE(length, std::numeric_limits<uint32>::max());
+#endif
+
+ return buffer_ + offset;
+}
+
+void SpdyFrameBuilder::EndWrite(char* dest, int length) {
+}
+
+bool SpdyFrameBuilder::WriteBytes(const void* data, uint16 data_len) {
+ DCHECK(capacity_ != kCapacityReadOnly);
+
+ char* dest = BeginWrite(data_len);
+ if (!dest)
+ return false;
+
+ memcpy(dest, data, data_len);
+
+ EndWrite(dest, data_len);
+ length_ += data_len;
+ return true;
+}
+
+bool SpdyFrameBuilder::WriteString(const std::string& value) {
+ if (value.size() > 0xffff)
+ return false;
+
+ if (!WriteUInt16(static_cast<int>(value.size())))
+ return false;
+
+ return WriteBytes(value.data(), static_cast<uint16>(value.size()));
+}
+
+char* SpdyFrameBuilder::BeginWriteData(uint16 length) {
+ DCHECK_EQ(variable_buffer_offset_, 0U) <<
+ "There can only be one variable buffer in a SpdyFrameBuilder";
+
+ if (!WriteUInt16(length))
+ return NULL;
+
+ char *data_ptr = BeginWrite(length);
+ if (!data_ptr)
+ return NULL;
+
+ variable_buffer_offset_ = data_ptr - buffer_ - sizeof(int);
+
+ // EndWrite doesn't necessarily have to be called after the write operation,
+ // so we call it here to pad out what the caller will eventually write.
+ EndWrite(data_ptr, length);
+ return data_ptr;
+}
+
+bool SpdyFrameBuilder::Resize(size_t new_capacity) {
+ if (new_capacity < capacity_)
+ return true;
+
+ char* p = new char[new_capacity];
+ if (buffer_) {
+ memcpy(p, buffer_, capacity_);
+ delete[] buffer_;
+ }
+ if (!p && new_capacity > 0)
+ return false;
+ buffer_ = p;
+ capacity_ = new_capacity;
+ return true;
+}
+
+} // namespace spdy
diff --git a/net/spdy/spdy_frame_builder.h b/net/spdy/spdy_frame_builder.h
new file mode 100644
index 0000000..5b70437
--- /dev/null
+++ b/net/spdy/spdy_frame_builder.h
@@ -0,0 +1,163 @@
+// Copyright (c) 2009 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.
+
+#ifndef NET_SPDY_FRAME_BUILDER_H_
+#define NET_SPDY_FRAME_BUILDER_H_
+
+#ifdef WIN32
+#include <winsock2.h> // for htonl() functions
+#else
+#include <arpa/inet.h>
+#endif
+
+#include <string>
+
+#include "base/logging.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace spdy {
+
+// This class provides facilities for basic binary value packing and unpacking
+// into Spdy frames.
+//
+// The SpdyFrameBuilder supports appending primitive values (int, string, etc)
+// to a frame instance. The SpdyFrameBuilder grows its internal memory buffer
+// dynamically to hold the sequence of primitive values. The internal memory
+// buffer is exposed as the "data" of the SpdyFrameBuilder.
+//
+// When reading from a SpdyFrameBuilder the consumer must know what value types
+// to read and in what order to read them as the SpdyFrameBuilder does not keep
+// track of the type of data written to it.
+class SpdyFrameBuilder {
+ public:
+ SpdyFrameBuilder();
+ ~SpdyFrameBuilder();
+
+ // Initializes a SpdyFrameBuilder from a const block of data. The data is
+ // not copied; instead the data is merely referenced by this
+ // SpdyFrameBuilder. Only const methods should be used when initialized
+ // this way.
+ SpdyFrameBuilder(const char* data, int data_len);
+
+ // Returns the size of the SpdyFrameBuilder's data.
+ int length() const { return length_; }
+
+ // Takes the buffer from the SpdyFrameBuilder.
+ SpdyFrame* take() {
+ SpdyFrame* rv = new SpdyFrame(buffer_, true);
+ buffer_ = NULL;
+ capacity_ = 0;
+ length_ = 0;
+ return rv;
+ }
+
+ // Methods for reading the payload of the SpdyFrameBuilder. To read from the
+ // start of the SpdyFrameBuilder, initialize *iter to NULL. If successful,
+ // these methods return true. Otherwise, false is returned to indicate that
+ // the result could not be extracted.
+ bool ReadUInt16(void** iter, uint16* result) const;
+ bool ReadUInt32(void** iter, uint32* result) const;
+ bool ReadString(void** iter, std::string* result) const;
+ bool ReadBytes(void** iter, const char** data, uint16 length) const;
+ bool ReadData(void** iter, const char** data, uint16* length) const;
+
+ // Methods for adding to the payload. These values are appended to the end
+ // of the SpdyFrameBuilder payload. When reading values, you must read them
+ // in the order they were added. Note - binary integers are converted from
+ // host to network form.
+ bool WriteUInt16(uint16 value) {
+ value = htons(value);
+ return WriteBytes(&value, sizeof(value));
+ }
+ bool WriteUInt32(uint32 value) {
+ value = htonl(value);
+ return WriteBytes(&value, sizeof(value));
+ }
+ bool WriteString(const std::string& value);
+ bool WriteBytes(const void* data, uint16 data_len);
+
+ // Write an integer to a particular offset in the data buffer.
+ bool WriteUInt32ToOffset(int offset, uint32 value) {
+ value = htonl(value);
+ return WriteBytesToOffset(offset, &value, sizeof(value));
+ }
+
+ // Write to a particular offset in the data buffer.
+ bool WriteBytesToOffset(int offset, const void* data, uint32 data_len) {
+ if (offset + data_len > length_)
+ return false;
+ char *ptr = buffer_ + offset;
+ memcpy(ptr, data, data_len);
+ return true;
+ }
+
+ // Allows the caller to write data directly into the SpdyFrameBuilder.
+ // This saves a copy when the data is not already available in a buffer.
+ // The caller must not write more than the length it declares it will.
+ // Use ReadData to get the data.
+ // Returns NULL on failure.
+ //
+ // The returned pointer will only be valid until the next write operation
+ // on this SpdyFrameBuilder.
+ char* BeginWriteData(uint16 length);
+
+ // Returns true if the given iterator could point to data with the given
+ // length. If there is no room for the given data before the end of the
+ // payload, returns false.
+ bool IteratorHasRoomFor(const void* iter, int len) const {
+ const char* end_of_region = reinterpret_cast<const char*>(iter) + len;
+ if (len < 0 ||
+ iter < buffer_ ||
+ iter > end_of_payload() ||
+ iter > end_of_region ||
+ end_of_region > end_of_payload())
+ return false;
+
+ // Watch out for overflow in pointer calculation, which wraps.
+ return (iter <= end_of_region) && (end_of_region <= end_of_payload());
+ }
+
+ protected:
+ size_t capacity() const {
+ return capacity_;
+ }
+
+ const char* end_of_payload() const { return buffer_ + length_; }
+
+ // Resizes the buffer for use when writing the specified amount of data. The
+ // location that the data should be written at is returned, or NULL if there
+ // was an error. Call EndWrite with the returned offset and the given length
+ // to pad out for the next write.
+ char* BeginWrite(size_t length);
+
+ // Completes the write operation by padding the data with NULL bytes until it
+ // is padded. Should be paired with BeginWrite, but it does not necessarily
+ // have to be called after the data is written.
+ void EndWrite(char* dest, int length);
+
+ // Resize the capacity, note that the input value should include the size of
+ // the header: new_capacity = sizeof(Header) + desired_payload_capacity.
+ // A new failure will cause a Resize failure... and caller should check
+ // the return result for true (i.e., successful resizing).
+ bool Resize(size_t new_capacity);
+
+ // Moves the iterator by the given number of bytes.
+ static void UpdateIter(void** iter, int bytes) {
+ *iter = static_cast<char*>(*iter) + bytes;
+ }
+
+ // Initial size of the payload.
+ static const int kInitialPayload = 1024;
+
+ private:
+ char* buffer_;
+ size_t capacity_; // Allocation size of payload (or -1 if buffer is const).
+ size_t length_; // current length of the buffer
+ size_t variable_buffer_offset_; // IF non-zero, then offset to a buffer.
+};
+
+} // namespace spdy
+
+#endif // NET_SPDY_FRAME_BUILDER_H_
+
diff --git a/net/spdy/spdy_framer.cc b/net/spdy/spdy_framer.cc
new file mode 100644
index 0000000..cd622b8
--- /dev/null
+++ b/net/spdy/spdy_framer.cc
@@ -0,0 +1,1113 @@
+// 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 "net/spdy/spdy_framer.h"
+
+#include "base/scoped_ptr.h"
+#include "base/stats_counters.h"
+
+#include "net/spdy/spdy_frame_builder.h"
+#include "net/spdy/spdy_bitmasks.h"
+
+#if defined(USE_SYSTEM_ZLIB)
+#include <zlib.h>
+#else
+#include "third_party/zlib/zlib.h"
+#endif
+
+namespace spdy {
+
+// The initial size of the control frame buffer; this is used internally
+// as we parse through control frames.
+static const size_t kControlFrameBufferInitialSize = 32 * 1024;
+// The maximum size of the control frame buffer that we support.
+// TODO(mbelshe): We should make this stream-based so there are no limits.
+static const size_t kControlFrameBufferMaxSize = 64 * 1024;
+
+// By default is compression on or off.
+bool SpdyFramer::compression_default_ = true;
+
+#ifdef DEBUG_SPDY_STATE_CHANGES
+#define CHANGE_STATE(newstate) \
+{ \
+ do { \
+ LOG(INFO) << "Changing state from: " \
+ << StateToString(state_) \
+ << " to " << StateToString(newstate) << "\n"; \
+ state_ = newstate; \
+ } while (false); \
+}
+#else
+#define CHANGE_STATE(newstate) (state_ = newstate)
+#endif
+
+SpdyFramer::SpdyFramer()
+ : state_(SPDY_RESET),
+ error_code_(SPDY_NO_ERROR),
+ remaining_payload_(0),
+ remaining_control_payload_(0),
+ current_frame_buffer_(NULL),
+ current_frame_len_(0),
+ current_frame_capacity_(0),
+ enable_compression_(compression_default_),
+ visitor_(NULL) {
+}
+
+SpdyFramer::~SpdyFramer() {
+ if (header_compressor_.get()) {
+ deflateEnd(header_compressor_.get());
+ }
+ if (header_decompressor_.get()) {
+ inflateEnd(header_decompressor_.get());
+ }
+ CleanupStreamCompressorsAndDecompressors();
+ delete [] current_frame_buffer_;
+}
+
+void SpdyFramer::Reset() {
+ state_ = SPDY_RESET;
+ error_code_ = SPDY_NO_ERROR;
+ remaining_payload_ = 0;
+ remaining_control_payload_ = 0;
+ current_frame_len_ = 0;
+ if (current_frame_capacity_ != kControlFrameBufferInitialSize) {
+ delete [] current_frame_buffer_;
+ current_frame_buffer_ = 0;
+ current_frame_capacity_ = 0;
+ ExpandControlFrameBuffer(kControlFrameBufferInitialSize);
+ }
+}
+
+const char* SpdyFramer::StateToString(int state) {
+ switch (state) {
+ case SPDY_ERROR:
+ return "ERROR";
+ case SPDY_DONE:
+ return "DONE";
+ case SPDY_AUTO_RESET:
+ return "AUTO_RESET";
+ case SPDY_RESET:
+ return "RESET";
+ case SPDY_READING_COMMON_HEADER:
+ return "READING_COMMON_HEADER";
+ case SPDY_INTERPRET_CONTROL_FRAME_COMMON_HEADER:
+ return "INTERPRET_CONTROL_FRAME_COMMON_HEADER";
+ case SPDY_CONTROL_FRAME_PAYLOAD:
+ return "CONTROL_FRAME_PAYLOAD";
+ case SPDY_IGNORE_REMAINING_PAYLOAD:
+ return "IGNORE_REMAINING_PAYLOAD";
+ case SPDY_FORWARD_STREAM_FRAME:
+ return "FORWARD_STREAM_FRAME";
+ }
+ return "UNKNOWN_STATE";
+}
+
+size_t SpdyFramer::BytesSafeToRead() const {
+ switch (state_) {
+ case SPDY_ERROR:
+ case SPDY_DONE:
+ case SPDY_AUTO_RESET:
+ case SPDY_RESET:
+ return 0;
+ case SPDY_READING_COMMON_HEADER:
+ DCHECK_LT(current_frame_len_, SpdyFrame::size());
+ return SpdyFrame::size() - current_frame_len_;
+ case SPDY_INTERPRET_CONTROL_FRAME_COMMON_HEADER:
+ return 0;
+ case SPDY_CONTROL_FRAME_PAYLOAD:
+ case SPDY_IGNORE_REMAINING_PAYLOAD:
+ case SPDY_FORWARD_STREAM_FRAME:
+ return remaining_payload_;
+ }
+ // We should never get to here.
+ return 0;
+}
+
+void SpdyFramer::set_error(SpdyError error) {
+ DCHECK(visitor_);
+ error_code_ = error;
+ CHANGE_STATE(SPDY_ERROR);
+ visitor_->OnError(this);
+}
+
+const char* SpdyFramer::ErrorCodeToString(int error_code) {
+ switch (error_code) {
+ case SPDY_NO_ERROR:
+ return "NO_ERROR";
+ case SPDY_INVALID_CONTROL_FRAME:
+ return "INVALID_CONTROL_FRAME";
+ case SPDY_CONTROL_PAYLOAD_TOO_LARGE:
+ return "CONTROL_PAYLOAD_TOO_LARGE";
+ case SPDY_ZLIB_INIT_FAILURE:
+ return "ZLIB_INIT_FAILURE";
+ case SPDY_UNSUPPORTED_VERSION:
+ return "UNSUPPORTED_VERSION";
+ case SPDY_DECOMPRESS_FAILURE:
+ return "DECOMPRESS_FAILURE";
+ case SPDY_COMPRESS_FAILURE:
+ return "COMPRESS_FAILURE";
+ }
+ return "UNKNOWN_ERROR";
+}
+
+size_t SpdyFramer::ProcessInput(const char* data, size_t len) {
+ DCHECK(visitor_);
+ DCHECK(data);
+
+ size_t original_len = len;
+ while (len != 0) {
+ switch (state_) {
+ case SPDY_ERROR:
+ case SPDY_DONE:
+ goto bottom;
+
+ case SPDY_AUTO_RESET:
+ case SPDY_RESET:
+ Reset();
+ CHANGE_STATE(SPDY_READING_COMMON_HEADER);
+ continue;
+
+ case SPDY_READING_COMMON_HEADER: {
+ int bytes_read = ProcessCommonHeader(data, len);
+ len -= bytes_read;
+ data += bytes_read;
+ continue;
+ }
+
+ // Arguably, this case is not necessary, as no bytes are consumed here.
+ // I felt it was a nice partitioning, however (which probably indicates
+ // that it should be refactored into its own function!)
+ case SPDY_INTERPRET_CONTROL_FRAME_COMMON_HEADER:
+ ProcessControlFrameHeader();
+ continue;
+
+ case SPDY_CONTROL_FRAME_PAYLOAD: {
+ int bytes_read = ProcessControlFramePayload(data, len);
+ len -= bytes_read;
+ data += bytes_read;
+ }
+ // intentional fallthrough
+ case SPDY_IGNORE_REMAINING_PAYLOAD:
+ // control frame has too-large payload
+ // intentional fallthrough
+ case SPDY_FORWARD_STREAM_FRAME: {
+ int bytes_read = ProcessDataFramePayload(data, len);
+ len -= bytes_read;
+ data += bytes_read;
+ continue;
+ }
+ default:
+ break;
+ }
+ }
+ bottom:
+ return original_len - len;
+}
+
+size_t SpdyFramer::ProcessCommonHeader(const char* data, size_t len) {
+ // This should only be called when we're in the SPDY_READING_COMMON_HEADER
+ // state.
+ DCHECK_EQ(state_, SPDY_READING_COMMON_HEADER);
+
+ int original_len = len;
+ SpdyFrame current_frame(current_frame_buffer_, false);
+
+ do {
+ if (current_frame_len_ < SpdyFrame::size()) {
+ size_t bytes_desired = SpdyFrame::size() - current_frame_len_;
+ size_t bytes_to_append = std::min(bytes_desired, len);
+ char* header_buffer = current_frame_buffer_;
+ memcpy(&header_buffer[current_frame_len_], data, bytes_to_append);
+ current_frame_len_ += bytes_to_append;
+ data += bytes_to_append;
+ len -= bytes_to_append;
+ // Check for an empty data frame.
+ if (current_frame_len_ == SpdyFrame::size() &&
+ !current_frame.is_control_frame() &&
+ current_frame.length() == 0) {
+ if (current_frame.flags() & CONTROL_FLAG_FIN) {
+ SpdyDataFrame data_frame(current_frame_buffer_, false);
+ visitor_->OnStreamFrameData(data_frame.stream_id(), NULL, 0);
+ }
+ CHANGE_STATE(SPDY_AUTO_RESET);
+ }
+ break;
+ }
+ remaining_payload_ = current_frame.length();
+
+ // This is just a sanity check for help debugging early frame errors.
+ if (remaining_payload_ > 1000000u) {
+ LOG(WARNING) <<
+ "Unexpectedly large frame. Spdy session is likely corrupt.";
+ }
+
+ // if we're here, then we have the common header all received.
+ if (!current_frame.is_control_frame())
+ CHANGE_STATE(SPDY_FORWARD_STREAM_FRAME);
+ else
+ CHANGE_STATE(SPDY_INTERPRET_CONTROL_FRAME_COMMON_HEADER);
+ } while (false);
+
+ return original_len - len;
+}
+
+void SpdyFramer::ProcessControlFrameHeader() {
+ DCHECK_EQ(SPDY_NO_ERROR, error_code_);
+ DCHECK_LE(SpdyFrame::size(), current_frame_len_);
+ SpdyControlFrame current_control_frame(current_frame_buffer_, false);
+
+ // We check version before we check validity: version can never be 'invalid',
+ // it can only be unsupported.
+ if (current_control_frame.version() != kSpdyProtocolVersion) {
+ set_error(SPDY_UNSUPPORTED_VERSION);
+ return;
+ }
+
+ // Next up, check to see if we have valid data. This should be after version
+ // checking (otherwise if the the type were out of bounds due to a version
+ // upgrade we would misclassify the error) and before checking the type
+ // (type can definitely be out of bounds)
+ if (!current_control_frame.AppearsToBeAValidControlFrame()) {
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ return;
+ }
+
+ // Do some sanity checking on the control frame sizes.
+ switch (current_control_frame.type()) {
+ case SYN_STREAM:
+ if (current_control_frame.length() <
+ SpdySynStreamControlFrame::size() - SpdyControlFrame::size())
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ break;
+ case SYN_REPLY:
+ if (current_control_frame.length() <
+ SpdySynReplyControlFrame::size() - SpdyControlFrame::size())
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ break;
+ case RST_STREAM:
+ if (current_control_frame.length() !=
+ SpdyRstStreamControlFrame::size() - SpdyFrame::size())
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ break;
+ case NOOP:
+ // NOOP. Swallow it.
+ CHANGE_STATE(SPDY_AUTO_RESET);
+ return;
+ case GOAWAY:
+ if (current_control_frame.length() !=
+ SpdyGoAwayControlFrame::size() - SpdyFrame::size())
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ break;
+ case SETTINGS:
+ if (current_control_frame.length() <
+ SpdySettingsControlFrame::size() - SpdyControlFrame::size())
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ break;
+ case WINDOW_UPDATE:
+ if (current_control_frame.length() !=
+ SpdyWindowUpdateControlFrame::size() - SpdyFrame::size())
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ break;
+ default:
+ LOG(WARNING) << "Valid spdy control frame with unknown type: "
+ << current_control_frame.type();
+ DCHECK(false);
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ break;
+ }
+
+ remaining_control_payload_ = current_control_frame.length();
+ if (remaining_control_payload_ > kControlFrameBufferMaxSize) {
+ set_error(SPDY_CONTROL_PAYLOAD_TOO_LARGE);
+ return;
+ }
+
+ ExpandControlFrameBuffer(remaining_control_payload_);
+ CHANGE_STATE(SPDY_CONTROL_FRAME_PAYLOAD);
+}
+
+size_t SpdyFramer::ProcessControlFramePayload(const char* data, size_t len) {
+ size_t original_len = len;
+ do {
+ if (remaining_control_payload_) {
+ size_t amount_to_consume = std::min(remaining_control_payload_, len);
+ memcpy(¤t_frame_buffer_[current_frame_len_], data,
+ amount_to_consume);
+ current_frame_len_ += amount_to_consume;
+ data += amount_to_consume;
+ len -= amount_to_consume;
+ remaining_control_payload_ -= amount_to_consume;
+ remaining_payload_ -= amount_to_consume;
+ if (remaining_control_payload_)
+ break;
+ }
+ SpdyControlFrame control_frame(current_frame_buffer_, false);
+ visitor_->OnControl(&control_frame);
+
+ // If this is a FIN, tell the caller.
+ if (control_frame.type() == SYN_REPLY &&
+ control_frame.flags() & CONTROL_FLAG_FIN) {
+ visitor_->OnStreamFrameData(reinterpret_cast<SpdySynReplyControlFrame*>(
+ &control_frame)->stream_id(),
+ NULL, 0);
+ }
+
+ CHANGE_STATE(SPDY_IGNORE_REMAINING_PAYLOAD);
+ } while (false);
+ return original_len - len;
+}
+
+size_t SpdyFramer::ProcessDataFramePayload(const char* data, size_t len) {
+ size_t original_len = len;
+
+ SpdyDataFrame current_data_frame(current_frame_buffer_, false);
+ if (remaining_payload_) {
+ size_t amount_to_forward = std::min(remaining_payload_, len);
+ if (amount_to_forward && state_ != SPDY_IGNORE_REMAINING_PAYLOAD) {
+ if (current_data_frame.flags() & DATA_FLAG_COMPRESSED) {
+ z_stream* decompressor =
+ GetStreamDecompressor(current_data_frame.stream_id());
+ if (!decompressor)
+ return 0;
+
+ size_t decompressed_max_size = amount_to_forward * 100;
+ scoped_array<char> decompressed(new char[decompressed_max_size]);
+ decompressor->next_in = reinterpret_cast<Bytef*>(
+ const_cast<char*>(data));
+ decompressor->avail_in = amount_to_forward;
+ decompressor->next_out =
+ reinterpret_cast<Bytef*>(decompressed.get());
+ decompressor->avail_out = decompressed_max_size;
+
+ int rv = inflate(decompressor, Z_SYNC_FLUSH);
+ if (rv != Z_OK) {
+ LOG(WARNING) << "inflate failure: " << rv;
+ set_error(SPDY_DECOMPRESS_FAILURE);
+ return 0;
+ }
+ size_t decompressed_size = decompressed_max_size -
+ decompressor->avail_out;
+
+ // Only inform the visitor if there is data.
+ if (decompressed_size)
+ visitor_->OnStreamFrameData(current_data_frame.stream_id(),
+ decompressed.get(),
+ decompressed_size);
+ amount_to_forward -= decompressor->avail_in;
+ } else {
+ // The data frame was not compressed.
+ // Only inform the visitor if there is data.
+ if (amount_to_forward)
+ visitor_->OnStreamFrameData(current_data_frame.stream_id(),
+ data, amount_to_forward);
+ }
+ }
+ data += amount_to_forward;
+ len -= amount_to_forward;
+ remaining_payload_ -= amount_to_forward;
+
+ // If the FIN flag is set, and there is no more data in this data
+ // frame, inform the visitor of EOF via a 0-length data frame.
+ if (!remaining_payload_ &&
+ current_data_frame.flags() & DATA_FLAG_FIN) {
+ visitor_->OnStreamFrameData(current_data_frame.stream_id(), NULL, 0);
+ CleanupDecompressorForStream(current_data_frame.stream_id());
+ }
+ } else {
+ CHANGE_STATE(SPDY_AUTO_RESET);
+ }
+ return original_len - len;
+}
+
+void SpdyFramer::ExpandControlFrameBuffer(size_t size) {
+ DCHECK_LT(size, kControlFrameBufferMaxSize);
+ if (size < current_frame_capacity_)
+ return;
+
+ int alloc_size = size + SpdyFrame::size();
+ char* new_buffer = new char[alloc_size];
+ memcpy(new_buffer, current_frame_buffer_, current_frame_len_);
+ delete [] current_frame_buffer_;
+ current_frame_capacity_ = alloc_size;
+ current_frame_buffer_ = new_buffer;
+}
+
+bool SpdyFramer::ParseHeaderBlock(const SpdyFrame* frame,
+ SpdyHeaderBlock* block) {
+ SpdyControlFrame control_frame(frame->data(), false);
+ uint32 type = control_frame.type();
+ if (type != SYN_STREAM && type != SYN_REPLY)
+ return false;
+
+ // Find the header data within the control frame.
+ scoped_ptr<SpdyFrame> decompressed_frame(DecompressFrame(*frame));
+ if (!decompressed_frame.get())
+ return false;
+
+ const char *header_data = NULL;
+ int header_length = 0;
+
+ switch (type) {
+ case SYN_STREAM:
+ {
+ SpdySynStreamControlFrame syn_frame(decompressed_frame->data(), false);
+ header_data = syn_frame.header_block();
+ header_length = syn_frame.header_block_len();
+ }
+ break;
+ case SYN_REPLY:
+ {
+ SpdySynReplyControlFrame syn_frame(decompressed_frame->data(), false);
+ header_data = syn_frame.header_block();
+ header_length = syn_frame.header_block_len();
+ }
+ break;
+ }
+
+ SpdyFrameBuilder builder(header_data, header_length);
+ void* iter = NULL;
+ uint16 num_headers;
+ if (builder.ReadUInt16(&iter, &num_headers)) {
+ int index = 0;
+ for ( ; index < num_headers; ++index) {
+ std::string name;
+ std::string value;
+ if (!builder.ReadString(&iter, &name))
+ break;
+ if (!builder.ReadString(&iter, &value))
+ break;
+ if (!name.size() || !value.size())
+ return false;
+ if (block->find(name) == block->end()) {
+ (*block)[name] = value;
+ } else {
+ return false;
+ }
+ }
+ return index == num_headers &&
+ iter == header_data + header_length;
+ }
+ return false;
+}
+
+/* static */
+bool SpdyFramer::ParseSettings(const SpdySettingsControlFrame* frame,
+ SpdySettings* settings) {
+ DCHECK_EQ(frame->type(), SETTINGS);
+ DCHECK(settings);
+
+ SpdyFrameBuilder parser(frame->header_block(), frame->header_block_len());
+ void* iter = NULL;
+ for (size_t index = 0; index < frame->num_entries(); ++index) {
+ uint32 id;
+ uint32 value;
+ if (!parser.ReadUInt32(&iter, &id))
+ return false;
+ if (!parser.ReadUInt32(&iter, &value))
+ return false;
+ settings->insert(settings->end(), std::make_pair(id, value));
+ }
+ return true;
+}
+
+SpdySynStreamControlFrame* SpdyFramer::CreateSynStream(
+ SpdyStreamId stream_id, SpdyStreamId associated_stream_id, int priority,
+ SpdyControlFlags flags, bool compressed, SpdyHeaderBlock* headers) {
+ SpdyFrameBuilder frame;
+
+ DCHECK_GT(stream_id, static_cast<SpdyStreamId>(0));
+ DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
+ DCHECK_EQ(0u, associated_stream_id & ~kStreamIdMask);
+
+ frame.WriteUInt16(kControlFlagMask | kSpdyProtocolVersion);
+ frame.WriteUInt16(SYN_STREAM);
+ frame.WriteUInt32(0); // Placeholder for the length and flags
+ frame.WriteUInt32(stream_id);
+ frame.WriteUInt32(associated_stream_id);
+ frame.WriteUInt16(ntohs(priority) << 6); // Priority.
+
+ frame.WriteUInt16(headers->size()); // Number of headers.
+ SpdyHeaderBlock::iterator it;
+ for (it = headers->begin(); it != headers->end(); ++it) {
+ bool wrote_header;
+ wrote_header = frame.WriteString(it->first);
+ wrote_header &= frame.WriteString(it->second);
+ DCHECK(wrote_header);
+ }
+
+ // Write the length and flags.
+ size_t length = frame.length() - SpdyFrame::size();
+ DCHECK_EQ(0u, length & ~static_cast<size_t>(kLengthMask));
+ FlagsAndLength flags_length;
+ flags_length.length_ = htonl(static_cast<uint32>(length));
+ DCHECK_EQ(0, flags & ~kControlFlagsMask);
+ flags_length.flags_[0] = flags;
+ frame.WriteBytesToOffset(4, &flags_length, sizeof(flags_length));
+
+ scoped_ptr<SpdyFrame> syn_frame(frame.take());
+ if (compressed) {
+ return reinterpret_cast<SpdySynStreamControlFrame*>(
+ CompressFrame(*syn_frame.get()));
+ }
+ return reinterpret_cast<SpdySynStreamControlFrame*>(syn_frame.release());
+}
+
+/* static */
+SpdyRstStreamControlFrame* SpdyFramer::CreateRstStream(SpdyStreamId stream_id,
+ SpdyStatusCodes status) {
+ DCHECK_GT(stream_id, 0u);
+ DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
+ DCHECK_NE(status, INVALID);
+ DCHECK_LT(status, NUM_STATUS_CODES);
+
+ SpdyFrameBuilder frame;
+ frame.WriteUInt16(kControlFlagMask | kSpdyProtocolVersion);
+ frame.WriteUInt16(RST_STREAM);
+ frame.WriteUInt32(8);
+ frame.WriteUInt32(stream_id);
+ frame.WriteUInt32(status);
+ return reinterpret_cast<SpdyRstStreamControlFrame*>(frame.take());
+}
+
+/* static */
+SpdyGoAwayControlFrame* SpdyFramer::CreateGoAway(
+ SpdyStreamId last_accepted_stream_id) {
+ DCHECK_EQ(0u, last_accepted_stream_id & ~kStreamIdMask);
+
+ SpdyFrameBuilder frame;
+ frame.WriteUInt16(kControlFlagMask | kSpdyProtocolVersion);
+ frame.WriteUInt16(GOAWAY);
+ size_t go_away_size = SpdyGoAwayControlFrame::size() - SpdyFrame::size();
+ frame.WriteUInt32(go_away_size);
+ frame.WriteUInt32(last_accepted_stream_id);
+ return reinterpret_cast<SpdyGoAwayControlFrame*>(frame.take());
+}
+
+/* static */
+SpdyWindowUpdateControlFrame* SpdyFramer::CreateWindowUpdate(
+ SpdyStreamId stream_id,
+ uint32 delta_window_size) {
+ DCHECK_GT(stream_id, 0u);
+ DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
+ DCHECK_GT(delta_window_size, 0u);
+ DCHECK_LT(delta_window_size, 0x80000000u); // 2^31
+
+ SpdyFrameBuilder frame;
+ frame.WriteUInt16(kControlFlagMask | kSpdyProtocolVersion);
+ frame.WriteUInt16(WINDOW_UPDATE);
+ size_t window_update_size = SpdyWindowUpdateControlFrame::size() -
+ SpdyFrame::size();
+ frame.WriteUInt32(window_update_size);
+ frame.WriteUInt32(stream_id);
+ frame.WriteUInt32(delta_window_size);
+ return reinterpret_cast<SpdyWindowUpdateControlFrame*>(frame.take());
+}
+
+/* static */
+SpdySettingsControlFrame* SpdyFramer::CreateSettings(
+ const SpdySettings& values) {
+ SpdyFrameBuilder frame;
+ frame.WriteUInt16(kControlFlagMask | kSpdyProtocolVersion);
+ frame.WriteUInt16(SETTINGS);
+ size_t settings_size = SpdySettingsControlFrame::size() - SpdyFrame::size() +
+ 8 * values.size();
+ frame.WriteUInt32(settings_size);
+ frame.WriteUInt32(values.size());
+ SpdySettings::const_iterator it = values.begin();
+ while (it != values.end()) {
+ frame.WriteUInt32(it->first.id_);
+ frame.WriteUInt32(it->second);
+ ++it;
+ }
+ return reinterpret_cast<SpdySettingsControlFrame*>(frame.take());
+}
+
+SpdySynReplyControlFrame* SpdyFramer::CreateSynReply(SpdyStreamId stream_id,
+ SpdyControlFlags flags, bool compressed, SpdyHeaderBlock* headers) {
+ DCHECK_GT(stream_id, 0u);
+ DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
+
+ SpdyFrameBuilder frame;
+
+ frame.WriteUInt16(kControlFlagMask | kSpdyProtocolVersion);
+ frame.WriteUInt16(SYN_REPLY);
+ frame.WriteUInt32(0); // Placeholder for the length and flags.
+ frame.WriteUInt32(stream_id);
+ frame.WriteUInt16(0); // Unused
+
+ frame.WriteUInt16(headers->size()); // Number of headers.
+ SpdyHeaderBlock::iterator it;
+ for (it = headers->begin(); it != headers->end(); ++it) {
+ bool wrote_header;
+ wrote_header = frame.WriteString(it->first);
+ wrote_header &= frame.WriteString(it->second);
+ DCHECK(wrote_header);
+ }
+
+ // Write the length and flags.
+ size_t length = frame.length() - SpdyFrame::size();
+ DCHECK_EQ(0u, length & ~static_cast<size_t>(kLengthMask));
+ FlagsAndLength flags_length;
+ flags_length.length_ = htonl(static_cast<uint32>(length));
+ DCHECK_EQ(0, flags & ~kControlFlagsMask);
+ flags_length.flags_[0] = flags;
+ frame.WriteBytesToOffset(4, &flags_length, sizeof(flags_length));
+
+ scoped_ptr<SpdyFrame> reply_frame(frame.take());
+ if (compressed) {
+ return reinterpret_cast<SpdySynReplyControlFrame*>(
+ CompressFrame(*reply_frame.get()));
+ }
+ return reinterpret_cast<SpdySynReplyControlFrame*>(reply_frame.release());
+}
+
+SpdyDataFrame* SpdyFramer::CreateDataFrame(SpdyStreamId stream_id,
+ const char* data,
+ uint32 len, SpdyDataFlags flags) {
+ SpdyFrameBuilder frame;
+
+ DCHECK_GT(stream_id, 0u);
+ DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
+ frame.WriteUInt32(stream_id);
+
+ DCHECK_EQ(0u, len & ~static_cast<size_t>(kLengthMask));
+ FlagsAndLength flags_length;
+ flags_length.length_ = htonl(len);
+ DCHECK_EQ(0, flags & ~kDataFlagsMask);
+ flags_length.flags_[0] = flags;
+ frame.WriteBytes(&flags_length, sizeof(flags_length));
+
+ frame.WriteBytes(data, len);
+ scoped_ptr<SpdyFrame> data_frame(frame.take());
+ SpdyDataFrame* rv;
+ if (flags & DATA_FLAG_COMPRESSED) {
+ rv = reinterpret_cast<SpdyDataFrame*>(CompressFrame(*data_frame.get()));
+ } else {
+ rv = reinterpret_cast<SpdyDataFrame*>(data_frame.release());
+ }
+
+ if (flags & DATA_FLAG_FIN) {
+ CleanupCompressorForStream(stream_id);
+ }
+
+ return rv;
+}
+
+/* static */
+SpdyControlFrame* SpdyFramer::CreateNopFrame() {
+ SpdyFrameBuilder frame;
+ frame.WriteUInt16(kControlFlagMask | kSpdyProtocolVersion);
+ frame.WriteUInt16(NOOP);
+ frame.WriteUInt32(0);
+ return reinterpret_cast<SpdyControlFrame*>(frame.take());
+}
+
+// The following compression setting are based on Brian Olson's analysis. See
+// https://groups.google.com/group/spdy-dev/browse_thread/thread/dfaf498542fac792
+// for more details.
+static const int kCompressorLevel = 9;
+static const int kCompressorWindowSizeInBits = 11;
+static const int kCompressorMemLevel = 1;
+
+// This is just a hacked dictionary to use for shrinking HTTP-like headers.
+// TODO(mbelshe): Use a scientific methodology for computing the dictionary.
+const char SpdyFramer::kDictionary[] =
+ "optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-"
+ "languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi"
+ "f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser"
+ "-agent10010120020120220320420520630030130230330430530630740040140240340440"
+ "5406407408409410411412413414415416417500501502503504505accept-rangesageeta"
+ "glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic"
+ "ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran"
+ "sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati"
+ "oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo"
+ "ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe"
+ "pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic"
+ "ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1"
+ ".1statusversionurl";
+const int SpdyFramer::kDictionarySize = arraysize(kDictionary);
+
+static uLong dictionary_id = 0;
+
+z_stream* SpdyFramer::GetHeaderCompressor() {
+ if (header_compressor_.get())
+ return header_compressor_.get(); // Already initialized.
+
+ header_compressor_.reset(new z_stream);
+ memset(header_compressor_.get(), 0, sizeof(z_stream));
+
+ int success = deflateInit2(header_compressor_.get(),
+ kCompressorLevel,
+ Z_DEFLATED,
+ kCompressorWindowSizeInBits,
+ kCompressorMemLevel,
+ Z_DEFAULT_STRATEGY);
+ if (success == Z_OK)
+ success = deflateSetDictionary(header_compressor_.get(),
+ reinterpret_cast<const Bytef*>(kDictionary),
+ kDictionarySize);
+ if (success != Z_OK) {
+ LOG(WARNING) << "deflateSetDictionary failure: " << success;
+ header_compressor_.reset(NULL);
+ return NULL;
+ }
+ return header_compressor_.get();
+}
+
+z_stream* SpdyFramer::GetHeaderDecompressor() {
+ if (header_decompressor_.get())
+ return header_decompressor_.get(); // Already initialized.
+
+ header_decompressor_.reset(new z_stream);
+ memset(header_decompressor_.get(), 0, sizeof(z_stream));
+
+ // Compute the id of our dictionary so that we know we're using the
+ // right one when asked for it.
+ if (dictionary_id == 0) {
+ dictionary_id = adler32(0L, Z_NULL, 0);
+ dictionary_id = adler32(dictionary_id,
+ reinterpret_cast<const Bytef*>(kDictionary),
+ kDictionarySize);
+ }
+
+ int success = inflateInit(header_decompressor_.get());
+ if (success != Z_OK) {
+ LOG(WARNING) << "inflateInit failure: " << success;
+ header_decompressor_.reset(NULL);
+ return NULL;
+ }
+ return header_decompressor_.get();
+}
+
+z_stream* SpdyFramer::GetStreamCompressor(SpdyStreamId stream_id) {
+ CompressorMap::iterator it = stream_compressors_.find(stream_id);
+ if (it != stream_compressors_.end())
+ return it->second; // Already initialized.
+
+ scoped_ptr<z_stream> compressor(new z_stream);
+ memset(compressor.get(), 0, sizeof(z_stream));
+
+ int success = deflateInit2(compressor.get(),
+ kCompressorLevel,
+ Z_DEFLATED,
+ kCompressorWindowSizeInBits,
+ kCompressorMemLevel,
+ Z_DEFAULT_STRATEGY);
+ if (success != Z_OK) {
+ LOG(WARNING) << "deflateInit failure: " << success;
+ return NULL;
+ }
+ return stream_compressors_[stream_id] = compressor.release();
+}
+
+z_stream* SpdyFramer::GetStreamDecompressor(SpdyStreamId stream_id) {
+ CompressorMap::iterator it = stream_decompressors_.find(stream_id);
+ if (it != stream_decompressors_.end())
+ return it->second; // Already initialized.
+
+ scoped_ptr<z_stream> decompressor(new z_stream);
+ memset(decompressor.get(), 0, sizeof(z_stream));
+
+ int success = inflateInit(decompressor.get());
+ if (success != Z_OK) {
+ LOG(WARNING) << "inflateInit failure: " << success;
+ return NULL;
+ }
+ return stream_decompressors_[stream_id] = decompressor.release();
+}
+
+bool SpdyFramer::GetFrameBoundaries(const SpdyFrame& frame,
+ int* payload_length,
+ int* header_length,
+ const char** payload) const {
+ size_t frame_size;
+ if (frame.is_control_frame()) {
+ const SpdyControlFrame& control_frame =
+ reinterpret_cast<const SpdyControlFrame&>(frame);
+ switch (control_frame.type()) {
+ case SYN_STREAM:
+ {
+ const SpdySynStreamControlFrame& syn_frame =
+ reinterpret_cast<const SpdySynStreamControlFrame&>(frame);
+ frame_size = SpdySynStreamControlFrame::size();
+ *payload_length = syn_frame.header_block_len();
+ *header_length = frame_size;
+ *payload = frame.data() + *header_length;
+ }
+ break;
+ case SYN_REPLY:
+ {
+ const SpdySynReplyControlFrame& syn_frame =
+ reinterpret_cast<const SpdySynReplyControlFrame&>(frame);
+ frame_size = SpdySynReplyControlFrame::size();
+ *payload_length = syn_frame.header_block_len();
+ *header_length = frame_size;
+ *payload = frame.data() + *header_length;
+ }
+ break;
+ default:
+ // TODO(mbelshe): set an error?
+ return false; // We can't compress this frame!
+ }
+ } else {
+ frame_size = SpdyFrame::size();
+ *header_length = frame_size;
+ *payload_length = frame.length();
+ *payload = frame.data() + SpdyFrame::size();
+ }
+ return true;
+}
+
+SpdyFrame* SpdyFramer::CompressFrame(const SpdyFrame& frame) {
+ if (frame.is_control_frame()) {
+ return CompressControlFrame(
+ reinterpret_cast<const SpdyControlFrame&>(frame));
+ }
+ return CompressDataFrame(reinterpret_cast<const SpdyDataFrame&>(frame));
+}
+
+SpdyFrame* SpdyFramer::DecompressFrame(const SpdyFrame& frame) {
+ if (frame.is_control_frame()) {
+ return DecompressControlFrame(
+ reinterpret_cast<const SpdyControlFrame&>(frame));
+ }
+ return DecompressDataFrame(reinterpret_cast<const SpdyDataFrame&>(frame));
+}
+
+SpdyControlFrame* SpdyFramer::CompressControlFrame(
+ const SpdyControlFrame& frame) {
+ z_stream* compressor = GetHeaderCompressor();
+ if (!compressor)
+ return NULL;
+ return reinterpret_cast<SpdyControlFrame*>(
+ CompressFrameWithZStream(frame, compressor));
+}
+
+SpdyControlFrame* SpdyFramer::DecompressControlFrame(
+ const SpdyControlFrame& frame) {
+ z_stream* decompressor = GetHeaderDecompressor();
+ if (!decompressor)
+ return NULL;
+ return reinterpret_cast<SpdyControlFrame*>(
+ DecompressFrameWithZStream(frame, decompressor));
+}
+
+SpdyDataFrame* SpdyFramer::CompressDataFrame(const SpdyDataFrame& frame) {
+ z_stream* compressor = GetStreamCompressor(frame.stream_id());
+ if (!compressor)
+ return NULL;
+ return reinterpret_cast<SpdyDataFrame*>(
+ CompressFrameWithZStream(frame, compressor));
+}
+
+SpdyDataFrame* SpdyFramer::DecompressDataFrame(const SpdyDataFrame& frame) {
+ z_stream* decompressor = GetStreamDecompressor(frame.stream_id());
+ if (!decompressor)
+ return NULL;
+ return reinterpret_cast<SpdyDataFrame*>(
+ DecompressFrameWithZStream(frame, decompressor));
+}
+
+SpdyFrame* SpdyFramer::CompressFrameWithZStream(const SpdyFrame& frame,
+ z_stream* compressor) {
+ int payload_length;
+ int header_length;
+ const char* payload;
+
+ static StatsCounter compressed_frames("spdy.CompressedFrames");
+ static StatsCounter pre_compress_bytes("spdy.PreCompressSize");
+ static StatsCounter post_compress_bytes("spdy.PostCompressSize");
+
+ if (!enable_compression_)
+ return DuplicateFrame(frame);
+
+ if (!GetFrameBoundaries(frame, &payload_length, &header_length, &payload))
+ return NULL;
+
+ // Create an output frame.
+ int compressed_max_size = deflateBound(compressor, payload_length);
+ int new_frame_size = header_length + compressed_max_size;
+ scoped_ptr<SpdyFrame> new_frame(new SpdyFrame(new_frame_size));
+ memcpy(new_frame->data(), frame.data(), frame.length() + SpdyFrame::size());
+
+ compressor->next_in = reinterpret_cast<Bytef*>(const_cast<char*>(payload));
+ compressor->avail_in = payload_length;
+ compressor->next_out = reinterpret_cast<Bytef*>(new_frame->data()) +
+ header_length;
+ compressor->avail_out = compressed_max_size;
+
+ // Data packets have a 'compressed' flag.
+ if (!new_frame->is_control_frame()) {
+ SpdyDataFrame* data_frame =
+ reinterpret_cast<SpdyDataFrame*>(new_frame.get());
+ data_frame->set_flags(data_frame->flags() | DATA_FLAG_COMPRESSED);
+ }
+
+ int rv = deflate(compressor, Z_SYNC_FLUSH);
+ if (rv != Z_OK) { // How can we know that it compressed everything?
+ // This shouldn't happen, right?
+ LOG(WARNING) << "deflate failure: " << rv;
+ return NULL;
+ }
+
+ int compressed_size = compressed_max_size - compressor->avail_out;
+ new_frame->set_length(header_length + compressed_size - SpdyFrame::size());
+
+ pre_compress_bytes.Add(payload_length);
+ post_compress_bytes.Add(new_frame->length());
+
+ compressed_frames.Increment();
+
+ return new_frame.release();
+}
+
+SpdyFrame* SpdyFramer::DecompressFrameWithZStream(const SpdyFrame& frame,
+ z_stream* decompressor) {
+ int payload_length;
+ int header_length;
+ const char* payload;
+
+ static StatsCounter decompressed_frames("spdy.DecompressedFrames");
+ static StatsCounter pre_decompress_bytes("spdy.PreDeCompressSize");
+ static StatsCounter post_decompress_bytes("spdy.PostDeCompressSize");
+
+ if (!enable_compression_)
+ return DuplicateFrame(frame);
+
+ if (!GetFrameBoundaries(frame, &payload_length, &header_length, &payload))
+ return NULL;
+
+ if (!frame.is_control_frame()) {
+ const SpdyDataFrame& data_frame =
+ reinterpret_cast<const SpdyDataFrame&>(frame);
+ if ((data_frame.flags() & DATA_FLAG_COMPRESSED) == 0)
+ return DuplicateFrame(frame);
+ }
+
+ // Create an output frame. Assume it does not need to be longer than
+ // the input data.
+ int decompressed_max_size = kControlFrameBufferInitialSize;
+ int new_frame_size = header_length + decompressed_max_size;
+ scoped_ptr<SpdyFrame> new_frame(new SpdyFrame(new_frame_size));
+ memcpy(new_frame->data(), frame.data(), frame.length() + SpdyFrame::size());
+
+ decompressor->next_in = reinterpret_cast<Bytef*>(const_cast<char*>(payload));
+ decompressor->avail_in = payload_length;
+ decompressor->next_out = reinterpret_cast<Bytef*>(new_frame->data()) +
+ header_length;
+ decompressor->avail_out = decompressed_max_size;
+
+ int rv = inflate(decompressor, Z_SYNC_FLUSH);
+ if (rv == Z_NEED_DICT) {
+ // Need to try again with the right dictionary.
+ if (decompressor->adler == dictionary_id) {
+ rv = inflateSetDictionary(decompressor, (const Bytef*)kDictionary,
+ kDictionarySize);
+ if (rv == Z_OK)
+ rv = inflate(decompressor, Z_SYNC_FLUSH);
+ }
+ }
+ if (rv != Z_OK) { // How can we know that it decompressed everything?
+ LOG(WARNING) << "inflate failure: " << rv;
+ return NULL;
+ }
+
+ // Unset the compressed flag for data frames.
+ if (!new_frame->is_control_frame()) {
+ SpdyDataFrame* data_frame =
+ reinterpret_cast<SpdyDataFrame*>(new_frame.get());
+ data_frame->set_flags(data_frame->flags() & ~DATA_FLAG_COMPRESSED);
+ }
+
+ int decompressed_size = decompressed_max_size - decompressor->avail_out;
+ new_frame->set_length(header_length + decompressed_size - SpdyFrame::size());
+
+ // If there is data left, then we're in trouble. This API assumes everything
+ // was consumed.
+ CHECK_EQ(decompressor->avail_in, 0u);
+
+ pre_decompress_bytes.Add(frame.length());
+ post_decompress_bytes.Add(new_frame->length());
+
+ decompressed_frames.Increment();
+
+ return new_frame.release();
+}
+
+void SpdyFramer::CleanupCompressorForStream(SpdyStreamId id) {
+ CompressorMap::iterator it = stream_compressors_.find(id);
+ if (it != stream_compressors_.end()) {
+ z_stream* compressor = it->second;
+ deflateEnd(compressor);
+ delete compressor;
+ stream_compressors_.erase(it);
+ }
+}
+
+void SpdyFramer::CleanupDecompressorForStream(SpdyStreamId id) {
+ CompressorMap::iterator it = stream_decompressors_.find(id);
+ if (it != stream_decompressors_.end()) {
+ z_stream* decompressor = it->second;
+ inflateEnd(decompressor);
+ delete decompressor;
+ stream_decompressors_.erase(it);
+ }
+}
+
+void SpdyFramer::CleanupStreamCompressorsAndDecompressors() {
+ CompressorMap::iterator it;
+
+ it = stream_compressors_.begin();
+ while (it != stream_compressors_.end()) {
+ z_stream* compressor = it->second;
+ deflateEnd(compressor);
+ delete compressor;
+ ++it;
+ }
+ stream_compressors_.clear();
+
+ it = stream_decompressors_.begin();
+ while (it != stream_decompressors_.end()) {
+ z_stream* decompressor = it->second;
+ inflateEnd(decompressor);
+ delete decompressor;
+ ++it;
+ }
+ stream_decompressors_.clear();
+}
+
+SpdyFrame* SpdyFramer::DuplicateFrame(const SpdyFrame& frame) {
+ int size = SpdyFrame::size() + frame.length();
+ SpdyFrame* new_frame = new SpdyFrame(size);
+ memcpy(new_frame->data(), frame.data(), size);
+ return new_frame;
+}
+
+bool SpdyFramer::IsCompressible(const SpdyFrame& frame) const {
+ // The important frames to compress are those which contain large
+ // amounts of compressible data - namely the headers in the SYN_STREAM
+ // and SYN_REPLY.
+ // TODO(mbelshe): Reconcile this with the spec when the spec is
+ // explicit about which frames compress and which do not.
+ if (frame.is_control_frame()) {
+ const SpdyControlFrame& control_frame =
+ reinterpret_cast<const SpdyControlFrame&>(frame);
+ return control_frame.type() == SYN_STREAM ||
+ control_frame.type() == SYN_REPLY;
+ }
+
+ const SpdyDataFrame& data_frame =
+ reinterpret_cast<const SpdyDataFrame&>(frame);
+ return (data_frame.flags() & DATA_FLAG_COMPRESSED) != 0;
+}
+
+void SpdyFramer::set_enable_compression(bool value) {
+ enable_compression_ = value;
+}
+
+void SpdyFramer::set_enable_compression_default(bool value) {
+ compression_default_ = value;
+}
+
+} // namespace spdy
diff --git a/net/spdy/spdy_framer.h b/net/spdy/spdy_framer.h
new file mode 100644
index 0000000..f188373
--- /dev/null
+++ b/net/spdy/spdy_framer.h
@@ -0,0 +1,334 @@
+// 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.
+
+#ifndef NET_SPDY_SPDY_FRAMER_H_
+#define NET_SPDY_SPDY_FRAMER_H_
+
+#ifdef _WIN32
+#include <winsock2.h>
+#else
+#include <arpa/inet.h>
+#endif
+#include <list>
+#include <map>
+#include <string>
+#include <utility>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gtest/include/gtest/gtest_prod.h"
+
+typedef struct z_stream_s z_stream; // Forward declaration for zlib.
+
+namespace net {
+class HttpNetworkLayer;
+class HttpNetworkTransactionTest;
+class SpdyNetworkTransactionTest;
+class SpdySessionTest;
+class SpdyStreamTest;
+class SpdyHttpStreamTest;
+}
+
+namespace spdy {
+
+class SpdyFramer;
+class SpdyFramerTest;
+
+namespace test {
+class TestSpdyVisitor;
+void FramerSetEnableCompressionHelper(SpdyFramer* framer, bool compress);
+} // namespace test
+
+// A datastructure for holding a set of headers from either a
+// SYN_STREAM or SYN_REPLY frame.
+typedef std::map<std::string, std::string> SpdyHeaderBlock;
+
+// A datastructure for holding a set of ID/value pairs for a SETTINGS frame.
+typedef std::pair<spdy::SettingsFlagsAndId, uint32> SpdySetting;
+typedef std::list<SpdySetting> SpdySettings;
+
+// SpdyFramerVisitorInterface is a set of callbacks for the SpdyFramer.
+// Implement this interface to receive event callbacks as frames are
+// decoded from the framer.
+class SpdyFramerVisitorInterface {
+ public:
+ virtual ~SpdyFramerVisitorInterface() {}
+
+ // Called if an error is detected in the SpdyFrame protocol.
+ virtual void OnError(SpdyFramer* framer) = 0;
+
+ // Called when a Control Frame is received.
+ virtual void OnControl(const SpdyControlFrame* frame) = 0;
+
+ // Called when data is received.
+ // |stream_id| The stream receiving data.
+ // |data| A buffer containing the data received.
+ // |len| The length of the data buffer.
+ // When the other side has finished sending data on this stream,
+ // this method will be called with a zero-length buffer.
+ virtual void OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len) = 0;
+};
+
+class SpdyFramer {
+ public:
+ // SPDY states.
+ // TODO(mbelshe): Can we move these into the implementation
+ // and avoid exposing through the header. (Needed for test)
+ enum SpdyState {
+ SPDY_ERROR,
+ SPDY_DONE,
+ SPDY_RESET,
+ SPDY_AUTO_RESET,
+ SPDY_READING_COMMON_HEADER,
+ SPDY_INTERPRET_CONTROL_FRAME_COMMON_HEADER,
+ SPDY_CONTROL_FRAME_PAYLOAD,
+ SPDY_IGNORE_REMAINING_PAYLOAD,
+ SPDY_FORWARD_STREAM_FRAME
+ };
+
+ // SPDY error codes.
+ enum SpdyError {
+ SPDY_NO_ERROR,
+ SPDY_INVALID_CONTROL_FRAME, // Control frame is mal-formatted.
+ SPDY_CONTROL_PAYLOAD_TOO_LARGE, // Control frame payload was too large.
+ SPDY_ZLIB_INIT_FAILURE, // The Zlib library could not initialize.
+ SPDY_UNSUPPORTED_VERSION, // Control frame has unsupported version.
+ SPDY_DECOMPRESS_FAILURE, // There was an error decompressing.
+ SPDY_COMPRESS_FAILURE, // There was an error compressing.
+
+ LAST_ERROR, // Must be the last entry in the enum.
+ };
+
+ // Create a new Framer.
+ SpdyFramer();
+ virtual ~SpdyFramer();
+
+ // Set callbacks to be called from the framer. A visitor must be set, or
+ // else the framer will likely crash. It is acceptable for the visitor
+ // to do nothing. If this is called multiple times, only the last visitor
+ // will be used.
+ void set_visitor(SpdyFramerVisitorInterface* visitor) {
+ visitor_ = visitor;
+ }
+
+ // Pass data into the framer for parsing.
+ // Returns the number of bytes consumed. It is safe to pass more bytes in
+ // than may be consumed.
+ size_t ProcessInput(const char* data, size_t len);
+
+ // Resets the framer state after a frame has been successfully decoded.
+ // TODO(mbelshe): can we make this private?
+ void Reset();
+
+ // Check the state of the framer.
+ SpdyError error_code() const { return error_code_; }
+ SpdyState state() const { return state_; }
+
+ bool MessageFullyRead() {
+ return state_ == SPDY_DONE || state_ == SPDY_AUTO_RESET;
+ }
+ bool HasError() { return state_ == SPDY_ERROR; }
+
+ // Further parsing utilities.
+ // Given a control frame, parse out a SpdyHeaderBlock. Only
+ // valid for SYN_STREAM and SYN_REPLY frames.
+ // Returns true if successfully parsed, false otherwise.
+ bool ParseHeaderBlock(const SpdyFrame* frame, SpdyHeaderBlock* block);
+
+ // Create a SpdySynStreamControlFrame.
+ // |stream_id| is the id for this stream.
+ // |associated_stream_id| is the associated stream id for this stream.
+ // |priority| is the priority (0-3) for this stream.
+ // |flags| is the flags to use with the data.
+ // To mark this frame as the last frame, enable CONTROL_FLAG_FIN.
+ // |compressed| specifies whether the frame should be compressed.
+ // |headers| is the header block to include in the frame.
+ SpdySynStreamControlFrame* CreateSynStream(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ int priority,
+ SpdyControlFlags flags,
+ bool compressed,
+ SpdyHeaderBlock* headers);
+
+ static SpdyRstStreamControlFrame* CreateRstStream(SpdyStreamId stream_id,
+ SpdyStatusCodes status);
+
+ // Creates an instance of SpdyGoAwayControlFrame. The GOAWAY frame is used
+ // prior to the shutting down of the TCP connection, and includes the
+ // stream_id of the last stream the sender of the frame is willing to process
+ // to completion.
+ static SpdyGoAwayControlFrame* CreateGoAway(
+ SpdyStreamId last_accepted_stream_id);
+
+ // Creates an instance of SpdyWindowUpdateControlFrame. The WINDOW_UPDATE
+ // frame is used to implement per stream flow control in SPDY.
+ static SpdyWindowUpdateControlFrame* CreateWindowUpdate(
+ SpdyStreamId stream_id, uint32 delta_window_size);
+
+ // Creates an instance of SpdySettingsControlFrame. The SETTINGS frame is
+ // used to communicate name/value pairs relevant to the communication channel.
+ // TODO(mbelshe): add the name/value pairs!!
+ static SpdySettingsControlFrame* CreateSettings(const SpdySettings& values);
+
+ // Given a SpdySettingsControlFrame, extract the settings.
+ // Returns true on successful parse, false otherwise.
+ static bool ParseSettings(const SpdySettingsControlFrame* frame,
+ SpdySettings* settings);
+
+ // Create a SpdySynReplyControlFrame.
+ // |stream_id| is the stream for this frame.
+ // |flags| is the flags to use with the data.
+ // To mark this frame as the last frame, enable CONTROL_FLAG_FIN.
+ // |compressed| specifies whether the frame should be compressed.
+ // |headers| is the header block to include in the frame.
+ SpdySynReplyControlFrame* CreateSynReply(SpdyStreamId stream_id,
+ SpdyControlFlags flags,
+ bool compressed,
+ SpdyHeaderBlock* headers);
+
+ // Create a data frame.
+ // |stream_id| is the stream for this frame
+ // |data| is the data to be included in the frame.
+ // |len| is the length of the data
+ // |flags| is the flags to use with the data.
+ // To create a compressed frame, enable DATA_FLAG_COMPRESSED.
+ // To mark this frame as the last data frame, enable DATA_FLAG_FIN.
+ SpdyDataFrame* CreateDataFrame(SpdyStreamId stream_id, const char* data,
+ uint32 len, SpdyDataFlags flags);
+
+ static SpdyControlFrame* CreateNopFrame();
+
+ // NOTES about frame compression.
+ // We want spdy to compress headers across the entire session. As long as
+ // the session is over TCP, frames are sent serially. The client & server
+ // can each compress frames in the same order and then compress them in that
+ // order, and the remote can do the reverse. However, we ultimately want
+ // the creation of frames to be less sensitive to order so that they can be
+ // placed over a UDP based protocol and yet still benefit from some
+ // compression. We don't know of any good compression protocol which does
+ // not build its state in a serial (stream based) manner.... For now, we're
+ // using zlib anyway.
+
+ // Compresses a SpdyFrame.
+ // On success, returns a new SpdyFrame with the payload compressed.
+ // Compression state is maintained as part of the SpdyFramer.
+ // Returned frame must be freed with "delete".
+ // On failure, returns NULL.
+ SpdyFrame* CompressFrame(const SpdyFrame& frame);
+
+ // Decompresses a SpdyFrame.
+ // On success, returns a new SpdyFrame with the payload decompressed.
+ // Compression state is maintained as part of the SpdyFramer.
+ // Returned frame must be freed with "delete".
+ // On failure, returns NULL.
+ SpdyFrame* DecompressFrame(const SpdyFrame& frame);
+
+ // Create a copy of a frame.
+ // Returned frame must be freed with "delete".
+ SpdyFrame* DuplicateFrame(const SpdyFrame& frame);
+
+ // Returns true if a frame could be compressed.
+ bool IsCompressible(const SpdyFrame& frame) const;
+
+ // For debugging.
+ static const char* StateToString(int state);
+ static const char* ErrorCodeToString(int error_code);
+
+ // Export the compression dictionary
+ static const char kDictionary[];
+ static const int kDictionarySize;
+
+ protected:
+ FRIEND_TEST(SpdyFramerTest, DataCompression);
+ FRIEND_TEST(SpdyFramerTest, UnclosedStreamDataCompressors);
+ friend class net::SpdyNetworkTransactionTest;
+ friend class net::HttpNetworkTransactionTest;
+ friend class net::HttpNetworkLayer; // This is temporary for the server.
+ friend class net::SpdySessionTest;
+ friend class net::SpdyHttpStreamTest;
+ friend class net::SpdyStreamTest;
+ friend class test::TestSpdyVisitor;
+ friend void test::FramerSetEnableCompressionHelper(SpdyFramer* framer,
+ bool compress);
+
+ // For ease of testing we can tweak compression on/off.
+ void set_enable_compression(bool value);
+ static void set_enable_compression_default(bool value);
+
+ private:
+ typedef std::map<SpdyStreamId, z_stream*> CompressorMap;
+
+ // Internal breakout from ProcessInput. Returns the number of bytes
+ // consumed from the data.
+ size_t ProcessCommonHeader(const char* data, size_t len);
+ void ProcessControlFrameHeader();
+ size_t ProcessControlFramePayload(const char* data, size_t len);
+ size_t ProcessDataFramePayload(const char* data, size_t len);
+
+ // Get (and lazily initialize) the ZLib state.
+ z_stream* GetHeaderCompressor();
+ z_stream* GetHeaderDecompressor();
+ z_stream* GetStreamCompressor(SpdyStreamId id);
+ z_stream* GetStreamDecompressor(SpdyStreamId id);
+
+ // Compression helpers
+ SpdyControlFrame* CompressControlFrame(const SpdyControlFrame& frame);
+ SpdyDataFrame* CompressDataFrame(const SpdyDataFrame& frame);
+ SpdyControlFrame* DecompressControlFrame(const SpdyControlFrame& frame);
+ SpdyDataFrame* DecompressDataFrame(const SpdyDataFrame& frame);
+ SpdyFrame* CompressFrameWithZStream(const SpdyFrame& frame,
+ z_stream* compressor);
+ SpdyFrame* DecompressFrameWithZStream(const SpdyFrame& frame,
+ z_stream* decompressor);
+ void CleanupCompressorForStream(SpdyStreamId id);
+ void CleanupDecompressorForStream(SpdyStreamId id);
+ void CleanupStreamCompressorsAndDecompressors();
+
+ // Not used (yet)
+ size_t BytesSafeToRead() const;
+
+ // Set the error code and moves the framer into the error state.
+ void set_error(SpdyError error);
+
+ // Expands the control frame buffer to accomodate a particular payload size.
+ void ExpandControlFrameBuffer(size_t size);
+
+ // Given a frame, breakdown the variable payload length, the static header
+ // header length, and variable payload pointer.
+ bool GetFrameBoundaries(const SpdyFrame& frame, int* payload_length,
+ int* header_length, const char** payload) const;
+
+ int num_stream_compressors() const { return stream_compressors_.size(); }
+ int num_stream_decompressors() const { return stream_decompressors_.size(); }
+
+ SpdyState state_;
+ SpdyError error_code_;
+ size_t remaining_payload_;
+ size_t remaining_control_payload_;
+
+ char* current_frame_buffer_;
+ size_t current_frame_len_; // Number of bytes read into the current_frame_.
+ size_t current_frame_capacity_;
+
+ bool enable_compression_; // Controls all compression
+ // SPDY header compressors.
+ scoped_ptr<z_stream> header_compressor_;
+ scoped_ptr<z_stream> header_decompressor_;
+
+ // Per-stream data compressors.
+ CompressorMap stream_compressors_;
+ CompressorMap stream_decompressors_;
+
+ SpdyFramerVisitorInterface* visitor_;
+
+ static bool compression_default_;
+};
+
+} // namespace spdy
+
+#endif // NET_SPDY_SPDY_FRAMER_H_
diff --git a/net/spdy/spdy_framer_test.cc b/net/spdy/spdy_framer_test.cc
new file mode 100644
index 0000000..4e976ab
--- /dev/null
+++ b/net/spdy/spdy_framer_test.cc
@@ -0,0 +1,1183 @@
+// 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 <algorithm>
+#include <iostream>
+
+#include "base/scoped_ptr.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_frame_builder.h"
+#include "testing/platform_test.h"
+
+namespace spdy {
+
+namespace test {
+
+std::string HexDumpWithMarks(const unsigned char* data, int length,
+ const bool* marks, int mark_length) {
+ static const char kHexChars[] = "0123456789ABCDEF";
+ static const int kColumns = 4;
+
+ std::string hex;
+ for (const unsigned char* row = data; length > 0;
+ row += kColumns, length -= kColumns) {
+ for (const unsigned char *p = row; p < row + 4; ++p) {
+ if (p < row + length) {
+ const bool mark =
+ (marks && (p - data) < mark_length && marks[p - data]);
+ hex += mark ? '*' : ' ';
+ hex += kHexChars[(*p & 0xf0) >> 4];
+ hex += kHexChars[*p & 0x0f];
+ hex += mark ? '*' : ' ';
+ } else {
+ hex += " ";
+ }
+ }
+ hex = hex + " ";
+
+ for (const unsigned char *p = row; p < row + 4 && p < row + length; ++p)
+ hex += (*p >= 0x20 && *p <= 0x7f) ? (*p) : '.';
+
+ hex = hex + '\n';
+ }
+ return hex;
+}
+
+void CompareCharArraysWithHexError(
+ const std::string& description,
+ const unsigned char* actual,
+ const int actual_len,
+ const unsigned char* expected,
+ const int expected_len) {
+ const int min_len = actual_len > expected_len ? expected_len : actual_len;
+ const int max_len = actual_len > expected_len ? actual_len : expected_len;
+ scoped_array<bool> marks(new bool[max_len]);
+ bool identical = (actual_len == expected_len);
+ for (int i = 0; i < min_len; ++i) {
+ if (actual[i] != expected[i]) {
+ marks[i] = true;
+ identical = false;
+ } else {
+ marks[i] = false;
+ }
+ }
+ for (int i = min_len; i < max_len; ++i) {
+ marks[i] = true;
+ }
+ if (identical) return;
+ ADD_FAILURE()
+ << "Description:\n"
+ << description
+ << "\n\nExpected:\n"
+ << HexDumpWithMarks(expected, expected_len, marks.get(), max_len)
+ << "\nActual:\n"
+ << HexDumpWithMarks(actual, actual_len, marks.get(), max_len);
+}
+
+void FramerSetEnableCompressionHelper(SpdyFramer* framer, bool compress) {
+ framer->set_enable_compression(compress);
+}
+
+class TestSpdyVisitor : public SpdyFramerVisitorInterface {
+ public:
+ TestSpdyVisitor()
+ : error_count_(0),
+ syn_frame_count_(0),
+ syn_reply_frame_count_(0),
+ data_bytes_(0),
+ fin_frame_count_(0),
+ fin_flag_count_(0),
+ zero_length_data_frame_count_(0) {
+ }
+
+ void OnError(SpdyFramer* f) {
+ error_count_++;
+ }
+
+ void OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len) {
+ if (len == 0)
+ ++zero_length_data_frame_count_;
+
+ data_bytes_ += len;
+ std::cerr << "OnStreamFrameData(" << stream_id << ", \"";
+ if (len > 0) {
+ for (size_t i = 0 ; i < len; ++i) {
+ std::cerr << std::hex << (0xFF & (unsigned int)data[i]) << std::dec;
+ }
+ }
+ std::cerr << "\", " << len << ")\n";
+ }
+
+ void OnControl(const SpdyControlFrame* frame) {
+ SpdyHeaderBlock headers;
+ bool parsed_headers = false;
+ switch (frame->type()) {
+ case SYN_STREAM:
+ parsed_headers = framer_.ParseHeaderBlock(frame, &headers);
+ DCHECK(parsed_headers);
+ syn_frame_count_++;
+ break;
+ case SYN_REPLY:
+ parsed_headers = framer_.ParseHeaderBlock(frame, &headers);
+ DCHECK(parsed_headers);
+ syn_reply_frame_count_++;
+ break;
+ case RST_STREAM:
+ fin_frame_count_++;
+ break;
+ default:
+ DCHECK(false); // Error!
+ }
+ if (frame->flags() & CONTROL_FLAG_FIN)
+ ++fin_flag_count_;
+ }
+
+ // Convenience function which runs a framer simulation with particular input.
+ void SimulateInFramer(const unsigned char* input, size_t size) {
+ framer_.set_enable_compression(false);
+ framer_.set_visitor(this);
+ size_t input_remaining = size;
+ const char* input_ptr = reinterpret_cast<const char*>(input);
+ while (input_remaining > 0 &&
+ framer_.error_code() == SpdyFramer::SPDY_NO_ERROR) {
+ // To make the tests more interesting, we feed random (amd small) chunks
+ // into the framer. This simulates getting strange-sized reads from
+ // the socket.
+ const size_t kMaxReadSize = 32;
+ size_t bytes_read =
+ (rand() % std::min(input_remaining, kMaxReadSize)) + 1;
+ size_t bytes_processed = framer_.ProcessInput(input_ptr, bytes_read);
+ input_remaining -= bytes_processed;
+ input_ptr += bytes_processed;
+ if (framer_.state() == SpdyFramer::SPDY_DONE)
+ framer_.Reset();
+ }
+ }
+
+ SpdyFramer framer_;
+ // Counters from the visitor callbacks.
+ int error_count_;
+ int syn_frame_count_;
+ int syn_reply_frame_count_;
+ int data_bytes_;
+ int fin_frame_count_; // The count of RST_STREAM type frames received.
+ int fin_flag_count_; // The count of frames with the FIN flag set.
+ int zero_length_data_frame_count_; // The count of zero-length data frames.
+};
+
+} // namespace test
+
+} // namespace spdy
+
+using spdy::SpdyControlFlags;
+using spdy::SpdyControlFrame;
+using spdy::SpdyDataFrame;
+using spdy::SpdyFrame;
+using spdy::SpdyFrameBuilder;
+using spdy::SpdyFramer;
+using spdy::SpdyHeaderBlock;
+using spdy::SpdySynStreamControlFrame;
+using spdy::kControlFlagMask;
+using spdy::CONTROL_FLAG_NONE;
+using spdy::DATA_FLAG_COMPRESSED;
+using spdy::DATA_FLAG_FIN;
+using spdy::SYN_STREAM;
+using spdy::test::CompareCharArraysWithHexError;
+using spdy::test::FramerSetEnableCompressionHelper;
+using spdy::test::TestSpdyVisitor;
+
+namespace spdy {
+
+class SpdyFramerTest : public PlatformTest {
+ public:
+ virtual void TearDown() {}
+
+ protected:
+ void CompareFrame(const std::string& description,
+ const SpdyFrame& actual_frame,
+ const unsigned char* expected,
+ const int expected_len) {
+ const unsigned char* actual =
+ reinterpret_cast<const unsigned char*>(actual_frame.data());
+ int actual_len = actual_frame.length() + SpdyFrame::size();
+ CompareCharArraysWithHexError(
+ description, actual, actual_len, expected, expected_len);
+ }
+};
+
+// Test that we can encode and decode a SpdyHeaderBlock.
+TEST_F(SpdyFramerTest, HeaderBlock) {
+ SpdyHeaderBlock headers;
+ headers["alpha"] = "beta";
+ headers["gamma"] = "charlie";
+ SpdyFramer framer;
+
+ // Encode the header block into a SynStream frame.
+ scoped_ptr<SpdySynStreamControlFrame> frame(
+ framer.CreateSynStream(1, 0, 1, CONTROL_FLAG_NONE, true, &headers));
+ EXPECT_TRUE(frame.get() != NULL);
+
+ SpdyHeaderBlock new_headers;
+ EXPECT_TRUE(framer.ParseHeaderBlock(frame.get(), &new_headers));
+
+ EXPECT_EQ(headers.size(), new_headers.size());
+ EXPECT_EQ(headers["alpha"], new_headers["alpha"]);
+ EXPECT_EQ(headers["gamma"], new_headers["gamma"]);
+}
+
+TEST_F(SpdyFramerTest, OutOfOrderHeaders) {
+ SpdyFrameBuilder frame;
+
+ frame.WriteUInt16(kControlFlagMask | 1);
+ frame.WriteUInt16(SYN_STREAM);
+ frame.WriteUInt32(0); // Placeholder for the length.
+ frame.WriteUInt32(3); // stream_id
+ frame.WriteUInt32(0); // associated stream id
+ frame.WriteUInt16(0); // Priority.
+
+ frame.WriteUInt16(2); // Number of headers.
+ SpdyHeaderBlock::iterator it;
+ frame.WriteString("gamma");
+ frame.WriteString("gamma");
+ frame.WriteString("alpha");
+ frame.WriteString("alpha");
+ // write the length
+ frame.WriteUInt32ToOffset(4, frame.length() - SpdyFrame::size());
+
+ SpdyHeaderBlock new_headers;
+ scoped_ptr<SpdyFrame> control_frame(frame.take());
+ SpdyFramer framer;
+ FramerSetEnableCompressionHelper(&framer, false);
+ EXPECT_TRUE(framer.ParseHeaderBlock(control_frame.get(), &new_headers));
+}
+
+TEST_F(SpdyFramerTest, WrongNumberOfHeaders) {
+ SpdyFrameBuilder frame1;
+ SpdyFrameBuilder frame2;
+
+ // a frame with smaller number of actual headers
+ frame1.WriteUInt16(kControlFlagMask | 1);
+ frame1.WriteUInt16(SYN_STREAM);
+ frame1.WriteUInt32(0); // Placeholder for the length.
+ frame1.WriteUInt32(3); // stream_id
+ frame1.WriteUInt32(0); // associated stream id
+ frame1.WriteUInt16(0); // Priority.
+
+ frame1.WriteUInt16(1); // Wrong number of headers (underflow)
+ frame1.WriteString("gamma");
+ frame1.WriteString("gamma");
+ frame1.WriteString("alpha");
+ frame1.WriteString("alpha");
+ // write the length
+ frame1.WriteUInt32ToOffset(4, frame1.length() - SpdyFrame::size());
+
+ // a frame with larger number of actual headers
+ frame2.WriteUInt16(kControlFlagMask | 1);
+ frame2.WriteUInt16(SYN_STREAM);
+ frame2.WriteUInt32(0); // Placeholder for the length.
+ frame2.WriteUInt32(3); // stream_id
+ frame2.WriteUInt32(0); // associated stream id
+ frame2.WriteUInt16(0); // Priority.
+
+ frame2.WriteUInt16(100); // Wrong number of headers (overflow)
+ frame2.WriteString("gamma");
+ frame2.WriteString("gamma");
+ frame2.WriteString("alpha");
+ frame2.WriteString("alpha");
+ // write the length
+ frame2.WriteUInt32ToOffset(4, frame2.length() - SpdyFrame::size());
+
+ SpdyHeaderBlock new_headers;
+ scoped_ptr<SpdyFrame> syn_frame1(frame1.take());
+ scoped_ptr<SpdyFrame> syn_frame2(frame2.take());
+ SpdyFramer framer;
+ FramerSetEnableCompressionHelper(&framer, false);
+ EXPECT_FALSE(framer.ParseHeaderBlock(syn_frame1.get(), &new_headers));
+ EXPECT_FALSE(framer.ParseHeaderBlock(syn_frame2.get(), &new_headers));
+}
+
+TEST_F(SpdyFramerTest, DuplicateHeader) {
+ SpdyFrameBuilder frame;
+
+ frame.WriteUInt16(kControlFlagMask | 1);
+ frame.WriteUInt16(SYN_STREAM);
+ frame.WriteUInt32(0); // Placeholder for the length.
+ frame.WriteUInt32(3); // stream_id
+ frame.WriteUInt32(0); // associated stream id
+ frame.WriteUInt16(0); // Priority.
+
+ frame.WriteUInt16(2); // Number of headers.
+ SpdyHeaderBlock::iterator it;
+ frame.WriteString("name");
+ frame.WriteString("value1");
+ frame.WriteString("name");
+ frame.WriteString("value2");
+ // write the length
+ frame.WriteUInt32ToOffset(4, frame.length() - SpdyFrame::size());
+
+ SpdyHeaderBlock new_headers;
+ scoped_ptr<SpdyFrame> control_frame(frame.take());
+ SpdyFramer framer;
+ FramerSetEnableCompressionHelper(&framer, false);
+ // This should fail because duplicate headers are verboten by the spec.
+ EXPECT_FALSE(framer.ParseHeaderBlock(control_frame.get(), &new_headers));
+}
+
+TEST_F(SpdyFramerTest, MultiValueHeader) {
+ SpdyFrameBuilder frame;
+
+ frame.WriteUInt16(kControlFlagMask | 1);
+ frame.WriteUInt16(SYN_STREAM);
+ frame.WriteUInt32(0); // Placeholder for the length.
+ frame.WriteUInt32(3); // stream_id
+ frame.WriteUInt32(0); // associated stream id
+ frame.WriteUInt16(0); // Priority.
+
+ frame.WriteUInt16(1); // Number of headers.
+ SpdyHeaderBlock::iterator it;
+ frame.WriteString("name");
+ std::string value("value1\0value2");
+ frame.WriteString(value);
+ // write the length
+ frame.WriteUInt32ToOffset(4, frame.length() - SpdyFrame::size());
+
+ SpdyHeaderBlock new_headers;
+ scoped_ptr<SpdyFrame> control_frame(frame.take());
+ SpdyFramer framer;
+ FramerSetEnableCompressionHelper(&framer, false);
+ EXPECT_TRUE(framer.ParseHeaderBlock(control_frame.get(), &new_headers));
+ EXPECT_TRUE(new_headers.find("name") != new_headers.end());
+ EXPECT_EQ(value, new_headers.find("name")->second);
+}
+
+TEST_F(SpdyFramerTest, ZeroLengthHeader) {
+ SpdyHeaderBlock header1;
+ SpdyHeaderBlock header2;
+ SpdyHeaderBlock header3;
+
+ header1[""] = "value2";
+ header2["name3"] = "";
+ header3[""] = "";
+
+ SpdyFramer framer;
+ SpdyHeaderBlock parsed_headers;
+
+ scoped_ptr<SpdySynStreamControlFrame> frame1(
+ framer.CreateSynStream(1, 0, 1, CONTROL_FLAG_NONE, true, &header1));
+ EXPECT_TRUE(frame1.get() != NULL);
+ EXPECT_FALSE(framer.ParseHeaderBlock(frame1.get(), &parsed_headers));
+
+ scoped_ptr<SpdySynStreamControlFrame> frame2(
+ framer.CreateSynStream(1, 0, 1, CONTROL_FLAG_NONE, true, &header2));
+ EXPECT_TRUE(frame2.get() != NULL);
+ EXPECT_FALSE(framer.ParseHeaderBlock(frame2.get(), &parsed_headers));
+
+ scoped_ptr<SpdySynStreamControlFrame> frame3(
+ framer.CreateSynStream(1, 0, 1, CONTROL_FLAG_NONE, true, &header3));
+ EXPECT_TRUE(frame3.get() != NULL);
+ EXPECT_FALSE(framer.ParseHeaderBlock(frame3.get(), &parsed_headers));
+}
+
+TEST_F(SpdyFramerTest, BasicCompression) {
+ SpdyHeaderBlock headers;
+ headers["server"] = "SpdyServer 1.0";
+ headers["date"] = "Mon 12 Jan 2009 12:12:12 PST";
+ headers["status"] = "200";
+ headers["version"] = "HTTP/1.1";
+ headers["content-type"] = "text/html";
+ headers["content-length"] = "12";
+
+ SpdyFramer framer;
+ FramerSetEnableCompressionHelper(&framer, true);
+ scoped_ptr<SpdySynStreamControlFrame>
+ frame1(framer.CreateSynStream(1, 0, 1, CONTROL_FLAG_NONE, true,
+ &headers));
+ scoped_ptr<SpdySynStreamControlFrame>
+ frame2(framer.CreateSynStream(1, 0, 1, CONTROL_FLAG_NONE, true,
+ &headers));
+
+ // Expect the second frame to be more compact than the first.
+ EXPECT_LE(frame2->length(), frame1->length());
+
+ // Decompress the first frame
+ scoped_ptr<SpdyFrame> frame3(framer.DecompressFrame(*frame1.get()));
+
+ // Decompress the second frame
+ scoped_ptr<SpdyFrame> frame4(framer.DecompressFrame(*frame2.get()));
+
+ // Expect frames 3 & 4 to be the same.
+ EXPECT_EQ(0,
+ memcmp(frame3->data(), frame4->data(),
+ SpdyFrame::size() + frame3->length()));
+}
+
+TEST_F(SpdyFramerTest, DecompressUncompressedFrame) {
+ SpdyHeaderBlock headers;
+ headers["server"] = "SpdyServer 1.0";
+ headers["date"] = "Mon 12 Jan 2009 12:12:12 PST";
+ headers["status"] = "200";
+ headers["version"] = "HTTP/1.1";
+ headers["content-type"] = "text/html";
+ headers["content-length"] = "12";
+
+ SpdyFramer framer;
+ FramerSetEnableCompressionHelper(&framer, true);
+ scoped_ptr<SpdySynStreamControlFrame>
+ frame1(framer.CreateSynStream(1, 0, 1, CONTROL_FLAG_NONE, false,
+ &headers));
+
+ // Decompress the frame
+ scoped_ptr<SpdyFrame> frame2(framer.DecompressFrame(*frame1.get()));
+
+ EXPECT_EQ(NULL, frame2.get());
+}
+
+TEST_F(SpdyFramerTest, Basic) {
+ const unsigned char input[] = {
+ 0x80, 0x01, 0x00, 0x01, // SYN Stream #1
+ 0x00, 0x00, 0x00, 0x14,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x02, 'h', 'h',
+ 0x00, 0x02, 'v', 'v',
+
+ 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1
+ 0x00, 0x00, 0x00, 0x0c,
+ 0xde, 0xad, 0xbe, 0xef,
+ 0xde, 0xad, 0xbe, 0xef,
+ 0xde, 0xad, 0xbe, 0xef,
+
+ 0x80, 0x01, 0x00, 0x01, // SYN Stream #3
+ 0x00, 0x00, 0x00, 0x0c,
+ 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+
+ 0x00, 0x00, 0x00, 0x03, // DATA on Stream #3
+ 0x00, 0x00, 0x00, 0x08,
+ 0xde, 0xad, 0xbe, 0xef,
+ 0xde, 0xad, 0xbe, 0xef,
+
+ 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1
+ 0x00, 0x00, 0x00, 0x04,
+ 0xde, 0xad, 0xbe, 0xef,
+
+ 0x80, 0x01, 0x00, 0x03, // RST_STREAM on Stream #1
+ 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+
+ 0x00, 0x00, 0x00, 0x03, // DATA on Stream #3
+ 0x00, 0x00, 0x00, 0x00,
+
+ 0x80, 0x01, 0x00, 0x03, // RST_STREAM on Stream #3
+ 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x00,
+ };
+
+ TestSpdyVisitor visitor;
+ visitor.SimulateInFramer(input, sizeof(input));
+
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(2, visitor.syn_frame_count_);
+ EXPECT_EQ(0, visitor.syn_reply_frame_count_);
+ EXPECT_EQ(24, visitor.data_bytes_);
+ EXPECT_EQ(2, visitor.fin_frame_count_);
+ EXPECT_EQ(0, visitor.fin_flag_count_);
+ EXPECT_EQ(0, visitor.zero_length_data_frame_count_);
+}
+
+// Test that the FIN flag on a data frame signifies EOF.
+TEST_F(SpdyFramerTest, FinOnDataFrame) {
+ const unsigned char input[] = {
+ 0x80, 0x01, 0x00, 0x01, // SYN Stream #1
+ 0x00, 0x00, 0x00, 0x14,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x02, 'h', 'h',
+ 0x00, 0x02, 'v', 'v',
+
+ 0x80, 0x01, 0x00, 0x02, // SYN REPLY Stream #1
+ 0x00, 0x00, 0x00, 0x10,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x02, 'a', 'a',
+ 0x00, 0x02, 'b', 'b',
+
+ 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1
+ 0x00, 0x00, 0x00, 0x0c,
+ 0xde, 0xad, 0xbe, 0xef,
+ 0xde, 0xad, 0xbe, 0xef,
+ 0xde, 0xad, 0xbe, 0xef,
+
+ 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1, with EOF
+ 0x01, 0x00, 0x00, 0x04,
+ 0xde, 0xad, 0xbe, 0xef,
+ };
+
+ TestSpdyVisitor visitor;
+ visitor.SimulateInFramer(input, sizeof(input));
+
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(1, visitor.syn_frame_count_);
+ EXPECT_EQ(1, visitor.syn_reply_frame_count_);
+ EXPECT_EQ(16, visitor.data_bytes_);
+ EXPECT_EQ(0, visitor.fin_frame_count_);
+ EXPECT_EQ(0, visitor.fin_flag_count_);
+ EXPECT_EQ(1, visitor.zero_length_data_frame_count_);
+}
+
+// Test that the FIN flag on a SYN reply frame signifies EOF.
+TEST_F(SpdyFramerTest, FinOnSynReplyFrame) {
+ const unsigned char input[] = {
+ 0x80, 0x01, 0x00, 0x01, // SYN Stream #1
+ 0x00, 0x00, 0x00, 0x14,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x02, 'h', 'h',
+ 0x00, 0x02, 'v', 'v',
+
+ 0x80, 0x01, 0x00, 0x02, // SYN REPLY Stream #1
+ 0x01, 0x00, 0x00, 0x10,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x02, 'a', 'a',
+ 0x00, 0x02, 'b', 'b',
+ };
+
+ TestSpdyVisitor visitor;
+ visitor.SimulateInFramer(input, sizeof(input));
+
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(1, visitor.syn_frame_count_);
+ EXPECT_EQ(1, visitor.syn_reply_frame_count_);
+ EXPECT_EQ(0, visitor.data_bytes_);
+ EXPECT_EQ(0, visitor.fin_frame_count_);
+ EXPECT_EQ(1, visitor.fin_flag_count_);
+ EXPECT_EQ(1, visitor.zero_length_data_frame_count_);
+}
+
+// Basic compression & decompression
+TEST_F(SpdyFramerTest, DataCompression) {
+ SpdyFramer send_framer;
+ SpdyFramer recv_framer;
+
+ FramerSetEnableCompressionHelper(&send_framer, true);
+ FramerSetEnableCompressionHelper(&recv_framer, true);
+
+ // Mix up some SYNs and DATA frames since they use different compressors.
+ const char kHeader1[] = "header1";
+ const char kHeader2[] = "header2";
+ const char kHeader3[] = "header3";
+ const char kValue1[] = "value1";
+ const char kValue2[] = "value2";
+ const char kValue3[] = "value3";
+
+ // SYN_STREAM #1
+ SpdyHeaderBlock block;
+ block[kHeader1] = kValue1;
+ block[kHeader2] = kValue2;
+ SpdyControlFlags flags(CONTROL_FLAG_NONE);
+ scoped_ptr<spdy::SpdyFrame> syn_frame_1(
+ send_framer.CreateSynStream(1, 0, 0, flags, true, &block));
+ EXPECT_TRUE(syn_frame_1.get() != NULL);
+
+ // DATA #1
+ const char bytes[] = "this is a test test test test test!";
+ scoped_ptr<SpdyFrame> data_frame_1(
+ send_framer.CreateDataFrame(1, bytes, arraysize(bytes),
+ DATA_FLAG_COMPRESSED));
+ EXPECT_TRUE(data_frame_1.get() != NULL);
+
+ // SYN_STREAM #2
+ block[kHeader3] = kValue3;
+ scoped_ptr<SpdyFrame> syn_frame_2(
+ send_framer.CreateSynStream(3, 0, 0, flags, true, &block));
+ EXPECT_TRUE(syn_frame_2.get() != NULL);
+
+ // DATA #2
+ scoped_ptr<SpdyFrame> data_frame_2(
+ send_framer.CreateDataFrame(3, bytes, arraysize(bytes),
+ DATA_FLAG_COMPRESSED));
+ EXPECT_TRUE(data_frame_2.get() != NULL);
+
+ // Now start decompressing
+ scoped_ptr<SpdyFrame> decompressed;
+ SpdyControlFrame* control_frame;
+ SpdyDataFrame* data_frame;
+ SpdyHeaderBlock decompressed_headers;
+
+ decompressed.reset(recv_framer.DuplicateFrame(*syn_frame_1.get()));
+ EXPECT_TRUE(decompressed.get() != NULL);
+ EXPECT_TRUE(decompressed->is_control_frame());
+ control_frame = reinterpret_cast<SpdyControlFrame*>(decompressed.get());
+ EXPECT_EQ(SYN_STREAM, control_frame->type());
+ EXPECT_TRUE(recv_framer.ParseHeaderBlock(
+ control_frame, &decompressed_headers));
+ EXPECT_EQ(2u, decompressed_headers.size());
+ EXPECT_EQ(SYN_STREAM, control_frame->type());
+ EXPECT_EQ(kValue1, decompressed_headers[kHeader1]);
+ EXPECT_EQ(kValue2, decompressed_headers[kHeader2]);
+
+ decompressed.reset(recv_framer.DecompressFrame(*data_frame_1.get()));
+ EXPECT_TRUE(decompressed.get() != NULL);
+ EXPECT_FALSE(decompressed->is_control_frame());
+ data_frame = reinterpret_cast<SpdyDataFrame*>(decompressed.get());
+ EXPECT_EQ(arraysize(bytes), data_frame->length());
+ EXPECT_EQ(0, memcmp(data_frame->payload(), bytes, data_frame->length()));
+
+ decompressed.reset(recv_framer.DuplicateFrame(*syn_frame_2.get()));
+ EXPECT_TRUE(decompressed.get() != NULL);
+ EXPECT_TRUE(decompressed->is_control_frame());
+ control_frame = reinterpret_cast<SpdyControlFrame*>(decompressed.get());
+ EXPECT_EQ(control_frame->type(), SYN_STREAM);
+ decompressed_headers.clear();
+ EXPECT_TRUE(recv_framer.ParseHeaderBlock(
+ control_frame, &decompressed_headers));
+ EXPECT_EQ(3u, decompressed_headers.size());
+ EXPECT_EQ(SYN_STREAM, control_frame->type());
+ EXPECT_EQ(kValue1, decompressed_headers[kHeader1]);
+ EXPECT_EQ(kValue2, decompressed_headers[kHeader2]);
+ EXPECT_EQ(kValue3, decompressed_headers[kHeader3]);
+
+ decompressed.reset(recv_framer.DecompressFrame(*data_frame_2.get()));
+ EXPECT_TRUE(decompressed.get() != NULL);
+ EXPECT_FALSE(decompressed->is_control_frame());
+ data_frame = reinterpret_cast<SpdyDataFrame*>(decompressed.get());
+ EXPECT_EQ(arraysize(bytes), data_frame->length());
+ EXPECT_EQ(0, memcmp(data_frame->payload(), bytes, data_frame->length()));
+
+ // We didn't close these streams, so the compressors should be active.
+ EXPECT_EQ(2, send_framer.num_stream_compressors());
+ EXPECT_EQ(0, send_framer.num_stream_decompressors());
+ EXPECT_EQ(0, recv_framer.num_stream_compressors());
+ EXPECT_EQ(2, recv_framer.num_stream_decompressors());
+}
+
+// Verify we don't leak when we leave streams unclosed
+TEST_F(SpdyFramerTest, UnclosedStreamDataCompressors) {
+ SpdyFramer send_framer;
+
+ FramerSetEnableCompressionHelper(&send_framer, false);
+
+ const char kHeader1[] = "header1";
+ const char kHeader2[] = "header2";
+ const char kValue1[] = "value1";
+ const char kValue2[] = "value2";
+
+ SpdyHeaderBlock block;
+ block[kHeader1] = kValue1;
+ block[kHeader2] = kValue2;
+ SpdyControlFlags flags(CONTROL_FLAG_NONE);
+ scoped_ptr<spdy::SpdyFrame> syn_frame(
+ send_framer.CreateSynStream(1, 0, 0, flags, true, &block));
+ EXPECT_TRUE(syn_frame.get() != NULL);
+
+ const char bytes[] = "this is a test test test test test!";
+ scoped_ptr<SpdyFrame> send_frame(
+ send_framer.CreateDataFrame(1, bytes, arraysize(bytes),
+ DATA_FLAG_FIN));
+ EXPECT_TRUE(send_frame.get() != NULL);
+
+ // Run the inputs through the framer.
+ TestSpdyVisitor visitor;
+ const unsigned char* data;
+ data = reinterpret_cast<const unsigned char*>(syn_frame->data());
+ visitor.SimulateInFramer(data, syn_frame->length() + SpdyFrame::size());
+ data = reinterpret_cast<const unsigned char*>(send_frame->data());
+ visitor.SimulateInFramer(data, send_frame->length() + SpdyFrame::size());
+
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(1, visitor.syn_frame_count_);
+ EXPECT_EQ(0, visitor.syn_reply_frame_count_);
+ EXPECT_EQ(arraysize(bytes), static_cast<unsigned>(visitor.data_bytes_));
+ EXPECT_EQ(0, visitor.fin_frame_count_);
+ EXPECT_EQ(0, visitor.fin_flag_count_);
+ EXPECT_EQ(1, visitor.zero_length_data_frame_count_);
+
+ // We closed the streams, so all compressors should be down.
+ EXPECT_EQ(0, visitor.framer_.num_stream_compressors());
+ EXPECT_EQ(0, visitor.framer_.num_stream_decompressors());
+ EXPECT_EQ(0, send_framer.num_stream_compressors());
+ EXPECT_EQ(0, send_framer.num_stream_decompressors());
+}
+
+TEST_F(SpdyFramerTest, CreateDataFrame) {
+ SpdyFramer framer;
+
+ {
+ const char kDescription[] = "'hello' data frame, no FIN";
+ const unsigned char kFrameData[] = {
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x05,
+ 'h', 'e', 'l', 'l',
+ 'o'
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame(
+ 1, "hello", 5, DATA_FLAG_NONE));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+
+ {
+ const char kDescription[] = "Data frame with negative data byte, no FIN";
+ const unsigned char kFrameData[] = {
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x01,
+ 0xff
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame(
+ 1, "\xff", 1, DATA_FLAG_NONE));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+
+ {
+ const char kDescription[] = "'hello' data frame, with FIN";
+ const unsigned char kFrameData[] = {
+ 0x00, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x05,
+ 'h', 'e', 'l', 'l',
+ 'o'
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame(
+ 1, "hello", 5, DATA_FLAG_FIN));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+
+ {
+ const char kDescription[] = "Empty data frame";
+ const unsigned char kFrameData[] = {
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame(
+ 1, "", 0, DATA_FLAG_NONE));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+
+ {
+ const char kDescription[] = "Data frame with max stream ID";
+ const unsigned char kFrameData[] = {
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x01, 0x00, 0x00, 0x05,
+ 'h', 'e', 'l', 'l',
+ 'o'
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame(
+ 0x7fffffff, "hello", 5, DATA_FLAG_FIN));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+}
+
+TEST_F(SpdyFramerTest, CreateSynStreamUncompressed) {
+ SpdyFramer framer;
+ FramerSetEnableCompressionHelper(&framer, false);
+
+ {
+ const char kDescription[] = "SYN_STREAM frame, lowest pri, no FIN";
+
+ SpdyHeaderBlock headers;
+ headers["bar"] = "foo";
+ headers["foo"] = "bar";
+
+ const unsigned char kFrameData[] = {
+ 0x80, 0x01, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ 0xC0, 0x00, 0x00, 0x02,
+ 0x00, 0x03, 'b', 'a',
+ 'r', 0x00, 0x03, 'f',
+ 'o', 'o', 0x00, 0x03,
+ 'f', 'o', 'o', 0x00,
+ 0x03, 'b', 'a', 'r'
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateSynStream(
+ 1, 0, SPDY_PRIORITY_LOWEST, CONTROL_FLAG_NONE,
+ false, &headers));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+
+ {
+ const char kDescription[] =
+ "SYN_STREAM frame with a 0-length header name, highest pri, FIN, "
+ "max stream ID";
+
+ SpdyHeaderBlock headers;
+ headers[""] = "foo";
+ headers["foo"] = "bar";
+
+ const unsigned char kFrameData[] = {
+ 0x80, 0x01, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x1D,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x03,
+ 'f', 'o', 'o', 0x00,
+ 0x03, 'f', 'o', 'o',
+ 0x00, 0x03, 'b', 'a',
+ 'r'
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateSynStream(
+ 0x7fffffff, 0x7fffffff, SPDY_PRIORITY_HIGHEST, CONTROL_FLAG_FIN,
+ false, &headers));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+
+ {
+ const char kDescription[] =
+ "SYN_STREAM frame with a 0-length header val, highest pri, FIN, "
+ "max stream ID";
+
+ SpdyHeaderBlock headers;
+ headers["bar"] = "foo";
+ headers["foo"] = "";
+
+ const unsigned char kFrameData[] = {
+ 0x80, 0x01, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x1D,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x03, 'b', 'a',
+ 'r', 0x00, 0x03, 'f',
+ 'o', 'o', 0x00, 0x03,
+ 'f', 'o', 'o', 0x00,
+ 0x00
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateSynStream(
+ 0x7fffffff, 0x7fffffff, SPDY_PRIORITY_HIGHEST, CONTROL_FLAG_FIN,
+ false, &headers));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+}
+
+TEST_F(SpdyFramerTest, CreateSynStreamCompressed) {
+ SpdyFramer framer;
+ FramerSetEnableCompressionHelper(&framer, true);
+
+ {
+ const char kDescription[] =
+ "SYN_STREAM frame, lowest pri, no FIN";
+
+ SpdyHeaderBlock headers;
+ headers["bar"] = "foo";
+ headers["foo"] = "bar";
+
+ const unsigned char kFrameData[] = {
+ 0x80, 0x01, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x25,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ 0xC0, 0x00, 0x38, 0xea,
+ 0xdf, 0xa2, 0x51, 0xb2,
+ 0x62, 0x60, 0x62, 0x60,
+ 0x4e, 0x4a, 0x2c, 0x62,
+ 0x60, 0x4e, 0xcb, 0xcf,
+ 0x87, 0x12, 0x40, 0x2e,
+ 0x00, 0x00, 0x00, 0xff,
+ 0xff
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateSynStream(
+ 1, 0, SPDY_PRIORITY_LOWEST, CONTROL_FLAG_NONE,
+ true, &headers));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+}
+
+TEST_F(SpdyFramerTest, CreateSynReplyUncompressed) {
+ SpdyFramer framer;
+ FramerSetEnableCompressionHelper(&framer, false);
+
+ {
+ const char kDescription[] = "SYN_REPLY frame, no FIN";
+
+ SpdyHeaderBlock headers;
+ headers["bar"] = "foo";
+ headers["foo"] = "bar";
+
+ const unsigned char kFrameData[] = {
+ 0x80, 0x01, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x1C,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x03, 'b', 'a',
+ 'r', 0x00, 0x03, 'f',
+ 'o', 'o', 0x00, 0x03,
+ 'f', 'o', 'o', 0x00,
+ 0x03, 'b', 'a', 'r'
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateSynReply(
+ 1, CONTROL_FLAG_NONE, false, &headers));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+
+ {
+ const char kDescription[] =
+ "SYN_REPLY frame with a 0-length header name, FIN, max stream ID";
+
+ SpdyHeaderBlock headers;
+ headers[""] = "foo";
+ headers["foo"] = "bar";
+
+ const unsigned char kFrameData[] = {
+ 0x80, 0x01, 0x00, 0x02,
+ 0x01, 0x00, 0x00, 0x19,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x03,
+ 'f', 'o', 'o', 0x00,
+ 0x03, 'f', 'o', 'o',
+ 0x00, 0x03, 'b', 'a',
+ 'r'
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateSynReply(
+ 0x7fffffff, CONTROL_FLAG_FIN, false, &headers));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+
+ {
+ const char kDescription[] =
+ "SYN_REPLY frame with a 0-length header val, FIN, max stream ID";
+
+ SpdyHeaderBlock headers;
+ headers["bar"] = "foo";
+ headers["foo"] = "";
+
+ const unsigned char kFrameData[] = {
+ 0x80, 0x01, 0x00, 0x02,
+ 0x01, 0x00, 0x00, 0x19,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x03, 'b', 'a',
+ 'r', 0x00, 0x03, 'f',
+ 'o', 'o', 0x00, 0x03,
+ 'f', 'o', 'o', 0x00,
+ 0x00
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateSynReply(
+ 0x7fffffff, CONTROL_FLAG_FIN, false, &headers));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+}
+
+TEST_F(SpdyFramerTest, CreateSynReplyCompressed) {
+ SpdyFramer framer;
+ FramerSetEnableCompressionHelper(&framer, true);
+
+ {
+ const char kDescription[] = "SYN_REPLY frame, no FIN";
+
+ SpdyHeaderBlock headers;
+ headers["bar"] = "foo";
+ headers["foo"] = "bar";
+
+ const unsigned char kFrameData[] = {
+ 0x80, 0x01, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x21,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x38, 0xea,
+ 0xdf, 0xa2, 0x51, 0xb2,
+ 0x62, 0x60, 0x62, 0x60,
+ 0x4e, 0x4a, 0x2c, 0x62,
+ 0x60, 0x4e, 0xcb, 0xcf,
+ 0x87, 0x12, 0x40, 0x2e,
+ 0x00, 0x00, 0x00, 0xff,
+ 0xff
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateSynReply(
+ 1, CONTROL_FLAG_NONE, true, &headers));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+}
+
+TEST_F(SpdyFramerTest, CreateRstStream) {
+ SpdyFramer framer;
+
+ {
+ const char kDescription[] = "RST_STREAM frame";
+ const unsigned char kFrameData[] = {
+ 0x80, 0x01, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x01,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateRstStream(1, PROTOCOL_ERROR));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+
+ {
+ const char kDescription[] = "RST_STREAM frame with max stream ID";
+ const unsigned char kFrameData[] = {
+ 0x80, 0x01, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x08,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x01,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateRstStream(0x7FFFFFFF,
+ PROTOCOL_ERROR));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+
+ {
+ const char kDescription[] = "RST_STREAM frame with max status code";
+ const unsigned char kFrameData[] = {
+ 0x80, 0x01, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x08,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x06,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateRstStream(0x7FFFFFFF,
+ INTERNAL_ERROR));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+}
+
+TEST_F(SpdyFramerTest, CreateSettings) {
+ SpdyFramer framer;
+
+ {
+ const char kDescription[] = "Basic SETTINGS frame";
+
+ SpdySettings settings;
+ settings.push_back(SpdySetting(0x00000000, 0x00000000));
+ settings.push_back(SpdySetting(0xffffffff, 0x00000001));
+ settings.push_back(SpdySetting(0xff000001, 0x00000002));
+
+ // Duplicates allowed
+ settings.push_back(SpdySetting(0x01000002, 0x00000003));
+ settings.push_back(SpdySetting(0x01000002, 0x00000003));
+
+ settings.push_back(SpdySetting(0x01000003, 0x000000ff));
+ settings.push_back(SpdySetting(0x01000004, 0xff000001));
+ settings.push_back(SpdySetting(0x01000004, 0xffffffff));
+
+ const unsigned char kFrameData[] = {
+ 0x80, 0x01, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x44,
+ 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0xff, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x01,
+ 0xff, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x01, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x03,
+ 0x01, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x03,
+ 0x01, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0xff,
+ 0x01, 0x00, 0x00, 0x04,
+ 0xff, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x04,
+ 0xff, 0xff, 0xff, 0xff,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateSettings(settings));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+
+ {
+ const char kDescription[] = "Empty SETTINGS frame";
+
+ SpdySettings settings;
+
+ const unsigned char kFrameData[] = {
+ 0x80, 0x01, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x00,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateSettings(settings));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+}
+
+TEST_F(SpdyFramerTest, CreateNopFrame) {
+ SpdyFramer framer;
+
+ {
+ const char kDescription[] = "NOOP frame";
+ const unsigned char kFrameData[] = {
+ 0x80, 0x01, 0x00, 0x05,
+ 0x00, 0x00, 0x00, 0x00,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateNopFrame());
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+}
+
+TEST_F(SpdyFramerTest, CreateGoAway) {
+ SpdyFramer framer;
+
+ {
+ const char kDescription[] = "GOAWAY frame";
+ const unsigned char kFrameData[] = {
+ 0x80, 0x01, 0x00, 0x07,
+ 0x00, 0x00, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x00,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateGoAway(0));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+
+ {
+ const char kDescription[] = "GOAWAY frame with max stream ID";
+ const unsigned char kFrameData[] = {
+ 0x80, 0x01, 0x00, 0x07,
+ 0x00, 0x00, 0x00, 0x04,
+ 0x7f, 0xff, 0xff, 0xff,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateGoAway(0x7FFFFFFF));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+}
+
+TEST_F(SpdyFramerTest, CreateWindowUpdate) {
+ SpdyFramer framer;
+
+ {
+ const char kDescription[] = "WINDOW_UPDATE frame";
+ const unsigned char kFrameData[] = {
+ 0x80, 0x01, 0x00, 0x09,
+ 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x01,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateWindowUpdate(1, 1));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+
+ {
+ const char kDescription[] = "WINDOW_UPDATE frame with max stream ID";
+ const unsigned char kFrameData[] = {
+ 0x80, 0x01, 0x00, 0x09,
+ 0x00, 0x00, 0x00, 0x08,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x01,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateWindowUpdate(0x7FFFFFFF, 1));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+
+ {
+ const char kDescription[] = "WINDOW_UPDATE frame with max window delta";
+ const unsigned char kFrameData[] = {
+ 0x80, 0x01, 0x00, 0x09,
+ 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x7f, 0xff, 0xff, 0xff,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateWindowUpdate(1, 0x7FFFFFFF));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+}
+
+} // namespace
diff --git a/net/spdy/spdy_http_stream.cc b/net/spdy/spdy_http_stream.cc
new file mode 100644
index 0000000..581cb27
--- /dev/null
+++ b/net/spdy/spdy_http_stream.cc
@@ -0,0 +1,462 @@
+// 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 "net/spdy/spdy_http_stream.h"
+
+#include <algorithm>
+#include <list>
+#include <string>
+
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "net/base/load_flags.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_info.h"
+#include "net/spdy/spdy_session.h"
+
+namespace {
+
+// Convert a SpdyHeaderBlock into an HttpResponseInfo.
+// |headers| input parameter with the SpdyHeaderBlock.
+// |info| output parameter for the HttpResponseInfo.
+// Returns true if successfully converted. False if there was a failure
+// or if the SpdyHeaderBlock was invalid.
+bool SpdyHeadersToHttpResponse(const spdy::SpdyHeaderBlock& headers,
+ net::HttpResponseInfo* response) {
+ std::string version;
+ std::string status;
+
+ // The "status" and "version" headers are required.
+ spdy::SpdyHeaderBlock::const_iterator it;
+ it = headers.find("status");
+ if (it == headers.end()) {
+ LOG(ERROR) << "SpdyHeaderBlock without status header.";
+ return false;
+ }
+ status = it->second;
+
+ // Grab the version. If not provided by the server,
+ it = headers.find("version");
+ if (it == headers.end()) {
+ LOG(ERROR) << "SpdyHeaderBlock without version header.";
+ return false;
+ }
+ version = it->second;
+
+ response->response_time = base::Time::Now();
+
+ std::string raw_headers(version);
+ raw_headers.push_back(' ');
+ raw_headers.append(status);
+ raw_headers.push_back('\0');
+ for (it = headers.begin(); it != headers.end(); ++it) {
+ // For each value, if the server sends a NUL-separated
+ // list of values, we separate that back out into
+ // individual headers for each value in the list.
+ // e.g.
+ // Set-Cookie "foo\0bar"
+ // becomes
+ // Set-Cookie: foo\0
+ // Set-Cookie: bar\0
+ std::string value = it->second;
+ size_t start = 0;
+ size_t end = 0;
+ do {
+ end = value.find('\0', start);
+ std::string tval;
+ if (end != value.npos)
+ tval = value.substr(start, (end - start));
+ else
+ tval = value.substr(start);
+ raw_headers.append(it->first);
+ raw_headers.push_back(':');
+ raw_headers.append(tval);
+ raw_headers.push_back('\0');
+ start = end + 1;
+ } while (end != value.npos);
+ }
+
+ response->headers = new net::HttpResponseHeaders(raw_headers);
+ response->was_fetched_via_spdy = true;
+ return true;
+}
+
+// Create a SpdyHeaderBlock for a Spdy SYN_STREAM Frame from
+// a HttpRequestInfo block.
+void CreateSpdyHeadersFromHttpRequest(
+ const net::HttpRequestInfo& info, spdy::SpdyHeaderBlock* headers) {
+ // TODO(willchan): It's not really necessary to convert from
+ // HttpRequestHeaders to spdy::SpdyHeaderBlock.
+
+ static const char kHttpProtocolVersion[] = "HTTP/1.1";
+
+ net::HttpRequestHeaders::Iterator it(info.extra_headers);
+
+ while (it.GetNext()) {
+ std::string name = StringToLowerASCII(it.name());
+ if (headers->find(name) == headers->end()) {
+ (*headers)[name] = it.value();
+ } else {
+ std::string new_value = (*headers)[name];
+ new_value.append(1, '\0'); // +=() doesn't append 0's
+ new_value += it.value();
+ (*headers)[name] = new_value;
+ }
+ }
+
+ // TODO(mbelshe): Add Proxy headers here. (See http_network_transaction.cc)
+ // TODO(mbelshe): Add authentication headers here.
+
+ (*headers)["method"] = info.method;
+ (*headers)["url"] = info.url.spec();
+ (*headers)["version"] = kHttpProtocolVersion;
+ if (!info.referrer.is_empty())
+ (*headers)["referer"] = info.referrer.spec();
+
+ // Honor load flags that impact proxy caches.
+ if (info.load_flags & net::LOAD_BYPASS_CACHE) {
+ (*headers)["pragma"] = "no-cache";
+ (*headers)["cache-control"] = "no-cache";
+ } else if (info.load_flags & net::LOAD_VALIDATE_CACHE) {
+ (*headers)["cache-control"] = "max-age=0";
+ }
+}
+
+} // anonymous namespace
+
+namespace net {
+
+SpdyHttpStream::SpdyHttpStream()
+ : ALLOW_THIS_IN_INITIALIZER_LIST(read_callback_factory_(this)),
+ stream_(NULL),
+ spdy_session_(NULL),
+ response_info_(NULL),
+ download_finished_(false),
+ user_callback_(NULL),
+ user_buffer_len_(0),
+ buffered_read_callback_pending_(false),
+ more_read_data_pending_(false) { }
+
+SpdyHttpStream::~SpdyHttpStream() {
+ if (stream_)
+ stream_->DetachDelegate();
+}
+
+int SpdyHttpStream::InitializeStream(
+ SpdySession* spdy_session,
+ const HttpRequestInfo& request_info,
+ const BoundNetLog& stream_net_log,
+ CompletionCallback* callback) {
+ spdy_session_ = spdy_session;
+ request_info_ = request_info;
+ if (request_info_.method == "GET") {
+ int error = spdy_session_->GetPushStream(request_info.url, &stream_,
+ stream_net_log);
+ if (error != OK)
+ return error;
+ }
+
+ if (stream_.get())
+ return OK;
+ else
+ return spdy_session_->CreateStream(request_info_.url,
+ request_info_.priority, &stream_,
+ stream_net_log, callback);
+}
+
+void SpdyHttpStream::InitializeRequest(
+ base::Time request_time,
+ UploadDataStream* upload_data) {
+ CHECK(stream_.get());
+ stream_->SetDelegate(this);
+ linked_ptr<spdy::SpdyHeaderBlock> headers(new spdy::SpdyHeaderBlock);
+ CreateSpdyHeadersFromHttpRequest(request_info_, headers.get());
+ stream_->set_spdy_headers(headers);
+
+ stream_->SetRequestTime(request_time);
+ // This should only get called in the case of a request occuring
+ // during server push that has already begun but hasn't finished,
+ // so we set the response's request time to be the actual one
+ if (response_info_)
+ response_info_->request_time = request_time;
+
+ CHECK(!request_body_stream_.get());
+ if (upload_data) {
+ if (upload_data->size())
+ request_body_stream_.reset(upload_data);
+ else
+ delete upload_data;
+ }
+}
+
+const HttpResponseInfo* SpdyHttpStream::GetResponseInfo() const {
+ return response_info_;
+}
+
+uint64 SpdyHttpStream::GetUploadProgress() const {
+ if (!request_body_stream_.get())
+ return 0;
+
+ return request_body_stream_->position();
+}
+
+int SpdyHttpStream::ReadResponseHeaders(CompletionCallback* callback) {
+ DCHECK(stream_->is_idle());
+ // Note: The SpdyStream may have already received the response headers, so
+ // this call may complete synchronously.
+ CHECK(callback);
+
+ if (stream_->response_complete())
+ return stream_->response_status();
+
+ int result = stream_->DoReadResponseHeaders();
+ if (result == ERR_IO_PENDING) {
+ CHECK(!user_callback_);
+ user_callback_ = callback;
+ }
+ return result;
+}
+
+int SpdyHttpStream::ReadResponseBody(
+ IOBuffer* buf, int buf_len, CompletionCallback* callback) {
+ CHECK(buf);
+ CHECK(buf_len);
+ CHECK(callback);
+ DCHECK(stream_->is_idle());
+
+ // If we have data buffered, complete the IO immediately.
+ if (!response_body_.empty()) {
+ int bytes_read = 0;
+ while (!response_body_.empty() && buf_len > 0) {
+ scoped_refptr<IOBufferWithSize> data = response_body_.front();
+ const int bytes_to_copy = std::min(buf_len, data->size());
+ memcpy(&(buf->data()[bytes_read]), data->data(), bytes_to_copy);
+ buf_len -= bytes_to_copy;
+ if (bytes_to_copy == data->size()) {
+ response_body_.pop_front();
+ } else {
+ const int bytes_remaining = data->size() - bytes_to_copy;
+ IOBufferWithSize* new_buffer = new IOBufferWithSize(bytes_remaining);
+ memcpy(new_buffer->data(), &(data->data()[bytes_to_copy]),
+ bytes_remaining);
+ response_body_.pop_front();
+ response_body_.push_front(new_buffer);
+ }
+ bytes_read += bytes_to_copy;
+ }
+ return bytes_read;
+ } else if (stream_->response_complete()) {
+ return stream_->response_status();
+ }
+
+ CHECK(!user_callback_);
+ CHECK(!user_buffer_);
+ CHECK_EQ(0, user_buffer_len_);
+
+ user_callback_ = callback;
+ user_buffer_ = buf;
+ user_buffer_len_ = buf_len;
+ return ERR_IO_PENDING;
+}
+
+int SpdyHttpStream::SendRequest(HttpResponseInfo* response,
+ CompletionCallback* callback) {
+ CHECK(callback);
+ CHECK(!stream_->cancelled());
+ CHECK(response);
+
+ if (stream_->response_complete()) {
+ if (stream_->response_status() == OK)
+ return ERR_FAILED;
+ else
+ return stream_->response_status();
+ }
+
+ // SendRequest can be called in two cases.
+ //
+ // a) A client initiated request. In this case, |response_info_| should be
+ // NULL to start with.
+ // b) A client request which matches a response that the server has already
+ // pushed. In this case, the value of |*push_response_info_| is copied
+ // over to the new response object |*response|. |push_response_info_| is
+ // deleted, and |response_info_| is reset |response|.
+ if (push_response_info_.get()) {
+ *response = *push_response_info_;
+ push_response_info_.reset();
+ response_info_ = NULL;
+ }
+
+ DCHECK_EQ(static_cast<HttpResponseInfo*>(NULL), response_info_);
+ response_info_ = response;
+
+ bool has_upload_data = request_body_stream_.get() != NULL;
+ int result = stream_->DoSendRequest(has_upload_data);
+ if (result == ERR_IO_PENDING) {
+ CHECK(!user_callback_);
+ user_callback_ = callback;
+ }
+ return result;
+}
+
+void SpdyHttpStream::Cancel() {
+ if (spdy_session_)
+ spdy_session_->CancelPendingCreateStreams(&stream_);
+ user_callback_ = NULL;
+ if (stream_)
+ stream_->Cancel();
+}
+
+bool SpdyHttpStream::OnSendHeadersComplete(int status) {
+ return request_body_stream_.get() == NULL;
+}
+
+int SpdyHttpStream::OnSendBody() {
+ CHECK(request_body_stream_.get());
+ int buf_len = static_cast<int>(request_body_stream_->buf_len());
+ if (!buf_len)
+ return OK;
+ return stream_->WriteStreamData(request_body_stream_->buf(), buf_len,
+ spdy::DATA_FLAG_FIN);
+}
+
+bool SpdyHttpStream::OnSendBodyComplete(int status) {
+ CHECK(request_body_stream_.get());
+ request_body_stream_->DidConsume(status);
+ return request_body_stream_->eof();
+}
+
+int SpdyHttpStream::OnResponseReceived(const spdy::SpdyHeaderBlock& response,
+ base::Time response_time,
+ int status) {
+ if (!response_info_) {
+ DCHECK(stream_->pushed());
+ push_response_info_.reset(new HttpResponseInfo);
+ response_info_ = push_response_info_.get();
+ }
+
+ if (!SpdyHeadersToHttpResponse(response, response_info_)) {
+ status = ERR_INVALID_RESPONSE;
+ } else {
+ stream_->GetSSLInfo(&response_info_->ssl_info,
+ &response_info_->was_npn_negotiated);
+ response_info_->request_time = stream_->GetRequestTime();
+ response_info_->vary_data.Init(request_info_, *response_info_->headers);
+ // TODO(ahendrickson): This is recorded after the entire SYN_STREAM control
+ // frame has been received and processed. Move to framer?
+ response_info_->response_time = response_time;
+ }
+
+ if (user_callback_)
+ DoCallback(status);
+ return status;
+}
+
+void SpdyHttpStream::OnDataReceived(const char* data, int length) {
+ // Note that data may be received for a SpdyStream prior to the user calling
+ // ReadResponseBody(), therefore user_buffer_ may be NULL. This may often
+ // happen for server initiated streams.
+ if (length > 0 && !stream_->response_complete()) {
+ // Save the received data.
+ IOBufferWithSize* io_buffer = new IOBufferWithSize(length);
+ memcpy(io_buffer->data(), data, length);
+ response_body_.push_back(io_buffer);
+
+ if (user_buffer_) {
+ // Handing small chunks of data to the caller creates measurable overhead.
+ // We buffer data in short time-spans and send a single read notification.
+ ScheduleBufferedReadCallback();
+ }
+ }
+}
+
+void SpdyHttpStream::OnDataSent(int length) {
+ // For HTTP streams, no data is sent from the client while in the OPEN state,
+ // so it is never called.
+ NOTREACHED();
+}
+
+void SpdyHttpStream::OnClose(int status) {
+ bool invoked_callback = false;
+ if (status == net::OK) {
+ // We need to complete any pending buffered read now.
+ invoked_callback = DoBufferedReadCallback();
+ }
+ if (!invoked_callback && user_callback_)
+ DoCallback(status);
+}
+
+void SpdyHttpStream::ScheduleBufferedReadCallback() {
+ // If there is already a scheduled DoBufferedReadCallback, don't issue
+ // another one. Mark that we have received more data and return.
+ if (buffered_read_callback_pending_) {
+ more_read_data_pending_ = true;
+ return;
+ }
+
+ more_read_data_pending_ = false;
+ buffered_read_callback_pending_ = true;
+ const int kBufferTimeMs = 1;
+ MessageLoop::current()->PostDelayedTask(FROM_HERE, read_callback_factory_.
+ NewRunnableMethod(&SpdyHttpStream::DoBufferedReadCallback),
+ kBufferTimeMs);
+}
+
+// Checks to see if we should wait for more buffered data before notifying
+// the caller. Returns true if we should wait, false otherwise.
+bool SpdyHttpStream::ShouldWaitForMoreBufferedData() const {
+ // If the response is complete, there is no point in waiting.
+ if (stream_->response_complete())
+ return false;
+
+ int bytes_buffered = 0;
+ std::list<scoped_refptr<IOBufferWithSize> >::const_iterator it;
+ for (it = response_body_.begin();
+ it != response_body_.end() && bytes_buffered < user_buffer_len_;
+ ++it)
+ bytes_buffered += (*it)->size();
+
+ return bytes_buffered < user_buffer_len_;
+}
+
+bool SpdyHttpStream::DoBufferedReadCallback() {
+ read_callback_factory_.RevokeAll();
+ buffered_read_callback_pending_ = false;
+
+ // If the transaction is cancelled or errored out, we don't need to complete
+ // the read.
+ if (!stream_ || stream_->response_status() != OK || stream_->cancelled())
+ return false;
+
+ // When more_read_data_pending_ is true, it means that more data has
+ // arrived since we started waiting. Wait a little longer and continue
+ // to buffer.
+ if (more_read_data_pending_ && ShouldWaitForMoreBufferedData()) {
+ ScheduleBufferedReadCallback();
+ return false;
+ }
+
+ int rv = 0;
+ if (user_buffer_) {
+ rv = ReadResponseBody(user_buffer_, user_buffer_len_, user_callback_);
+ CHECK_NE(rv, ERR_IO_PENDING);
+ user_buffer_ = NULL;
+ user_buffer_len_ = 0;
+ DoCallback(rv);
+ return true;
+ }
+ return false;
+}
+
+void SpdyHttpStream::DoCallback(int rv) {
+ CHECK_NE(rv, ERR_IO_PENDING);
+ CHECK(user_callback_);
+
+ // Since Run may result in being called back, clear user_callback_ in advance.
+ CompletionCallback* c = user_callback_;
+ user_callback_ = NULL;
+ c->Run(rv);
+}
+
+} // namespace net
diff --git a/net/spdy/spdy_http_stream.h b/net/spdy/spdy_http_stream.h
new file mode 100644
index 0000000..3926501
--- /dev/null
+++ b/net/spdy/spdy_http_stream.h
@@ -0,0 +1,154 @@
+// 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.
+
+#ifndef NET_SPDY_SPDY_HTTP_STREAM_H_
+#define NET_SPDY_SPDY_HTTP_STREAM_H_
+
+#include <list>
+
+#include "base/basictypes.h"
+#include "base/ref_counted.h"
+#include "base/task.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_log.h"
+#include "net/http/http_request_info.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_stream.h"
+
+namespace net {
+
+class HttpResponseInfo;
+class IOBuffer;
+class SpdySession;
+class UploadData;
+class UploadDataStream;
+
+// The SpdyHttpStream is a HTTP-specific type of stream known to a SpdySession.
+class SpdyHttpStream : public SpdyStream::Delegate {
+ public:
+ // SpdyHttpStream constructor
+ SpdyHttpStream();
+ virtual ~SpdyHttpStream();
+
+ SpdyStream* stream() { return stream_.get(); }
+
+ // Initialize stream. Must be called before calling InitializeRequest().
+ int InitializeStream(SpdySession* spdy_session,
+ const HttpRequestInfo& request_info,
+ const BoundNetLog& stream_net_log,
+ CompletionCallback* callback);
+
+ // Initialize request. Must be called before calling SendRequest().
+ // SpdyHttpStream takes ownership of |upload_data|. |upload_data| may be NULL.
+ void InitializeRequest(base::Time request_time,
+ UploadDataStream* upload_data);
+
+ const HttpResponseInfo* GetResponseInfo() const;
+
+ // ===================================================
+ // Interface for [Http|Spdy]NetworkTransaction to use.
+
+ // Sends the request.
+ // |callback| is used when this completes asynchronously.
+ // The actual SYN_STREAM packet will be sent if the stream is non-pushed.
+ int SendRequest(HttpResponseInfo* response,
+ CompletionCallback* callback);
+
+ // Reads the response headers. Returns a net error code.
+ int ReadResponseHeaders(CompletionCallback* callback);
+
+ // Reads the response body. Returns a net error code or the number of bytes
+ // read.
+ int ReadResponseBody(
+ IOBuffer* buf, int buf_len, CompletionCallback* callback);
+
+ // Cancels any callbacks from being invoked and deletes the stream.
+ void Cancel();
+
+ // Returns the number of bytes uploaded.
+ uint64 GetUploadProgress() const;
+
+ // ===================================================
+ // SpdyStream::Delegate.
+
+ virtual bool OnSendHeadersComplete(int status);
+ virtual int OnSendBody();
+ virtual bool OnSendBodyComplete(int status);
+
+ // Called by the SpdySession when a response (e.g. a SYN_REPLY) has been
+ // received for this stream.
+ // SpdyHttpSession calls back |callback| set by SendRequest or
+ // ReadResponseHeaders.
+ virtual int OnResponseReceived(const spdy::SpdyHeaderBlock& response,
+ base::Time response_time,
+ int status);
+
+ // Called by the SpdySession when response data has been received for this
+ // stream. This callback may be called multiple times as data arrives
+ // from the network, and will never be called prior to OnResponseReceived.
+ // SpdyHttpSession schedule to call back |callback| set by ReadResponseBody.
+ virtual void OnDataReceived(const char* buffer, int bytes);
+
+ // For HTTP streams, no data is sent from the client while in the OPEN state,
+ // so OnDataSent is never called.
+ virtual void OnDataSent(int length);
+
+ // Called by the SpdySession when the request is finished. This callback
+ // will always be called at the end of the request and signals to the
+ // stream that the stream has no more network events. No further callbacks
+ // to the stream will be made after this call.
+ // SpdyHttpSession call back |callback| set by SendRequest,
+ // ReadResponseHeaders or ReadResponseBody.
+ virtual void OnClose(int status);
+
+ private:
+ // Call the user callback.
+ void DoCallback(int rv);
+
+ void ScheduleBufferedReadCallback();
+
+ // Returns true if the callback is invoked.
+ bool DoBufferedReadCallback();
+ bool ShouldWaitForMoreBufferedData() const;
+
+ ScopedRunnableMethodFactory<SpdyHttpStream> read_callback_factory_;
+ scoped_refptr<SpdyStream> stream_;
+ scoped_refptr<SpdySession> spdy_session_;
+
+ // The request to send.
+ HttpRequestInfo request_info_;
+
+ scoped_ptr<UploadDataStream> request_body_stream_;
+
+ // |response_info_| is the HTTP response data object which is filled in
+ // when a SYN_REPLY comes in for the stream.
+ // It is not owned by this stream object, or point to |push_response_info_|.
+ HttpResponseInfo* response_info_;
+
+ scoped_ptr<HttpResponseInfo> push_response_info_;
+
+ bool download_finished_;
+
+ // We buffer the response body as it arrives asynchronously from the stream.
+ // TODO(mbelshe): is this infinite buffering?
+ std::list<scoped_refptr<IOBufferWithSize> > response_body_;
+
+ CompletionCallback* user_callback_;
+
+ // User provided buffer for the ReadResponseBody() response.
+ scoped_refptr<IOBuffer> user_buffer_;
+ int user_buffer_len_;
+
+ // Is there a scheduled read callback pending.
+ bool buffered_read_callback_pending_;
+ // Has more data been received from the network during the wait for the
+ // scheduled read callback.
+ bool more_read_data_pending_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyHttpStream);
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_HTTP_STREAM_H_
diff --git a/net/spdy/spdy_http_stream_unittest.cc b/net/spdy/spdy_http_stream_unittest.cc
new file mode 100644
index 0000000..65a8068
--- /dev/null
+++ b/net/spdy/spdy_http_stream_unittest.cc
@@ -0,0 +1,76 @@
+// 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 "net/spdy/spdy_http_stream.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+class SpdyHttpStreamTest : public testing::Test {
+ protected:
+ SpdyHttpStreamTest(){}
+
+ void EnableCompression(bool enabled) {
+ spdy::SpdyFramer::set_enable_compression_default(enabled);
+ }
+
+ virtual void TearDown() {
+ MessageLoop::current()->RunAllPending();
+ }
+};
+
+TEST_F(SpdyHttpStreamTest, SendRequest) {
+ EnableCompression(false);
+ SpdySession::SetSSLMode(false);
+
+ SpdySessionDependencies session_deps;
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = {
+ CreateMockWrite(*req.get(), 1),
+ };
+ MockRead reads[] = {
+ MockRead(false, 0, 2) // EOF
+ };
+ scoped_refptr<OrderedSocketData> data(
+ new OrderedSocketData(reads, arraysize(reads),
+ writes, arraysize(writes)));
+ session_deps.socket_factory.AddSocketDataProvider(data.get());
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+ scoped_refptr<SpdySessionPool> spdy_session_pool(
+ http_session->spdy_session_pool());
+ HostPortPair host_port_pair("www.google.com", 80);
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(
+ host_port_pair, http_session.get(), BoundNetLog());
+ scoped_refptr<TCPSocketParams> tcp_params =
+ new TCPSocketParams(host_port_pair.host, host_port_pair.port,
+ MEDIUM, GURL(), false);
+ int rv = session->Connect(host_port_pair.host, tcp_params, MEDIUM);
+ ASSERT_EQ(OK, rv);
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ TestCompletionCallback callback;
+ HttpResponseInfo response;
+ scoped_ptr<SpdyHttpStream> http_stream(new SpdyHttpStream());
+ ASSERT_EQ(
+ OK,
+ http_stream->InitializeStream(session, request, BoundNetLog(), NULL));
+ http_stream->InitializeRequest(base::Time::Now(), NULL);
+ EXPECT_EQ(ERR_IO_PENDING,
+ http_stream->SendRequest(&response, &callback));
+ MessageLoop::current()->RunAllPending();
+ EXPECT_TRUE(spdy_session_pool->HasSession(host_port_pair));
+ spdy_session_pool->Remove(session);
+}
+
+// TODO(willchan): Write a longer test for SpdyStream that exercises all
+// methods.
+
+} // namespace net
diff --git a/net/spdy/spdy_io_buffer.cc b/net/spdy/spdy_io_buffer.cc
new file mode 100644
index 0000000..f7120c8
--- /dev/null
+++ b/net/spdy/spdy_io_buffer.cc
@@ -0,0 +1,29 @@
+// Copyright (c) 2009 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 "net/spdy/spdy_io_buffer.h"
+#include "net/spdy/spdy_stream.h"
+
+namespace net {
+
+// static
+uint64 SpdyIOBuffer::order_ = 0;
+
+SpdyIOBuffer::SpdyIOBuffer(
+ IOBuffer* buffer, int size, int priority, SpdyStream* stream)
+ : buffer_(new DrainableIOBuffer(buffer, size)),
+ priority_(priority),
+ position_(++order_),
+ stream_(stream) {}
+
+SpdyIOBuffer::SpdyIOBuffer() : priority_(0), position_(0), stream_(NULL) {}
+
+SpdyIOBuffer::~SpdyIOBuffer() {}
+
+void SpdyIOBuffer::release() {
+ buffer_ = NULL;
+ stream_ = NULL;
+}
+
+} // namespace net
diff --git a/net/spdy/spdy_io_buffer.h b/net/spdy/spdy_io_buffer.h
new file mode 100644
index 0000000..e34c7fe
--- /dev/null
+++ b/net/spdy/spdy_io_buffer.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2009 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.
+
+#ifndef NET_SPDY_SPDY_IO_BUFFER_H_
+#define NET_SPDY_SPDY_IO_BUFFER_H_
+
+#include "base/ref_counted.h"
+#include "net/base/io_buffer.h"
+
+namespace net {
+
+class SpdyStream;
+
+// A class for managing SPDY IO buffers. These buffers need to be prioritized
+// so that the SpdySession sends them in the right order. Further, they need
+// to track the SpdyStream which they are associated with so that incremental
+// completion of the IO can notify the appropriate stream of completion.
+class SpdyIOBuffer {
+ public:
+ // Constructor
+ // |buffer| is the actual data buffer.
+ // |size| is the size of the data buffer.
+ // |priority| is the priority of this buffer. Lower numbers are higher
+ // priority.
+ // |stream| is a pointer to the stream which is managing this buffer.
+ SpdyIOBuffer(IOBuffer* buffer, int size, int priority, SpdyStream* stream);
+ SpdyIOBuffer();
+ ~SpdyIOBuffer();
+
+ // Accessors.
+ DrainableIOBuffer* buffer() const { return buffer_; }
+ size_t size() const { return buffer_->size(); }
+ void release();
+ int priority() const { return priority_; }
+ const scoped_refptr<SpdyStream>& stream() const { return stream_; }
+
+ // Comparison operator to support sorting.
+ bool operator<(const SpdyIOBuffer& other) const {
+ if (priority_ != other.priority_)
+ return priority_ > other.priority_;
+ return position_ > other.position_;
+ }
+
+ private:
+ scoped_refptr<DrainableIOBuffer> buffer_;
+ int priority_;
+ uint64 position_;
+ scoped_refptr<SpdyStream> stream_;
+ static uint64 order_; // Maintains a FIFO order for equal priorities.
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_IO_BUFFER_H_
diff --git a/net/spdy/spdy_network_transaction.cc b/net/spdy/spdy_network_transaction.cc
new file mode 100644
index 0000000..7814553
--- /dev/null
+++ b/net/spdy/spdy_network_transaction.cc
@@ -0,0 +1,335 @@
+// Copyright (c) 2009 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 "net/spdy/spdy_network_transaction.h"
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "base/stats_counters.h"
+#include "net/base/host_resolver.h"
+#include "net/base/io_buffer.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "net/base/upload_data_stream.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_info.h"
+#include "net/socket/tcp_client_socket_pool.h"
+#include "net/spdy/spdy_http_stream.h"
+
+using base::Time;
+
+namespace net {
+
+//-----------------------------------------------------------------------------
+
+SpdyNetworkTransaction::SpdyNetworkTransaction(HttpNetworkSession* session)
+ : ALLOW_THIS_IN_INITIALIZER_LIST(
+ io_callback_(this, &SpdyNetworkTransaction::OnIOComplete)),
+ user_callback_(NULL),
+ user_buffer_len_(0),
+ session_(session),
+ request_(NULL),
+ next_state_(STATE_NONE),
+ stream_(NULL) {
+}
+
+SpdyNetworkTransaction::~SpdyNetworkTransaction() {
+ LOG(INFO) << "SpdyNetworkTransaction dead. " << this;
+ if (stream_.get())
+ stream_->Cancel();
+}
+
+int SpdyNetworkTransaction::Start(const HttpRequestInfo* request_info,
+ CompletionCallback* callback,
+ const BoundNetLog& net_log) {
+ CHECK(request_info);
+ CHECK(callback);
+
+ SIMPLE_STATS_COUNTER("SpdyNetworkTransaction.Count");
+
+ net_log_ = net_log;
+ request_ = request_info;
+ start_time_ = base::TimeTicks::Now();
+
+ next_state_ = STATE_INIT_CONNECTION;
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ user_callback_ = callback;
+ return rv;
+}
+
+int SpdyNetworkTransaction::RestartIgnoringLastError(
+ CompletionCallback* callback) {
+ // TODO(mbelshe): implement me.
+ NOTIMPLEMENTED();
+ return ERR_NOT_IMPLEMENTED;
+}
+
+int SpdyNetworkTransaction::RestartWithCertificate(
+ X509Certificate* client_cert, CompletionCallback* callback) {
+ // TODO(mbelshe): implement me.
+ NOTIMPLEMENTED();
+ return ERR_NOT_IMPLEMENTED;
+}
+
+int SpdyNetworkTransaction::RestartWithAuth(
+ const std::wstring& username,
+ const std::wstring& password,
+ CompletionCallback* callback) {
+ // TODO(mbelshe): implement me.
+ NOTIMPLEMENTED();
+ return 0;
+}
+
+int SpdyNetworkTransaction::Read(IOBuffer* buf, int buf_len,
+ CompletionCallback* callback) {
+ DCHECK(buf);
+ DCHECK_GT(buf_len, 0);
+ DCHECK(callback);
+
+ user_buffer_ = buf;
+ user_buffer_len_ = buf_len;
+
+ next_state_ = STATE_READ_BODY;
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ user_callback_ = callback;
+ return rv;
+}
+
+const HttpResponseInfo* SpdyNetworkTransaction::GetResponseInfo() const {
+ return (response_.headers || response_.ssl_info.cert) ? &response_ : NULL;
+}
+
+LoadState SpdyNetworkTransaction::GetLoadState() const {
+ switch (next_state_) {
+ case STATE_INIT_CONNECTION_COMPLETE:
+ if (spdy_.get())
+ return spdy_->GetLoadState();
+ return LOAD_STATE_CONNECTING;
+ case STATE_GET_STREAM_COMPLETE:
+ case STATE_SEND_REQUEST_COMPLETE:
+ return LOAD_STATE_SENDING_REQUEST;
+ case STATE_READ_HEADERS_COMPLETE:
+ return LOAD_STATE_WAITING_FOR_RESPONSE;
+ case STATE_READ_BODY_COMPLETE:
+ return LOAD_STATE_READING_RESPONSE;
+ default:
+ return LOAD_STATE_IDLE;
+ }
+}
+
+uint64 SpdyNetworkTransaction::GetUploadProgress() const {
+ if (!stream_.get())
+ return 0;
+
+ return stream_->GetUploadProgress();
+}
+
+void SpdyNetworkTransaction::DoCallback(int rv) {
+ CHECK_NE(rv, ERR_IO_PENDING);
+ CHECK(user_callback_);
+
+ // Since Run may result in Read being called, clear user_callback_ up front.
+ CompletionCallback* c = user_callback_;
+ user_callback_ = NULL;
+ c->Run(rv);
+}
+
+void SpdyNetworkTransaction::OnIOComplete(int result) {
+ int rv = DoLoop(result);
+ if (rv != ERR_IO_PENDING)
+ DoCallback(rv);
+}
+
+int SpdyNetworkTransaction::DoLoop(int result) {
+ DCHECK(next_state_ != STATE_NONE);
+ DCHECK(request_);
+
+ if (!request_)
+ return 0;
+
+ int rv = result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_INIT_CONNECTION:
+ DCHECK_EQ(OK, rv);
+ net_log_.BeginEvent(NetLog::TYPE_SPDY_TRANSACTION_INIT_CONNECTION,
+ NULL);
+ rv = DoInitConnection();
+ break;
+ case STATE_INIT_CONNECTION_COMPLETE:
+ net_log_.EndEvent(NetLog::TYPE_SPDY_TRANSACTION_INIT_CONNECTION, NULL);
+ rv = DoInitConnectionComplete(rv);
+ break;
+ case STATE_GET_STREAM:
+ DCHECK_EQ(OK, rv);
+ rv = DoGetStream();
+ break;
+ case STATE_GET_STREAM_COMPLETE:
+ rv = DoGetStreamComplete(rv);
+ case STATE_SEND_REQUEST:
+ DCHECK_EQ(OK, rv);
+ net_log_.BeginEvent(NetLog::TYPE_SPDY_TRANSACTION_SEND_REQUEST, NULL);
+ rv = DoSendRequest();
+ break;
+ case STATE_SEND_REQUEST_COMPLETE:
+ net_log_.EndEvent(NetLog::TYPE_SPDY_TRANSACTION_SEND_REQUEST, NULL);
+ rv = DoSendRequestComplete(rv);
+ break;
+ case STATE_READ_HEADERS:
+ DCHECK_EQ(OK, rv);
+ net_log_.BeginEvent(NetLog::TYPE_SPDY_TRANSACTION_READ_HEADERS, NULL);
+ rv = DoReadHeaders();
+ break;
+ case STATE_READ_HEADERS_COMPLETE:
+ net_log_.EndEvent(NetLog::TYPE_SPDY_TRANSACTION_READ_HEADERS, NULL);
+ rv = DoReadHeadersComplete(rv);
+ break;
+ case STATE_READ_BODY:
+ DCHECK_EQ(OK, rv);
+ net_log_.BeginEvent(NetLog::TYPE_SPDY_TRANSACTION_READ_BODY, NULL);
+ rv = DoReadBody();
+ break;
+ case STATE_READ_BODY_COMPLETE:
+ net_log_.EndEvent(NetLog::TYPE_SPDY_TRANSACTION_READ_BODY, NULL);
+ rv = DoReadBodyComplete(rv);
+ break;
+ case STATE_NONE:
+ rv = ERR_FAILED;
+ break;
+ default:
+ NOTREACHED() << "bad state";
+ rv = ERR_FAILED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+
+ return rv;
+}
+
+int SpdyNetworkTransaction::DoInitConnection() {
+ next_state_ = STATE_INIT_CONNECTION_COMPLETE;
+
+ std::string host = request_->url.HostNoBrackets();
+ int port = request_->url.EffectiveIntPort();
+
+ // Use the fixed testing ports if they've been provided. This is useful for
+ // debugging.
+ if (SpdySession::SSLMode()) {
+ if (session_->fixed_https_port() != 0)
+ port = session_->fixed_https_port();
+ } else if (session_->fixed_http_port() != 0) {
+ port = session_->fixed_http_port();
+ }
+
+ std::string connection_group = "spdy.";
+ connection_group.append(host);
+
+ HostPortPair host_port_pair(host, port);
+ scoped_refptr<TCPSocketParams> tcp_params =
+ new TCPSocketParams(host_port_pair, request_->priority,
+ request_->referrer, false);
+
+ spdy_ = session_->spdy_session_pool()->Get(
+ host_port_pair, session_, net_log_);
+ DCHECK(spdy_);
+
+ return spdy_->Connect(
+ connection_group, tcp_params, request_->priority);
+}
+
+int SpdyNetworkTransaction::DoInitConnectionComplete(int result) {
+ if (result < 0)
+ return result;
+
+ next_state_ = STATE_GET_STREAM;
+ return OK;
+}
+
+int SpdyNetworkTransaction::DoGetStream() {
+ next_state_ = STATE_GET_STREAM_COMPLETE;
+
+ // It is possible that the spdy session was shut down while it was
+ // asynchronously waiting to connect.
+ if (spdy_->IsClosed())
+ return ERR_CONNECTION_CLOSED;
+
+ CHECK(!stream_.get());
+
+ stream_.reset(new SpdyHttpStream());
+ return stream_->InitializeStream(spdy_, *request_,
+ net_log_, &io_callback_);
+}
+
+int SpdyNetworkTransaction::DoGetStreamComplete(int result) {
+ if (result < 0) {
+ return result;
+ }
+
+ next_state_ = STATE_SEND_REQUEST;
+ return OK;
+}
+
+int SpdyNetworkTransaction::DoSendRequest() {
+ next_state_ = STATE_SEND_REQUEST_COMPLETE;
+
+ UploadDataStream* upload_data_stream = NULL;
+ if (request_->upload_data) {
+ int error_code;
+ upload_data_stream = UploadDataStream::Create(request_->upload_data,
+ &error_code);
+ if (!upload_data_stream)
+ return error_code;
+ }
+ stream_->InitializeRequest(base::Time::Now(), upload_data_stream);
+ spdy_ = NULL;
+
+ return stream_->SendRequest(&response_, &io_callback_);
+}
+
+int SpdyNetworkTransaction::DoSendRequestComplete(int result) {
+ if (result < 0) {
+ stream_.reset();
+ return result;
+ }
+
+ next_state_ = STATE_READ_HEADERS;
+ return OK;
+}
+
+int SpdyNetworkTransaction::DoReadHeaders() {
+ next_state_ = STATE_READ_HEADERS_COMPLETE;
+ return stream_->ReadResponseHeaders(&io_callback_);
+}
+
+int SpdyNetworkTransaction::DoReadHeadersComplete(int result) {
+ // TODO(willchan): Flesh out the support for HTTP authentication here.
+ return result;
+}
+
+int SpdyNetworkTransaction::DoReadBody() {
+ next_state_ = STATE_READ_BODY_COMPLETE;
+
+ return stream_->ReadResponseBody(
+ user_buffer_, user_buffer_len_, &io_callback_);
+}
+
+int SpdyNetworkTransaction::DoReadBodyComplete(int result) {
+ user_buffer_ = NULL;
+ user_buffer_len_ = 0;
+
+ if (result <= 0)
+ stream_.reset();
+
+ return result;
+}
+
+} // namespace net
diff --git a/net/spdy/spdy_network_transaction.h b/net/spdy/spdy_network_transaction.h
new file mode 100644
index 0000000..4611cda
--- /dev/null
+++ b/net/spdy/spdy_network_transaction.h
@@ -0,0 +1,123 @@
+// Copyright (c) 2009 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.
+
+#ifndef NET_SPDY_NETWORK_TRANSACTION_H_
+#define NET_SPDY_NETWORK_TRANSACTION_H_
+
+#include <string>
+#include <deque>
+
+#include "base/basictypes.h"
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "base/time.h"
+#include "net/base/completion_callback.h"
+#include "net/base/load_states.h"
+#include "net/base/net_log.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_transaction.h"
+#include "net/spdy/spdy_session.h"
+
+namespace net {
+
+class SpdySession;
+class SpdyHttpStream;
+class HttpNetworkSession;
+class HttpResponseInfo;
+class IOBuffer;
+class UploadDataStream;
+
+// A SpdyNetworkTransaction can be used to fetch HTTP conent.
+// The SpdyDelegate is the consumer of events from the SpdySession.
+class SpdyNetworkTransaction : public HttpTransaction {
+ public:
+ explicit SpdyNetworkTransaction(HttpNetworkSession* session);
+ virtual ~SpdyNetworkTransaction();
+
+ // HttpTransaction methods:
+ virtual int Start(const HttpRequestInfo* request_info,
+ CompletionCallback* callback,
+ const BoundNetLog& net_log);
+ virtual int RestartIgnoringLastError(CompletionCallback* callback);
+ virtual int RestartWithCertificate(X509Certificate* client_cert,
+ CompletionCallback* callback);
+ virtual int RestartWithAuth(const std::wstring& username,
+ const std::wstring& password,
+ CompletionCallback* callback);
+ virtual bool IsReadyToRestartForAuth() { return false; }
+ virtual int Read(IOBuffer* buf, int buf_len, CompletionCallback* callback);
+ virtual void StopCaching() {}
+ virtual const HttpResponseInfo* GetResponseInfo() const;
+ virtual LoadState GetLoadState() const;
+ virtual uint64 GetUploadProgress() const;
+
+ private:
+ FRIEND_TEST(SpdyNetworkTransactionTest, WindowUpdate);
+ FRIEND_TEST(SpdyNetworkTransactionTest, WindowUpdateOverflow);
+
+ enum State {
+ STATE_INIT_CONNECTION,
+ STATE_INIT_CONNECTION_COMPLETE,
+ STATE_GET_STREAM,
+ STATE_GET_STREAM_COMPLETE,
+ STATE_SEND_REQUEST,
+ STATE_SEND_REQUEST_COMPLETE,
+ STATE_READ_HEADERS,
+ STATE_READ_HEADERS_COMPLETE,
+ STATE_READ_BODY,
+ STATE_READ_BODY_COMPLETE,
+ STATE_NONE
+ };
+
+ void DoCallback(int result);
+ void OnIOComplete(int result);
+
+ // Runs the state transition loop.
+ int DoLoop(int result);
+
+ // Each of these methods corresponds to a State value. Those with an input
+ // argument receive the result from the previous state. If a method returns
+ // ERR_IO_PENDING, then the result from OnIOComplete will be passed to the
+ // next state method as the result arg.
+ int DoInitConnection();
+ int DoInitConnectionComplete(int result);
+ int DoGetStream();
+ int DoGetStreamComplete(int result);
+ int DoSendRequest();
+ int DoSendRequestComplete(int result);
+ int DoReadHeaders();
+ int DoReadHeadersComplete(int result);
+ int DoReadBody();
+ int DoReadBodyComplete(int result);
+
+ BoundNetLog net_log_;
+
+ scoped_refptr<SpdySession> spdy_;
+
+ CompletionCallbackImpl<SpdyNetworkTransaction> io_callback_;
+ CompletionCallback* user_callback_;
+
+ // Used to pass onto the SpdyStream
+ scoped_refptr<IOBuffer> user_buffer_;
+ int user_buffer_len_;
+
+ scoped_refptr<HttpNetworkSession> session_;
+
+ const HttpRequestInfo* request_;
+ HttpResponseInfo response_;
+
+ // The time the Start method was called.
+ base::TimeTicks start_time_;
+
+ // The next state in the state machine.
+ State next_state_;
+
+ scoped_ptr<SpdyHttpStream> stream_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyNetworkTransaction);
+};
+
+} // namespace net
+
+#endif // NET_SPDY_NETWORK_TRANSACTION_H_
diff --git a/net/spdy/spdy_network_transaction_unittest.cc b/net/spdy/spdy_network_transaction_unittest.cc
new file mode 100644
index 0000000..7dbc12c
--- /dev/null
+++ b/net/spdy/spdy_network_transaction_unittest.cc
@@ -0,0 +1,2534 @@
+// 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 "net/base/net_log_unittest.h"
+#include "net/http/http_transaction_unittest.h"
+#include "net/spdy/spdy_http_stream.h"
+#include "net/spdy/spdy_network_transaction.h"
+#include "net/spdy/spdy_test_util.h"
+#include "testing/platform_test.h"
+
+//-----------------------------------------------------------------------------
+
+namespace net {
+
+class SpdyNetworkTransactionTest : public PlatformTest {
+ protected:
+ virtual void SetUp() {
+ // By default, all tests turn off compression.
+ EnableCompression(false);
+ google_get_request_initialized_ = false;
+ HttpNetworkTransaction::SetUseAlternateProtocols(true);
+ HttpNetworkTransaction::SetNextProtos(
+ "\x08http/1.1\x07http1.1\x06spdy/1\x04spdy");
+ }
+
+ virtual void TearDown() {
+ // Empty the current queue.
+ MessageLoop::current()->RunAllPending();
+ PlatformTest::TearDown();
+ }
+
+ void KeepAliveConnectionResendRequestTest(const MockRead& read_failure);
+
+ struct TransactionHelperResult {
+ int rv;
+ std::string status_line;
+ std::string response_data;
+ HttpResponseInfo response_info;
+ };
+
+ void EnableCompression(bool enabled) {
+ spdy::SpdyFramer::set_enable_compression_default(enabled);
+ }
+
+ class StartTransactionCallback;
+ class DeleteSessionCallback;
+
+ // A helper class that handles all the initial npn/ssl setup.
+ class NormalSpdyTransactionHelper {
+ public:
+ NormalSpdyTransactionHelper(const HttpRequestInfo& request,
+ const BoundNetLog& log)
+ : request_(request),
+ session_(SpdySessionDependencies::SpdyCreateSession(&session_deps_)),
+ log_(log), add_data_allowed_(true) {}
+
+ void RunPreTestSetup() {
+ // Disallow future calls to AddData
+ add_data_allowed_ = false;
+
+ // Set up http data.
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Alternate-Protocol: 443:npn-spdy/1\r\n\r\n"),
+ MockRead("hello world"),
+ MockRead(true, OK),
+ };
+ first_transaction_.reset(
+ new StaticSocketDataProvider(data_reads, arraysize(data_reads),
+ NULL, 0));
+ session_deps_.socket_factory.AddSocketDataProvider(
+ first_transaction_.get());
+
+ // Set up actual test data. Also add one SSLSocketDataProvider per
+ // DataProvider.
+ for(DataVector::iterator it = data_vector_.begin();
+ it != data_vector_.end(); ++it) {
+ linked_ptr<SSLSocketDataProvider> ssl_(
+ new SSLSocketDataProvider(true, OK));
+ ssl_->next_proto_status = SSLClientSocket::kNextProtoNegotiated;
+ ssl_->next_proto = "spdy/1";
+ ssl_->was_npn_negotiated = true;
+ ssl_vector_.push_back(ssl_);
+ session_deps_.socket_factory.AddSSLSocketDataProvider(ssl_.get());
+ session_deps_.socket_factory.AddSocketDataProvider(*it);
+ }
+
+ // We first send an http request. The Alternate-Protocol header switches
+ // the HttpNetworkTransaction into SSL/SPDY mode.
+ trans_http_.reset(new HttpNetworkTransaction(session_));
+ int rv = trans_http_->Start(&request_, &callback, log_);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+ const HttpResponseInfo* response = trans_http_->GetResponseInfo();
+ EXPECT_TRUE(response != NULL);
+ EXPECT_TRUE(response->headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ std::string response_data;
+ EXPECT_EQ(OK, ReadTransaction(trans_http_.get(), &response_data));
+ EXPECT_EQ("hello world", response_data);
+
+ // We're now ready to use SSL-npn SPDY.
+ trans_.reset(new HttpNetworkTransaction(session_));
+ }
+
+ // Start the transaction, read some data, finish.
+ void RunDefaultTest() {
+ output_.rv = trans_->Start(&request_, &callback, log_);
+
+ // We expect an IO Pending or some sort of error.
+ EXPECT_LT(output_.rv, 0);
+ if (output_.rv != ERR_IO_PENDING)
+ return;
+
+ output_.rv = callback.WaitForResult();
+ if (output_.rv != OK) {
+ session_->spdy_session_pool()->ClearSessions();
+ return;
+ }
+
+ // Verify responses.
+ const HttpResponseInfo* response = trans_->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ EXPECT_TRUE(response->was_npn_negotiated);
+ EXPECT_TRUE(response->was_alternate_protocol_available);
+ output_.status_line = response->headers->GetStatusLine();
+ output_.response_info = *response; // Make a copy so we can verify.
+ output_.rv = ReadTransaction(trans_.get(), &output_.response_data);
+ EXPECT_EQ(OK, output_.rv);
+ return;
+ }
+
+ // Most tests will want to call this function. In particular, the MockReads
+ // should end with an empty read, and that read needs to be processed to
+ // ensure proper deletion of the spdy_session_pool.
+ void VerifyDataConsumed() {
+ for(DataVector::iterator it = data_vector_.begin();
+ it != data_vector_.end(); ++it) {
+ EXPECT_TRUE((*it)->at_read_eof()) << "Read count: "
+ << (*it)->read_count()
+ << " Read index: "
+ << (*it)->read_index();
+ EXPECT_TRUE((*it)->at_write_eof()) << "Write count: "
+ << (*it)->write_count()
+ << " Write index: "
+ << (*it)->write_index();
+ }
+ }
+
+ // Occasionally a test will expect to error out before certain reads are
+ // processed. In that case we want to explicitly ensure that the reads were
+ // not processed.
+ void VerifyDataNotConsumed() {
+ for(DataVector::iterator it = data_vector_.begin();
+ it != data_vector_.end(); ++it) {
+ EXPECT_TRUE(!(*it)->at_read_eof()) << "Read count: "
+ << (*it)->read_count()
+ << " Read index: "
+ << (*it)->read_index();
+ EXPECT_TRUE(!(*it)->at_write_eof()) << "Write count: "
+ << (*it)->write_count()
+ << " Write index: "
+ << (*it)->write_index();
+
+ }
+ }
+
+ void RunToCompletion(StaticSocketDataProvider* data) {
+ AddData(data);
+ RunPreTestSetup();
+ RunDefaultTest();
+ VerifyDataConsumed();
+ }
+
+ // Only call AddData before calling RunPreTestSetup!! When RunPreTestSetup
+ // is run, it will add this Data Provider, and a corresponding SSL data
+ // provider.
+ void AddData(StaticSocketDataProvider* data) {
+ EXPECT_TRUE(add_data_allowed_);
+ data_vector_.push_back(data);
+ }
+
+ // This can only be called after RunPreTestSetup. It adds a Data Provider,
+ // but not a corresponding SSL data provider
+ void AddDataNoSSL(StaticSocketDataProvider* data) {
+ session_deps_.socket_factory.AddSocketDataProvider(data);
+ }
+
+ void SetSession(scoped_refptr<HttpNetworkSession>& session) {
+ session_ = session;
+ }
+ HttpNetworkTransaction* trans() { return trans_.get(); }
+ void ResetTrans() { trans_.reset(); }
+ TransactionHelperResult& output() { return output_; }
+ HttpRequestInfo& request() { return request_; }
+ scoped_refptr<HttpNetworkSession>& session() { return session_; }
+
+ private:
+ typedef std::vector<StaticSocketDataProvider*> DataVector;
+ typedef std::vector<linked_ptr<SSLSocketDataProvider> > SSLVector;
+ HttpRequestInfo request_;
+ SpdySessionDependencies session_deps_;
+ scoped_refptr<HttpNetworkSession> session_;
+ TransactionHelperResult output_;
+ scoped_ptr<StaticSocketDataProvider> first_transaction_;
+ SSLVector ssl_vector_;
+ TestCompletionCallback callback;
+ scoped_ptr<HttpNetworkTransaction> trans_;
+ scoped_ptr<HttpNetworkTransaction> trans_http_;
+ DataVector data_vector_;
+ const BoundNetLog& log_;
+ bool add_data_allowed_;
+ };
+
+ void ConnectStatusHelperWithExpectedStatus(const MockRead& status,
+ int expected_status);
+
+ void ConnectStatusHelper(const MockRead& status);
+
+ const HttpRequestInfo& CreateGetRequest() {
+ if (!google_get_request_initialized_) {
+ google_get_request_.method = "GET";
+ google_get_request_.url = GURL("http://www.google.com/");
+ google_get_request_.load_flags = 0;
+ google_get_request_initialized_ = true;
+ }
+ return google_get_request_;
+ }
+
+ private:
+ bool google_get_request_initialized_;
+ HttpRequestInfo google_get_request_;
+};
+
+//-----------------------------------------------------------------------------
+
+// Verify HttpNetworkTransaction constructor.
+TEST_F(SpdyNetworkTransactionTest, Constructor) {
+ SpdySessionDependencies session_deps;
+ scoped_refptr<HttpNetworkSession> session =
+ SpdySessionDependencies::SpdyCreateSession(&session_deps);
+ scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
+}
+
+TEST_F(SpdyNetworkTransactionTest, Get) {
+ // Construct the request.
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(1, reads, arraysize(reads),
+ writes, arraysize(writes)));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog());
+ helper.RunToCompletion(data.get());
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!", out.response_data);
+}
+
+// Start three gets simultaniously; making sure that multiplexed
+// streams work properly.
+
+// This can't use the TransactionHelper method, since it only
+// handles a single transaction, and finishes them as soon
+// as it launches them.
+
+// TODO(gavinp): create a working generalized TransactionHelper that
+// can allow multiple streams in flight.
+
+TEST_F(SpdyNetworkTransactionTest, ThreeGets) {
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<spdy::SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+ scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<spdy::SpdyFrame> body2(ConstructSpdyBodyFrame(3, false));
+ scoped_ptr<spdy::SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true));
+
+ scoped_ptr<spdy::SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 5, LOWEST));
+ scoped_ptr<spdy::SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 5));
+ scoped_ptr<spdy::SpdyFrame> body3(ConstructSpdyBodyFrame(5, false));
+ scoped_ptr<spdy::SpdyFrame> fbody3(ConstructSpdyBodyFrame(5, true));
+
+ MockWrite writes[] = { CreateMockWrite(*req),
+ CreateMockWrite(*req2),
+ CreateMockWrite(*req3),
+ };
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1),
+ CreateMockRead(*body),
+ CreateMockRead(*resp2, 4),
+ CreateMockRead(*body2),
+ CreateMockRead(*resp3, 7),
+ CreateMockRead(*body3),
+
+ CreateMockRead(*fbody),
+ CreateMockRead(*fbody2),
+ CreateMockRead(*fbody3),
+
+ MockRead(true, 0, 0), // EOF
+ };
+
+ scoped_refptr<OrderedSocketData> data(
+ new OrderedSocketData(reads, arraysize(reads),
+ writes, arraysize(writes)));
+
+ BoundNetLog log;
+ TransactionHelperResult out;
+ {
+ SpdySessionDependencies session_deps;
+ HttpNetworkSession* session =
+ SpdySessionDependencies::SpdyCreateSession(&session_deps);
+ SpdySession::SetSSLMode(false);
+ scoped_ptr<SpdyNetworkTransaction> trans1(
+ new SpdyNetworkTransaction(session));
+ scoped_ptr<SpdyNetworkTransaction> trans2(
+ new SpdyNetworkTransaction(session));
+ scoped_ptr<SpdyNetworkTransaction> trans3(
+ new SpdyNetworkTransaction(session));
+
+ session_deps.socket_factory.AddSocketDataProvider(data);
+
+ TestCompletionCallback callback1;
+ TestCompletionCallback callback2;
+ TestCompletionCallback callback3;
+
+ HttpRequestInfo httpreq1 = CreateGetRequest();
+ HttpRequestInfo httpreq2 = CreateGetRequest();
+ HttpRequestInfo httpreq3 = CreateGetRequest();
+
+ out.rv = trans1->Start(&httpreq1, &callback1, log);
+ ASSERT_EQ(ERR_IO_PENDING, out.rv);
+ out.rv = trans2->Start(&httpreq2, &callback2, log);
+ ASSERT_EQ(ERR_IO_PENDING, out.rv);
+ out.rv = trans3->Start(&httpreq3, &callback3, log);
+ ASSERT_EQ(ERR_IO_PENDING, out.rv);
+
+ out.rv = callback1.WaitForResult();
+ ASSERT_EQ(OK, out.rv);
+ out.rv = callback3.WaitForResult();
+ ASSERT_EQ(OK, out.rv);
+
+ const HttpResponseInfo* response1 = trans1->GetResponseInfo();
+ EXPECT_TRUE(response1->headers != NULL);
+ EXPECT_TRUE(response1->was_fetched_via_spdy);
+ out.status_line = response1->headers->GetStatusLine();
+ out.response_info = *response1;
+
+ trans2->GetResponseInfo();
+
+ out.rv = ReadTransaction(trans1.get(), &out.response_data);
+ }
+ EXPECT_EQ(OK, out.rv);
+
+ EXPECT_TRUE(data->at_read_eof());
+ EXPECT_TRUE(data->at_write_eof());
+
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!hello!", out.response_data);
+}
+
+
+// Similar to ThreeGets above, however this test adds a SETTINGS
+// frame. The SETTINGS frame is read during the IO loop waiting on
+// the first transaction completion, and sets a maximum concurrent
+// stream limit of 1. This means that our IO loop exists after the
+// second transaction completes, so we can assert on read_index().
+TEST_F(SpdyNetworkTransactionTest, ThreeGetsWithMaxConcurrent) {
+ // Construct the request.
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<spdy::SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+ scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<spdy::SpdyFrame> body2(ConstructSpdyBodyFrame(3, false));
+ scoped_ptr<spdy::SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true));
+
+ scoped_ptr<spdy::SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 5, LOWEST));
+ scoped_ptr<spdy::SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 5));
+ scoped_ptr<spdy::SpdyFrame> body3(ConstructSpdyBodyFrame(5, false));
+ scoped_ptr<spdy::SpdyFrame> fbody3(ConstructSpdyBodyFrame(5, true));
+
+ spdy::SpdySettings settings;
+ spdy::SettingsFlagsAndId id(0);
+ id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS);
+ const size_t max_concurrent_streams = 1;
+
+ settings.push_back(spdy::SpdySetting(id, max_concurrent_streams));
+ scoped_ptr<spdy::SpdyFrame> settings_frame(ConstructSpdySettings(settings));
+
+ MockWrite writes[] = { CreateMockWrite(*req),
+ CreateMockWrite(*req2),
+ CreateMockWrite(*req3),
+ };
+ MockRead reads[] = {
+ CreateMockRead(*settings_frame, 0),
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ CreateMockRead(*fbody),
+ CreateMockRead(*resp2, 6),
+ CreateMockRead(*body2),
+ CreateMockRead(*fbody2),
+ CreateMockRead(*resp3, 11),
+ CreateMockRead(*body3),
+ CreateMockRead(*fbody3),
+
+ MockRead(true, 0, 0), // EOF
+ };
+
+ scoped_refptr<OrderedSocketData> data(
+ new OrderedSocketData(reads, arraysize(reads),
+ writes, arraysize(writes)));
+
+ BoundNetLog log;
+ TransactionHelperResult out;
+ {
+ SpdySessionDependencies session_deps;
+ HttpNetworkSession* session =
+ SpdySessionDependencies::SpdyCreateSession(&session_deps);
+ SpdySession::SetSSLMode(false);
+ scoped_ptr<SpdyNetworkTransaction> trans1(
+ new SpdyNetworkTransaction(session));
+ scoped_ptr<SpdyNetworkTransaction> trans2(
+ new SpdyNetworkTransaction(session));
+ scoped_ptr<SpdyNetworkTransaction> trans3(
+ new SpdyNetworkTransaction(session));
+
+ session_deps.socket_factory.AddSocketDataProvider(data);
+
+ TestCompletionCallback callback1;
+ TestCompletionCallback callback2;
+ TestCompletionCallback callback3;
+
+ HttpRequestInfo httpreq1 = CreateGetRequest();
+ HttpRequestInfo httpreq2 = CreateGetRequest();
+ HttpRequestInfo httpreq3 = CreateGetRequest();
+
+ out.rv = trans1->Start(&httpreq1, &callback1, log);
+ ASSERT_EQ(out.rv, ERR_IO_PENDING);
+ // run transaction 1 through quickly to force a read of our SETTINGS
+ // frame
+ out.rv = callback1.WaitForResult();
+
+ out.rv = trans2->Start(&httpreq2, &callback2, log);
+ ASSERT_EQ(out.rv, ERR_IO_PENDING);
+ out.rv = trans3->Start(&httpreq3, &callback3, log);
+ ASSERT_EQ(out.rv, ERR_IO_PENDING);
+ out.rv = callback2.WaitForResult();
+ ASSERT_EQ(OK, out.rv);
+ EXPECT_EQ(7U, data->read_index()); // i.e. the third trans was queued
+
+ out.rv = callback3.WaitForResult();
+ ASSERT_EQ(OK, out.rv);
+
+ const HttpResponseInfo* response1 = trans1->GetResponseInfo();
+ EXPECT_TRUE(response1->headers != NULL);
+ EXPECT_TRUE(response1->was_fetched_via_spdy);
+ out.status_line = response1->headers->GetStatusLine();
+ out.response_info = *response1;
+ out.rv = ReadTransaction(trans1.get(), &out.response_data);
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!hello!", out.response_data);
+
+ const HttpResponseInfo* response2 = trans2->GetResponseInfo();
+ out.status_line = response2->headers->GetStatusLine();
+ out.response_info = *response2;
+ out.rv = ReadTransaction(trans2.get(), &out.response_data);
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!hello!", out.response_data);
+
+ const HttpResponseInfo* response3 = trans3->GetResponseInfo();
+ out.status_line = response3->headers->GetStatusLine();
+ out.response_info = *response3;
+ out.rv = ReadTransaction(trans3.get(), &out.response_data);
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!hello!", out.response_data);
+ }
+ EXPECT_EQ(OK, out.rv);
+
+ EXPECT_TRUE(data->at_read_eof());
+ EXPECT_TRUE(data->at_write_eof());
+}
+
+// Similar to ThreeGetsWithMaxConcurrent above, however this test adds
+// a fourth transaction. The third and fourth transactions have
+// different data ("hello!" vs "hello!hello!") and because of the
+// user specified priority, we expect to see them inverted in
+// the response from the server.
+TEST_F(SpdyNetworkTransactionTest, FourGetsWithMaxConcurrentPriority) {
+ // Construct the request.
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<spdy::SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+ scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<spdy::SpdyFrame> body2(ConstructSpdyBodyFrame(3, false));
+ scoped_ptr<spdy::SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true));
+
+ scoped_ptr<spdy::SpdyFrame> req4(
+ ConstructSpdyGet(NULL, 0, false, 5, HIGHEST));
+ scoped_ptr<spdy::SpdyFrame> resp4(ConstructSpdyGetSynReply(NULL, 0, 5));
+ scoped_ptr<spdy::SpdyFrame> fbody4(ConstructSpdyBodyFrame(5, true));
+
+ scoped_ptr<spdy::SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 7, LOWEST));
+ scoped_ptr<spdy::SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 7));
+ scoped_ptr<spdy::SpdyFrame> body3(ConstructSpdyBodyFrame(7, false));
+ scoped_ptr<spdy::SpdyFrame> fbody3(ConstructSpdyBodyFrame(7, true));
+
+
+ spdy::SpdySettings settings;
+ spdy::SettingsFlagsAndId id(0);
+ id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS);
+ const size_t max_concurrent_streams = 1;
+
+ settings.push_back(spdy::SpdySetting(id, max_concurrent_streams));
+ scoped_ptr<spdy::SpdyFrame> settings_frame(ConstructSpdySettings(settings));
+
+ MockWrite writes[] = { CreateMockWrite(*req),
+ CreateMockWrite(*req2),
+ CreateMockWrite(*req4),
+ CreateMockWrite(*req3),
+ };
+ MockRead reads[] = {
+ CreateMockRead(*settings_frame, 0),
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ CreateMockRead(*fbody),
+ CreateMockRead(*resp2, 6),
+ CreateMockRead(*body2),
+ CreateMockRead(*fbody2),
+ CreateMockRead(*resp4, 12),
+ CreateMockRead(*fbody4),
+ CreateMockRead(*resp3, 15),
+ CreateMockRead(*body3),
+ CreateMockRead(*fbody3),
+
+ MockRead(true, 0, 0), // EOF
+ };
+
+ scoped_refptr<OrderedSocketData> data(
+ new OrderedSocketData(reads, arraysize(reads),
+ writes, arraysize(writes)));
+
+ BoundNetLog log;
+ TransactionHelperResult out;
+ {
+ SpdySessionDependencies session_deps;
+ HttpNetworkSession* session =
+ SpdySessionDependencies::SpdyCreateSession(&session_deps);
+ SpdySession::SetSSLMode(false);
+ scoped_ptr<SpdyNetworkTransaction> trans1(
+ new SpdyNetworkTransaction(session));
+ scoped_ptr<SpdyNetworkTransaction> trans2(
+ new SpdyNetworkTransaction(session));
+ scoped_ptr<SpdyNetworkTransaction> trans3(
+ new SpdyNetworkTransaction(session));
+ scoped_ptr<SpdyNetworkTransaction> trans4(
+ new SpdyNetworkTransaction(session));
+
+ session_deps.socket_factory.AddSocketDataProvider(data);
+
+ TestCompletionCallback callback1;
+ TestCompletionCallback callback2;
+ TestCompletionCallback callback3;
+ TestCompletionCallback callback4;
+
+ HttpRequestInfo httpreq1 = CreateGetRequest();
+ HttpRequestInfo httpreq2 = CreateGetRequest();
+ HttpRequestInfo httpreq3 = CreateGetRequest();
+ HttpRequestInfo httpreq4 = CreateGetRequest();
+ httpreq4.priority = HIGHEST;
+
+ out.rv = trans1->Start(&httpreq1, &callback1, log);
+ ASSERT_EQ(ERR_IO_PENDING, out.rv);
+ // run transaction 1 through quickly to force a read of our SETTINGS
+ // frame
+ out.rv = callback1.WaitForResult();
+ ASSERT_EQ(OK, out.rv);
+
+ out.rv = trans2->Start(&httpreq2, &callback2, log);
+ ASSERT_EQ(ERR_IO_PENDING, out.rv);
+ out.rv = trans3->Start(&httpreq3, &callback3, log);
+ ASSERT_EQ(ERR_IO_PENDING, out.rv);
+ out.rv = trans4->Start(&httpreq4, &callback4, log);
+ ASSERT_EQ(ERR_IO_PENDING, out.rv);
+
+ out.rv = callback2.WaitForResult();
+ ASSERT_EQ(OK, out.rv);
+ EXPECT_EQ(data->read_index(), 7U); // i.e. the third & fourth trans queued
+
+ out.rv = callback3.WaitForResult();
+ ASSERT_EQ(OK, out.rv);
+
+ const HttpResponseInfo* response1 = trans1->GetResponseInfo();
+ EXPECT_TRUE(response1->headers != NULL);
+ EXPECT_TRUE(response1->was_fetched_via_spdy);
+ out.status_line = response1->headers->GetStatusLine();
+ out.response_info = *response1;
+ out.rv = ReadTransaction(trans1.get(), &out.response_data);
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!hello!", out.response_data);
+
+ const HttpResponseInfo* response2 = trans2->GetResponseInfo();
+ out.status_line = response2->headers->GetStatusLine();
+ out.response_info = *response2;
+ out.rv = ReadTransaction(trans2.get(), &out.response_data);
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!hello!", out.response_data);
+
+ // notice: response3 gets two hellos, response4 gets one
+ // hello, so we know dequeuing priority was respected.
+ const HttpResponseInfo* response3 = trans3->GetResponseInfo();
+ out.status_line = response3->headers->GetStatusLine();
+ out.response_info = *response3;
+ out.rv = ReadTransaction(trans3.get(), &out.response_data);
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!hello!", out.response_data);
+
+ out.rv = callback4.WaitForResult();
+ EXPECT_EQ(OK, out.rv);
+ const HttpResponseInfo* response4 = trans4->GetResponseInfo();
+ out.status_line = response4->headers->GetStatusLine();
+ out.response_info = *response4;
+ out.rv = ReadTransaction(trans4.get(), &out.response_data);
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!", out.response_data);
+ }
+ EXPECT_EQ(OK, out.rv);
+
+ EXPECT_TRUE(data->at_read_eof());
+ EXPECT_TRUE(data->at_write_eof());
+}
+
+// Similar to ThreeGetsMaxConcurrrent above, however, this test
+// deletes a session in the middle of the transaction to insure
+// that we properly remove pendingcreatestream objects from
+// the spdy_session
+TEST_F(SpdyNetworkTransactionTest, ThreeGetsWithMaxConcurrentDelete) {
+ // Construct the request.
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<spdy::SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+ scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<spdy::SpdyFrame> body2(ConstructSpdyBodyFrame(3, false));
+ scoped_ptr<spdy::SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true));
+
+ scoped_ptr<spdy::SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 5, LOWEST));
+ scoped_ptr<spdy::SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 5));
+ scoped_ptr<spdy::SpdyFrame> body3(ConstructSpdyBodyFrame(5, false));
+ scoped_ptr<spdy::SpdyFrame> fbody3(ConstructSpdyBodyFrame(5, true));
+
+ spdy::SpdySettings settings;
+ spdy::SettingsFlagsAndId id(0);
+ id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS);
+ const size_t max_concurrent_streams = 1;
+
+ settings.push_back(spdy::SpdySetting(id, max_concurrent_streams));
+ scoped_ptr<spdy::SpdyFrame> settings_frame(ConstructSpdySettings(settings));
+
+ MockWrite writes[] = { CreateMockWrite(*req),
+ CreateMockWrite(*req2),
+ };
+ MockRead reads[] = {
+ CreateMockRead(*settings_frame, 0),
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ CreateMockRead(*fbody),
+ CreateMockRead(*resp2, 6),
+ CreateMockRead(*body2),
+ CreateMockRead(*fbody2),
+ MockRead(true, 0, 0), // EOF
+ };
+
+ scoped_refptr<OrderedSocketData> data(
+ new OrderedSocketData(reads, arraysize(reads),
+ writes, arraysize(writes)));
+
+ BoundNetLog log;
+ TransactionHelperResult out;
+ {
+ SpdySessionDependencies session_deps;
+ HttpNetworkSession* session =
+ SpdySessionDependencies::SpdyCreateSession(&session_deps);
+ SpdySession::SetSSLMode(false);
+ scoped_ptr<SpdyNetworkTransaction> trans1(
+ new SpdyNetworkTransaction(session));
+ scoped_ptr<SpdyNetworkTransaction> trans2(
+ new SpdyNetworkTransaction(session));
+ scoped_ptr<SpdyNetworkTransaction> trans3(
+ new SpdyNetworkTransaction(session));
+
+ session_deps.socket_factory.AddSocketDataProvider(data);
+
+ TestCompletionCallback callback1;
+ TestCompletionCallback callback2;
+ TestCompletionCallback callback3;
+
+ HttpRequestInfo httpreq1 = CreateGetRequest();
+ HttpRequestInfo httpreq2 = CreateGetRequest();
+ HttpRequestInfo httpreq3 = CreateGetRequest();
+
+ out.rv = trans1->Start(&httpreq1, &callback1, log);
+ ASSERT_EQ(out.rv, ERR_IO_PENDING);
+ // run transaction 1 through quickly to force a read of our SETTINGS
+ // frame
+ out.rv = callback1.WaitForResult();
+
+ out.rv = trans2->Start(&httpreq2, &callback2, log);
+ ASSERT_EQ(out.rv, ERR_IO_PENDING);
+ out.rv = trans3->Start(&httpreq3, &callback3, log);
+ delete trans3.release();
+ ASSERT_EQ(out.rv, ERR_IO_PENDING);
+ out.rv = callback2.WaitForResult();
+
+ EXPECT_EQ(8U, data->read_index());
+
+ const HttpResponseInfo* response1 = trans1->GetResponseInfo();
+ EXPECT_TRUE(response1->headers != NULL);
+ EXPECT_TRUE(response1->was_fetched_via_spdy);
+ out.status_line = response1->headers->GetStatusLine();
+ out.response_info = *response1;
+ out.rv = ReadTransaction(trans1.get(), &out.response_data);
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!hello!", out.response_data);
+
+ const HttpResponseInfo* response2 = trans2->GetResponseInfo();
+ out.status_line = response2->headers->GetStatusLine();
+ out.response_info = *response2;
+ out.rv = ReadTransaction(trans2.get(), &out.response_data);
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!hello!", out.response_data);
+ }
+ EXPECT_EQ(OK, out.rv);
+
+ EXPECT_TRUE(data->at_read_eof());
+ EXPECT_TRUE(data->at_write_eof());
+}
+
+// Test that a simple POST works.
+TEST_F(SpdyNetworkTransactionTest, Post) {
+ static const char upload[] = { "hello!" };
+
+ // Setup the request
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.google.com/");
+ request.upload_data = new UploadData();
+ request.upload_data->AppendBytes(upload, strlen(upload));
+
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPost(NULL, 0));
+ scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*body), // POST upload frame
+ };
+
+ scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(2, reads, arraysize(reads),
+ writes, arraysize(writes)));
+ NormalSpdyTransactionHelper helper(request,
+ BoundNetLog());
+ helper.RunToCompletion(data.get());
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!", out.response_data);
+}
+
+// Test that a simple POST works.
+TEST_F(SpdyNetworkTransactionTest, EmptyPost) {
+ // Setup the request
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.google.com/");
+ // Create an empty UploadData.
+ request.upload_data = new UploadData();
+
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPost(NULL, 0));
+ // Set the FIN bit since there will be no body.
+ req->set_flags(spdy::CONTROL_FLAG_FIN);
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ };
+
+ scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+ scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(1, reads, arraysize(reads),
+ writes, arraysize(writes)));
+
+ NormalSpdyTransactionHelper helper(request,
+ BoundNetLog());
+ helper.RunToCompletion(data.get());
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!", out.response_data);
+}
+
+// While we're doing a post, the server sends back a SYN_REPLY.
+TEST_F(SpdyNetworkTransactionTest, PostWithEarlySynReply) {
+ static const char upload[] = { "hello!" };
+
+ // Setup the request
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.google.com/");
+ request.upload_data = new UploadData();
+ request.upload_data->AppendBytes(upload, sizeof(upload));
+
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPost(NULL, 0));
+ scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req.get(), 2),
+ CreateMockWrite(*body.get(), 3), // POST upload frame
+ };
+
+ scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+ MockRead reads[] = {
+ CreateMockRead(*resp.get(), 2),
+ CreateMockRead(*body.get(), 3),
+ MockRead(false, 0, 0) // EOF
+ };
+
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(0, reads, arraysize(reads),
+ writes, arraysize(writes)));
+ NormalSpdyTransactionHelper helper(request,
+ BoundNetLog());
+ helper.AddData(data.get());
+ helper.RunPreTestSetup();
+ helper.RunDefaultTest();
+ helper.VerifyDataNotConsumed();
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
+}
+
+// Test that the transaction doesn't crash when we don't have a reply.
+TEST_F(SpdyNetworkTransactionTest, ResponseWithoutSynReply) {
+ scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*body),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(1, reads, arraysize(reads), NULL, 0));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog());
+ helper.RunToCompletion(data.get());
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(ERR_SYN_REPLY_NOT_RECEIVED, out.rv);
+}
+
+// Test that the transaction doesn't crash when we get two replies on the same
+// stream ID. See http://crbug.com/45639.
+TEST_F(SpdyNetworkTransactionTest, ResponseWithTwoSynReplies) {
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(1, reads, arraysize(reads),
+ writes, arraysize(writes)));
+
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog());
+ helper.AddData(data.get());
+ helper.RunPreTestSetup();
+
+ HttpNetworkTransaction* trans = helper.trans();
+
+ TestCompletionCallback callback;
+ int rv = trans->Start(&helper.request(), &callback, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_TRUE(response->headers != NULL);
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ std::string response_data;
+ rv = ReadTransaction(trans, &response_data);
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, rv);
+
+ helper.VerifyDataConsumed();
+}
+
+// Test that WINDOW_UPDATE frames change window_size correctly.
+TEST_F(SpdyNetworkTransactionTest, WindowUpdate) {
+ SpdySessionDependencies session_deps;
+ scoped_refptr<HttpNetworkSession> session =
+ SpdySessionDependencies::SpdyCreateSession(&session_deps);
+
+ // We disable SSL for this test.
+ SpdySession::SetSSLMode(false);
+
+ // Setup the request
+ static const char upload[] = { "hello!" };
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.google.com/");
+ request.upload_data = new UploadData();
+ request.upload_data->AppendBytes(upload, strlen(upload));
+
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPost(NULL, 0));
+ scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*body),
+ };
+
+ // Response frames, send WINDOW_UPDATE first
+ static const int kDeltaWindowSize = 0xff;
+ scoped_ptr<spdy::SpdyFrame> window_update(
+ ConstructSpdyWindowUpdate(1, kDeltaWindowSize));
+ scoped_ptr<spdy::SpdyFrame> reply(ConstructSpdyPostSynReply(NULL, 0));
+ MockRead reads[] = {
+ CreateMockRead(*window_update),
+ CreateMockRead(*reply),
+ CreateMockRead(*body),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(2, reads, arraysize(reads),
+ writes, arraysize(writes)));
+ session_deps.socket_factory.AddSocketDataProvider(data.get());
+
+ scoped_ptr<SpdyNetworkTransaction> trans(
+ new SpdyNetworkTransaction(session));
+
+ TestCompletionCallback callback;
+ int rv = trans->Start(&request, &callback, BoundNetLog());
+
+ ASSERT_TRUE(trans->stream_ != NULL);
+ ASSERT_TRUE(trans->stream_->stream() != NULL);
+ EXPECT_EQ(spdy::kInitialWindowSize, trans->stream_->stream()->window_size());
+
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ ASSERT_TRUE(trans->stream_ != NULL);
+ ASSERT_TRUE(trans->stream_->stream() != NULL);
+ EXPECT_EQ(spdy::kInitialWindowSize + kDeltaWindowSize,
+ trans->stream_->stream()->window_size());
+ EXPECT_TRUE(data->at_read_eof());
+ EXPECT_TRUE(data->at_write_eof());
+}
+
+// Test that WINDOW_UPDATE frame causing overflow is handled correctly.
+TEST_F(SpdyNetworkTransactionTest, WindowUpdateOverflow) {
+ SpdySessionDependencies session_deps;
+ scoped_refptr<HttpNetworkSession> session =
+ SpdySessionDependencies::SpdyCreateSession(&session_deps);
+
+ // We disable SSL for this test.
+ SpdySession::SetSSLMode(false);
+
+ // Setup the request
+ static const char upload[] = { "hello!" };
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.google.com/");
+ request.upload_data = new UploadData();
+ request.upload_data->AppendBytes(upload, strlen(upload));
+
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPost(NULL, 0));
+ scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<spdy::SpdyFrame> rst(
+ ConstructSpdyRstStream(1, spdy::FLOW_CONTROL_ERROR));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*body),
+ CreateMockWrite(*rst),
+ };
+
+ // Response frames, send WINDOW_UPDATE first
+ static const int kDeltaWindowSize = 0x7fffffff; // cause an overflow
+ scoped_ptr<spdy::SpdyFrame> window_update(
+ ConstructSpdyWindowUpdate(1, kDeltaWindowSize));
+ scoped_ptr<spdy::SpdyFrame> reply(ConstructSpdyPostSynReply(NULL, 0));
+ MockRead reads[] = {
+ CreateMockRead(*window_update),
+ CreateMockRead(*reply),
+ CreateMockRead(*body),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(2, reads, arraysize(reads),
+ writes, arraysize(writes)));
+ session_deps.socket_factory.AddSocketDataProvider(data.get());
+
+ scoped_ptr<SpdyNetworkTransaction> trans(
+ new SpdyNetworkTransaction(session));
+
+ TestCompletionCallback callback;
+ int rv = trans->Start(&request, &callback, BoundNetLog());
+
+ ASSERT_TRUE(trans->stream_ != NULL);
+ ASSERT_TRUE(trans->stream_->stream() != NULL);
+ EXPECT_EQ(spdy::kInitialWindowSize, trans->stream_->stream()->window_size());
+
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, rv);
+
+ ASSERT_TRUE(session != NULL);
+ ASSERT_TRUE(session->spdy_session_pool() != NULL);
+ session->spdy_session_pool()->ClearSessions();
+
+ EXPECT_FALSE(data->at_read_eof());
+ EXPECT_TRUE(data->at_write_eof());
+}
+
+TEST_F(SpdyNetworkTransactionTest, CancelledTransaction) {
+ // Construct the request.
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ };
+
+ scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ // This following read isn't used by the test, except during the
+ // RunAllPending() call at the end since the SpdySession survives the
+ // HttpNetworkTransaction and still tries to continue Read()'ing. Any
+ // MockRead will do here.
+ MockRead(true, 0, 0) // EOF
+ };
+
+ StaticSocketDataProvider data(reads, arraysize(reads),
+ writes, arraysize(writes));
+
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog());
+ helper.AddData(&data);
+ helper.RunPreTestSetup();
+ HttpNetworkTransaction* trans = helper.trans();
+
+ TestCompletionCallback callback;
+ int rv = trans->Start(&CreateGetRequest(), &callback, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ helper.ResetTrans(); // Cancel the transaction.
+
+ // Flush the MessageLoop while the SpdySessionDependencies (in particular, the
+ // MockClientSocketFactory) are still alive.
+ MessageLoop::current()->RunAllPending();
+ helper.VerifyDataNotConsumed();
+}
+
+class SpdyNetworkTransactionTest::StartTransactionCallback
+ : public CallbackRunner< Tuple1<int> > {
+ public:
+ explicit StartTransactionCallback(
+ scoped_refptr<HttpNetworkSession>& session,
+ NormalSpdyTransactionHelper& helper)
+ : session_(session), helper_(helper) {}
+
+ // We try to start another transaction, which should succeed.
+ virtual void RunWithParams(const Tuple1<int>& params) {
+ scoped_ptr<HttpNetworkTransaction> trans(
+ new HttpNetworkTransaction(session_));
+ TestCompletionCallback callback;
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+ int rv = trans->Start(&request, &callback, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ }
+
+ private:
+ scoped_refptr<HttpNetworkSession>& session_;
+ NormalSpdyTransactionHelper& helper_;
+};
+
+// Verify that the client can correctly deal with the user callback attempting
+// to start another transaction on a session that is closing down. See
+// http://crbug.com/47455
+TEST_F(SpdyNetworkTransactionTest, StartTransactionOnReadCallback) {
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+ MockWrite writes2[] = { CreateMockWrite(*req) };
+
+ // The indicated length of this packet is longer than its actual length. When
+ // the session receives an empty packet after this one, it shuts down the
+ // session, and calls the read callback with the incomplete data.
+ const uint8 kGetBodyFrame2[] = {
+ 0x00, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x07,
+ 'h', 'e', 'l', 'l', 'o', '!',
+ };
+
+ scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 2),
+ MockRead(true, ERR_IO_PENDING, 3), // Force a pause
+ MockRead(true, reinterpret_cast<const char*>(kGetBodyFrame2),
+ arraysize(kGetBodyFrame2), 4),
+ MockRead(true, ERR_IO_PENDING, 5), // Force a pause
+ MockRead(true, 0, 0, 6), // EOF
+ };
+ MockRead reads2[] = {
+ CreateMockRead(*resp, 2),
+ MockRead(true, 0, 0, 3), // EOF
+ };
+
+ scoped_refptr<OrderedSocketData> data(
+ new OrderedSocketData(reads, arraysize(reads),
+ writes, arraysize(writes)));
+ scoped_refptr<DelayedSocketData> data2(
+ new DelayedSocketData(0, reads2, arraysize(reads2),
+ writes2, arraysize(writes2)));
+
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog());
+ helper.AddData(data.get());
+ helper.AddData(data2.get());
+ helper.RunPreTestSetup();
+ HttpNetworkTransaction* trans = helper.trans();
+
+ // Start the transaction with basic parameters.
+ TestCompletionCallback callback;
+ int rv = trans->Start(&helper.request(), &callback, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+
+ StartTransactionCallback callback2(helper.session(), helper);
+ const int kSize = 3000;
+ scoped_refptr<net::IOBuffer> buf = new net::IOBuffer(kSize);
+ rv = trans->Read(buf, kSize, &callback2);
+ // This forces an err_IO_pending, which sets the callback.
+ data->CompleteRead();
+ // This finishes the read.
+ data->CompleteRead();
+ helper.VerifyDataConsumed();
+}
+
+class SpdyNetworkTransactionTest::DeleteSessionCallback
+ : public CallbackRunner< Tuple1<int> > {
+ public:
+ explicit DeleteSessionCallback(NormalSpdyTransactionHelper& helper) :
+ helper_(helper) {}
+
+ // We kill the transaction, which deletes the session and stream.
+ virtual void RunWithParams(const Tuple1<int>& params) {
+ helper_.ResetTrans();
+ }
+
+ private:
+ NormalSpdyTransactionHelper& helper_;
+};
+
+// Verify that the client can correctly deal with the user callback deleting the
+// transaction. Failures will usually be valgrind errors. See
+// http://crbug.com/46925
+TEST_F(SpdyNetworkTransactionTest, DeleteSessionOnReadCallback) {
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp.get(), 2),
+ MockRead(true, ERR_IO_PENDING, 3), // Force a pause
+ CreateMockRead(*body.get(), 4),
+ MockRead(true, 0, 0, 5), // EOF
+ };
+
+ scoped_refptr<OrderedSocketData> data(
+ new OrderedSocketData(reads, arraysize(reads),
+ writes, arraysize(writes)));
+
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog());
+ helper.AddData(data.get());
+ helper.RunPreTestSetup();
+ HttpNetworkTransaction* trans = helper.trans();
+
+ // Start the transaction with basic parameters.
+ TestCompletionCallback callback;
+ int rv = trans->Start(&helper.request(), &callback, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+
+ // Setup a user callback which will delete the session, and clear out the
+ // memory holding the stream object. Note that the callback deletes trans.
+ DeleteSessionCallback callback2(helper);
+ const int kSize = 3000;
+ scoped_refptr<net::IOBuffer> buf = new net::IOBuffer(kSize);
+ rv = trans->Read(buf, kSize, &callback2);
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+ data->CompleteRead();
+
+ // Finish running rest of tasks.
+ MessageLoop::current()->RunAllPending();
+ helper.VerifyDataConsumed();
+}
+
+// Verify that various SynReply headers parse correctly through the
+// HTTP layer.
+TEST_F(SpdyNetworkTransactionTest, SynReplyHeaders) {
+ struct SynReplyHeadersTests {
+ int num_headers;
+ const char* extra_headers[5];
+ const char* expected_headers;
+ } test_cases[] = {
+ // This uses a multi-valued cookie header.
+ { 2,
+ { "cookie", "val1",
+ "cookie", "val2", // will get appended separated by NULL
+ NULL
+ },
+ "cookie: val1\n"
+ "cookie: val2\n"
+ "hello: bye\n"
+ "status: 200\n"
+ "url: /index.php\n"
+ "version: HTTP/1.1\n"
+ },
+ // This is the minimalist set of headers.
+ { 0,
+ { NULL },
+ "hello: bye\n"
+ "status: 200\n"
+ "url: /index.php\n"
+ "version: HTTP/1.1\n"
+ },
+ // Headers with a comma separated list.
+ { 1,
+ { "cookie", "val1,val2",
+ NULL
+ },
+ "cookie: val1,val2\n"
+ "hello: bye\n"
+ "status: 200\n"
+ "url: /index.php\n"
+ "version: HTTP/1.1\n"
+ }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ scoped_ptr<spdy::SpdyFrame> req(
+ ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<spdy::SpdyFrame> resp(
+ ConstructSpdyGetSynReply(test_cases[i].extra_headers,
+ test_cases[i].num_headers,
+ 1));
+ scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(1, reads, arraysize(reads),
+ writes, arraysize(writes)));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog());
+ helper.RunToCompletion(data.get());
+ TransactionHelperResult out = helper.output();
+
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!", out.response_data);
+
+ scoped_refptr<HttpResponseHeaders> headers = out.response_info.headers;
+ EXPECT_TRUE(headers.get() != NULL);
+ void* iter = NULL;
+ std::string name, value, lines;
+ while (headers->EnumerateHeaderLines(&iter, &name, &value)) {
+ lines.append(name);
+ lines.append(": ");
+ lines.append(value);
+ lines.append("\n");
+ }
+ EXPECT_EQ(std::string(test_cases[i].expected_headers), lines);
+ }
+}
+
+// Verify that various SynReply headers parse vary fields correctly
+// through the HTTP layer, and the response matches the request.
+TEST_F(SpdyNetworkTransactionTest, SynReplyHeadersVary) {
+ static const SpdyHeaderInfo syn_reply_info = {
+ spdy::SYN_REPLY, // Syn Reply
+ 1, // Stream ID
+ 0, // Associated Stream ID
+ SPDY_PRIORITY_LOWEST, // Priority
+ spdy::CONTROL_FLAG_NONE, // Control Flags
+ false, // Compressed
+ spdy::INVALID, // Status
+ NULL, // Data
+ 0, // Data Length
+ spdy::DATA_FLAG_NONE // Data Flags
+ };
+ // Modify the following data to change/add test cases:
+ struct SynReplyTests {
+ const SpdyHeaderInfo* syn_reply;
+ bool vary_matches;
+ int num_headers[2];
+ const char* extra_headers[2][16];
+ } test_cases[] = {
+ // Test the case of a multi-valued cookie. When the value is delimited
+ // with NUL characters, it needs to be unfolded into multiple headers.
+ {
+ &syn_reply_info,
+ true,
+ { 1, 4 },
+ { { "cookie", "val1,val2",
+ NULL
+ },
+ { "vary", "cookie",
+ "status", "200",
+ "url", "/index.php",
+ "version", "HTTP/1.1",
+ NULL
+ }
+ }
+ }, { // Multiple vary fields.
+ &syn_reply_info,
+ true,
+ { 2, 5 },
+ { { "friend", "barney",
+ "enemy", "snaggletooth",
+ NULL
+ },
+ { "vary", "friend",
+ "vary", "enemy",
+ "status", "200",
+ "url", "/index.php",
+ "version", "HTTP/1.1",
+ NULL
+ }
+ }
+ }, { // Test a '*' vary field.
+ &syn_reply_info,
+ false,
+ { 1, 4 },
+ { { "cookie", "val1,val2",
+ NULL
+ },
+ { "vary", "*",
+ "status", "200",
+ "url", "/index.php",
+ "version", "HTTP/1.1",
+ NULL
+ }
+ }
+ }, { // Multiple comma-separated vary fields.
+ &syn_reply_info,
+ true,
+ { 2, 4 },
+ { { "friend", "barney",
+ "enemy", "snaggletooth",
+ NULL
+ },
+ { "vary", "friend,enemy",
+ "status", "200",
+ "url", "/index.php",
+ "version", "HTTP/1.1",
+ NULL
+ }
+ }
+ }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ // Construct the request.
+ scoped_ptr<spdy::SpdyFrame> frame_req(
+ ConstructSpdyGet(test_cases[i].extra_headers[0],
+ test_cases[i].num_headers[0],
+ false, 1, LOWEST));
+
+ MockWrite writes[] = {
+ CreateMockWrite(*frame_req),
+ };
+
+ // Construct the reply.
+ scoped_ptr<spdy::SpdyFrame> frame_reply(
+ ConstructSpdyPacket(*test_cases[i].syn_reply,
+ test_cases[i].extra_headers[1],
+ test_cases[i].num_headers[1],
+ NULL,
+ 0));
+
+ scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*frame_reply),
+ CreateMockRead(*body),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ // Attach the headers to the request.
+ int header_count = test_cases[i].num_headers[0];
+
+ HttpRequestInfo request = CreateGetRequest();
+ for (int ct = 0; ct < header_count; ct++) {
+ const char* header_key = test_cases[i].extra_headers[0][ct * 2];
+ const char* header_value = test_cases[i].extra_headers[0][ct * 2 + 1];
+ request.extra_headers.SetHeader(header_key, header_value);
+ }
+
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(1, reads, arraysize(reads),
+ writes, arraysize(writes)));
+ NormalSpdyTransactionHelper helper(request,
+ BoundNetLog());
+ helper.RunToCompletion(data.get());
+ TransactionHelperResult out = helper.output();
+
+ EXPECT_EQ(OK, out.rv) << i;
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line) << i;
+ EXPECT_EQ("hello!", out.response_data) << i;
+
+ // Test the response information.
+ EXPECT_TRUE(out.response_info.response_time >
+ out.response_info.request_time) << i;
+ base::TimeDelta test_delay = out.response_info.response_time -
+ out.response_info.request_time;
+ base::TimeDelta min_expected_delay;
+ min_expected_delay.FromMilliseconds(10);
+ EXPECT_GT(test_delay.InMillisecondsF(),
+ min_expected_delay.InMillisecondsF()) << i;
+ EXPECT_EQ(out.response_info.vary_data.is_valid(),
+ test_cases[i].vary_matches) << i;
+
+ // Check the headers.
+ scoped_refptr<HttpResponseHeaders> headers = out.response_info.headers;
+ ASSERT_TRUE(headers.get() != NULL) << i;
+ void* iter = NULL;
+ std::string name, value, lines;
+ while (headers->EnumerateHeaderLines(&iter, &name, &value)) {
+ lines.append(name);
+ lines.append(": ");
+ lines.append(value);
+ lines.append("\n");
+ }
+
+ // Construct the expected header reply string.
+ char reply_buffer[256] = "";
+ ConstructSpdyReplyString(test_cases[i].extra_headers[1],
+ test_cases[i].num_headers[1],
+ reply_buffer,
+ 256);
+
+ EXPECT_EQ(std::string(reply_buffer), lines) << i;
+ }
+}
+
+// Verify that we don't crash on invalid SynReply responses.
+TEST_F(SpdyNetworkTransactionTest, InvalidSynReply) {
+ const SpdyHeaderInfo kSynStartHeader = {
+ spdy::SYN_REPLY, // Kind = SynReply
+ 1, // Stream ID
+ 0, // Associated stream ID
+ SPDY_PRIORITY_LOWEST, // Priority
+ spdy::CONTROL_FLAG_NONE, // Control Flags
+ false, // Compressed
+ spdy::INVALID, // Status
+ NULL, // Data
+ 0, // Length
+ spdy::DATA_FLAG_NONE // Data Flags
+ };
+
+ struct InvalidSynReplyTests {
+ int num_headers;
+ const char* headers[10];
+ } test_cases[] = {
+ // SYN_REPLY missing status header
+ { 4,
+ { "cookie", "val1",
+ "cookie", "val2",
+ "url", "/index.php",
+ "version", "HTTP/1.1",
+ NULL
+ },
+ },
+ // SYN_REPLY missing version header
+ { 2,
+ { "status", "200",
+ "url", "/index.php",
+ NULL
+ },
+ },
+ // SYN_REPLY with no headers
+ { 0, { NULL }, },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ scoped_ptr<spdy::SpdyFrame> req(
+ ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ };
+
+ scoped_ptr<spdy::SpdyFrame> resp(
+ ConstructSpdyPacket(kSynStartHeader,
+ NULL, 0,
+ test_cases[i].headers,
+ test_cases[i].num_headers));
+ scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(1, reads, arraysize(reads),
+ writes, arraysize(writes)));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog());
+ helper.RunToCompletion(data.get());
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(ERR_INVALID_RESPONSE, out.rv);
+ }
+}
+
+// Verify that we don't crash on some corrupt frames.
+// TODO(eroman): Renable this test, see http://crbug.com/48588
+TEST_F(SpdyNetworkTransactionTest, DISABLED_CorruptFrameSessionError) {
+ // This is the length field with a big number
+ scoped_ptr<spdy::SpdyFrame> syn_reply_massive_length(
+ ConstructSpdyGetSynReply(NULL, 0, 1));
+ syn_reply_massive_length->set_length(0x111126);
+
+ struct SynReplyTests {
+ const spdy::SpdyFrame* syn_reply;
+ } test_cases[] = {
+ { syn_reply_massive_length.get(), },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ scoped_ptr<spdy::SpdyFrame> req(
+ ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ MockWrite(true, 0, 0) // EOF
+ };
+
+ scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*test_cases[i].syn_reply),
+ CreateMockRead(*body),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(1, reads, arraysize(reads),
+ writes, arraysize(writes)));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog());
+ helper.RunToCompletion(data.get());
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
+ }
+}
+
+// Test that we shutdown correctly on write errors.
+TEST_F(SpdyNetworkTransactionTest, WriteError) {
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = {
+ // We'll write 10 bytes successfully
+ MockWrite(true, req->data(), 10),
+ // Followed by ERROR!
+ MockWrite(true, ERR_FAILED),
+ };
+
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(2, NULL, 0,
+ writes, arraysize(writes)));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog());
+ helper.RunToCompletion(data.get());
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(ERR_FAILED, out.rv);
+ data->Reset();
+}
+
+// Test that partial writes work.
+TEST_F(SpdyNetworkTransactionTest, PartialWrite) {
+ // Chop the SYN_STREAM frame into 5 chunks.
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ const int kChunks = 5;
+ scoped_array<MockWrite> writes(ChopWriteFrame(*req.get(), kChunks));
+
+ scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(kChunks, reads, arraysize(reads),
+ writes.get(), kChunks));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog());
+ helper.RunToCompletion(data.get());
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!", out.response_data);
+}
+
+// In this test, we enable compression, but get a uncompressed SynReply from
+// the server. Verify that teardown is all clean.
+TEST_F(SpdyNetworkTransactionTest, DecompressFailureOnSynReply) {
+ // For this test, we turn on the normal compression.
+ EnableCompression(true);
+
+ scoped_ptr<spdy::SpdyFrame> compressed(
+ ConstructSpdyGet(NULL, 0, true, 1, LOWEST));
+ MockWrite writes[] = {
+ CreateMockWrite(*compressed),
+ };
+
+ scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(1, reads, arraysize(reads),
+ writes, arraysize(writes)));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog());
+ helper.RunToCompletion(data.get());
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(ERR_SYN_REPLY_NOT_RECEIVED, out.rv);
+ data->Reset();
+
+ EnableCompression(false);
+}
+
+// Test that the NetLog contains good data for a simple GET request.
+TEST_F(SpdyNetworkTransactionTest, NetLog) {
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ net::CapturingBoundNetLog log(net::CapturingNetLog::kUnbounded);
+
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(1, reads, arraysize(reads),
+ writes, arraysize(writes)));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ log.bound());
+ helper.RunToCompletion(data.get());
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!", out.response_data);
+
+ // Check that the NetLog was filled reasonably.
+ // This test is intentionally non-specific about the exact ordering of the
+ // log; instead we just check to make sure that certain events exist, and that
+ // they are in the right order.
+ EXPECT_LT(0u, log.entries().size());
+ int pos = 0;
+ pos = net::ExpectLogContainsSomewhere(log.entries(), 0,
+ net::NetLog::TYPE_SPDY_TRANSACTION_SEND_REQUEST,
+ net::NetLog::PHASE_BEGIN);
+ pos = net::ExpectLogContainsSomewhere(log.entries(), pos + 1,
+ net::NetLog::TYPE_SPDY_TRANSACTION_SEND_REQUEST,
+ net::NetLog::PHASE_END);
+ pos = net::ExpectLogContainsSomewhere(log.entries(), pos + 1,
+ net::NetLog::TYPE_SPDY_TRANSACTION_READ_HEADERS,
+ net::NetLog::PHASE_BEGIN);
+ pos = net::ExpectLogContainsSomewhere(log.entries(), pos + 1,
+ net::NetLog::TYPE_SPDY_TRANSACTION_READ_HEADERS,
+ net::NetLog::PHASE_END);
+ pos = net::ExpectLogContainsSomewhere(log.entries(), pos + 1,
+ net::NetLog::TYPE_SPDY_TRANSACTION_READ_BODY,
+ net::NetLog::PHASE_BEGIN);
+ pos = net::ExpectLogContainsSomewhere(log.entries(), pos + 1,
+ net::NetLog::TYPE_SPDY_TRANSACTION_READ_BODY,
+ net::NetLog::PHASE_END);
+}
+
+// Since we buffer the IO from the stream to the renderer, this test verifies
+// that when we read out the maximum amount of data (e.g. we received 50 bytes
+// on the network, but issued a Read for only 5 of those bytes) that the data
+// flow still works correctly.
+TEST_F(SpdyNetworkTransactionTest, BufferFull) {
+ spdy::SpdyFramer framer;
+
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ // 2 data frames in a single read.
+ scoped_ptr<spdy::SpdyFrame> data_frame_1(
+ framer.CreateDataFrame(1, "goodby", 6, spdy::DATA_FLAG_NONE));
+ scoped_ptr<spdy::SpdyFrame> data_frame_2(
+ framer.CreateDataFrame(1, "e worl", 6, spdy::DATA_FLAG_NONE));
+ const spdy::SpdyFrame* data_frames[2] = {
+ data_frame_1.get(),
+ data_frame_2.get(),
+ };
+ char combined_data_frames[100];
+ int combined_data_frames_len =
+ CombineFrames(data_frames, arraysize(data_frames),
+ combined_data_frames, arraysize(combined_data_frames));
+ scoped_ptr<spdy::SpdyFrame> last_frame(
+ framer.CreateDataFrame(1, "d", 1, spdy::DATA_FLAG_FIN));
+
+ scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ MockRead(true, ERR_IO_PENDING), // Force a pause
+ MockRead(true, combined_data_frames, combined_data_frames_len),
+ MockRead(true, ERR_IO_PENDING), // Force a pause
+ CreateMockRead(*last_frame),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(1, reads, arraysize(reads),
+ writes, arraysize(writes)));
+
+
+ TestCompletionCallback callback;
+
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog());
+ helper.AddData(data.get());
+ helper.RunPreTestSetup();
+ HttpNetworkTransaction* trans = helper.trans();
+ int rv = trans->Start(&CreateGetRequest(), &callback, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ TransactionHelperResult out = helper.output();
+ out.rv = callback.WaitForResult();
+ EXPECT_EQ(out.rv, OK);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_TRUE(response->headers != NULL);
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ out.status_line = response->headers->GetStatusLine();
+ out.response_info = *response; // Make a copy so we can verify.
+
+ // Read Data
+ TestCompletionCallback read_callback;
+
+ std::string content;
+ do {
+ // Read small chunks at a time.
+ const int kSmallReadSize = 3;
+ scoped_refptr<net::IOBuffer> buf = new net::IOBuffer(kSmallReadSize);
+ rv = trans->Read(buf, kSmallReadSize, &read_callback);
+ if (rv == net::ERR_IO_PENDING) {
+ data->CompleteRead();
+ rv = read_callback.WaitForResult();
+ }
+ if (rv > 0) {
+ content.append(buf->data(), rv);
+ } else if (rv < 0) {
+ NOTREACHED();
+ }
+ } while (rv > 0);
+
+ out.response_data.swap(content);
+
+ // Flush the MessageLoop while the SpdySessionDependencies (in particular, the
+ // MockClientSocketFactory) are still alive.
+ MessageLoop::current()->RunAllPending();
+
+ // Verify that we consumed all test data.
+ helper.VerifyDataConsumed();
+
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("goodbye world", out.response_data);
+}
+
+TEST_F(SpdyNetworkTransactionTest, ConnectFailureFallbackToHttp) {
+ MockConnect connects[] = {
+ MockConnect(true, ERR_NAME_NOT_RESOLVED),
+ MockConnect(false, ERR_NAME_NOT_RESOLVED),
+ MockConnect(true, ERR_INTERNET_DISCONNECTED),
+ MockConnect(false, ERR_INTERNET_DISCONNECTED)
+ };
+
+ for (size_t index = 0; index < arraysize(connects); ++index) {
+ scoped_ptr<spdy::SpdyFrame> req(
+ ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ MockWrite(true, 0, 0) // EOF
+ };
+
+ scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(connects[index], 1, reads, arraysize(reads),
+ writes, arraysize(writes)));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog());
+ helper.AddData(data.get());
+ helper.RunPreTestSetup();
+
+ // Set up http fallback data.
+ MockRead http_fallback_data[] = {
+ MockRead("HTTP/1.1 200 OK\r\n\r\n"),
+ MockRead("hello world!!!"),
+ MockRead(true, OK),
+ };
+
+ scoped_ptr<StaticSocketDataProvider> http_fallback(
+ new StaticSocketDataProvider(http_fallback_data,
+ arraysize(http_fallback_data),
+ NULL, 0));
+ helper.AddDataNoSSL(http_fallback.get());
+ HttpNetworkTransaction* trans = helper.trans();
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&helper.request(), &callback, BoundNetLog());
+ EXPECT_EQ(rv, ERR_IO_PENDING);
+ rv = callback.WaitForResult();
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ std::string response_data;
+ rv = ReadTransaction(trans, &response_data);
+ EXPECT_EQ(OK, rv);
+
+ EXPECT_TRUE(!response->was_fetched_via_spdy);
+ EXPECT_TRUE(!response->was_npn_negotiated);
+ EXPECT_TRUE(response->was_alternate_protocol_available);
+ EXPECT_TRUE(http_fallback->at_read_eof());
+ EXPECT_EQ(0u, data->read_index());
+ EXPECT_EQ(0u, data->write_index());
+ EXPECT_EQ("hello world!!!", response_data);
+ }
+}
+
+// Verify that basic buffering works; when multiple data frames arrive
+// at the same time, ensure that we don't notify a read completion for
+// each data frame individually.
+TEST_F(SpdyNetworkTransactionTest, Buffering) {
+ spdy::SpdyFramer framer;
+
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ // 4 data frames in a single read.
+ scoped_ptr<spdy::SpdyFrame> data_frame(
+ framer.CreateDataFrame(1, "message", 7, spdy::DATA_FLAG_NONE));
+ scoped_ptr<spdy::SpdyFrame> data_frame_fin(
+ framer.CreateDataFrame(1, "message", 7, spdy::DATA_FLAG_FIN));
+ const spdy::SpdyFrame* data_frames[4] = {
+ data_frame.get(),
+ data_frame.get(),
+ data_frame.get(),
+ data_frame_fin.get()
+ };
+ char combined_data_frames[100];
+ int combined_data_frames_len =
+ CombineFrames(data_frames, arraysize(data_frames),
+ combined_data_frames, arraysize(combined_data_frames));
+
+ scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ MockRead(true, ERR_IO_PENDING), // Force a pause
+ MockRead(true, combined_data_frames, combined_data_frames_len),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(1, reads, arraysize(reads),
+ writes, arraysize(writes)));
+
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog());
+ helper.AddData(data.get());
+ helper.RunPreTestSetup();
+ HttpNetworkTransaction* trans = helper.trans();
+
+ TestCompletionCallback callback;
+ int rv = trans->Start(&CreateGetRequest(), &callback, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ TransactionHelperResult out = helper.output();
+ out.rv = callback.WaitForResult();
+ EXPECT_EQ(out.rv, OK);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_TRUE(response->headers != NULL);
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ out.status_line = response->headers->GetStatusLine();
+ out.response_info = *response; // Make a copy so we can verify.
+
+ // Read Data
+ TestCompletionCallback read_callback;
+
+ std::string content;
+ int reads_completed = 0;
+ do {
+ // Read small chunks at a time.
+ const int kSmallReadSize = 14;
+ scoped_refptr<net::IOBuffer> buf = new net::IOBuffer(kSmallReadSize);
+ rv = trans->Read(buf, kSmallReadSize, &read_callback);
+ if (rv == net::ERR_IO_PENDING) {
+ data->CompleteRead();
+ rv = read_callback.WaitForResult();
+ }
+ if (rv > 0) {
+ EXPECT_EQ(kSmallReadSize, rv);
+ content.append(buf->data(), rv);
+ } else if (rv < 0) {
+ FAIL() << "Unexpected read error: " << rv;
+ }
+ reads_completed++;
+ } while (rv > 0);
+
+ EXPECT_EQ(3, reads_completed); // Reads are: 14 bytes, 14 bytes, 0 bytes.
+
+ out.response_data.swap(content);
+
+ // Flush the MessageLoop while the SpdySessionDependencies (in particular, the
+ // MockClientSocketFactory) are still alive.
+ MessageLoop::current()->RunAllPending();
+
+ // Verify that we consumed all test data.
+ helper.VerifyDataConsumed();
+
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("messagemessagemessagemessage", out.response_data);
+}
+
+// Verify the case where we buffer data but read it after it has been buffered.
+TEST_F(SpdyNetworkTransactionTest, BufferedAll) {
+ spdy::SpdyFramer framer;
+
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ // 5 data frames in a single read.
+ scoped_ptr<spdy::SpdyFrame> syn_reply(
+ ConstructSpdyGetSynReply(NULL, 0, 1));
+ syn_reply->set_flags(spdy::CONTROL_FLAG_NONE); // turn off FIN bit
+ scoped_ptr<spdy::SpdyFrame> data_frame(
+ framer.CreateDataFrame(1, "message", 7, spdy::DATA_FLAG_NONE));
+ scoped_ptr<spdy::SpdyFrame> data_frame_fin(
+ framer.CreateDataFrame(1, "message", 7, spdy::DATA_FLAG_FIN));
+ const spdy::SpdyFrame* frames[5] = {
+ syn_reply.get(),
+ data_frame.get(),
+ data_frame.get(),
+ data_frame.get(),
+ data_frame_fin.get()
+ };
+ char combined_frames[200];
+ int combined_frames_len =
+ CombineFrames(frames, arraysize(frames),
+ combined_frames, arraysize(combined_frames));
+
+ MockRead reads[] = {
+ MockRead(true, combined_frames, combined_frames_len),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(1, reads, arraysize(reads),
+ writes, arraysize(writes)));
+
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog());
+ helper.AddData(data.get());
+ helper.RunPreTestSetup();
+ HttpNetworkTransaction* trans = helper.trans();
+
+ TestCompletionCallback callback;
+ int rv = trans->Start(&CreateGetRequest(), &callback, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ TransactionHelperResult out = helper.output();
+ out.rv = callback.WaitForResult();
+ EXPECT_EQ(out.rv, OK);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_TRUE(response->headers != NULL);
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ out.status_line = response->headers->GetStatusLine();
+ out.response_info = *response; // Make a copy so we can verify.
+
+ // Read Data
+ TestCompletionCallback read_callback;
+
+ std::string content;
+ int reads_completed = 0;
+ do {
+ // Read small chunks at a time.
+ const int kSmallReadSize = 14;
+ scoped_refptr<net::IOBuffer> buf = new net::IOBuffer(kSmallReadSize);
+ rv = trans->Read(buf, kSmallReadSize, &read_callback);
+ if (rv > 0) {
+ EXPECT_EQ(kSmallReadSize, rv);
+ content.append(buf->data(), rv);
+ } else if (rv < 0) {
+ FAIL() << "Unexpected read error: " << rv;
+ }
+ reads_completed++;
+ } while (rv > 0);
+
+ EXPECT_EQ(3, reads_completed);
+
+ out.response_data.swap(content);
+
+ // Flush the MessageLoop while the SpdySessionDependencies (in particular, the
+ // MockClientSocketFactory) are still alive.
+ MessageLoop::current()->RunAllPending();
+
+ // Verify that we consumed all test data.
+ helper.VerifyDataConsumed();
+
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("messagemessagemessagemessage", out.response_data);
+}
+
+// Verify the case where we buffer data and close the connection.
+TEST_F(SpdyNetworkTransactionTest, BufferedClosed) {
+ spdy::SpdyFramer framer;
+
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ // All data frames in a single read.
+ // NOTE: We don't FIN the stream.
+ scoped_ptr<spdy::SpdyFrame> data_frame(
+ framer.CreateDataFrame(1, "message", 7, spdy::DATA_FLAG_NONE));
+ const spdy::SpdyFrame* data_frames[4] = {
+ data_frame.get(),
+ data_frame.get(),
+ data_frame.get(),
+ data_frame.get()
+ };
+ char combined_data_frames[100];
+ int combined_data_frames_len =
+ CombineFrames(data_frames, arraysize(data_frames),
+ combined_data_frames, arraysize(combined_data_frames));
+ scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ MockRead(true, ERR_IO_PENDING), // Force a wait
+ MockRead(true, combined_data_frames, combined_data_frames_len),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(1, reads, arraysize(reads),
+ writes, arraysize(writes)));
+
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog());
+ helper.AddData(data.get());
+ helper.RunPreTestSetup();
+ HttpNetworkTransaction* trans = helper.trans();
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&CreateGetRequest(), &callback, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ TransactionHelperResult out = helper.output();
+ out.rv = callback.WaitForResult();
+ EXPECT_EQ(out.rv, OK);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_TRUE(response->headers != NULL);
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ out.status_line = response->headers->GetStatusLine();
+ out.response_info = *response; // Make a copy so we can verify.
+
+ // Read Data
+ TestCompletionCallback read_callback;
+
+ std::string content;
+ int reads_completed = 0;
+ do {
+ // Read small chunks at a time.
+ const int kSmallReadSize = 14;
+ scoped_refptr<net::IOBuffer> buf = new net::IOBuffer(kSmallReadSize);
+ rv = trans->Read(buf, kSmallReadSize, &read_callback);
+ if (rv == net::ERR_IO_PENDING) {
+ data->CompleteRead();
+ rv = read_callback.WaitForResult();
+ }
+ if (rv > 0) {
+ content.append(buf->data(), rv);
+ } else if (rv < 0) {
+ // This test intentionally closes the connection, and will get an error.
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, rv);
+ break;
+ }
+ reads_completed++;
+ } while (rv > 0);
+
+ EXPECT_EQ(0, reads_completed);
+
+ out.response_data.swap(content);
+
+ // Flush the MessageLoop while the SpdySessionDependencies (in particular, the
+ // MockClientSocketFactory) are still alive.
+ MessageLoop::current()->RunAllPending();
+
+ // Verify that we consumed all test data.
+ helper.VerifyDataConsumed();
+}
+
+// Verify the case where we buffer data and cancel the transaction.
+TEST_F(SpdyNetworkTransactionTest, BufferedCancelled) {
+ spdy::SpdyFramer framer;
+
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ // NOTE: We don't FIN the stream.
+ scoped_ptr<spdy::SpdyFrame> data_frame(
+ framer.CreateDataFrame(1, "message", 7, spdy::DATA_FLAG_NONE));
+
+ scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ MockRead(true, ERR_IO_PENDING), // Force a wait
+ CreateMockRead(*data_frame),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(1, reads, arraysize(reads),
+ writes, arraysize(writes)));
+
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog());
+ helper.AddData(data.get());
+ helper.RunPreTestSetup();
+ HttpNetworkTransaction* trans = helper.trans();
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&CreateGetRequest(), &callback, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ TransactionHelperResult out = helper.output();
+ out.rv = callback.WaitForResult();
+ EXPECT_EQ(out.rv, OK);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_TRUE(response->headers != NULL);
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ out.status_line = response->headers->GetStatusLine();
+ out.response_info = *response; // Make a copy so we can verify.
+
+ // Read Data
+ TestCompletionCallback read_callback;
+
+ do {
+ const int kReadSize = 256;
+ scoped_refptr<net::IOBuffer> buf = new net::IOBuffer(kReadSize);
+ rv = trans->Read(buf, kReadSize, &read_callback);
+ if (rv == net::ERR_IO_PENDING) {
+ // Complete the read now, which causes buffering to start.
+ data->CompleteRead();
+ // Destroy the transaction, causing the stream to get cancelled
+ // and orphaning the buffered IO task.
+ helper.ResetTrans();
+ break;
+ }
+ // We shouldn't get here in this test.
+ FAIL() << "Unexpected read: " << rv;
+ } while (rv > 0);
+
+ // Flush the MessageLoop; this will cause the buffered IO task
+ // to run for the final time.
+ MessageLoop::current()->RunAllPending();
+
+ // Verify that we consumed all test data.
+ helper.VerifyDataConsumed();
+}
+
+// Test that if the server requests persistence of settings, that we save
+// the settings in the SpdySettingsStorage.
+TEST_F(SpdyNetworkTransactionTest, SettingsSaved) {
+ static const SpdyHeaderInfo kSynReplyInfo = {
+ spdy::SYN_REPLY, // Syn Reply
+ 1, // Stream ID
+ 0, // Associated Stream ID
+ SPDY_PRIORITY_LOWEST, // Priority
+ spdy::CONTROL_FLAG_NONE, // Control Flags
+ false, // Compressed
+ spdy::INVALID, // Status
+ NULL, // Data
+ 0, // Data Length
+ spdy::DATA_FLAG_NONE // Data Flags
+ };
+ static const char* const kExtraHeaders[] = {
+ "status", "200",
+ "version", "HTTP/1.1"
+ };
+
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog());
+
+ // Verify that no settings exist initially.
+ HostPortPair host_port_pair("www.google.com", 443);
+ EXPECT_TRUE(helper.session()->spdy_settings().Get(host_port_pair).empty());
+
+ // Construct the request.
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ // Construct the reply.
+ scoped_ptr<spdy::SpdyFrame> reply(
+ ConstructSpdyPacket(kSynReplyInfo,
+ kExtraHeaders,
+ arraysize(kExtraHeaders) / 2,
+ NULL,
+ 0));
+
+ unsigned int kSampleId1 = 0x1;
+ unsigned int kSampleValue1 = 0x0a0a0a0a;
+ unsigned int kSampleId2 = 0x2;
+ unsigned int kSampleValue2 = 0x0b0b0b0b;
+ unsigned int kSampleId3 = 0xababab;
+ unsigned int kSampleValue3 = 0x0c0c0c0c;
+ scoped_ptr<spdy::SpdyFrame> settings_frame;
+ {
+ // Construct the SETTINGS frame.
+ spdy::SpdySettings settings;
+ spdy::SettingsFlagsAndId setting(0);
+ // First add a persisted setting
+ setting.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST);
+ setting.set_id(kSampleId1);
+ settings.push_back(std::make_pair(setting, kSampleValue1));
+ // Next add a non-persisted setting
+ setting.set_flags(0);
+ setting.set_id(kSampleId2);
+ settings.push_back(std::make_pair(setting, kSampleValue2));
+ // Next add another persisted setting
+ setting.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST);
+ setting.set_id(kSampleId3);
+ settings.push_back(std::make_pair(setting, kSampleValue3));
+ settings_frame.reset(ConstructSpdySettings(settings));
+ }
+
+ scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*reply),
+ CreateMockRead(*body),
+ CreateMockRead(*settings_frame),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(1, reads, arraysize(reads),
+ writes, arraysize(writes)));
+ helper.RunToCompletion(data.get());
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!", out.response_data);
+
+ {
+ // Verify we had two persisted settings.
+ spdy::SpdySettings saved_settings =
+ helper.session()->spdy_settings().Get(host_port_pair);
+ ASSERT_EQ(2u, saved_settings.size());
+
+ // Verify the first persisted setting.
+ spdy::SpdySetting setting = saved_settings.front();
+ saved_settings.pop_front();
+ EXPECT_EQ(spdy::SETTINGS_FLAG_PERSISTED, setting.first.flags());
+ EXPECT_EQ(kSampleId1, setting.first.id());
+ EXPECT_EQ(kSampleValue1, setting.second);
+
+ // Verify the second persisted setting.
+ setting = saved_settings.front();
+ saved_settings.pop_front();
+ EXPECT_EQ(spdy::SETTINGS_FLAG_PERSISTED, setting.first.flags());
+ EXPECT_EQ(kSampleId3, setting.first.id());
+ EXPECT_EQ(kSampleValue3, setting.second);
+ }
+}
+
+// Test that when there are settings saved that they are sent back to the
+// server upon session establishment.
+TEST_F(SpdyNetworkTransactionTest, SettingsPlayback) {
+ static const SpdyHeaderInfo kSynReplyInfo = {
+ spdy::SYN_REPLY, // Syn Reply
+ 1, // Stream ID
+ 0, // Associated Stream ID
+ SPDY_PRIORITY_LOWEST, // Priority
+ spdy::CONTROL_FLAG_NONE, // Control Flags
+ false, // Compressed
+ spdy::INVALID, // Status
+ NULL, // Data
+ 0, // Data Length
+ spdy::DATA_FLAG_NONE // Data Flags
+ };
+ static const char* kExtraHeaders[] = {
+ "status", "200",
+ "version", "HTTP/1.1"
+ };
+
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog());
+
+ // Verify that no settings exist initially.
+ HostPortPair host_port_pair("www.google.com", 443);
+ EXPECT_TRUE(helper.session()->spdy_settings().Get(host_port_pair).empty());
+
+ unsigned int kSampleId1 = 0x1;
+ unsigned int kSampleValue1 = 0x0a0a0a0a;
+ unsigned int kSampleId2 = 0xababab;
+ unsigned int kSampleValue2 = 0x0c0c0c0c;
+ // Manually insert settings into the SpdySettingsStorage here.
+ {
+ spdy::SpdySettings settings;
+ spdy::SettingsFlagsAndId setting(0);
+ // First add a persisted setting
+ setting.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST);
+ setting.set_id(kSampleId1);
+ settings.push_back(std::make_pair(setting, kSampleValue1));
+ // Next add another persisted setting
+ setting.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST);
+ setting.set_id(kSampleId2);
+ settings.push_back(std::make_pair(setting, kSampleValue2));
+
+ helper.session()->mutable_spdy_settings()->Set(host_port_pair, settings);
+ }
+
+ EXPECT_EQ(2u, helper.session()->spdy_settings().Get(host_port_pair).size());
+
+ // Construct the SETTINGS frame.
+ const spdy::SpdySettings& settings =
+ helper.session()->spdy_settings().Get(host_port_pair);
+ scoped_ptr<spdy::SpdyFrame> settings_frame(ConstructSpdySettings(settings));
+
+ // Construct the request.
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+
+ MockWrite writes[] = {
+ CreateMockWrite(*settings_frame),
+ CreateMockWrite(*req),
+ };
+
+ // Construct the reply.
+ scoped_ptr<spdy::SpdyFrame> reply(
+ ConstructSpdyPacket(kSynReplyInfo,
+ kExtraHeaders,
+ arraysize(kExtraHeaders) / 2,
+ NULL,
+ 0));
+
+ scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*reply),
+ CreateMockRead(*body),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(2, reads, arraysize(reads),
+ writes, arraysize(writes)));
+ helper.RunToCompletion(data.get());
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!", out.response_data);
+
+ {
+ // Verify we had two persisted settings.
+ spdy::SpdySettings saved_settings =
+ helper.session()->spdy_settings().Get(host_port_pair);
+ ASSERT_EQ(2u, saved_settings.size());
+
+ // Verify the first persisted setting.
+ spdy::SpdySetting setting = saved_settings.front();
+ saved_settings.pop_front();
+ EXPECT_EQ(spdy::SETTINGS_FLAG_PERSISTED, setting.first.flags());
+ EXPECT_EQ(kSampleId1, setting.first.id());
+ EXPECT_EQ(kSampleValue1, setting.second);
+
+ // Verify the second persisted setting.
+ setting = saved_settings.front();
+ saved_settings.pop_front();
+ EXPECT_EQ(spdy::SETTINGS_FLAG_PERSISTED, setting.first.flags());
+ EXPECT_EQ(kSampleId2, setting.first.id());
+ EXPECT_EQ(kSampleValue2, setting.second);
+ }
+}
+
+TEST_F(SpdyNetworkTransactionTest, GoAwayWithActiveStream) {
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<spdy::SpdyFrame> go_away(ConstructSpdyGoAway());
+ MockRead reads[] = {
+ CreateMockRead(*go_away),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(1, reads, arraysize(reads),
+ writes, arraysize(writes)));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog());
+ helper.RunToCompletion(data.get());
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, out.rv);
+}
+
+TEST_F(SpdyNetworkTransactionTest, CloseWithActiveStream) {
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ MockRead(false, 0, 0) // EOF
+ };
+
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(1, reads, arraysize(reads),
+ writes, arraysize(writes)));
+ BoundNetLog log;
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ log);
+ helper.AddData(data.get());
+ helper.RunPreTestSetup();
+ HttpNetworkTransaction* trans = helper.trans();
+
+ TestCompletionCallback callback;
+ TransactionHelperResult out;
+ out.rv = trans->Start(&CreateGetRequest(), &callback, log);
+
+ EXPECT_EQ(out.rv, ERR_IO_PENDING);
+ out.rv = callback.WaitForResult();
+ EXPECT_EQ(out.rv, OK);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_TRUE(response->headers != NULL);
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ out.rv = ReadTransaction(trans, &out.response_data);
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, out.rv);
+
+ // Verify that we consumed all test data.
+ helper.VerifyDataConsumed();
+}
+
+} // namespace net
diff --git a/net/spdy/spdy_protocol.h b/net/spdy/spdy_protocol.h
new file mode 100644
index 0000000..810d5b3
--- /dev/null
+++ b/net/spdy/spdy_protocol.h
@@ -0,0 +1,664 @@
+// 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.
+
+// This file contains some protocol structures for use with Spdy.
+
+#ifndef NET_SPDY_SPDY_PROTOCOL_H_
+#define NET_SPDY_SPDY_PROTOCOL_H_
+
+#ifdef WIN32
+#include <winsock2.h>
+#else
+#include <arpa/inet.h>
+#endif
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "net/spdy/spdy_bitmasks.h"
+
+// Data Frame Format
+// +----------------------------------+
+// |0| Stream-ID (31bits) |
+// +----------------------------------+
+// | flags (8) | Length (24 bits) |
+// +----------------------------------+
+// | Data |
+// +----------------------------------+
+//
+// Control Frame Format
+// +----------------------------------+
+// |1| Version(15bits) | Type(16bits) |
+// +----------------------------------+
+// | flags (8) | Length (24 bits) |
+// +----------------------------------+
+// | Data |
+// +----------------------------------+
+//
+// Control Frame: SYN_STREAM
+// +----------------------------------+
+// |1|000000000000001|0000000000000001|
+// +----------------------------------+
+// | flags (8) | Length (24 bits) | >= 8
+// +----------------------------------+
+// |X| Stream-ID(31bits) |
+// +----------------------------------+
+// |X|Associated-To-Stream-ID (31bits)|
+// +----------------------------------+
+// |Pri| unused | Length (16bits)|
+// +----------------------------------+
+//
+// Control Frame: SYN_REPLY
+// +----------------------------------+
+// |1|000000000000001|0000000000000010|
+// +----------------------------------+
+// | flags (8) | Length (24 bits) | >= 8
+// +----------------------------------+
+// |X| Stream-ID(31bits) |
+// +----------------------------------+
+// | unused (16 bits)| Length (16bits)|
+// +----------------------------------+
+//
+// Control Frame: RST_STREAM
+// +----------------------------------+
+// |1|000000000000001|0000000000000011|
+// +----------------------------------+
+// | flags (8) | Length (24 bits) | >= 4
+// +----------------------------------+
+// |X| Stream-ID(31bits) |
+// +----------------------------------+
+// | Status code (32 bits) |
+// +----------------------------------+
+//
+// Control Frame: SETTINGS
+// +----------------------------------+
+// |1|000000000000001|0000000000000100|
+// +----------------------------------+
+// | flags (8) | Length (24 bits) |
+// +----------------------------------+
+// | # of entries (32) |
+// +----------------------------------+
+//
+// Control Frame: NOOP
+// +----------------------------------+
+// |1|000000000000001|0000000000000101|
+// +----------------------------------+
+// | flags (8) | Length (24 bits) | = 0
+// +----------------------------------+
+//
+// Control Frame: PING
+// +----------------------------------+
+// |1|000000000000001|0000000000000110|
+// +----------------------------------+
+// | flags (8) | Length (24 bits) | = 4
+// +----------------------------------+
+// | Unique id (32 bits) |
+// +----------------------------------+
+//
+// Control Frame: GOAWAY
+// +----------------------------------+
+// |1|000000000000001|0000000000000111|
+// +----------------------------------+
+// | flags (8) | Length (24 bits) | = 4
+// +----------------------------------+
+// |X| Last-accepted-stream-id |
+// +----------------------------------+
+//
+// Control Frame: WINDOW_UPDATE
+// +----------------------------------+
+// |1|000000000000001|0000000000001001|
+// +----------------------------------+
+// | flags (8) | Length (24 bits) | = 8
+// +----------------------------------+
+// |X| Stream-ID (31 bits) |
+// +----------------------------------+
+// | Delta-Window-Size (32 bits) |
+// +----------------------------------+
+
+
+namespace spdy {
+
+// This implementation of Spdy is version 1.
+const int kSpdyProtocolVersion = 1;
+
+// Default initial window size.
+const int kInitialWindowSize = 64 * 1024;
+
+// Note: all protocol data structures are on-the-wire format. That means that
+// data is stored in network-normalized order. Readers must use the
+// accessors provided or call ntohX() functions.
+
+// Types of Spdy Control Frames.
+enum SpdyControlType {
+ SYN_STREAM = 1,
+ SYN_REPLY,
+ RST_STREAM,
+ SETTINGS,
+ NOOP,
+ PING,
+ GOAWAY,
+ HEADERS,
+ WINDOW_UPDATE,
+ NUM_CONTROL_FRAME_TYPES
+};
+
+// Flags on data packets.
+enum SpdyDataFlags {
+ DATA_FLAG_NONE = 0,
+ DATA_FLAG_FIN = 1,
+ DATA_FLAG_COMPRESSED = 2
+};
+
+// Flags on control packets
+enum SpdyControlFlags {
+ CONTROL_FLAG_NONE = 0,
+ CONTROL_FLAG_FIN = 1,
+ CONTROL_FLAG_UNIDIRECTIONAL = 2
+};
+
+// Flags on the SETTINGS control frame.
+enum SpdySettingsControlFlags {
+ SETTINGS_FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS = 0x1
+};
+
+// Flags for settings within a SETTINGS frame.
+enum SpdySettingsFlags {
+ SETTINGS_FLAG_PLEASE_PERSIST = 0x1,
+ SETTINGS_FLAG_PERSISTED = 0x2
+};
+
+// List of known settings.
+enum SpdySettingsIds {
+ SETTINGS_UPLOAD_BANDWIDTH = 0x1,
+ SETTINGS_DOWNLOAD_BANDWIDTH = 0x2,
+ SETTINGS_ROUND_TRIP_TIME = 0x3,
+ SETTINGS_MAX_CONCURRENT_STREAMS = 0x4,
+ SETTINGS_CURRENT_CWND = 0x5,
+ // Downstream byte retransmission rate in percentage.
+ SETTINGS_DOWNLOAD_RETRANS_RATE = 0x6
+};
+
+// Status codes, as used in control frames (primarily RST_STREAM).
+enum SpdyStatusCodes {
+ INVALID = 0,
+ PROTOCOL_ERROR = 1,
+ INVALID_STREAM = 2,
+ REFUSED_STREAM = 3,
+ UNSUPPORTED_VERSION = 4,
+ CANCEL = 5,
+ INTERNAL_ERROR = 6,
+ FLOW_CONTROL_ERROR = 7,
+ NUM_STATUS_CODES = 8
+};
+
+// A SPDY stream id is a 31 bit entity.
+typedef uint32 SpdyStreamId;
+
+// A SPDY priority is a number between 0 and 4.
+typedef uint8 SpdyPriority;
+
+// SPDY Priorities. (there are only 2 bits)
+#define SPDY_PRIORITY_LOWEST 3
+#define SPDY_PRIORITY_HIGHEST 0
+
+// -------------------------------------------------------------------------
+// These structures mirror the protocol structure definitions.
+
+// For the control data structures, we pack so that sizes match the
+// protocol over-the-wire sizes.
+#pragma pack(push)
+#pragma pack(1)
+
+// A special structure for the 8 bit flags and 24 bit length fields.
+union FlagsAndLength {
+ uint8 flags_[4]; // 8 bits
+ uint32 length_; // 24 bits
+};
+
+// The basic SPDY Frame structure.
+struct SpdyFrameBlock {
+ union {
+ struct {
+ uint16 version_;
+ uint16 type_;
+ } control_;
+ struct {
+ SpdyStreamId stream_id_;
+ } data_;
+ };
+ FlagsAndLength flags_length_;
+};
+
+// A SYN_STREAM Control Frame structure.
+struct SpdySynStreamControlFrameBlock : SpdyFrameBlock {
+ SpdyStreamId stream_id_;
+ SpdyStreamId associated_stream_id_;
+ SpdyPriority priority_;
+ uint8 unused_;
+};
+
+// A SYN_REPLY Control Frame structure.
+struct SpdySynReplyControlFrameBlock : SpdyFrameBlock {
+ SpdyStreamId stream_id_;
+ uint16 unused_;
+};
+
+// A RST_STREAM Control Frame structure.
+struct SpdyRstStreamControlFrameBlock : SpdyFrameBlock {
+ SpdyStreamId stream_id_;
+ uint32 status_;
+};
+
+// A GOAWAY Control Frame structure.
+struct SpdyGoAwayControlFrameBlock : SpdyFrameBlock {
+ SpdyStreamId last_accepted_stream_id_;
+};
+
+// A structure for the 8 bit flags and 24 bit ID fields.
+union SettingsFlagsAndId {
+ uint8 flags_[4]; // 8 bits
+ uint32 id_; // 24 bits
+
+ SettingsFlagsAndId(uint32 val) : id_(val) {};
+ uint8 flags() const { return flags_[0]; }
+ void set_flags(uint8 flags) { flags_[0] = flags; }
+ uint32 id() const { return (ntohl(id_) & kSettingsIdMask); };
+ void set_id(uint32 id) {
+ DCHECK_EQ(0u, (id & ~kSettingsIdMask));
+ id = htonl(id & kSettingsIdMask);
+ id_ = flags() | id;
+ }
+};
+
+// A SETTINGS Control Frame structure.
+struct SpdySettingsControlFrameBlock : SpdyFrameBlock {
+ uint32 num_entries_;
+ // Variable data here.
+};
+
+// A WINDOW_UPDATE Control Frame structure
+struct SpdyWindowUpdateControlFrameBlock : SpdyFrameBlock {
+ SpdyStreamId stream_id_;
+ uint32 delta_window_size_;
+};
+
+#pragma pack(pop)
+
+// -------------------------------------------------------------------------
+// Wrapper classes for various SPDY frames.
+
+// All Spdy Frame types derive from this SpdyFrame class.
+class SpdyFrame {
+ public:
+ // Create a SpdyFrame for a given sized buffer.
+ explicit SpdyFrame(size_t size) : frame_(NULL), owns_buffer_(true) {
+ DCHECK_GE(size, sizeof(struct SpdyFrameBlock));
+ char* buffer = new char[size];
+ memset(buffer, 0, size);
+ frame_ = reinterpret_cast<struct SpdyFrameBlock*>(buffer);
+ }
+
+ // Create a SpdyFrame using a pre-created buffer.
+ // If |owns_buffer| is true, this class takes ownership of the buffer
+ // and will delete it on cleanup. The buffer must have been created using
+ // new char[].
+ // If |owns_buffer| is false, the caller retains ownership of the buffer and
+ // is responsible for making sure the buffer outlives this frame. In other
+ // words, this class does NOT create a copy of the buffer.
+ SpdyFrame(char* data, bool owns_buffer)
+ : frame_(reinterpret_cast<struct SpdyFrameBlock*>(data)),
+ owns_buffer_(owns_buffer) {
+ DCHECK(frame_);
+ }
+
+ ~SpdyFrame() {
+ if (owns_buffer_) {
+ char* buffer = reinterpret_cast<char*>(frame_);
+ delete [] buffer;
+ }
+ frame_ = NULL;
+ }
+
+ // Provides access to the frame bytes, which is a buffer containing
+ // the frame packed as expected for sending over the wire.
+ char* data() const { return reinterpret_cast<char*>(frame_); }
+
+ uint8 flags() const { return frame_->flags_length_.flags_[0]; }
+ void set_flags(uint8 flags) { frame_->flags_length_.flags_[0] = flags; }
+
+ uint32 length() const {
+ return ntohl(frame_->flags_length_.length_) & kLengthMask;
+ }
+
+ void set_length(uint32 length) {
+ DCHECK_EQ(0u, (length & ~kLengthMask));
+ length = htonl(length & kLengthMask);
+ frame_->flags_length_.length_ = flags() | length;
+ }
+
+ bool is_control_frame() const {
+ return (ntohs(frame_->control_.version_) & kControlFlagMask) ==
+ kControlFlagMask;
+ }
+
+ // Returns the size of the SpdyFrameBlock structure.
+ // Every SpdyFrame* class has a static size() method for accessing
+ // the size of the data structure which will be sent over the wire.
+ // Note: this is not the same as sizeof(SpdyFrame).
+ static size_t size() { return sizeof(struct SpdyFrameBlock); }
+
+ protected:
+ SpdyFrameBlock* frame_;
+
+ private:
+ bool owns_buffer_;
+ DISALLOW_COPY_AND_ASSIGN(SpdyFrame);
+};
+
+// A Data Frame.
+class SpdyDataFrame : public SpdyFrame {
+ public:
+ SpdyDataFrame() : SpdyFrame(size()) {}
+ SpdyDataFrame(char* data, bool owns_buffer)
+ : SpdyFrame(data, owns_buffer) {}
+
+ SpdyStreamId stream_id() const {
+ return ntohl(frame_->data_.stream_id_) & kStreamIdMask;
+ }
+
+ // Note that setting the stream id sets the control bit to false.
+ // As stream id should always be set, this means the control bit
+ // should always be set correctly.
+ void set_stream_id(SpdyStreamId id) {
+ DCHECK_EQ(0u, (id & ~kStreamIdMask));
+ frame_->data_.stream_id_ = htonl(id & kStreamIdMask);
+ }
+
+ // Returns the size of the SpdyFrameBlock structure.
+ // Note: this is not the size of the SpdyDataFrame class.
+ static size_t size() { return SpdyFrame::size(); }
+
+ const char* payload() const {
+ return reinterpret_cast<const char*>(frame_) + size();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SpdyDataFrame);
+};
+
+// A Control Frame.
+class SpdyControlFrame : public SpdyFrame {
+ public:
+ explicit SpdyControlFrame(size_t size) : SpdyFrame(size) {}
+ SpdyControlFrame(char* data, bool owns_buffer)
+ : SpdyFrame(data, owns_buffer) {}
+
+ // Callers can use this method to check if the frame appears to be a valid
+ // frame. Does not guarantee that there are no errors.
+ bool AppearsToBeAValidControlFrame() const {
+ // Right now we only check if the frame has an out-of-bounds type.
+ uint16 type = ntohs(block()->control_.type_);
+ return (type >= SYN_STREAM && type < NUM_CONTROL_FRAME_TYPES);
+ }
+
+ uint16 version() const {
+ const int kVersionMask = 0x7fff;
+ return ntohs(block()->control_.version_) & kVersionMask;
+ }
+
+ void set_version(uint16 version) {
+ DCHECK_EQ(0u, version & kControlFlagMask);
+ mutable_block()->control_.version_ = htons(kControlFlagMask | version);
+ }
+
+ SpdyControlType type() const {
+ uint16 type = ntohs(block()->control_.type_);
+ DCHECK(type >= SYN_STREAM && type < NUM_CONTROL_FRAME_TYPES);
+ return static_cast<SpdyControlType>(type);
+ }
+
+ void set_type(SpdyControlType type) {
+ DCHECK(type >= SYN_STREAM && type < NUM_CONTROL_FRAME_TYPES);
+ mutable_block()->control_.type_ = htons(type);
+ }
+
+ // Returns the size of the SpdyFrameBlock structure.
+ // Note: this is not the size of the SpdyControlFrame class.
+ static size_t size() { return sizeof(SpdyFrameBlock); }
+
+ private:
+ const struct SpdyFrameBlock* block() const {
+ return frame_;
+ }
+ struct SpdyFrameBlock* mutable_block() {
+ return frame_;
+ }
+ DISALLOW_COPY_AND_ASSIGN(SpdyControlFrame);
+};
+
+// A SYN_STREAM frame.
+class SpdySynStreamControlFrame : public SpdyControlFrame {
+ public:
+ SpdySynStreamControlFrame() : SpdyControlFrame(size()) {}
+ SpdySynStreamControlFrame(char* data, bool owns_buffer)
+ : SpdyControlFrame(data, owns_buffer) {}
+
+ SpdyStreamId stream_id() const {
+ return ntohl(block()->stream_id_) & kStreamIdMask;
+ }
+
+ void set_stream_id(SpdyStreamId id) {
+ mutable_block()->stream_id_ = htonl(id & kStreamIdMask);
+ }
+
+ SpdyStreamId associated_stream_id() const {
+ return ntohl(block()->associated_stream_id_) & kStreamIdMask;
+ }
+
+ void set_associated_stream_id(SpdyStreamId id) {
+ mutable_block()->associated_stream_id_ = htonl(id & kStreamIdMask);
+ }
+
+ SpdyPriority priority() const {
+ return (block()->priority_ & kPriorityMask) >> 6;
+ }
+
+ // The number of bytes in the header block beyond the frame header length.
+ int header_block_len() const {
+ return length() - (size() - SpdyFrame::size());
+ }
+
+ const char* header_block() const {
+ return reinterpret_cast<const char*>(block()) + size();
+ }
+
+ // Returns the size of the SpdySynStreamControlFrameBlock structure.
+ // Note: this is not the size of the SpdySynStreamControlFrame class.
+ static size_t size() { return sizeof(SpdySynStreamControlFrameBlock); }
+
+ private:
+ const struct SpdySynStreamControlFrameBlock* block() const {
+ return static_cast<SpdySynStreamControlFrameBlock*>(frame_);
+ }
+ struct SpdySynStreamControlFrameBlock* mutable_block() {
+ return static_cast<SpdySynStreamControlFrameBlock*>(frame_);
+ }
+ DISALLOW_COPY_AND_ASSIGN(SpdySynStreamControlFrame);
+};
+
+// A SYN_REPLY frame.
+class SpdySynReplyControlFrame : public SpdyControlFrame {
+ public:
+ SpdySynReplyControlFrame() : SpdyControlFrame(size()) {}
+ SpdySynReplyControlFrame(char* data, bool owns_buffer)
+ : SpdyControlFrame(data, owns_buffer) {}
+
+ SpdyStreamId stream_id() const {
+ return ntohl(block()->stream_id_) & kStreamIdMask;
+ }
+
+ void set_stream_id(SpdyStreamId id) {
+ mutable_block()->stream_id_ = htonl(id & kStreamIdMask);
+ }
+
+ int header_block_len() const {
+ return length() - (size() - SpdyFrame::size());
+ }
+
+ const char* header_block() const {
+ return reinterpret_cast<const char*>(block()) + size();
+ }
+
+ // Returns the size of the SpdySynReplyControlFrameBlock structure.
+ // Note: this is not the size of the SpdySynReplyControlFrame class.
+ static size_t size() { return sizeof(SpdySynReplyControlFrameBlock); }
+
+ private:
+ const struct SpdySynReplyControlFrameBlock* block() const {
+ return static_cast<SpdySynReplyControlFrameBlock*>(frame_);
+ }
+ struct SpdySynReplyControlFrameBlock* mutable_block() {
+ return static_cast<SpdySynReplyControlFrameBlock*>(frame_);
+ }
+ DISALLOW_COPY_AND_ASSIGN(SpdySynReplyControlFrame);
+};
+
+// A RST_STREAM frame.
+class SpdyRstStreamControlFrame : public SpdyControlFrame {
+ public:
+ SpdyRstStreamControlFrame() : SpdyControlFrame(size()) {}
+ SpdyRstStreamControlFrame(char* data, bool owns_buffer)
+ : SpdyControlFrame(data, owns_buffer) {}
+
+ SpdyStreamId stream_id() const {
+ return ntohl(block()->stream_id_) & kStreamIdMask;
+ }
+
+ void set_stream_id(SpdyStreamId id) {
+ mutable_block()->stream_id_ = htonl(id & kStreamIdMask);
+ }
+
+ SpdyStatusCodes status() const {
+ return static_cast<SpdyStatusCodes>(ntohl(block()->status_));
+ }
+ void set_status(SpdyStatusCodes status) {
+ mutable_block()->status_ = htonl(static_cast<uint32>(status));
+ }
+
+ // Returns the size of the SpdyRstStreamControlFrameBlock structure.
+ // Note: this is not the size of the SpdyRstStreamControlFrame class.
+ static size_t size() { return sizeof(SpdyRstStreamControlFrameBlock); }
+
+ private:
+ const struct SpdyRstStreamControlFrameBlock* block() const {
+ return static_cast<SpdyRstStreamControlFrameBlock*>(frame_);
+ }
+ struct SpdyRstStreamControlFrameBlock* mutable_block() {
+ return static_cast<SpdyRstStreamControlFrameBlock*>(frame_);
+ }
+ DISALLOW_COPY_AND_ASSIGN(SpdyRstStreamControlFrame);
+};
+
+class SpdyGoAwayControlFrame : public SpdyControlFrame {
+ public:
+ SpdyGoAwayControlFrame() : SpdyControlFrame(size()) {}
+ SpdyGoAwayControlFrame(char* data, bool owns_buffer)
+ : SpdyControlFrame(data, owns_buffer) {}
+
+ SpdyStreamId last_accepted_stream_id() const {
+ return ntohl(block()->last_accepted_stream_id_) & kStreamIdMask;
+ }
+
+ void set_last_accepted_stream_id(SpdyStreamId id) {
+ mutable_block()->last_accepted_stream_id_ = htonl(id & kStreamIdMask);
+ }
+
+ static size_t size() { return sizeof(SpdyGoAwayControlFrameBlock); }
+
+ private:
+ const struct SpdyGoAwayControlFrameBlock* block() const {
+ return static_cast<SpdyGoAwayControlFrameBlock*>(frame_);
+ }
+ struct SpdyGoAwayControlFrameBlock* mutable_block() {
+ return static_cast<SpdyGoAwayControlFrameBlock*>(frame_);
+ }
+ DISALLOW_COPY_AND_ASSIGN(SpdyGoAwayControlFrame);
+};
+
+class SpdySettingsControlFrame : public SpdyControlFrame {
+ public:
+ SpdySettingsControlFrame() : SpdyControlFrame(size()) {}
+ SpdySettingsControlFrame(char* data, bool owns_buffer)
+ : SpdyControlFrame(data, owns_buffer) {}
+
+ uint32 num_entries() const {
+ return ntohl(block()->num_entries_);
+ }
+
+ void set_num_entries(int val) {
+ mutable_block()->num_entries_ = htonl(val);
+ }
+
+ int header_block_len() const {
+ return length() - (size() - SpdyFrame::size());
+ }
+
+ const char* header_block() const {
+ return reinterpret_cast<const char*>(block()) + size();
+ }
+
+ // Returns the size of the SpdySettingsControlFrameBlock structure.
+ // Note: this is not the size of the SpdySettingsControlFrameBlock class.
+ static size_t size() { return sizeof(SpdySettingsControlFrameBlock); }
+
+ private:
+ const struct SpdySettingsControlFrameBlock* block() const {
+ return static_cast<SpdySettingsControlFrameBlock*>(frame_);
+ }
+ struct SpdySettingsControlFrameBlock* mutable_block() {
+ return static_cast<SpdySettingsControlFrameBlock*>(frame_);
+ }
+ DISALLOW_COPY_AND_ASSIGN(SpdySettingsControlFrame);
+};
+
+// A WINDOW_UPDATE frame.
+class SpdyWindowUpdateControlFrame : public SpdyControlFrame {
+ public:
+ SpdyWindowUpdateControlFrame() : SpdyControlFrame(size()) {}
+ SpdyWindowUpdateControlFrame(char* data, bool owns_buffer)
+ : SpdyControlFrame(data, owns_buffer) {}
+
+ SpdyStreamId stream_id() const {
+ return ntohl(block()->stream_id_) & kStreamIdMask;
+ }
+
+ void set_stream_id(SpdyStreamId id) {
+ mutable_block()->stream_id_ = htonl(id & kStreamIdMask);
+ }
+
+ uint32 delta_window_size() const {
+ return ntohl(block()->delta_window_size_);
+ }
+
+ void set_delta_window_size(uint32 delta_window_size) {
+ mutable_block()->delta_window_size_ = htonl(delta_window_size);
+ }
+
+ // Returns the size of the SpdyWindowUpdateControlFrameBlock structure.
+ // Note: this is not the size of the SpdyWindowUpdateControlFrame class.
+ static size_t size() { return sizeof(SpdyWindowUpdateControlFrameBlock); }
+
+ private:
+ const struct SpdyWindowUpdateControlFrameBlock* block() const {
+ return static_cast<SpdyWindowUpdateControlFrameBlock*>(frame_);
+ }
+ struct SpdyWindowUpdateControlFrameBlock* mutable_block() {
+ return static_cast<SpdyWindowUpdateControlFrameBlock*>(frame_);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyWindowUpdateControlFrame);
+};
+
+} // namespace spdy
+
+#endif // NET_SPDY_SPDY_PROTOCOL_H_
diff --git a/net/spdy/spdy_protocol_test.cc b/net/spdy/spdy_protocol_test.cc
new file mode 100644
index 0000000..e0b0591
--- /dev/null
+++ b/net/spdy/spdy_protocol_test.cc
@@ -0,0 +1,308 @@
+// 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 "net/spdy/spdy_protocol.h"
+
+#include "base/scoped_ptr.h"
+#include "net/spdy/spdy_bitmasks.h"
+#include "net/spdy/spdy_framer.h"
+#include "testing/platform_test.h"
+
+using spdy::CONTROL_FLAG_FIN;
+using spdy::CONTROL_FLAG_NONE;
+using spdy::GOAWAY;
+using spdy::HEADERS;
+using spdy::NOOP;
+using spdy::PING;
+using spdy::RST_STREAM;
+using spdy::SETTINGS;
+using spdy::SYN_REPLY;
+using spdy::SYN_STREAM;
+using spdy::WINDOW_UPDATE;
+using spdy::FlagsAndLength;
+using spdy::SpdyControlFrame;
+using spdy::SpdyControlType;
+using spdy::SpdyDataFrame;
+using spdy::SpdyFrame;
+using spdy::SpdyFramer;
+using spdy::SpdyHeaderBlock;
+using spdy::SpdyGoAwayControlFrame;
+using spdy::SpdyRstStreamControlFrame;
+using spdy::SpdySettings;
+using spdy::SpdySettingsControlFrame;
+using spdy::SpdyStatusCodes;
+using spdy::SpdySynReplyControlFrame;
+using spdy::SpdySynStreamControlFrame;
+using spdy::SpdyWindowUpdateControlFrame;
+using spdy::SettingsFlagsAndId;
+using spdy::kLengthMask;
+using spdy::kSpdyProtocolVersion;
+using spdy::kStreamIdMask;
+
+namespace {
+
+// Test our protocol constants
+TEST(SpdyProtocolTest, ProtocolConstants) {
+ EXPECT_EQ(8u, SpdyFrame::size());
+ EXPECT_EQ(8u, SpdyDataFrame::size());
+ EXPECT_EQ(8u, SpdyControlFrame::size());
+ EXPECT_EQ(18u, SpdySynStreamControlFrame::size());
+ EXPECT_EQ(14u, SpdySynReplyControlFrame::size());
+ EXPECT_EQ(16u, SpdyRstStreamControlFrame::size());
+ EXPECT_EQ(12u, SpdyGoAwayControlFrame::size());
+ EXPECT_EQ(12u, SpdySettingsControlFrame::size());
+ EXPECT_EQ(16u, SpdyWindowUpdateControlFrame::size());
+ EXPECT_EQ(4u, sizeof(FlagsAndLength));
+ EXPECT_EQ(1, SYN_STREAM);
+ EXPECT_EQ(2, SYN_REPLY);
+ EXPECT_EQ(3, RST_STREAM);
+ EXPECT_EQ(4, SETTINGS);
+ EXPECT_EQ(5, NOOP);
+ EXPECT_EQ(6, PING);
+ EXPECT_EQ(7, GOAWAY);
+ EXPECT_EQ(8, HEADERS);
+ EXPECT_EQ(9, WINDOW_UPDATE);
+}
+
+// Test some of the protocol helper functions
+TEST(SpdyProtocolTest, FrameStructs) {
+ SpdyFrame frame(SpdyFrame::size());
+ frame.set_length(12345);
+ frame.set_flags(10);
+ EXPECT_EQ(12345u, frame.length());
+ EXPECT_EQ(10u, frame.flags());
+ EXPECT_EQ(false, frame.is_control_frame());
+
+ frame.set_length(0);
+ frame.set_flags(10);
+ EXPECT_EQ(0u, frame.length());
+ EXPECT_EQ(10u, frame.flags());
+ EXPECT_EQ(false, frame.is_control_frame());
+}
+
+TEST(SpdyProtocolTest, DataFrameStructs) {
+ SpdyDataFrame data_frame;
+ data_frame.set_stream_id(12345);
+ EXPECT_EQ(12345u, data_frame.stream_id());
+}
+
+TEST(SpdyProtocolTest, ControlFrameStructs) {
+ SpdyFramer framer;
+ SpdyHeaderBlock headers;
+
+ scoped_ptr<SpdySynStreamControlFrame> syn_frame(
+ framer.CreateSynStream(123, 456, 2, CONTROL_FLAG_FIN, false, &headers));
+ EXPECT_EQ(kSpdyProtocolVersion, syn_frame->version());
+ EXPECT_TRUE(syn_frame->is_control_frame());
+ EXPECT_EQ(SYN_STREAM, syn_frame->type());
+ EXPECT_EQ(123u, syn_frame->stream_id());
+ EXPECT_EQ(456u, syn_frame->associated_stream_id());
+ EXPECT_EQ(2u, syn_frame->priority());
+ EXPECT_EQ(2, syn_frame->header_block_len());
+ EXPECT_EQ(1u, syn_frame->flags());
+ syn_frame->set_associated_stream_id(999u);
+ EXPECT_EQ(123u, syn_frame->stream_id());
+ EXPECT_EQ(999u, syn_frame->associated_stream_id());
+
+ scoped_ptr<SpdySynReplyControlFrame> syn_reply(
+ framer.CreateSynReply(123, CONTROL_FLAG_NONE, false, &headers));
+ EXPECT_EQ(kSpdyProtocolVersion, syn_reply->version());
+ EXPECT_TRUE(syn_reply->is_control_frame());
+ EXPECT_EQ(SYN_REPLY, syn_reply->type());
+ EXPECT_EQ(123u, syn_reply->stream_id());
+ EXPECT_EQ(2, syn_reply->header_block_len());
+ EXPECT_EQ(0, syn_reply->flags());
+
+ scoped_ptr<SpdyRstStreamControlFrame> rst_frame(
+ framer.CreateRstStream(123, spdy::PROTOCOL_ERROR));
+ EXPECT_EQ(kSpdyProtocolVersion, rst_frame->version());
+ EXPECT_TRUE(rst_frame->is_control_frame());
+ EXPECT_EQ(RST_STREAM, rst_frame->type());
+ EXPECT_EQ(123u, rst_frame->stream_id());
+ EXPECT_EQ(spdy::PROTOCOL_ERROR, rst_frame->status());
+ rst_frame->set_status(spdy::INVALID_STREAM);
+ EXPECT_EQ(spdy::INVALID_STREAM, rst_frame->status());
+ EXPECT_EQ(0, rst_frame->flags());
+
+ scoped_ptr<SpdyGoAwayControlFrame> goaway_frame(
+ framer.CreateGoAway(123));
+ EXPECT_EQ(kSpdyProtocolVersion, goaway_frame->version());
+ EXPECT_TRUE(goaway_frame->is_control_frame());
+ EXPECT_EQ(GOAWAY, goaway_frame->type());
+ EXPECT_EQ(123u, goaway_frame->last_accepted_stream_id());
+
+ scoped_ptr<SpdyWindowUpdateControlFrame> window_update_frame(
+ framer.CreateWindowUpdate(123, 456));
+ EXPECT_EQ(kSpdyProtocolVersion, window_update_frame->version());
+ EXPECT_TRUE(window_update_frame->is_control_frame());
+ EXPECT_EQ(WINDOW_UPDATE, window_update_frame->type());
+ EXPECT_EQ(123u, window_update_frame->stream_id());
+ EXPECT_EQ(456u, window_update_frame->delta_window_size());
+}
+
+TEST(SpdyProtocolTest, TestDataFrame) {
+ SpdyDataFrame frame;
+
+ // Set the stream ID to various values.
+ frame.set_stream_id(0);
+ EXPECT_EQ(0u, frame.stream_id());
+ EXPECT_FALSE(frame.is_control_frame());
+ frame.set_stream_id(~0 & kStreamIdMask);
+ EXPECT_EQ(~0 & kStreamIdMask, frame.stream_id());
+ EXPECT_FALSE(frame.is_control_frame());
+
+ // Set length to various values. Make sure that when you set_length(x),
+ // length() == x. Also make sure the flags are unaltered.
+ memset(frame.data(), '1', SpdyDataFrame::size());
+ int8 flags = frame.flags();
+ frame.set_length(0);
+ EXPECT_EQ(0u, frame.length());
+ EXPECT_EQ(flags, frame.flags());
+ frame.set_length(kLengthMask);
+ EXPECT_EQ(kLengthMask, frame.length());
+ EXPECT_EQ(flags, frame.flags());
+ frame.set_length(5u);
+ EXPECT_EQ(5u, frame.length());
+ EXPECT_EQ(flags, frame.flags());
+
+ // Set flags to various values. Make sure that when you set_flags(x),
+ // flags() == x. Also make sure the length is unaltered.
+ memset(frame.data(), '1', SpdyDataFrame::size());
+ uint32 length = frame.length();
+ frame.set_flags(0u);
+ EXPECT_EQ(0u, frame.flags());
+ EXPECT_EQ(length, frame.length());
+ int8 all_flags = ~0;
+ frame.set_flags(all_flags);
+ flags = frame.flags();
+ EXPECT_EQ(all_flags, flags);
+ EXPECT_EQ(length, frame.length());
+ frame.set_flags(5u);
+ EXPECT_EQ(5u, frame.flags());
+ EXPECT_EQ(length, frame.length());
+}
+
+// Test various types of SETTINGS frames.
+TEST(SpdyProtocolTest, TestSpdySettingsFrame) {
+ SpdyFramer framer;
+
+ // Create a settings frame with no settings.
+ SpdySettings settings;
+ scoped_ptr<SpdySettingsControlFrame> settings_frame(
+ framer.CreateSettings(settings));
+ EXPECT_EQ(kSpdyProtocolVersion, settings_frame->version());
+ EXPECT_TRUE(settings_frame->is_control_frame());
+ EXPECT_EQ(SETTINGS, settings_frame->type());
+ EXPECT_EQ(0u, settings_frame->num_entries());
+
+ // We'll add several different ID/Flag combinations and then verify
+ // that they encode and decode properly.
+ SettingsFlagsAndId ids[] = {
+ 0x00000000,
+ 0xffffffff,
+ 0xff000001,
+ 0x01000002,
+ };
+
+ for (size_t index = 0; index < arraysize(ids); ++index) {
+ settings.insert(settings.end(), std::make_pair(ids[index], index));
+ settings_frame.reset(framer.CreateSettings(settings));
+ EXPECT_EQ(kSpdyProtocolVersion, settings_frame->version());
+ EXPECT_TRUE(settings_frame->is_control_frame());
+ EXPECT_EQ(SETTINGS, settings_frame->type());
+ EXPECT_EQ(index + 1, settings_frame->num_entries());
+
+ SpdySettings parsed_settings;
+ EXPECT_TRUE(framer.ParseSettings(settings_frame.get(), &parsed_settings));
+ EXPECT_EQ(parsed_settings.size(), settings.size());
+ SpdySettings::const_iterator it = parsed_settings.begin();
+ int pos = 0;
+ while (it != parsed_settings.end()) {
+ SettingsFlagsAndId parsed = it->first;
+ uint32 value = it->second;
+ EXPECT_EQ(parsed.flags(), ids[pos].flags());
+ EXPECT_EQ(parsed.id(), ids[pos].id());
+ EXPECT_EQ(value, static_cast<uint32>(pos));
+ ++it;
+ ++pos;
+ }
+ }
+}
+
+// Make sure that overflows both die in debug mode, and do not cause problems
+// in opt mode. Note: The EXPECT_DEBUG_DEATH call does not work on Win32 yet,
+// so we comment it out.
+TEST(SpdyProtocolDeathTest, TestDataFrame) {
+ SpdyDataFrame frame;
+
+ frame.set_stream_id(0);
+ // TODO(mbelshe): implement EXPECT_DEBUG_DEATH on windows.
+#ifndef WIN32
+ EXPECT_DEBUG_DEATH(frame.set_stream_id(~0), "");
+#endif
+ EXPECT_FALSE(frame.is_control_frame());
+
+ frame.set_flags(0);
+#ifndef WIN32
+ EXPECT_DEBUG_DEATH(frame.set_length(~0), "");
+#endif
+ EXPECT_EQ(0, frame.flags());
+}
+
+TEST(SpdyProtocolDeathTest, TestSpdyControlFrameStreamId) {
+ SpdyControlFrame frame_store(SpdySynStreamControlFrame::size());
+ memset(frame_store.data(), '1', SpdyControlFrame::size());
+ SpdySynStreamControlFrame* frame =
+ reinterpret_cast<SpdySynStreamControlFrame*>(&frame_store);
+
+ // Set the stream ID to various values.
+ frame->set_stream_id(0);
+ EXPECT_EQ(0u, frame->stream_id());
+ EXPECT_FALSE(frame->is_control_frame());
+ frame->set_stream_id(kStreamIdMask);
+ EXPECT_EQ(kStreamIdMask, frame->stream_id());
+ EXPECT_FALSE(frame->is_control_frame());
+}
+
+TEST(SpdyProtocolDeathTest, TestSpdyControlFrameVersion) {
+ const unsigned int kVersionMask = 0x7fff;
+ SpdyControlFrame frame(SpdySynStreamControlFrame::size());
+ memset(frame.data(), '1', SpdyControlFrame::size());
+
+ // Set the version to various values, and make sure it does not affect the
+ // type.
+ frame.set_type(SYN_STREAM);
+ frame.set_version(0);
+ EXPECT_EQ(0, frame.version());
+ EXPECT_TRUE(frame.is_control_frame());
+ EXPECT_EQ(SYN_STREAM, frame.type());
+
+ SpdySynStreamControlFrame* syn_stream =
+ reinterpret_cast<SpdySynStreamControlFrame*>(&frame);
+ syn_stream->set_stream_id(~0 & kVersionMask);
+ EXPECT_EQ(~0 & kVersionMask, syn_stream->stream_id());
+ EXPECT_TRUE(frame.is_control_frame());
+ EXPECT_EQ(SYN_STREAM, frame.type());
+}
+
+TEST(SpdyProtocolDeathTest, TestSpdyControlFrameType) {
+ SpdyControlFrame frame(SpdyControlFrame::size());
+ memset(frame.data(), 255, SpdyControlFrame::size());
+
+ // type() should be out of bounds.
+ EXPECT_FALSE(frame.AppearsToBeAValidControlFrame());
+
+ uint16 version = frame.version();
+
+ for (int i = SYN_STREAM; i <= spdy::NOOP; ++i) {
+ frame.set_type(static_cast<SpdyControlType>(i));
+ EXPECT_EQ(i, static_cast<int>(frame.type()));
+ EXPECT_TRUE(frame.AppearsToBeAValidControlFrame());
+ // Make sure setting type does not alter the version block.
+ EXPECT_EQ(version, frame.version());
+ EXPECT_TRUE(frame.is_control_frame());
+ }
+}
+
+} // namespace
diff --git a/net/spdy/spdy_session.cc b/net/spdy/spdy_session.cc
new file mode 100644
index 0000000..06ba7d5
--- /dev/null
+++ b/net/spdy/spdy_session.cc
@@ -0,0 +1,1324 @@
+// 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 "net/spdy/spdy_session.h"
+
+#include "base/basictypes.h"
+#include "base/linked_ptr.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/rand_util.h"
+#include "base/stats_counters.h"
+#include "base/stl_util-inl.h"
+#include "base/string_util.h"
+#include "base/time.h"
+#include "base/values.h"
+#include "net/base/connection_type_histograms.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "net/http/http_network_session.h"
+#include "net/socket/client_socket.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/spdy/spdy_frame_builder.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_settings_storage.h"
+#include "net/spdy/spdy_stream.h"
+
+namespace {
+
+// Diagnostics function to dump the headers of a request.
+// TODO(mbelshe): Remove this function.
+void DumpSpdyHeaders(const spdy::SpdyHeaderBlock& headers) {
+ // Because this function gets called on every request,
+ // take extra care to optimize it away if logging is turned off.
+ if (logging::LOG_INFO < logging::GetMinLogLevel())
+ return;
+
+ spdy::SpdyHeaderBlock::const_iterator it = headers.begin();
+ while (it != headers.end()) {
+ std::string val = (*it).second;
+ std::string::size_type pos = 0;
+ while ((pos = val.find('\0', pos)) != val.npos)
+ val[pos] = '\n';
+ LOG(INFO) << (*it).first << "==" << val;
+ ++it;
+ }
+}
+
+} // namespace
+
+namespace net {
+
+namespace {
+
+#ifdef WIN32
+// We use an artificially small buffer size on windows because the async IO
+// system will artifiially delay IO completions when we use large buffers.
+const int kReadBufferSize = 2 * 1024;
+#else
+const int kReadBufferSize = 8 * 1024;
+#endif
+
+void AdjustSocketBufferSizes(ClientSocket* socket) {
+ // Adjust socket buffer sizes.
+ // SPDY uses one socket, and we want a really big buffer.
+ // This greatly helps on links with packet loss - we can even
+ // outperform Vista's dynamic window sizing algorithm.
+ // TODO(mbelshe): more study.
+ const int kSocketBufferSize = 512 * 1024;
+ socket->SetReceiveBufferSize(kSocketBufferSize);
+ socket->SetSendBufferSize(kSocketBufferSize);
+}
+
+class NetLogSpdySynParameter : public NetLog::EventParameters {
+ public:
+ NetLogSpdySynParameter(const linked_ptr<spdy::SpdyHeaderBlock>& headers,
+ spdy::SpdyControlFlags flags,
+ spdy::SpdyStreamId id)
+ : headers_(headers), flags_(flags), id_(id) {}
+
+ Value* ToValue() const {
+ DictionaryValue* dict = new DictionaryValue();
+ DictionaryValue* headers_dict = new DictionaryValue();
+ for (spdy::SpdyHeaderBlock::const_iterator it = headers_->begin();
+ it != headers_->end(); ++it) {
+ headers_dict->SetString(ASCIIToWide(it->first), it->second);
+ }
+ dict->SetInteger(L"flags", flags_);
+ dict->Set(L"headers", headers_dict);
+ dict->SetInteger(L"id", id_);
+ return dict;
+ }
+
+ private:
+ ~NetLogSpdySynParameter() {}
+
+ const linked_ptr<spdy::SpdyHeaderBlock> headers_;
+ spdy::SpdyControlFlags flags_;
+ spdy::SpdyStreamId id_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetLogSpdySynParameter);
+};
+
+class NetLogSpdySettingsParameter : public NetLog::EventParameters {
+ public:
+ explicit NetLogSpdySettingsParameter(const spdy::SpdySettings& settings)
+ : settings_(settings) {}
+
+ Value* ToValue() const {
+ DictionaryValue* dict = new DictionaryValue();
+ ListValue* settings = new ListValue();
+ for (spdy::SpdySettings::const_iterator it = settings_.begin();
+ it != settings_.end(); ++it) {
+ settings->Append(new StringValue(
+ StringPrintf("[%u:%u]", it->first.id(), it->second)));
+ }
+ dict->Set(L"settings", settings);
+ return dict;
+ }
+
+ private:
+ ~NetLogSpdySettingsParameter() {}
+
+ const spdy::SpdySettings settings_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetLogSpdySettingsParameter);
+};
+
+} // namespace
+
+// static
+bool SpdySession::use_ssl_ = true;
+
+SpdySession::SpdySession(const HostPortPair& host_port_pair,
+ HttpNetworkSession* session,
+ NetLog* net_log)
+ : ALLOW_THIS_IN_INITIALIZER_LIST(
+ connect_callback_(this, &SpdySession::OnTCPConnect)),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ ssl_connect_callback_(this, &SpdySession::OnSSLConnect)),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ read_callback_(this, &SpdySession::OnReadComplete)),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ write_callback_(this, &SpdySession::OnWriteComplete)),
+ host_port_pair_(host_port_pair),
+ session_(session),
+ connection_(new ClientSocketHandle),
+ read_buffer_(new IOBuffer(kReadBufferSize)),
+ read_pending_(false),
+ stream_hi_water_mark_(1), // Always start at 1 for the first stream id.
+ write_pending_(false),
+ delayed_write_pending_(false),
+ is_secure_(false),
+ certificate_error_code_(OK),
+ error_(OK),
+ state_(IDLE),
+ max_concurrent_streams_(kDefaultMaxConcurrentStreams),
+ streams_initiated_count_(0),
+ streams_pushed_count_(0),
+ streams_pushed_and_claimed_count_(0),
+ streams_abandoned_count_(0),
+ sent_settings_(false),
+ received_settings_(false),
+ in_session_pool_(true),
+ initial_window_size_(spdy::kInitialWindowSize),
+ net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_SPDY_SESSION)) {
+ net_log_.BeginEvent(
+ NetLog::TYPE_SPDY_SESSION,
+ new NetLogStringParameter("host_port", host_port_pair_.ToString()));
+
+ // TODO(mbelshe): consider randomization of the stream_hi_water_mark.
+
+ spdy_framer_.set_visitor(this);
+
+ session_->ssl_config_service()->GetSSLConfig(&ssl_config_);
+
+ SendSettings();
+}
+
+SpdySession::~SpdySession() {
+ state_ = CLOSED;
+
+ // Cleanup all the streams.
+ CloseAllStreams(net::ERR_ABORTED);
+
+ if (connection_->is_initialized()) {
+ // With Spdy we can't recycle sockets.
+ connection_->socket()->Disconnect();
+ }
+
+ RecordHistograms();
+
+ net_log_.EndEvent(NetLog::TYPE_SPDY_SESSION, NULL);
+}
+
+net::Error SpdySession::InitializeWithSSLSocket(
+ ClientSocketHandle* connection,
+ int certificate_error_code) {
+ static StatsCounter spdy_sessions("spdy.sessions");
+ spdy_sessions.Increment();
+
+ AdjustSocketBufferSizes(connection->socket());
+
+ state_ = CONNECTED;
+ connection_.reset(connection);
+ is_secure_ = true; // |connection| contains an SSLClientSocket.
+ certificate_error_code_ = certificate_error_code;
+
+ // This is a newly initialized session that no client should have a handle to
+ // yet, so there's no need to start writing data as in OnTCPConnect(), but we
+ // should start reading data.
+ net::Error error = ReadSocket();
+ if (error == ERR_IO_PENDING)
+ return OK;
+ return error;
+}
+
+net::Error SpdySession::Connect(
+ const std::string& group_name,
+ const scoped_refptr<TCPSocketParams>& destination,
+ RequestPriority priority) {
+ DCHECK(priority >= SPDY_PRIORITY_HIGHEST && priority <= SPDY_PRIORITY_LOWEST);
+
+ // If the connect process is started, let the caller continue.
+ if (state_ > IDLE)
+ return net::OK;
+
+ state_ = CONNECTING;
+
+ static StatsCounter spdy_sessions("spdy.sessions");
+ spdy_sessions.Increment();
+
+ int rv = connection_->Init(group_name, destination, priority,
+ &connect_callback_, session_->tcp_socket_pool(),
+ net_log_);
+ DCHECK(rv <= 0);
+
+ // If the connect is pending, we still return ok. The APIs enqueue
+ // work until after the connect completes asynchronously later.
+ if (rv == net::ERR_IO_PENDING)
+ return net::OK;
+ OnTCPConnect(rv);
+ return static_cast<net::Error>(rv);
+}
+
+int SpdySession::GetPushStream(
+ const GURL& url,
+ scoped_refptr<SpdyStream>* stream,
+ const BoundNetLog& stream_net_log) {
+ CHECK_NE(state_, CLOSED);
+
+ *stream = NULL;
+
+ // Don't allow access to secure push streams over an unauthenticated, but
+ // encrypted SSL socket.
+ if (is_secure_ && certificate_error_code_ != OK &&
+ (url.SchemeIs("https") || url.SchemeIs("wss"))) {
+ LOG(DFATAL) << "Tried to get pushed spdy stream for secure content over an "
+ << "unauthenticated session.";
+ return certificate_error_code_;
+ }
+
+ const std::string& path = url.PathForRequest();
+
+ *stream = GetActivePushStream(path);
+ if (stream->get()) {
+ DCHECK(streams_pushed_and_claimed_count_ < streams_pushed_count_);
+ streams_pushed_and_claimed_count_++;
+ return OK;
+ }
+
+ // Check if we have a pending push stream for this url.
+ // Note that we shouldn't have a pushed stream for non-GET method.
+ PendingStreamMap::iterator it;
+ it = pending_streams_.find(path);
+ if (it != pending_streams_.end()) {
+ // Server has advertised a stream, but not yet sent it.
+ DCHECK(!it->second);
+ // Server will assign a stream id when the push stream arrives. Use 0 for
+ // now.
+ net_log_.AddEvent(NetLog::TYPE_SPDY_STREAM_ADOPTED_PUSH_STREAM, NULL);
+ *stream = new SpdyStream(this, 0, true);
+ (*stream)->set_path(path);
+ (*stream)->set_net_log(stream_net_log);
+ it->second = *stream;
+ return OK;
+ }
+ return OK;
+}
+
+int SpdySession::CreateStream(
+ const GURL& url,
+ RequestPriority priority,
+ scoped_refptr<SpdyStream>* spdy_stream,
+ const BoundNetLog& stream_net_log,
+ CompletionCallback* callback) {
+ if (!max_concurrent_streams_ ||
+ active_streams_.size() < max_concurrent_streams_) {
+ return CreateStreamImpl(url, priority, spdy_stream, stream_net_log);
+ }
+
+ create_stream_queues_[priority].push(
+ PendingCreateStream(url, priority, spdy_stream,
+ stream_net_log, callback));
+ return ERR_IO_PENDING;
+}
+
+void SpdySession::ProcessPendingCreateStreams() {
+ while (!max_concurrent_streams_ ||
+ active_streams_.size() < max_concurrent_streams_) {
+ bool no_pending_create_streams = true;
+ for (int i = 0;i < NUM_PRIORITIES;++i) {
+ if (!create_stream_queues_[i].empty()) {
+ PendingCreateStream& pending_create = create_stream_queues_[i].front();
+ no_pending_create_streams = false;
+ int error = CreateStreamImpl(*pending_create.url,
+ pending_create.priority,
+ pending_create.spdy_stream,
+ *pending_create.stream_net_log);
+ pending_create.callback->Run(error);
+ create_stream_queues_[i].pop();
+ break;
+ }
+ }
+ if (no_pending_create_streams)
+ return; // there were no streams in any queue
+ }
+}
+
+void SpdySession::CancelPendingCreateStreams(
+ const scoped_refptr<SpdyStream>* spdy_stream) {
+ for (int i = 0;i < NUM_PRIORITIES;++i) {
+ PendingCreateStreamQueue tmp;
+ // Make a copy removing this trans
+ while (!create_stream_queues_[i].empty()) {
+ PendingCreateStream& pending_create = create_stream_queues_[i].front();
+ if (pending_create.spdy_stream != spdy_stream)
+ tmp.push(pending_create);
+ create_stream_queues_[i].pop();
+ }
+ // Now copy it back
+ while (!tmp.empty()) {
+ create_stream_queues_[i].push(tmp.front());
+ tmp.pop();
+ }
+ }
+}
+
+int SpdySession::CreateStreamImpl(
+ const GURL& url,
+ RequestPriority priority,
+ scoped_refptr<SpdyStream>* spdy_stream,
+ const BoundNetLog& stream_net_log) {
+ // Make sure that we don't try to send https/wss over an unauthenticated, but
+ // encrypted SSL socket.
+ if (is_secure_ && certificate_error_code_ != OK &&
+ (url.SchemeIs("https") || url.SchemeIs("wss"))) {
+ LOG(DFATAL) << "Tried to create spdy stream for secure content over an "
+ << "unauthenticated session.";
+ return certificate_error_code_;
+ }
+
+ const std::string& path = url.PathForRequest();
+
+ const spdy::SpdyStreamId stream_id = GetNewStreamId();
+
+ *spdy_stream = new SpdyStream(this, stream_id, false);
+ const scoped_refptr<SpdyStream>& stream = *spdy_stream;
+
+ stream->set_priority(priority);
+ stream->set_path(path);
+ stream->set_net_log(stream_net_log);
+ stream->set_window_size(initial_window_size_);
+ ActivateStream(stream);
+
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyPriorityCount",
+ static_cast<int>(priority), 0, 10, 11);
+
+ LOG(INFO) << "SpdyStream: Creating stream " << stream_id << " for " << url;
+ // TODO(mbelshe): Optimize memory allocations
+ DCHECK(priority >= SPDY_PRIORITY_HIGHEST &&
+ priority <= SPDY_PRIORITY_LOWEST);
+
+ DCHECK_EQ(active_streams_[stream_id].get(), stream.get());
+ return OK;
+}
+
+int SpdySession::WriteSynStream(
+ spdy::SpdyStreamId stream_id,
+ RequestPriority priority,
+ spdy::SpdyControlFlags flags,
+ const linked_ptr<spdy::SpdyHeaderBlock>& headers) {
+ // Find our stream
+ if (!IsStreamActive(stream_id))
+ return ERR_INVALID_SPDY_STREAM;
+ const scoped_refptr<SpdyStream>& stream = active_streams_[stream_id];
+ CHECK_EQ(stream->stream_id(), stream_id);
+
+ scoped_ptr<spdy::SpdySynStreamControlFrame> syn_frame(
+ spdy_framer_.CreateSynStream(stream_id, 0, priority, flags, false,
+ headers.get()));
+ QueueFrame(syn_frame.get(), priority, stream);
+
+ static StatsCounter spdy_requests("spdy.requests");
+ spdy_requests.Increment();
+ streams_initiated_count_++;
+
+ LOG(INFO) << "SPDY SYN_STREAM HEADERS ----------------------------------";
+ DumpSpdyHeaders(*headers);
+
+ const BoundNetLog& log = stream->net_log();
+ if (log.HasListener()) {
+ log.AddEvent(
+ NetLog::TYPE_SPDY_STREAM_SYN_STREAM,
+ new NetLogSpdySynParameter(headers, flags, stream_id));
+ }
+
+ return ERR_IO_PENDING;
+}
+
+int SpdySession::WriteStreamData(spdy::SpdyStreamId stream_id,
+ net::IOBuffer* data, int len,
+ spdy::SpdyDataFlags flags) {
+ LOG(INFO) << "Writing Stream Data for stream " << stream_id << " (" << len
+ << " bytes)";
+ const int kMss = 1430; // This is somewhat arbitrary and not really fixed,
+ // but it will always work reasonably with ethernet.
+ // Chop the world into 2-packet chunks. This is somewhat arbitrary, but
+ // is reasonably small and ensures that we elicit ACKs quickly from TCP
+ // (because TCP tries to only ACK every other packet).
+ const int kMaxSpdyFrameChunkSize = (2 * kMss) - spdy::SpdyFrame::size();
+
+ // Find our stream
+ DCHECK(IsStreamActive(stream_id));
+ scoped_refptr<SpdyStream> stream = active_streams_[stream_id];
+ CHECK_EQ(stream->stream_id(), stream_id);
+ if (!stream)
+ return ERR_INVALID_SPDY_STREAM;
+
+ if (len > kMaxSpdyFrameChunkSize) {
+ len = kMaxSpdyFrameChunkSize;
+ flags = spdy::DATA_FLAG_NONE;
+ }
+
+ // TODO(mbelshe): reduce memory copies here.
+ scoped_ptr<spdy::SpdyDataFrame> frame(
+ spdy_framer_.CreateDataFrame(stream_id, data->data(), len, flags));
+ QueueFrame(frame.get(), stream->priority(), stream);
+ return ERR_IO_PENDING;
+}
+
+void SpdySession::CloseStream(spdy::SpdyStreamId stream_id, int status) {
+ LOG(INFO) << "Closing stream " << stream_id << " with status " << status;
+ // TODO(mbelshe): We should send a RST_STREAM control frame here
+ // so that the server can cancel a large send.
+
+ DeleteStream(stream_id, status);
+}
+
+void SpdySession::ResetStream(
+ spdy::SpdyStreamId stream_id, spdy::SpdyStatusCodes status) {
+ DCHECK(IsStreamActive(stream_id));
+ scoped_refptr<SpdyStream> stream = active_streams_[stream_id];
+ CHECK_EQ(stream->stream_id(), stream_id);
+
+ LOG(INFO) << "Sending a RST_STREAM frame for stream " << stream_id
+ << " with status " << status;
+
+ scoped_ptr<spdy::SpdyRstStreamControlFrame> rst_frame(
+ spdy_framer_.CreateRstStream(stream_id, status));
+ QueueFrame(rst_frame.get(), stream->priority(), stream);
+
+ DeleteStream(stream_id, ERR_SPDY_PROTOCOL_ERROR);
+}
+
+bool SpdySession::IsStreamActive(spdy::SpdyStreamId stream_id) const {
+ return ContainsKey(active_streams_, stream_id);
+}
+
+LoadState SpdySession::GetLoadState() const {
+ // NOTE: The application only queries the LoadState via the
+ // SpdyNetworkTransaction, and details are only needed when
+ // we're in the process of connecting.
+
+ // If we're connecting, defer to the connection to give us the actual
+ // LoadState.
+ if (state_ == CONNECTING)
+ return connection_->GetLoadState();
+
+ // Just report that we're idle since the session could be doing
+ // many things concurrently.
+ return LOAD_STATE_IDLE;
+}
+
+void SpdySession::OnTCPConnect(int result) {
+ LOG(INFO) << "Spdy socket connected (result=" << result << ")";
+
+ // We shouldn't be coming through this path if we didn't just open a fresh
+ // socket (or have an error trying to do so).
+ DCHECK(!connection_->socket() || !connection_->is_reused());
+
+ if (result != net::OK) {
+ DCHECK_LT(result, 0);
+ CloseSessionOnError(static_cast<net::Error>(result));
+ return;
+ } else {
+ UpdateConnectionTypeHistograms(CONNECTION_SPDY);
+ }
+
+ AdjustSocketBufferSizes(connection_->socket());
+
+ if (use_ssl_) {
+ // Add a SSL socket on top of our existing transport socket.
+ ClientSocket* socket = connection_->release_socket();
+ // TODO(mbelshe): Fix the hostname. This is BROKEN without having
+ // a real hostname.
+ socket = session_->socket_factory()->CreateSSLClientSocket(
+ socket, "" /* request_->url.HostNoBrackets() */ , ssl_config_);
+ connection_->set_socket(socket);
+ is_secure_ = true;
+ int status = connection_->socket()->Connect(&ssl_connect_callback_);
+ if (status != ERR_IO_PENDING)
+ OnSSLConnect(status);
+ } else {
+ DCHECK_EQ(state_, CONNECTING);
+ state_ = CONNECTED;
+
+ // Make sure we get any pending data sent.
+ WriteSocketLater();
+ // Start reading
+ ReadSocket();
+ }
+}
+
+void SpdySession::OnSSLConnect(int result) {
+ // TODO(mbelshe): We need to replicate the functionality of
+ // HttpNetworkTransaction::DoSSLConnectComplete here, where it calls
+ // HandleCertificateError() and such.
+ if (IsCertificateError(result))
+ result = OK; // TODO(mbelshe): pretend we're happy anyway.
+
+ if (result == OK) {
+ DCHECK_EQ(state_, CONNECTING);
+ state_ = CONNECTED;
+
+ // After we've connected, send any data to the server, and then issue
+ // our read.
+ WriteSocketLater();
+ ReadSocket();
+ } else {
+ DCHECK_LT(result, 0); // It should be an error, not a byte count.
+ CloseSessionOnError(static_cast<net::Error>(result));
+ }
+}
+
+void SpdySession::OnReadComplete(int bytes_read) {
+ // Parse a frame. For now this code requires that the frame fit into our
+ // buffer (32KB).
+ // TODO(mbelshe): support arbitrarily large frames!
+
+ LOG(INFO) << "Spdy socket read: " << bytes_read << " bytes";
+
+ read_pending_ = false;
+
+ if (bytes_read <= 0) {
+ // Session is tearing down.
+ net::Error error = static_cast<net::Error>(bytes_read);
+ if (bytes_read == 0) {
+ LOG(INFO) << "Spdy socket closed by server[" <<
+ host_port_pair().ToString() << "].";
+ error = ERR_CONNECTION_CLOSED;
+ }
+ CloseSessionOnError(error);
+ return;
+ }
+
+ // The SpdyFramer will use callbacks onto |this| as it parses frames.
+ // When errors occur, those callbacks can lead to teardown of all references
+ // to |this|, so maintain a reference to self during this call for safe
+ // cleanup.
+ scoped_refptr<SpdySession> self(this);
+
+ char *data = read_buffer_->data();
+ while (bytes_read &&
+ spdy_framer_.error_code() == spdy::SpdyFramer::SPDY_NO_ERROR) {
+ uint32 bytes_processed = spdy_framer_.ProcessInput(data, bytes_read);
+ bytes_read -= bytes_processed;
+ data += bytes_processed;
+ if (spdy_framer_.state() == spdy::SpdyFramer::SPDY_DONE)
+ spdy_framer_.Reset();
+ }
+
+ if (state_ != CLOSED)
+ ReadSocket();
+}
+
+void SpdySession::OnWriteComplete(int result) {
+ DCHECK(write_pending_);
+ DCHECK(in_flight_write_.size());
+ DCHECK_NE(result, 0); // This shouldn't happen for write.
+
+ write_pending_ = false;
+
+ scoped_refptr<SpdyStream> stream = in_flight_write_.stream();
+
+ LOG(INFO) << "Spdy write complete (result=" << result << ")"
+ << (stream ? std::string(" for stream ") +
+ IntToString(stream->stream_id()) : "");
+
+ if (result >= 0) {
+ // It should not be possible to have written more bytes than our
+ // in_flight_write_.
+ DCHECK_LE(result, in_flight_write_.buffer()->BytesRemaining());
+
+ in_flight_write_.buffer()->DidConsume(result);
+
+ // We only notify the stream when we've fully written the pending frame.
+ if (!in_flight_write_.buffer()->BytesRemaining()) {
+ if (stream) {
+ // Report the number of bytes written to the caller, but exclude the
+ // frame size overhead. NOTE: if this frame was compressed the
+ // reported bytes written is the compressed size, not the original
+ // size.
+ if (result > 0) {
+ result = in_flight_write_.buffer()->size();
+ DCHECK_GT(result, static_cast<int>(spdy::SpdyFrame::size()));
+ result -= static_cast<int>(spdy::SpdyFrame::size());
+ }
+
+ // It is possible that the stream was cancelled while we were writing
+ // to the socket.
+ if (!stream->cancelled())
+ stream->OnWriteComplete(result);
+ }
+
+ // Cleanup the write which just completed.
+ in_flight_write_.release();
+ }
+
+ // Write more data. We're already in a continuation, so we can
+ // go ahead and write it immediately (without going back to the
+ // message loop).
+ WriteSocketLater();
+ } else {
+ in_flight_write_.release();
+
+ // The stream is now errored. Close it down.
+ CloseSessionOnError(static_cast<net::Error>(result));
+ }
+}
+
+net::Error SpdySession::ReadSocket() {
+ if (read_pending_)
+ return OK;
+
+ if (state_ == CLOSED) {
+ NOTREACHED();
+ return ERR_UNEXPECTED;
+ }
+
+ CHECK(connection_.get());
+ CHECK(connection_->socket());
+ int bytes_read = connection_->socket()->Read(read_buffer_.get(),
+ kReadBufferSize,
+ &read_callback_);
+ switch (bytes_read) {
+ case 0:
+ // Socket is closed!
+ CloseSessionOnError(ERR_CONNECTION_CLOSED);
+ return ERR_CONNECTION_CLOSED;
+ case net::ERR_IO_PENDING:
+ // Waiting for data. Nothing to do now.
+ read_pending_ = true;
+ return ERR_IO_PENDING;
+ default:
+ // Data was read, process it.
+ // Schedule the work through the message loop to avoid recursive
+ // callbacks.
+ read_pending_ = true;
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &SpdySession::OnReadComplete, bytes_read));
+ break;
+ }
+ return OK;
+}
+
+void SpdySession::WriteSocketLater() {
+ if (delayed_write_pending_)
+ return;
+
+ if (state_ < CONNECTED)
+ return;
+
+ delayed_write_pending_ = true;
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &SpdySession::WriteSocket));
+}
+
+void SpdySession::WriteSocket() {
+ // This function should only be called via WriteSocketLater.
+ DCHECK(delayed_write_pending_);
+ delayed_write_pending_ = false;
+
+ // If the socket isn't connected yet, just wait; we'll get called
+ // again when the socket connection completes. If the socket is
+ // closed, just return.
+ if (state_ < CONNECTED || state_ == CLOSED)
+ return;
+
+ if (write_pending_) // Another write is in progress still.
+ return;
+
+ // Loop sending frames until we've sent everything or until the write
+ // returns error (or ERR_IO_PENDING).
+ while (in_flight_write_.buffer() || !queue_.empty()) {
+ if (!in_flight_write_.buffer()) {
+ // Grab the next SpdyFrame to send.
+ SpdyIOBuffer next_buffer = queue_.top();
+ queue_.pop();
+
+ // We've deferred compression until just before we write it to the socket,
+ // which is now. At this time, we don't compress our data frames.
+ spdy::SpdyFrame uncompressed_frame(next_buffer.buffer()->data(), false);
+ size_t size;
+ if (spdy_framer_.IsCompressible(uncompressed_frame)) {
+ scoped_ptr<spdy::SpdyFrame> compressed_frame(
+ spdy_framer_.CompressFrame(uncompressed_frame));
+ if (!compressed_frame.get()) {
+ LOG(ERROR) << "SPDY Compression failure";
+ CloseSessionOnError(net::ERR_SPDY_PROTOCOL_ERROR);
+ return;
+ }
+
+ size = compressed_frame->length() + spdy::SpdyFrame::size();
+
+ DCHECK_GT(size, 0u);
+
+ // TODO(mbelshe): We have too much copying of data here.
+ IOBufferWithSize* buffer = new IOBufferWithSize(size);
+ memcpy(buffer->data(), compressed_frame->data(), size);
+
+ // Attempt to send the frame.
+ in_flight_write_ = SpdyIOBuffer(buffer, size, 0, next_buffer.stream());
+ } else {
+ size = uncompressed_frame.length() + spdy::SpdyFrame::size();
+ in_flight_write_ = next_buffer;
+ }
+ } else {
+ DCHECK(in_flight_write_.buffer()->BytesRemaining());
+ }
+
+ write_pending_ = true;
+ int rv = connection_->socket()->Write(in_flight_write_.buffer(),
+ in_flight_write_.buffer()->BytesRemaining(), &write_callback_);
+ if (rv == net::ERR_IO_PENDING)
+ break;
+
+ // We sent the frame successfully.
+ OnWriteComplete(rv);
+
+ // TODO(mbelshe): Test this error case. Maybe we should mark the socket
+ // as in an error state.
+ if (rv < 0)
+ break;
+ }
+}
+
+void SpdySession::CloseAllStreams(net::Error status) {
+ LOG(INFO) << "Closing all SPDY Streams for " << host_port_pair().ToString();
+
+ static StatsCounter abandoned_streams("spdy.abandoned_streams");
+ static StatsCounter abandoned_push_streams("spdy.abandoned_push_streams");
+
+ if (!active_streams_.empty())
+ abandoned_streams.Add(active_streams_.size());
+ if (!pushed_streams_.empty()) {
+ streams_abandoned_count_ += pushed_streams_.size();
+ abandoned_push_streams.Add(pushed_streams_.size());
+ }
+
+ for (int i = 0;i < NUM_PRIORITIES;++i) {
+ while (!create_stream_queues_[i].empty()) {
+ PendingCreateStream& pending_create = create_stream_queues_[i].front();
+ pending_create.callback->Run(ERR_ABORTED);
+ create_stream_queues_[i].pop();
+ }
+ }
+
+ while (!active_streams_.empty()) {
+ ActiveStreamMap::iterator it = active_streams_.begin();
+ const scoped_refptr<SpdyStream>& stream = it->second;
+ DCHECK(stream);
+ LOG(ERROR) << "ABANDONED (stream_id=" << stream->stream_id()
+ << "): " << stream->path();
+ DeleteStream(stream->stream_id(), status);
+ }
+
+ // TODO(erikchen): ideally stream->OnClose() is only ever called by
+ // DeleteStream, but pending streams fall into their own category for now.
+ PendingStreamMap::iterator it;
+ for (it = pending_streams_.begin(); it != pending_streams_.end(); ++it) {
+ const scoped_refptr<SpdyStream>& stream = it->second;
+ if (stream)
+ stream->OnClose(ERR_ABORTED);
+ }
+ pending_streams_.clear();
+
+ // We also need to drain the queue.
+ while (queue_.size())
+ queue_.pop();
+}
+
+int SpdySession::GetNewStreamId() {
+ int id = stream_hi_water_mark_;
+ stream_hi_water_mark_ += 2;
+ if (stream_hi_water_mark_ > 0x7fff)
+ stream_hi_water_mark_ = 1;
+ return id;
+}
+
+void SpdySession::QueueFrame(spdy::SpdyFrame* frame,
+ spdy::SpdyPriority priority,
+ SpdyStream* stream) {
+ int length = spdy::SpdyFrame::size() + frame->length();
+ IOBuffer* buffer = new IOBuffer(length);
+ memcpy(buffer->data(), frame->data(), length);
+ queue_.push(SpdyIOBuffer(buffer, length, priority, stream));
+
+ WriteSocketLater();
+}
+
+void SpdySession::CloseSessionOnError(net::Error err) {
+ // Closing all streams can have a side-effect of dropping the last reference
+ // to |this|. Hold a reference through this function.
+ scoped_refptr<SpdySession> self(this);
+
+ DCHECK_LT(err, OK);
+ LOG(INFO) << "spdy::CloseSessionOnError(" << err << ") for " <<
+ host_port_pair().ToString();
+
+ // Don't close twice. This can occur because we can have both
+ // a read and a write outstanding, and each can complete with
+ // an error.
+ if (state_ != CLOSED) {
+ state_ = CLOSED;
+ error_ = err;
+ RemoveFromPool();
+ CloseAllStreams(err);
+ }
+}
+
+void SpdySession::ActivateStream(SpdyStream* stream) {
+ const spdy::SpdyStreamId id = stream->stream_id();
+ DCHECK(!IsStreamActive(id));
+
+ active_streams_[id] = stream;
+}
+
+void SpdySession::DeleteStream(spdy::SpdyStreamId id, int status) {
+ // Remove the stream from pushed_streams_ and active_streams_.
+ ActivePushedStreamList::iterator it;
+ for (it = pushed_streams_.begin(); it != pushed_streams_.end(); ++it) {
+ scoped_refptr<SpdyStream> curr = *it;
+ if (id == curr->stream_id()) {
+ pushed_streams_.erase(it);
+ break;
+ }
+ }
+
+ // The stream might have been deleted.
+ ActiveStreamMap::iterator it2 = active_streams_.find(id);
+ if (it2 == active_streams_.end())
+ return;
+
+ // If this is an active stream, call the callback.
+ const scoped_refptr<SpdyStream> stream(it2->second);
+ active_streams_.erase(it2);
+ if (stream)
+ stream->OnClose(status);
+ ProcessPendingCreateStreams();
+}
+
+void SpdySession::RemoveFromPool() {
+ if (in_session_pool_) {
+ session_->spdy_session_pool()->Remove(this);
+ in_session_pool_ = false;
+ }
+}
+
+scoped_refptr<SpdyStream> SpdySession::GetActivePushStream(
+ const std::string& path) {
+ static StatsCounter used_push_streams("spdy.claimed_push_streams");
+
+ LOG(INFO) << "Looking for push stream: " << path;
+
+ scoped_refptr<SpdyStream> stream;
+
+ // We just walk a linear list here.
+ ActivePushedStreamList::iterator it;
+ for (it = pushed_streams_.begin(); it != pushed_streams_.end(); ++it) {
+ stream = *it;
+ if (path == stream->path()) {
+ CHECK(stream->pushed());
+ pushed_streams_.erase(it);
+ used_push_streams.Increment();
+ LOG(INFO) << "Push Stream Claim for: " << path;
+ return stream;
+ }
+ }
+
+ return NULL;
+}
+
+bool SpdySession::GetSSLInfo(SSLInfo* ssl_info, bool* was_npn_negotiated) {
+ if (is_secure_) {
+ SSLClientSocket* ssl_socket =
+ reinterpret_cast<SSLClientSocket*>(connection_->socket());
+ ssl_socket->GetSSLInfo(ssl_info);
+ *was_npn_negotiated = ssl_socket->wasNpnNegotiated();
+ return true;
+ }
+ return false;
+}
+
+void SpdySession::OnError(spdy::SpdyFramer* framer) {
+ LOG(ERROR) << "SpdySession error: " << framer->error_code();
+ CloseSessionOnError(net::ERR_SPDY_PROTOCOL_ERROR);
+}
+
+void SpdySession::OnStreamFrameData(spdy::SpdyStreamId stream_id,
+ const char* data,
+ size_t len) {
+ LOG(INFO) << "Spdy data for stream " << stream_id << ", " << len << " bytes";
+
+ if (!IsStreamActive(stream_id)) {
+ // NOTE: it may just be that the stream was cancelled.
+ LOG(WARNING) << "Received data frame for invalid stream " << stream_id;
+ return;
+ }
+
+ scoped_refptr<SpdyStream> stream = active_streams_[stream_id];
+ stream->OnDataReceived(data, len);
+}
+
+bool SpdySession::Respond(const spdy::SpdyHeaderBlock& headers,
+ const scoped_refptr<SpdyStream> stream) {
+ int rv = OK;
+
+ rv = stream->OnResponseReceived(headers);
+ if (rv < 0) {
+ DCHECK_NE(rv, ERR_IO_PENDING);
+ const spdy::SpdyStreamId stream_id = stream->stream_id();
+ DeleteStream(stream_id, rv);
+ return false;
+ }
+ return true;
+}
+
+void SpdySession::OnSyn(const spdy::SpdySynStreamControlFrame& frame,
+ const linked_ptr<spdy::SpdyHeaderBlock>& headers) {
+ spdy::SpdyStreamId stream_id = frame.stream_id();
+
+ LOG(INFO) << "Spdy SynStream for stream " << stream_id;
+
+ // Server-initiated streams should have even sequence numbers.
+ if ((stream_id & 0x1) != 0) {
+ LOG(ERROR) << "Received invalid OnSyn stream id " << stream_id;
+ return;
+ }
+
+ if (IsStreamActive(stream_id)) {
+ LOG(ERROR) << "Received OnSyn for active stream " << stream_id;
+ return;
+ }
+
+ streams_pushed_count_++;
+
+ LOG(INFO) << "SpdySession: Syn received for stream: " << stream_id;
+
+ LOG(INFO) << "SPDY SYN RESPONSE HEADERS -----------------------";
+ DumpSpdyHeaders(*headers);
+
+ // TODO(mbelshe): DCHECK that this is a GET method?
+
+ const std::string& path = ContainsKey(*headers, "path") ?
+ headers->find("path")->second : "";
+
+ // Verify that the response had a URL for us.
+ DCHECK(!path.empty());
+ if (path.empty()) {
+ LOG(WARNING) << "Pushed stream did not contain a path.";
+ return;
+ }
+
+ // Only HTTP push a stream.
+ scoped_refptr<SpdyStream> stream;
+
+ // Check if we already have a delegate awaiting this stream.
+ PendingStreamMap::iterator it;
+ it = pending_streams_.find(path);
+ if (it != pending_streams_.end()) {
+ stream = it->second;
+ pending_streams_.erase(it);
+ }
+
+ if (stream) {
+ CHECK(stream->pushed());
+ CHECK_EQ(0u, stream->stream_id());
+ stream->set_stream_id(stream_id);
+ const BoundNetLog& log = stream->net_log();
+ if (log.HasListener()) {
+ log.AddEvent(
+ NetLog::TYPE_SPDY_STREAM_PUSHED_SYN_STREAM,
+ new NetLogSpdySynParameter(
+ headers, static_cast<spdy::SpdyControlFlags>(frame.flags()),
+ stream_id));
+ }
+ } else {
+ stream = new SpdyStream(this, stream_id, true);
+
+ if (net_log_.HasListener()) {
+ net_log_.AddEvent(
+ NetLog::TYPE_SPDY_SESSION_PUSHED_SYN_STREAM,
+ new NetLogSpdySynParameter(
+ headers, static_cast<spdy::SpdyControlFlags>(frame.flags()),
+ stream_id));
+ }
+ }
+
+ pushed_streams_.push_back(stream);
+
+ // Activate a stream and parse the headers.
+ ActivateStream(stream);
+
+ stream->set_path(path);
+
+ if (!Respond(*headers, stream))
+ return;
+
+ LOG(INFO) << "Got pushed stream for " << stream->path();
+
+ static StatsCounter push_requests("spdy.pushed_streams");
+ push_requests.Increment();
+}
+
+void SpdySession::OnSynReply(const spdy::SpdySynReplyControlFrame& frame,
+ const linked_ptr<spdy::SpdyHeaderBlock>& headers) {
+ spdy::SpdyStreamId stream_id = frame.stream_id();
+ LOG(INFO) << "Spdy SynReply for stream " << stream_id;
+
+ bool valid_stream = IsStreamActive(stream_id);
+ if (!valid_stream) {
+ // NOTE: it may just be that the stream was cancelled.
+ LOG(WARNING) << "Received SYN_REPLY for invalid stream " << stream_id;
+ return;
+ }
+
+ LOG(INFO) << "SPDY SYN_REPLY RESPONSE HEADERS for stream: " << stream_id;
+ DumpSpdyHeaders(*headers);
+
+ scoped_refptr<SpdyStream> stream = active_streams_[stream_id];
+ CHECK_EQ(stream->stream_id(), stream_id);
+ CHECK(!stream->cancelled());
+
+ if (stream->syn_reply_received()) {
+ LOG(WARNING) << "Received duplicate SYN_REPLY for stream " << stream_id;
+ CloseStream(stream->stream_id(), ERR_SPDY_PROTOCOL_ERROR);
+ return;
+ }
+ stream->set_syn_reply_received();
+
+ // We record content declared as being pushed so that we don't
+ // request a duplicate stream which is already scheduled to be
+ // sent to us.
+ spdy::SpdyHeaderBlock::const_iterator it;
+ it = headers->find("x-associated-content");
+ if (it != headers->end()) {
+ const std::string& content = it->second;
+ std::string::size_type start = 0;
+ std::string::size_type end = 0;
+ do {
+ end = content.find("||", start);
+ if (end == std::string::npos)
+ end = content.length();
+ std::string url = content.substr(start, end - start);
+ std::string::size_type pos = url.find("??");
+ if (pos == std::string::npos)
+ break;
+ url = url.substr(pos + 2);
+ GURL gurl(url);
+ std::string path = gurl.PathForRequest();
+ if (path.length())
+ pending_streams_[path] = NULL;
+ else
+ LOG(INFO) << "Invalid X-Associated-Content path: " << url;
+ start = end + 2;
+ } while (start < content.length());
+ }
+
+ const BoundNetLog& log = stream->net_log();
+ if (log.HasListener()) {
+ log.AddEvent(
+ NetLog::TYPE_SPDY_STREAM_SYN_REPLY,
+ new NetLogSpdySynParameter(
+ headers, static_cast<spdy::SpdyControlFlags>(frame.flags()),
+ stream_id));
+ }
+
+ Respond(*headers, stream);
+}
+
+void SpdySession::OnControl(const spdy::SpdyControlFrame* frame) {
+ const linked_ptr<spdy::SpdyHeaderBlock> headers(new spdy::SpdyHeaderBlock);
+ uint32 type = frame->type();
+ if (type == spdy::SYN_STREAM || type == spdy::SYN_REPLY) {
+ if (!spdy_framer_.ParseHeaderBlock(frame, headers.get())) {
+ LOG(WARNING) << "Could not parse Spdy Control Frame Header";
+ // TODO(mbelshe): Error the session?
+ return;
+ }
+ }
+
+ switch (type) {
+ case spdy::GOAWAY:
+ OnGoAway(*reinterpret_cast<const spdy::SpdyGoAwayControlFrame*>(frame));
+ break;
+ case spdy::SETTINGS:
+ OnSettings(
+ *reinterpret_cast<const spdy::SpdySettingsControlFrame*>(frame));
+ break;
+ case spdy::RST_STREAM:
+ OnFin(*reinterpret_cast<const spdy::SpdyRstStreamControlFrame*>(frame));
+ break;
+ case spdy::SYN_STREAM:
+ OnSyn(*reinterpret_cast<const spdy::SpdySynStreamControlFrame*>(frame),
+ headers);
+ break;
+ case spdy::SYN_REPLY:
+ OnSynReply(
+ *reinterpret_cast<const spdy::SpdySynReplyControlFrame*>(frame),
+ headers);
+ break;
+ case spdy::WINDOW_UPDATE:
+ OnWindowUpdate(
+ *reinterpret_cast<const spdy::SpdyWindowUpdateControlFrame*>(frame));
+ break;
+ default:
+ DCHECK(false); // Error!
+ }
+}
+
+void SpdySession::OnFin(const spdy::SpdyRstStreamControlFrame& frame) {
+ spdy::SpdyStreamId stream_id = frame.stream_id();
+ LOG(INFO) << "Spdy Fin for stream " << stream_id;
+
+ bool valid_stream = IsStreamActive(stream_id);
+ if (!valid_stream) {
+ // NOTE: it may just be that the stream was cancelled.
+ LOG(WARNING) << "Received FIN for invalid stream" << stream_id;
+ return;
+ }
+ scoped_refptr<SpdyStream> stream = active_streams_[stream_id];
+ CHECK_EQ(stream->stream_id(), stream_id);
+ CHECK(!stream->cancelled());
+
+ const BoundNetLog& log = stream->net_log();
+ log.AddEvent(
+ NetLog::TYPE_SPDY_STREAM_RST_STREAM,
+ new NetLogIntegerParameter("status", frame.status()));
+
+ if (frame.status() == 0) {
+ stream->OnDataReceived(NULL, 0);
+ } else {
+ LOG(ERROR) << "Spdy stream closed: " << frame.status();
+ // TODO(mbelshe): Map from Spdy-protocol errors to something sensical.
+ // For now, it doesn't matter much - it is a protocol error.
+ DeleteStream(stream_id, ERR_SPDY_PROTOCOL_ERROR);
+ }
+}
+
+void SpdySession::OnGoAway(const spdy::SpdyGoAwayControlFrame& frame) {
+ LOG(INFO) << "Spdy GOAWAY for session[" << this << "] for " <<
+ host_port_pair().ToString();
+
+ net_log_.AddEvent(
+ NetLog::TYPE_SPDY_SESSION_GOAWAY,
+ new NetLogIntegerParameter(
+ "last_accepted_stream_id",
+ frame.last_accepted_stream_id()));
+
+ RemoveFromPool();
+
+ // TODO(willchan): Cancel any streams that are past the GoAway frame's
+ // |last_accepted_stream_id|.
+
+ // Don't bother killing any streams that are still reading. They'll either
+ // complete successfully or get an ERR_CONNECTION_CLOSED when the socket is
+ // closed.
+}
+
+void SpdySession::OnSettings(const spdy::SpdySettingsControlFrame& frame) {
+ spdy::SpdySettings settings;
+ if (spdy_framer_.ParseSettings(&frame, &settings)) {
+ HandleSettings(settings);
+ SpdySettingsStorage* settings_storage = session_->mutable_spdy_settings();
+ settings_storage->Set(host_port_pair_, settings);
+ }
+
+ // TODO(agayev): Implement initial and per stream window size update.
+
+ received_settings_ = true;
+
+ net_log_.AddEvent(
+ NetLog::TYPE_SPDY_SESSION_RECV_SETTINGS,
+ new NetLogSpdySettingsParameter(settings));
+}
+
+void SpdySession::OnWindowUpdate(
+ const spdy::SpdyWindowUpdateControlFrame& frame) {
+ spdy::SpdyStreamId stream_id = frame.stream_id();
+ LOG(INFO) << "Spdy WINDOW_UPDATE for stream " << stream_id;
+
+ if (!IsStreamActive(stream_id)) {
+ LOG(WARNING) << "Received WINDOW_UPDATE for invalid stream " << stream_id;
+ return;
+ }
+
+ int delta_window_size = static_cast<int>(frame.delta_window_size());
+ if (delta_window_size < 1) {
+ LOG(WARNING) << "Received WINDOW_UPDATE with an invalid delta_window_size "
+ << delta_window_size;
+ ResetStream(stream_id, spdy::FLOW_CONTROL_ERROR);
+ return;
+ }
+
+ scoped_refptr<SpdyStream> stream = active_streams_[stream_id];
+ CHECK_EQ(stream->stream_id(), stream_id);
+ CHECK(!stream->cancelled());
+
+ stream->UpdateWindowSize(delta_window_size);
+}
+
+void SpdySession::SendSettings() {
+ const SpdySettingsStorage& settings_storage = session_->spdy_settings();
+ const spdy::SpdySettings& settings = settings_storage.Get(host_port_pair_);
+ if (settings.empty())
+ return;
+ HandleSettings(settings);
+
+ net_log_.AddEvent(
+ NetLog::TYPE_SPDY_SESSION_SEND_SETTINGS,
+ new NetLogSpdySettingsParameter(settings));
+
+ // Create the SETTINGS frame and send it.
+ scoped_ptr<spdy::SpdySettingsControlFrame> settings_frame(
+ spdy_framer_.CreateSettings(settings));
+ sent_settings_ = true;
+ QueueFrame(settings_frame.get(), 0, NULL);
+}
+
+void SpdySession::HandleSettings(const spdy::SpdySettings& settings) {
+ for (spdy::SpdySettings::const_iterator i = settings.begin(),
+ end = settings.end(); i != end; ++i) {
+ const uint32 id = i->first.id();
+ const uint32 val = i->second;
+ switch (id) {
+ case spdy::SETTINGS_MAX_CONCURRENT_STREAMS:
+ max_concurrent_streams_ = val;
+ ProcessPendingCreateStreams();
+ break;
+ }
+ }
+}
+
+void SpdySession::RecordHistograms() {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyStreamsPerSession",
+ streams_initiated_count_,
+ 0, 300, 50);
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyStreamsPushedPerSession",
+ streams_pushed_count_,
+ 0, 300, 50);
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyStreamsPushedAndClaimedPerSession",
+ streams_pushed_and_claimed_count_,
+ 0, 300, 50);
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyStreamsAbandonedPerSession",
+ streams_abandoned_count_,
+ 0, 300, 50);
+ UMA_HISTOGRAM_ENUMERATION("Net.SpdySettingsSent",
+ sent_settings_ ? 1 : 0, 2);
+ UMA_HISTOGRAM_ENUMERATION("Net.SpdySettingsReceived",
+ received_settings_ ? 1 : 0, 2);
+
+ if (received_settings_) {
+ // Enumerate the saved settings, and set histograms for it.
+ const SpdySettingsStorage& settings_storage = session_->spdy_settings();
+ const spdy::SpdySettings& settings = settings_storage.Get(host_port_pair_);
+
+ spdy::SpdySettings::const_iterator it;
+ for (it = settings.begin(); it != settings.end(); ++it) {
+ const spdy::SpdySetting setting = *it;
+ switch (setting.first.id()) {
+ case spdy::SETTINGS_CURRENT_CWND:
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwnd",
+ setting.second,
+ 1, 200, 100);
+ break;
+ case spdy::SETTINGS_ROUND_TRIP_TIME:
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsRTT",
+ setting.second,
+ 1, 1200, 100);
+ break;
+ case spdy::SETTINGS_DOWNLOAD_RETRANS_RATE:
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsRetransRate",
+ setting.second,
+ 1, 100, 50);
+ break;
+ }
+ }
+ }
+}
+
+} // namespace net
diff --git a/net/spdy/spdy_session.h b/net/spdy/spdy_session.h
new file mode 100644
index 0000000..3add9b0
--- /dev/null
+++ b/net/spdy/spdy_session.h
@@ -0,0 +1,342 @@
+// 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.
+
+#ifndef NET_SPDY_SPDY_SESSION_H_
+#define NET_SPDY_SPDY_SESSION_H_
+
+#include <deque>
+#include <list>
+#include <map>
+#include <queue>
+#include <string>
+
+#include "base/linked_ptr.h"
+#include "base/ref_counted.h"
+#include "net/base/io_buffer.h"
+#include "net/base/load_states.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/request_priority.h"
+#include "net/base/ssl_config_service.h"
+#include "net/base/upload_data_stream.h"
+#include "net/socket/client_socket.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/tcp_client_socket_pool.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_io_buffer.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_session_pool.h"
+#include "testing/gtest/include/gtest/gtest_prod.h" // For FRIEND_TEST
+
+namespace net {
+
+class SpdyStream;
+class HttpNetworkSession;
+class BoundNetLog;
+class SSLInfo;
+
+class SpdySession : public base::RefCounted<SpdySession>,
+ public spdy::SpdyFramerVisitorInterface {
+ public:
+ // Create a new SpdySession.
+ // |host_port_pair| is the host/port that this session connects to.
+ // |session| is the HttpNetworkSession. |net_log| is the NetLog that we log
+ // network events to.
+ SpdySession(const HostPortPair& host_port_pair, HttpNetworkSession* session,
+ NetLog* net_log);
+
+ const HostPortPair& host_port_pair() const { return host_port_pair_; }
+
+ // Connect the Spdy Socket.
+ // Returns net::Error::OK on success.
+ // Note that this call does not wait for the connect to complete. Callers can
+ // immediately start using the SpdySession while it connects.
+ net::Error Connect(const std::string& group_name,
+ const scoped_refptr<TCPSocketParams>& destination,
+ RequestPriority priority);
+
+ // Get a pushed stream for a given |url|.
+ // If the server initiates a stream, it might already exist for a given path.
+ // The server might also not have initiated the stream yet, but indicated it
+ // will via X-Associated-Content. Writes the stream out to |spdy_stream|.
+ // Returns a net error code.
+ int GetPushStream(
+ const GURL& url,
+ scoped_refptr<SpdyStream>* spdy_stream,
+ const BoundNetLog& stream_net_log);
+
+ // Create a new stream for a given |url|. Writes it out to |spdy_stream|.
+ // Returns a net error code, possibly ERR_IO_PENDING.
+ int CreateStream(
+ const GURL& url,
+ RequestPriority priority,
+ scoped_refptr<SpdyStream>* spdy_stream,
+ const BoundNetLog& stream_net_log,
+ CompletionCallback* callback);
+
+ // Remove PendingCreateStream objects on transaction deletion
+ void CancelPendingCreateStreams(const scoped_refptr<SpdyStream>* spdy_stream);
+
+ // Used by SpdySessionPool to initialize with a pre-existing SSL socket.
+ // Returns OK on success, or an error on failure.
+ net::Error InitializeWithSSLSocket(ClientSocketHandle* connection,
+ int certificate_error_code);
+
+ // Send the SYN frame for |stream_id|.
+ int WriteSynStream(
+ spdy::SpdyStreamId stream_id,
+ RequestPriority priority,
+ spdy::SpdyControlFlags flags,
+ const linked_ptr<spdy::SpdyHeaderBlock>& headers);
+
+ // Write a data frame to the stream.
+ // Used to create and queue a data frame for the given stream.
+ int WriteStreamData(spdy::SpdyStreamId stream_id, net::IOBuffer* data,
+ int len,
+ spdy::SpdyDataFlags flags);
+
+ // Close a stream.
+ void CloseStream(spdy::SpdyStreamId stream_id, int status);
+
+ // Reset a stream by sending a RST_STREAM frame with given status code.
+ // Also closes the stream. Was not piggybacked to CloseStream since not
+ // all of the calls to CloseStream necessitate sending a RST_STREAM.
+ void ResetStream(spdy::SpdyStreamId stream_id, spdy::SpdyStatusCodes status);
+
+ // Check if a stream is active.
+ bool IsStreamActive(spdy::SpdyStreamId stream_id) const;
+
+ // The LoadState is used for informing the user of the current network
+ // status, such as "resolving host", "connecting", etc.
+ LoadState GetLoadState() const;
+
+ // Fills SSL info in |ssl_info| and returns true when SSL is in use.
+ bool GetSSLInfo(SSLInfo* ssl_info, bool* was_npn_negotiated);
+
+ // Enable or disable SSL.
+ static void SetSSLMode(bool enable) { use_ssl_ = enable; }
+ static bool SSLMode() { return use_ssl_; }
+
+ // If session is closed, no new streams/transactions should be created.
+ bool IsClosed() const { return state_ == CLOSED; }
+
+ // Closes this session. This will close all active streams and mark
+ // the session as permanently closed.
+ // |err| should not be OK; this function is intended to be called on
+ // error.
+ void CloseSessionOnError(net::Error err);
+
+ private:
+ friend class base::RefCounted<SpdySession>;
+ FRIEND_TEST(SpdySessionTest, GetActivePushStream);
+
+ enum State {
+ IDLE,
+ CONNECTING,
+ CONNECTED,
+ CLOSED
+ };
+
+ enum { kDefaultMaxConcurrentStreams = 100 }; // TODO(mbelshe) remove this
+
+ struct PendingCreateStream {
+ const GURL* url;
+ RequestPriority priority;
+ scoped_refptr<SpdyStream>* spdy_stream;
+ const BoundNetLog* stream_net_log;
+ CompletionCallback* callback;
+
+ PendingCreateStream(const GURL& url, RequestPriority priority,
+ scoped_refptr<SpdyStream>* spdy_stream,
+ const BoundNetLog& stream_net_log,
+ CompletionCallback* callback)
+ : url(&url), priority(priority), spdy_stream(spdy_stream),
+ stream_net_log(&stream_net_log), callback(callback) { }
+ };
+ typedef std::queue<PendingCreateStream, std::list< PendingCreateStream> >
+ PendingCreateStreamQueue;
+ typedef std::map<int, scoped_refptr<SpdyStream> > ActiveStreamMap;
+ // Only HTTP push a stream.
+ typedef std::list<scoped_refptr<SpdyStream> > ActivePushedStreamList;
+ typedef std::map<std::string, scoped_refptr<SpdyStream> > PendingStreamMap;
+ typedef std::priority_queue<SpdyIOBuffer> OutputQueue;
+
+ virtual ~SpdySession();
+
+ void ProcessPendingCreateStreams();
+ int CreateStreamImpl(
+ const GURL& url,
+ RequestPriority priority,
+ scoped_refptr<SpdyStream>* spdy_stream,
+ const BoundNetLog& stream_net_log);
+
+ // SpdyFramerVisitorInterface
+ virtual void OnError(spdy::SpdyFramer*);
+ virtual void OnStreamFrameData(spdy::SpdyStreamId stream_id,
+ const char* data,
+ size_t len);
+ virtual void OnControl(const spdy::SpdyControlFrame* frame);
+
+ // Control frame handlers.
+ void OnSyn(const spdy::SpdySynStreamControlFrame& frame,
+ const linked_ptr<spdy::SpdyHeaderBlock>& headers);
+ void OnSynReply(const spdy::SpdySynReplyControlFrame& frame,
+ const linked_ptr<spdy::SpdyHeaderBlock>& headers);
+ void OnFin(const spdy::SpdyRstStreamControlFrame& frame);
+ void OnGoAway(const spdy::SpdyGoAwayControlFrame& frame);
+ void OnSettings(const spdy::SpdySettingsControlFrame& frame);
+ void OnWindowUpdate(const spdy::SpdyWindowUpdateControlFrame& frame);
+
+ // IO Callbacks
+ void OnTCPConnect(int result);
+ void OnSSLConnect(int result);
+ void OnReadComplete(int result);
+ void OnWriteComplete(int result);
+
+ // Send relevant SETTINGS. This is generally called on connection setup.
+ void SendSettings();
+
+ // Handle SETTINGS. Either when we send settings, or when we receive a
+ // SETTINGS ontrol frame, update our SpdySession accordingly.
+ void HandleSettings(const spdy::SpdySettings& settings);
+
+ // Start reading from the socket.
+ // Returns OK on success, or an error on failure.
+ net::Error ReadSocket();
+
+ // Write current data to the socket.
+ void WriteSocketLater();
+ void WriteSocket();
+
+ // Get a new stream id.
+ int GetNewStreamId();
+
+ // Queue a frame for sending.
+ // |frame| is the frame to send.
+ // |priority| is the priority for insertion into the queue.
+ // |stream| is the stream which this IO is associated with (or NULL).
+ void QueueFrame(spdy::SpdyFrame* frame, spdy::SpdyPriority priority,
+ SpdyStream* stream);
+
+ // Track active streams in the active stream list.
+ void ActivateStream(SpdyStream* stream);
+ void DeleteStream(spdy::SpdyStreamId id, int status);
+
+ // Removes this session from the session pool.
+ void RemoveFromPool();
+
+ // Check if we have a pending pushed-stream for this url
+ // Returns the stream if found (and returns it from the pending
+ // list), returns NULL otherwise.
+ scoped_refptr<SpdyStream> GetActivePushStream(const std::string& url);
+
+ // Calls OnResponseReceived().
+ // Returns true if successful.
+ bool Respond(const spdy::SpdyHeaderBlock& headers,
+ const scoped_refptr<SpdyStream> stream);
+
+ void RecordHistograms();
+
+ // Closes all streams. Used as part of shutdown.
+ void CloseAllStreams(net::Error status);
+
+ // Callbacks for the Spdy session.
+ CompletionCallbackImpl<SpdySession> connect_callback_;
+ CompletionCallbackImpl<SpdySession> ssl_connect_callback_;
+ CompletionCallbackImpl<SpdySession> read_callback_;
+ CompletionCallbackImpl<SpdySession> write_callback_;
+
+ // The domain this session is connected to.
+ const HostPortPair host_port_pair_;
+
+ SSLConfig ssl_config_;
+
+ scoped_refptr<HttpNetworkSession> session_;
+
+ // The socket handle for this session.
+ scoped_ptr<ClientSocketHandle> connection_;
+
+ // The read buffer used to read data from the socket.
+ scoped_refptr<IOBuffer> read_buffer_;
+ bool read_pending_;
+
+ int stream_hi_water_mark_; // The next stream id to use.
+
+ // Queue, for each priority, of pending Create Streams that have not
+ // yet been satisfied
+ PendingCreateStreamQueue create_stream_queues_[NUM_PRIORITIES];
+
+ // TODO(mbelshe): We need to track these stream lists better.
+ // I suspect it is possible to remove a stream from
+ // one list, but not the other.
+
+ // Map from stream id to all active streams. Streams are active in the sense
+ // that they have a consumer (typically SpdyNetworkTransaction and regardless
+ // of whether or not there is currently any ongoing IO [might be waiting for
+ // the server to start pushing the stream]) or there are still network events
+ // incoming even though the consumer has already gone away (cancellation).
+ // TODO(willchan): Perhaps we should separate out cancelled streams and move
+ // them into a separate ActiveStreamMap, and not deliver network events to
+ // them?
+ ActiveStreamMap active_streams_;
+ // List of all the streams that have already started to be pushed by the
+ // server, but do not have consumers yet.
+ ActivePushedStreamList pushed_streams_;
+ // List of streams declared in X-Associated-Content headers, but do not have
+ // consumers yet.
+ // The key is a string representing the path of the URI being pushed.
+ PendingStreamMap pending_streams_;
+
+ // As we gather data to be sent, we put it into the output queue.
+ OutputQueue queue_;
+
+ // The packet we are currently sending.
+ bool write_pending_; // Will be true when a write is in progress.
+ SpdyIOBuffer in_flight_write_; // This is the write buffer in progress.
+
+ // Flag if we have a pending message scheduled for WriteSocket.
+ bool delayed_write_pending_;
+
+ // Flag if we're using an SSL connection for this SpdySession.
+ bool is_secure_;
+
+ // Certificate error code when using a secure connection.
+ int certificate_error_code_;
+
+ // Spdy Frame state.
+ spdy::SpdyFramer spdy_framer_;
+
+ // If an error has occurred on the session, the session is effectively
+ // dead. Record this error here. When no error has occurred, |error_| will
+ // be OK.
+ net::Error error_;
+ State state_;
+
+ // Limits
+ size_t max_concurrent_streams_; // 0 if no limit
+
+ // Some statistics counters for the session.
+ int streams_initiated_count_;
+ int streams_pushed_count_;
+ int streams_pushed_and_claimed_count_;
+ int streams_abandoned_count_;
+ bool sent_settings_; // Did this session send settings when it started.
+ bool received_settings_; // Did this session receive at least one settings
+ // frame.
+
+ bool in_session_pool_; // True if the session is currently in the pool.
+
+ int initial_window_size_; // Initial window size for the session; can be
+ // changed by an arriving SETTINGS frame; newly
+ // created streams use this value for the initial
+ // window size.
+
+ BoundNetLog net_log_;
+
+ static bool use_ssl_;
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_SESSION_H_
diff --git a/net/spdy/spdy_session_pool.cc b/net/spdy/spdy_session_pool.cc
new file mode 100644
index 0000000..8caa040
--- /dev/null
+++ b/net/spdy/spdy_session_pool.cc
@@ -0,0 +1,147 @@
+// 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 "net/spdy/spdy_session_pool.h"
+
+#include "base/logging.h"
+#include "net/spdy/spdy_session.h"
+
+namespace net {
+
+// The maximum number of sessions to open to a single domain.
+static const size_t kMaxSessionsPerDomain = 1;
+
+int SpdySessionPool::g_max_sessions_per_domain = kMaxSessionsPerDomain;
+
+SpdySessionPool::SpdySessionPool() {
+ NetworkChangeNotifier::AddObserver(this);
+}
+
+SpdySessionPool::~SpdySessionPool() {
+ CloseAllSessions();
+
+ NetworkChangeNotifier::RemoveObserver(this);
+}
+
+scoped_refptr<SpdySession> SpdySessionPool::Get(
+ const HostPortPair& host_port_pair, HttpNetworkSession* session,
+ const BoundNetLog& net_log) {
+ scoped_refptr<SpdySession> spdy_session;
+ SpdySessionList* list = GetSessionList(host_port_pair);
+ if (list) {
+ if (list->size() >= static_cast<unsigned int>(g_max_sessions_per_domain)) {
+ spdy_session = list->front();
+ list->pop_front();
+ }
+ } else {
+ list = AddSessionList(host_port_pair);
+ }
+
+ DCHECK(list);
+ if (!spdy_session)
+ spdy_session = new SpdySession(host_port_pair, session, net_log.net_log());
+
+ DCHECK(spdy_session);
+ list->push_back(spdy_session);
+ DCHECK_LE(list->size(), static_cast<unsigned int>(g_max_sessions_per_domain));
+ return spdy_session;
+}
+
+net::Error SpdySessionPool::GetSpdySessionFromSSLSocket(
+ const HostPortPair& host_port_pair,
+ HttpNetworkSession* session,
+ ClientSocketHandle* connection,
+ const BoundNetLog& net_log,
+ int certificate_error_code,
+ scoped_refptr<SpdySession>* spdy_session) {
+ // Create the SPDY session and add it to the pool.
+ *spdy_session = new SpdySession(host_port_pair, session, net_log.net_log());
+ SpdySessionList* list = GetSessionList(host_port_pair);
+ if (!list)
+ list = AddSessionList(host_port_pair);
+ DCHECK(list->empty());
+ list->push_back(*spdy_session);
+
+ // Now we can initialize the session with the SSL socket.
+ return (*spdy_session)->InitializeWithSSLSocket(connection,
+ certificate_error_code);
+}
+
+bool SpdySessionPool::HasSession(const HostPortPair& host_port_pair) const {
+ if (GetSessionList(host_port_pair))
+ return true;
+ return false;
+}
+
+void SpdySessionPool::Remove(const scoped_refptr<SpdySession>& session) {
+ SpdySessionList* list = GetSessionList(session->host_port_pair());
+ DCHECK(list); // We really shouldn't remove if we've already been removed.
+ if (!list)
+ return;
+ list->remove(session);
+ if (list->empty())
+ RemoveSessionList(session->host_port_pair());
+}
+
+void SpdySessionPool::OnIPAddressChanged() {
+ ClearSessions();
+}
+
+SpdySessionPool::SpdySessionList*
+ SpdySessionPool::AddSessionList(const HostPortPair& host_port_pair) {
+ DCHECK(sessions_.find(host_port_pair) == sessions_.end());
+ SpdySessionPool::SpdySessionList* list = new SpdySessionList();
+ sessions_[host_port_pair] = list;
+ return list;
+}
+
+SpdySessionPool::SpdySessionList*
+ SpdySessionPool::GetSessionList(const HostPortPair& host_port_pair) {
+ SpdySessionsMap::iterator it = sessions_.find(host_port_pair);
+ if (it == sessions_.end())
+ return NULL;
+ return it->second;
+}
+
+const SpdySessionPool::SpdySessionList*
+ SpdySessionPool::GetSessionList(const HostPortPair& host_port_pair) const {
+ SpdySessionsMap::const_iterator it = sessions_.find(host_port_pair);
+ if (it == sessions_.end())
+ return NULL;
+ return it->second;
+}
+
+void SpdySessionPool::RemoveSessionList(const HostPortPair& host_port_pair) {
+ SpdySessionList* list = GetSessionList(host_port_pair);
+ if (list) {
+ delete list;
+ sessions_.erase(host_port_pair);
+ } else {
+ DCHECK(false) << "removing orphaned session list";
+ }
+}
+
+void SpdySessionPool::ClearSessions() {
+ while (!sessions_.empty()) {
+ SpdySessionList* list = sessions_.begin()->second;
+ DCHECK(list);
+ sessions_.erase(sessions_.begin()->first);
+ while (list->size()) {
+ list->pop_front();
+ }
+ delete list;
+ }
+}
+
+void SpdySessionPool::CloseAllSessions() {
+ while (!sessions_.empty()) {
+ SpdySessionList* list = sessions_.begin()->second;
+ DCHECK(list);
+ const scoped_refptr<SpdySession>& session = list->front();
+ DCHECK(session);
+ session->CloseSessionOnError(net::ERR_ABORTED);
+ }
+}
+
+} // namespace net
diff --git a/net/spdy/spdy_session_pool.h b/net/spdy/spdy_session_pool.h
new file mode 100644
index 0000000..1004169
--- /dev/null
+++ b/net/spdy/spdy_session_pool.h
@@ -0,0 +1,113 @@
+// 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.
+
+#ifndef NET_SPDY_SPDY_SESSION_POOL_H_
+#define NET_SPDY_SPDY_SESSION_POOL_H_
+
+#include <map>
+#include <list>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_errors.h"
+#include "net/base/network_change_notifier.h"
+#include "testing/gtest/include/gtest/gtest_prod.h" // For FRIEND_TEST
+
+namespace net {
+
+class BoundNetLog;
+class ClientSocketHandle;
+class HttpNetworkSession;
+class SpdySession;
+
+// This is a very simple pool for open SpdySessions.
+// TODO(mbelshe): Make this production ready.
+class SpdySessionPool
+ : public base::RefCounted<SpdySessionPool>,
+ public NetworkChangeNotifier::Observer {
+ public:
+ SpdySessionPool();
+
+ // Either returns an existing SpdySession or creates a new SpdySession for
+ // use.
+ scoped_refptr<SpdySession> Get(
+ const HostPortPair& host_port_pair, HttpNetworkSession* session,
+ const BoundNetLog& net_log);
+
+ // Set the maximum concurrent sessions per domain.
+ static void set_max_sessions_per_domain(int max) {
+ if (max >= 1)
+ g_max_sessions_per_domain = max;
+ }
+
+ // Builds a SpdySession from an existing SSL socket. Users should try
+ // calling Get() first to use an existing SpdySession so we don't get
+ // multiple SpdySessions per domain. Note that ownership of |connection| is
+ // transferred from the caller to the SpdySession.
+ // |certificate_error_code| is used to indicate the certificate error
+ // encountered when connecting the SSL socket. OK means there was no error.
+ // Returns OK on success, and the |spdy_session| will be provided.
+ // Returns an error on failure, and |spdy_session| will be NULL.
+ net::Error GetSpdySessionFromSSLSocket(
+ const HostPortPair& host_port_pair,
+ HttpNetworkSession* session,
+ ClientSocketHandle* connection,
+ const BoundNetLog& net_log,
+ int certificate_error_code,
+ scoped_refptr<SpdySession>* spdy_session);
+
+ // TODO(willchan): Consider renaming to HasReusableSession, since perhaps we
+ // should be creating a new session.
+ bool HasSession(const HostPortPair& host_port_pair)const;
+
+ // Close all Spdy Sessions; used for debugging.
+ void CloseAllSessions();
+
+ // Removes a SpdySession from the SpdySessionPool.
+ void Remove(const scoped_refptr<SpdySession>& session);
+
+ // NetworkChangeNotifier::Observer methods:
+
+ // We flush all idle sessions and release references to the active ones so
+ // they won't get re-used. The active ones will either complete successfully
+ // or error out due to the IP address change.
+ virtual void OnIPAddressChanged();
+
+ private:
+ friend class base::RefCounted<SpdySessionPool>;
+ friend class SpdySessionPoolPeer; // For testing.
+ friend class SpdyNetworkTransactionTest; // For testing.
+ FRIEND_TEST(SpdyNetworkTransactionTest, WindowUpdateOverflow);
+
+ typedef std::list<scoped_refptr<SpdySession> > SpdySessionList;
+ typedef std::map<HostPortPair, SpdySessionList*> SpdySessionsMap;
+
+ virtual ~SpdySessionPool();
+
+ // Helper functions for manipulating the lists.
+ SpdySessionList* AddSessionList(const HostPortPair& host_port_pair);
+ SpdySessionList* GetSessionList(const HostPortPair& host_port_pair);
+ const SpdySessionList* GetSessionList(
+ const HostPortPair& host_port_pair) const;
+ void RemoveSessionList(const HostPortPair& host_port_pair);
+ // Releases the SpdySessionPool reference to all sessions. Will result in all
+ // idle sessions being deleted, and the active sessions from being reused, so
+ // they will be deleted once all active streams belonging to that session go
+ // away.
+ void ClearSessions();
+
+ // This is our weak session pool - one session per domain.
+ SpdySessionsMap sessions_;
+
+ static int g_max_sessions_per_domain;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdySessionPool);
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_SESSION_POOL_H_
diff --git a/net/spdy/spdy_session_unittest.cc b/net/spdy/spdy_session_unittest.cc
new file mode 100644
index 0000000..c012347
--- /dev/null
+++ b/net/spdy/spdy_session_unittest.cc
@@ -0,0 +1,197 @@
+// 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 "net/spdy/spdy_io_buffer.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_stream.h"
+#include "net/spdy/spdy_test_util.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+// TODO(cbentzel): Expose compression setter/getter in public SpdySession
+// interface rather than going through all these contortions.
+class SpdySessionTest : public PlatformTest {
+ public:
+ static void TurnOffCompression() {
+ spdy::SpdyFramer::set_enable_compression_default(false);
+ }
+};
+
+namespace {
+// Test the SpdyIOBuffer class.
+TEST_F(SpdySessionTest, SpdyIOBuffer) {
+ std::priority_queue<SpdyIOBuffer> queue_;
+ const size_t kQueueSize = 100;
+
+ // Insert 100 items; pri 100 to 1.
+ for (size_t index = 0; index < kQueueSize; ++index) {
+ SpdyIOBuffer buffer(new IOBuffer(), 0, kQueueSize - index, NULL);
+ queue_.push(buffer);
+ }
+
+ // Insert several priority 0 items last.
+ const size_t kNumDuplicates = 12;
+ IOBufferWithSize* buffers[kNumDuplicates];
+ for (size_t index = 0; index < kNumDuplicates; ++index) {
+ buffers[index] = new IOBufferWithSize(index+1);
+ queue_.push(SpdyIOBuffer(buffers[index], buffers[index]->size(), 0, NULL));
+ }
+
+ EXPECT_EQ(kQueueSize + kNumDuplicates, queue_.size());
+
+ // Verify the P0 items come out in FIFO order.
+ for (size_t index = 0; index < kNumDuplicates; ++index) {
+ SpdyIOBuffer buffer = queue_.top();
+ EXPECT_EQ(0, buffer.priority());
+ EXPECT_EQ(index + 1, buffer.size());
+ queue_.pop();
+ }
+
+ int priority = 1;
+ while (queue_.size()) {
+ SpdyIOBuffer buffer = queue_.top();
+ EXPECT_EQ(priority++, buffer.priority());
+ queue_.pop();
+ }
+}
+
+TEST_F(SpdySessionTest, GoAway) {
+ SpdySessionDependencies session_deps;
+ session_deps.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(false, OK);
+ scoped_ptr<spdy::SpdyFrame> goaway(ConstructSpdyGoAway());
+ MockRead reads[] = {
+ CreateMockRead(*goaway),
+ MockRead(false, 0, 0) // EOF
+ };
+ StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+ data.set_connect_data(connect_data);
+ session_deps.socket_factory.AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(false, OK);
+ session_deps.socket_factory.AddSSLSocketDataProvider(&ssl);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ const std::string kTestHost("www.foo.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair;
+ test_host_port_pair.host = kTestHost;
+ test_host_port_pair.port = kTestPort;
+
+ scoped_refptr<SpdySessionPool> spdy_session_pool(
+ http_session->spdy_session_pool());
+ EXPECT_FALSE(spdy_session_pool->HasSession(test_host_port_pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(
+ test_host_port_pair, http_session.get(), BoundNetLog());
+ EXPECT_TRUE(spdy_session_pool->HasSession(test_host_port_pair));
+
+ scoped_refptr<TCPSocketParams> tcp_params =
+ new TCPSocketParams(kTestHost, kTestPort, MEDIUM, GURL(), false);
+ int rv = session->Connect(kTestHost, tcp_params, MEDIUM);
+ ASSERT_EQ(OK, rv);
+
+ // Flush the SpdySession::OnReadComplete() task.
+ MessageLoop::current()->RunAllPending();
+
+ EXPECT_FALSE(spdy_session_pool->HasSession(test_host_port_pair));
+
+ scoped_refptr<SpdySession> session2 =
+ spdy_session_pool->Get(
+ test_host_port_pair, http_session.get(), BoundNetLog());
+
+ // Delete the first session.
+ session = NULL;
+
+ // Delete the second session.
+ spdy_session_pool->Remove(session2);
+ session2 = NULL;
+}
+
+} // namespace
+
+TEST_F(SpdySessionTest, GetActivePushStream) {
+ spdy::SpdyFramer framer;
+ SpdySessionTest::TurnOffCompression();
+
+ SpdySessionDependencies session_deps;
+ session_deps.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(false, OK);
+ spdy::SpdyHeaderBlock headers;
+ headers["path"] = "/foo.js";
+ headers["status"] = "200";
+ headers["version"] = "HTTP/1.1";
+ scoped_ptr<spdy::SpdyFrame> push_syn(framer.CreateSynStream(
+ 2, 1, 0, spdy::CONTROL_FLAG_NONE, false, &headers));
+ MockRead reads[] = {
+ CreateMockRead(*push_syn),
+ MockRead(true, ERR_IO_PENDING, 0) // EOF
+ };
+ StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+ data.set_connect_data(connect_data);
+ session_deps.socket_factory.AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(false, OK);
+ session_deps.socket_factory.AddSSLSocketDataProvider(&ssl);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ const std::string kTestHost("www.foo.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair;
+ test_host_port_pair.host = kTestHost;
+ test_host_port_pair.port = kTestPort;
+
+ scoped_refptr<SpdySessionPool> spdy_session_pool(
+ http_session->spdy_session_pool());
+ EXPECT_FALSE(spdy_session_pool->HasSession(test_host_port_pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(
+ test_host_port_pair, http_session.get(), BoundNetLog());
+ EXPECT_TRUE(spdy_session_pool->HasSession(test_host_port_pair));
+
+ // No push streams should exist in the beginning.
+ std::string test_push_path = "/foo.js";
+ scoped_refptr<SpdyStream> first_stream = session->GetActivePushStream(
+ test_push_path);
+ EXPECT_EQ(static_cast<SpdyStream*>(NULL), first_stream.get());
+
+ // Read in the data which contains a server-issued SYN_STREAM.
+ scoped_refptr<TCPSocketParams> tcp_params =
+ new TCPSocketParams(test_host_port_pair, MEDIUM, GURL(), false);
+ int rv = session->Connect(kTestHost, tcp_params, MEDIUM);
+ ASSERT_EQ(OK, rv);
+ MessageLoop::current()->RunAllPending();
+
+ // An unpushed path should not work.
+ scoped_refptr<SpdyStream> unpushed_stream = session->GetActivePushStream(
+ "/unpushed_path");
+ EXPECT_EQ(static_cast<SpdyStream*>(NULL), unpushed_stream.get());
+
+ // The pushed path should be found.
+ scoped_refptr<SpdyStream> second_stream = session->GetActivePushStream(
+ test_push_path);
+ ASSERT_NE(static_cast<SpdyStream*>(NULL), second_stream.get());
+ EXPECT_EQ(test_push_path, second_stream->path());
+ EXPECT_EQ(2U, second_stream->stream_id());
+ EXPECT_EQ(0, second_stream->priority());
+
+ // Clean up
+ second_stream = NULL;
+ session = NULL;
+ spdy_session_pool->CloseAllSessions();
+
+ // RunAllPending needs to be called here because the
+ // ClientSocketPoolBase posts a task to clean up and destroy the
+ // underlying socket.
+ MessageLoop::current()->RunAllPending();
+}
+
+} // namespace net
diff --git a/net/spdy/spdy_settings_storage.cc b/net/spdy/spdy_settings_storage.cc
new file mode 100644
index 0000000..32069b7
--- /dev/null
+++ b/net/spdy/spdy_settings_storage.cc
@@ -0,0 +1,47 @@
+// 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 "net/spdy/spdy_settings_storage.h"
+
+#include <utility>
+
+namespace net {
+
+SpdySettingsStorage::SpdySettingsStorage() {
+}
+
+const spdy::SpdySettings& SpdySettingsStorage::Get(
+ const HostPortPair& host_port_pair) const {
+ SettingsMap::const_iterator it = settings_map_.find(host_port_pair);
+ if (it == settings_map_.end()) {
+ static const spdy::SpdySettings kEmpty;
+ return kEmpty;
+ }
+ return it->second;
+}
+
+void SpdySettingsStorage::Set(const HostPortPair& host_port_pair,
+ const spdy::SpdySettings& settings) {
+ spdy::SpdySettings persistent_settings;
+
+ // Iterate through the list, and only copy those settings which are marked
+ // for persistence.
+ spdy::SpdySettings::const_iterator it;
+ for (it = settings.begin(); it != settings.end(); ++it) {
+ spdy::SettingsFlagsAndId id = it->first;
+ if (id.flags() & spdy::SETTINGS_FLAG_PLEASE_PERSIST) {
+ id.set_flags(spdy::SETTINGS_FLAG_PERSISTED);
+ persistent_settings.push_back(std::make_pair(id, it->second));
+ }
+ }
+
+ // If we didn't persist anything, then we are done.
+ if (persistent_settings.empty())
+ return;
+
+ settings_map_[host_port_pair] = persistent_settings;
+}
+
+} // namespace net
+
diff --git a/net/spdy/spdy_settings_storage.h b/net/spdy/spdy_settings_storage.h
new file mode 100644
index 0000000..93b44dc
--- /dev/null
+++ b/net/spdy/spdy_settings_storage.h
@@ -0,0 +1,40 @@
+// 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.
+
+#ifndef NET_SPDY_SPDY_SETTING_STORAGE_H_
+#define NET_SPDY_SPDY_SETTING_STORAGE_H_
+
+#include <map>
+#include "base/basictypes.h"
+#include "net/base/host_port_pair.h"
+#include "net/spdy/spdy_framer.h"
+
+namespace net {
+
+// SpdySettingsStorage stores SpdySettings which have been transmitted between
+// endpoints for the SPDY SETTINGS frame.
+class SpdySettingsStorage {
+ public:
+ SpdySettingsStorage();
+
+ // Get a copy of the SpdySettings stored for a host.
+ // If no settings are stored, returns an empty set of settings.
+ const spdy::SpdySettings& Get(const HostPortPair& host_port_pair) const;
+
+ // Save settings for a host.
+ void Set(const HostPortPair& host_port_pair,
+ const spdy::SpdySettings& settings);
+
+ private:
+ typedef std::map<HostPortPair, spdy::SpdySettings> SettingsMap;
+
+ SettingsMap settings_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdySettingsStorage);
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_SETTING_STORAGE_H_
+
diff --git a/net/spdy/spdy_stream.cc b/net/spdy/spdy_stream.cc
new file mode 100644
index 0000000..a5f9d1f
--- /dev/null
+++ b/net/spdy/spdy_stream.cc
@@ -0,0 +1,437 @@
+// 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 "net/spdy/spdy_stream.h"
+
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/singleton.h"
+#include "net/spdy/spdy_session.h"
+
+namespace net {
+
+SpdyStream::SpdyStream(
+ SpdySession* session, spdy::SpdyStreamId stream_id, bool pushed)
+ : stream_id_(stream_id),
+ priority_(0),
+ pushed_(pushed),
+ metrics_(Singleton<BandwidthMetrics>::get()),
+ syn_reply_received_(false),
+ session_(session),
+ delegate_(NULL),
+ request_time_(base::Time::Now()),
+ response_(new spdy::SpdyHeaderBlock),
+ response_complete_(false),
+ io_state_(STATE_NONE),
+ response_status_(OK),
+ cancelled_(false),
+ send_bytes_(0),
+ recv_bytes_(0),
+ histograms_recorded_(false) {}
+
+SpdyStream::~SpdyStream() {
+ DLOG(INFO) << "Deleting SpdyStream for stream " << stream_id_;
+
+ // When the stream_id_ is 0, we expect that it is because
+ // we've cancelled or closed the stream and set the stream_id to 0.
+ if (!stream_id_)
+ DCHECK(response_complete_);
+}
+
+void SpdyStream::SetDelegate(Delegate* delegate) {
+ CHECK(delegate);
+ delegate_ = delegate;
+
+ if (!response_->empty()) {
+ // The stream already got response.
+ delegate_->OnResponseReceived(*response_, response_time_, OK);
+ }
+
+ std::vector<scoped_refptr<IOBufferWithSize> > buffers;
+ buffers.swap(pending_buffers_);
+ for (size_t i = 0; i < buffers.size(); ++i) {
+ if (delegate_)
+ delegate_->OnDataReceived(buffers[i]->data(), buffers[i]->size());
+ }
+}
+
+void SpdyStream::DetachDelegate() {
+ delegate_ = NULL;
+ if (!cancelled())
+ Cancel();
+}
+
+const linked_ptr<spdy::SpdyHeaderBlock>& SpdyStream::spdy_headers() const {
+ return request_;
+}
+
+void SpdyStream::set_spdy_headers(
+ const linked_ptr<spdy::SpdyHeaderBlock>& headers) {
+ request_ = headers;
+}
+
+void SpdyStream::UpdateWindowSize(int delta_window_size) {
+ DCHECK_GE(delta_window_size, 1);
+ int new_window_size = window_size_ + delta_window_size;
+
+ // it's valid for window_size_ to become negative (via an incoming
+ // SETTINGS frame which is handled in SpdySession::OnSettings), in which
+ // case incoming WINDOW_UPDATEs will eventually make it positive;
+ // however, if window_size_ is positive and incoming WINDOW_UPDATE makes
+ // it negative, we have an overflow.
+ if (window_size_ > 0 && new_window_size < 0) {
+ LOG(WARNING) << "Received WINDOW_UPDATE [delta:" << delta_window_size
+ << "] for stream " << stream_id_
+ << " overflows window size [current:" << window_size_ << "]";
+ session_->ResetStream(stream_id_, spdy::FLOW_CONTROL_ERROR);
+ return;
+ }
+ window_size_ = new_window_size;
+}
+
+base::Time SpdyStream::GetRequestTime() const {
+ return request_time_;
+}
+
+void SpdyStream::SetRequestTime(base::Time t) {
+ request_time_ = t;
+}
+
+int SpdyStream::OnResponseReceived(const spdy::SpdyHeaderBlock& response) {
+ int rv = OK;
+ LOG(INFO) << "OnResponseReceived";
+ DCHECK_NE(io_state_, STATE_OPEN);
+
+ metrics_.StartStream();
+
+ DCHECK(response_->empty());
+ *response_ = response; // TODO(ukai): avoid copy.
+
+ recv_first_byte_time_ = base::TimeTicks::Now();
+ response_time_ = base::Time::Now();
+
+ if (io_state_ == STATE_NONE) {
+ CHECK(pushed_);
+ io_state_ = STATE_READ_HEADERS;
+ } else if (io_state_ == STATE_READ_HEADERS_COMPLETE) {
+ // This SpdyStream could be in this state in both true and false pushed_
+ // conditions.
+ // The false pushed_ condition (client request) will always go through
+ // this state.
+ // The true pushed_condition (server push) can be in this state when the
+ // client requests an X-Associated-Content piece of content prior
+ // to when the server push happens.
+ } else {
+ // We're not expecting a response while in this state. Error!
+ rv = ERR_SPDY_PROTOCOL_ERROR;
+ }
+
+ rv = DoLoop(rv);
+ if (delegate_)
+ rv = delegate_->OnResponseReceived(*response_, response_time_, rv);
+ // if delegate_ is not yet attached, we'll return response when delegate
+ // gets attached to the stream.
+
+ return rv;
+}
+
+void SpdyStream::OnDataReceived(const char* data, int length) {
+ DCHECK_GE(length, 0);
+ LOG(INFO) << "SpdyStream: Data (" << length << " bytes) received for "
+ << stream_id_;
+
+ CHECK(!response_complete_);
+
+ // If we don't have a response, then the SYN_REPLY did not come through.
+ // We cannot pass data up to the caller unless the reply headers have been
+ // received.
+ if (response_->empty()) {
+ session_->CloseStream(stream_id_, ERR_SYN_REPLY_NOT_RECEIVED);
+ return;
+ }
+
+ // A zero-length read means that the stream is being closed.
+ if (!length) {
+ metrics_.StopStream();
+ scoped_refptr<SpdyStream> self(this);
+ session_->CloseStream(stream_id_, net::OK);
+ UpdateHistograms();
+ return;
+ }
+
+ // Track our bandwidth.
+ metrics_.RecordBytes(length);
+ recv_bytes_ += length;
+ recv_last_byte_time_ = base::TimeTicks::Now();
+
+ if (!delegate_) {
+ // It should be valid for this to happen in the server push case.
+ // We'll return received data when delegate gets attached to the stream.
+ IOBufferWithSize* buf = new IOBufferWithSize(length);
+ memcpy(buf->data(), data, length);
+ pending_buffers_.push_back(buf);
+ return;
+ }
+
+ delegate_->OnDataReceived(data, length);
+}
+
+void SpdyStream::OnWriteComplete(int status) {
+ // TODO(mbelshe): Check for cancellation here. If we're cancelled, we
+ // should discontinue the DoLoop.
+
+ // It is possible that this stream was closed while we had a write pending.
+ if (response_complete_)
+ return;
+
+ if (status > 0)
+ send_bytes_ += status;
+
+ DoLoop(status);
+}
+
+void SpdyStream::OnClose(int status) {
+ response_complete_ = true;
+ response_status_ = status;
+ stream_id_ = 0;
+ Delegate* delegate = delegate_;
+ delegate_ = NULL;
+ if (delegate)
+ delegate->OnClose(status);
+}
+
+void SpdyStream::Cancel() {
+ cancelled_ = true;
+ session_->CloseStream(stream_id_, ERR_ABORTED);
+}
+
+int SpdyStream::DoSendRequest(bool has_upload_data) {
+ CHECK(!cancelled_);
+
+ if (!pushed_) {
+ spdy::SpdyControlFlags flags = spdy::CONTROL_FLAG_NONE;
+ if (!has_upload_data)
+ flags = spdy::CONTROL_FLAG_FIN;
+
+ CHECK(request_.get());
+ int result = session_->WriteSynStream(
+ stream_id_, static_cast<RequestPriority>(priority_), flags,
+ request_);
+ if (result != ERR_IO_PENDING)
+ return result;
+ }
+
+ send_time_ = base::TimeTicks::Now();
+
+ int result = OK;
+ if (!pushed_) {
+ DCHECK_EQ(io_state_, STATE_NONE);
+ io_state_ = STATE_SEND_HEADERS;
+ } else {
+ DCHECK(!has_upload_data);
+ if (!response_->empty()) {
+ // We already have response headers, so we don't need to read the header.
+ // Pushed stream should not have upload data.
+ // We don't need to call DoLoop() in this state.
+ DCHECK_EQ(io_state_, STATE_OPEN);
+ return OK;
+ } else {
+ io_state_ = STATE_READ_HEADERS;
+ }
+ }
+ return DoLoop(result);
+}
+
+int SpdyStream::DoReadResponseHeaders() {
+ CHECK(!cancelled_);
+
+ // The SYN_REPLY has already been received.
+ if (!response_->empty()) {
+ CHECK_EQ(STATE_OPEN, io_state_);
+ return OK;
+ } else {
+ CHECK_EQ(STATE_NONE, io_state_);
+ }
+
+
+ io_state_ = STATE_READ_HEADERS;
+ // Q: do we need to run DoLoop here?
+ return ERR_IO_PENDING;
+}
+
+int SpdyStream::WriteStreamData(IOBuffer* data, int length,
+ spdy::SpdyDataFlags flags) {
+ return session_->WriteStreamData(stream_id_, data, length, flags);
+}
+
+bool SpdyStream::GetSSLInfo(SSLInfo* ssl_info, bool* was_npn_negotiated) {
+ return session_->GetSSLInfo(ssl_info, was_npn_negotiated);
+}
+
+int SpdyStream::DoLoop(int result) {
+ do {
+ State state = io_state_;
+ io_state_ = STATE_NONE;
+ switch (state) {
+ // State machine 1: Send headers and wait for response headers.
+ case STATE_SEND_HEADERS:
+ CHECK_EQ(OK, result);
+ net_log_.BeginEvent(NetLog::TYPE_SPDY_STREAM_SEND_HEADERS, NULL);
+ result = DoSendHeaders();
+ break;
+ case STATE_SEND_HEADERS_COMPLETE:
+ net_log_.EndEvent(NetLog::TYPE_SPDY_STREAM_SEND_HEADERS, NULL);
+ result = DoSendHeadersComplete(result);
+ break;
+ case STATE_SEND_BODY:
+ CHECK_EQ(OK, result);
+ net_log_.BeginEvent(NetLog::TYPE_SPDY_STREAM_SEND_BODY, NULL);
+ result = DoSendBody();
+ break;
+ case STATE_SEND_BODY_COMPLETE:
+ net_log_.EndEvent(NetLog::TYPE_SPDY_STREAM_SEND_BODY, NULL);
+ result = DoSendBodyComplete(result);
+ break;
+ case STATE_READ_HEADERS:
+ CHECK_EQ(OK, result);
+ net_log_.BeginEvent(NetLog::TYPE_SPDY_STREAM_READ_HEADERS, NULL);
+ result = DoReadHeaders();
+ break;
+ case STATE_READ_HEADERS_COMPLETE:
+ net_log_.EndEvent(NetLog::TYPE_SPDY_STREAM_READ_HEADERS, NULL);
+ result = DoReadHeadersComplete(result);
+ break;
+
+ // State machine 2: connection is established.
+ // In STATE_OPEN, OnResponseReceived has already been called.
+ // OnDataReceived, OnClose and OnWriteCompelte can be called.
+ // Only OnWriteCompletee calls DoLoop(().
+ //
+ // For HTTP streams, no data is sent from the client while in the OPEN
+ // state, so OnWriteComplete is never called here. The HTTP body is
+ // handled in the OnDataReceived callback, which does not call into
+ // DoLoop.
+ //
+ // For WebSocket streams, which are bi-directional, we'll send and
+ // receive data once the connection is established. Received data is
+ // handled in OnDataReceived. Sent data is handled in OnWriteComplete,
+ // which calls DoOpen().
+ case STATE_OPEN:
+ result = DoOpen(result);
+ break;
+
+ case STATE_DONE:
+ DCHECK(result != ERR_IO_PENDING);
+ break;
+ default:
+ NOTREACHED() << io_state_;
+ break;
+ }
+ } while (result != ERR_IO_PENDING && io_state_ != STATE_NONE &&
+ io_state_ != STATE_OPEN);
+
+ return result;
+}
+
+int SpdyStream::DoSendHeaders() {
+ // The SpdySession will always call us back when the send is complete.
+ // TODO(willchan): This code makes the assumption that for the non-push stream
+ // case, the client code calls SendRequest() after creating the stream and
+ // before yielding back to the MessageLoop. This is true in the current code,
+ // but is not obvious from the headers. We should make the code handle
+ // SendRequest() being called after the SYN_REPLY has been received.
+ io_state_ = STATE_SEND_HEADERS_COMPLETE;
+ return ERR_IO_PENDING;
+}
+
+int SpdyStream::DoSendHeadersComplete(int result) {
+ if (result < 0)
+ return result;
+
+ CHECK_GT(result, 0);
+
+ if (!delegate_)
+ return ERR_UNEXPECTED;
+
+ // There is no body, skip that state.
+ if (delegate_->OnSendHeadersComplete(result)) {
+ io_state_ = STATE_READ_HEADERS;
+ return OK;
+ }
+
+ io_state_ = STATE_SEND_BODY;
+ return OK;
+}
+
+// DoSendBody is called to send the optional body for the request. This call
+// will also be called as each write of a chunk of the body completes.
+int SpdyStream::DoSendBody() {
+ // If we're already in the STATE_SENDING_BODY state, then we've already
+ // sent a portion of the body. In that case, we need to first consume
+ // the bytes written in the body stream. Note that the bytes written is
+ // the number of bytes in the frame that were written, only consume the
+ // data portion, of course.
+ io_state_ = STATE_SEND_BODY_COMPLETE;
+ if (!delegate_)
+ return ERR_UNEXPECTED;
+ return delegate_->OnSendBody();
+}
+
+int SpdyStream::DoSendBodyComplete(int result) {
+ if (result < 0)
+ return result;
+
+ CHECK_NE(result, 0);
+
+ if (!delegate_)
+ return ERR_UNEXPECTED;
+
+ if (!delegate_->OnSendBodyComplete(result))
+ io_state_ = STATE_SEND_BODY;
+ else
+ io_state_ = STATE_READ_HEADERS;
+
+ return OK;
+}
+
+int SpdyStream::DoReadHeaders() {
+ io_state_ = STATE_READ_HEADERS_COMPLETE;
+ return !response_->empty() ? OK : ERR_IO_PENDING;
+}
+
+int SpdyStream::DoReadHeadersComplete(int result) {
+ io_state_ = STATE_OPEN;
+ return result;
+}
+
+int SpdyStream::DoOpen(int result) {
+ if (delegate_)
+ delegate_->OnDataSent(result);
+ io_state_ = STATE_OPEN;
+ return result;
+}
+
+void SpdyStream::UpdateHistograms() {
+ if (histograms_recorded_)
+ return;
+
+ histograms_recorded_ = true;
+
+ // We need all timers to be filled in, otherwise metrics can be bogus.
+ if (send_time_.is_null() || recv_first_byte_time_.is_null() ||
+ recv_last_byte_time_.is_null())
+ return;
+
+ UMA_HISTOGRAM_TIMES("Net.SpdyStreamTimeToFirstByte",
+ recv_first_byte_time_ - send_time_);
+ UMA_HISTOGRAM_TIMES("Net.SpdyStreamDownloadTime",
+ recv_last_byte_time_ - recv_first_byte_time_);
+ UMA_HISTOGRAM_TIMES("Net.SpdyStreamTime",
+ recv_last_byte_time_ - send_time_);
+
+ UMA_HISTOGRAM_COUNTS("Net.SpdySendBytes", send_bytes_);
+ UMA_HISTOGRAM_COUNTS("Net.SpdyRecvBytes", recv_bytes_);
+}
+
+} // namespace net
diff --git a/net/spdy/spdy_stream.h b/net/spdy/spdy_stream.h
new file mode 100644
index 0000000..13a6644
--- /dev/null
+++ b/net/spdy/spdy_stream.h
@@ -0,0 +1,256 @@
+// 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.
+
+#ifndef NET_SPDY_SPDY_STREAM_H_
+#define NET_SPDY_SPDY_STREAM_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/linked_ptr.h"
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "net/base/bandwidth_metrics.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_log.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace net {
+
+class SpdySession;
+class SSLInfo;
+
+// The SpdyStream is used by the SpdySession to represent each stream known
+// on the SpdySession. This class provides interfaces for SpdySession to use.
+// Streams can be created either by the client or by the server. When they
+// are initiated by the client, both the SpdySession and client object (such as
+// a SpdyNetworkTransaction) will maintain a reference to the stream. When
+// initiated by the server, only the SpdySession will maintain any reference,
+// until such a time as a client object requests a stream for the path.
+class SpdyStream : public base::RefCounted<SpdyStream> {
+ public:
+ // Delegate handles protocol specific behavior of spdy stream.
+ class Delegate {
+ public:
+ Delegate() {}
+
+ // Called when SYN frame has been sent.
+ // Returns true if no more data to be sent after SYN frame.
+ virtual bool OnSendHeadersComplete(int status) = 0;
+
+ // Called when stream is ready to send data.
+ // Returns network error code. OK when it successfully sent data.
+ virtual int OnSendBody() = 0;
+
+ // Called when data has been sent. |status| indicates network error
+ // or number of bytes has been sent.
+ // Returns true if no more data to be sent.
+ virtual bool OnSendBodyComplete(int status) = 0;
+
+ // Called when SYN_REPLY received. |status| indicates network error.
+ // Returns network error code.
+ virtual int OnResponseReceived(const spdy::SpdyHeaderBlock& response,
+ base::Time response_time,
+ int status) = 0;
+
+ // Called when data is received.
+ virtual void OnDataReceived(const char* data, int length) = 0;
+
+ // Called when data is sent.
+ virtual void OnDataSent(int length) = 0;
+
+ // Called when SpdyStream is closed.
+ virtual void OnClose(int status) = 0;
+
+ protected:
+ friend class base::RefCounted<Delegate>;
+ virtual ~Delegate() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Delegate);
+ };
+
+ // SpdyStream constructor
+ SpdyStream(SpdySession* session, spdy::SpdyStreamId stream_id, bool pushed);
+
+ // Set new |delegate|. |delegate| must not be NULL.
+ // If it already received SYN_REPLY or data, OnResponseReceived() or
+ // OnDataReceived() will be called.
+ void SetDelegate(Delegate* delegate);
+ Delegate* GetDelegate() { return delegate_; }
+
+ // Detach delegate from the stream. It will cancel the stream if it was not
+ // cancelled yet. It is safe to call multiple times.
+ void DetachDelegate();
+
+ // Is this stream a pushed stream from the server.
+ bool pushed() const { return pushed_; }
+
+ spdy::SpdyStreamId stream_id() const { return stream_id_; }
+ void set_stream_id(spdy::SpdyStreamId stream_id) { stream_id_ = stream_id; }
+
+ bool syn_reply_received() const { return syn_reply_received_; }
+ void set_syn_reply_received() { syn_reply_received_ = true; }
+
+ // For pushed streams, we track a path to identify them.
+ const std::string& path() const { return path_; }
+ void set_path(const std::string& path) { path_ = path; }
+
+ int priority() const { return priority_; }
+ void set_priority(int priority) { priority_ = priority; }
+
+ int window_size() const { return window_size_; }
+ void set_window_size(int window_size) { window_size_ = window_size; }
+
+ // Updates |window_size_| with delta extracted from a WINDOW_UPDATE
+ // frame; sends a RST_STREAM if delta overflows |window_size_| and
+ // removes the stream from the session.
+ void UpdateWindowSize(int delta_window_size);
+
+ const BoundNetLog& net_log() const { return net_log_; }
+ void set_net_log(const BoundNetLog& log) { net_log_ = log; }
+
+ const linked_ptr<spdy::SpdyHeaderBlock>& spdy_headers() const;
+ void set_spdy_headers(const linked_ptr<spdy::SpdyHeaderBlock>& headers);
+ base::Time GetRequestTime() const;
+ void SetRequestTime(base::Time t);
+
+ // Called by the SpdySession when a response (e.g. a SYN_REPLY) has been
+ // received for this stream. |path| is the path of the URL for a server
+ // initiated stream, otherwise is empty.
+ // Returns a status code.
+ int OnResponseReceived(const spdy::SpdyHeaderBlock& response);
+
+ // Called by the SpdySession when response data has been received for this
+ // stream. This callback may be called multiple times as data arrives
+ // from the network, and will never be called prior to OnResponseReceived.
+ // |buffer| contains the data received. The stream must copy any data
+ // from this buffer before returning from this callback.
+ // |length| is the number of bytes received or an error.
+ // A zero-length count does not indicate end-of-stream.
+ void OnDataReceived(const char* buffer, int bytes);
+
+ // Called by the SpdySession when a write has completed. This callback
+ // will be called multiple times for each write which completes. Writes
+ // include the SYN_STREAM write and also DATA frame writes.
+ // |result| is the number of bytes written or a net error code.
+ void OnWriteComplete(int status);
+
+ // Called by the SpdySession when the request is finished. This callback
+ // will always be called at the end of the request and signals to the
+ // stream that the stream has no more network events. No further callbacks
+ // to the stream will be made after this call.
+ // |status| is an error code or OK.
+ void OnClose(int status);
+
+ void Cancel();
+ bool cancelled() const { return cancelled_; }
+
+ // Interface for Spdy[Http|WebSocket]Stream to use.
+
+ // Sends the request.
+ // For non push stream, it will send SYN_STREAM frame.
+ int DoSendRequest(bool has_upload_data);
+
+ // Reads response headers. If the SpdyStream have already received
+ // the response headers, return OK and response headers filled in
+ // |response| given in SendRequest.
+ // Otherwise, return ERR_IO_PENDING and OnResponseReceived() will be called.
+ int DoReadResponseHeaders();
+
+ // Sends DATA frame.
+ int WriteStreamData(IOBuffer* data, int length,
+ spdy::SpdyDataFlags flags);
+
+ bool GetSSLInfo(SSLInfo* ssl_info, bool* was_npn_negotiated);
+
+ bool is_idle() const {
+ return io_state_ == STATE_NONE || io_state_ == STATE_OPEN;
+ }
+ bool response_complete() const { return response_complete_; }
+ int response_status() const { return response_status_; }
+
+ private:
+ enum State {
+ STATE_NONE,
+ STATE_SEND_HEADERS,
+ STATE_SEND_HEADERS_COMPLETE,
+ STATE_SEND_BODY,
+ STATE_SEND_BODY_COMPLETE,
+ STATE_READ_HEADERS,
+ STATE_READ_HEADERS_COMPLETE,
+ STATE_OPEN,
+ STATE_DONE
+ };
+
+ friend class base::RefCounted<SpdyStream>;
+ virtual ~SpdyStream();
+
+ // Try to make progress sending/receiving the request/response.
+ int DoLoop(int result);
+
+ // The implementations of each state of the state machine.
+ int DoSendHeaders();
+ int DoSendHeadersComplete(int result);
+ int DoSendBody();
+ int DoSendBodyComplete(int result);
+ int DoReadHeaders();
+ int DoReadHeadersComplete(int result);
+ int DoOpen(int result);
+
+ // Update the histograms. Can safely be called repeatedly, but should only
+ // be called after the stream has completed.
+ void UpdateHistograms();
+
+ spdy::SpdyStreamId stream_id_;
+ std::string path_;
+ int priority_;
+ int window_size_;
+ const bool pushed_;
+ ScopedBandwidthMetrics metrics_;
+ bool syn_reply_received_;
+
+ scoped_refptr<SpdySession> session_;
+
+ // The transaction should own the delegate.
+ SpdyStream::Delegate* delegate_;
+
+ // The request to send.
+ linked_ptr<spdy::SpdyHeaderBlock> request_;
+
+ // The time at which the request was made that resulted in this response.
+ // For cached responses, this time could be "far" in the past.
+ base::Time request_time_;
+
+ linked_ptr<spdy::SpdyHeaderBlock> response_;
+ base::Time response_time_;
+
+ bool response_complete_; // TODO(mbelshe): fold this into the io_state.
+ State io_state_;
+
+ // Since we buffer the response, we also buffer the response status.
+ // Not valid until response_complete_ is true.
+ int response_status_;
+
+ bool cancelled_;
+
+ BoundNetLog net_log_;
+
+ base::TimeTicks send_time_;
+ base::TimeTicks recv_first_byte_time_;
+ base::TimeTicks recv_last_byte_time_;
+ int send_bytes_;
+ int recv_bytes_;
+ bool histograms_recorded_;
+ // Data received before delegate is attached.
+ std::vector<scoped_refptr<IOBufferWithSize> > pending_buffers_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyStream);
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_STREAM_H_
diff --git a/net/spdy/spdy_stream_unittest.cc b/net/spdy/spdy_stream_unittest.cc
new file mode 100644
index 0000000..4daad96
--- /dev/null
+++ b/net/spdy/spdy_stream_unittest.cc
@@ -0,0 +1,232 @@
+// 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 "net/spdy/spdy_stream.h"
+#include "base/ref_counted.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+// TODO(ukai): factor out common part with spdy_http_stream_unittest.cc
+class SpdySessionPoolPeer {
+ public:
+ explicit SpdySessionPoolPeer(const scoped_refptr<SpdySessionPool>& pool)
+ : pool_(pool) {}
+
+ void RemoveSpdySession(const scoped_refptr<SpdySession>& session) {
+ pool_->Remove(session);
+ }
+
+ private:
+ const scoped_refptr<SpdySessionPool> pool_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdySessionPoolPeer);
+};
+
+namespace {
+
+// Create a proxy service which fails on all requests (falls back to direct).
+ProxyService* CreateNullProxyService() {
+ return ProxyService::CreateNull();
+}
+
+class TestSpdyStreamDelegate : public SpdyStream::Delegate {
+ public:
+ TestSpdyStreamDelegate(SpdyStream* stream,
+ IOBufferWithSize* buf,
+ CompletionCallback* callback)
+ : stream_(stream),
+ buf_(buf),
+ callback_(callback),
+ send_headers_completed_(false),
+ response_(new spdy::SpdyHeaderBlock),
+ data_sent_(0),
+ closed_(false) {}
+ virtual ~TestSpdyStreamDelegate() {}
+
+ virtual bool OnSendHeadersComplete(int status) {
+ send_headers_completed_ = true;
+ return true;
+ }
+ virtual int OnSendBody() {
+ ADD_FAILURE() << "OnSendBody should not be called";
+ return ERR_UNEXPECTED;
+ }
+ virtual bool OnSendBodyComplete(int status) {
+ ADD_FAILURE() << "OnSendBodyComplete should not be called";
+ return true;
+ }
+ virtual int OnResponseReceived(const spdy::SpdyHeaderBlock& response,
+ base::Time response_time,
+ int status) {
+ EXPECT_TRUE(send_headers_completed_);
+ *response_ = response;
+ if (buf_) {
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream_->WriteStreamData(buf_.get(), buf_->size(),
+ spdy::DATA_FLAG_NONE));
+ }
+ return status;
+ }
+ virtual void OnDataReceived(const char* buffer, int bytes) {
+ received_data_ += std::string(buffer, bytes);
+ }
+ virtual void OnDataSent(int length) {
+ data_sent_ += length;
+ }
+ virtual void OnClose(int status) {
+ closed_ = true;
+ CompletionCallback* callback = callback_;
+ callback_ = NULL;
+ callback->Run(OK);
+ }
+
+ bool send_headers_completed() const { return send_headers_completed_; }
+ const linked_ptr<spdy::SpdyHeaderBlock>& response() const {
+ return response_;
+ }
+ const std::string& received_data() const { return received_data_; }
+ int data_sent() const { return data_sent_; }
+ bool closed() const { return closed_; }
+
+ private:
+ SpdyStream* stream_;
+ scoped_refptr<IOBufferWithSize> buf_;
+ CompletionCallback* callback_;
+ bool send_headers_completed_;
+ linked_ptr<spdy::SpdyHeaderBlock> response_;
+ std::string received_data_;
+ int data_sent_;
+ bool closed_;
+};
+
+spdy::SpdyFrame* ConstructSpdyBodyFrame(const char* data, int length) {
+ spdy::SpdyFramer framer;
+ return framer.CreateDataFrame(1, data, length, spdy::DATA_FLAG_NONE);
+}
+
+} // anonymous namespace
+
+class SpdyStreamTest : public testing::Test {
+ protected:
+ SpdyStreamTest() {
+ }
+
+ scoped_refptr<SpdySession> CreateSpdySession() {
+ spdy::SpdyFramer::set_enable_compression_default(false);
+ HostPortPair host_port_pair("www.google.com", 80);
+ scoped_refptr<SpdySession> session(
+ session_->spdy_session_pool()->Get(
+ host_port_pair, session_, BoundNetLog()));
+ return session;
+ }
+
+ virtual void TearDown() {
+ MessageLoop::current()->RunAllPending();
+ }
+
+ scoped_refptr<HttpNetworkSession> session_;
+};
+
+TEST_F(SpdyStreamTest, SendDataAfterOpen) {
+ SpdySessionDependencies session_deps;
+
+ session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps);
+ SpdySessionPoolPeer pool_peer_(session_->spdy_session_pool());
+
+ const SpdyHeaderInfo kSynStartHeader = {
+ spdy::SYN_STREAM,
+ 1,
+ 0,
+ SPDY_PRIORITY_LOWEST,
+ spdy::CONTROL_FLAG_NONE,
+ false,
+ spdy::INVALID,
+ NULL,
+ 0,
+ spdy::DATA_FLAG_NONE
+ };
+ static const char* const kGetHeaders[] = {
+ "method",
+ "GET",
+ "url",
+ "http://www.google.com/",
+ "version",
+ "HTTP/1.1",
+ };
+ scoped_ptr<spdy::SpdyFrame> req(
+ ConstructSpdyPacket(
+ kSynStartHeader, NULL, 0, kGetHeaders, arraysize(kGetHeaders) / 2));
+ scoped_ptr<spdy::SpdyFrame> msg(
+ ConstructSpdyBodyFrame("\0hello!\xff", 8));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*msg),
+ };
+ writes[0].sequence_number = 0;
+ writes[1].sequence_number = 2;
+
+ scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<spdy::SpdyFrame> echo(
+ ConstructSpdyBodyFrame("\0hello!\xff", 8));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*echo),
+ MockRead(true, 0, 0), // EOF
+ };
+ reads[0].sequence_number = 1;
+ reads[1].sequence_number = 3;
+ reads[2].sequence_number = 4;
+
+ scoped_refptr<OrderedSocketData> data(
+ new OrderedSocketData(reads, arraysize(reads),
+ writes, arraysize(writes)));
+ MockConnect connect_data(false, OK);
+ data->set_connect_data(connect_data);
+
+ session_deps.socket_factory.AddSocketDataProvider(data.get());
+ SpdySession::SetSSLMode(false);
+
+ scoped_refptr<SpdySession> session(CreateSpdySession());
+ GURL url("http://www.google.com/");
+
+ HostPortPair host_port_pair("www.google.com", 80);
+ scoped_refptr<TCPSocketParams> tcp_params =
+ new TCPSocketParams(host_port_pair, LOWEST, GURL(), false);
+ EXPECT_EQ(OK, session->Connect("spdy.www.google.com", tcp_params,
+ LOWEST));
+
+ scoped_refptr<SpdyStream> stream;
+ ASSERT_EQ(
+ OK,
+ session->CreateStream(url, LOWEST, &stream, BoundNetLog(), NULL));
+ scoped_refptr<IOBufferWithSize> buf(new IOBufferWithSize(8));
+ memcpy(buf->data(), "\0hello!\xff", 8);
+ TestCompletionCallback callback;
+
+ scoped_ptr<TestSpdyStreamDelegate> delegate(
+ new TestSpdyStreamDelegate(stream.get(), buf.get(), &callback));
+ stream->SetDelegate(delegate.get());
+
+ linked_ptr<spdy::SpdyHeaderBlock> headers(new spdy::SpdyHeaderBlock);
+ (*headers)["method"] = "GET";
+ (*headers)["url"] = "http://www.google.com/";
+ (*headers)["version"] = "HTTP/1.1";
+ stream->set_spdy_headers(headers);
+
+ EXPECT_EQ(ERR_IO_PENDING, stream->DoSendRequest(true));
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ EXPECT_TRUE(delegate->send_headers_completed());
+ EXPECT_EQ("200", (*delegate->response())["status"]);
+ EXPECT_EQ("HTTP/1.1", (*delegate->response())["version"]);
+ EXPECT_EQ(std::string("\0hello!\xff", 8), delegate->received_data());
+ EXPECT_EQ(8, delegate->data_sent());
+ EXPECT_TRUE(delegate->closed());
+}
+
+} // namespace net
diff --git a/net/spdy/spdy_test_util.cc b/net/spdy/spdy_test_util.cc
new file mode 100644
index 0000000..99af4f8
--- /dev/null
+++ b/net/spdy/spdy_test_util.cc
@@ -0,0 +1,523 @@
+// 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 "net/spdy/spdy_test_util.h"
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/string_util.h"
+
+namespace net {
+
+// Chop a frame into an array of MockWrites.
+// |data| is the frame to chop.
+// |length| is the length of the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockWrite* ChopWriteFrame(const char* data, int length, int num_chunks) {
+ MockWrite* chunks = new MockWrite[num_chunks];
+ int chunk_size = length / num_chunks;
+ for (int index = 0; index < num_chunks; index++) {
+ const char* ptr = data + (index * chunk_size);
+ if (index == num_chunks - 1)
+ chunk_size += length % chunk_size; // The last chunk takes the remainder.
+ chunks[index] = MockWrite(true, ptr, chunk_size);
+ }
+ return chunks;
+}
+
+// Chop a SpdyFrame into an array of MockWrites.
+// |frame| is the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockWrite* ChopWriteFrame(const spdy::SpdyFrame& frame, int num_chunks) {
+ return ChopWriteFrame(frame.data(),
+ frame.length() + spdy::SpdyFrame::size(),
+ num_chunks);
+}
+
+// Chop a frame into an array of MockReads.
+// |data| is the frame to chop.
+// |length| is the length of the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockRead* ChopReadFrame(const char* data, int length, int num_chunks) {
+ MockRead* chunks = new MockRead[num_chunks];
+ int chunk_size = length / num_chunks;
+ for (int index = 0; index < num_chunks; index++) {
+ const char* ptr = data + (index * chunk_size);
+ if (index == num_chunks - 1)
+ chunk_size += length % chunk_size; // The last chunk takes the remainder.
+ chunks[index] = MockRead(true, ptr, chunk_size);
+ }
+ return chunks;
+}
+
+// Chop a SpdyFrame into an array of MockReads.
+// |frame| is the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockRead* ChopReadFrame(const spdy::SpdyFrame& frame, int num_chunks) {
+ return ChopReadFrame(frame.data(),
+ frame.length() + spdy::SpdyFrame::size(),
+ num_chunks);
+}
+
+// Adds headers and values to a map.
+// |extra_headers| is an array of { name, value } pairs, arranged as strings
+// where the even entries are the header names, and the odd entries are the
+// header values.
+// |headers| gets filled in from |extra_headers|.
+void AppendHeadersToSpdyFrame(const char* const extra_headers[],
+ int extra_header_count,
+ spdy::SpdyHeaderBlock* headers) {
+ std::string this_header;
+ std::string this_value;
+
+ if (!extra_header_count)
+ return;
+
+ // Sanity check: Non-NULL header list.
+ DCHECK(NULL != extra_headers) << "NULL header value pair list";
+ // Sanity check: Non-NULL header map.
+ DCHECK(NULL != headers) << "NULL header map";
+ // Copy in the headers.
+ for (int i = 0; i < extra_header_count; i++) {
+ // Sanity check: Non-empty header.
+ DCHECK_NE('\0', *extra_headers[i * 2]) << "Empty header value pair";
+ this_header = extra_headers[i * 2];
+ std::string::size_type header_len = this_header.length();
+ if (!header_len)
+ continue;
+ this_value = extra_headers[1 + (i * 2)];
+ std::string new_value;
+ if (headers->find(this_header) != headers->end()) {
+ // More than one entry in the header.
+ // Don't add the header again, just the append to the value,
+ // separated by a NULL character.
+
+ // Adjust the value.
+ new_value = (*headers)[this_header];
+ // Put in a NULL separator.
+ new_value.append(1, '\0');
+ // Append the new value.
+ new_value += this_value;
+ } else {
+ // Not a duplicate, just write the value.
+ new_value = this_value;
+ }
+ (*headers)[this_header] = new_value;
+ }
+}
+
+// Writes |val| to a location of size |len|, in big-endian format.
+// in the buffer pointed to by |buffer_handle|.
+// Updates the |*buffer_handle| pointer by |len|
+// Returns the number of bytes written
+int AppendToBuffer(int val,
+ int len,
+ unsigned char** buffer_handle,
+ int* buffer_len_remaining) {
+ if (len <= 0)
+ return 0;
+ DCHECK((size_t) len <= sizeof(len)) << "Data length too long for data type";
+ DCHECK(NULL != buffer_handle) << "NULL buffer handle";
+ DCHECK(NULL != *buffer_handle) << "NULL pointer";
+ DCHECK(NULL != buffer_len_remaining)
+ << "NULL buffer remainder length pointer";
+ DCHECK_GE(*buffer_len_remaining, len) << "Insufficient buffer size";
+ for (int i = 0; i < len; i++) {
+ int shift = (8 * (len - (i + 1)));
+ unsigned char val_chunk = (val >> shift) & 0x0FF;
+ *(*buffer_handle)++ = val_chunk;
+ *buffer_len_remaining += 1;
+ }
+ return len;
+}
+
+// Construct a SPDY packet.
+// |head| is the start of the packet, up to but not including
+// the header value pairs.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// |tail| is any (relatively constant) header-value pairs to add.
+// |buffer| is the buffer we're filling in.
+// Returns a SpdyFrame.
+spdy::SpdyFrame* ConstructSpdyPacket(const SpdyHeaderInfo& header_info,
+ const char* const extra_headers[],
+ int extra_header_count,
+ const char* const tail[],
+ int tail_header_count) {
+ spdy::SpdyFramer framer;
+ spdy::SpdyHeaderBlock headers;
+ // Copy in the extra headers to our map.
+ AppendHeadersToSpdyFrame(extra_headers, extra_header_count, &headers);
+ // Copy in the tail headers to our map.
+ if (tail && tail_header_count)
+ AppendHeadersToSpdyFrame(tail, tail_header_count, &headers);
+ spdy::SpdyFrame* frame = NULL;
+ switch (header_info.kind) {
+ case spdy::SYN_STREAM:
+ frame = framer.CreateSynStream(header_info.id, header_info.assoc_id,
+ header_info.priority,
+ header_info.control_flags,
+ header_info.compressed, &headers);
+ break;
+ case spdy::SYN_REPLY:
+ frame = framer.CreateSynReply(header_info.id, header_info.control_flags,
+ header_info.compressed, &headers);
+ break;
+ case spdy::RST_STREAM:
+ frame = framer.CreateRstStream(header_info.id, header_info.status);
+ break;
+ default:
+ frame = framer.CreateDataFrame(header_info.id, header_info.data,
+ header_info.data_length,
+ header_info.data_flags);
+ break;
+ }
+ return frame;
+}
+
+// Construct an expected SPDY SETTINGS frame.
+// |settings| are the settings to set.
+// Returns the constructed frame. The caller takes ownership of the frame.
+spdy::SpdyFrame* ConstructSpdySettings(spdy::SpdySettings settings) {
+ spdy::SpdyFramer framer;
+ return framer.CreateSettings(settings);
+}
+
+// Construct a SPDY GOAWAY frame.
+// Returns the constructed frame. The caller takes ownership of the frame.
+spdy::SpdyFrame* ConstructSpdyGoAway() {
+ spdy::SpdyFramer framer;
+ return framer.CreateGoAway(0);
+}
+
+// Construct a SPDY WINDOW_UPDATE frame.
+// Returns the constructed frame. The caller takes ownership of the frame.
+spdy::SpdyFrame* ConstructSpdyWindowUpdate(
+ const spdy::SpdyStreamId stream_id, uint32 delta_window_size) {
+ spdy::SpdyFramer framer;
+ return framer.CreateWindowUpdate(stream_id, delta_window_size);
+}
+
+// Construct a SPDY RST_STREAM frame.
+// Returns the constructed frame. The caller takes ownership of the frame.
+spdy::SpdyFrame* ConstructSpdyRstStream(spdy::SpdyStreamId stream_id,
+ spdy::SpdyStatusCodes status) {
+ spdy::SpdyFramer framer;
+ return framer.CreateRstStream(stream_id, status);
+}
+
+// Construct a single SPDY header entry, for validation.
+// |extra_headers| are the extra header-value pairs.
+// |buffer| is the buffer we're filling in.
+// |index| is the index of the header we want.
+// Returns the number of bytes written into |buffer|.
+int ConstructSpdyHeader(const char* const extra_headers[],
+ int extra_header_count,
+ char* buffer,
+ int buffer_length,
+ int index) {
+ const char* this_header = NULL;
+ const char* this_value = NULL;
+ if (!buffer || !buffer_length)
+ return 0;
+ *buffer = '\0';
+ // Sanity check: Non-empty header list.
+ DCHECK(NULL != extra_headers) << "NULL extra headers pointer";
+ // Sanity check: Index out of range.
+ DCHECK((index >= 0) && (index < extra_header_count))
+ << "Index " << index
+ << " out of range [0, " << extra_header_count << ")";
+ this_header = extra_headers[index * 2];
+ // Sanity check: Non-empty header.
+ if (!*this_header)
+ return 0;
+ std::string::size_type header_len = strlen(this_header);
+ if (!header_len)
+ return 0;
+ this_value = extra_headers[1 + (index * 2)];
+ // Sanity check: Non-empty value.
+ if (!*this_value)
+ this_value = "";
+ int n = base::snprintf(buffer,
+ buffer_length,
+ "%s: %s\r\n",
+ this_header,
+ this_value);
+ return n;
+}
+
+// Constructs a standard SPDY GET SYN packet, optionally compressed.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+spdy::SpdyFrame* ConstructSpdyGet(const char* const extra_headers[],
+ int extra_header_count,
+ bool compressed,
+ int stream_id,
+ RequestPriority request_priority) {
+ const SpdyHeaderInfo kSynStartHeader = {
+ spdy::SYN_STREAM, // Kind = Syn
+ stream_id, // Stream ID
+ 0, // Associated stream ID
+ request_priority, // Priority
+ spdy::CONTROL_FLAG_FIN, // Control Flags
+ compressed, // Compressed
+ spdy::INVALID, // Status
+ NULL, // Data
+ 0, // Length
+ spdy::DATA_FLAG_NONE // Data Flags
+ };
+ static const char* const kStandardGetHeaders[] = {
+ "method",
+ "GET",
+ "url",
+ "http://www.google.com/",
+ "version",
+ "HTTP/1.1"
+ };
+ return ConstructSpdyPacket(
+ kSynStartHeader,
+ extra_headers,
+ extra_header_count,
+ kStandardGetHeaders,
+ arraysize(kStandardGetHeaders) / 2);
+}
+
+// Constructs a standard SPDY SYN_REPLY packet to match the SPDY GET.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+spdy::SpdyFrame* ConstructSpdyGetSynReply(const char* const extra_headers[],
+ int extra_header_count,
+ int stream_id) {
+ const SpdyHeaderInfo kSynStartHeader = {
+ spdy::SYN_REPLY, // Kind = SynReply
+ stream_id, // Stream ID
+ 0, // Associated stream ID
+ SPDY_PRIORITY_LOWEST, // Priority
+ spdy::CONTROL_FLAG_NONE, // Control Flags
+ false, // Compressed
+ spdy::INVALID, // Status
+ NULL, // Data
+ 0, // Length
+ spdy::DATA_FLAG_NONE // Data Flags
+ };
+ static const char* const kStandardGetHeaders[] = {
+ "hello",
+ "bye",
+ "status",
+ "200",
+ "url",
+ "/index.php",
+ "version",
+ "HTTP/1.1"
+ };
+ return ConstructSpdyPacket(
+ kSynStartHeader,
+ extra_headers,
+ extra_header_count,
+ kStandardGetHeaders,
+ arraysize(kStandardGetHeaders) / 2);
+}
+
+// Constructs a standard SPDY POST SYN packet.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+spdy::SpdyFrame* ConstructSpdyPost(const char* const extra_headers[],
+ int extra_header_count) {
+ const SpdyHeaderInfo kSynStartHeader = {
+ spdy::SYN_STREAM, // Kind = Syn
+ 1, // Stream ID
+ 0, // Associated stream ID
+ SPDY_PRIORITY_LOWEST, // Priority
+ spdy::CONTROL_FLAG_NONE, // Control Flags
+ false, // Compressed
+ spdy::INVALID, // Status
+ NULL, // Data
+ 0, // Length
+ spdy::DATA_FLAG_NONE // Data Flags
+ };
+ static const char* const kStandardGetHeaders[] = {
+ "method",
+ "POST",
+ "url",
+ "http://www.google.com/",
+ "version",
+ "HTTP/1.1"
+ };
+ return ConstructSpdyPacket(
+ kSynStartHeader,
+ extra_headers,
+ extra_header_count,
+ kStandardGetHeaders,
+ arraysize(kStandardGetHeaders) / 2);
+}
+
+// Constructs a standard SPDY SYN_REPLY packet to match the SPDY POST.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+spdy::SpdyFrame* ConstructSpdyPostSynReply(const char* const extra_headers[],
+ int extra_header_count) {
+ const SpdyHeaderInfo kSynStartHeader = {
+ spdy::SYN_REPLY, // Kind = SynReply
+ 1, // Stream ID
+ 0, // Associated stream ID
+ SPDY_PRIORITY_LOWEST, // Priority
+ spdy::CONTROL_FLAG_NONE, // Control Flags
+ false, // Compressed
+ spdy::INVALID, // Status
+ NULL, // Data
+ 0, // Length
+ spdy::DATA_FLAG_NONE // Data Flags
+ };
+ static const char* const kStandardGetHeaders[] = {
+ "hello",
+ "bye",
+ "status",
+ "200",
+ "url",
+ "/index.php",
+ "version",
+ "HTTP/1.1"
+ };
+ return ConstructSpdyPacket(
+ kSynStartHeader,
+ extra_headers,
+ extra_header_count,
+ kStandardGetHeaders,
+ arraysize(kStandardGetHeaders) / 2);
+}
+
+// Constructs a single SPDY data frame with the contents "hello!"
+spdy::SpdyFrame* ConstructSpdyBodyFrame(int stream_id, bool fin) {
+ spdy::SpdyFramer framer;
+ return
+ framer.CreateDataFrame(stream_id, "hello!", 6,
+ fin ? spdy::DATA_FLAG_FIN : spdy::DATA_FLAG_NONE);
+}
+
+// Construct an expected SPDY reply string.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// |buffer| is the buffer we're filling in.
+// Returns the number of bytes written into |buffer|.
+int ConstructSpdyReplyString(const char* const extra_headers[],
+ int extra_header_count,
+ char* buffer,
+ int buffer_length) {
+ int packet_size = 0;
+ int header_count = 0;
+ char* buffer_write = buffer;
+ int buffer_left = buffer_length;
+ spdy::SpdyHeaderBlock headers;
+ if (!buffer || !buffer_length)
+ return 0;
+ // Copy in the extra headers.
+ AppendHeadersToSpdyFrame(extra_headers, extra_header_count, &headers);
+ header_count = headers.size();
+ // The iterator gets us the list of header/value pairs in sorted order.
+ spdy::SpdyHeaderBlock::iterator next = headers.begin();
+ spdy::SpdyHeaderBlock::iterator last = headers.end();
+ for ( ; next != last; ++next) {
+ // Write the header.
+ int value_len, current_len, offset;
+ const char* header_string = next->first.c_str();
+ packet_size += AppendToBuffer(header_string,
+ next->first.length(),
+ &buffer_write,
+ &buffer_left);
+ packet_size += AppendToBuffer(": ",
+ strlen(": "),
+ &buffer_write,
+ &buffer_left);
+ // Write the value(s).
+ const char* value_string = next->second.c_str();
+ // Check if it's split among two or more values.
+ value_len = next->second.length();
+ current_len = strlen(value_string);
+ offset = 0;
+ // Handle the first N-1 values.
+ while (current_len < value_len) {
+ // Finish this line -- write the current value.
+ packet_size += AppendToBuffer(value_string + offset,
+ current_len - offset,
+ &buffer_write,
+ &buffer_left);
+ packet_size += AppendToBuffer("\n",
+ strlen("\n"),
+ &buffer_write,
+ &buffer_left);
+ // Advance to next value.
+ offset = current_len + 1;
+ current_len += 1 + strlen(value_string + offset);
+ // Start another line -- add the header again.
+ packet_size += AppendToBuffer(header_string,
+ next->first.length(),
+ &buffer_write,
+ &buffer_left);
+ packet_size += AppendToBuffer(": ",
+ strlen(": "),
+ &buffer_write,
+ &buffer_left);
+ }
+ EXPECT_EQ(value_len, current_len);
+ // Copy the last (or only) value.
+ packet_size += AppendToBuffer(value_string + offset,
+ value_len - offset,
+ &buffer_write,
+ &buffer_left);
+ packet_size += AppendToBuffer("\n",
+ strlen("\n"),
+ &buffer_write,
+ &buffer_left);
+ }
+ return packet_size;
+}
+
+// Create a MockWrite from the given SpdyFrame.
+MockWrite CreateMockWrite(const spdy::SpdyFrame& req) {
+ return MockWrite(
+ true, req.data(), req.length() + spdy::SpdyFrame::size());
+}
+
+// Create a MockWrite from the given SpdyFrame and sequence number.
+MockWrite CreateMockWrite(const spdy::SpdyFrame& req, int seq) {
+ return MockWrite(
+ true, req.data(), req.length() + spdy::SpdyFrame::size(), seq);
+}
+
+// Create a MockRead from the given SpdyFrame.
+MockRead CreateMockRead(const spdy::SpdyFrame& resp) {
+ return MockRead(
+ true, resp.data(), resp.length() + spdy::SpdyFrame::size());
+}
+
+// Create a MockRead from the given SpdyFrame and sequence number.
+MockRead CreateMockRead(const spdy::SpdyFrame& resp, int seq) {
+ return MockRead(
+ true, resp.data(), resp.length() + spdy::SpdyFrame::size(), seq);
+}
+
+// Combines the given SpdyFrames into the given char array and returns
+// the total length.
+int CombineFrames(const spdy::SpdyFrame** frames, int num_frames,
+ char* buff, int buff_len) {
+ int total_len = 0;
+ for (int i = 0; i < num_frames; ++i) {
+ total_len += frames[i]->length() + spdy::SpdyFrame::size();
+ }
+ DCHECK_LE(total_len, buff_len);
+ char* ptr = buff;
+ for (int i = 0; i < num_frames; ++i) {
+ int len = frames[i]->length() + spdy::SpdyFrame::size();
+ memcpy(ptr, frames[i]->data(), len);
+ ptr += len;
+ }
+ return total_len;
+}
+
+} // namespace net
diff --git a/net/spdy/spdy_test_util.h b/net/spdy/spdy_test_util.h
new file mode 100644
index 0000000..6e7a8d0
--- /dev/null
+++ b/net/spdy/spdy_test_util.h
@@ -0,0 +1,255 @@
+// 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.
+
+#ifndef NET_SPDY_SPDY_TEST_UTIL_H_
+#define NET_SPDY_SPDY_TEST_UTIL_H_
+
+#include "base/basictypes.h"
+#include "net/base/mock_host_resolver.h"
+#include "net/base/request_priority.h"
+#include "net/base/ssl_config_service_defaults.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_network_session.h"
+#include "net/proxy/proxy_service.h"
+#include "net/socket/socket_test_util.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_session_pool.h"
+
+namespace net {
+
+// NOTE: In GCC, on a Mac, this can't be in an anonymous namespace!
+// This struct holds information used to construct spdy control and data frames.
+struct SpdyHeaderInfo {
+ spdy::SpdyControlType kind;
+ spdy::SpdyStreamId id;
+ spdy::SpdyStreamId assoc_id;
+ spdy::SpdyPriority priority;
+ spdy::SpdyControlFlags control_flags;
+ bool compressed;
+ spdy::SpdyStatusCodes status;
+ const char* data;
+ uint32 data_length;
+ spdy::SpdyDataFlags data_flags;
+};
+
+// Chop a frame into an array of MockWrites.
+// |data| is the frame to chop.
+// |length| is the length of the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockWrite* ChopWriteFrame(const char* data, int length, int num_chunks);
+
+// Chop a SpdyFrame into an array of MockWrites.
+// |frame| is the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockWrite* ChopWriteFrame(const spdy::SpdyFrame& frame, int num_chunks);
+
+// Chop a frame into an array of MockReads.
+// |data| is the frame to chop.
+// |length| is the length of the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockRead* ChopReadFrame(const char* data, int length, int num_chunks);
+
+// Chop a SpdyFrame into an array of MockReads.
+// |frame| is the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockRead* ChopReadFrame(const spdy::SpdyFrame& frame, int num_chunks);
+
+// Adds headers and values to a map.
+// |extra_headers| is an array of { name, value } pairs, arranged as strings
+// where the even entries are the header names, and the odd entries are the
+// header values.
+// |headers| gets filled in from |extra_headers|.
+void AppendHeadersToSpdyFrame(const char* const extra_headers[],
+ int extra_header_count,
+ spdy::SpdyHeaderBlock* headers);
+
+// Writes |str| of the given |len| to the buffer pointed to by |buffer_handle|.
+// Uses a template so buffer_handle can be a char* or an unsigned char*.
+// Updates the |*buffer_handle| pointer by |len|
+// Returns the number of bytes written into *|buffer_handle|
+template<class T>
+int AppendToBuffer(const char* str,
+ int len,
+ T** buffer_handle,
+ int* buffer_len_remaining) {
+ DCHECK_GT(len, 0);
+ DCHECK(NULL != buffer_handle) << "NULL buffer handle";
+ DCHECK(NULL != *buffer_handle) << "NULL pointer";
+ DCHECK(NULL != buffer_len_remaining)
+ << "NULL buffer remainder length pointer";
+ DCHECK_GE(*buffer_len_remaining, len) << "Insufficient buffer size";
+ memcpy(*buffer_handle, str, len);
+ *buffer_handle += len;
+ *buffer_len_remaining -= len;
+ return len;
+}
+
+// Writes |val| to a location of size |len|, in big-endian format.
+// in the buffer pointed to by |buffer_handle|.
+// Updates the |*buffer_handle| pointer by |len|
+// Returns the number of bytes written
+int AppendToBuffer(int val,
+ int len,
+ unsigned char** buffer_handle,
+ int* buffer_len_remaining);
+
+// Construct a SPDY packet.
+// |head| is the start of the packet, up to but not including
+// the header value pairs.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// |tail| is any (relatively constant) header-value pairs to add.
+// |buffer| is the buffer we're filling in.
+// Returns a SpdyFrame.
+spdy::SpdyFrame* ConstructSpdyPacket(const SpdyHeaderInfo& header_info,
+ const char* const extra_headers[],
+ int extra_header_count,
+ const char* const tail[],
+ int tail_header_count);
+
+// Construct an expected SPDY reply string.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// |buffer| is the buffer we're filling in.
+// Returns the number of bytes written into |buffer|.
+int ConstructSpdyReplyString(const char* const extra_headers[],
+ int extra_header_count,
+ char* buffer,
+ int buffer_length);
+
+// Construct an expected SPDY SETTINGS frame.
+// |settings| are the settings to set.
+// Returns the constructed frame. The caller takes ownership of the frame.
+spdy::SpdyFrame* ConstructSpdySettings(spdy::SpdySettings settings);
+
+// Construct a SPDY GOAWAY frame.
+// Returns the constructed frame. The caller takes ownership of the frame.
+spdy::SpdyFrame* ConstructSpdyGoAway();
+
+// Construct a SPDY WINDOW_UPDATE frame.
+// Returns the constructed frame. The caller takes ownership of the frame.
+spdy::SpdyFrame* ConstructSpdyWindowUpdate(spdy::SpdyStreamId,
+ uint32 delta_window_size);
+
+// Construct a SPDY RST_STREAM frame.
+// Returns the constructed frame. The caller takes ownership of the frame.
+spdy::SpdyFrame* ConstructSpdyRstStream(spdy::SpdyStreamId stream_id,
+ spdy::SpdyStatusCodes status);
+
+// Construct a single SPDY header entry, for validation.
+// |extra_headers| are the extra header-value pairs.
+// |buffer| is the buffer we're filling in.
+// |index| is the index of the header we want.
+// Returns the number of bytes written into |buffer|.
+int ConstructSpdyHeader(const char* const extra_headers[],
+ int extra_header_count,
+ char* buffer,
+ int buffer_length,
+ int index);
+
+// Constructs a standard SPDY GET SYN packet, optionally compressed.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+spdy::SpdyFrame* ConstructSpdyGet(const char* const extra_headers[],
+ int extra_header_count,
+ bool compressed,
+ int stream_id,
+ RequestPriority request_priority);
+
+// Constructs a standard SPDY SYN_REPLY packet to match the SPDY GET.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+spdy::SpdyFrame* ConstructSpdyGetSynReply(const char* const extra_headers[],
+ int extra_header_count,
+ int stream_id);
+
+// Constructs a standard SPDY POST SYN packet.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+spdy::SpdyFrame* ConstructSpdyPost(const char* const extra_headers[],
+ int extra_header_count);
+
+// Constructs a standard SPDY SYN_REPLY packet to match the SPDY POST.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+spdy::SpdyFrame* ConstructSpdyPostSynReply(const char* const extra_headers[],
+ int extra_header_count);
+
+// Constructs a single SPDY data frame with the contents "hello!"
+spdy::SpdyFrame* ConstructSpdyBodyFrame(int stream_id,
+ bool fin);
+
+// Create an async MockWrite from the given SpdyFrame.
+MockWrite CreateMockWrite(const spdy::SpdyFrame& req);
+
+// Create an async MockWrite from the given SpdyFrame and sequence number.
+MockWrite CreateMockWrite(const spdy::SpdyFrame& req, int seq);
+
+// Create a MockRead from the given SpdyFrame.
+MockRead CreateMockRead(const spdy::SpdyFrame& resp);
+
+// Create a MockRead from the given SpdyFrame and sequence number.
+MockRead CreateMockRead(const spdy::SpdyFrame& resp, int seq);
+
+// Combines the given SpdyFrames into the given char array and returns
+// the total length.
+int CombineFrames(const spdy::SpdyFrame** frames, int num_frames,
+ char* buff, int buff_len);
+
+// Helper to manage the lifetimes of the dependencies for a
+// HttpNetworkTransaction.
+class SpdySessionDependencies {
+ public:
+ // Default set of dependencies -- "null" proxy service.
+ SpdySessionDependencies()
+ : host_resolver(new MockHostResolver),
+ proxy_service(ProxyService::CreateNull()),
+ ssl_config_service(new SSLConfigServiceDefaults),
+ http_auth_handler_factory(HttpAuthHandlerFactory::CreateDefault()),
+ spdy_session_pool(new SpdySessionPool()) {
+ // Note: The CancelledTransaction test does cleanup by running all
+ // tasks in the message loop (RunAllPending). Unfortunately, that
+ // doesn't clean up tasks on the host resolver thread; and
+ // TCPConnectJob is currently not cancellable. Using synchronous
+ // lookups allows the test to shutdown cleanly. Until we have
+ // cancellable TCPConnectJobs, use synchronous lookups.
+ host_resolver->set_synchronous_mode(true);
+ }
+
+ // Custom proxy service dependency.
+ explicit SpdySessionDependencies(ProxyService* proxy_service)
+ : host_resolver(new MockHostResolver),
+ proxy_service(proxy_service),
+ ssl_config_service(new SSLConfigServiceDefaults),
+ http_auth_handler_factory(HttpAuthHandlerFactory::CreateDefault()),
+ spdy_session_pool(new SpdySessionPool()) {}
+
+ scoped_refptr<MockHostResolverBase> host_resolver;
+ scoped_refptr<ProxyService> proxy_service;
+ scoped_refptr<SSLConfigService> ssl_config_service;
+ MockClientSocketFactory socket_factory;
+ scoped_ptr<HttpAuthHandlerFactory> http_auth_handler_factory;
+ scoped_refptr<SpdySessionPool> spdy_session_pool;
+
+ static HttpNetworkSession* SpdyCreateSession(
+ SpdySessionDependencies* session_deps) {
+ return new HttpNetworkSession(session_deps->host_resolver,
+ session_deps->proxy_service,
+ &session_deps->socket_factory,
+ session_deps->ssl_config_service,
+ session_deps->spdy_session_pool,
+ session_deps->http_auth_handler_factory.get(),
+ NULL,
+ NULL);
+}
+};
+
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_TEST_UTIL_H_
diff --git a/net/spdy/spdy_transaction_factory.h b/net/spdy/spdy_transaction_factory.h
new file mode 100644
index 0000000..bb626f9
--- /dev/null
+++ b/net/spdy/spdy_transaction_factory.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2009 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.
+
+#ifndef NET_SPDY_SPDY_TRANSACTION_FACTORY_H__
+#define NET_SPDY_SPDY_TRANSACTION_FACTORY_H__
+
+#include "net/http/http_transaction_factory.h"
+#include "net/spdy/spdy_network_transaction.h"
+
+namespace net {
+
+class SpdyTransactionFactory : public HttpTransactionFactory {
+ public:
+ explicit SpdyTransactionFactory(HttpNetworkSession* session)
+ : session_(session) {
+ }
+ virtual ~SpdyTransactionFactory() {}
+
+ // HttpTransactionFactory Interface.
+ virtual HttpTransaction* CreateTransaction() {
+ return new SpdyNetworkTransaction(session_);
+ }
+ virtual HttpCache* GetCache() {
+ return NULL;
+ }
+ virtual void Suspend(bool suspend) {
+ }
+
+ private:
+ scoped_refptr<HttpNetworkSession> session_;
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_TRANSACTION_FACTORY_H__
diff --git a/net/stress_cache.target.mk b/net/stress_cache.target.mk
new file mode 100644
index 0000000..1bd44eb
--- /dev/null
+++ b/net/stress_cache.target.mk
@@ -0,0 +1,183 @@
+# This file is generated by gyp; do not edit.
+
+TOOLSET := target
+TARGET := stress_cache
+DEFS_Debug := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-D__STDC_FORMAT_MACROS' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=1' \
+ '-D_DEBUG'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Debug := -Werror \
+ -pthread \
+ -fno-exceptions \
+ -Wall \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/atk-1.0 \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -O0 \
+ -g
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Debug :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Debug := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Debug := -I.
+
+DEFS_Release := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-D__STDC_FORMAT_MACROS' \
+ '-DNDEBUG' \
+ '-DNVALGRIND' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=0'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Release := -Werror \
+ -pthread \
+ -fno-exceptions \
+ -Wall \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/atk-1.0 \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -O2 \
+ -fno-ident \
+ -fdata-sections \
+ -ffunction-sections
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Release :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Release := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Release := -I.
+
+OBJS := $(obj).target/$(TARGET)/net/disk_cache/stress_cache.o
+
+# Add to the list of files we specially track dependencies for.
+all_deps += $(OBJS)
+
+# Make sure our dependencies are built before any of us.
+$(OBJS): | $(obj).target/net/libnet.a $(obj).target/net/libnet_test_support.a $(obj).target/base/libbase.a $(obj).target/third_party/modp_b64/libmodp_b64.a $(obj).target/base/third_party/dynamic_annotations/libdynamic_annotations.a $(obj).target/base/libsymbolize.a $(obj).target/net/third_party/nss/libssl.a $(obj).target/third_party/zlib/libzlib.a $(obj).target/base/libxdg_mime.a $(obj).target/base/allocator/liballocator.a $(obj).target/third_party/libevent/libevent.a $(obj).target/base/libbase_i18n.a $(obj).target/third_party/icu/libicui18n.a $(obj).target/third_party/icu/libicuuc.a $(obj).target/third_party/icu/libicudata.a $(obj).target/build/temp_gyp/libgoogleurl.a $(obj).target/sdch/libsdch.a $(obj).target/net/libnet_base.a $(obj).target/v8/tools/gyp/libv8_snapshot.a $(obj).target/v8/tools/gyp/libv8_base.a $(obj).target/testing/libgtest.a
+
+# CFLAGS et al overrides must be target-local.
+# See "Target-specific Variable Values" in the GNU Make manual.
+$(OBJS): TOOLSET := $(TOOLSET)
+$(OBJS): GYP_CFLAGS := $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_C_$(BUILDTYPE)) $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE))
+$(OBJS): GYP_CXXFLAGS := $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_CC_$(BUILDTYPE)) $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE))
+
+# Suffix rules, putting all outputs into $(obj).
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(srcdir)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+# Try building from generated source, too.
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj).$(TOOLSET)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+# End of this set of suffix rules
+### Rules for final target.
+LDFLAGS_Debug := -pthread \
+ -Wl,-z,noexecstack \
+ -Wl,-uIsHeapProfilerRunning,-uProfilerStart \
+ -Wl,-u_Z21InitialMallocHook_NewPKvj,-u_Z22InitialMallocHook_MMapPKvS0_jiiix,-u_Z22InitialMallocHook_SbrkPKvi \
+ -Wl,-u_Z21InitialMallocHook_NewPKvm,-u_Z22InitialMallocHook_MMapPKvS0_miiil,-u_Z22InitialMallocHook_SbrkPKvl \
+ -rdynamic
+
+LDFLAGS_Release := -pthread \
+ -Wl,-z,noexecstack \
+ -Wl,-uIsHeapProfilerRunning,-uProfilerStart \
+ -Wl,-u_Z21InitialMallocHook_NewPKvj,-u_Z22InitialMallocHook_MMapPKvS0_jiiix,-u_Z22InitialMallocHook_SbrkPKvi \
+ -Wl,-u_Z21InitialMallocHook_NewPKvm,-u_Z22InitialMallocHook_MMapPKvS0_miiil,-u_Z22InitialMallocHook_SbrkPKvl \
+ -Wl,--gc-sections
+
+LIBS := -lrt \
+ -ldl \
+ -lgtk-x11-2.0 \
+ -lgdk-x11-2.0 \
+ -latk-1.0 \
+ -lgio-2.0 \
+ -lpangoft2-1.0 \
+ -lgdk_pixbuf-2.0 \
+ -lm \
+ -lpangocairo-1.0 \
+ -lcairo \
+ -lpango-1.0 \
+ -lfreetype \
+ -lfontconfig \
+ -lgobject-2.0 \
+ -lgmodule-2.0 \
+ -lgthread-2.0 \
+ -lglib-2.0 \
+ -lnss3 \
+ -lnssutil3 \
+ -lsmime3 \
+ -lplds4 \
+ -lplc4 \
+ -lnspr4 \
+ -lpthread \
+ -lz \
+ -lgconf-2
+
+$(builddir)/stress_cache: GYP_LDFLAGS := $(LDFLAGS_$(BUILDTYPE))
+$(builddir)/stress_cache: LIBS := $(LIBS)
+$(builddir)/stress_cache: TOOLSET := $(TOOLSET)
+$(builddir)/stress_cache: $(OBJS) $(obj).target/net/libnet.a $(obj).target/net/libnet_test_support.a $(obj).target/base/libbase.a $(obj).target/third_party/modp_b64/libmodp_b64.a $(obj).target/base/third_party/dynamic_annotations/libdynamic_annotations.a $(obj).target/base/libsymbolize.a $(obj).target/net/third_party/nss/libssl.a $(obj).target/third_party/zlib/libzlib.a $(obj).target/base/libxdg_mime.a $(obj).target/base/allocator/liballocator.a $(obj).target/third_party/libevent/libevent.a $(obj).target/base/libbase_i18n.a $(obj).target/third_party/icu/libicui18n.a $(obj).target/third_party/icu/libicuuc.a $(obj).target/third_party/icu/libicudata.a $(obj).target/build/temp_gyp/libgoogleurl.a $(obj).target/sdch/libsdch.a $(obj).target/net/libnet_base.a $(obj).target/v8/tools/gyp/libv8_snapshot.a $(obj).target/v8/tools/gyp/libv8_base.a $(obj).target/testing/libgtest.a FORCE_DO_CMD
+ $(call do_cmd,link)
+
+all_deps += $(builddir)/stress_cache
+# Add target alias
+.PHONY: stress_cache
+stress_cache: $(builddir)/stress_cache
+
+# Add executable to "all" target.
+.PHONY: all
+all: $(builddir)/stress_cache
+
diff --git a/net/test/test_server.cc b/net/test/test_server.cc
new file mode 100644
index 0000000..4abed54
--- /dev/null
+++ b/net/test/test_server.cc
@@ -0,0 +1,414 @@
+// 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 "net/test/test_server.h"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#include <wincrypt.h>
+#elif defined(OS_MACOSX)
+#include "net/base/x509_certificate.h"
+#endif
+
+#include "base/file_util.h"
+#include "base/leak_annotations.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/utf_string_conversions.h"
+#include "net/base/cert_test_util.h"
+#include "net/base/host_resolver.h"
+#include "net/base/net_test_constants.h"
+#include "net/base/test_completion_callback.h"
+#include "net/socket/tcp_client_socket.h"
+#include "net/socket/tcp_pinger.h"
+#include "testing/platform_test.h"
+
+#if defined(OS_WIN)
+#pragma comment(lib, "crypt32.lib")
+#endif
+
+namespace net {
+
+#if defined(OS_MACOSX)
+void SetMacTestCertificate(X509Certificate* cert);
+#endif
+
+// static
+const char TestServerLauncher::kHostName[] = "127.0.0.1";
+const char TestServerLauncher::kMismatchedHostName[] = "localhost";
+const int TestServerLauncher::kOKHTTPSPort = 9443;
+const int TestServerLauncher::kBadHTTPSPort = 9666;
+
+// The issuer name of the cert that should be trusted for the test to work.
+const wchar_t TestServerLauncher::kCertIssuerName[] = L"Test CA";
+
+TestServerLauncher::TestServerLauncher() : process_handle_(
+ base::kNullProcessHandle),
+ forking_(false),
+ connection_attempts_(kDefaultTestConnectionAttempts),
+ connection_timeout_(kDefaultTestConnectionTimeout)
+{
+ InitCertPath();
+}
+
+TestServerLauncher::TestServerLauncher(int connection_attempts,
+ int connection_timeout)
+ : process_handle_(base::kNullProcessHandle),
+ forking_(false),
+ connection_attempts_(connection_attempts),
+ connection_timeout_(connection_timeout)
+{
+ InitCertPath();
+}
+
+void TestServerLauncher::InitCertPath() {
+ PathService::Get(base::DIR_SOURCE_ROOT, &cert_dir_);
+ cert_dir_ = cert_dir_.Append(FILE_PATH_LITERAL("net"))
+ .Append(FILE_PATH_LITERAL("data"))
+ .Append(FILE_PATH_LITERAL("ssl"))
+ .Append(FILE_PATH_LITERAL("certificates"));
+}
+
+namespace {
+
+void AppendToPythonPath(const FilePath& dir) {
+ // Do nothing if dir already on path.
+
+#if defined(OS_WIN)
+ const wchar_t kPythonPath[] = L"PYTHONPATH";
+ // TODO(dkegel): handle longer PYTHONPATH variables
+ wchar_t oldpath[4096];
+ if (GetEnvironmentVariable(kPythonPath, oldpath, arraysize(oldpath)) == 0) {
+ SetEnvironmentVariableW(kPythonPath, dir.value().c_str());
+ } else if (!wcsstr(oldpath, dir.value().c_str())) {
+ std::wstring newpath(oldpath);
+ newpath.append(L";");
+ newpath.append(dir.value());
+ SetEnvironmentVariableW(kPythonPath, newpath.c_str());
+ }
+#elif defined(OS_POSIX)
+ const char kPythonPath[] = "PYTHONPATH";
+ const char* oldpath = getenv(kPythonPath);
+ // setenv() leaks memory intentionally on Mac
+ if (!oldpath) {
+ setenv(kPythonPath, dir.value().c_str(), 1);
+ } else if (!strstr(oldpath, dir.value().c_str())) {
+ std::string newpath(oldpath);
+ newpath.append(":");
+ newpath.append(dir.value());
+ setenv(kPythonPath, newpath.c_str(), 1);
+ }
+#endif
+}
+
+} // end namespace
+
+void TestServerLauncher::SetPythonPath() {
+ FilePath third_party_dir;
+ CHECK(PathService::Get(base::DIR_SOURCE_ROOT, &third_party_dir));
+ third_party_dir = third_party_dir.Append(FILE_PATH_LITERAL("third_party"));
+
+ AppendToPythonPath(third_party_dir.Append(FILE_PATH_LITERAL("tlslite")));
+ AppendToPythonPath(third_party_dir.Append(FILE_PATH_LITERAL("pyftpdlib")));
+
+ // Locate the Python code generated by the protocol buffers compiler.
+ FilePath generated_code_dir;
+ CHECK(PathService::Get(base::DIR_EXE, &generated_code_dir));
+ generated_code_dir = generated_code_dir.Append(FILE_PATH_LITERAL("pyproto"));
+ AppendToPythonPath(generated_code_dir);
+ AppendToPythonPath(generated_code_dir.Append(FILE_PATH_LITERAL("sync_pb")));
+}
+
+bool TestServerLauncher::Start(Protocol protocol,
+ const std::string& host_name, int port,
+ const FilePath& document_root,
+ const FilePath& cert_path,
+ const std::wstring& file_root_url) {
+ if (!cert_path.value().empty()) {
+ if (!LoadTestRootCert())
+ return false;
+ if (!CheckCATrusted())
+ return false;
+ }
+
+ std::string port_str = IntToString(port);
+
+ // Get path to python server script
+ FilePath testserver_path;
+ if (!PathService::Get(base::DIR_SOURCE_ROOT, &testserver_path))
+ return false;
+ testserver_path = testserver_path
+ .Append(FILE_PATH_LITERAL("net"))
+ .Append(FILE_PATH_LITERAL("tools"))
+ .Append(FILE_PATH_LITERAL("testserver"))
+ .Append(FILE_PATH_LITERAL("testserver.py"));
+
+ PathService::Get(base::DIR_SOURCE_ROOT, &document_root_dir_);
+ document_root_dir_ = document_root_dir_.Append(document_root);
+
+ SetPythonPath();
+
+#if defined(OS_WIN)
+ // Get path to python interpreter
+ if (!PathService::Get(base::DIR_SOURCE_ROOT, &python_runtime_))
+ return false;
+ python_runtime_ = python_runtime_
+ .Append(FILE_PATH_LITERAL("third_party"))
+ .Append(FILE_PATH_LITERAL("python_24"))
+ .Append(FILE_PATH_LITERAL("python.exe"));
+
+ std::wstring command_line =
+ L"\"" + python_runtime_.ToWStringHack() + L"\" " +
+ L"\"" + testserver_path.ToWStringHack() +
+ L"\" --port=" + UTF8ToWide(port_str) +
+ L" --data-dir=\"" + document_root_dir_.ToWStringHack() + L"\"";
+ if (protocol == ProtoFTP)
+ command_line.append(L" -f");
+ if (!cert_path.value().empty()) {
+ command_line.append(L" --https=\"");
+ command_line.append(cert_path.ToWStringHack());
+ command_line.append(L"\"");
+ }
+ if (!file_root_url.empty()) {
+ command_line.append(L" --file-root-url=\"");
+ command_line.append(file_root_url);
+ command_line.append(L"\"");
+ }
+ // Deliberately do not pass the --forking flag. It breaks the tests
+ // on Windows.
+
+ if (!LaunchTestServerAsJob(command_line,
+ true,
+ &process_handle_,
+ &job_handle_)) {
+ LOG(ERROR) << "Failed to launch " << command_line;
+ return false;
+ }
+#elif defined(OS_POSIX)
+ std::vector<std::string> command_line;
+ command_line.push_back("python");
+ command_line.push_back(testserver_path.value());
+ command_line.push_back("--port=" + port_str);
+ command_line.push_back("--data-dir=" + document_root_dir_.value());
+ if (protocol == ProtoFTP)
+ command_line.push_back("-f");
+ if (!cert_path.value().empty())
+ command_line.push_back("--https=" + cert_path.value());
+ if (forking_)
+ command_line.push_back("--forking");
+
+ base::file_handle_mapping_vector no_mappings;
+ LOG(INFO) << "Trying to launch " << command_line[0] << " ...";
+ if (!base::LaunchApp(command_line, no_mappings, false, &process_handle_)) {
+ LOG(ERROR) << "Failed to launch " << command_line[0] << " ...";
+ return false;
+ }
+#endif
+
+ // Let the server start, then verify that it's up.
+ // Our server is Python, and takes about 500ms to start
+ // up the first time, and about 200ms after that.
+ if (!WaitToStart(host_name, port)) {
+ LOG(ERROR) << "Failed to connect to server";
+ Stop();
+ return false;
+ }
+
+ LOG(INFO) << "Started on port " << port_str;
+ return true;
+}
+
+bool TestServerLauncher::WaitToStart(const std::string& host_name, int port) {
+ // Verify that the webserver is actually started.
+ // Otherwise tests can fail if they run faster than Python can start.
+ net::AddressList addr;
+ scoped_refptr<net::HostResolver> resolver(
+ net::CreateSystemHostResolver(net::HostResolver::kDefaultParallelism));
+ net::HostResolver::RequestInfo info(host_name, port);
+ int rv = resolver->Resolve(info, &addr, NULL, NULL, BoundNetLog());
+ if (rv != net::OK)
+ return false;
+
+ net::TCPPinger pinger(addr);
+ rv = pinger.Ping(base::TimeDelta::FromMilliseconds(connection_timeout_),
+ connection_attempts_);
+ return rv == net::OK;
+}
+
+bool TestServerLauncher::WaitToFinish(int timeout_ms) {
+ if (!process_handle_)
+ return true;
+
+ bool ret = base::WaitForSingleProcess(process_handle_, timeout_ms);
+ if (ret) {
+ base::CloseProcessHandle(process_handle_);
+ process_handle_ = base::kNullProcessHandle;
+ LOG(INFO) << "Finished.";
+ } else {
+ LOG(INFO) << "Timed out.";
+ }
+ return ret;
+}
+
+bool TestServerLauncher::Stop() {
+ if (!process_handle_)
+ return true;
+
+ // First check if the process has already terminated.
+ bool ret = base::WaitForSingleProcess(process_handle_, 0);
+ if (!ret)
+ ret = base::KillProcess(process_handle_, 1, true);
+
+ if (ret) {
+ base::CloseProcessHandle(process_handle_);
+ process_handle_ = base::kNullProcessHandle;
+ LOG(INFO) << "Stopped.";
+ } else {
+ LOG(INFO) << "Kill failed?";
+ }
+
+ return ret;
+}
+
+TestServerLauncher::~TestServerLauncher() {
+#if defined(OS_MACOSX)
+ SetMacTestCertificate(NULL);
+#endif
+ Stop();
+}
+
+FilePath TestServerLauncher::GetRootCertPath() {
+ FilePath path(cert_dir_);
+ path = path.AppendASCII("root_ca_cert.crt");
+ return path;
+}
+
+FilePath TestServerLauncher::GetOKCertPath() {
+ FilePath path(cert_dir_);
+ path = path.AppendASCII("ok_cert.pem");
+ return path;
+}
+
+FilePath TestServerLauncher::GetExpiredCertPath() {
+ FilePath path(cert_dir_);
+ path = path.AppendASCII("expired_cert.pem");
+ return path;
+}
+
+bool TestServerLauncher::LoadTestRootCert() {
+#if defined(USE_NSS)
+ if (cert_)
+ return true;
+
+ // TODO(dkegel): figure out how to get this to only happen once?
+
+ // This currently leaks a little memory.
+ // TODO(dkegel): fix the leak and remove the entry in
+ // tools/valgrind/memcheck/suppressions.txt
+ ANNOTATE_SCOPED_MEMORY_LEAK; // Tell heap checker about the leak.
+ cert_ = LoadTemporaryRootCert(GetRootCertPath());
+ DCHECK(cert_);
+ return (cert_ != NULL);
+#elif defined(OS_MACOSX)
+ X509Certificate* cert = LoadTemporaryRootCert(GetRootCertPath());
+ if (!cert)
+ return false;
+ SetMacTestCertificate(cert);
+ return true;
+#else
+ return true;
+#endif
+}
+
+bool TestServerLauncher::CheckCATrusted() {
+#if defined(OS_WIN)
+ HCERTSTORE cert_store = CertOpenSystemStore(NULL, L"ROOT");
+ if (!cert_store) {
+ LOG(ERROR) << " could not open trusted root CA store";
+ return false;
+ }
+ PCCERT_CONTEXT cert =
+ CertFindCertificateInStore(cert_store,
+ X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
+ 0,
+ CERT_FIND_ISSUER_STR,
+ kCertIssuerName,
+ NULL);
+ if (cert)
+ CertFreeCertificateContext(cert);
+ CertCloseStore(cert_store, 0);
+
+ if (!cert) {
+ LOG(ERROR) << " TEST CONFIGURATION ERROR: you need to import the test ca "
+ "certificate to your trusted roots for this test to work. "
+ "For more info visit:\n"
+ "http://dev.chromium.org/developers/testing\n";
+ return false;
+ }
+#endif
+ return true;
+}
+
+#if defined(OS_WIN)
+bool LaunchTestServerAsJob(const std::wstring& cmdline,
+ bool start_hidden,
+ base::ProcessHandle* process_handle,
+ ScopedHandle* job_handle) {
+ // Launch test server process.
+ STARTUPINFO startup_info = {0};
+ startup_info.cb = sizeof(startup_info);
+ startup_info.dwFlags = STARTF_USESHOWWINDOW;
+ startup_info.wShowWindow = start_hidden ? SW_HIDE : SW_SHOW;
+ PROCESS_INFORMATION process_info;
+
+ // If this code is run under a debugger, the test server process is
+ // automatically associated with a job object created by the debugger.
+ // The CREATE_BREAKAWAY_FROM_JOB flag is used to prevent this.
+ if (!CreateProcess(NULL,
+ const_cast<wchar_t*>(cmdline.c_str()), NULL, NULL,
+ FALSE, CREATE_BREAKAWAY_FROM_JOB, NULL, NULL,
+ &startup_info, &process_info)) {
+ LOG(ERROR) << "Could not create process.";
+ return false;
+ }
+ CloseHandle(process_info.hThread);
+
+ // If the caller wants the process handle, we won't close it.
+ if (process_handle) {
+ *process_handle = process_info.hProcess;
+ } else {
+ CloseHandle(process_info.hProcess);
+ }
+
+ // Create a JobObject and associate the test server process with it.
+ job_handle->Set(CreateJobObject(NULL, NULL));
+ if (!job_handle->IsValid()) {
+ LOG(ERROR) << "Could not create JobObject.";
+ return false;
+ } else {
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION limit_info = {0};
+ limit_info.BasicLimitInformation.LimitFlags =
+ JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
+ if (0 == SetInformationJobObject(job_handle->Get(),
+ JobObjectExtendedLimitInformation, &limit_info, sizeof(limit_info))) {
+ LOG(ERROR) << "Could not SetInformationJobObject.";
+ return false;
+ }
+ if (0 == AssignProcessToJobObject(job_handle->Get(),
+ process_info.hProcess)) {
+ LOG(ERROR) << "Could not AssignProcessToObject.";
+ return false;
+ }
+ }
+ return true;
+}
+#endif
+
+} // namespace net
diff --git a/net/test/test_server.h b/net/test/test_server.h
new file mode 100644
index 0000000..25f2c8b
--- /dev/null
+++ b/net/test/test_server.h
@@ -0,0 +1,149 @@
+// 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.
+
+#ifndef NET_TEST_TEST_SERVER_H_
+#define NET_TEST_TEST_SERVER_H_
+
+#include "build/build_config.h"
+
+#include <string>
+
+#include "base/file_path.h"
+#include "base/process_util.h"
+
+#if defined(OS_WIN)
+#include "base/scoped_handle_win.h"
+#endif
+
+#if defined(USE_NSS)
+#include "base/ref_counted.h"
+#include "net/base/x509_certificate.h"
+#endif
+
+namespace net {
+
+// This object bounds the lifetime of an external python-based HTTP/HTTPS/FTP
+// server that can provide various responses useful for testing.
+// A few basic convenience methods are provided, but no
+// URL handling methods (those belong at a higher layer, e.g. in
+// url_request_unittest.h).
+
+class TestServerLauncher {
+ public:
+ TestServerLauncher();
+ TestServerLauncher(int connection_attempts, int connection_timeout);
+
+ virtual ~TestServerLauncher();
+
+ enum Protocol {
+ ProtoHTTP, ProtoFTP
+ };
+
+ // Load the test root cert, if it hasn't been loaded yet.
+ bool LoadTestRootCert();
+
+ // Tells the server to enable/disable servicing each request
+ // in a separate process. Takes effect only if called before Start.
+ void set_forking(bool forking) { forking_ = forking; }
+
+ // Start src/net/tools/testserver/testserver.py and
+ // ask it to serve the given protocol.
+ // If protocol is HTTP, and cert_path is not empty, serves HTTPS.
+ // file_root_url specifies the root url on the server that documents will be
+ // served out of. This is /files/ by default.
+ // Returns true on success, false if files not found or root cert
+ // not trusted.
+ bool Start(net::TestServerLauncher::Protocol protocol,
+ const std::string& host_name, int port,
+ const FilePath& document_root,
+ const FilePath& cert_path,
+ const std::wstring& file_root_url);
+
+ // Stop the server started by Start().
+ bool Stop();
+
+ // If you access the server's Kill url, it will exit by itself
+ // without a call to Stop().
+ // WaitToFinish is handy in that case.
+ // It returns true if the server exited cleanly.
+ bool WaitToFinish(int milliseconds);
+
+ // Paths to a good, an expired, and an invalid server certificate
+ // (use as arguments to Start()).
+ FilePath GetOKCertPath();
+ FilePath GetExpiredCertPath();
+
+ FilePath GetDocumentRootPath() { return document_root_dir_; }
+
+ // Issuer name of the root cert that should be trusted for the test to work.
+ static const wchar_t kCertIssuerName[];
+
+ // Hostname to use for test server
+ static const char kHostName[];
+
+ // Different hostname to use for test server (that still resolves to same IP)
+ static const char kMismatchedHostName[];
+
+ // Port to use for test server
+ static const int kOKHTTPSPort;
+
+ // Port to use for bad test server
+ static const int kBadHTTPSPort;
+
+ private:
+ // Wait a while for the server to start, return whether
+ // we were able to make a connection to it.
+ bool WaitToStart(const std::string& host_name, int port);
+
+ // Append to PYTHONPATH so Python can find pyftpdlib and tlslite.
+ void SetPythonPath();
+
+ // Path to our test root certificate.
+ FilePath GetRootCertPath();
+
+ // Returns false if our test root certificate is not trusted.
+ bool CheckCATrusted();
+
+ // Initilize the certificate path.
+ void InitCertPath();
+
+ FilePath document_root_dir_;
+
+ FilePath cert_dir_;
+
+ FilePath python_runtime_;
+
+ base::ProcessHandle process_handle_;
+
+#if defined(OS_WIN)
+ // JobObject used to clean up orphaned child processes.
+ ScopedHandle job_handle_;
+#endif
+
+ // True if the server should handle each request in a separate process.
+ bool forking_;
+
+ // Number of tries and timeout for each try used for WaitToStart.
+ int connection_attempts_;
+ int connection_timeout_;
+
+#if defined(USE_NSS)
+ scoped_refptr<X509Certificate> cert_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(TestServerLauncher);
+};
+
+#if defined(OS_WIN)
+// Launch test server as a job so that it is not orphaned if the test case is
+// abnormally terminated.
+bool LaunchTestServerAsJob(const std::wstring& cmdline,
+ bool start_hidden,
+ base::ProcessHandle* process_handle,
+ ScopedHandle* job_handle);
+#endif
+
+} // namespace net
+
+#endif // NET_TEST_TEST_SERVER_H_
diff --git a/net/third_party/gssapi/LICENSE b/net/third_party/gssapi/LICENSE
new file mode 100644
index 0000000..cac53b2
--- /dev/null
+++ b/net/third_party/gssapi/LICENSE
@@ -0,0 +1,19 @@
+Copyright 1993 by OpenVision Technologies, Inc.
+
+Permission to use, copy, modify, distribute, and sell this software
+and its documentation for any purpose is hereby granted without fee,
+provided that the above copyright notice appears in all copies and
+that both that copyright notice and this permission notice appear in
+supporting documentation, and that the name of OpenVision not be used
+in advertising or publicity pertaining to distribution of the software
+without specific, written prior permission. OpenVision makes no
+representations about the suitability of this software for any
+purpose. It is provided "as is" without express or implied warranty.
+
+OPENVISION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+EVENT SHALL OPENVISION BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
+USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
diff --git a/net/third_party/gssapi/README.chromium b/net/third_party/gssapi/README.chromium
new file mode 100644
index 0000000..5261c72
--- /dev/null
+++ b/net/third_party/gssapi/README.chromium
@@ -0,0 +1,18 @@
+Name: gssapi
+URL: https://hg.mozilla.org/mozilla-central/file/05f3c68e73c9/extensions/auth/gssapi.h
+InfoURL: http://web.mit.edu/Kerberos/krb5-1.7/krb5-1.7.1/doc/krb5-install.html
+Version: krb5-1.8.1
+License: OpenVision Technologies
+
+Description:
+This is Chromium's copy of the Mozilla gssapi header.
+
+Originally obtained from Mozilla's Mercurial repository
+on 11 May 2010.
+
+The LICENSE from the header has been copied here.
+
+Local Modifications:
+There are no local changes to the code itself.
+
+A gssapi.gyp file has been added for building with Chromium.
diff --git a/net/third_party/gssapi/gssapi.gyp b/net/third_party/gssapi/gssapi.gyp
new file mode 100644
index 0000000..0b8ae8c
--- /dev/null
+++ b/net/third_party/gssapi/gssapi.gyp
@@ -0,0 +1,21 @@
+# 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.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'gssapi',
+ 'type': 'none',
+ 'sources': [
+ 'gssapi.h',
+ ],
+ },
+ ],
+}
+
+# Local Variables:
+# tab-width:2
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=2 shiftwidth=2:
diff --git a/net/third_party/gssapi/gssapi.h b/net/third_party/gssapi/gssapi.h
new file mode 100644
index 0000000..45e5020
--- /dev/null
+++ b/net/third_party/gssapi/gssapi.h
@@ -0,0 +1,844 @@
+/*
+ * Copied from Firefox source extensions/auth/gssapi.h
+ */
+
+/* ***** BEGIN LICENSE BLOCK *****
+ * Copyright 1993 by OpenVision Technologies, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without fee,
+ * provided that the above copyright notice appears in all copies and
+ * that both that copyright notice and this permission notice appear in
+ * supporting documentation, and that the name of OpenVision not be used
+ * in advertising or publicity pertaining to distribution of the software
+ * without specific, written prior permission. OpenVision makes no
+ * representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied warranty.
+ *
+ * OPENVISION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL OPENVISION BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
+ * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+ * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ ****** END LICENSE BLOCK ***** */
+
+#ifndef GSSAPI_H_
+#define GSSAPI_H_
+
+/*
+ * Also define _GSSAPI_H_ as that is what the Kerberos 5 code defines and
+ * what header files on some systems look for.
+ */
+#define _GSSAPI_H_
+
+/*
+ * On Mac OS X, Kerberos/Kerberos.h is used to gain access to certain
+ * system-specific Kerberos functions, but on 10.4, that file also brings
+ * in other headers that conflict with this one.
+ */
+#define _GSSAPI_GENERIC_H_
+#define _GSSAPI_KRB5_H_
+
+/*
+ * Define windows specific needed parameters.
+ */
+
+#ifndef GSS_CALLCONV
+#if defined(_WIN32)
+#define GSS_CALLCONV __stdcall
+#define GSS_CALLCONV_C __cdecl
+#else
+#define GSS_CALLCONV
+#define GSS_CALLCONV_C
+#endif
+#endif /* GSS_CALLCONV */
+
+#ifdef GSS_USE_FUNCTION_POINTERS
+#ifdef _WIN32
+#undef GSS_CALLCONV
+#define GSS_CALLCONV
+#define GSS_FUNC(f) (__stdcall *f##_type)
+#else
+#define GSS_FUNC(f) (*f##_type)
+#endif
+#define GSS_MAKE_TYPEDEF typedef
+#else
+#define GSS_FUNC(f) f
+#define GSS_MAKE_TYPEDEF
+#endif
+
+/*
+ * First, include stddef.h to get size_t defined.
+ */
+#include <stddef.h>
+
+/*
+ * Configure set the following
+ */
+
+#ifndef SIZEOF_LONG
+#undef SIZEOF_LONG
+#endif
+#ifndef SIZEOF_SHORT
+#undef SIZEOF_SHORT
+#endif
+
+#ifndef EXTERN_C_BEGIN
+#ifdef __cplusplus
+#define EXTERN_C_BEGIN extern "C" {
+#define EXTERN_C_END }
+#else
+#define EXTERN_C_BEGIN
+#define EXTERN_C_END
+#endif
+#endif
+
+EXTERN_C_BEGIN
+
+/*
+ * If the platform supports the xom.h header file, it should be
+ * included here.
+ */
+/* #include <xom.h> */
+
+
+/*
+ * Now define the three implementation-dependent types.
+ */
+
+typedef void * gss_name_t ;
+typedef void * gss_ctx_id_t ;
+typedef void * gss_cred_id_t ;
+
+
+/*
+ * The following type must be defined as the smallest natural
+ * unsigned integer supported by the platform that has at least
+ * 32 bits of precision.
+ */
+
+#if SIZEOF_LONG == 4
+typedef unsigned long gss_uint32;
+#elif SIZEOF_SHORT == 4
+typedef unsigned short gss_uint32;
+#else
+typedef unsigned int gss_uint32;
+#endif
+
+#ifdef OM_STRING
+
+/*
+ * We have included the xom.h header file. Verify that OM_uint32
+ * is defined correctly.
+ */
+
+#if sizeof(gss_uint32) != sizeof(OM_uint32)
+#error Incompatible definition of OM_uint32 from xom.h
+#endif
+
+typedef OM_object_identifier gss_OID_desc, *gss_OID;
+
+#else /* !OM_STRING */
+
+/*
+ * We can't use X/Open definitions, so roll our own.
+ */
+typedef gss_uint32 OM_uint32;
+typedef struct gss_OID_desc_struct {
+ OM_uint32 length;
+ void *elements;
+} gss_OID_desc, *gss_OID;
+
+#endif /* !OM_STRING */
+
+typedef struct gss_OID_set_desc_struct {
+ size_t count;
+ gss_OID elements;
+} gss_OID_set_desc, *gss_OID_set;
+
+
+/*
+ * For now, define a QOP-type as an OM_uint32
+ */
+typedef OM_uint32 gss_qop_t;
+
+typedef int gss_cred_usage_t;
+
+
+typedef struct gss_buffer_desc_struct {
+ size_t length;
+ void *value;
+} gss_buffer_desc, *gss_buffer_t;
+
+typedef struct gss_channel_bindings_struct {
+ OM_uint32 initiator_addrtype;
+ gss_buffer_desc initiator_address;
+ OM_uint32 acceptor_addrtype;
+ gss_buffer_desc acceptor_address;
+ gss_buffer_desc application_data;
+} *gss_channel_bindings_t;
+
+
+/*
+ * Flag bits for context-level services.
+ */
+#define GSS_C_DELEG_FLAG 1
+#define GSS_C_MUTUAL_FLAG 2
+#define GSS_C_REPLAY_FLAG 4
+#define GSS_C_SEQUENCE_FLAG 8
+#define GSS_C_CONF_FLAG 16
+#define GSS_C_INTEG_FLAG 32
+#define GSS_C_ANON_FLAG 64
+#define GSS_C_PROT_READY_FLAG 128
+#define GSS_C_TRANS_FLAG 256
+
+/*
+ * Credential usage options
+ */
+#define GSS_C_BOTH 0
+#define GSS_C_INITIATE 1
+#define GSS_C_ACCEPT 2
+
+/*
+ * Status code types for gss_display_status
+ */
+#define GSS_C_GSS_CODE 1
+#define GSS_C_MECH_CODE 2
+
+/*
+ * The constant definitions for channel-bindings address families
+ */
+#define GSS_C_AF_UNSPEC 0
+#define GSS_C_AF_LOCAL 1
+#define GSS_C_AF_INET 2
+#define GSS_C_AF_IMPLINK 3
+#define GSS_C_AF_PUP 4
+#define GSS_C_AF_CHAOS 5
+#define GSS_C_AF_NS 6
+#define GSS_C_AF_NBS 7
+#define GSS_C_AF_ECMA 8
+#define GSS_C_AF_DATAKIT 9
+#define GSS_C_AF_CCITT 10
+#define GSS_C_AF_SNA 11
+#define GSS_C_AF_DECnet 12
+#define GSS_C_AF_DLI 13
+#define GSS_C_AF_LAT 14
+#define GSS_C_AF_HYLINK 15
+#define GSS_C_AF_APPLETALK 16
+#define GSS_C_AF_BSC 17
+#define GSS_C_AF_DSS 18
+#define GSS_C_AF_OSI 19
+#define GSS_C_AF_X25 21
+
+#define GSS_C_AF_NULLADDR 255
+
+/*
+ * Various Null values
+ */
+#define GSS_C_NO_NAME ((gss_name_t) 0)
+#define GSS_C_NO_BUFFER ((gss_buffer_t) 0)
+#define GSS_C_NO_OID ((gss_OID) 0)
+#define GSS_C_NO_OID_SET ((gss_OID_set) 0)
+#define GSS_C_NO_CONTEXT ((gss_ctx_id_t) 0)
+#define GSS_C_NO_CREDENTIAL ((gss_cred_id_t) 0)
+#define GSS_C_NO_CHANNEL_BINDINGS ((gss_channel_bindings_t) 0)
+#define GSS_C_EMPTY_BUFFER {0, NULL}
+
+/*
+ * Some alternate names for a couple of the above
+ * values. These are defined for V1 compatibility.
+ */
+#define GSS_C_NULL_OID GSS_C_NO_OID
+#define GSS_C_NULL_OID_SET GSS_C_NO_OID_SET
+
+/*
+ * Define the default Quality of Protection for per-message
+ * services. Note that an implementation that offers multiple
+ * levels of QOP may define GSS_C_QOP_DEFAULT to be either zero
+ * (as done here) to mean "default protection", or to a specific
+ * explicit QOP value. However, a value of 0 should always be
+ * interpreted by a GSSAPI implementation as a request for the
+ * default protection level.
+ */
+#define GSS_C_QOP_DEFAULT 0
+
+/*
+ * Expiration time of 2^32-1 seconds means infinite lifetime for a
+ * credential or security context
+ */
+#define GSS_C_INDEFINITE 0xfffffffful
+
+/*
+ * The implementation must reserve static storage for a
+ * gss_OID_desc object containing the value
+ * {10, (void *)"\x2a\x86\x48\x86\xf7\x12"
+ * "\x01\x02\x01\x01"},
+ * corresponding to an object-identifier value of
+ * {iso(1) member-body(2) United States(840) mit(113554)
+ * infosys(1) gssapi(2) generic(1) user_name(1)}. The constant
+ * GSS_C_NT_USER_NAME should be initialized to point
+ * to that gss_OID_desc.
+ */
+extern gss_OID GSS_C_NT_USER_NAME;
+
+/*
+ * The implementation must reserve static storage for a
+ * gss_OID_desc object containing the value
+ * {10, (void *)"\x2a\x86\x48\x86\xf7\x12"
+ * "\x01\x02\x01\x02"},
+ * corresponding to an object-identifier value of
+ * {iso(1) member-body(2) United States(840) mit(113554)
+ * infosys(1) gssapi(2) generic(1) machine_uid_name(2)}.
+ * The constant GSS_C_NT_MACHINE_UID_NAME should be
+ * initialized to point to that gss_OID_desc.
+ */
+extern gss_OID GSS_C_NT_MACHINE_UID_NAME;
+
+/*
+ * The implementation must reserve static storage for a
+ * gss_OID_desc object containing the value
+ * {10, (void *)"\x2a\x86\x48\x86\xf7\x12"
+ * "\x01\x02\x01\x03"},
+ * corresponding to an object-identifier value of
+ * {iso(1) member-body(2) United States(840) mit(113554)
+ * infosys(1) gssapi(2) generic(1) string_uid_name(3)}.
+ * The constant GSS_C_NT_STRING_UID_NAME should be
+ * initialized to point to that gss_OID_desc.
+ */
+extern gss_OID GSS_C_NT_STRING_UID_NAME;
+
+/*
+ * The implementation must reserve static storage for a
+ * gss_OID_desc object containing the value
+ * {6, (void *)"\x2b\x06\x01\x05\x06\x02"},
+ * corresponding to an object-identifier value of
+ * {iso(1) org(3) dod(6) internet(1) security(5)
+ * nametypes(6) gss-host-based-services(2)). The constant
+ * GSS_C_NT_HOSTBASED_SERVICE_X should be initialized to point
+ * to that gss_OID_desc. This is a deprecated OID value, and
+ * implementations wishing to support hostbased-service names
+ * should instead use the GSS_C_NT_HOSTBASED_SERVICE OID,
+ * defined below, to identify such names;
+ * GSS_C_NT_HOSTBASED_SERVICE_X should be accepted a synonym
+ * for GSS_C_NT_HOSTBASED_SERVICE when presented as an input
+ * parameter, but should not be emitted by GSSAPI
+ * implementations
+ */
+extern gss_OID GSS_C_NT_HOSTBASED_SERVICE_X;
+
+/*
+ * The implementation must reserve static storage for a
+ * gss_OID_desc object containing the value
+ * {10, (void *)"\x2a\x86\x48\x86\xf7\x12"
+ * "\x01\x02\x01\x04"}, corresponding to an
+ * object-identifier value of {iso(1) member-body(2)
+ * Unites States(840) mit(113554) infosys(1) gssapi(2)
+ * generic(1) service_name(4)}. The constant
+ * GSS_C_NT_HOSTBASED_SERVICE should be initialized
+ * to point to that gss_OID_desc.
+ */
+extern gss_OID GSS_C_NT_HOSTBASED_SERVICE;
+
+
+/*
+ * The implementation must reserve static storage for a
+ * gss_OID_desc object containing the value
+ * {6, (void *)"\x2b\x06\01\x05\x06\x03"},
+ * corresponding to an object identifier value of
+ * {1(iso), 3(org), 6(dod), 1(internet), 5(security),
+ * 6(nametypes), 3(gss-anonymous-name)}. The constant
+ * and GSS_C_NT_ANONYMOUS should be initialized to point
+ * to that gss_OID_desc.
+ */
+extern gss_OID GSS_C_NT_ANONYMOUS;
+
+/*
+ * The implementation must reserve static storage for a
+ * gss_OID_desc object containing the value
+ * {6, (void *)"\x2b\x06\x01\x05\x06\x04"},
+ * corresponding to an object-identifier value of
+ * {1(iso), 3(org), 6(dod), 1(internet), 5(security),
+ * 6(nametypes), 4(gss-api-exported-name)}. The constant
+ * GSS_C_NT_EXPORT_NAME should be initialized to point
+ * to that gss_OID_desc.
+ */
+extern gss_OID GSS_C_NT_EXPORT_NAME;
+
+/* Major status codes */
+
+#define GSS_S_COMPLETE 0
+
+/*
+ * Some "helper" definitions to make the status code macros obvious.
+ */
+#define GSS_C_CALLING_ERROR_OFFSET 24
+#define GSS_C_ROUTINE_ERROR_OFFSET 16
+#define GSS_C_SUPPLEMENTARY_OFFSET 0
+#define GSS_C_CALLING_ERROR_MASK 0377ul
+#define GSS_C_ROUTINE_ERROR_MASK 0377ul
+#define GSS_C_SUPPLEMENTARY_MASK 0177777ul
+
+/*
+ * The macros that test status codes for error conditions.
+ * Note that the GSS_ERROR() macro has changed slightly from
+ * the V1 GSSAPI so that it now evaluates its argument
+ * only once.
+ */
+#define GSS_CALLING_ERROR(x) \
+(x & (GSS_C_CALLING_ERROR_MASK << GSS_C_CALLING_ERROR_OFFSET))
+#define GSS_ROUTINE_ERROR(x) \
+ (x & (GSS_C_ROUTINE_ERROR_MASK << GSS_C_ROUTINE_ERROR_OFFSET))
+#define GSS_SUPPLEMENTARY_INFO(x) \
+ (x & (GSS_C_SUPPLEMENTARY_MASK << GSS_C_SUPPLEMENTARY_OFFSET))
+#define GSS_ERROR(x) \
+ (x & ((GSS_C_CALLING_ERROR_MASK << GSS_C_CALLING_ERROR_OFFSET) | \
+ (GSS_C_ROUTINE_ERROR_MASK << GSS_C_ROUTINE_ERROR_OFFSET)))
+
+/*
+ * Now the actual status code definitions
+ */
+
+/*
+ * Calling errors:
+ */
+#define GSS_S_CALL_INACCESSIBLE_READ \
+ (1ul << GSS_C_CALLING_ERROR_OFFSET)
+#define GSS_S_CALL_INACCESSIBLE_WRITE \
+ (2ul << GSS_C_CALLING_ERROR_OFFSET)
+#define GSS_S_CALL_BAD_STRUCTURE \
+ (3ul << GSS_C_CALLING_ERROR_OFFSET)
+
+/*
+ * Routine errors:
+ */
+#define GSS_S_BAD_MECH (1ul << GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_BAD_NAME (2ul << GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_BAD_NAMETYPE (3ul << GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_BAD_BINDINGS (4ul << GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_BAD_STATUS (5ul << GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_BAD_SIG (6ul << GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_BAD_MIC GSS_S_BAD_SIG
+#define GSS_S_NO_CRED (7ul << GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_NO_CONTEXT (8ul << GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_DEFECTIVE_TOKEN (9ul << GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_DEFECTIVE_CREDENTIAL (10ul << GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_CREDENTIALS_EXPIRED (11ul << GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_CONTEXT_EXPIRED (12ul << GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_FAILURE (13ul << GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_BAD_QOP (14ul << GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_UNAUTHORIZED (15ul << GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_UNAVAILABLE (16ul << GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_DUPLICATE_ELEMENT (17ul << GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_NAME_NOT_MN (18ul << GSS_C_ROUTINE_ERROR_OFFSET)
+
+/*
+ * Supplementary info bits:
+ */
+#define GSS_S_CONTINUE_NEEDED (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 0))
+#define GSS_S_DUPLICATE_TOKEN (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 1))
+#define GSS_S_OLD_TOKEN (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 2))
+#define GSS_S_UNSEQ_TOKEN (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 3))
+#define GSS_S_GAP_TOKEN (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 4))
+
+/*
+ * Finally, function prototypes for the GSS-API routines.
+ */
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_acquire_cred)
+(OM_uint32 *, /* minor_status */
+ const gss_name_t, /* desired_name */
+ OM_uint32, /* time_req */
+ const gss_OID_set, /* desired_mechs */
+ gss_cred_usage_t, /* cred_usage */
+ gss_cred_id_t *, /* output_cred_handle */
+ gss_OID_set *, /* actual_mechs */
+ OM_uint32 * /* time_rec */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_release_cred)
+(OM_uint32 *, /* minor_status */
+ gss_cred_id_t * /* cred_handle */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_init_sec_context)
+(OM_uint32 *, /* minor_status */
+ const gss_cred_id_t, /* initiator_cred_handle */
+ gss_ctx_id_t *, /* context_handle */
+ const gss_name_t, /* target_name */
+ const gss_OID, /* mech_type */
+ OM_uint32, /* req_flags */
+ OM_uint32, /* time_req */
+ const gss_channel_bindings_t, /* input_chan_bindings */
+ const gss_buffer_t, /* input_token */
+ gss_OID *, /* actual_mech_type */
+ gss_buffer_t, /* output_token */
+ OM_uint32 *, /* ret_flags */
+ OM_uint32 * /* time_rec */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_accept_sec_context)
+(OM_uint32 *, /* minor_status */
+ gss_ctx_id_t *, /* context_handle */
+ const gss_cred_id_t, /* acceptor_cred_handle */
+ const gss_buffer_t, /* input_token_buffer */
+ const gss_channel_bindings_t, /* input_chan_bindings */
+ gss_name_t *, /* src_name */
+ gss_OID *, /* mech_type */
+ gss_buffer_t, /* output_token */
+ OM_uint32 *, /* ret_flags */
+ OM_uint32 *, /* time_rec */
+ gss_cred_id_t * /* delegated_cred_handle */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_process_context_token)
+(OM_uint32 *, /* minor_status */
+ const gss_ctx_id_t, /* context_handle */
+ const gss_buffer_t /* token_buffer */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_delete_sec_context)
+(OM_uint32 *, /* minor_status */
+ gss_ctx_id_t *, /* context_handle */
+ gss_buffer_t /* output_token */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_context_time)
+(OM_uint32 *, /* minor_status */
+ const gss_ctx_id_t, /* context_handle */
+ OM_uint32 * /* time_rec */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_get_mic)
+(OM_uint32 *, /* minor_status */
+ const gss_ctx_id_t, /* context_handle */
+ gss_qop_t, /* qop_req */
+ const gss_buffer_t, /* message_buffer */
+ gss_buffer_t /* message_token */
+ );
+
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_verify_mic)
+(OM_uint32 *, /* minor_status */
+ const gss_ctx_id_t, /* context_handle */
+ const gss_buffer_t, /* message_buffer */
+ const gss_buffer_t, /* token_buffer */
+ gss_qop_t * /* qop_state */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_wrap)
+(OM_uint32 *, /* minor_status */
+ const gss_ctx_id_t, /* context_handle */
+ int, /* conf_req_flag */
+ gss_qop_t, /* qop_req */
+ const gss_buffer_t, /* input_message_buffer */
+ int *, /* conf_state */
+ gss_buffer_t /* output_message_buffer */
+ );
+
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_unwrap)
+(OM_uint32 *, /* minor_status */
+ const gss_ctx_id_t, /* context_handle */
+ const gss_buffer_t, /* input_message_buffer */
+ gss_buffer_t, /* output_message_buffer */
+ int *, /* conf_state */
+ gss_qop_t * /* qop_state */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_display_status)
+(OM_uint32 *, /* minor_status */
+ OM_uint32, /* status_value */
+ int, /* status_type */
+ const gss_OID, /* mech_type */
+ OM_uint32 *, /* message_context */
+ gss_buffer_t /* status_string */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_indicate_mechs)
+(OM_uint32 *, /* minor_status */
+ gss_OID_set * /* mech_set */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_compare_name)
+(OM_uint32 *, /* minor_status */
+ const gss_name_t, /* name1 */
+ const gss_name_t, /* name2 */
+ int * /* name_equal */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_display_name)
+(OM_uint32 *, /* minor_status */
+ const gss_name_t, /* input_name */
+ gss_buffer_t, /* output_name_buffer */
+ gss_OID * /* output_name_type */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_import_name)
+(OM_uint32 *, /* minor_status */
+ const gss_buffer_t, /* input_name_buffer */
+ const gss_OID, /* input_name_type */
+ gss_name_t * /* output_name */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_export_name)
+(OM_uint32 *, /* minor_status */
+ const gss_name_t, /* input_name */
+ gss_buffer_t /* exported_name */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_release_name)
+(OM_uint32 *, /* minor_status */
+ gss_name_t * /* input_name */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_release_buffer)
+(OM_uint32 *, /* minor_status */
+ gss_buffer_t /* buffer */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_release_oid_set)
+(OM_uint32 *, /* minor_status */
+ gss_OID_set * /* set */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_inquire_cred)
+(OM_uint32 *, /* minor_status */
+ const gss_cred_id_t, /* cred_handle */
+ gss_name_t *, /* name */
+ OM_uint32 *, /* lifetime */
+ gss_cred_usage_t *, /* cred_usage */
+ gss_OID_set * /* mechanisms */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_inquire_context)
+(OM_uint32 *, /* minor_status */
+ const gss_ctx_id_t, /* context_handle */
+ gss_name_t *, /* src_name */
+ gss_name_t *, /* targ_name */
+ OM_uint32 *, /* lifetime_rec */
+ gss_OID *, /* mech_type */
+ OM_uint32 *, /* ctx_flags */
+ int *, /* locally_initiated */
+ int * /* open */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_wrap_size_limit)
+(OM_uint32 *, /* minor_status */
+ const gss_ctx_id_t, /* context_handle */
+ int, /* conf_req_flag */
+ gss_qop_t, /* qop_req */
+ OM_uint32, /* req_output_size */
+ OM_uint32 * /* max_input_size */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_add_cred)
+(OM_uint32 *, /* minor_status */
+ const gss_cred_id_t, /* input_cred_handle */
+ const gss_name_t, /* desired_name */
+ const gss_OID, /* desired_mech */
+ gss_cred_usage_t, /* cred_usage */
+ OM_uint32, /* initiator_time_req */
+ OM_uint32, /* acceptor_time_req */
+ gss_cred_id_t *, /* output_cred_handle */
+ gss_OID_set *, /* actual_mechs */
+ OM_uint32 *, /* initiator_time_rec */
+ OM_uint32 * /* acceptor_time_rec */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_inquire_cred_by_mech)
+(OM_uint32 *, /* minor_status */
+ const gss_cred_id_t, /* cred_handle */
+ const gss_OID, /* mech_type */
+ gss_name_t *, /* name */
+ OM_uint32 *, /* initiator_lifetime */
+ OM_uint32 *, /* acceptor_lifetime */
+ gss_cred_usage_t * /* cred_usage */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_export_sec_context)
+(OM_uint32 *, /* minor_status */
+ gss_ctx_id_t *, /* context_handle */
+ gss_buffer_t /* interprocess_token */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_import_sec_context)
+(OM_uint32 *, /* minor_status */
+ const gss_buffer_t, /* interprocess_token */
+ gss_ctx_id_t * /* context_handle */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_create_empty_oid_set)
+(OM_uint32 *, /* minor_status */
+ gss_OID_set * /* oid_set */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_add_oid_set_member)
+(OM_uint32 *, /* minor_status */
+ const gss_OID, /* member_oid */
+ gss_OID_set * /* oid_set */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_test_oid_set_member)
+(OM_uint32 *, /* minor_status */
+ const gss_OID, /* member */
+ const gss_OID_set, /* set */
+ int * /* present */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_inquire_names_for_mech)
+(OM_uint32 *, /* minor_status */
+ const gss_OID, /* mechanism */
+ gss_OID_set * /* name_types */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_inquire_mechs_for_name)
+(OM_uint32 *, /* minor_status */
+ const gss_name_t, /* input_name */
+ gss_OID_set * /* mech_types */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_canonicalize_name)
+(OM_uint32 *, /* minor_status */
+ const gss_name_t, /* input_name */
+ const gss_OID, /* mech_type */
+ gss_name_t * /* output_name */
+ );
+
+GSS_MAKE_TYPEDEF
+OM_uint32
+GSS_CALLCONV GSS_FUNC(gss_duplicate_name)
+(OM_uint32 *, /* minor_status */
+ const gss_name_t, /* src_name */
+ gss_name_t * /* dest_name */
+ );
+
+ /*
+ * The following routines are obsolete variants of gss_get_mic,
+ * gss_verify_mic, gss_wrap and gss_unwrap. They should be
+ * provided by GSSAPI V2 implementations for backwards
+ * compatibility with V1 applications. Distinct entrypoints
+ * (as opposed to #defines) should be provided, both to allow
+ * GSSAPI V1 applications to link against GSSAPI V2 implementations,
+ * and to retain the slight parameter type differences between the
+ * obsolete versions of these routines and their current forms.
+ */
+
+ GSS_MAKE_TYPEDEF
+ OM_uint32
+ GSS_CALLCONV GSS_FUNC(gss_sign)
+ (OM_uint32 *, /* minor_status */
+ gss_ctx_id_t, /* context_handle */
+ int, /* qop_req */
+ gss_buffer_t, /* message_buffer */
+ gss_buffer_t /* message_token */
+ );
+
+
+ GSS_MAKE_TYPEDEF
+ OM_uint32
+ GSS_CALLCONV GSS_FUNC(gss_verify)
+ (OM_uint32 *, /* minor_status */
+ gss_ctx_id_t, /* context_handle */
+ gss_buffer_t, /* message_buffer */
+ gss_buffer_t, /* token_buffer */
+ int * /* qop_state */
+ );
+
+ GSS_MAKE_TYPEDEF
+ OM_uint32
+ GSS_CALLCONV GSS_FUNC(gss_seal)
+ (OM_uint32 *, /* minor_status */
+ gss_ctx_id_t, /* context_handle */
+ int, /* conf_req_flag */
+ int, /* qop_req */
+ gss_buffer_t, /* input_message_buffer */
+ int *, /* conf_state */
+ gss_buffer_t /* output_message_buffer */
+ );
+
+
+ GSS_MAKE_TYPEDEF
+ OM_uint32
+ GSS_CALLCONV GSS_FUNC(gss_unseal)
+ (OM_uint32 *, /* minor_status */
+ gss_ctx_id_t, /* context_handle */
+ gss_buffer_t, /* input_message_buffer */
+ gss_buffer_t, /* output_message_buffer */
+ int *, /* conf_state */
+ int * /* qop_state */
+ );
+
+
+
+EXTERN_C_END
+
+#endif /* GSSAPI_H_ */
+
diff --git a/net/third_party/mozilla_security_manager/LICENSE b/net/third_party/mozilla_security_manager/LICENSE
new file mode 100644
index 0000000..17de8fb
--- /dev/null
+++ b/net/third_party/mozilla_security_manager/LICENSE
@@ -0,0 +1,35 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2001
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
diff --git a/net/third_party/mozilla_security_manager/README.chromium b/net/third_party/mozilla_security_manager/README.chromium
new file mode 100644
index 0000000..d030d65
--- /dev/null
+++ b/net/third_party/mozilla_security_manager/README.chromium
@@ -0,0 +1,12 @@
+Name: Mozilla Personal Security Manager
+URL: http://mxr.mozilla.org/mozilla1.9.2/source/security/manager/
+InfoURL: http://www.mozilla.org/
+Version: Mozilla 1.9.2
+
+Description:
+This is selected code bits from Mozilla's Personal Security Manager.
+
+Local Modifications:
+Files are forked from Mozilla's because of the heavy adaptations necessary.
+Differences are using Chromium localization and other libraries instead of
+Mozilla's, matching the Chromium style, etc.
diff --git a/net/third_party/mozilla_security_manager/nsKeygenHandler.cpp b/net/third_party/mozilla_security_manager/nsKeygenHandler.cpp
new file mode 100644
index 0000000..ffef66d
--- /dev/null
+++ b/net/third_party/mozilla_security_manager/nsKeygenHandler.cpp
@@ -0,0 +1,270 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Vipul Gupta <vipul.gupta@sun.com>
+ * Douglas Stebila <douglas@stebila.ca>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "net/third_party/mozilla_security_manager/nsKeygenHandler.h"
+
+#include <pk11pub.h>
+#include <prerror.h> // PR_GetError()
+#include <secmod.h>
+#include <secder.h> // DER_Encode()
+#include <cryptohi.h> // SEC_DerSignData()
+#include <keyhi.h> // SECKEY_CreateSubjectPublicKeyInfo()
+
+#include "base/base64.h"
+#include "base/nss_util_internal.h"
+#include "base/nss_util.h"
+#include "base/logging.h"
+
+namespace {
+
+// Template for creating the signed public key structure to be sent to the CA.
+DERTemplate SECAlgorithmIDTemplate[] = {
+ { DER_SEQUENCE,
+ 0, NULL, sizeof(SECAlgorithmID) },
+ { DER_OBJECT_ID,
+ offsetof(SECAlgorithmID, algorithm), },
+ { DER_OPTIONAL | DER_ANY,
+ offsetof(SECAlgorithmID, parameters), },
+ { 0, }
+};
+
+DERTemplate CERTSubjectPublicKeyInfoTemplate[] = {
+ { DER_SEQUENCE,
+ 0, NULL, sizeof(CERTSubjectPublicKeyInfo) },
+ { DER_INLINE,
+ offsetof(CERTSubjectPublicKeyInfo, algorithm),
+ SECAlgorithmIDTemplate, },
+ { DER_BIT_STRING,
+ offsetof(CERTSubjectPublicKeyInfo, subjectPublicKey), },
+ { 0, }
+};
+
+DERTemplate CERTPublicKeyAndChallengeTemplate[] = {
+ { DER_SEQUENCE,
+ 0, NULL, sizeof(CERTPublicKeyAndChallenge) },
+ { DER_ANY,
+ offsetof(CERTPublicKeyAndChallenge, spki), },
+ { DER_IA5_STRING,
+ offsetof(CERTPublicKeyAndChallenge, challenge), },
+ { 0, }
+};
+
+} // namespace
+
+namespace mozilla_security_manager {
+
+// This function is based on the nsKeygenFormProcessor::GetPublicKey function
+// in mozilla/security/manager/ssl/src/nsKeygenHandler.cpp.
+std::string GenKeyAndSignChallenge(int key_size_in_bits,
+ const std::string& challenge,
+ bool stores_key) {
+ // Key pair generation mechanism - only RSA is supported at present.
+ PRUint32 keyGenMechanism = CKM_RSA_PKCS_KEY_PAIR_GEN; // from nss/pkcs11t.h
+
+ // Temporary structures used for generating the result
+ // in the right format.
+ PK11SlotInfo *slot = NULL;
+ PK11RSAGenParams rsaKeyGenParams; // Keygen parameters.
+ SECOidTag algTag; // used by SEC_DerSignData().
+ SECKEYPrivateKey *privateKey = NULL;
+ SECKEYPublicKey *publicKey = NULL;
+ CERTSubjectPublicKeyInfo *spkInfo = NULL;
+ PRArenaPool *arena = NULL;
+ SECStatus sec_rv =SECFailure;
+ SECItem spkiItem;
+ SECItem pkacItem;
+ SECItem signedItem;
+ CERTPublicKeyAndChallenge pkac;
+ void *keyGenParams;
+ bool isSuccess = true; // Set to false as soon as a step fails.
+
+ std::string result_blob; // the result.
+
+ // Ensure NSS is initialized.
+ base::EnsureNSSInit();
+
+ slot = base::GetDefaultNSSKeySlot();
+ if (!slot) {
+ LOG(ERROR) << "Couldn't get Internal key slot!";
+ isSuccess = false;
+ goto failure;
+ }
+
+ switch (keyGenMechanism) {
+ case CKM_RSA_PKCS_KEY_PAIR_GEN:
+ rsaKeyGenParams.keySizeInBits = key_size_in_bits;
+ rsaKeyGenParams.pe = DEFAULT_RSA_KEYGEN_PE;
+ keyGenParams = &rsaKeyGenParams;
+
+ algTag = DEFAULT_RSA_KEYGEN_ALG;
+ break;
+ default:
+ // TODO(gauravsh): If we ever support other mechanisms,
+ // this can be changed.
+ LOG(ERROR) << "Only RSA keygen mechanism is supported";
+ isSuccess = false;
+ goto failure;
+ }
+
+ // Need to make sure that the token was initialized.
+ // Assume a null password.
+ sec_rv = PK11_Authenticate(slot, PR_TRUE, NULL);
+ if (SECSuccess != sec_rv) {
+ LOG(ERROR) << "Couldn't initialze PK11 token!";
+ isSuccess = false;
+ goto failure;
+ }
+
+ LOG(INFO) << "Creating key pair...";
+ {
+ base::AutoNSSWriteLock lock;
+ privateKey = PK11_GenerateKeyPair(slot,
+ keyGenMechanism,
+ keyGenParams,
+ &publicKey,
+ PR_TRUE, // isPermanent?
+ PR_TRUE, // isSensitive?
+ NULL);
+ }
+ LOG(INFO) << "done.";
+
+ if (!privateKey) {
+ LOG(INFO) << "Generation of Keypair failed!";
+ isSuccess = false;
+ goto failure;
+ }
+
+ // The CA expects the signed public key in a specific format
+ // Let's create that now.
+
+ // Create a subject public key info from the public key.
+ spkInfo = SECKEY_CreateSubjectPublicKeyInfo(publicKey);
+ if (!spkInfo) {
+ LOG(ERROR) << "Couldn't create SubjectPublicKeyInfo from public key";
+ isSuccess = false;
+ goto failure;
+ }
+
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena) {
+ LOG(ERROR) << "PORT_NewArena: Couldn't allocate memory";
+ isSuccess = false;
+ goto failure;
+ }
+
+ // DER encode the whole subjectPublicKeyInfo.
+ sec_rv = DER_Encode(arena, &spkiItem, CERTSubjectPublicKeyInfoTemplate,
+ spkInfo);
+ if (SECSuccess != sec_rv) {
+ LOG(ERROR) << "Couldn't DER Encode subjectPublicKeyInfo";
+ isSuccess = false;
+ goto failure;
+ }
+
+ // Set up the PublicKeyAndChallenge data structure, then DER encode it.
+ pkac.spki = spkiItem;
+ pkac.challenge.type = siBuffer;
+ pkac.challenge.len = challenge.length();
+ pkac.challenge.data = (unsigned char *)challenge.data();
+ sec_rv = DER_Encode(arena, &pkacItem, CERTPublicKeyAndChallengeTemplate,
+ &pkac);
+ if (SECSuccess != sec_rv) {
+ LOG(ERROR) << "Couldn't DER Encode PublicKeyAndChallenge";
+ isSuccess = false;
+ goto failure;
+ }
+
+ // Sign the DER encoded PublicKeyAndChallenge.
+ sec_rv = SEC_DerSignData(arena, &signedItem, pkacItem.data, pkacItem.len,
+ privateKey, algTag);
+ if (SECSuccess != sec_rv) {
+ LOG(ERROR) << "Couldn't sign the DER encoded PublicKeyandChallenge";
+ isSuccess = false;
+ goto failure;
+ }
+
+ // Convert the signed public key and challenge into base64/ascii.
+ if (!base::Base64Encode(std::string(reinterpret_cast<char*>(signedItem.data),
+ signedItem.len),
+ &result_blob)) {
+ LOG(ERROR) << "Couldn't convert signed public key into base64";
+ isSuccess = false;
+ goto failure;
+ }
+
+ failure:
+ if (!isSuccess) {
+ LOG(ERROR) << "SSL Keygen failed! (NSS error code " << PR_GetError() << ")";
+ } else {
+ LOG(INFO) << "SSL Keygen succeeded!";
+ }
+
+ // Do cleanups
+ if (privateKey) {
+ // On successful keygen we need to keep the private key, of course,
+ // or we won't be able to use the client certificate.
+ if (!isSuccess || !stores_key) {
+ base::AutoNSSWriteLock lock;
+ PK11_DestroyTokenObject(privateKey->pkcs11Slot, privateKey->pkcs11ID);
+ }
+ SECKEY_DestroyPrivateKey(privateKey);
+ }
+
+ if (publicKey) {
+ if (!isSuccess || !stores_key) {
+ base::AutoNSSWriteLock lock;
+ PK11_DestroyTokenObject(publicKey->pkcs11Slot, publicKey->pkcs11ID);
+ }
+ SECKEY_DestroyPublicKey(publicKey);
+ }
+ if (spkInfo) {
+ SECKEY_DestroySubjectPublicKeyInfo(spkInfo);
+ }
+ if (arena) {
+ PORT_FreeArena(arena, PR_TRUE);
+ }
+ if (slot != NULL) {
+ PK11_FreeSlot(slot);
+ }
+
+ return (isSuccess ? result_blob : std::string());
+}
+
+} // namespace mozilla_security_manager
diff --git a/net/third_party/mozilla_security_manager/nsKeygenHandler.h b/net/third_party/mozilla_security_manager/nsKeygenHandler.h
new file mode 100644
index 0000000..75703bb
--- /dev/null
+++ b/net/third_party/mozilla_security_manager/nsKeygenHandler.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2001
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * David Drinan. (ddrinan@netscape.com)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef _NSKEYGENHANDLER_H_
+#define _NSKEYGENHANDLER_H_
+
+#include <string>
+
+namespace mozilla_security_manager {
+
+#define DEFAULT_RSA_KEYGEN_PE 65537L
+#define DEFAULT_RSA_KEYGEN_ALG SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION
+
+// Generates the key pair and the cert request (SPKAC), and returns a
+// base64-encoded string suitable for use as the form value of <keygen>.
+// Parameters:
+// key_size_in_bits: key size in bits (usually 2048)
+// challenge: challenge string sent by server
+// stores_key: should the generated key pair be stored persistently?
+std::string GenKeyAndSignChallenge(int key_size_in_bits,
+ const std::string& challenge,
+ bool stores_key);
+
+} // namespace mozilla_security_manager
+
+#endif //_NSKEYGENHANDLER_H_
diff --git a/net/third_party/nss/LICENSE b/net/third_party/nss/LICENSE
new file mode 100644
index 0000000..0367164
--- /dev/null
+++ b/net/third_party/nss/LICENSE
@@ -0,0 +1,35 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Netscape security libraries.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1994-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
diff --git a/net/third_party/nss/README.chromium b/net/third_party/nss/README.chromium
new file mode 100644
index 0000000..b1141fe
--- /dev/null
+++ b/net/third_party/nss/README.chromium
@@ -0,0 +1,32 @@
+Name: Network Security Services (NSS)
+URL: http://www.mozilla.org/projects/security/pki/nss/
+
+This directory includes a copy of NSS's libssl from the CVS repo at:
+ :pserver:anonymous@cvs-mirror.mozilla.org:/cvsroot
+
+The snapshot was updated to the CVS tag: NSS_3_12_6_RC0
+
+Patches:
+
+ * Next protocol negotiation support.
+ patches/nextproto.patch
+ http://codereview.chromium.org/415005
+
+ * False start support
+ patches/falsestart.patch
+
+ * Commenting out a couple of functions because they need NSS symbols
+ which may not exist in the system NSS library.
+ patches/versionskew.patch
+
+ * Send empty renegotiation info extension instead of SCSV unless TLS is
+ disabled.
+ patches/renegoscsv.patch
+ https://bugzilla.mozilla.org/show_bug.cgi?id=549042
+
+ * Cache the peer's intermediate CA certificates in session ID, so that
+ they're available when we resume a session.
+ patches/cachecerts.patch
+
+The ssl/bodge directory contains files taken from the NSS repo that we required
+for building libssl outside of its usual build environment.
diff --git a/net/third_party/nss/SConstruct b/net/third_party/nss/SConstruct
new file mode 100644
index 0000000..8b7134d
--- /dev/null
+++ b/net/third_party/nss/SConstruct
@@ -0,0 +1,2 @@
+# This file is generated; do not edit.
+SConscript('nss_main.scons')
diff --git a/net/third_party/nss/nss_main.scons b/net/third_party/nss/nss_main.scons
new file mode 100644
index 0000000..b310129
--- /dev/null
+++ b/net/third_party/nss/nss_main.scons
@@ -0,0 +1,326 @@
+# This file is generated; do not edit.
+
+__doc__ = '''
+Wrapper configuration for building this entire "solution,"
+including all the specific targets in various *.scons files.
+'''
+
+import os
+import sys
+
+import SCons.Environment
+import SCons.Util
+
+def GetProcessorCount():
+ '''
+ Detects the number of CPUs on the system. Adapted form:
+ http://codeliberates.blogspot.com/2008/05/detecting-cpuscores-in-python.html
+ '''
+ # Linux, Unix and Mac OS X:
+ if hasattr(os, 'sysconf'):
+ if os.sysconf_names.has_key('SC_NPROCESSORS_ONLN'):
+ # Linux and Unix or Mac OS X with python >= 2.5:
+ return os.sysconf('SC_NPROCESSORS_ONLN')
+ else: # Mac OS X with Python < 2.5:
+ return int(os.popen2("sysctl -n hw.ncpu")[1].read())
+ # Windows:
+ if os.environ.has_key('NUMBER_OF_PROCESSORS'):
+ return max(int(os.environ.get('NUMBER_OF_PROCESSORS', '1')), 1)
+ return 1 # Default
+
+# Support PROGRESS= to show progress in different ways.
+p = ARGUMENTS.get('PROGRESS')
+if p == 'spinner':
+ Progress(['/\r', '|\r', '\\\r', '-\r'],
+ interval=5,
+ file=open('/dev/tty', 'w'))
+elif p == 'name':
+ Progress('$TARGET\r', overwrite=True, file=open('/dev/tty', 'w'))
+
+# Set the default -j value based on the number of processors.
+SetOption('num_jobs', GetProcessorCount() + 1)
+
+# Have SCons use its cached dependency information.
+SetOption('implicit_cache', 1)
+
+# Only re-calculate MD5 checksums if a timestamp has changed.
+Decider('MD5-timestamp')
+
+# Since we set the -j value by default, suppress SCons warnings about being
+# unable to support parallel build on versions of Python with no threading.
+default_warnings = ['no-no-parallel-support']
+SetOption('warn', default_warnings + GetOption('warn'))
+
+AddOption('--mode', nargs=1, dest='conf_list', default=[],
+ action='append', help='Configuration to build.')
+
+AddOption('--verbose', dest='verbose', default=False,
+ action='store_true', help='Verbose command-line output.')
+
+
+#
+sconscript_file_map = dict(
+ ssl = 'ssl.scons',
+)
+
+class LoadTarget:
+ '''
+ Class for deciding if a given target sconscript is to be included
+ based on a list of included target names, optionally prefixed with '-'
+ to exclude a target name.
+ '''
+ def __init__(self, load):
+ '''
+ Initialize a class with a list of names for possible loading.
+
+ Arguments:
+ load: list of elements in the LOAD= specification
+ '''
+ self.included = set([c for c in load if not c.startswith('-')])
+ self.excluded = set([c[1:] for c in load if c.startswith('-')])
+
+ if not self.included:
+ self.included = set(['all'])
+
+ def __call__(self, target):
+ '''
+ Returns True if the specified target's sconscript file should be
+ loaded, based on the initialized included and excluded lists.
+ '''
+ return (target in self.included or
+ ('all' in self.included and not target in self.excluded))
+
+if 'LOAD' in ARGUMENTS:
+ load = ARGUMENTS['LOAD'].split(',')
+else:
+ load = []
+load_target = LoadTarget(load)
+
+sconscript_files = []
+for target, sconscript in sconscript_file_map.iteritems():
+ if load_target(target):
+ sconscript_files.append(sconscript)
+
+
+target_alias_list= []
+
+conf_list = GetOption('conf_list')
+if conf_list:
+ # In case the same --mode= value was specified multiple times.
+ conf_list = list(set(conf_list))
+else:
+ conf_list = ['Debug']
+
+sconsbuild_dir = Dir('../../../sconsbuild')
+
+
+def FilterOut(self, **kw):
+ kw = SCons.Environment.copy_non_reserved_keywords(kw)
+ for key, val in kw.items():
+ envval = self.get(key, None)
+ if envval is None:
+ # No existing variable in the environment, so nothing to delete.
+ continue
+
+ for vremove in val:
+ # Use while not if, so we can handle duplicates.
+ while vremove in envval:
+ envval.remove(vremove)
+
+ self[key] = envval
+
+ # TODO(sgk): SCons.Environment.Append() has much more logic to deal
+ # with various types of values. We should handle all those cases in here
+ # too. (If variable is a dict, etc.)
+
+
+non_compilable_suffixes = {
+ 'LINUX' : set([
+ '.bdic',
+ '.css',
+ '.dat',
+ '.fragment',
+ '.gperf',
+ '.h',
+ '.hh',
+ '.hpp',
+ '.html',
+ '.hxx',
+ '.idl',
+ '.in',
+ '.in0',
+ '.in1',
+ '.js',
+ '.mk',
+ '.rc',
+ '.sigs',
+ '',
+ ]),
+ 'WINDOWS' : set([
+ '.h',
+ '.hh',
+ '.hpp',
+ '.dat',
+ '.idl',
+ '.in',
+ '.in0',
+ '.in1',
+ ]),
+}
+
+def compilable(env, file):
+ base, ext = os.path.splitext(str(file))
+ if ext in non_compilable_suffixes[env['TARGET_PLATFORM']]:
+ return False
+ return True
+
+def compilable_files(env, sources):
+ return [x for x in sources if compilable(env, x)]
+
+def GypProgram(env, target, source, *args, **kw):
+ source = compilable_files(env, source)
+ result = env.Program(target, source, *args, **kw)
+ if env.get('INCREMENTAL'):
+ env.Precious(result)
+ return result
+
+def GypTestProgram(env, target, source, *args, **kw):
+ source = compilable_files(env, source)
+ result = env.Program(target, source, *args, **kw)
+ if env.get('INCREMENTAL'):
+ env.Precious(*result)
+ return result
+
+def GypLibrary(env, target, source, *args, **kw):
+ source = compilable_files(env, source)
+ result = env.Library(target, source, *args, **kw)
+ return result
+
+def GypLoadableModule(env, target, source, *args, **kw):
+ source = compilable_files(env, source)
+ result = env.LoadableModule(target, source, *args, **kw)
+ return result
+
+def GypStaticLibrary(env, target, source, *args, **kw):
+ source = compilable_files(env, source)
+ result = env.StaticLibrary(target, source, *args, **kw)
+ return result
+
+def GypSharedLibrary(env, target, source, *args, **kw):
+ source = compilable_files(env, source)
+ result = env.SharedLibrary(target, source, *args, **kw)
+ if env.get('INCREMENTAL'):
+ env.Precious(result)
+ return result
+
+def add_gyp_methods(env):
+ env.AddMethod(GypProgram)
+ env.AddMethod(GypTestProgram)
+ env.AddMethod(GypLibrary)
+ env.AddMethod(GypLoadableModule)
+ env.AddMethod(GypStaticLibrary)
+ env.AddMethod(GypSharedLibrary)
+
+ env.AddMethod(FilterOut)
+
+ env.AddMethod(compilable)
+
+
+base_env = Environment(
+ tools = ['ar', 'as', 'gcc', 'g++', 'gnulink', 'chromium_builders'],
+ INTERMEDIATE_DIR='$OBJ_DIR/${COMPONENT_NAME}/_${TARGET_NAME}_intermediate',
+ LIB_DIR='$TOP_BUILDDIR/lib',
+ OBJ_DIR='$TOP_BUILDDIR/obj',
+ SCONSBUILD_DIR=sconsbuild_dir.abspath,
+ SHARED_INTERMEDIATE_DIR='$OBJ_DIR/_global_intermediate',
+ SRC_DIR=Dir('../../..'),
+ TARGET_PLATFORM='LINUX',
+ TOP_BUILDDIR='$SCONSBUILD_DIR/$CONFIG_NAME',
+ LIBPATH=['$LIB_DIR'],
+)
+
+if not GetOption('verbose'):
+ base_env.SetDefault(
+ ARCOMSTR='Creating library $TARGET',
+ ASCOMSTR='Assembling $TARGET',
+ CCCOMSTR='Compiling $TARGET',
+ CONCATSOURCECOMSTR='ConcatSource $TARGET',
+ CXXCOMSTR='Compiling $TARGET',
+ LDMODULECOMSTR='Building loadable module $TARGET',
+ LINKCOMSTR='Linking $TARGET',
+ MANIFESTCOMSTR='Updating manifest for $TARGET',
+ MIDLCOMSTR='Compiling IDL $TARGET',
+ PCHCOMSTR='Precompiling $TARGET',
+ RANLIBCOMSTR='Indexing $TARGET',
+ RCCOMSTR='Compiling resource $TARGET',
+ SHCCCOMSTR='Compiling $TARGET',
+ SHCXXCOMSTR='Compiling $TARGET',
+ SHLINKCOMSTR='Linking $TARGET',
+ SHMANIFESTCOMSTR='Updating manifest for $TARGET',
+ )
+
+add_gyp_methods(base_env)
+
+for conf in conf_list:
+ env = base_env.Clone(CONFIG_NAME=conf)
+ SConsignFile(env.File('$TOP_BUILDDIR/.sconsign').abspath)
+ for sconscript in sconscript_files:
+ target_alias = env.SConscript(sconscript, exports=['env'])
+ if target_alias:
+ target_alias_list.extend(target_alias)
+
+Default(Alias('all', target_alias_list))
+
+help_fmt = '''
+Usage: hammer [SCONS_OPTIONS] [VARIABLES] [TARGET] ...
+
+Local command-line build options:
+ --mode=CONFIG Configuration to build:
+ --mode=Debug [default]
+ --mode=Release
+ --verbose Print actual executed command lines.
+
+Supported command-line build variables:
+ LOAD=[module,...] Comma-separated list of components to load in the
+ dependency graph ('-' prefix excludes)
+ PROGRESS=type Display a progress indicator:
+ name: print each evaluated target name
+ spinner: print a spinner every 5 targets
+
+The following TARGET names can also be used as LOAD= module names:
+
+%s
+'''
+
+if GetOption('help'):
+ def columnar_text(items, width=78, indent=2, sep=2):
+ result = []
+ colwidth = max(map(len, items)) + sep
+ cols = (width - indent) / colwidth
+ if cols < 1:
+ cols = 1
+ rows = (len(items) + cols - 1) / cols
+ indent = '%*s' % (indent, '')
+ sep = indent
+ for row in xrange(0, rows):
+ result.append(sep)
+ for i in xrange(row, len(items), rows):
+ result.append('%-*s' % (colwidth, items[i]))
+ sep = '\n' + indent
+ result.append('\n')
+ return ''.join(result)
+
+ load_list = set(sconscript_file_map.keys())
+ target_aliases = set(map(str, target_alias_list))
+
+ common = load_list and target_aliases
+ load_only = load_list - common
+ target_only = target_aliases - common
+ help_text = [help_fmt % columnar_text(sorted(list(common)))]
+ if target_only:
+ fmt = "The following are additional TARGET names:\n\n%s\n"
+ help_text.append(fmt % columnar_text(sorted(list(target_only))))
+ if load_only:
+ fmt = "The following are additional LOAD= module names:\n\n%s\n"
+ help_text.append(fmt % columnar_text(sorted(list(load_only))))
+ Help(''.join(help_text))
diff --git a/net/third_party/nss/patches/cachecerts.patch b/net/third_party/nss/patches/cachecerts.patch
new file mode 100644
index 0000000..c91ad60
--- /dev/null
+++ b/net/third_party/nss/patches/cachecerts.patch
@@ -0,0 +1,124 @@
+diff --git a/mozilla/security/nss/lib/ssl/ssl3con.c b/mozilla/security/nss/lib/ssl/ssl3con.c
+index 45bf853..e3f9a9a 100644
+--- a/mozilla/security/nss/lib/ssl/ssl3con.c
++++ b/mozilla/security/nss/lib/ssl/ssl3con.c
+@@ -72,6 +72,7 @@
+ #endif
+
+ static void ssl3_CleanupPeerCerts(sslSocket *ss);
++static void ssl3_CopyPeerCertsFromSID(sslSocket *ss, sslSessionID *sid);
+ static PK11SymKey *ssl3_GenerateRSAPMS(sslSocket *ss, ssl3CipherSpec *spec,
+ PK11SlotInfo * serverKeySlot);
+ static SECStatus ssl3_DeriveMasterSecret(sslSocket *ss, PK11SymKey *pms);
+@@ -5136,6 +5137,7 @@ ssl3_HandleServerHello(sslSocket *ss, SSL3Opaque *b, PRUint32 length)
+ /* copy the peer cert from the SID */
+ if (sid->peerCert != NULL) {
+ ss->sec.peerCert = CERT_DupCertificate(sid->peerCert);
++ ssl3_CopyPeerCertsFromSID(ss, sid);
+ }
+
+
+@@ -6378,6 +6380,7 @@ compression_found:
+ ss->sec.ci.sid = sid;
+ if (sid->peerCert != NULL) {
+ ss->sec.peerCert = CERT_DupCertificate(sid->peerCert);
++ ssl3_CopyPeerCertsFromSID(ss, sid);
+ }
+
+ /*
+@@ -7746,6 +7749,38 @@ ssl3_CleanupPeerCerts(sslSocket *ss)
+ ss->ssl3.peerCertChain = NULL;
+ }
+
++static void
++ssl3_CopyPeerCertsFromSID(sslSocket *ss, sslSessionID *sid)
++{
++ PRArenaPool *arena;
++ ssl3CertNode *certs = NULL;
++ int i;
++
++ if (!sid->peerCertChain[0])
++ return;
++ PORT_Assert(!ss->ssl3.peerCertArena);
++ PORT_Assert(!ss->ssl3.peerCertChain);
++ ss->ssl3.peerCertArena = arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
++ for (i = 0; i < MAX_PEER_CERT_CHAIN_SIZE && sid->peerCertChain[i]; i++) {
++ ssl3CertNode *c = PORT_ArenaNew(arena, ssl3CertNode);
++ c->cert = CERT_DupCertificate(sid->peerCertChain[i]);
++ c->next = certs;
++ certs = c;
++ }
++ ss->ssl3.peerCertChain = certs;
++}
++
++static void
++ssl3_CopyPeerCertsToSID(ssl3CertNode *certs, sslSessionID *sid)
++{
++ int i = 0;
++ ssl3CertNode *c = certs;
++ for (; i < MAX_PEER_CERT_CHAIN_SIZE && c; i++, c = c->next) {
++ PORT_Assert(!sid->peerCertChain[i]);
++ sid->peerCertChain[i] = CERT_DupCertificate(c->cert);
++ }
++}
++
+ /* Called from ssl3_HandleHandshakeMessage() when it has deciphered a complete
+ * ssl3 Certificate message.
+ * Caller must hold Handshake and RecvBuf locks.
+@@ -7932,6 +7967,7 @@ ssl3_HandleCertificate(sslSocket *ss, SSL3Opaque *b, PRUint32 length)
+ }
+
+ ss->sec.ci.sid->peerCert = CERT_DupCertificate(ss->sec.peerCert);
++ ssl3_CopyPeerCertsToSID(certs, ss->sec.ci.sid);
+
+ if (!ss->sec.isServer) {
+ /* set the server authentication and key exchange types and sizes
+@@ -8103,6 +8139,8 @@ ssl3_RestartHandshakeAfterServerCert(sslSocket *ss)
+ if (ss->handshake != NULL) {
+ ss->handshake = ssl_GatherRecord1stHandshake;
+ ss->sec.ci.sid->peerCert = CERT_DupCertificate(ss->sec.peerCert);
++ ssl3_CopyPeerCertsToSID((ssl3CertNode *)ss->ssl3.peerCertChain,
++ ss->sec.ci.sid);
+
+ ssl_GetRecvBufLock(ss);
+ if (ss->ssl3.hs.msgState.buf != NULL) {
+diff --git a/mozilla/security/nss/lib/ssl/sslimpl.h b/mozilla/security/nss/lib/ssl/sslimpl.h
+index a800d56..fe7ac7a 100644
+--- a/mozilla/security/nss/lib/ssl/sslimpl.h
++++ b/mozilla/security/nss/lib/ssl/sslimpl.h
+@@ -569,10 +569,13 @@ typedef enum { never_cached,
+ invalid_cache /* no longer in any cache. */
+ } Cached;
+
++#define MAX_PEER_CERT_CHAIN_SIZE 8
++
+ struct sslSessionIDStr {
+ sslSessionID * next; /* chain used for client sockets, only */
+
+ CERTCertificate * peerCert;
++ CERTCertificate * peerCertChain[MAX_PEER_CERT_CHAIN_SIZE];
+ const char * peerID; /* client only */
+ const char * urlSvrName; /* client only */
+ CERTCertificate * localCert;
+diff --git a/mozilla/security/nss/lib/ssl/sslnonce.c b/mozilla/security/nss/lib/ssl/sslnonce.c
+index 63dc5a2..64adc1f 100644
+--- a/mozilla/security/nss/lib/ssl/sslnonce.c
++++ b/mozilla/security/nss/lib/ssl/sslnonce.c
+@@ -197,6 +197,7 @@ lock_cache(void)
+ static void
+ ssl_DestroySID(sslSessionID *sid)
+ {
++ int i;
+ SSL_TRC(8, ("SSL: destroy sid: sid=0x%x cached=%d", sid, sid->cached));
+ PORT_Assert((sid->references == 0));
+
+@@ -216,6 +217,9 @@ ssl_DestroySID(sslSessionID *sid)
+ if ( sid->peerCert ) {
+ CERT_DestroyCertificate(sid->peerCert);
+ }
++ for (i = 0; i < MAX_PEER_CERT_CHAIN_SIZE && sid->peerCertChain[i]; i++) {
++ CERT_DestroyCertificate(sid->peerCertChain[i]);
++ }
+ if ( sid->localCert ) {
+ CERT_DestroyCertificate(sid->localCert);
+ }
diff --git a/net/third_party/nss/patches/falsestart.patch b/net/third_party/nss/patches/falsestart.patch
new file mode 100644
index 0000000..6a71159
--- /dev/null
+++ b/net/third_party/nss/patches/falsestart.patch
@@ -0,0 +1,355 @@
+Index: mozilla/security/nss/cmd/strsclnt/strsclnt.c
+===================================================================
+RCS file: /cvsroot/mozilla/security/nss/cmd/strsclnt/strsclnt.c,v
+retrieving revision 1.66
+diff -u -p -r1.66 strsclnt.c
+--- mozilla/security/nss/cmd/strsclnt/strsclnt.c 10 Feb 2010 18:07:20 -0000 1.66
++++ mozilla/security/nss/cmd/strsclnt/strsclnt.c 16 Mar 2010 01:25:41 -0000
+@@ -162,6 +162,7 @@ static PRBool disableLocking = PR_FALSE
+ static PRBool ignoreErrors = PR_FALSE;
+ static PRBool enableSessionTickets = PR_FALSE;
+ static PRBool enableCompression = PR_FALSE;
++static PRBool enableFalseStart = PR_FALSE;
+
+ PRIntervalTime maxInterval = PR_INTERVAL_NO_TIMEOUT;
+
+@@ -197,7 +198,8 @@ Usage(const char *progName)
+ " -U means enable throttling up threads\n"
+ " -B bypasses the PKCS11 layer for SSL encryption and MACing\n"
+ " -u enable TLS Session Ticket extension\n"
+- " -z enable compression\n",
++ " -z enable compression\n"
++ " -g enable false start\n",
+ progName);
+ exit(1);
+ }
+@@ -1244,6 +1246,12 @@ client_main(
+ errExit("SSL_OptionSet SSL_ENABLE_DEFLATE");
+ }
+
++ if (enableFalseStart) {
++ rv = SSL_OptionSet(model_sock, SSL_ENABLE_FALSE_START, PR_TRUE);
++ if (rv != SECSuccess)
++ errExit("SSL_OptionSet SSL_ENABLE_FALSE_START");
++ }
++
+ SSL_SetURL(model_sock, hostName);
+
+ SSL_AuthCertificateHook(model_sock, mySSLAuthCertificate,
+@@ -1354,7 +1362,7 @@ main(int argc, char **argv)
+
+
+ optstate = PL_CreateOptState(argc, argv,
+- "23BC:DNP:TUW:a:c:d:f:in:op:qst:uvw:z");
++ "23BC:DNP:TUW:a:c:d:f:gin:op:qst:uvw:z");
+ while ((status = PL_GetNextOpt(optstate)) == PL_OPT_OK) {
+ switch(optstate->option) {
+
+@@ -1384,6 +1392,8 @@ main(int argc, char **argv)
+
+ case 'f': fileName = optstate->value; break;
+
++ case 'g': enableFalseStart = PR_TRUE; break;
++
+ case 'i': ignoreErrors = PR_TRUE; break;
+
+ case 'n': nickName = PL_strdup(optstate->value); break;
+Index: mozilla/security/nss/cmd/tstclnt/tstclnt.c
+===================================================================
+RCS file: /cvsroot/mozilla/security/nss/cmd/tstclnt/tstclnt.c,v
+retrieving revision 1.62
+diff -u -p -r1.62 tstclnt.c
+--- mozilla/security/nss/cmd/tstclnt/tstclnt.c 10 Feb 2010 18:07:21 -0000 1.62
++++ mozilla/security/nss/cmd/tstclnt/tstclnt.c 16 Mar 2010 01:25:41 -0000
+@@ -225,6 +225,7 @@ static void Usage(const char *progName)
+ fprintf(stderr, "%-20s Renegotiate N times (resuming session if N>1).\n", "-r N");
+ fprintf(stderr, "%-20s Enable the session ticket extension.\n", "-u");
+ fprintf(stderr, "%-20s Enable compression.\n", "-z");
++ fprintf(stderr, "%-20s Enable false start.\n", "-g");
+ fprintf(stderr, "%-20s Letter(s) chosen from the following list\n",
+ "-c ciphers");
+ fprintf(stderr,
+@@ -521,6 +522,7 @@ int main(int argc, char **argv)
+ int useExportPolicy = 0;
+ int enableSessionTickets = 0;
+ int enableCompression = 0;
++ int enableFalseStart = 0;
+ PRSocketOptionData opt;
+ PRNetAddr addr;
+ PRPollDesc pollset[2];
+@@ -551,7 +553,7 @@ int main(int argc, char **argv)
+ }
+
+ optstate = PL_CreateOptState(argc, argv,
+- "23BSTW:a:c:d:fh:m:n:op:qr:suvw:xz");
++ "23BSTW:a:c:d:fgh:m:n:op:qr:suvw:xz");
+ while ((optstatus = PL_GetNextOpt(optstate)) == PL_OPT_OK) {
+ switch (optstate->option) {
+ case '?':
+@@ -578,6 +580,8 @@ int main(int argc, char **argv)
+
+ case 'c': cipherString = PORT_Strdup(optstate->value); break;
+
++ case 'g': enableFalseStart = 1; break;
++
+ case 'd': certDir = PORT_Strdup(optstate->value); break;
+
+ case 'f': clientSpeaksFirst = PR_TRUE; break;
+@@ -863,7 +867,14 @@ int main(int argc, char **argv)
+ SECU_PrintError(progName, "error enabling compression");
+ return 1;
+ }
+-
++
++ /* enable false start. */
++ rv = SSL_OptionSet(s, SSL_ENABLE_FALSE_START, enableFalseStart);
++ if (rv != SECSuccess) {
++ SECU_PrintError(progName, "error enabling false start");
++ return 1;
++ }
++
+ SSL_SetPKCS11PinArg(s, &pwdata);
+
+ SSL_AuthCertificateHook(s, SSL_AuthCertificate, (void *)handle);
+Index: mozilla/security/nss/lib/ssl/ssl.h
+===================================================================
+RCS file: /cvsroot/mozilla/security/nss/lib/ssl/ssl.h,v
+retrieving revision 1.38
+diff -u -p -r1.38 ssl.h
+--- mozilla/security/nss/lib/ssl/ssl.h 17 Feb 2010 02:29:07 -0000 1.38
++++ mozilla/security/nss/lib/ssl/ssl.h 16 Mar 2010 01:25:41 -0000
+@@ -128,6 +128,17 @@ SSL_IMPORT PRFileDesc *SSL_ImportFD(PRFi
+ /* Renegotiation Info (RI) */
+ /* extension in ALL handshakes. */
+ /* default: off */
++#define SSL_ENABLE_FALSE_START 22 /* Enable SSL false start (off by */
++ /* default, applies only to */
++ /* clients). False start is a */
++/* mode where an SSL client will start sending application data before */
++/* verifying the server's Finished message. This means that we could end up */
++/* sending data to an imposter. However, the data will be encrypted and */
++/* only the true server can derive the session key. Thus, so long as the */
++/* cipher isn't broken this is safe. Because of this, False Start will only */
++/* occur on RSA or DH ciphersuites where the cipher's key length is >= 80 */
++/* bits. The advantage of False Start is that it saves a round trip for */
++/* client-speaks-first protocols when performing a full handshake. */
+
+ #ifdef SSL_DEPRECATED_FUNCTION
+ /* Old deprecated function names */
+Index: mozilla/security/nss/lib/ssl/ssl3con.c
+===================================================================
+RCS file: /cvsroot/mozilla/security/nss/lib/ssl/ssl3con.c,v
+retrieving revision 1.136
+diff -u -p -r1.136 ssl3con.c
+--- mozilla/security/nss/lib/ssl/ssl3con.c 17 Feb 2010 02:29:07 -0000 1.136
++++ mozilla/security/nss/lib/ssl/ssl3con.c 16 Mar 2010 01:25:41 -0000
+@@ -5656,7 +5656,17 @@ ssl3_RestartHandshakeAfterCertReq(sslSoc
+ return rv;
+ }
+
+-
++PRBool
++ssl3_CanFalseStart(sslSocket *ss) {
++ return ss->opt.enableFalseStart &&
++ !ss->sec.isServer &&
++ !ss->ssl3.hs.isResuming &&
++ ss->ssl3.cwSpec &&
++ ss->ssl3.cwSpec->cipher_def->secret_key_size >= 10 &&
++ (ss->ssl3.hs.kea_def->exchKeyType == ssl_kea_rsa ||
++ ss->ssl3.hs.kea_def->exchKeyType == ssl_kea_dh ||
++ ss->ssl3.hs.kea_def->exchKeyType == ssl_kea_ecdh);
++}
+
+ /* Called from ssl3_HandleHandshakeMessage() when it has deciphered a complete
+ * ssl3 Server Hello Done message.
+@@ -5728,6 +5738,12 @@ ssl3_HandleServerHelloDone(sslSocket *ss
+ ss->ssl3.hs.ws = wait_new_session_ticket;
+ else
+ ss->ssl3.hs.ws = wait_change_cipher;
++
++ /* Do the handshake callback for sslv3 here. */
++ if (ss->handshakeCallback != NULL && ssl3_CanFalseStart(ss)) {
++ (ss->handshakeCallback)(ss->fd, ss->handshakeCallbackData);
++ }
++
+ return SECSuccess;
+
+ loser:
+@@ -8468,7 +8484,7 @@ xmit_loser:
+ ss->ssl3.hs.ws = idle_handshake;
+
+ /* Do the handshake callback for sslv3 here. */
+- if (ss->handshakeCallback != NULL) {
++ if (ss->handshakeCallback != NULL && !ssl3_CanFalseStart(ss)) {
+ (ss->handshakeCallback)(ss->fd, ss->handshakeCallbackData);
+ }
+
+Index: mozilla/security/nss/lib/ssl/ssl3gthr.c
+===================================================================
+RCS file: /cvsroot/mozilla/security/nss/lib/ssl/ssl3gthr.c,v
+retrieving revision 1.9
+diff -u -p -r1.9 ssl3gthr.c
+--- mozilla/security/nss/lib/ssl/ssl3gthr.c 20 Nov 2008 07:37:25 -0000 1.9
++++ mozilla/security/nss/lib/ssl/ssl3gthr.c 16 Mar 2010 01:25:41 -0000
+@@ -188,6 +188,7 @@ ssl3_GatherCompleteHandshake(sslSocket *
+ {
+ SSL3Ciphertext cText;
+ int rv;
++ PRBool canFalseStart = PR_FALSE;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+ do {
+@@ -207,7 +208,20 @@ ssl3_GatherCompleteHandshake(sslSocket *
+ if (rv < 0) {
+ return ss->recvdCloseNotify ? 0 : rv;
+ }
+- } while (ss->ssl3.hs.ws != idle_handshake && ss->gs.buf.len == 0);
++
++ /* If we kicked off a false start in ssl3_HandleServerHelloDone, break
++ * out of this loop early without finishing the handshake.
++ */
++ if (ss->opt.enableFalseStart) {
++ ssl_GetSSL3HandshakeLock(ss);
++ canFalseStart = (ss->ssl3.hs.ws == wait_change_cipher ||
++ ss->ssl3.hs.ws == wait_new_session_ticket) &&
++ ssl3_CanFalseStart(ss);
++ ssl_ReleaseSSL3HandshakeLock(ss);
++ }
++ } while (ss->ssl3.hs.ws != idle_handshake &&
++ !canFalseStart &&
++ ss->gs.buf.len == 0);
+
+ ss->gs.readOffset = 0;
+ ss->gs.writeOffset = ss->gs.buf.len;
+Index: mozilla/security/nss/lib/ssl/sslimpl.h
+===================================================================
+RCS file: /cvsroot/mozilla/security/nss/lib/ssl/sslimpl.h,v
+retrieving revision 1.77
+diff -u -p -r1.77 sslimpl.h
+--- mozilla/security/nss/lib/ssl/sslimpl.h 10 Feb 2010 00:33:50 -0000 1.77
++++ mozilla/security/nss/lib/ssl/sslimpl.h 16 Mar 2010 01:25:41 -0000
+@@ -333,6 +333,7 @@ typedef struct sslOptionsStr {
+ unsigned int enableDeflate : 1; /* 19 */
+ unsigned int enableRenegotiation : 2; /* 20-21 */
+ unsigned int requireSafeNegotiation : 1; /* 22 */
++ unsigned int enableFalseStart : 1; /* 23 */
+ } sslOptions;
+
+ typedef enum { sslHandshakingUndetermined = 0,
+@@ -1250,6 +1251,8 @@ extern void ssl_SetAlwaysBlock(sslS
+
+ extern SECStatus ssl_EnableNagleDelay(sslSocket *ss, PRBool enabled);
+
++extern PRBool ssl3_CanFalseStart(sslSocket *ss);
++
+ #define SSL_LOCK_READER(ss) if (ss->recvLock) PZ_Lock(ss->recvLock)
+ #define SSL_UNLOCK_READER(ss) if (ss->recvLock) PZ_Unlock(ss->recvLock)
+ #define SSL_LOCK_WRITER(ss) if (ss->sendLock) PZ_Lock(ss->sendLock)
+Index: mozilla/security/nss/lib/ssl/sslsecur.c
+===================================================================
+RCS file: /cvsroot/mozilla/security/nss/lib/ssl/sslsecur.c,v
+retrieving revision 1.43
+diff -u -p -r1.43 sslsecur.c
+--- mozilla/security/nss/lib/ssl/sslsecur.c 14 Jan 2010 22:15:25 -0000 1.43
++++ mozilla/security/nss/lib/ssl/sslsecur.c 16 Mar 2010 01:25:41 -0000
+@@ -1199,8 +1199,17 @@ ssl_SecureSend(sslSocket *ss, const unsi
+ ss->writerThread = PR_GetCurrentThread();
+ /* If any of these is non-zero, the initial handshake is not done. */
+ if (!ss->firstHsDone) {
++ PRBool canFalseStart = PR_FALSE;
+ ssl_Get1stHandshakeLock(ss);
+- if (ss->handshake || ss->nextHandshake || ss->securityHandshake) {
++ if (ss->version >= SSL_LIBRARY_VERSION_3_0 &&
++ (ss->ssl3.hs.ws == wait_change_cipher ||
++ ss->ssl3.hs.ws == wait_finished ||
++ ss->ssl3.hs.ws == wait_new_session_ticket) &&
++ ssl3_CanFalseStart(ss)) {
++ canFalseStart = PR_TRUE;
++ }
++ if (!canFalseStart &&
++ (ss->handshake || ss->nextHandshake || ss->securityHandshake)) {
+ rv = ssl_Do1stHandshake(ss);
+ }
+ ssl_Release1stHandshakeLock(ss);
+Index: mozilla/security/nss/lib/ssl/sslsock.c
+===================================================================
+RCS file: /cvsroot/mozilla/security/nss/lib/ssl/sslsock.c,v
+retrieving revision 1.66
+diff -u -p -r1.66 sslsock.c
+--- mozilla/security/nss/lib/ssl/sslsock.c 26 Feb 2010 20:44:54 -0000 1.66
++++ mozilla/security/nss/lib/ssl/sslsock.c 16 Mar 2010 01:25:41 -0000
+@@ -183,6 +183,7 @@ static sslOptions ssl_defaults = {
+ PR_FALSE, /* enableDeflate */
+ 2, /* enableRenegotiation (default: requires extension) */
+ PR_FALSE, /* requireSafeNegotiation */
++ PR_FALSE, /* enableFalseStart */
+ };
+
+ sslSessionIDLookupFunc ssl_sid_lookup;
+@@ -728,6 +729,10 @@ SSL_OptionSet(PRFileDesc *fd, PRInt32 wh
+ ss->opt.requireSafeNegotiation = on;
+ break;
+
++ case SSL_ENABLE_FALSE_START:
++ ss->opt.enableFalseStart = on;
++ break;
++
+ default:
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ rv = SECFailure;
+@@ -791,6 +796,7 @@ SSL_OptionGet(PRFileDesc *fd, PRInt32 wh
+ on = ss->opt.enableRenegotiation; break;
+ case SSL_REQUIRE_SAFE_NEGOTIATION:
+ on = ss->opt.requireSafeNegotiation; break;
++ case SSL_ENABLE_FALSE_START: on = ss->opt.enableFalseStart; break;
+
+ default:
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+@@ -841,6 +847,7 @@ SSL_OptionGetDefault(PRInt32 which, PRBo
+ case SSL_REQUIRE_SAFE_NEGOTIATION:
+ on = ssl_defaults.requireSafeNegotiation;
+ break;
++ case SSL_ENABLE_FALSE_START: on = ssl_defaults.enableFalseStart; break;
+
+ default:
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+@@ -984,6 +991,10 @@ SSL_OptionSetDefault(PRInt32 which, PRBo
+ ssl_defaults.requireSafeNegotiation = on;
+ break;
+
++ case SSL_ENABLE_FALSE_START:
++ ssl_defaults.enableFalseStart = on;
++ break;
++
+ default:
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+Index: mozilla/security/nss/tests/ssl/sslstress.txt
+===================================================================
+RCS file: /cvsroot/mozilla/security/nss/tests/ssl/sslstress.txt,v
+retrieving revision 1.18
+diff -u -p -r1.18 sslstress.txt
+--- mozilla/security/nss/tests/ssl/sslstress.txt 3 Feb 2010 02:25:36 -0000 1.18
++++ mozilla/security/nss/tests/ssl/sslstress.txt 16 Mar 2010 01:25:41 -0000
+@@ -42,9 +42,11 @@
+ noECC 0 _ -c_1000_-C_A Stress SSL2 RC4 128 with MD5
+ noECC 0 _ -c_1000_-C_c_-T Stress SSL3 RC4 128 with MD5
+ noECC 0 _ -c_1000_-C_c Stress TLS RC4 128 with MD5
++ noECC 0 _ -c_1000_-C_c_-h Stress TLS RC4 128 with MD5 (false start)
+ noECC 0 -u -2_-c_1000_-C_c_-u Stress TLS RC4 128 with MD5 (session ticket)
+ noECC 0 -z -2_-c_1000_-C_c_-z Stress TLS RC4 128 with MD5 (compression)
+ noECC 0 -u_-z -2_-c_1000_-C_c_-u_-z Stress TLS RC4 128 with MD5 (session ticket, compression)
++ noECC 0 -u_-z -2_-c_1000_-C_c_-u_-z_-h Stress TLS RC4 128 with MD5 (session ticket, compression, false start)
+ SNI 0 -u_-a_Host-sni.Dom -2_-3_-c_1000_-C_c_-u Stress TLS RC4 128 with MD5 (session ticket, SNI)
+
+ #
+@@ -55,7 +57,9 @@
+ noECC 0 -r_-r -c_100_-C_c_-N_-n_TestUser Stress TLS RC4 128 with MD5 (no reuse, client auth)
+ noECC 0 -r_-r_-u -2_-c_100_-C_c_-n_TestUser_-u Stress TLS RC4 128 with MD5 (session ticket, client auth)
+ noECC 0 -r_-r_-z -2_-c_100_-C_c_-n_TestUser_-z Stress TLS RC4 128 with MD5 (compression, client auth)
++ noECC 0 -r_-r_-z -2_-c_100_-C_c_-n_TestUser_-z_-h Stress TLS RC4 128 with MD5 (compression, client auth, false start)
+ noECC 0 -r_-r_-u_-z -2_-c_100_-C_c_-n_TestUser_-u_-z Stress TLS RC4 128 with MD5 (session ticket, compression, client auth)
++ noECC 0 -r_-r_-u_-z -2_-c_100_-C_c_-n_TestUser_-u_-z_-h Stress TLS RC4 128 with MD5 (session ticket, compression, client auth, false start)
+ SNI 0 -r_-r_-u_-a_Host-sni.Dom -2_-3_-c_1000_-C_c_-u Stress TLS RC4 128 with MD5 (session ticket, SNI, client auth, default virt host)
+ SNI 0 -r_-r_-u_-a_Host-sni.Dom_-k_Host-sni.Dom -2_-3_-c_1000_-C_c_-u_-a_Host-sni.Dom Stress TLS RC4 128 with MD5 (session ticket, SNI, client auth, change virt host)
+
diff --git a/net/third_party/nss/patches/nextproto.patch b/net/third_party/nss/patches/nextproto.patch
new file mode 100644
index 0000000..837295e
--- /dev/null
+++ b/net/third_party/nss/patches/nextproto.patch
@@ -0,0 +1,498 @@
+diff --git a/mozilla/security/nss/cmd/tstclnt/tstclnt.c b/mozilla/security/nss/cmd/tstclnt/tstclnt.c
+index c15a0ad..b6210bf 100644
+--- a/mozilla/security/nss/cmd/tstclnt/tstclnt.c
++++ b/mozilla/security/nss/cmd/tstclnt/tstclnt.c
+@@ -863,7 +863,13 @@ int main(int argc, char **argv)
+ SECU_PrintError(progName, "error enabling compression");
+ return 1;
+ }
+-
++
++ rv = SSL_SetNextProtoNego(s, "\004flip\004http1.1", 10);
++ if (rv != SECSuccess) {
++ SECU_PrintError(progName, "error enabling next protocol negotiation");
++ return 1;
++ }
++
+ SSL_SetPKCS11PinArg(s, &pwdata);
+
+ SSL_AuthCertificateHook(s, SSL_AuthCertificate, (void *)handle);
+diff --git a/mozilla/security/nss/lib/ssl/ssl.def b/mozilla/security/nss/lib/ssl/ssl.def
+index d3f455c..a1f4b51 100644
+--- a/mozilla/security/nss/lib/ssl/ssl.def
++++ b/mozilla/security/nss/lib/ssl/ssl.def
+@@ -152,3 +152,10 @@ SSL_SNISocketConfigHook;
+ ;+ local:
+ ;+*;
+ ;+};
++;+NSS_CHROMIUM {
++;+ global:
++SSL_GetNextProto;
++SSL_SetNextProtoNego;
++;+ local:
++;+*;
++;+};
+diff --git a/mozilla/security/nss/lib/ssl/ssl.h b/mozilla/security/nss/lib/ssl/ssl.h
+index d60a73c..00c250b 100644
+--- a/mozilla/security/nss/lib/ssl/ssl.h
++++ b/mozilla/security/nss/lib/ssl/ssl.h
+@@ -142,6 +142,18 @@ SSL_IMPORT SECStatus SSL_OptionSetDefault(PRInt32 option, PRBool on);
+ SSL_IMPORT SECStatus SSL_OptionGetDefault(PRInt32 option, PRBool *on);
+ SSL_IMPORT SECStatus SSL_CertDBHandleSet(PRFileDesc *fd, CERTCertDBHandle *dbHandle);
+
++SSL_IMPORT SECStatus SSL_SetNextProtoNego(PRFileDesc *fd,
++ const unsigned char *data,
++ unsigned short length);
++SSL_IMPORT SECStatus SSL_GetNextProto(PRFileDesc *fd,
++ int *state,
++ unsigned char *buf,
++ unsigned *length,
++ unsigned buf_len);
++#define SSL_NEXT_PROTO_NO_SUPPORT 0 /* No peer support */
++#define SSL_NEXT_PROTO_NEGOTIATED 1 /* Mutual agreement */
++#define SSL_NEXT_PROTO_NO_OVERLAP 2 /* No protocol overlap found */
++
+ /*
+ ** Control ciphers that SSL uses. If on is non-zero then the named cipher
+ ** is enabled, otherwise it is disabled.
+diff --git a/mozilla/security/nss/lib/ssl/ssl3con.c b/mozilla/security/nss/lib/ssl/ssl3con.c
+index 083248d..5c14672 100644
+--- a/mozilla/security/nss/lib/ssl/ssl3con.c
++++ b/mozilla/security/nss/lib/ssl/ssl3con.c
+@@ -81,6 +81,7 @@ static SECStatus ssl3_InitState( sslSocket *ss);
+ static SECStatus ssl3_SendCertificate( sslSocket *ss);
+ static SECStatus ssl3_SendEmptyCertificate( sslSocket *ss);
+ static SECStatus ssl3_SendCertificateRequest(sslSocket *ss);
++static SECStatus ssl3_SendNextProto( sslSocket *ss);
+ static SECStatus ssl3_SendFinished( sslSocket *ss, PRInt32 flags);
+ static SECStatus ssl3_SendServerHello( sslSocket *ss);
+ static SECStatus ssl3_SendServerHelloDone( sslSocket *ss);
+@@ -5717,6 +5718,12 @@ ssl3_HandleServerHelloDone(sslSocket *ss)
+ if (rv != SECSuccess) {
+ goto loser; /* err code was set. */
+ }
++
++ rv = ssl3_SendNextProto(ss);
++ if (rv != SECSuccess) {
++ goto loser; /* err code was set. */
++ }
++
+ rv = ssl3_SendFinished(ss, 0);
+ if (rv != SECSuccess) {
+ goto loser; /* err code was set. */
+@@ -8138,6 +8145,40 @@ ssl3_ComputeTLSFinished(ssl3CipherSpec *spec,
+ }
+
+ /* called from ssl3_HandleServerHelloDone
++ */
++static SECStatus
++ssl3_SendNextProto(sslSocket *ss)
++{
++ SECStatus rv;
++ int padding_len;
++ static const unsigned char padding[32] = {0};
++
++ if (ss->ssl3.nextProtoState == SSL_NEXT_PROTO_NO_SUPPORT)
++ return SECSuccess;
++
++ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
++ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
++
++ padding_len = 32 - ((ss->ssl3.nextProto.len + 2) % 32);
++
++ rv = ssl3_AppendHandshakeHeader(ss, next_proto, ss->ssl3.nextProto.len +
++ 2 + padding_len);
++ if (rv != SECSuccess) {
++ return rv; /* error code set by AppendHandshakeHeader */
++ }
++ rv = ssl3_AppendHandshakeVariable(ss, ss->ssl3.nextProto.data,
++ ss->ssl3.nextProto.len, 1);
++ if (rv != SECSuccess) {
++ return rv; /* error code set by AppendHandshake */
++ }
++ rv = ssl3_AppendHandshakeVariable(ss, padding, padding_len, 1);
++ if (rv != SECSuccess) {
++ return rv; /* error code set by AppendHandshake */
++ }
++ return rv;
++}
++
++/* called from ssl3_HandleServerHelloDone
+ * ssl3_HandleClientHello
+ * ssl3_HandleFinished
+ */
+@@ -8390,6 +8431,14 @@ ssl3_HandleFinished(sslSocket *ss, SSL3Opaque *b, PRUint32 length,
+ if (doStepUp || ss->writerThread == PR_GetCurrentThread()) {
+ flags = ssl_SEND_FLAG_FORCE_INTO_BUFFER;
+ }
++
++ if (!isServer) {
++ rv = ssl3_SendNextProto(ss);
++ if (rv != SECSuccess) {
++ goto xmit_loser; /* err code was set. */
++ }
++ }
++
+ rv = ssl3_SendFinished(ss, flags);
+ if (rv != SECSuccess) {
+ goto xmit_loser; /* err is set. */
+@@ -9455,6 +9504,11 @@ ssl3_DestroySSL3Info(sslSocket *ss)
+ ssl3_DestroyCipherSpec(&ss->ssl3.specs[1], PR_TRUE/*freeSrvName*/);
+
+ ss->ssl3.initialized = PR_FALSE;
++
++ if (ss->ssl3.nextProto.data) {
++ PORT_Free(ss->ssl3.nextProto.data);
++ ss->ssl3.nextProto.data = NULL;
++ }
+ }
+
+ /* End of ssl3con.c */
+diff --git a/mozilla/security/nss/lib/ssl/ssl3ext.c b/mozilla/security/nss/lib/ssl/ssl3ext.c
+index ac2b067..04f45a4 100644
+--- a/mozilla/security/nss/lib/ssl/ssl3ext.c
++++ b/mozilla/security/nss/lib/ssl/ssl3ext.c
+@@ -235,6 +235,7 @@ static const ssl3HelloExtensionHandler clientHelloHandlers[] = {
+ #endif
+ { ssl_session_ticket_xtn, &ssl3_ServerHandleSessionTicketXtn },
+ { ssl_renegotiation_info_xtn, &ssl3_HandleRenegotiationInfoXtn },
++ { ssl_next_proto_neg_xtn, &ssl3_ServerHandleNextProtoNegoXtn },
+ { -1, NULL }
+ };
+
+@@ -245,6 +246,7 @@ static const ssl3HelloExtensionHandler serverHelloHandlersTLS[] = {
+ /* TODO: add a handler for ssl_ec_point_formats_xtn */
+ { ssl_session_ticket_xtn, &ssl3_ClientHandleSessionTicketXtn },
+ { ssl_renegotiation_info_xtn, &ssl3_HandleRenegotiationInfoXtn },
++ { ssl_next_proto_neg_xtn, &ssl3_ClientHandleNextProtoNegoXtn },
+ { -1, NULL }
+ };
+
+@@ -267,7 +269,8 @@ ssl3HelloExtensionSender clientHelloSendersTLS[SSL_MAX_EXTENSIONS] = {
+ { ssl_elliptic_curves_xtn, &ssl3_SendSupportedCurvesXtn },
+ { ssl_ec_point_formats_xtn, &ssl3_SendSupportedPointFormatsXtn },
+ #endif
+- { ssl_session_ticket_xtn, &ssl3_SendSessionTicketXtn }
++ { ssl_session_ticket_xtn, &ssl3_SendSessionTicketXtn },
++ { ssl_next_proto_neg_xtn, &ssl3_ClientSendNextProtoNegoXtn }
+ /* any extra entries will appear as { 0, NULL } */
+ };
+
+@@ -532,6 +535,123 @@ ssl3_SendSessionTicketXtn(
+ return -1;
+ }
+
++/* handle an incoming Next Protocol Negotiation extension. */
++SECStatus
++ssl3_ServerHandleNextProtoNegoXtn(sslSocket * ss, PRUint16 ex_type, SECItem *data)
++{
++ if (data->len != 0) {
++ /* Clients MUST send an empty NPN extension, if any. */
++ return SECFailure;
++ }
++
++ ss->ssl3.hs.nextProtoNego = PR_TRUE;
++ return SECSuccess;
++}
++
++/* ssl3_ValidateNextProtoNego checks that the given block of data is valid: none
++ * of the length may be 0 and the sum of the lengths must equal the length of
++ * the block. */
++SECStatus
++ssl3_ValidateNextProtoNego(const unsigned char* data, unsigned short length)
++{
++ unsigned int offset = 0;
++
++ while (offset < length) {
++ if (data[offset] == 0) {
++ return SECFailure;
++ }
++ offset += (unsigned int)data[offset] + 1;
++ }
++
++ if (offset > length)
++ return SECFailure;
++
++ return SECSuccess;
++}
++
++SECStatus
++ssl3_ClientHandleNextProtoNegoXtn(sslSocket *ss, PRUint16 ex_type,
++ SECItem *data)
++{
++ unsigned int i, j;
++ SECStatus rv;
++ unsigned char *result;
++
++ if (data->len == 0) {
++ /* The server supports the extension, but doesn't have any
++ * protocols configured. In this case we request our favoured
++ * protocol. */
++ goto pick_first;
++ }
++
++ rv = ssl3_ValidateNextProtoNego(data->data, data->len);
++ if (rv != SECSuccess)
++ return rv;
++
++ /* For each protocol in server preference order, see if we support it. */
++ for (i = 0; i < data->len; ) {
++ for (j = 0; j < ss->opt.nextProtoNego.len; ) {
++ if (data->data[i] == ss->opt.nextProtoNego.data[j] &&
++ memcmp(&data->data[i+1], &ss->opt.nextProtoNego.data[j+1],
++ data->data[i]) == 0) {
++ /* We found a match */
++ ss->ssl3.nextProtoState = SSL_NEXT_PROTO_NEGOTIATED;
++ result = &data->data[i];
++ goto found;
++ }
++ j += (unsigned int)ss->opt.nextProtoNego.data[j] + 1;
++ }
++
++ i += (unsigned int)data->data[i] + 1;
++ }
++
++ pick_first:
++ ss->ssl3.nextProtoState = SSL_NEXT_PROTO_NO_OVERLAP;
++ result = ss->opt.nextProtoNego.data;
++
++ found:
++ if (ss->ssl3.nextProto.data)
++ PORT_Free(ss->ssl3.nextProto.data);
++ ss->ssl3.nextProto.data = PORT_Alloc(result[0]);
++ PORT_Memcpy(ss->ssl3.nextProto.data, result + 1, result[0]);
++ ss->ssl3.nextProto.len = result[0];
++ return SECSuccess;
++}
++
++PRInt32
++ssl3_ClientSendNextProtoNegoXtn(sslSocket * ss,
++ PRBool append,
++ PRUint32 maxBytes)
++{
++ PRInt32 extension_length;
++
++ /* Renegotiations do not send this extension. */
++ if (ss->opt.nextProtoNego.len == 0 || ss->firstHsDone) {
++ return 0;
++ }
++
++ extension_length = 4;
++
++ if (append && maxBytes >= extension_length) {
++ SECStatus rv;
++ rv = ssl3_AppendHandshakeNumber(ss, ssl_next_proto_neg_xtn, 2);
++ if (rv != SECSuccess)
++ goto loser;
++ rv = ssl3_AppendHandshakeNumber(ss, 0, 2);
++ if (rv != SECSuccess)
++ goto loser;
++ ss->xtnData.advertised[ss->xtnData.numAdvertised++] =
++ ssl_next_proto_neg_xtn;
++ } else if (maxBytes < extension_length) {
++ return 0;
++ }
++
++ return extension_length;
++
++ loser:
++ return -1;
++}
++
+ /*
+ * NewSessionTicket
+ * Called from ssl3_HandleFinished
+diff --git a/mozilla/security/nss/lib/ssl/ssl3prot.h b/mozilla/security/nss/lib/ssl/ssl3prot.h
+index 0fc1675..c82c891 100644
+--- a/mozilla/security/nss/lib/ssl/ssl3prot.h
++++ b/mozilla/security/nss/lib/ssl/ssl3prot.h
+@@ -157,7 +157,8 @@ typedef enum {
+ server_hello_done = 14,
+ certificate_verify = 15,
+ client_key_exchange = 16,
+- finished = 20
++ finished = 20,
++ next_proto = 67
+ } SSL3HandshakeType;
+
+ typedef struct {
+diff --git a/mozilla/security/nss/lib/ssl/sslimpl.h b/mozilla/security/nss/lib/ssl/sslimpl.h
+index 7581b98..0658d2c 100644
+--- a/mozilla/security/nss/lib/ssl/sslimpl.h
++++ b/mozilla/security/nss/lib/ssl/sslimpl.h
+@@ -313,6 +313,11 @@ typedef struct {
+ #endif /* NSS_ENABLE_ECC */
+
+ typedef struct sslOptionsStr {
++ /* For clients, this is a validated list of protocols in preference order
++ * and wire format. For servers, this is the list of support protocols,
++ * also in wire format. */
++ SECItem nextProtoNego;
++
+ unsigned int useSecurity : 1; /* 1 */
+ unsigned int useSocks : 1; /* 2 */
+ unsigned int requestCertificate : 1; /* 3 */
+@@ -785,6 +790,7 @@ const ssl3CipherSuiteDef *suite_def;
+ #ifdef NSS_ENABLE_ECC
+ PRUint32 negotiatedECCurves; /* bit mask */
+ #endif /* NSS_ENABLE_ECC */
++ PRBool nextProtoNego;/* Our peer has sent this extension */
+ } SSL3HandshakeState;
+
+
+@@ -826,6 +832,16 @@ struct ssl3StateStr {
+ PRBool initialized;
+ SSL3HandshakeState hs;
+ ssl3CipherSpec specs[2]; /* one is current, one is pending. */
++
++ /* In a client: if the server supports Next Protocol Negotiation, then
++ * this is the protocol that was requested.
++ * In a server: this is the protocol that the client requested via Next
++ * Protocol Negotiation.
++ *
++ * In either case, if the data pointer is non-NULL, then it is malloced
++ * data. */
++ SECItem nextProto;
++ int nextProtoState; /* See SSL_NEXT_PROTO_* defines */
+ };
+
+ typedef struct {
+@@ -1491,8 +1507,12 @@ extern SECStatus ssl3_HandleSupportedPointFormatsXtn(sslSocket * ss,
+ PRUint16 ex_type, SECItem *data);
+ extern SECStatus ssl3_ClientHandleSessionTicketXtn(sslSocket *ss,
+ PRUint16 ex_type, SECItem *data);
++extern SECStatus ssl3_ClientHandleNextProtoNegoXtn(sslSocket *ss,
++ PRUint16 ex_type, SECItem *data);
+ extern SECStatus ssl3_ServerHandleSessionTicketXtn(sslSocket *ss,
+ PRUint16 ex_type, SECItem *data);
++extern SECStatus ssl3_ServerHandleNextProtoNegoXtn(sslSocket *ss,
++ PRUint16 ex_type, SECItem *data);
+
+ /* ClientHello and ServerHello extension senders.
+ * Note that not all extension senders are exposed here; only those that
+@@ -1523,6 +1543,10 @@ extern PRInt32 ssl3_SendSupportedCurvesXtn(sslSocket *ss,
+ extern PRInt32 ssl3_SendSupportedPointFormatsXtn(sslSocket *ss,
+ PRBool append, PRUint32 maxBytes);
+ #endif
++extern PRInt32 ssl3_ClientSendNextProtoNegoXtn(sslSocket *ss, PRBool append,
++ PRUint32 maxBytes);
++extern SECStatus ssl3_ValidateNextProtoNego(const unsigned char* data,
++ unsigned short length);
+
+ /* call the registered extension handlers. */
+ extern SECStatus ssl3_HandleHelloExtensions(sslSocket *ss,
+diff --git a/mozilla/security/nss/lib/ssl/sslsock.c b/mozilla/security/nss/lib/ssl/sslsock.c
+index f1d1921..6536354 100644
+--- a/mozilla/security/nss/lib/ssl/sslsock.c
++++ b/mozilla/security/nss/lib/ssl/sslsock.c
+@@ -163,6 +163,7 @@ static const sslSocketOps ssl_secure_ops = { /* SSL. */
+ ** default settings for socket enables
+ */
+ static sslOptions ssl_defaults = {
++ { siBuffer, NULL, 0 }, /* nextProtoNego */
+ PR_TRUE, /* useSecurity */
+ PR_FALSE, /* useSocks */
+ PR_FALSE, /* requestCertificate */
+@@ -437,6 +438,10 @@ ssl_DestroySocketContents(sslSocket *ss)
+ ssl3_FreeKeyPair(ss->ephemeralECDHKeyPair);
+ ss->ephemeralECDHKeyPair = NULL;
+ }
++ if (ss->opt.nextProtoNego.data) {
++ PORT_Free(ss->opt.nextProtoNego.data);
++ ss->opt.nextProtoNego.data = NULL;
++ }
+ PORT_Assert(!ss->xtnData.sniNameArr);
+ if (ss->xtnData.sniNameArr) {
+ PORT_Free(ss->xtnData.sniNameArr);
+@@ -1255,6 +1260,75 @@ SSL_ImportFD(PRFileDesc *model, PRFileDesc *fd)
+ return fd;
+ }
+
++/* SSL_SetNextProtoNego sets the list of supported protocols for the given
++ * socket. The list is a series of 8-bit, length prefixed strings. */
++SECStatus
++SSL_SetNextProtoNego(PRFileDesc *fd, const unsigned char *data,
++ unsigned short length)
++{
++ sslSocket *ss = ssl_FindSocket(fd);
++
++ if (!ss) {
++ SSL_DBG(("%d: SSL[%d]: bad socket in SSL_SetNextProtoNego", SSL_GETPID(),
++ fd));
++ return SECFailure;
++ }
++
++ if (ssl3_ValidateNextProtoNego(data, length) != SECSuccess)
++ return SECFailure;
++
++ ssl_GetSSL3HandshakeLock(ss);
++ if (ss->opt.nextProtoNego.data)
++ PORT_Free(ss->opt.nextProtoNego.data);
++ ss->opt.nextProtoNego.data = PORT_Alloc(length);
++ if (!ss->opt.nextProtoNego.data) {
++ ssl_ReleaseSSL3HandshakeLock(ss);
++ return SECFailure;
++ }
++ memcpy(ss->opt.nextProtoNego.data, data, length);
++ ss->opt.nextProtoNego.len = length;
++ ss->opt.nextProtoNego.type = siBuffer;
++ ssl_ReleaseSSL3HandshakeLock(ss);
++
++ return SECSuccess;
++}
++
++/* SSL_GetNextProto reads the resulting Next Protocol Negotiation result for
++ * the given socket. It's only valid to call this once the handshake has
++ * completed.
++ *
++ * state is set to one of the SSL_NEXT_PROTO_* constants. The negotiated
++ * protocol, if any, is written into buf, which must be at least buf_len
++ * bytes long. If the negotiated protocol is longer than this, it is truncated.
++ * The number of bytes copied is written into length.
++ */
++SECStatus
++SSL_GetNextProto(PRFileDesc *fd, int *state, unsigned char *buf,
++ unsigned int *length, unsigned int buf_len)
++{
++ sslSocket *ss = ssl_FindSocket(fd);
++
++ if (!ss) {
++ SSL_DBG(("%d: SSL[%d]: bad socket in SSL_GetNextProto", SSL_GETPID(),
++ fd));
++ return SECFailure;
++ }
++
++ *state = ss->ssl3.nextProtoState;
++
++ if (ss->ssl3.nextProtoState != SSL_NEXT_PROTO_NO_SUPPORT &&
++ ss->ssl3.nextProto.data) {
++ *length = ss->ssl3.nextProto.len;
++ if (*length > buf_len)
++ *length = buf_len;
++ PORT_Memcpy(buf, ss->ssl3.nextProto.data, *length);
++ } else {
++ *length = 0;
++ }
++
++ return SECSuccess;
++}
++
+ PRFileDesc *
+ SSL_ReconfigFD(PRFileDesc *model, PRFileDesc *fd)
+ {
+diff --git a/mozilla/security/nss/lib/ssl/sslt.h b/mozilla/security/nss/lib/ssl/sslt.h
+index c7d4553..f6e0b62 100644
+--- a/mozilla/security/nss/lib/ssl/sslt.h
++++ b/mozilla/security/nss/lib/ssl/sslt.h
+@@ -203,9 +203,10 @@ typedef enum {
+ ssl_ec_point_formats_xtn = 11,
+ #endif
+ ssl_session_ticket_xtn = 35,
++ ssl_next_proto_neg_xtn = 13172,
+ ssl_renegotiation_info_xtn = 0xff01 /* experimental number */
+ } SSLExtensionType;
+
+-#define SSL_MAX_EXTENSIONS 5
++#define SSL_MAX_EXTENSIONS 6
+
+ #endif /* __sslt_h_ */
diff --git a/net/third_party/nss/patches/renegoscsv.patch b/net/third_party/nss/patches/renegoscsv.patch
new file mode 100644
index 0000000..a9c188c
--- /dev/null
+++ b/net/third_party/nss/patches/renegoscsv.patch
@@ -0,0 +1,29 @@
+Index: mozilla/security/nss/lib/ssl/ssl3con.c
+===================================================================
+RCS file: /cvsroot/mozilla/security/nss/lib/ssl/ssl3con.c,v
+retrieving revision 1.136
+diff -u -p -u -8 -r1.136 ssl3con.c
+--- mozilla/security/nss/lib/ssl/ssl3con.c 17 Feb 2010 02:29:07 -0000 1.136
++++ mozilla/security/nss/lib/ssl/ssl3con.c 27 Feb 2010 02:55:21 -0000
+@@ -3863,19 +3863,19 @@ ssl3_SendClientHello(sslSocket *ss)
+ }
+
+ /* how many suites does our PKCS11 support (regardless of policy)? */
+ num_suites = ssl3_config_match_init(ss);
+ if (!num_suites)
+ return SECFailure; /* ssl3_config_match_init has set error code. */
+
+ /* HACK for SCSV in SSL 3.0. On initial handshake, prepend SCSV,
+- * only if we're willing to complete an SSL 3.0 handshake.
++ * only if TLS is disabled.
+ */
+- if (!ss->firstHsDone && ss->opt.enableSSL3) {
++ if (!ss->firstHsDone && !isTLS) {
+ /* Must set this before calling Hello Extension Senders,
+ * to suppress sending of empty RI extension.
+ */
+ ss->ssl3.hs.sendingSCSV = PR_TRUE;
+ }
+
+ if (isTLS || (ss->firstHsDone && ss->peerRequestedProtection)) {
+ PRUint32 maxBytes = 65535; /* 2^16 - 1 */
diff --git a/net/third_party/nss/patches/versionskew.patch b/net/third_party/nss/patches/versionskew.patch
new file mode 100644
index 0000000..1b96983
--- /dev/null
+++ b/net/third_party/nss/patches/versionskew.patch
@@ -0,0 +1,47 @@
+diff --git a/mozilla/security/nss/lib/ssl/sslsecur.c b/mozilla/security/nss/lib/ssl/sslsecur.c
+index 8f79135..80c2ba6 100644
+--- a/mozilla/security/nss/lib/ssl/sslsecur.c
++++ b/mozilla/security/nss/lib/ssl/sslsecur.c
+@@ -1307,6 +1307,10 @@ SSL_SetURL(PRFileDesc *fd, const char *url)
+ SECStatus
+ SSL_SetTrustAnchors(PRFileDesc *fd, CERTCertList *certList)
+ {
++ PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
++ PR_NOT_REACHED("not implemented");
++ return SECFailure;
++#if 0
+ sslSocket * ss = ssl_FindSocket(fd);
+ CERTDistNames *names = NULL;
+
+@@ -1334,6 +1338,7 @@ SSL_SetTrustAnchors(PRFileDesc *fd, CERTCertList *certList)
+ ssl_Release1stHandshakeLock(ss);
+
+ return SECSuccess;
++#endif
+ }
+
+ /*
+diff --git a/mozilla/security/nss/lib/ssl/sslsock.c b/mozilla/security/nss/lib/ssl/sslsock.c
+index aab48d6..01ef3bd 100644
+--- a/mozilla/security/nss/lib/ssl/sslsock.c
++++ b/mozilla/security/nss/lib/ssl/sslsock.c
+@@ -1258,6 +1258,11 @@ SSL_ImportFD(PRFileDesc *model, PRFileDesc *fd)
+ PRFileDesc *
+ SSL_ReconfigFD(PRFileDesc *model, PRFileDesc *fd)
+ {
++ PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
++ PR_NOT_REACHED("not implemented");
++ return NULL;
++
++#if 0
+ sslSocket * sm = NULL, *ss = NULL;
+ int i;
+ sslServerCerts * mc = sm->serverCerts;
+@@ -1360,6 +1365,7 @@ SSL_ReconfigFD(PRFileDesc *model, PRFileDesc *fd)
+ return fd;
+ loser:
+ return NULL;
++#endif
+ }
+
+ /************************************************************************/
diff --git a/net/third_party/nss/ssl.Makefile b/net/third_party/nss/ssl.Makefile
new file mode 100644
index 0000000..4f52412
--- /dev/null
+++ b/net/third_party/nss/ssl.Makefile
@@ -0,0 +1,6 @@
+# This file is generated by gyp; do not edit.
+
+export builddir_name ?= /usr/local/google/src/chromium-merge/src/net/third_party/nss/out
+.PHONY: all
+all:
+ $(MAKE) -C ../../.. ssl
diff --git a/net/third_party/nss/ssl.gyp b/net/third_party/nss/ssl.gyp
new file mode 100644
index 0000000..3166be8
--- /dev/null
+++ b/net/third_party/nss/ssl.gyp
@@ -0,0 +1,149 @@
+# Copyright (c) 2009 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.
+
+{
+ 'conditions': [
+ [ 'OS == "linux" or OS == "freebsd" or OS == "openbsd"', {
+ 'conditions': [
+ ['sysroot!=""', {
+ 'variables': {
+ 'pkg-config': '../../../build/linux/pkg-config-wrapper "<(sysroot)"',
+ },
+ }, {
+ 'variables': {
+ 'pkg-config': 'pkg-config'
+ },
+ }],
+ ],
+ }],
+ ],
+
+ 'targets': [
+ {
+ 'target_name': 'ssl',
+ 'product_name': 'ssl',
+ 'type': '<(library)',
+ 'sources': [
+ 'ssl/authcert.c',
+ 'ssl/cmpcert.c',
+ 'ssl/derive.c',
+ 'ssl/nsskea.c',
+ 'ssl/os2_err.c',
+ 'ssl/os2_err.h',
+ 'ssl/preenc.h',
+ 'ssl/prelib.c',
+ 'ssl/ssl.h',
+ 'ssl/ssl3con.c',
+ 'ssl/ssl3ecc.c',
+ 'ssl/ssl3ext.c',
+ 'ssl/ssl3gthr.c',
+ 'ssl/ssl3prot.h',
+ 'ssl/sslauth.c',
+ 'ssl/sslcon.c',
+ 'ssl/ssldef.c',
+ 'ssl/sslenum.c',
+ 'ssl/sslerr.c',
+ 'ssl/sslerr.h',
+ 'ssl/sslgathr.c',
+ 'ssl/sslimpl.h',
+ 'ssl/sslinfo.c',
+ 'ssl/sslmutex.c',
+ 'ssl/sslmutex.h',
+ 'ssl/sslnonce.c',
+ 'ssl/sslproto.h',
+ 'ssl/sslreveal.c',
+ 'ssl/sslsecur.c',
+ 'ssl/sslsnce.c',
+ 'ssl/sslsock.c',
+ 'ssl/sslt.h',
+ 'ssl/ssltrace.c',
+ 'ssl/sslver.c',
+ 'ssl/unix_err.c',
+ 'ssl/unix_err.h',
+ 'ssl/win32err.c',
+ 'ssl/win32err.h',
+ 'ssl/bodge/loader.c',
+ 'ssl/bodge/loader.h',
+ 'ssl/bodge/secure_memcmp.c',
+ ],
+ 'sources!': [
+ 'ssl/os2_err.c',
+ 'ssl/os2_err.h',
+ ],
+ 'defines': [
+ 'NSS_ENABLE_ECC',
+ 'NSS_ENABLE_ZLIB',
+ 'USE_UTIL_DIRECTLY',
+ ],
+ 'defines!': [
+ # Regrettably, NSS can't be compiled with NO_NSPR_10_SUPPORT yet.
+ 'NO_NSPR_10_SUPPORT',
+ ],
+ 'conditions': [
+ [ 'OS == "win"', {
+ 'sources!': [
+ 'ssl/unix_err.c',
+ 'ssl/unix_err.h',
+ ],
+ },
+ { # else: OS != "win"
+ 'sources!': [
+ 'ssl/win32err.c',
+ 'ssl/win32err.h',
+ ],
+ },
+ ],
+ [ 'OS == "linux" or OS == "freebsd" or OS == "openbsd"', {
+ 'defines': [
+ # These macros are needed only for compiling the files in
+ # ssl/bodge.
+ 'SHLIB_PREFIX="lib"',
+ 'SHLIB_SUFFIX="so"',
+ 'SHLIB_VERSION="3"',
+ 'SOFTOKEN_SHLIB_VERSION="3"',
+ ],
+ 'include_dirs': [
+ 'ssl/bodge',
+ ],
+ 'cflags': [
+ '<!@(<(pkg-config) --cflags nss)',
+ ],
+ 'ldflags': [
+ '<!@(<(pkg-config) --libs-only-L --libs-only-other nss)',
+ ],
+ 'libraries': [
+ '<!@(<(pkg-config) --libs-only-l nss | sed -e "s/-lssl3//")',
+ ],
+ }],
+ [ 'OS == "mac" or OS == "win"', {
+ 'sources/': [
+ ['exclude', 'ssl/bodge/'],
+ ],
+ 'dependencies': [
+ '../../../third_party/zlib/zlib.gyp:zlib',
+ '../../../third_party/nss/nss.gyp:nss',
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ 'ssl',
+ ],
+ },
+ }],
+ ],
+ 'configurations': {
+ 'Debug_Base': {
+ 'defines': [
+ 'DEBUG',
+ ],
+ },
+ },
+ },
+ ],
+}
+
+# Local Variables:
+# tab-width:2
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=2 shiftwidth=2:
diff --git a/net/third_party/nss/ssl.scons b/net/third_party/nss/ssl.scons
new file mode 100644
index 0000000..c1b52c4
--- /dev/null
+++ b/net/third_party/nss/ssl.scons
@@ -0,0 +1,351 @@
+# This file is generated; do not edit.
+
+import os
+
+Import("env")
+
+env = env.Clone(COMPONENT_NAME='nss',
+ TARGET_NAME='ssl')
+
+configurations = {
+ 'Release' : {
+ 'Append' : dict(
+ CCFLAGS = [
+ '-pthread',
+ '-fno-exceptions',
+ '-D_FILE_OFFSET_BITS=64',
+ '-fvisibility=hidden',
+ '-fno-strict-aliasing',
+ '-I/usr/include/nss',
+ '-I/usr/include/nspr',
+ '-O2',
+ '-fno-ident',
+ '-fdata-sections',
+ '-ffunction-sections',
+ '-fno-asynchronous-unwind-tables'
+ ],
+ CPPDEFINES = [
+ 'CHROMIUM_BUILD',
+ 'ENABLE_GPU=1',
+ 'NSS_ENABLE_ECC',
+ 'NSS_ENABLE_ZLIB',
+ 'USE_UTIL_DIRECTLY',
+ 'SHLIB_PREFIX=\\"lib\\"',
+ 'SHLIB_SUFFIX=\\"so\\"',
+ 'SHLIB_VERSION=\\"3\\"',
+ 'SOFTOKEN_SHLIB_VERSION=\\"3\\"',
+ 'NDEBUG',
+ 'NVALGRIND'
+ ],
+ CPPPATH = [
+ env.Dir('$SRC_DIR/net/third_party/nss/ssl/bodge')
+ ],
+ CXXFLAGS = [
+ '-fno-rtti',
+ '-fno-threadsafe-statics',
+ '-fvisibility-inlines-hidden'
+ ],
+ LINKFLAGS = [
+ '-pthread',
+ '-Wl,--gc-sections'
+ ],
+ LIBS = [
+ '-lnss3',
+ '-lnssutil3',
+ '-lsmime3',
+ '-lplds4',
+ '-lplc4',
+ '-lnspr4',
+ '-lpthread',
+ '-ldl'
+ ],
+ ),
+ 'FilterOut' : dict(
+ ),
+ 'Replace' : dict(
+ FLOCK_LDMODULE = ['flock', '$TOP_BUILDDIR/linker.lock', '$LDMODULE'],
+ FLOCK_LINK = ['flock', '$TOP_BUILDDIR/linker.lock', '$LINK'],
+ FLOCK_SHLINK = ['flock', '$TOP_BUILDDIR/linker.lock', '$SHLINK'],
+ IMPLICIT_COMMAND_DEPENDENCIES = '0',
+ LDMODULECOM = [['$FLOCK_LDMODULE',
+ '-o',
+ '$TARGET',
+ '$_LIBDIRFLAGS',
+ '$LDMODULEFLAGS',
+ '$SOURCES',
+ '-Wl,--start-group',
+ '$_LIBFLAGS',
+ '-Wl,--end-group']],
+ LIBPATH = ['$LIB_DIR'],
+ LINKCOM = [['$FLOCK_LINK',
+ '-o',
+ '$TARGET',
+ '$_LIBDIRFLAGS',
+ '$LINKFLAGS',
+ '$SOURCES',
+ '-Wl,--start-group',
+ '$_LIBFLAGS',
+ '-Wl,--end-group']],
+ SHLINKCOM = [['$FLOCK_SHLINK',
+ '-o',
+ '$TARGET',
+ '$_LIBDIRFLAGS',
+ '$SHLINKFLAGS',
+ '$SOURCES',
+ '-Wl,--start-group',
+ '$_LIBFLAGS',
+ '-Wl,--end-group']],
+ ),
+ 'ImportExternal' : [
+ 'AS',
+ 'CC',
+ 'CXX',
+ 'LINK',
+ ],
+ 'PropagateExternal' : [
+ 'AS',
+ 'CC',
+ 'CCACHE_DIR',
+ 'CXX',
+ 'DISTCC_DIR',
+ 'DISTCC_HOSTS',
+ 'HOME',
+ 'INCLUDE_SERVER_ARGS',
+ 'INCLUDE_SERVER_PORT',
+ 'LINK',
+ 'CHROME_BUILD_TYPE',
+ 'CHROMIUM_BUILD',
+ 'OFFICIAL_BUILD',
+ ],
+ },
+ 'Debug' : {
+ 'Append' : dict(
+ CCFLAGS = [
+ '-pthread',
+ '-fno-exceptions',
+ '-D_FILE_OFFSET_BITS=64',
+ '-fvisibility=hidden',
+ '-fno-strict-aliasing',
+ '-I/usr/include/nss',
+ '-I/usr/include/nspr',
+ '-O0',
+ '-g'
+ ],
+ CPPDEFINES = [
+ 'CHROMIUM_BUILD',
+ 'ENABLE_GPU=1',
+ 'NSS_ENABLE_ECC',
+ 'NSS_ENABLE_ZLIB',
+ 'USE_UTIL_DIRECTLY',
+ 'SHLIB_PREFIX=\\"lib\\"',
+ 'SHLIB_SUFFIX=\\"so\\"',
+ 'SHLIB_VERSION=\\"3\\"',
+ 'SOFTOKEN_SHLIB_VERSION=\\"3\\"',
+ '_DEBUG'
+ ],
+ CPPPATH = [
+ env.Dir('$SRC_DIR/net/third_party/nss/ssl/bodge')
+ ],
+ CXXFLAGS = [
+ '-fno-rtti',
+ '-fno-threadsafe-statics',
+ '-fvisibility-inlines-hidden'
+ ],
+ LINKFLAGS = [
+ '-pthread',
+ '-rdynamic'
+ ],
+ LIBS = [
+ '-lnss3',
+ '-lnssutil3',
+ '-lsmime3',
+ '-lplds4',
+ '-lplc4',
+ '-lnspr4',
+ '-lpthread',
+ '-ldl'
+ ],
+ ),
+ 'FilterOut' : dict(
+ ),
+ 'Replace' : dict(
+ FLOCK_LDMODULE = ['flock', '$TOP_BUILDDIR/linker.lock', '$LDMODULE'],
+ FLOCK_LINK = ['flock', '$TOP_BUILDDIR/linker.lock', '$LINK'],
+ FLOCK_SHLINK = ['flock', '$TOP_BUILDDIR/linker.lock', '$SHLINK'],
+ IMPLICIT_COMMAND_DEPENDENCIES = '0',
+ LDMODULECOM = [['$FLOCK_LDMODULE',
+ '-o',
+ '$TARGET',
+ '$_LIBDIRFLAGS',
+ '$LDMODULEFLAGS',
+ '$SOURCES',
+ '-Wl,--start-group',
+ '$_LIBFLAGS',
+ '-Wl,--end-group']],
+ LIBPATH = ['$LIB_DIR'],
+ LINKCOM = [['$FLOCK_LINK',
+ '-o',
+ '$TARGET',
+ '$_LIBDIRFLAGS',
+ '$LINKFLAGS',
+ '$SOURCES',
+ '-Wl,--start-group',
+ '$_LIBFLAGS',
+ '-Wl,--end-group']],
+ SHLINKCOM = [['$FLOCK_SHLINK',
+ '-o',
+ '$TARGET',
+ '$_LIBDIRFLAGS',
+ '$SHLINKFLAGS',
+ '$SOURCES',
+ '-Wl,--start-group',
+ '$_LIBFLAGS',
+ '-Wl,--end-group']],
+ ),
+ 'ImportExternal' : [
+ 'AS',
+ 'CC',
+ 'CXX',
+ 'LINK',
+ ],
+ 'PropagateExternal' : [
+ 'AS',
+ 'CC',
+ 'CCACHE_DIR',
+ 'CXX',
+ 'DISTCC_DIR',
+ 'DISTCC_HOSTS',
+ 'HOME',
+ 'INCLUDE_SERVER_ARGS',
+ 'INCLUDE_SERVER_PORT',
+ 'LINK',
+ 'CHROME_BUILD_TYPE',
+ 'CHROMIUM_BUILD',
+ 'OFFICIAL_BUILD',
+ ],
+ },
+}
+
+config = configurations[env['CONFIG_NAME']]
+env.Append(**config['Append'])
+env.FilterOut(**config['FilterOut'])
+env.Replace(**config['Replace'])
+
+# Scons forces -fPIC for SHCCFLAGS on some platforms.
+# Disable that so we can control it from cflags in gyp.
+# Note that Scons itself is inconsistent with its -fPIC
+# setting. SHCCFLAGS forces -fPIC, and SHCFLAGS does not.
+# This will make SHCCFLAGS consistent with SHCFLAGS.
+env['SHCCFLAGS'] = ['$CCFLAGS']
+
+for _var in config['ImportExternal']:
+ if _var in ARGUMENTS:
+ env[_var] = ARGUMENTS[_var]
+ elif _var in os.environ:
+ env[_var] = os.environ[_var]
+for _var in config['PropagateExternal']:
+ if _var in ARGUMENTS:
+ env[_var] = ARGUMENTS[_var]
+ elif _var in os.environ:
+ env['ENV'][_var] = os.environ[_var]
+
+env['ENV']['LD_LIBRARY_PATH'] = env.subst('$LIB_DIR')
+
+if ARGUMENTS.get('COVERAGE') not in (None, '0'):
+ env.AppendUnique(
+ CCFLAGS = [
+ '-fprofile-arcs',
+ '-ftest-coverage'
+ ],
+ LINKFLAGS = [
+ '-fprofile-arcs'
+ ],
+ )
+
+if ARGUMENTS.get('PROFILE') not in (None, '0'):
+ env.AppendUnique(
+ CCFLAGS = [
+ '-pg',
+ '-g'
+ ],
+ LINKFLAGS = [
+ '-pg'
+ ],
+ )
+
+if ARGUMENTS.get('SYMBOLS') not in (None, '0'):
+ env.AppendUnique(
+ CCFLAGS = [
+ '-g'
+ ],
+ )
+
+input_files = [
+ 'ssl/authcert.c',
+ 'ssl/cmpcert.c',
+ 'ssl/derive.c',
+ 'ssl/nsskea.c',
+ 'ssl/preenc.h',
+ 'ssl/prelib.c',
+ 'ssl/ssl.h',
+ 'ssl/ssl3con.c',
+ 'ssl/ssl3ecc.c',
+ 'ssl/ssl3ext.c',
+ 'ssl/ssl3gthr.c',
+ 'ssl/ssl3prot.h',
+ 'ssl/sslauth.c',
+ 'ssl/sslcon.c',
+ 'ssl/ssldef.c',
+ 'ssl/sslenum.c',
+ 'ssl/sslerr.c',
+ 'ssl/sslerr.h',
+ 'ssl/sslgathr.c',
+ 'ssl/sslimpl.h',
+ 'ssl/sslinfo.c',
+ 'ssl/sslmutex.c',
+ 'ssl/sslmutex.h',
+ 'ssl/sslnonce.c',
+ 'ssl/sslproto.h',
+ 'ssl/sslreveal.c',
+ 'ssl/sslsecur.c',
+ 'ssl/sslsnce.c',
+ 'ssl/sslsock.c',
+ 'ssl/sslt.h',
+ 'ssl/ssltrace.c',
+ 'ssl/sslver.c',
+ 'ssl/unix_err.c',
+ 'ssl/unix_err.h',
+ 'ssl/bodge/loader.c',
+ 'ssl/bodge/loader.h',
+ 'ssl/bodge/secure_memcmp.c',
+]
+
+target_files = []
+prerequisites = []
+
+_result = []
+for infile in input_files:
+ if env.compilable(infile):
+ if (type(infile) == type('')
+ and (infile.startswith('$SRC_DIR/net/third_party/nss/')
+ or not os.path.isabs(env.subst(infile)))):
+ # Force files below the build directory by replacing all '..'
+ # elements in the path with '__':
+ base, ext = os.path.splitext(os.path.normpath(infile))
+ base = [d == '..' and '__' or d for d in base.split('/')]
+ base = os.path.join(*base)
+ object = '${OBJ_DIR}/${COMPONENT_NAME}/${TARGET_NAME}/' + base
+ if not infile.startswith('$SRC_DIR/net/third_party/nss/'):
+ infile = '$SRC_DIR/net/third_party/nss/' + infile
+ infile = env.StaticObject(object, infile)[0]
+ else:
+ infile = env.StaticObject(infile)[0]
+ _result.append(infile)
+input_files = _result
+
+_outputs = env.GypStaticLibrary(env.File('${LIB_DIR}/${LIBPREFIX}ssl${LIBSUFFIX}'), input_files)
+target_files.extend(_outputs)
+
+gyp_target = env.Alias('ssl', target_files)
+env.Requires(gyp_target, prerequisites)
+Return("gyp_target")
diff --git a/net/third_party/nss/ssl.target.mk b/net/third_party/nss/ssl.target.mk
new file mode 100644
index 0000000..235d092
--- /dev/null
+++ b/net/third_party/nss/ssl.target.mk
@@ -0,0 +1,166 @@
+# This file is generated by gyp; do not edit.
+
+TOOLSET := target
+TARGET := ssl
+DEFS_Debug := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-DNSS_ENABLE_ECC' \
+ '-DNSS_ENABLE_ZLIB' \
+ '-DUSE_UTIL_DIRECTLY' \
+ '-DSHLIB_PREFIX="lib"' \
+ '-DSHLIB_SUFFIX="so"' \
+ '-DSHLIB_VERSION="3"' \
+ '-DSOFTOKEN_SHLIB_VERSION="3"' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=1' \
+ '-D_DEBUG' \
+ '-DDEBUG'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Debug := -pthread \
+ -fno-exceptions \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -I/usr/include/nss \
+ -I/usr/include/nspr \
+ -O0 \
+ -g
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Debug :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Debug := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Debug := -Inet/third_party/nss/ssl/bodge
+
+DEFS_Release := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-DNSS_ENABLE_ECC' \
+ '-DNSS_ENABLE_ZLIB' \
+ '-DUSE_UTIL_DIRECTLY' \
+ '-DSHLIB_PREFIX="lib"' \
+ '-DSHLIB_SUFFIX="so"' \
+ '-DSHLIB_VERSION="3"' \
+ '-DSOFTOKEN_SHLIB_VERSION="3"' \
+ '-DNDEBUG' \
+ '-DNVALGRIND' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=0'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Release := -pthread \
+ -fno-exceptions \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -I/usr/include/nss \
+ -I/usr/include/nspr \
+ -O2 \
+ -fno-ident \
+ -fdata-sections \
+ -ffunction-sections
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Release :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Release := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Release := -Inet/third_party/nss/ssl/bodge
+
+OBJS := $(obj).target/$(TARGET)/net/third_party/nss/ssl/authcert.o \
+ $(obj).target/$(TARGET)/net/third_party/nss/ssl/cmpcert.o \
+ $(obj).target/$(TARGET)/net/third_party/nss/ssl/derive.o \
+ $(obj).target/$(TARGET)/net/third_party/nss/ssl/nsskea.o \
+ $(obj).target/$(TARGET)/net/third_party/nss/ssl/prelib.o \
+ $(obj).target/$(TARGET)/net/third_party/nss/ssl/ssl3con.o \
+ $(obj).target/$(TARGET)/net/third_party/nss/ssl/ssl3ecc.o \
+ $(obj).target/$(TARGET)/net/third_party/nss/ssl/ssl3ext.o \
+ $(obj).target/$(TARGET)/net/third_party/nss/ssl/ssl3gthr.o \
+ $(obj).target/$(TARGET)/net/third_party/nss/ssl/sslauth.o \
+ $(obj).target/$(TARGET)/net/third_party/nss/ssl/sslcon.o \
+ $(obj).target/$(TARGET)/net/third_party/nss/ssl/ssldef.o \
+ $(obj).target/$(TARGET)/net/third_party/nss/ssl/sslenum.o \
+ $(obj).target/$(TARGET)/net/third_party/nss/ssl/sslerr.o \
+ $(obj).target/$(TARGET)/net/third_party/nss/ssl/sslgathr.o \
+ $(obj).target/$(TARGET)/net/third_party/nss/ssl/sslinfo.o \
+ $(obj).target/$(TARGET)/net/third_party/nss/ssl/sslmutex.o \
+ $(obj).target/$(TARGET)/net/third_party/nss/ssl/sslnonce.o \
+ $(obj).target/$(TARGET)/net/third_party/nss/ssl/sslreveal.o \
+ $(obj).target/$(TARGET)/net/third_party/nss/ssl/sslsecur.o \
+ $(obj).target/$(TARGET)/net/third_party/nss/ssl/sslsnce.o \
+ $(obj).target/$(TARGET)/net/third_party/nss/ssl/sslsock.o \
+ $(obj).target/$(TARGET)/net/third_party/nss/ssl/ssltrace.o \
+ $(obj).target/$(TARGET)/net/third_party/nss/ssl/sslver.o \
+ $(obj).target/$(TARGET)/net/third_party/nss/ssl/unix_err.o \
+ $(obj).target/$(TARGET)/net/third_party/nss/ssl/bodge/loader.o \
+ $(obj).target/$(TARGET)/net/third_party/nss/ssl/bodge/secure_memcmp.o
+
+# Add to the list of files we specially track dependencies for.
+all_deps += $(OBJS)
+
+# CFLAGS et al overrides must be target-local.
+# See "Target-specific Variable Values" in the GNU Make manual.
+$(OBJS): TOOLSET := $(TOOLSET)
+$(OBJS): GYP_CFLAGS := $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_C_$(BUILDTYPE)) $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE))
+$(OBJS): GYP_CXXFLAGS := $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_CC_$(BUILDTYPE)) $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE))
+
+# Suffix rules, putting all outputs into $(obj).
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(srcdir)/%.c FORCE_DO_CMD
+ @$(call do_cmd,cc,1)
+
+# Try building from generated source, too.
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj).$(TOOLSET)/%.c FORCE_DO_CMD
+ @$(call do_cmd,cc,1)
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj)/%.c FORCE_DO_CMD
+ @$(call do_cmd,cc,1)
+
+# End of this set of suffix rules
+### Rules for final target.
+LDFLAGS_Debug := -pthread \
+ -Wl,-z,noexecstack \
+ -rdynamic
+
+LDFLAGS_Release := -pthread \
+ -Wl,-z,noexecstack \
+ -Wl,--gc-sections
+
+LIBS := -lnss3 \
+ -lnssutil3 \
+ -lsmime3 \
+ -lplds4 \
+ -lplc4 \
+ -lnspr4 \
+ -lpthread \
+ -ldl
+
+$(obj).target/net/third_party/nss/libssl.a: GYP_LDFLAGS := $(LDFLAGS_$(BUILDTYPE))
+$(obj).target/net/third_party/nss/libssl.a: LIBS := $(LIBS)
+$(obj).target/net/third_party/nss/libssl.a: TOOLSET := $(TOOLSET)
+$(obj).target/net/third_party/nss/libssl.a: $(OBJS) FORCE_DO_CMD
+ $(call do_cmd,alink)
+
+all_deps += $(obj).target/net/third_party/nss/libssl.a
+# Add target alias
+.PHONY: ssl
+ssl: $(obj).target/net/third_party/nss/libssl.a
+
+# Add target alias to "all" target.
+.PHONY: all
+all: ssl
+
diff --git a/net/third_party/nss/ssl/ssl.def b/net/third_party/nss/ssl/ssl.def
index 6a11804..a1f4b51 100644
--- a/net/third_party/nss/ssl/ssl.def
+++ b/net/third_party/nss/ssl/ssl.def
@@ -139,7 +139,20 @@
;+ local:
;+*;
;+};
-;+NSS_CHROMIUM { # Chromium experimental
+;+NSS_3.12.6 { # NSS 3.12.6 release
+;+ global:
+SSL_ConfigServerSessionIDCacheWithOpt;
+SSL_GetImplementedCiphers;
+SSL_GetNegotiatedHostInfo;
+SSL_GetNumImplementedCiphers;
+SSL_HandshakeNegotiatedExtension;
+SSL_ReconfigFD;
+SSL_SetTrustAnchors;
+SSL_SNISocketConfigHook;
+;+ local:
+;+*;
+;+};
+;+NSS_CHROMIUM {
;+ global:
SSL_GetNextProto;
SSL_SetNextProtoNego;
diff --git a/net/third_party/nss/ssl/ssl.h b/net/third_party/nss/ssl/ssl.h
index c17f7b1..0bc02f8 100644
--- a/net/third_party/nss/ssl/ssl.h
+++ b/net/third_party/nss/ssl/ssl.h
@@ -36,7 +36,7 @@
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
-/* $Id: ssl.h,v 1.31 2009/11/25 05:24:25 wtc%google.com Exp $ */
+/* $Id: ssl.h,v 1.36 2010/02/10 18:07:21 wtc%google.com Exp $ */
#ifndef __ssl_h_
#define __ssl_h_
@@ -61,9 +61,15 @@
/* constant table enumerating all implemented SSL 2 and 3 cipher suites. */
SSL_IMPORT const PRUint16 SSL_ImplementedCiphers[];
+/* the same as the above, but is a function */
+SSL_IMPORT const PRUint16 *SSL_GetImplementedCiphers(void);
+
/* number of entries in the above table. */
SSL_IMPORT const PRUint16 SSL_NumImplementedCiphers;
+/* the same as the above, but is a function */
+SSL_IMPORT PRUint16 SSL_GetNumImplementedCiphers(void);
+
/* Macro to tell which ciphers in table are SSL2 vs SSL3/TLS. */
#define SSL_IS_SSL2_CIPHER(which) (((which) & 0xfff0) == 0xff00)
@@ -117,10 +123,22 @@
#define SSL_ENABLE_DEFLATE 19 /* Enable TLS compression with */
/* DEFLATE (off by default) */
#define SSL_ENABLE_RENEGOTIATION 20 /* Values below (default: never) */
-#define SSL_REQUIRE_SAFE_NEGOTIATION 21 /* Peer must use renegotiation */
- /* extension in ALL handshakes. */
+#define SSL_REQUIRE_SAFE_NEGOTIATION 21 /* Peer must send Signalling */
+ /* Cipher Suite Value (SCSV) or */
+ /* Renegotiation Info (RI) */
+ /* extension in ALL handshakes. */
/* default: off */
- /* NOT YET IMPLEMENTED in 3.12.5 */
+#define SSL_ENABLE_FALSE_START 22 /* Enable SSL false start (off by */
+ /* default, applies only to */
+ /* clients). False start is a */
+/* mode where an SSL client will start sending application data before */
+/* verifying the server's Finished message. This means that we could end up */
+/* sending data to an imposter. However, the data will be encrypted and */
+/* only the true server can derive the session key. Thus, so long as the */
+/* cipher isn't broken this is safe. Because of this, False Start will only */
+/* occur on RSA or DH ciphersuites where the cipher's key length is >= 80 */
+/* bits. The advantage of False Start is that it saves a round trip for */
+/* client-speaks-first protocols when performing a full handshake. */
#ifdef SSL_DEPRECATED_FUNCTION
/* Old deprecated function names */
@@ -184,11 +202,14 @@
/* Never renegotiate at all. */
#define SSL_RENEGOTIATE_NEVER ((PRBool)0)
/* Renegotiate without restriction, whether or not the peer's client hello */
-/* bears the renegotiation info extension (like we always did in the past).*/
+/* bears the renegotiation info extension. Vulnerable, as in the past. */
#define SSL_RENEGOTIATE_UNRESTRICTED ((PRBool)1)
-/* Only renegotiate if the peer's hello bears the TLS renegotiation_info */
-/* extension. Cannot renegotiate in SSL 3.0 sessions. */
-#define SSL_RENEGOTIATE_REQUIRES_XTN ((PRBool)2) /* (NOT YET IMPLEMENTED) */
+/* Only renegotiate if the peer's hello bears the TLS renegotiation_info */
+/* extension. This is safe renegotiation. */
+#define SSL_RENEGOTIATE_REQUIRES_XTN ((PRBool)2)
+/* Disallow all renegotiation in server sockets only, but allow clients */
+/* to continue to renegotiate with vulnerable servers. */
+#define SSL_RENEGOTIATE_CLIENT_ONLY ((PRBool)3)
/*
** Reset the handshake state for fd. This will make the complete SSL
@@ -282,6 +303,61 @@
/*
+** SNI extension processing callback function.
+** It is called when SSL socket receives SNI extension in ClientHello message.
+** Upon this callback invocation, application is responsible to reconfigure the
+** socket with the data for a particular server name.
+** There are three potential outcomes of this function invocation:
+** * application does not recognize the name or the type and wants the
+** "unrecognized_name" alert be sent to the client. In this case the callback
+** function must return SSL_SNI_SEND_ALERT status.
+** * application does not recognize the name, but wants to continue with
+** the handshake using the current socket configuration. In this case,
+** no socket reconfiguration is needed and the function should return
+** SSL_SNI_CURRENT_CONFIG_IS_USED.
+** * application recognizes the name and reconfigures the socket with
+** appropriate certs, key, etc. There are many ways to reconfigure. NSS
+** provides SSL_ReconfigFD function that can be used to update the socket
+** data from model socket. To continue with the rest of the handshake, the
+** implementation function should return an index of a name it has chosen.
+** LibSSL will ignore any SNI extension received in a ClientHello message
+** if application does not register a SSLSNISocketConfig callback.
+** Each type field of SECItem indicates the name type.
+** NOTE: currently RFC3546 defines only one name type: sni_host_name.
+** Client is allowed to send only one name per known type. LibSSL will
+** send an "unrecognized_name" alert if SNI extension name list contains more
+** then one name of a type.
+*/
+typedef PRInt32 (PR_CALLBACK *SSLSNISocketConfig)(PRFileDesc *fd,
+ const SECItem *srvNameArr,
+ PRUint32 srvNameArrSize,
+ void *arg);
+
+/*
+** SSLSNISocketConfig should return an index within 0 and srvNameArrSize-1
+** when it has reconfigured the socket fd to use certs and keys, etc
+** for a specific name. There are two other allowed return values. One
+** tells libSSL to use the default cert and key. The other tells libSSL
+** to send the "unrecognized_name" alert. These values are:
+**/
+#define SSL_SNI_CURRENT_CONFIG_IS_USED -1
+#define SSL_SNI_SEND_ALERT -2
+
+/*
+** Set application implemented SNISocketConfig callback.
+*/
+SSL_IMPORT SECStatus SSL_SNISocketConfigHook(PRFileDesc *fd,
+ SSLSNISocketConfig f,
+ void *arg);
+
+/*
+** Reconfigure fd SSL socket with model socket parameters. Sets
+** server certs and keys, list of trust anchor, socket options
+** and all SSL socket call backs and parameters.
+*/
+SSL_IMPORT PRFileDesc *SSL_ReconfigFD(PRFileDesc *model, PRFileDesc *fd);
+
+/*
* Set the client side argument for SSL to retrieve PKCS #11 pin.
* fd - the file descriptor for the connection in question
* a - pkcs11 application specific data
@@ -298,7 +374,7 @@
void *arg);
/*
-** Configure ssl for running a secure server. Needs the
+** Configure SSL socket for running a secure server. Needs the
** certificate for the server and the servers private key. The arguments
** are copied.
*/
@@ -307,7 +383,7 @@
SECKEYPrivateKey *key, SSLKEAType kea);
/*
-** Configure a secure servers session-id cache. Define the maximum number
+** Configure a secure server's session-id cache. Define the maximum number
** of entries in the cache, the longevity of the entires, and the directory
** where the cache files will be placed. These values can be zero, and
** if so, the implementation will choose defaults.
@@ -318,6 +394,18 @@
PRUint32 timeout,
PRUint32 ssl3_timeout,
const char * directory);
+
+/* Configure a secure server's session-id cache. Depends on value of
+ * enableMPCache, configures malti-proc or single proc cache. */
+SSL_IMPORT SECStatus SSL_ConfigServerSessionIDCacheWithOpt(
+ PRUint32 timeout,
+ PRUint32 ssl3_timeout,
+ const char * directory,
+ int maxCacheEntries,
+ int maxCertCacheEntries,
+ int maxSrvNameCacheEntries,
+ PRBool enableMPCache);
+
/*
** Like SSL_ConfigServerSessionIDCache, with one important difference.
** If the application will run multiple processes (as opposed to, or in
@@ -393,11 +481,17 @@
#endif
/*
- * Allow the application to pass a URL or hostname into the SSL library
+ * Allow the application to pass a URL or hostname into the SSL library.
*/
SSL_IMPORT SECStatus SSL_SetURL(PRFileDesc *fd, const char *url);
/*
+ * Allow an application to define a set of trust anchors for peer
+ * cert validation.
+ */
+SSL_IMPORT SECStatus SSL_SetTrustAnchors(PRFileDesc *fd, CERTCertList *list);
+
+/*
** Return the number of bytes that SSL has waiting in internal buffers.
** Return 0 if security is not enabled.
*/
@@ -436,7 +530,6 @@
SSL_IMPORT void * SSL_RevealPinArg(PRFileDesc * socket);
SSL_IMPORT char * SSL_RevealURL(PRFileDesc * socket);
-
/* This callback may be passed to the SSL library via a call to
* SSL_GetClientAuthDataHook() for each SSL client socket.
* It will be invoked when SSL needs to know what certificate and private key
@@ -499,6 +592,9 @@
SSL_IMPORT SECStatus SSL_GetCipherSuiteInfo(PRUint16 cipherSuite,
SSLCipherSuiteInfo *info, PRUintn len);
+/* Returnes negotiated through SNI host info. */
+SSL_IMPORT SECItem *SSL_GetNegotiatedHostInfo(PRFileDesc *fd);
+
/*
** Return a new reference to the certificate that was most recently sent
** to the peer on this SSL/TLS connection, or NULL if none has been sent.
@@ -536,6 +632,14 @@
PRUint16 *ciphers, int nciphers,
PRBool *pcanbypass, void *pwArg);
+/*
+** Did the handshake with the peer negotiate the given extension?
+** Output parameter valid only if function returns SECSuccess
+*/
+SSL_IMPORT SECStatus SSL_HandshakeNegotiatedExtension(PRFileDesc * socket,
+ SSLExtensionType extId,
+ PRBool *yes);
+
SEC_END_PROTOS
#endif /* __ssl_h_ */
diff --git a/net/third_party/nss/ssl/ssl3con.c b/net/third_party/nss/ssl/ssl3con.c
index 0018859..9b671e7 100644
--- a/net/third_party/nss/ssl/ssl3con.c
+++ b/net/third_party/nss/ssl/ssl3con.c
@@ -39,7 +39,7 @@
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
-/* $Id: ssl3con.c,v 1.126 2009/12/01 17:59:46 wtc%google.com Exp $ */
+/* $Id: ssl3con.c,v 1.134 2010/02/03 03:44:29 wtc%google.com Exp $ */
#include "cert.h"
#include "ssl.h"
@@ -72,6 +72,7 @@
#endif
static void ssl3_CleanupPeerCerts(sslSocket *ss);
+static void ssl3_CopyPeerCertsFromSID(sslSocket *ss, sslSessionID *sid);
static PK11SymKey *ssl3_GenerateRSAPMS(sslSocket *ss, ssl3CipherSpec *spec,
PK11SlotInfo * serverKeySlot);
static SECStatus ssl3_DeriveMasterSecret(sslSocket *ss, PK11SymKey *pms);
@@ -1169,7 +1170,7 @@
** Caller must hold SpecWriteLock.
*/
static void
-ssl3_DestroyCipherSpec(ssl3CipherSpec *spec)
+ssl3_DestroyCipherSpec(ssl3CipherSpec *spec, PRBool freeSrvName)
{
PRBool freeit = (PRBool)(!spec->bypassCiphers);
/* PORT_Assert( ss->opt.noLocks || ssl_HaveSpecWriteLock(ss)); Don't have ss! */
@@ -1187,6 +1188,9 @@
spec->destroyDecompressContext(spec->decompressContext, 1);
spec->decompressContext = NULL;
}
+ if (freeSrvName && spec->srvVirtName.data) {
+ SECITEM_FreeItem(&spec->srvVirtName, PR_FALSE);
+ }
if (spec->master_secret != NULL) {
PK11_FreeSymKey(spec->master_secret);
spec->master_secret = NULL;
@@ -1445,8 +1449,8 @@
SSLCompressionMethod compression_method;
SECStatus rv;
- PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
-
+ PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+ PORT_Assert(ss->opt.noLocks || ssl_HaveSpecWriteLock(ss));
PORT_Assert(ss->ssl3.prSpec == ss->ssl3.pwSpec);
pwSpec = ss->ssl3.pwSpec;
@@ -1558,6 +1562,14 @@
* is decrypting, and vice versa.
*/
optArg1 = !optArg1;
+ break;
+ /* kill warnings. */
+ case ssl_calg_null:
+ case ssl_calg_rc4:
+ case ssl_calg_rc2:
+ case ssl_calg_idea:
+ case ssl_calg_fortezza:
+ break;
}
rv = (*initFn)(clientContext,
@@ -1626,7 +1638,7 @@
SSLCipherAlgorithm calg;
PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
-
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSpecWriteLock(ss));
PORT_Assert(ss->ssl3.prSpec == ss->ssl3.pwSpec);
pwSpec = ss->ssl3.pwSpec;
@@ -2723,7 +2735,7 @@
* (Both the read and write sides have changed) destroy it.
*/
if (ss->ssl3.prSpec == ss->ssl3.pwSpec) {
- ssl3_DestroyCipherSpec(ss->ssl3.pwSpec);
+ ssl3_DestroyCipherSpec(ss->ssl3.pwSpec, PR_FALSE/*freeSrvName*/);
}
ssl_ReleaseSpecWriteLock(ss); /**************************************/
@@ -2785,7 +2797,7 @@
* (Both the read and write sides have changed) destroy it.
*/
if (ss->ssl3.prSpec == ss->ssl3.pwSpec) {
- ssl3_DestroyCipherSpec(ss->ssl3.prSpec);
+ ssl3_DestroyCipherSpec(ss->ssl3.prSpec, PR_FALSE/*freeSrvName*/);
}
ssl_ReleaseSpecWriteLock(ss); /*************************************/
return SECSuccess;
@@ -3206,6 +3218,8 @@
PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) ); /* protects sendBuf. */
+ if (!bytes)
+ return SECSuccess;
if (ss->sec.ci.sendBuf.space < MAX_SEND_BUF_LENGTH && room < bytes) {
rv = sslBuffer_Grow(&ss->sec.ci.sendBuf, PR_MAX(MIN_SEND_BUF_LENGTH,
PR_MIN(MAX_SEND_BUF_LENGTH, ss->sec.ci.sendBuf.len + bytes)));
@@ -3715,6 +3729,7 @@
int length;
int num_suites;
int actual_count = 0;
+ PRBool isTLS = PR_FALSE;
PRInt32 total_exten_len = 0;
unsigned numCompressionMethods;
@@ -3728,6 +3743,7 @@
if (rv != SECSuccess) {
return rv; /* ssl3_InitState has set the error code. */
}
+ ss->ssl3.hs.sendingSCSV = PR_FALSE; /* Must be reset every handshake */
/* We might be starting a session renegotiation in which case we should
* clear previous state.
@@ -3825,6 +3841,7 @@
}
}
+ isTLS = (ss->version > SSL_LIBRARY_VERSION_3_0);
ssl_GetSpecWriteLock(ss);
cwSpec = ss->ssl3.cwSpec;
if (cwSpec->mac_def->mac == mac_null) {
@@ -3852,7 +3869,17 @@
if (!num_suites)
return SECFailure; /* ssl3_config_match_init has set error code. */
- if (ss->opt.enableTLS && ss->version > SSL_LIBRARY_VERSION_3_0) {
+ /* HACK for SCSV in SSL 3.0. On initial handshake, prepend SCSV,
+ * only if TLS is disabled.
+ */
+ if (!ss->firstHsDone && !isTLS) {
+ /* Must set this before calling Hello Extension Senders,
+ * to suppress sending of empty RI extension.
+ */
+ ss->ssl3.hs.sendingSCSV = PR_TRUE;
+ }
+
+ if (isTLS || (ss->firstHsDone && ss->peerRequestedProtection)) {
PRUint32 maxBytes = 65535; /* 2^16 - 1 */
PRInt32 extLen;
@@ -3866,8 +3893,10 @@
if (total_exten_len > 0)
total_exten_len += 2;
}
+
#if defined(NSS_ENABLE_ECC) && !defined(NSS_ECC_MORE_THAN_SUITE_B)
- else { /* SSL3 only */
+ if (!total_exten_len || !isTLS) {
+ /* not sending the elliptic_curves and ec_point_formats extensions */
ssl3_DisableECCSuites(ss, NULL); /* disable all ECC suites */
}
#endif
@@ -3876,6 +3905,9 @@
num_suites = count_cipher_suites(ss, ss->ssl3.policy, PR_TRUE);
if (!num_suites)
return SECFailure; /* count_cipher_suites has set error code. */
+ if (ss->ssl3.hs.sendingSCSV) {
+ ++num_suites; /* make room for SCSV */
+ }
/* count compression methods */
numCompressionMethods = 0;
@@ -3923,7 +3955,15 @@
return rv; /* err set by ssl3_AppendHandshake* */
}
-
+ if (ss->ssl3.hs.sendingSCSV) {
+ /* Add the actual SCSV */
+ rv = ssl3_AppendHandshakeNumber(ss, TLS_RENEGO_PROTECTION_REQUEST,
+ sizeof(ssl3CipherSuite));
+ if (rv != SECSuccess) {
+ return rv; /* err set by ssl3_AppendHandshake* */
+ }
+ actual_count++;
+ }
for (i = 0; i < ssl_V3_SUITES_IMPLEMENTED; i++) {
ssl3CipherSuiteCfg *suite = &ss->cipherSuites[i];
if (config_match(suite, ss->ssl3.policy, PR_TRUE)) {
@@ -3978,9 +4018,14 @@
}
maxBytes -= extLen;
PORT_Assert(!maxBytes);
+ }
+ if (ss->ssl3.hs.sendingSCSV) {
+ /* Since we sent the SCSV, pretend we sent empty RI extension. */
+ TLSExtensionData *xtnData = &ss->xtnData;
+ xtnData->advertised[xtnData->numAdvertised++] =
+ ssl_renegotiation_info_xtn;
}
-
rv = ssl3_FlushHandshake(ss, 0);
if (rv != SECSuccess) {
return rv; /* error code set by ssl3_FlushHandshake */
@@ -4491,19 +4536,6 @@
goto loser;
}
-#if defined(TRACE)
- if (ssl_trace >= 100) {
- SECStatus extractRV = PK11_ExtractKeyValue(pms);
- if (extractRV == SECSuccess) {
- SECItem * keyData = PK11_GetKeyData(pms);
- if (keyData && keyData->data && keyData->len) {
- ssl_PrintBuf(ss, "Pre-Master Secret",
- keyData->data, keyData->len);
- }
- }
- }
-#endif
-
/* Get the wrapped (encrypted) pre-master secret, enc_pms */
enc_pms.len = SECKEY_PublicKeyStrength(svrPubKey);
enc_pms.data = (unsigned char*)PORT_Alloc(enc_pms.len);
@@ -4518,6 +4550,48 @@
goto loser;
}
+#if defined(TRACE)
+ if (ssl_trace >= 100 || ssl_keylog_iob) {
+ SECStatus extractRV = PK11_ExtractKeyValue(pms);
+ if (extractRV == SECSuccess) {
+ SECItem * keyData = PK11_GetKeyData(pms);
+ if (keyData && keyData->data && keyData->len) {
+ if (ssl_trace >= 100) {
+ ssl_PrintBuf(ss, "Pre-Master Secret",
+ keyData->data, keyData->len);
+ }
+ if (ssl_keylog_iob && enc_pms.len >= 8 && keyData->len == 48) {
+ /* https://developer.mozilla.org/en/NSS_Key_Log_Format */
+
+ /* There could be multiple, concurrent writers to the
+ * keylog, so we have to do everything in a single call to
+ * fwrite. */
+ char buf[4 + 8*2 + 1 + 48*2 + 1];
+ static const char hextable[16] = "0123456789abcdef";
+ unsigned int i;
+
+ strcpy(buf, "RSA ");
+
+ for (i = 0; i < 8; i++) {
+ buf[4 + i*2] = hextable[enc_pms.data[i] >> 4];
+ buf[4 + i*2 + 1] = hextable[enc_pms.data[i] & 15];
+ }
+ buf[20] = ' ';
+
+ for (i = 0; i < 48; i++) {
+ buf[21 + i*2] = hextable[keyData->data[i] >> 4];
+ buf[21 + i*2 + 1] = hextable[keyData->data[i] & 15];
+ }
+ buf[sizeof(buf) - 1] = '\n';
+
+ fwrite(buf, sizeof(buf), 1, ssl_keylog_iob);
+ fflush(ssl_keylog_iob);
+ }
+ }
+ }
+ }
+#endif
+
rv = ssl3_InitPendingCipherSpec(ss, pms);
PK11_FreeSymKey(pms); pms = NULL;
@@ -4909,20 +4983,36 @@
}
ss->ssl3.hs.compression = (SSLCompressionMethod)temp;
- /* Note that if !isTLS && length != 0, we do NOT goto alert_loser.
+ /* Note that if !isTLS and the extra stuff is not extensions, we
+ * do NOT goto alert_loser.
* There are some old SSL 3.0 implementations that do send stuff
* after the end of the server hello, and we deliberately ignore
* such stuff in the interest of maximal interoperability (being
* "generous in what you accept").
+ * Update: Starting in NSS 3.12.6, we handle the renegotiation_info
+ * extension in SSL 3.0.
*/
- if (isTLS && length != 0) {
+ if (length != 0) {
SECItem extensions;
rv = ssl3_ConsumeHandshakeVariable(ss, &extensions, 2, &b, &length);
- if (rv != SECSuccess || length != 0)
- goto alert_loser;
- rv = ssl3_HandleHelloExtensions(ss, &extensions.data, &extensions.len);
- if (rv != SECSuccess)
- goto alert_loser;
+ if (rv != SECSuccess || length != 0) {
+ if (isTLS)
+ goto alert_loser;
+ } else {
+ rv = ssl3_HandleHelloExtensions(ss, &extensions.data,
+ &extensions.len);
+ if (rv != SECSuccess)
+ goto alert_loser;
+ }
+ }
+ if ((ss->opt.requireSafeNegotiation ||
+ (ss->firstHsDone && (ss->peerRequestedProtection ||
+ ss->opt.enableRenegotiation == SSL_RENEGOTIATE_REQUIRES_XTN))) &&
+ !ssl3_ExtensionNegotiated(ss, ssl_renegotiation_info_xtn)) {
+ desc = handshake_failure;
+ errCode = ss->firstHsDone ? SSL_ERROR_RENEGOTIATION_NOT_ALLOWED
+ : SSL_ERROR_UNSAFE_NEGOTIATION;
+ goto alert_loser;
}
/* Any errors after this point are not "malformed" errors. */
@@ -5037,7 +5127,7 @@
sid->u.ssl3.sessionTicket.ticket.data != NULL)
SSL_AtomicIncrementLong(& ssl3stats.hsh_sid_stateless_resumes );
- if (ssl3_ExtensionNegotiated(ss, session_ticket_xtn))
+ if (ssl3_ExtensionNegotiated(ss, ssl_session_ticket_xtn))
ss->ssl3.hs.ws = wait_new_session_ticket;
else
ss->ssl3.hs.ws = wait_change_cipher;
@@ -5047,6 +5137,7 @@
/* copy the peer cert from the SID */
if (sid->peerCert != NULL) {
ss->sec.peerCert = CERT_DupCertificate(sid->peerCert);
+ ssl3_CopyPeerCertsFromSID(ss, sid);
}
@@ -5568,7 +5659,17 @@
return rv;
}
-
+PRBool
+ssl3_CanFalseStart(sslSocket *ss) {
+ return ss->opt.enableFalseStart &&
+ !ss->sec.isServer &&
+ !ss->ssl3.hs.isResuming &&
+ ss->ssl3.cwSpec &&
+ ss->ssl3.cwSpec->cipher_def->secret_key_size >= 10 &&
+ (ss->ssl3.hs.kea_def->exchKeyType == ssl_kea_rsa ||
+ ss->ssl3.hs.kea_def->exchKeyType == ssl_kea_dh ||
+ ss->ssl3.hs.kea_def->exchKeyType == ssl_kea_ecdh);
+}
/* Called from ssl3_HandleHandshakeMessage() when it has deciphered a complete
* ssl3 Server Hello Done message.
@@ -5642,10 +5743,16 @@
ssl_ReleaseXmitBufLock(ss); /*******************************/
- if (ssl3_ExtensionNegotiated(ss, session_ticket_xtn))
+ if (ssl3_ExtensionNegotiated(ss, ssl_session_ticket_xtn))
ss->ssl3.hs.ws = wait_new_session_ticket;
else
ss->ssl3.hs.ws = wait_change_cipher;
+
+ /* Do the handshake callback for sslv3 here. */
+ if (ss->handshakeCallback != NULL && ssl3_CanFalseStart(ss)) {
+ (ss->handshakeCallback)(ss->fd, ss->handshakeCallbackData);
+ }
+
return SECSuccess;
loser:
@@ -5679,6 +5786,25 @@
return SECSuccess;
}
+/*
+ * Called from:
+ * ssl3_HandleClientHello()
+ */
+static SECComparison
+ssl3_ServerNameCompare(const SECItem *name1, const SECItem *name2)
+{
+ if (!name1 != !name2) {
+ return SECLessThan;
+ }
+ if (!name1) {
+ return SECEqual;
+ }
+ if (name1->type != name2->type) {
+ return SECLessThan;
+ }
+ return SECITEM_CompareItem(name1, name2);
+}
+
/* Sets memory error when returning NULL.
* Called from:
* ssl3_SendClientHello()
@@ -5695,6 +5821,21 @@
if (sid == NULL)
return sid;
+ if (is_server) {
+ const SECItem * srvName;
+ SECStatus rv = SECSuccess;
+
+ ssl_GetSpecReadLock(ss); /********************************/
+ srvName = &ss->ssl3.prSpec->srvVirtName;
+ if (srvName->len && srvName->data) {
+ rv = SECITEM_CopyItem(NULL, &sid->u.ssl3.srvName, srvName);
+ }
+ ssl_ReleaseSpecReadLock(ss); /************************************/
+ if (rv != SECSuccess) {
+ PORT_Free(sid);
+ return NULL;
+ }
+ }
sid->peerID = (ss->peerID == NULL) ? NULL : PORT_Strdup(ss->peerID);
sid->urlSvrName = (ss->url == NULL) ? NULL : PORT_Strdup(ss->url);
sid->addr = ss->sec.ci.peer;
@@ -5802,6 +5943,9 @@
return SECSuccess;
}
+/* An empty TLS Renegotiation Info (RI) extension */
+static const PRUint8 emptyRIext[5] = {0xff, 0x01, 0x00, 0x01, 0x00};
+
/* Called from ssl3_HandleHandshakeMessage() when it has deciphered a complete
* ssl3 Client Hello message.
* Caller must hold Handshake and RecvBuf locks.
@@ -5854,7 +5998,8 @@
goto alert_loser;
}
if (ss->ssl3.hs.ws == idle_handshake &&
- ss->opt.enableRenegotiation == SSL_RENEGOTIATE_NEVER) {
+ (ss->opt.enableRenegotiation == SSL_RENEGOTIATE_NEVER ||
+ ss->opt.enableRenegotiation == SSL_RENEGOTIATE_CLIENT_ONLY)) {
desc = no_renegotiation;
level = alert_warning;
errCode = SSL_ERROR_RENEGOTIATION_NOT_ALLOWED;
@@ -5923,13 +6068,43 @@
goto loser; /* malformed */
}
}
+ if (!ssl3_ExtensionNegotiated(ss, ssl_renegotiation_info_xtn)) {
+ /* If we didn't receive an RI extension, look for the SCSV,
+ * and if found, treat it just like an empty RI extension
+ * by processing a local copy of an empty RI extension.
+ */
+ for (i = 0; i + 1 < suites.len; i += 2) {
+ PRUint16 suite_i = (suites.data[i] << 8) | suites.data[i + 1];
+ if (suite_i == TLS_RENEGO_PROTECTION_REQUEST) {
+ SSL3Opaque * b2 = (SSL3Opaque *)emptyRIext;
+ PRUint32 L2 = sizeof emptyRIext;
+ (void)ssl3_HandleHelloExtensions(ss, &b2, &L2);
+ break;
+ }
+ }
+ }
+ if (ss->firstHsDone &&
+ ss->opt.enableRenegotiation == SSL_RENEGOTIATE_REQUIRES_XTN &&
+ !ssl3_ExtensionNegotiated(ss, ssl_renegotiation_info_xtn)) {
+ desc = no_renegotiation;
+ level = alert_warning;
+ errCode = SSL_ERROR_RENEGOTIATION_NOT_ALLOWED;
+ goto alert_loser;
+ }
+ if ((ss->opt.requireSafeNegotiation ||
+ (ss->firstHsDone && ss->peerRequestedProtection)) &&
+ !ssl3_ExtensionNegotiated(ss, ssl_renegotiation_info_xtn)) {
+ desc = handshake_failure;
+ errCode = SSL_ERROR_UNSAFE_NEGOTIATION;
+ goto alert_loser;
+ }
/* We do stateful resumes only if either of the following
* conditions are satisfied: (1) the client does not support the
* session ticket extension, or (2) the client support the session
* ticket extension, but sent an empty ticket.
*/
- if (!ssl3_ExtensionNegotiated(ss, session_ticket_xtn) ||
+ if (!ssl3_ExtensionNegotiated(ss, ssl_session_ticket_xtn) ||
ss->xtnData.emptySessionTicket) {
if (sidBytes.len > 0 && !ss->opt.noCache) {
SSL_TRC(7, ("%d: SSL3[%d]: server, lookup client session-id for 0x%08x%08x%08x%08x",
@@ -5973,9 +6148,9 @@
* but OpenSSL-0.9.8g does not accept session tickets while
* resuming.)
*/
- if (ssl3_ExtensionNegotiated(ss, session_ticket_xtn) && sid == NULL) {
+ if (ssl3_ExtensionNegotiated(ss, ssl_session_ticket_xtn) && sid == NULL) {
ssl3_RegisterServerHelloExtensionSender(ss,
- session_ticket_xtn, ssl3_SendSessionTicketXtn);
+ ssl_session_ticket_xtn, ssl3_SendSessionTicketXtn);
}
if (sid != NULL) {
@@ -6053,10 +6228,9 @@
break;
#endif
/* Double check that the cached cipher suite is in the client's list */
- for (i = 0; i < suites.len; i += 2) {
- if ((suites.data[i] == MSB(suite->cipher_suite)) &&
- (suites.data[i + 1] == LSB(suite->cipher_suite))) {
-
+ for (i = 0; i + 1 < suites.len; i += 2) {
+ PRUint16 suite_i = (suites.data[i] << 8) | suites.data[i + 1];
+ if (suite_i == suite->cipher_suite) {
ss->ssl3.hs.cipher_suite = suite->cipher_suite;
ss->ssl3.hs.suite_def =
ssl_LookupCipherSuiteDef(ss->ssl3.hs.cipher_suite);
@@ -6087,10 +6261,9 @@
ssl3CipherSuiteCfg *suite = &ss->cipherSuites[j];
if (!config_match(suite, ss->ssl3.policy, PR_TRUE))
continue;
- for (i = 0; i < suites.len; i += 2) {
- if ((suites.data[i] == MSB(suite->cipher_suite)) &&
- (suites.data[i + 1] == LSB(suite->cipher_suite))) {
-
+ for (i = 0; i + 1 < suites.len; i += 2) {
+ PRUint16 suite_i = (suites.data[i] << 8) | suites.data[i + 1];
+ if (suite_i == suite->cipher_suite) {
ss->ssl3.hs.cipher_suite = suite->cipher_suite;
ss->ssl3.hs.suite_def =
ssl_LookupCipherSuiteDef(ss->ssl3.hs.cipher_suite);
@@ -6207,6 +6380,7 @@
ss->sec.ci.sid = sid;
if (sid->peerCert != NULL) {
ss->sec.peerCert = CERT_DupCertificate(sid->peerCert);
+ ssl3_CopyPeerCertsFromSID(ss, sid);
}
/*
@@ -6232,6 +6406,32 @@
ss->sec.localCert =
CERT_DupCertificate(ss->serverCerts[sid->keaType].serverCert);
+ /* Copy cached name in to pending spec */
+ if (sid != NULL &&
+ sid->version > SSL_LIBRARY_VERSION_3_0 &&
+ sid->u.ssl3.srvName.len && sid->u.ssl3.srvName.data) {
+ /* Set server name from sid */
+ SECItem *sidName = &sid->u.ssl3.srvName;
+ SECItem *pwsName = &ss->ssl3.pwSpec->srvVirtName;
+ if (pwsName->data) {
+ SECITEM_FreeItem(pwsName, PR_FALSE);
+ }
+ rv = SECITEM_CopyItem(NULL, pwsName, sidName);
+ if (rv != SECSuccess) {
+ errCode = PORT_GetError();
+ desc = internal_error;
+ goto alert_loser;
+ }
+ }
+
+ /* Clean up sni name array */
+ if (ssl3_ExtensionNegotiated(ss, ssl_server_name_xtn) &&
+ ss->xtnData.sniNameArr) {
+ PORT_Free(ss->xtnData.sniNameArr);
+ ss->xtnData.sniNameArr = NULL;
+ ss->xtnData.sniNameArrSize = 0;
+ }
+
ssl_GetXmitBufLock(ss); haveXmitBufLock = PR_TRUE;
rv = ssl3_SendServerHello(ss);
@@ -6285,6 +6485,146 @@
}
SSL_AtomicIncrementLong(& ssl3stats.hch_sid_cache_misses );
+ if (ssl3_ExtensionNegotiated(ss, ssl_server_name_xtn)) {
+ int ret = 0;
+ if (ss->sniSocketConfig) do { /* not a loop */
+ ret = SSL_SNI_SEND_ALERT;
+ /* If extension is negotiated, the len of names should > 0. */
+ if (ss->xtnData.sniNameArrSize) {
+ /* Calling client callback to reconfigure the socket. */
+ ret = (SECStatus)(*ss->sniSocketConfig)(ss->fd,
+ ss->xtnData.sniNameArr,
+ ss->xtnData.sniNameArrSize,
+ ss->sniSocketConfigArg);
+ }
+ if (ret <= SSL_SNI_SEND_ALERT) {
+ /* Application does not know the name or was not able to
+ * properly reconfigure the socket. */
+ errCode = SSL_ERROR_UNRECOGNIZED_NAME_ALERT;
+ desc = unrecognized_name;
+ break;
+ } else if (ret == SSL_SNI_CURRENT_CONFIG_IS_USED) {
+ SECStatus rv = SECSuccess;
+ SECItem * cwsName, *pwsName;
+
+ ssl_GetSpecWriteLock(ss); /*******************************/
+ pwsName = &ss->ssl3.pwSpec->srvVirtName;
+ cwsName = &ss->ssl3.cwSpec->srvVirtName;
+#ifndef SSL_SNI_ALLOW_NAME_CHANGE_2HS
+ /* not allow name change on the 2d HS */
+ if (ss->firstHsDone) {
+ if (ssl3_ServerNameCompare(pwsName, cwsName)) {
+ ssl_ReleaseSpecWriteLock(ss); /******************/
+ errCode = SSL_ERROR_UNRECOGNIZED_NAME_ALERT;
+ desc = handshake_failure;
+ ret = SSL_SNI_SEND_ALERT;
+ break;
+ }
+ }
+#endif
+ if (pwsName->data) {
+ SECITEM_FreeItem(pwsName, PR_FALSE);
+ }
+ if (cwsName->data) {
+ rv = SECITEM_CopyItem(NULL, pwsName, cwsName);
+ }
+ ssl_ReleaseSpecWriteLock(ss); /**************************/
+ if (rv != SECSuccess) {
+ errCode = SSL_ERROR_INTERNAL_ERROR_ALERT;
+ desc = internal_error;
+ ret = SSL_SNI_SEND_ALERT;
+ break;
+ }
+ } else if (ret < ss->xtnData.sniNameArrSize) {
+ /* Application has configured new socket info. Lets check it
+ * and save the name. */
+ SECStatus rv;
+ SECItem * name = &ss->xtnData.sniNameArr[ret];
+ int configedCiphers;
+ SECItem * pwsName;
+
+ /* get rid of the old name and save the newly picked. */
+ /* This code is protected by ssl3HandshakeLock. */
+ ssl_GetSpecWriteLock(ss); /*******************************/
+#ifndef SSL_SNI_ALLOW_NAME_CHANGE_2HS
+ /* not allow name change on the 2d HS */
+ if (ss->firstHsDone) {
+ SECItem *cwsName = &ss->ssl3.cwSpec->srvVirtName;
+ if (ssl3_ServerNameCompare(name, cwsName)) {
+ ssl_ReleaseSpecWriteLock(ss); /******************/
+ errCode = SSL_ERROR_UNRECOGNIZED_NAME_ALERT;
+ desc = handshake_failure;
+ ret = SSL_SNI_SEND_ALERT;
+ break;
+ }
+ }
+#endif
+ pwsName = &ss->ssl3.pwSpec->srvVirtName;
+ if (pwsName->data) {
+ SECITEM_FreeItem(pwsName, PR_FALSE);
+ }
+ rv = SECITEM_CopyItem(NULL, pwsName, name);
+ ssl_ReleaseSpecWriteLock(ss); /***************************/
+ if (rv != SECSuccess) {
+ errCode = SSL_ERROR_INTERNAL_ERROR_ALERT;
+ desc = internal_error;
+ ret = SSL_SNI_SEND_ALERT;
+ break;
+ }
+ configedCiphers = ssl3_config_match_init(ss);
+ if (configedCiphers <= 0) {
+ /* no ciphers are working/supported */
+ errCode = PORT_GetError();
+ desc = handshake_failure;
+ ret = SSL_SNI_SEND_ALERT;
+ break;
+ }
+ /* Need to tell the client that application has picked
+ * the name from the offered list and reconfigured the socket.
+ */
+ ssl3_RegisterServerHelloExtensionSender(ss, ssl_server_name_xtn,
+ ssl3_SendServerNameXtn);
+ } else {
+ /* Callback returned index outside of the boundary. */
+ PORT_Assert(ret < ss->xtnData.sniNameArrSize);
+ errCode = SSL_ERROR_INTERNAL_ERROR_ALERT;
+ desc = internal_error;
+ ret = SSL_SNI_SEND_ALERT;
+ break;
+ }
+ } while (0);
+ /* Free sniNameArr. The data that each SECItem in the array
+ * points into is the data from the input buffer "b". It will
+ * not be available outside the scope of this or it's child
+ * functions.*/
+ if (ss->xtnData.sniNameArr) {
+ PORT_Free(ss->xtnData.sniNameArr);
+ ss->xtnData.sniNameArr = NULL;
+ ss->xtnData.sniNameArrSize = 0;
+ }
+ if (ret <= SSL_SNI_SEND_ALERT) {
+ /* desc and errCode should be set. */
+ goto alert_loser;
+ }
+ }
+#ifndef SSL_SNI_ALLOW_NAME_CHANGE_2HS
+ else if (ss->firstHsDone) {
+ /* Check that we don't have the name is current spec
+ * if this extension was not negotiated on the 2d hs. */
+ PRBool passed = PR_TRUE;
+ ssl_GetSpecReadLock(ss); /*******************************/
+ if (ss->ssl3.cwSpec->srvVirtName.data) {
+ passed = PR_FALSE;
+ }
+ ssl_ReleaseSpecReadLock(ss); /***************************/
+ if (!passed) {
+ errCode = SSL_ERROR_UNRECOGNIZED_NAME_ALERT;
+ desc = handshake_failure;
+ goto alert_loser;
+ }
+ }
+#endif
+
sid = ssl3_NewSessionID(ss, PR_TRUE);
if (sid == NULL) {
errCode = PORT_GetError();
@@ -6430,11 +6770,9 @@
ssl3CipherSuiteCfg *suite = &ss->cipherSuites[j];
if (!config_match(suite, ss->ssl3.policy, PR_TRUE))
continue;
- for (i = 0; i < suite_length; i += 3) {
- if ((suites[i] == 0) &&
- (suites[i+1] == MSB(suite->cipher_suite)) &&
- (suites[i+2] == LSB(suite->cipher_suite))) {
-
+ for (i = 0; i+2 < suite_length; i += 3) {
+ PRUint32 suite_i = (suites[i] << 16)|(suites[i+1] << 8)|suites[i+2];
+ if (suite_i == suite->cipher_suite) {
ss->ssl3.hs.cipher_suite = suite->cipher_suite;
ss->ssl3.hs.suite_def =
ssl_LookupCipherSuiteDef(ss->ssl3.hs.cipher_suite);
@@ -6447,6 +6785,26 @@
suite_found:
+ /* Look for the SCSV, and if found, treat it just like an empty RI
+ * extension by processing a local copy of an empty RI extension.
+ */
+ for (i = 0; i+2 < suite_length; i += 3) {
+ PRUint32 suite_i = (suites[i] << 16) | (suites[i+1] << 8) | suites[i+2];
+ if (suite_i == TLS_RENEGO_PROTECTION_REQUEST) {
+ SSL3Opaque * b2 = (SSL3Opaque *)emptyRIext;
+ PRUint32 L2 = sizeof emptyRIext;
+ (void)ssl3_HandleHelloExtensions(ss, &b2, &L2);
+ break;
+ }
+ }
+
+ if (ss->opt.requireSafeNegotiation &&
+ !ssl3_ExtensionNegotiated(ss, ssl_renegotiation_info_xtn)) {
+ desc = handshake_failure;
+ errCode = SSL_ERROR_UNSAFE_NEGOTIATION;
+ goto alert_loser;
+ }
+
ss->ssl3.hs.compression = ssl_compression_null;
ss->sec.send = ssl3_SendApplicationData;
@@ -7391,6 +7749,38 @@
ss->ssl3.peerCertChain = NULL;
}
+static void
+ssl3_CopyPeerCertsFromSID(sslSocket *ss, sslSessionID *sid)
+{
+ PRArenaPool *arena;
+ ssl3CertNode *certs = NULL;
+ int i;
+
+ if (!sid->peerCertChain[0])
+ return;
+ PORT_Assert(!ss->ssl3.peerCertArena);
+ PORT_Assert(!ss->ssl3.peerCertChain);
+ ss->ssl3.peerCertArena = arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ for (i = 0; i < MAX_PEER_CERT_CHAIN_SIZE && sid->peerCertChain[i]; i++) {
+ ssl3CertNode *c = PORT_ArenaNew(arena, ssl3CertNode);
+ c->cert = CERT_DupCertificate(sid->peerCertChain[i]);
+ c->next = certs;
+ certs = c;
+ }
+ ss->ssl3.peerCertChain = certs;
+}
+
+static void
+ssl3_CopyPeerCertsToSID(ssl3CertNode *certs, sslSessionID *sid)
+{
+ int i = 0;
+ ssl3CertNode *c = certs;
+ for (; i < MAX_PEER_CERT_CHAIN_SIZE && c; i++, c = c->next) {
+ PORT_Assert(!sid->peerCertChain[i]);
+ sid->peerCertChain[i] = CERT_DupCertificate(c->cert);
+ }
+}
+
/* Called from ssl3_HandleHandshakeMessage() when it has deciphered a complete
* ssl3 Certificate message.
* Caller must hold Handshake and RecvBuf locks.
@@ -7577,6 +7967,7 @@
}
ss->sec.ci.sid->peerCert = CERT_DupCertificate(ss->sec.peerCert);
+ ssl3_CopyPeerCertsToSID(certs, ss->sec.ci.sid);
if (!ss->sec.isServer) {
/* set the server authentication and key exchange types and sizes
@@ -7748,6 +8139,8 @@
if (ss->handshake != NULL) {
ss->handshake = ssl_GatherRecord1stHandshake;
ss->sec.ci.sid->peerCert = CERT_DupCertificate(ss->sec.peerCert);
+ ssl3_CopyPeerCertsToSID((ssl3CertNode *)ss->ssl3.peerCertChain,
+ ss->sec.ci.sid);
ssl_GetRecvBufLock(ss);
if (ss->ssl3.hs.msgState.buf != NULL) {
@@ -7872,6 +8265,11 @@
}
if (isTLS) {
+ if (isServer)
+ ss->ssl3.hs.finishedMsgs.tFinished[1] = tlsFinished;
+ else
+ ss->ssl3.hs.finishedMsgs.tFinished[0] = tlsFinished;
+ ss->ssl3.hs.finishedBytes = sizeof tlsFinished;
rv = ssl3_AppendHandshakeHeader(ss, finished, sizeof tlsFinished);
if (rv != SECSuccess)
goto fail; /* err set by AppendHandshake. */
@@ -7879,6 +8277,11 @@
if (rv != SECSuccess)
goto fail; /* err set by AppendHandshake. */
} else {
+ if (isServer)
+ ss->ssl3.hs.finishedMsgs.sFinished[1] = hashes;
+ else
+ ss->ssl3.hs.finishedMsgs.sFinished[0] = hashes;
+ ss->ssl3.hs.finishedBytes = sizeof hashes;
rv = ssl3_AppendHandshakeHeader(ss, finished, sizeof hashes);
if (rv != SECSuccess)
goto fail; /* err set by AppendHandshake. */
@@ -8017,6 +8420,11 @@
}
rv = ssl3_ComputeTLSFinished(ss->ssl3.crSpec, !isServer,
hashes, &tlsFinished);
+ if (!isServer)
+ ss->ssl3.hs.finishedMsgs.tFinished[1] = tlsFinished;
+ else
+ ss->ssl3.hs.finishedMsgs.tFinished[0] = tlsFinished;
+ ss->ssl3.hs.finishedBytes = sizeof tlsFinished;
if (rv != SECSuccess ||
0 != NSS_SecureMemcmp(&tlsFinished, b, length)) {
(void)SSL3_SendAlert(ss, alert_fatal, decrypt_error);
@@ -8030,6 +8438,11 @@
return SECFailure;
}
+ if (!isServer)
+ ss->ssl3.hs.finishedMsgs.sFinished[1] = *hashes;
+ else
+ ss->ssl3.hs.finishedMsgs.sFinished[0] = *hashes;
+ ss->ssl3.hs.finishedBytes = sizeof *hashes;
if (0 != NSS_SecureMemcmp(hashes, b, length)) {
(void)ssl3_HandshakeFailure(ss);
PORT_SetError(SSL_ERROR_BAD_HANDSHAKE_HASH_VALUE);
@@ -8052,7 +8465,7 @@
* ServerHello message.)
*/
if (isServer && !ss->ssl3.hs.isResuming &&
- ssl3_ExtensionNegotiated(ss, session_ticket_xtn)) {
+ ssl3_ExtensionNegotiated(ss, ssl_session_ticket_xtn)) {
rv = ssl3_SendNewSessionTicket(ss);
if (rv != SECSuccess) {
goto xmit_loser;
@@ -8072,6 +8485,14 @@
if (doStepUp || ss->writerThread == PR_GetCurrentThread()) {
flags = ssl_SEND_FLAG_FORCE_INTO_BUFFER;
}
+
+ if (!isServer) {
+ rv = ssl3_SendNextProto(ss);
+ if (rv != SECSuccess) {
+ goto xmit_loser; /* err code was set. */
+ }
+ }
+
rv = ssl3_SendFinished(ss, flags);
if (rv != SECSuccess) {
goto xmit_loser; /* err is set. */
@@ -8150,7 +8571,7 @@
ss->ssl3.hs.ws = idle_handshake;
/* Do the handshake callback for sslv3 here. */
- if (ss->handshakeCallback != NULL) {
+ if (ss->handshakeCallback != NULL && !ssl3_CanFalseStart(ss)) {
(ss->handshakeCallback)(ss->fd, ss->handshakeCallbackData);
}
@@ -8643,11 +9064,33 @@
databuf->space,
plaintext->buf,
plaintext->len);
+
if (rv != SECSuccess) {
int err = ssl_MapLowLevelError(SSL_ERROR_DECOMPRESSION_FAILURE);
- PORT_Free(plaintext->buf);
SSL3_SendAlert(ss, alert_fatal,
isTLS ? decompression_failure : bad_record_mac);
+
+ /* There appears to be a bug with (at least) Apache + OpenSSL where
+ * resumed SSLv3 connections don't actually use compression. See
+ * comments 93-95 of
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=275744
+ *
+ * So, if we get a decompression error, and the record appears to
+ * be already uncompressed, then we return a more specific error
+ * code to hopefully save somebody some debugging time in the
+ * future.
+ */
+ if (plaintext->len >= 4) {
+ unsigned int len = ((unsigned int) plaintext->buf[1] << 16) |
+ ((unsigned int) plaintext->buf[2] << 8) |
+ (unsigned int) plaintext->buf[3];
+ if (len == plaintext->len - 4) {
+ /* This appears to be uncompressed already */
+ err = SSL_ERROR_RX_UNEXPECTED_UNCOMPRESSED_RECORD;
+ }
+ }
+
+ PORT_Free(plaintext->buf);
PORT_SetError(err);
return SECFailure;
}
@@ -8782,6 +9225,7 @@
ss->ssl3.crSpec = ss->ssl3.cwSpec = &ss->ssl3.specs[0];
ss->ssl3.prSpec = ss->ssl3.pwSpec = &ss->ssl3.specs[1];
ss->ssl3.hs.rehandshake = PR_FALSE;
+ ss->ssl3.hs.sendingSCSV = PR_FALSE;
ssl3_InitCipherSpec(ss, ss->ssl3.crSpec);
ssl3_InitCipherSpec(ss, ss->ssl3.prSpec);
@@ -9049,7 +9493,9 @@
PORT_SetError(SSL_ERROR_HANDSHAKE_NOT_COMPLETED);
return SECFailure;
}
- if (ss->opt.enableRenegotiation == SSL_RENEGOTIATE_NEVER) {
+ if (ss->opt.enableRenegotiation == SSL_RENEGOTIATE_NEVER ||
+ (ss->opt.enableRenegotiation == SSL_RENEGOTIATE_CLIENT_ONLY &&
+ ss->sec.isServer)) {
PORT_SetError(SSL_ERROR_RENEGOTIATION_NOT_ALLOWED);
return SECFailure;
}
@@ -9110,8 +9556,8 @@
PORT_Free(ss->ssl3.hs.msg_body.buf);
/* free up the CipherSpecs */
- ssl3_DestroyCipherSpec(&ss->ssl3.specs[0]);
- ssl3_DestroyCipherSpec(&ss->ssl3.specs[1]);
+ ssl3_DestroyCipherSpec(&ss->ssl3.specs[0], PR_TRUE/*freeSrvName*/);
+ ssl3_DestroyCipherSpec(&ss->ssl3.specs[1], PR_TRUE/*freeSrvName*/);
ss->ssl3.initialized = PR_FALSE;
diff --git a/net/third_party/nss/ssl/ssl3ecc.c b/net/third_party/nss/ssl/ssl3ecc.c
index fafecfa..42720e5 100644
--- a/net/third_party/nss/ssl/ssl3ecc.c
+++ b/net/third_party/nss/ssl/ssl3ecc.c
@@ -40,7 +40,7 @@
* ***** END LICENSE BLOCK ***** */
/* ECC code moved here from ssl3con.c */
-/* $Id: ssl3ecc.c,v 1.22 2008/03/10 00:01:28 wtc%google.com Exp $ */
+/* $Id: ssl3ecc.c,v 1.23 2010/01/28 16:14:25 kaie%kuix.de Exp $ */
#include "nss.h"
#include "cert.h"
@@ -1059,7 +1059,7 @@
if (!ss->sec.isServer) {
TLSExtensionData *xtnData = &ss->xtnData;
xtnData->advertised[xtnData->numAdvertised++] =
- elliptic_curves_xtn;
+ ssl_elliptic_curves_xtn;
}
}
return (sizeof EClist);
@@ -1083,7 +1083,7 @@
if (!ss->sec.isServer) {
TLSExtensionData *xtnData = &ss->xtnData;
xtnData->advertised[xtnData->numAdvertised++] =
- ec_point_formats_xtn;
+ ssl_ec_point_formats_xtn;
}
}
return (sizeof ECPtFmt);
diff --git a/net/third_party/nss/ssl/ssl3ext.c b/net/third_party/nss/ssl/ssl3ext.c
index 1eaf47c..ead0cfd 100644
--- a/net/third_party/nss/ssl/ssl3ext.c
+++ b/net/third_party/nss/ssl/ssl3ext.c
@@ -41,11 +41,12 @@
* ***** END LICENSE BLOCK ***** */
/* TLS extension code moved here from ssl3ecc.c */
-/* $Id: ssl3ext.c,v 1.5 2009/11/07 18:23:06 wtc%google.com Exp $ */
+/* $Id: ssl3ext.c,v 1.11 2010/02/03 02:38:20 wtc%google.com Exp $ */
#include "nssrenam.h"
#include "nss.h"
#include "ssl.h"
+#include "sslproto.h"
#include "sslimpl.h"
#include "pk11pub.h"
#include "blapi.h"
@@ -61,8 +62,7 @@
static PRBool session_ticket_keys_initialized = PR_FALSE;
static PRCallOnceType generate_session_keys_once;
-static PRInt32 ssl3_SendServerNameXtn(sslSocket * ss,
- PRBool append, PRUint32 maxBytes);
+/* forward static function declarations */
static SECStatus ssl3_ParseEncryptedSessionTicket(sslSocket *ss,
SECItem *data, EncryptedSessionTicket *enc_session_ticket);
static SECStatus ssl3_AppendToItem(SECItem *item, const unsigned char *buf,
@@ -74,6 +74,10 @@
static SECStatus ssl3_GetSessionTicketKeys(const unsigned char **aes_key,
PRUint32 *aes_key_length, const unsigned char **mac_key,
PRUint32 *mac_key_length);
+static PRInt32 ssl3_SendRenegotiationInfoXtn(sslSocket * ss,
+ PRBool append, PRUint32 maxBytes);
+static SECStatus ssl3_HandleRenegotiationInfoXtn(sslSocket *ss,
+ PRUint16 ex_type, SECItem *data);
/*
* Write bytes. Using this function means the SECItem structure
@@ -222,42 +226,58 @@
* In the second generation, this table will be dynamic, and functions
* will be registered here.
*/
+/* This table is used by the server, to handle client hello extensions. */
static const ssl3HelloExtensionHandler clientHelloHandlers[] = {
- { server_name_xtn, &ssl3_HandleServerNameXtn },
+ { ssl_server_name_xtn, &ssl3_HandleServerNameXtn },
#ifdef NSS_ENABLE_ECC
- { elliptic_curves_xtn, &ssl3_HandleSupportedCurvesXtn },
- { ec_point_formats_xtn, &ssl3_HandleSupportedPointFormatsXtn },
+ { ssl_elliptic_curves_xtn, &ssl3_HandleSupportedCurvesXtn },
+ { ssl_ec_point_formats_xtn, &ssl3_HandleSupportedPointFormatsXtn },
#endif
- { session_ticket_xtn, &ssl3_ServerHandleSessionTicketXtn },
- { next_proto_neg_xtn, &ssl3_ServerHandleNextProtoNegoXtn },
+ { ssl_session_ticket_xtn, &ssl3_ServerHandleSessionTicketXtn },
+ { ssl_renegotiation_info_xtn, &ssl3_HandleRenegotiationInfoXtn },
+ { ssl_next_proto_neg_xtn, &ssl3_ServerHandleNextProtoNegoXtn },
{ -1, NULL }
};
-static const ssl3HelloExtensionHandler serverHelloHandlers[] = {
- { server_name_xtn, &ssl3_HandleServerNameXtn },
- /* TODO: add a handler for ec_point_formats_xtn */
- { session_ticket_xtn, &ssl3_ClientHandleSessionTicketXtn },
- { next_proto_neg_xtn, &ssl3_ClientHandleNextProtoNegoXtn },
+/* These two tables are used by the client, to handle server hello
+ * extensions. */
+static const ssl3HelloExtensionHandler serverHelloHandlersTLS[] = {
+ { ssl_server_name_xtn, &ssl3_HandleServerNameXtn },
+ /* TODO: add a handler for ssl_ec_point_formats_xtn */
+ { ssl_session_ticket_xtn, &ssl3_ClientHandleSessionTicketXtn },
+ { ssl_renegotiation_info_xtn, &ssl3_HandleRenegotiationInfoXtn },
+ { ssl_next_proto_neg_xtn, &ssl3_ClientHandleNextProtoNegoXtn },
{ -1, NULL }
};
-/* Table of functions to format TLS hello extensions, one per extension.
- * This static table is for the formatting of client hello extensions.
+static const ssl3HelloExtensionHandler serverHelloHandlersSSL3[] = {
+ { ssl_renegotiation_info_xtn, &ssl3_HandleRenegotiationInfoXtn },
+ { -1, NULL }
+};
+
+/* Tables of functions to format TLS hello extensions, one function per
+ * extension.
+ * These static tables are for the formatting of client hello extensions.
* The server's table of hello senders is dynamic, in the socket struct,
* and sender functions are registered there.
*/
static const
-ssl3HelloExtensionSender clientHelloSenders[MAX_EXTENSIONS] = {
- { server_name_xtn, &ssl3_SendServerNameXtn },
+ssl3HelloExtensionSender clientHelloSendersTLS[SSL_MAX_EXTENSIONS] = {
+ { ssl_server_name_xtn, &ssl3_SendServerNameXtn },
+ { ssl_renegotiation_info_xtn, &ssl3_SendRenegotiationInfoXtn },
#ifdef NSS_ENABLE_ECC
- { elliptic_curves_xtn, &ssl3_SendSupportedCurvesXtn },
- { ec_point_formats_xtn, &ssl3_SendSupportedPointFormatsXtn },
-#else
- { -1, NULL },
- { -1, NULL },
+ { ssl_elliptic_curves_xtn, &ssl3_SendSupportedCurvesXtn },
+ { ssl_ec_point_formats_xtn, &ssl3_SendSupportedPointFormatsXtn },
#endif
- { session_ticket_xtn, ssl3_SendSessionTicketXtn },
- { next_proto_neg_xtn, ssl3_ClientSendNextProtoNegoXtn }
+ { ssl_session_ticket_xtn, &ssl3_SendSessionTicketXtn },
+ { ssl_next_proto_neg_xtn, &ssl3_ClientSendNextProtoNegoXtn }
+ /* any extra entries will appear as { 0, NULL } */
+};
+
+static const
+ssl3HelloExtensionSender clientHelloSendersSSL3[SSL_MAX_EXTENSIONS] = {
+ { ssl_renegotiation_info_xtn, &ssl3_SendRenegotiationInfoXtn }
+ /* any extra entries will appear as { 0, NULL } */
};
static PRBool
@@ -287,60 +307,159 @@
/* Format an SNI extension, using the name from the socket's URL,
* unless that name is a dotted decimal string.
+ * Used by client and server.
*/
-static PRInt32
-ssl3_SendServerNameXtn(
- sslSocket * ss,
- PRBool append,
- PRUint32 maxBytes)
+PRInt32
+ssl3_SendServerNameXtn(sslSocket * ss, PRBool append,
+ PRUint32 maxBytes)
{
- PRUint32 len;
- PRNetAddr netAddr;
-
- /* must have a hostname */
- if (!ss || !ss->url || !ss->url[0])
- return 0;
- /* must not be an IPv4 or IPv6 address */
- if (PR_SUCCESS == PR_StringToNetAddr(ss->url, &netAddr)) {
- /* is an IP address (v4 or v6) */
- return 0;
+ SECStatus rv;
+ if (!ss->sec.isServer) {
+ PRUint32 len;
+ PRNetAddr netAddr;
+
+ /* must have a hostname */
+ if (!ss || !ss->url || !ss->url[0])
+ return 0;
+ /* must not be an IPv4 or IPv6 address */
+ if (PR_SUCCESS == PR_StringToNetAddr(ss->url, &netAddr)) {
+ /* is an IP address (v4 or v6) */
+ return 0;
+ }
+ len = PORT_Strlen(ss->url);
+ if (append && maxBytes >= len + 9) {
+ /* extension_type */
+ rv = ssl3_AppendHandshakeNumber(ss, ssl_server_name_xtn, 2);
+ if (rv != SECSuccess) return -1;
+ /* length of extension_data */
+ rv = ssl3_AppendHandshakeNumber(ss, len + 5, 2);
+ if (rv != SECSuccess) return -1;
+ /* length of server_name_list */
+ rv = ssl3_AppendHandshakeNumber(ss, len + 3, 2);
+ if (rv != SECSuccess) return -1;
+ /* Name Type (sni_host_name) */
+ rv = ssl3_AppendHandshake(ss, "\0", 1);
+ if (rv != SECSuccess) return -1;
+ /* HostName (length and value) */
+ rv = ssl3_AppendHandshakeVariable(ss, (PRUint8 *)ss->url, len, 2);
+ if (rv != SECSuccess) return -1;
+ if (!ss->sec.isServer) {
+ TLSExtensionData *xtnData = &ss->xtnData;
+ xtnData->advertised[xtnData->numAdvertised++] =
+ ssl_server_name_xtn;
+ }
+ }
+ return len + 9;
}
- len = PORT_Strlen(ss->url);
- if (append && maxBytes >= len + 9) {
- SECStatus rv;
- /* extension_type */
- rv = ssl3_AppendHandshakeNumber(ss, server_name_xtn, 2);
- if (rv != SECSuccess) return -1;
- /* length of extension_data */
- rv = ssl3_AppendHandshakeNumber(ss, len + 5, 2);
- if (rv != SECSuccess) return -1;
- /* length of server_name_list */
- rv = ssl3_AppendHandshakeNumber(ss, len + 3, 2);
- if (rv != SECSuccess) return -1;
- /* Name Type (host_name) */
- rv = ssl3_AppendHandshake(ss, "\0", 1);
- if (rv != SECSuccess) return -1;
- /* HostName (length and value) */
- rv = ssl3_AppendHandshakeVariable(ss, (unsigned char *)ss->url, len, 2);
- if (rv != SECSuccess) return -1;
- if (!ss->sec.isServer) {
- TLSExtensionData *xtnData = &ss->xtnData;
- xtnData->advertised[xtnData->numAdvertised++] = server_name_xtn;
- }
+ /* Server side */
+ if (append && maxBytes >= 4) {
+ rv = ssl3_AppendHandshakeNumber(ss, ssl_server_name_xtn, 2);
+ if (rv != SECSuccess) return -1;
+ /* length of extension_data */
+ rv = ssl3_AppendHandshakeNumber(ss, 0, 2);
+ if (rv != SECSuccess) return -1;
}
- return len + 9;
+ return 4;
}
/* handle an incoming SNI extension, by ignoring it. */
SECStatus
ssl3_HandleServerNameXtn(sslSocket * ss, PRUint16 ex_type, SECItem *data)
{
- /* TODO: if client, should verify extension_data is empty. */
- /* TODO: if server, should send empty extension_data. */
- /* For now, we ignore this, as if we didn't understand it. :-) */
- return SECSuccess;
-}
+ SECItem *names = NULL;
+ PRUint32 listCount = 0, namesPos = 0, i;
+ TLSExtensionData *xtnData = &ss->xtnData;
+ SECItem ldata;
+ PRInt32 listLenBytes = 0;
+ if (!ss->sec.isServer) {
+ /* Verify extension_data is empty. */
+ if (data->data || data->len ||
+ !ssl3_ExtensionNegotiated(ss, ssl_server_name_xtn)) {
+ /* malformed or was not initiated by the client.*/
+ return SECFailure;
+ }
+ return SECSuccess;
+ }
+
+ /* Server side - consume client data and register server sender. */
+ /* do not parse the data if don't have user extension handling function. */
+ if (!ss->sniSocketConfig) {
+ return SECSuccess;
+ }
+ /* length of server_name_list */
+ listLenBytes = ssl3_ConsumeHandshakeNumber(ss, 2, &data->data, &data->len);
+ if (listLenBytes == 0 || listLenBytes != data->len) {
+ return SECFailure;
+ }
+ ldata = *data;
+ /* Calculate the size of the array.*/
+ while (listLenBytes > 0) {
+ SECItem litem;
+ SECStatus rv;
+ PRInt32 type;
+ /* Name Type (sni_host_name) */
+ type = ssl3_ConsumeHandshakeNumber(ss, 1, &ldata.data, &ldata.len);
+ if (!ldata.len) {
+ return SECFailure;
+ }
+ rv = ssl3_ConsumeHandshakeVariable(ss, &litem, 2, &ldata.data, &ldata.len);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+ /* Adjust total length for cunsumed item, item len and type.*/
+ listLenBytes -= litem.len + 3;
+ if (listLenBytes > 0 && !ldata.len) {
+ return SECFailure;
+ }
+ listCount += 1;
+ }
+ if (!listCount) {
+ return SECFailure;
+ }
+ names = PORT_ZNewArray(SECItem, listCount);
+ if (!names) {
+ return SECFailure;
+ }
+ for (i = 0;i < listCount;i++) {
+ int j;
+ PRInt32 type;
+ SECStatus rv;
+ PRBool nametypePresent = PR_FALSE;
+ /* Name Type (sni_host_name) */
+ type = ssl3_ConsumeHandshakeNumber(ss, 1, &data->data, &data->len);
+ /* Check if we have such type in the list */
+ for (j = 0;j < listCount && names[j].data;j++) {
+ if (names[j].type == type) {
+ nametypePresent = PR_TRUE;
+ break;
+ }
+ }
+ /* HostName (length and value) */
+ rv = ssl3_ConsumeHandshakeVariable(ss, &names[namesPos], 2,
+ &data->data, &data->len);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ if (nametypePresent == PR_FALSE) {
+ namesPos += 1;
+ }
+ }
+ /* Free old and set the new data. */
+ if (xtnData->sniNameArr) {
+ PORT_Free(ss->xtnData.sniNameArr);
+ }
+ xtnData->sniNameArr = names;
+ xtnData->sniNameArrSize = namesPos;
+ xtnData->negotiated[xtnData->numNegotiated++] = ssl_server_name_xtn;
+
+ return SECSuccess;
+
+loser:
+ PORT_Free(names);
+ return SECFailure;
+}
+
/* Called by both clients and servers.
* Clients sends a filled in session ticket if one is available, and otherwise
* sends an empty ticket. Servers always send empty tickets.
@@ -386,7 +505,7 @@
if (append && maxBytes >= extension_length) {
SECStatus rv;
/* extension_type */
- rv = ssl3_AppendHandshakeNumber(ss, session_ticket_xtn, 2);
+ rv = ssl3_AppendHandshakeNumber(ss, ssl_session_ticket_xtn, 2);
if (rv != SECSuccess)
goto loser;
if (session_ticket && session_ticket->ticket.data &&
@@ -402,7 +521,8 @@
if (!ss->sec.isServer) {
TLSExtensionData *xtnData = &ss->xtnData;
- xtnData->advertised[xtnData->numAdvertised++] = session_ticket_xtn;
+ xtnData->advertised[xtnData->numAdvertised++] =
+ ssl_session_ticket_xtn;
}
} else if (maxBytes < extension_length) {
PORT_Assert(0);
@@ -514,15 +634,14 @@
if (append && maxBytes >= extension_length) {
SECStatus rv;
- TLSExtensionData *xtnData;
- rv = ssl3_AppendHandshakeNumber(ss, next_proto_neg_xtn, 2);
+ rv = ssl3_AppendHandshakeNumber(ss, ssl_next_proto_neg_xtn, 2);
if (rv != SECSuccess)
goto loser;
rv = ssl3_AppendHandshakeNumber(ss, 0, 2);
if (rv != SECSuccess)
goto loser;
- xtnData = &ss->xtnData;
- xtnData->advertised[xtnData->numAdvertised++] = next_proto_neg_xtn;
+ ss->xtnData.advertised[ss->xtnData.numAdvertised++] =
+ ssl_next_proto_neg_xtn;
} else if (maxBytes < extension_length) {
return 0;
}
@@ -575,6 +694,8 @@
unsigned int computed_mac_length;
unsigned char iv[AES_BLOCK_SIZE];
SECItem ivItem;
+ SECItem *srvName = NULL;
+ PRUint32 srvNameLen = 0;
CK_MECHANISM_TYPE msWrapMech = 0; /* dummy default value,
* must be >= 0 */
@@ -635,6 +756,11 @@
}
ms_is_wrapped = PR_TRUE;
}
+ /* Prep to send negotiated name */
+ srvName = &ss->ssl3.pwSpec->srvVirtName;
+ if (srvName->data && srvName->len) {
+ srvNameLen = 2 + srvName->len; /* len bytes + name len */
+ }
ciphertext_length =
sizeof(PRUint16) /* ticket_version */
@@ -649,6 +775,8 @@
+ ms_item.len /* master_secret */
+ 1 /* client_auth_type */
+ cert_length /* cert */
+ + 1 /* server name type */
+ + srvNameLen /* name len + length field */
+ sizeof(ticket.ticket_lifetime_hint);
padding_length = AES_BLOCK_SIZE -
(ciphertext_length % AES_BLOCK_SIZE);
@@ -731,6 +859,22 @@
sizeof(ticket.ticket_lifetime_hint));
if (rv != SECSuccess) goto loser;
+ if (srvNameLen) {
+ /* Name Type (sni_host_name) */
+ rv = ssl3_AppendNumberToItem(&plaintext, srvName->type, 1);
+ if (rv != SECSuccess) goto loser;
+ /* HostName (length and value) */
+ rv = ssl3_AppendNumberToItem(&plaintext, srvName->len, 2);
+ if (rv != SECSuccess) goto loser;
+ rv = ssl3_AppendToItem(&plaintext, srvName->data, srvName->len);
+ if (rv != SECSuccess) goto loser;
+ } else {
+ /* No Name */
+ rv = ssl3_AppendNumberToItem(&plaintext, (char)TLS_STE_NO_SERVER_NAME,
+ 1);
+ if (rv != SECSuccess) goto loser;
+ }
+
PORT_Assert(plaintext.len == padding_length);
for (i = 0; i < padding_length; i++)
plaintext.data[i] = (unsigned char)padding_length;
@@ -903,6 +1047,7 @@
unsigned int buffer_len;
PRInt32 temp;
SECItem cert_item;
+ PRInt8 nameType = TLS_STE_NO_SERVER_NAME;
/* Turn off stateless session resumption if the client sends a
* SessionTicket extension, even if the extension turns out to be
@@ -1155,6 +1300,20 @@
goto no_ticket;
parsed_session_ticket->timestamp = (PRUint32)temp;
+ /* Read server name */
+ nameType =
+ ssl3_ConsumeHandshakeNumber(ss, 1, &buffer, &buffer_len);
+ if (nameType != TLS_STE_NO_SERVER_NAME) {
+ SECItem name_item;
+ rv = ssl3_ConsumeHandshakeVariable(ss, &name_item, 2, &buffer,
+ &buffer_len);
+ if (rv != SECSuccess) goto no_ticket;
+ rv = SECITEM_CopyItem(NULL, &parsed_session_ticket->srvName,
+ &name_item);
+ if (rv != SECSuccess) goto no_ticket;
+ parsed_session_ticket->srvName.type = nameType;
+ }
+
/* Done parsing. Check that all bytes have been consumed. */
if (buffer_len != padding_length)
goto no_ticket;
@@ -1211,6 +1370,9 @@
goto loser;
}
}
+ if (parsed_session_ticket->srvName.data != NULL) {
+ sid->u.ssl3.srvName = parsed_session_ticket->srvName;
+ }
ss->statelessResume = PR_TRUE;
ss->sec.ci.sid = sid;
}
@@ -1293,8 +1455,15 @@
SECStatus
ssl3_HandleHelloExtensions(sslSocket *ss, SSL3Opaque **b, PRUint32 *length)
{
- const ssl3HelloExtensionHandler * handlers =
- ss->sec.isServer ? clientHelloHandlers : serverHelloHandlers;
+ const ssl3HelloExtensionHandler * handlers;
+
+ if (ss->sec.isServer) {
+ handlers = clientHelloHandlers;
+ } else if (ss->version > SSL_LIBRARY_VERSION_3_0) {
+ handlers = serverHelloHandlersTLS;
+ } else {
+ handlers = serverHelloHandlersSSL3;
+ }
while (*length) {
const ssl3HelloExtensionHandler * handler;
@@ -1347,7 +1516,7 @@
int i;
ssl3HelloExtensionSender *sender = &ss->xtnData.serverSenders[0];
- for (i = 0; i < MAX_EXTENSIONS; ++i, ++sender) {
+ for (i = 0; i < SSL_MAX_EXTENSIONS; ++i, ++sender) {
if (!sender->ex_sender) {
sender->ex_type = ex_type;
sender->ex_sender = cb;
@@ -1360,7 +1529,7 @@
break;
}
}
- PORT_Assert(i < MAX_EXTENSIONS); /* table needs to grow */
+ PORT_Assert(i < SSL_MAX_EXTENSIONS); /* table needs to grow */
PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
return SECFailure;
}
@@ -1373,10 +1542,12 @@
PRInt32 total_exten_len = 0;
int i;
- if (!sender)
- sender = &clientHelloSenders[0];
+ if (!sender) {
+ sender = ss->version > SSL_LIBRARY_VERSION_3_0 ?
+ &clientHelloSendersTLS[0] : &clientHelloSendersSSL3[0];
+ }
- for (i = 0; i < MAX_EXTENSIONS; ++i, ++sender) {
+ for (i = 0; i < SSL_MAX_EXTENSIONS; ++i, ++sender) {
if (sender->ex_sender) {
PRInt32 extLen = (*sender->ex_sender)(ss, append, maxBytes);
if (extLen < 0)
@@ -1387,3 +1558,82 @@
}
return total_exten_len;
}
+
+
+/* Extension format:
+ * Extension number: 2 bytes
+ * Extension length: 2 bytes
+ * Verify Data Length: 1 byte
+ * Verify Data (TLS): 12 bytes (client) or 24 bytes (server)
+ * Verify Data (SSL): 36 bytes (client) or 72 bytes (server)
+ */
+static PRInt32
+ssl3_SendRenegotiationInfoXtn(
+ sslSocket * ss,
+ PRBool append,
+ PRUint32 maxBytes)
+{
+ PRInt32 len, needed;
+
+ /* In draft-ietf-tls-renegotiation-03, it is NOT RECOMMENDED to send
+ * both the SCSV and the empty RI, so when we send SCSV in
+ * the initial handshake, we don't also send RI.
+ */
+ if (!ss || ss->ssl3.hs.sendingSCSV)
+ return 0;
+ len = !ss->firstHsDone ? 0 :
+ (ss->sec.isServer ? ss->ssl3.hs.finishedBytes * 2
+ : ss->ssl3.hs.finishedBytes);
+ needed = 5 + len;
+ if (append && maxBytes >= needed) {
+ SECStatus rv;
+ /* extension_type */
+ rv = ssl3_AppendHandshakeNumber(ss, ssl_renegotiation_info_xtn, 2);
+ if (rv != SECSuccess) return -1;
+ /* length of extension_data */
+ rv = ssl3_AppendHandshakeNumber(ss, len + 1, 2);
+ if (rv != SECSuccess) return -1;
+ /* verify_Data from previous Finished message(s) */
+ rv = ssl3_AppendHandshakeVariable(ss,
+ ss->ssl3.hs.finishedMsgs.data, len, 1);
+ if (rv != SECSuccess) return -1;
+ if (!ss->sec.isServer) {
+ TLSExtensionData *xtnData = &ss->xtnData;
+ xtnData->advertised[xtnData->numAdvertised++] =
+ ssl_renegotiation_info_xtn;
+ }
+ }
+ return needed;
+}
+
+/* This function runs in both the client and server. */
+static SECStatus
+ssl3_HandleRenegotiationInfoXtn(sslSocket *ss, PRUint16 ex_type, SECItem *data)
+{
+ SECStatus rv = SECSuccess;
+ PRUint32 len = 0;
+
+ if (ss->firstHsDone) {
+ len = ss->sec.isServer ? ss->ssl3.hs.finishedBytes
+ : ss->ssl3.hs.finishedBytes * 2;
+ }
+ if (data->len != 1 + len ||
+ data->data[0] != len || (len &&
+ NSS_SecureMemcmp(ss->ssl3.hs.finishedMsgs.data,
+ data->data + 1, len))) {
+ /* Can we do this here? Or, must we arrange for the caller to do it? */
+ (void)SSL3_SendAlert(ss, alert_fatal, handshake_failure);
+ PORT_SetError(SSL_ERROR_BAD_HANDSHAKE_HASH_VALUE);
+ return SECFailure;
+ }
+ /* remember that we got this extension and it was correct. */
+ ss->peerRequestedProtection = 1;
+ ss->xtnData.negotiated[ss->xtnData.numNegotiated++] = ex_type;
+ if (ss->sec.isServer) {
+ /* prepare to send back the appropriate response */
+ rv = ssl3_RegisterServerHelloExtensionSender(ss, ex_type,
+ ssl3_SendRenegotiationInfoXtn);
+ }
+ return rv;
+}
+
diff --git a/net/third_party/nss/ssl/ssl3gthr.c b/net/third_party/nss/ssl/ssl3gthr.c
index bdd2958..6712370 100644
--- a/net/third_party/nss/ssl/ssl3gthr.c
+++ b/net/third_party/nss/ssl/ssl3gthr.c
@@ -188,6 +188,7 @@
{
SSL3Ciphertext cText;
int rv;
+ PRBool canFalseStart = PR_FALSE;
PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
do {
@@ -207,7 +208,20 @@
if (rv < 0) {
return ss->recvdCloseNotify ? 0 : rv;
}
- } while (ss->ssl3.hs.ws != idle_handshake && ss->gs.buf.len == 0);
+
+ /* If we kicked off a false start in ssl3_HandleServerHelloDone, break
+ * out of this loop early without finishing the handshake.
+ */
+ if (ss->opt.enableFalseStart) {
+ ssl_GetSSL3HandshakeLock(ss);
+ canFalseStart = (ss->ssl3.hs.ws == wait_change_cipher ||
+ ss->ssl3.hs.ws == wait_new_session_ticket) &&
+ ssl3_CanFalseStart(ss);
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ }
+ } while (ss->ssl3.hs.ws != idle_handshake &&
+ !canFalseStart &&
+ ss->gs.buf.len == 0);
ss->gs.readOffset = 0;
ss->gs.writeOffset = ss->gs.buf.len;
diff --git a/net/third_party/nss/ssl/ssl3prot.h b/net/third_party/nss/ssl/ssl3prot.h
index 84d73a9..c82c891 100644
--- a/net/third_party/nss/ssl/ssl3prot.h
+++ b/net/third_party/nss/ssl/ssl3prot.h
@@ -38,7 +38,7 @@
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
-/* $Id: ssl3prot.h,v 1.15 2009/11/07 18:23:06 wtc%google.com Exp $ */
+/* $Id: ssl3prot.h,v 1.18 2010/02/03 02:25:35 alexei.volkov.bugs%sun.com Exp $ */
#ifndef __ssl3proto_h_
#define __ssl3proto_h_
@@ -344,20 +344,8 @@
unsigned char *mac;
} EncryptedSessionTicket;
-/* Supported extensions. */
-/* Update MAX_EXTENSIONS whenever a new extension type is added. */
-typedef enum {
- server_name_xtn = 0,
-#ifdef NSS_ENABLE_ECC
- elliptic_curves_xtn = 10,
- ec_point_formats_xtn = 11,
-#endif
- session_ticket_xtn = 35,
- next_proto_neg_xtn = 13172
-} ExtensionType;
-
-#define MAX_EXTENSIONS 5
-
#define TLS_EX_SESS_TICKET_MAC_LENGTH 32
+#define TLS_STE_NO_SERVER_NAME -1
+
#endif /* __ssl3proto_h_ */
diff --git a/net/third_party/nss/ssl/sslcon.c b/net/third_party/nss/ssl/sslcon.c
index 500b787..c02b315 100644
--- a/net/third_party/nss/ssl/sslcon.c
+++ b/net/third_party/nss/ssl/sslcon.c
@@ -37,7 +37,7 @@
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
-/* $Id: sslcon.c,v 1.37 2009/10/16 17:45:35 wtc%google.com Exp $ */
+/* $Id: sslcon.c,v 1.39 2010/02/04 03:08:44 wtc%google.com Exp $ */
#include "nssrenam.h"
#include "cert.h"
@@ -3007,6 +3007,7 @@
unsigned int i;
int sendLen, sidLen = 0;
SECStatus rv;
+ TLSExtensionData *xtnData;
PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
@@ -3151,7 +3152,8 @@
localCipherSpecs = ss->cipherSpecs;
localCipherSize = ss->sizeCipherSpecs;
- sendLen = SSL_HL_CLIENT_HELLO_HBYTES + localCipherSize + sidLen +
+ /* Add 3 for SCSV */
+ sendLen = SSL_HL_CLIENT_HELLO_HBYTES + localCipherSize + 3 + sidLen +
SSL_CHALLENGE_BYTES;
/* Generate challenge bytes for server */
@@ -3176,8 +3178,9 @@
msg[1] = MSB(ss->clientHelloVersion);
msg[2] = LSB(ss->clientHelloVersion);
- msg[3] = MSB(localCipherSize);
- msg[4] = LSB(localCipherSize);
+ /* Add 3 for SCSV */
+ msg[3] = MSB(localCipherSize + 3);
+ msg[4] = LSB(localCipherSize + 3);
msg[5] = MSB(sidLen);
msg[6] = LSB(sidLen);
msg[7] = MSB(SSL_CHALLENGE_BYTES);
@@ -3185,6 +3188,16 @@
cp += SSL_HL_CLIENT_HELLO_HBYTES;
PORT_Memcpy(cp, localCipherSpecs, localCipherSize);
cp += localCipherSize;
+ /*
+ * Add SCSV. SSL 2.0 cipher suites are listed before SSL 3.0 cipher
+ * suites in localCipherSpecs for compatibility with SSL 2.0 servers.
+ * Since SCSV looks like an SSL 3.0 cipher suite, we can't add it at
+ * the beginning.
+ */
+ cp[0] = 0x00;
+ cp[1] = 0x00;
+ cp[2] = 0xff;
+ cp += 3;
if (sidLen) {
PORT_Memcpy(cp, sid->u.ssl2.sessionID, sidLen);
cp += sidLen;
@@ -3207,6 +3220,14 @@
goto loser;
}
+ /*
+ * Since we sent the SCSV, pretend we sent empty RI extension. We need
+ * to record the extension has been advertised after ssl3_InitState has
+ * been called, which ssl3_StartHandshakeHash took care for us above.
+ */
+ xtnData = &ss->xtnData;
+ xtnData->advertised[xtnData->numAdvertised++] = ssl_renegotiation_info_xtn;
+
/* Setup to receive servers hello message */
ssl_GetRecvBufLock(ss);
ss->gs.recordLen = 0;
diff --git a/net/third_party/nss/ssl/sslenum.c b/net/third_party/nss/ssl/sslenum.c
index fa834f9..b8aa8cc 100644
--- a/net/third_party/nss/ssl/sslenum.c
+++ b/net/third_party/nss/ssl/sslenum.c
@@ -39,7 +39,7 @@
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
-/* $Id: sslenum.c,v 1.16 2008/12/17 06:09:19 nelson%bolyard.com Exp $ */
+/* $Id: sslenum.c,v 1.17 2010/02/10 18:07:21 wtc%google.com Exp $ */
#include "ssl.h"
#include "sslproto.h"
@@ -54,6 +54,9 @@
* such as AES and RC4 to allow servers that prefer Camellia to negotiate
* Camellia without having to disable AES and RC4, which are needed for
* interoperability with clients that don't yet implement Camellia.
+ *
+ * If new ECC cipher suites are added, also update the ssl3CipherSuite arrays
+ * in ssl3ecc.c.
*/
const PRUint16 SSL_ImplementedCiphers[] = {
/* 256-bit */
@@ -149,3 +152,14 @@
const PRUint16 SSL_NumImplementedCiphers =
(sizeof SSL_ImplementedCiphers) / (sizeof SSL_ImplementedCiphers[0]) - 1;
+const PRUint16 *
+SSL_GetImplementedCiphers(void)
+{
+ return SSL_ImplementedCiphers;
+}
+
+PRUint16
+SSL_GetNumImplementedCiphers(void)
+{
+ return SSL_NumImplementedCiphers;
+}
diff --git a/net/third_party/nss/ssl/sslerr.h b/net/third_party/nss/ssl/sslerr.h
index c132ab9..61b721c 100644
--- a/net/third_party/nss/ssl/sslerr.h
+++ b/net/third_party/nss/ssl/sslerr.h
@@ -36,7 +36,7 @@
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
-/* $Id: sslerr.h,v 1.8 2009/11/06 20:11:28 nelson%bolyard.com Exp $ */
+/* $Id: sslerr.h,v 1.10 2010/02/03 03:44:29 wtc%google.com Exp $ */
#ifndef __SSL_ERR_H_
#define __SSL_ERR_H_
@@ -197,6 +197,9 @@
SSL_ERROR_DECOMPRESSION_FAILURE = (SSL_ERROR_BASE + 111),
SSL_ERROR_RENEGOTIATION_NOT_ALLOWED = (SSL_ERROR_BASE + 112),
+SSL_ERROR_UNSAFE_NEGOTIATION = (SSL_ERROR_BASE + 113),
+
+SSL_ERROR_RX_UNEXPECTED_UNCOMPRESSED_RECORD = (SSL_ERROR_BASE + 114),
SSL_ERROR_END_OF_LIST /* let the c compiler determine the value of this. */
} SSLErrorCodes;
diff --git a/net/third_party/nss/ssl/sslimpl.h b/net/third_party/nss/ssl/sslimpl.h
index 6f2316a..fe7ac7a 100644
--- a/net/third_party/nss/ssl/sslimpl.h
+++ b/net/third_party/nss/ssl/sslimpl.h
@@ -39,7 +39,7 @@
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
-/* $Id: sslimpl.h,v 1.70 2009/11/21 03:40:49 wtc%google.com Exp $ */
+/* $Id: sslimpl.h,v 1.77 2010/02/10 00:33:50 wtc%google.com Exp $ */
#ifndef __sslimpl_h_
#define __sslimpl_h_
@@ -130,14 +130,10 @@
#define SSL_DBG(b)
#endif
-#ifdef macintosh
-#include "pprthred.h"
-#else
#include "private/pprthred.h" /* for PR_InMonitor() */
-#endif
#define ssl_InMonitor(m) PZ_InMonitor(m)
-#define LSB(x) ((unsigned char) (x & 0xff))
+#define LSB(x) ((unsigned char) ((x) & 0xff))
#define MSB(x) ((unsigned char) (((unsigned)(x)) >> 8))
/************************************************************************/
@@ -342,6 +338,7 @@
unsigned int enableDeflate : 1; /* 19 */
unsigned int enableRenegotiation : 2; /* 20-21 */
unsigned int requireSafeNegotiation : 1; /* 22 */
+ unsigned int enableFalseStart : 1; /* 23 */
} sslOptions;
typedef enum { sslHandshakingUndetermined = 0,
@@ -561,6 +558,9 @@
SECItem msItem;
unsigned char key_block[NUM_MIXERS * MD5_LENGTH];
unsigned char raw_master_secret[56];
+ SECItem srvVirtName; /* for server: name that was negotiated
+ * with a client. For client - is
+ * always set to NULL.*/
} ssl3CipherSpec;
typedef enum { never_cached,
@@ -569,10 +569,13 @@
invalid_cache /* no longer in any cache. */
} Cached;
+#define MAX_PEER_CERT_CHAIN_SIZE 8
+
struct sslSessionIDStr {
sslSessionID * next; /* chain used for client sockets, only */
CERTCertificate * peerCert;
+ CERTCertificate * peerCertChain[MAX_PEER_CERT_CHAIN_SIZE];
const char * peerID; /* client only */
const char * urlSvrName; /* client only */
CERTCertificate * localCert;
@@ -656,6 +659,7 @@
* ClientHello message. This field is used by clients.
*/
NewSessionTicket sessionTicket;
+ SECItem srvName;
} ssl3;
} u;
};
@@ -730,16 +734,23 @@
struct TLSExtensionDataStr {
/* registered callbacks that send server hello extensions */
- ssl3HelloExtensionSender serverSenders[MAX_EXTENSIONS];
+ ssl3HelloExtensionSender serverSenders[SSL_MAX_EXTENSIONS];
/* Keep track of the extensions that are negotiated. */
PRUint16 numAdvertised;
PRUint16 numNegotiated;
- PRUint16 advertised[MAX_EXTENSIONS];
- PRUint16 negotiated[MAX_EXTENSIONS];
+ PRUint16 advertised[SSL_MAX_EXTENSIONS];
+ PRUint16 negotiated[SSL_MAX_EXTENSIONS];
/* SessionTicket Extension related data. */
PRBool ticketTimestampVerified;
PRBool emptySessionTicket;
+
+ /* SNI Extension related data
+ * Names data is not coppied from the input buffer. It can not be
+ * used outside the scope where input buffer is defined and that
+ * is beyond ssl3_HandleClientHello function. */
+ SECItem *sniNameArr;
+ PRUint32 sniNameArrSize;
};
/*
@@ -770,9 +781,16 @@
PRBool rehandshake; /* immediately start another handshake
* when this one finishes */
PRBool usedStepDownKey; /* we did a server key exchange. */
+ PRBool sendingSCSV; /* instead of empty RI */
sslBuffer msgState; /* current state for handshake messages*/
/* protected by recvBufLock */
sslBuffer messages; /* Accumulated handshake messages */
+ PRUint16 finishedBytes; /* size of single finished below */
+ union {
+ TLSFinished tFinished[2]; /* client, then server */
+ SSL3Hashes sFinished[2];
+ SSL3Opaque data[72];
+ } finishedMsgs;
#ifdef NSS_ENABLE_ECC
PRUint32 negotiatedECCurves; /* bit mask */
#endif /* NSS_ENABLE_ECC */
@@ -877,6 +895,7 @@
ClientIdentity client_identity;
SECItem peer_cert;
uint32 timestamp;
+ SECItem srvName; /* negotiated server name */
} SessionTicket;
/*
@@ -1022,6 +1041,7 @@
unsigned long recvdCloseNotify; /* received SSL EOF. */
unsigned long TCPconnected;
unsigned long appDataBuffered;
+ unsigned long peerRequestedProtection; /* from old renegotiation */
/* version of the protocol to use */
SSL3ProtocolVersion version;
@@ -1050,6 +1070,8 @@
void *authCertificateArg;
SSLGetClientAuthData getClientAuthData;
void *getClientAuthDataArg;
+ SSLSNISocketConfig sniSocketConfig;
+ void *sniSocketConfigArg;
SSLBadCertHandler handleBadCert;
void *badCertArg;
SSLHandshakeCallback handshakeCallback;
@@ -1130,6 +1152,7 @@
extern char ssl_debug;
extern char ssl_trace;
extern FILE * ssl_trace_iob;
+extern FILE * ssl_keylog_iob;
extern CERTDistNames * ssl3_server_ca_list;
extern PRUint32 ssl_sid_timeout;
extern PRUint32 ssl3_sid_timeout;
@@ -1247,6 +1270,8 @@
extern SECStatus ssl_EnableNagleDelay(sslSocket *ss, PRBool enabled);
+extern PRBool ssl3_CanFalseStart(sslSocket *ss);
+
#define SSL_LOCK_READER(ss) if (ss->recvLock) PZ_Lock(ss->recvLock)
#define SSL_UNLOCK_READER(ss) if (ss->recvLock) PZ_Unlock(ss->recvLock)
#define SSL_LOCK_WRITER(ss) if (ss->sendLock) PZ_Lock(ss->sendLock)
@@ -1501,6 +1526,23 @@
*/
extern PRInt32 ssl3_SendSessionTicketXtn(sslSocket *ss, PRBool append,
PRUint32 maxBytes);
+
+/* ClientHello and ServerHello extension senders.
+ * The code is in ssl3ext.c.
+ */
+extern PRInt32 ssl3_SendServerNameXtn(sslSocket *ss, PRBool append,
+ PRUint32 maxBytes);
+
+/* Assigns new cert, cert chain and keys to ss->serverCerts
+ * struct. If certChain is NULL, tries to find one. Aborts if
+ * fails to do so. If cert and keyPair are NULL - unconfigures
+ * sslSocket of kea type.*/
+extern SECStatus ssl_ConfigSecureServer(sslSocket *ss, CERTCertificate *cert,
+ CERTCertificateList *certChain,
+ ssl3KeyPair *keyPair, SSLKEAType kea);
+/* Return key type for the cert */
+extern SSLKEAType ssl_FindCertKEAType(CERTCertificate * cert);
+
#ifdef NSS_ENABLE_ECC
extern PRInt32 ssl3_SendSupportedCurvesXtn(sslSocket *ss,
PRBool append, PRUint32 maxBytes);
diff --git a/net/third_party/nss/ssl/sslinfo.c b/net/third_party/nss/ssl/sslinfo.c
index baa1ab3..e4ee35f 100644
--- a/net/third_party/nss/ssl/sslinfo.c
+++ b/net/third_party/nss/ssl/sslinfo.c
@@ -34,7 +34,7 @@
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
-/* $Id: sslinfo.c,v 1.21 2009/11/09 22:00:18 wtc%google.com Exp $ */
+/* $Id: sslinfo.c,v 1.23 2010/01/15 01:49:33 alexei.volkov.bugs%sun.com Exp $ */
#include "ssl.h"
#include "sslimpl.h"
#include "sslproto.h"
@@ -307,3 +307,43 @@
}
return PR_FALSE;
}
+
+SECItem*
+SSL_GetNegotiatedHostInfo(PRFileDesc *fd)
+{
+ SECItem *sniName = NULL;
+ sslSocket *ss;
+ char *name = NULL;
+
+ ss = ssl_FindSocket(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in SSL_GetNegotiatedHostInfo",
+ SSL_GETPID(), fd));
+ return NULL;
+ }
+
+ if (ss->sec.isServer) {
+ if (ss->version > SSL_LIBRARY_VERSION_3_0 &&
+ ss->ssl3.initialized) { /* TLS */
+ SECItem *crsName;
+ ssl_GetSpecReadLock(ss); /*********************************/
+ crsName = &ss->ssl3.crSpec->srvVirtName;
+ if (crsName->data) {
+ sniName = SECITEM_DupItem(crsName);
+ }
+ ssl_ReleaseSpecReadLock(ss); /*----------------------------*/
+ }
+ return sniName;
+ }
+ name = SSL_RevealURL(fd);
+ if (name) {
+ sniName = PORT_ZNew(SECItem);
+ if (!sniName) {
+ PORT_Free(name);
+ return NULL;
+ }
+ sniName->data = (void*)name;
+ sniName->len = PORT_Strlen(name);
+ }
+ return sniName;
+}
diff --git a/net/third_party/nss/ssl/sslnonce.c b/net/third_party/nss/ssl/sslnonce.c
index 63dc5a2..64adc1f 100644
--- a/net/third_party/nss/ssl/sslnonce.c
+++ b/net/third_party/nss/ssl/sslnonce.c
@@ -197,6 +197,7 @@
static void
ssl_DestroySID(sslSessionID *sid)
{
+ int i;
SSL_TRC(8, ("SSL: destroy sid: sid=0x%x cached=%d", sid, sid->cached));
PORT_Assert((sid->references == 0));
@@ -216,6 +217,9 @@
if ( sid->peerCert ) {
CERT_DestroyCertificate(sid->peerCert);
}
+ for (i = 0; i < MAX_PEER_CERT_CHAIN_SIZE && sid->peerCertChain[i]; i++) {
+ CERT_DestroyCertificate(sid->peerCertChain[i]);
+ }
if ( sid->localCert ) {
CERT_DestroyCertificate(sid->localCert);
}
diff --git a/net/third_party/nss/ssl/sslproto.h b/net/third_party/nss/ssl/sslproto.h
index b0b466f..bf7b71b 100644
--- a/net/third_party/nss/ssl/sslproto.h
+++ b/net/third_party/nss/ssl/sslproto.h
@@ -39,7 +39,7 @@
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
-/* $Id: sslproto.h,v 1.13 2008/12/17 06:09:19 nelson%bolyard.com Exp $ */
+/* $Id: sslproto.h,v 1.14 2010/01/28 06:19:12 nelson%bolyard.com Exp $ */
#ifndef __sslproto_h_
#define __sslproto_h_
@@ -181,6 +181,15 @@
#define TLS_RSA_WITH_SEED_CBC_SHA 0x0096
+/* TLS "Signalling Cipher Suite Value" (SCSV). May be requested by client.
+ * Must NEVER be chosen by server. SSL 3.0 server acknowledges by sending
+ * back an empty Renegotiation Info (RI) server hello extension.
+ */
+#define TLS_RENEGO_PROTECTION_REQUEST 0x00FF
+
+/* Cipher Suite Values starting with 0xC000 are defined in informational
+ * RFCs.
+ */
#define TLS_ECDH_ECDSA_WITH_NULL_SHA 0xC001
#define TLS_ECDH_ECDSA_WITH_RC4_128_SHA 0xC002
#define TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA 0xC003
diff --git a/net/third_party/nss/ssl/sslreveal.c b/net/third_party/nss/ssl/sslreveal.c
index a981dee..74f8814 100644
--- a/net/third_party/nss/ssl/sslreveal.c
+++ b/net/third_party/nss/ssl/sslreveal.c
@@ -36,7 +36,7 @@
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
-/* $Id: sslreveal.c,v 1.4 2004/04/27 23:04:39 gerv%gerv.net Exp $ */
+/* $Id: sslreveal.c,v 1.7 2010/02/04 03:21:11 wtc%google.com Exp $ */
#include "cert.h"
#include "ssl.h"
@@ -82,7 +82,7 @@
/* given PRFileDesc, returns a pointer to the URL associated with the socket
- * the caller should free url when done
+ * the caller should free url when done
*/
char *
SSL_RevealURL(PRFileDesc * fd)
@@ -98,3 +98,41 @@
return url;
}
+
+/* given PRFileDesc, returns status information related to extensions
+ * negotiated with peer during the handshake.
+ */
+
+SECStatus
+SSL_HandshakeNegotiatedExtension(PRFileDesc * socket,
+ SSLExtensionType extId,
+ PRBool *pYes)
+{
+ /* some decisions derived from SSL_GetChannelInfo */
+ sslSocket * sslsocket = NULL;
+ SECStatus rv = SECFailure;
+
+ if (!pYes)
+ return rv;
+
+ sslsocket = ssl_FindSocket(socket);
+
+ /* according to public API SSL_GetChannelInfo, this doesn't need a lock */
+ if (sslsocket && sslsocket->opt.useSecurity && sslsocket->firstHsDone) {
+ if (sslsocket->ssl3.initialized) { /* SSL3 and TLS */
+ /* now we know this socket went through ssl3_InitState() and
+ * ss->xtnData got initialized, which is the only member accessed by
+ * ssl3_ExtensionNegotiated();
+ * Member xtnData appears to get accessed in functions that handle
+ * the handshake (hello messages and extension sending),
+ * therefore the handshake lock should be sufficient.
+ */
+ ssl_GetSSL3HandshakeLock(sslsocket);
+ *pYes = ssl3_ExtensionNegotiated(sslsocket, extId);
+ ssl_ReleaseSSL3HandshakeLock(sslsocket);
+ rv = SECSuccess;
+ }
+ }
+
+ return rv;
+}
diff --git a/net/third_party/nss/ssl/sslsecur.c b/net/third_party/nss/ssl/sslsecur.c
index c13100a..49a81bc 100644
--- a/net/third_party/nss/ssl/sslsecur.c
+++ b/net/third_party/nss/ssl/sslsecur.c
@@ -37,7 +37,7 @@
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
-/* $Id: sslsecur.c,v 1.42 2008/10/03 19:20:20 wtc%google.com Exp $ */
+/* $Id: sslsecur.c,v 1.43 2010/01/14 22:15:25 alexei.volkov.bugs%sun.com Exp $ */
#include "cert.h"
#include "secitem.h"
#include "keyhi.h"
@@ -672,6 +672,79 @@
return PR_FAILURE;
}
+SECStatus
+ssl_ConfigSecureServer(sslSocket *ss, CERTCertificate *cert,
+ CERTCertificateList *certChain,
+ ssl3KeyPair *keyPair, SSLKEAType kea)
+{
+ CERTCertificateList *localCertChain = NULL;
+ sslServerCerts *sc = ss->serverCerts + kea;
+
+ /* load the server certificate */
+ if (sc->serverCert != NULL) {
+ CERT_DestroyCertificate(sc->serverCert);
+ sc->serverCert = NULL;
+ sc->serverKeyBits = 0;
+ }
+ /* load the server cert chain */
+ if (sc->serverCertChain != NULL) {
+ CERT_DestroyCertificateList(sc->serverCertChain);
+ sc->serverCertChain = NULL;
+ }
+ if (cert) {
+ sc->serverCert = CERT_DupCertificate(cert);
+ /* get the size of the cert's public key, and remember it */
+ sc->serverKeyBits = SECKEY_PublicKeyStrengthInBits(keyPair->pubKey);
+ if (!certChain) {
+ localCertChain =
+ CERT_CertChainFromCert(sc->serverCert, certUsageSSLServer,
+ PR_TRUE);
+ if (!localCertChain)
+ goto loser;
+ }
+ sc->serverCertChain = (certChain) ? CERT_DupCertList(certChain) :
+ localCertChain;
+ if (!sc->serverCertChain) {
+ goto loser;
+ }
+ localCertChain = NULL; /* consumed */
+ }
+
+ /* get keyPair */
+ if (sc->serverKeyPair != NULL) {
+ ssl3_FreeKeyPair(sc->serverKeyPair);
+ sc->serverKeyPair = NULL;
+ }
+ if (keyPair) {
+ SECKEY_CacheStaticFlags(keyPair->privKey);
+ sc->serverKeyPair = ssl3_GetKeyPairRef(keyPair);
+ }
+ if (kea == kt_rsa && cert && sc->serverKeyBits > 512 &&
+ !ss->opt.noStepDown && !ss->stepDownKeyPair) {
+ if (ssl3_CreateRSAStepDownKeys(ss) != SECSuccess) {
+ goto loser;
+ }
+ }
+ return SECSuccess;
+
+loser:
+ if (localCertChain) {
+ CERT_DestroyCertificateList(localCertChain);
+ }
+ if (sc->serverCert != NULL) {
+ CERT_DestroyCertificate(sc->serverCert);
+ sc->serverCert = NULL;
+ }
+ if (sc->serverCertChain != NULL) {
+ CERT_DestroyCertificateList(sc->serverCertChain);
+ sc->serverCertChain = NULL;
+ }
+ if (sc->serverKeyPair != NULL) {
+ ssl3_FreeKeyPair(sc->serverKeyPair);
+ sc->serverKeyPair = NULL;
+ }
+ return SECFailure;
+}
/* XXX need to protect the data that gets changed here.!! */
@@ -679,10 +752,10 @@
SSL_ConfigSecureServer(PRFileDesc *fd, CERTCertificate *cert,
SECKEYPrivateKey *key, SSL3KEAType kea)
{
- SECStatus rv;
sslSocket *ss;
- sslServerCerts *sc;
- SECKEYPublicKey * pubKey = NULL;
+ SECKEYPublicKey *pubKey = NULL;
+ ssl3KeyPair *keyPair = NULL;
+ SECStatus rv = SECFailure;
ss = ssl_FindSocket(fd);
if (!ss) {
@@ -708,41 +781,13 @@
return SECFailure;
}
- sc = ss->serverCerts + kea;
- /* load the server certificate */
- if (sc->serverCert != NULL) {
- CERT_DestroyCertificate(sc->serverCert);
- sc->serverCert = NULL;
- }
if (cert) {
- sc->serverCert = CERT_DupCertificate(cert);
- if (!sc->serverCert)
- goto loser;
/* get the size of the cert's public key, and remember it */
pubKey = CERT_ExtractPublicKey(cert);
if (!pubKey)
- goto loser;
- sc->serverKeyBits = SECKEY_PublicKeyStrengthInBits(pubKey);
+ return SECFailure;
}
-
- /* load the server cert chain */
- if (sc->serverCertChain != NULL) {
- CERT_DestroyCertificateList(sc->serverCertChain);
- sc->serverCertChain = NULL;
- }
- if (cert) {
- sc->serverCertChain = CERT_CertChainFromCert(
- sc->serverCert, certUsageSSLServer, PR_TRUE);
- if (sc->serverCertChain == NULL)
- goto loser;
- }
-
- /* load the private key */
- if (sc->serverKeyPair != NULL) {
- ssl3_FreeKeyPair(sc->serverKeyPair);
- sc->serverKeyPair = NULL;
- }
if (key) {
SECKEYPrivateKey * keyCopy = NULL;
CK_MECHANISM_TYPE keyMech = CKM_INVALID_MECHANISM;
@@ -770,51 +815,34 @@
keyCopy = SECKEY_CopyPrivateKey(key);
if (keyCopy == NULL)
goto loser;
- SECKEY_CacheStaticFlags(keyCopy);
- sc->serverKeyPair = ssl3_NewKeyPair(keyCopy, pubKey);
- if (sc->serverKeyPair == NULL) {
+ keyPair = ssl3_NewKeyPair(keyCopy, pubKey);
+ if (keyPair == NULL) {
SECKEY_DestroyPrivateKey(keyCopy);
goto loser;
}
pubKey = NULL; /* adopted by serverKeyPair */
}
-
- if (kea == kt_rsa && cert && sc->serverKeyBits > 512) {
- if (ss->opt.noStepDown) {
- /* disable all export ciphersuites */
- } else {
- rv = ssl3_CreateRSAStepDownKeys(ss);
- if (rv != SECSuccess) {
- return SECFailure; /* err set by ssl3_CreateRSAStepDownKeys */
- }
- }
+ if (ssl_ConfigSecureServer(ss, cert, NULL,
+ keyPair, kea) == SECFailure) {
+ goto loser;
}
/* Only do this once because it's global. */
if (PR_SUCCESS == PR_CallOnceWithArg(&setupServerCAListOnce,
&serverCAListSetup,
(void *)(ss->dbHandle))) {
- return SECSuccess;
+ rv = SECSuccess;
}
loser:
+ if (keyPair) {
+ ssl3_FreeKeyPair(keyPair);
+ }
if (pubKey) {
SECKEY_DestroyPublicKey(pubKey);
pubKey = NULL;
}
- if (sc->serverCert != NULL) {
- CERT_DestroyCertificate(sc->serverCert);
- sc->serverCert = NULL;
- }
- if (sc->serverCertChain != NULL) {
- CERT_DestroyCertificateList(sc->serverCertChain);
- sc->serverCertChain = NULL;
- }
- if (sc->serverKeyPair != NULL) {
- ssl3_FreeKeyPair(sc->serverKeyPair);
- sc->serverKeyPair = NULL;
- }
- return SECFailure;
+ return rv;
}
/************************************************************************/
@@ -1171,8 +1199,17 @@
ss->writerThread = PR_GetCurrentThread();
/* If any of these is non-zero, the initial handshake is not done. */
if (!ss->firstHsDone) {
+ PRBool canFalseStart = PR_FALSE;
ssl_Get1stHandshakeLock(ss);
- if (ss->handshake || ss->nextHandshake || ss->securityHandshake) {
+ if (ss->version >= SSL_LIBRARY_VERSION_3_0 &&
+ (ss->ssl3.hs.ws == wait_change_cipher ||
+ ss->ssl3.hs.ws == wait_finished ||
+ ss->ssl3.hs.ws == wait_new_session_ticket) &&
+ ssl3_CanFalseStart(ss)) {
+ canFalseStart = PR_TRUE;
+ }
+ if (!canFalseStart &&
+ (ss->handshake || ss->nextHandshake || ss->securityHandshake)) {
rv = ssl_Do1stHandshake(ss);
}
ssl_Release1stHandshakeLock(ss);
@@ -1241,7 +1278,8 @@
/*
* Allow the application to pass the url or hostname into the SSL library
- * so that we can do some checking on it.
+ * so that we can do some checking on it. It will be used for the value in
+ * SNI extension of client hello message.
*/
SECStatus
SSL_SetURL(PRFileDesc *fd, const char *url)
@@ -1273,6 +1311,46 @@
}
/*
+ * Allow the application to pass the set of trust anchors
+ */
+SECStatus
+SSL_SetTrustAnchors(PRFileDesc *fd, CERTCertList *certList)
+{
+ PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
+ PR_NOT_REACHED("not implemented");
+ return SECFailure;
+#if 0
+ sslSocket * ss = ssl_FindSocket(fd);
+ CERTDistNames *names = NULL;
+
+ if (!certList) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in SSL_SetTrustAnchors",
+ SSL_GETPID(), fd));
+ return SECFailure;
+ }
+
+ names = CERT_DistNamesFromCertList(certList);
+ if (names == NULL) {
+ return SECFailure;
+ }
+ ssl_Get1stHandshakeLock(ss);
+ ssl_GetSSL3HandshakeLock(ss);
+ if (ss->ssl3.ca_list) {
+ CERT_FreeDistNames(ss->ssl3.ca_list);
+ }
+ ss->ssl3.ca_list = names;
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ ssl_Release1stHandshakeLock(ss);
+
+ return SECSuccess;
+#endif
+}
+
+/*
** Returns Negative number on error, zero or greater on success.
** Returns the amount of data immediately available to be read.
*/
@@ -1440,3 +1518,22 @@
ssl_Release1stHandshakeLock(ss);
return rv;
}
+
+/* For more info see ssl.h */
+SECStatus
+SSL_SNISocketConfigHook(PRFileDesc *fd, SSLSNISocketConfig func,
+ void *arg)
+{
+ sslSocket *ss;
+
+ ss = ssl_FindSocket(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in SNISocketConfigHook",
+ SSL_GETPID(), fd));
+ return SECFailure;
+ }
+
+ ss->sniSocketConfig = func;
+ ss->sniSocketConfigArg = arg;
+ return SECSuccess;
+}
diff --git a/net/third_party/nss/ssl/sslsnce.c b/net/third_party/nss/ssl/sslsnce.c
index 115766c..5658dc2 100644
--- a/net/third_party/nss/ssl/sslsnce.c
+++ b/net/third_party/nss/ssl/sslsnce.c
@@ -36,7 +36,7 @@
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
-/* $Id: sslsnce.c,v 1.51 2009/11/07 18:23:06 wtc%google.com Exp $ */
+/* $Id: sslsnce.c,v 1.52 2010/01/14 22:15:25 alexei.volkov.bugs%sun.com Exp $ */
/* Note: ssl_FreeSID() in sslnonce.c gets used for both client and server
* cache sids!
@@ -71,6 +71,8 @@
* encKeyCacheEntry ticketEncKey; // Wrapped in non-bypass mode
* encKeyCacheEntry ticketMacKey; // Wrapped in non-bypass mode
* PRBool ticketKeysValid;
+ * sidCacheLock srvNameCacheLock;
+ * srvNameCacheEntry srvNameData[ numSrvNameCacheEntries ];
* } cacheMemCacheData;
*/
#include "seccomon.h"
@@ -84,6 +86,7 @@
#include "pk11func.h"
#include "base64.h"
#include "keyhi.h"
+#include "blapi.h"
#include <stdio.h>
@@ -150,10 +153,12 @@
/* 4 */ PRUint32 masterWrapMech;
/* 4 */ SSL3KEAType exchKeyType;
/* 4 */ PRInt32 certIndex;
-/*116 */} ssl3;
+/* 4 */ PRInt32 srvNameIndex;
+/* 32 */ PRUint8 srvNameHash[SHA256_LENGTH]; /* SHA256 name hash */
+/*152 */} ssl3;
/* force sizeof(sidCacheEntry) to be a multiple of cache line size */
struct {
-/*120 */ PRUint8 filler[120]; /* 72+120==196, a multiple of 16 */
+/*152 */ PRUint8 filler[120]; /* 72+152==224, a multiple of 16 */
} forceSize;
} u;
};
@@ -186,6 +191,18 @@
};
typedef struct encKeyCacheEntryStr encKeyCacheEntry;
+#define SSL_MAX_DNS_HOST_NAME 1024
+
+struct srvNameCacheEntryStr {
+ PRUint16 type; /* 2 */
+ PRUint16 nameLen; /* 2 */
+ PRUint8 name[SSL_MAX_DNS_HOST_NAME + 12]; /* 1034 */
+ PRUint8 nameHash[SHA256_LENGTH]; /* 32 */
+ /* 1072 */
+};
+typedef struct srvNameCacheEntryStr srvNameCacheEntry;
+
+
struct cacheDescStr {
PRUint32 cacheMemSize;
@@ -203,6 +220,9 @@
PRUint32 numKeyCacheEntries;
PRUint32 keyCacheSize;
+ PRUint32 numSrvNameCacheEntries;
+ PRUint32 srvNameCacheSize;
+
PRUint32 ssl2Timeout;
PRUint32 ssl3Timeout;
@@ -218,6 +238,7 @@
sidCacheLock * sidCacheLocks;
sidCacheLock * keyCacheLock;
sidCacheLock * certCacheLock;
+ sidCacheLock * srvNameCacheLock;
sidCacheSet * sidCacheSets;
sidCacheEntry * sidCacheData;
certCacheEntry * certCacheData;
@@ -226,6 +247,7 @@
encKeyCacheEntry * ticketEncKey;
encKeyCacheEntry * ticketMacKey;
PRUint32 * ticketKeysValid;
+ srvNameCacheEntry * srvNameCacheData;
/* Only the private copies of these pointers are valid */
char * cacheMem;
@@ -248,6 +270,7 @@
#define DEF_CERT_CACHE_ENTRIES 250
#define MIN_CERT_CACHE_ENTRIES 125 /* the effective size in old releases. */
#define DEF_KEY_CACHE_ENTRIES 250
+#define DEF_NAME_CACHE_ENTRIES 1000
#define SID_CACHE_ENTRIES_PER_SET 128
#define SID_ALIGNMENT 16
@@ -394,6 +417,59 @@
}
+/* Server configuration hash tables need to account the SECITEM.type
+ * field as well. These functions accomplish that. */
+static PLHashNumber
+Get32BitNameHash(const SECItem *name)
+{
+ PLHashNumber rv = SECITEM_Hash(name);
+
+ PRUint8 *rvc = (PRUint8 *)&rv;
+ rvc[ name->len % sizeof(rv) ] ^= name->type;
+
+ return rv;
+}
+
+/* Put a name in the cache. Update the cert index in the sce.
+*/
+static PRUint32
+CacheSrvName(cacheDesc * cache, SECItem *name, sidCacheEntry *sce)
+{
+ PRUint32 now;
+ PRUint32 ndx;
+ srvNameCacheEntry snce;
+
+ if (!name || name->len <= 0 ||
+ name->len > SSL_MAX_DNS_HOST_NAME) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return 0;
+ }
+
+ snce.type = name->type;
+ snce.nameLen = name->len;
+ PORT_Memcpy(snce.name, name->data, snce.nameLen);
+ SHA256_HashBuf(snce.nameHash, (unsigned char*)name->data,
+ name->len);
+ /* get index of the next name */
+ ndx = Get32BitNameHash(name);
+ /* get lock on cert cache */
+ now = LockSidCacheLock(cache->srvNameCacheLock, 0);
+ if (now) {
+ if (cache->numSrvNameCacheEntries > 0) {
+ /* Fit the index into array */
+ ndx %= cache->numSrvNameCacheEntries;
+ /* write the entry */
+ cache->srvNameCacheData[ndx] = snce;
+ /* remember where we put it. */
+ sce->u.ssl3.srvNameIndex = ndx;
+ /* Copy hash into sid hash */
+ PORT_Memcpy(sce->u.ssl3.srvNameHash, snce.nameHash, SHA256_LENGTH);
+ }
+ UnlockSidCacheLock(cache->srvNameCacheLock);
+ }
+ return now;
+}
+
/*
** Convert local SID to shared memory one
*/
@@ -454,6 +530,7 @@
to->u.ssl3.exchKeyType = from->u.ssl3.exchKeyType;
to->sessionIDLength = from->u.ssl3.sessionIDLength;
to->u.ssl3.certIndex = -1;
+ to->u.ssl3.srvNameIndex = -1;
PORT_Memcpy(to->sessionID, from->u.ssl3.sessionID,
to->sessionIDLength);
@@ -472,7 +549,9 @@
** Caller must hold cache lock when calling this.
*/
static sslSessionID *
-ConvertToSID(sidCacheEntry *from, certCacheEntry *pcce,
+ConvertToSID(sidCacheEntry * from,
+ certCacheEntry * pcce,
+ srvNameCacheEntry *psnce,
CERTCertDBHandle * dbHandle)
{
sslSessionID *to;
@@ -526,6 +605,17 @@
to->u.ssl3.keys = from->u.ssl3.keys;
to->u.ssl3.masterWrapMech = from->u.ssl3.masterWrapMech;
to->u.ssl3.exchKeyType = from->u.ssl3.exchKeyType;
+ if (from->u.ssl3.srvNameIndex != -1 && psnce) {
+ SECItem name;
+ SECStatus rv;
+ name.type = psnce->type;
+ name.len = psnce->nameLen;
+ name.data = psnce->name;
+ rv = SECITEM_CopyItem(NULL, &to->u.ssl3.srvName, &name);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ }
PORT_Memcpy(to->u.ssl3.sessionID, from->sessionID, from->sessionIDLength);
@@ -582,7 +672,9 @@
PORT_Free(to->u.ssl2.masterKey.data);
if (to->u.ssl2.cipherArg.data)
PORT_Free(to->u.ssl2.cipherArg.data);
- }
+ } else {
+ SECITEM_FreeItem(&to->u.ssl3.srvName, PR_FALSE);
+ }
PORT_Free(to);
}
return NULL;
@@ -682,12 +774,14 @@
sslSessionID * sid = 0;
sidCacheEntry * psce;
certCacheEntry *pcce = 0;
+ srvNameCacheEntry *psnce = 0;
cacheDesc * cache = &globalCache;
PRUint32 now;
PRUint32 set;
PRInt32 cndx;
sidCacheEntry sce;
certCacheEntry cce;
+ srvNameCacheEntry snce;
set = SIDindex(cache, addr, sessionID, sessionIDLength);
now = LockSet(cache, set, 0);
@@ -696,36 +790,65 @@
psce = FindSID(cache, set, now, addr, sessionID, sessionIDLength);
if (psce) {
- if (psce->version >= SSL_LIBRARY_VERSION_3_0 &&
- (cndx = psce->u.ssl3.certIndex) != -1) {
-
- PRUint32 gotLock = LockSidCacheLock(cache->certCacheLock, now);
- if (gotLock) {
- pcce = &cache->certCacheData[cndx];
-
- /* See if the cert's session ID matches the sce cache. */
- if ((pcce->sessionIDLength == psce->sessionIDLength) &&
- !PORT_Memcmp(pcce->sessionID, psce->sessionID,
- pcce->sessionIDLength)) {
- cce = *pcce;
- } else {
- /* The cert doesen't match the SID cache entry,
- ** so invalidate the SID cache entry.
- */
- psce->valid = 0;
- psce = 0;
- pcce = 0;
- }
- UnlockSidCacheLock(cache->certCacheLock);
- } else {
- /* what the ??. Didn't get the cert cache lock.
- ** Don't invalidate the SID cache entry, but don't find it.
- */
- PORT_Assert(!("Didn't get cert Cache Lock!"));
- psce = 0;
- pcce = 0;
- }
- }
+ if (psce->version >= SSL_LIBRARY_VERSION_3_0) {
+ if ((cndx = psce->u.ssl3.certIndex) != -1) {
+
+ PRUint32 gotLock = LockSidCacheLock(cache->certCacheLock, now);
+ if (gotLock) {
+ pcce = &cache->certCacheData[cndx];
+
+ /* See if the cert's session ID matches the sce cache. */
+ if ((pcce->sessionIDLength == psce->sessionIDLength) &&
+ !PORT_Memcmp(pcce->sessionID, psce->sessionID,
+ pcce->sessionIDLength)) {
+ cce = *pcce;
+ } else {
+ /* The cert doesen't match the SID cache entry,
+ ** so invalidate the SID cache entry.
+ */
+ psce->valid = 0;
+ psce = 0;
+ pcce = 0;
+ }
+ UnlockSidCacheLock(cache->certCacheLock);
+ } else {
+ /* what the ??. Didn't get the cert cache lock.
+ ** Don't invalidate the SID cache entry, but don't find it.
+ */
+ PORT_Assert(!("Didn't get cert Cache Lock!"));
+ psce = 0;
+ pcce = 0;
+ }
+ }
+ if ((cndx = psce->u.ssl3.srvNameIndex) != -1) {
+ PRUint32 gotLock = LockSidCacheLock(cache->srvNameCacheLock,
+ now);
+ if (gotLock) {
+ psnce = &cache->srvNameCacheData[cndx];
+
+ if (!PORT_Memcmp(psnce->nameHash, psce->u.ssl3.srvNameHash,
+ SHA256_LENGTH)) {
+ snce = *psnce;
+ } else {
+ /* The name doesen't match the SID cache entry,
+ ** so invalidate the SID cache entry.
+ */
+ psce->valid = 0;
+ psce = 0;
+ psnce = 0;
+ }
+ UnlockSidCacheLock(cache->srvNameCacheLock);
+ } else {
+ /* what the ??. Didn't get the cert cache lock.
+ ** Don't invalidate the SID cache entry, but don't find it.
+ */
+ PORT_Assert(!("Didn't get name Cache Lock!"));
+ psce = 0;
+ psnce = 0;
+ }
+
+ }
+ }
if (psce) {
psce->lastAccessTime = now;
sce = *psce; /* grab a copy while holding the lock */
@@ -736,7 +859,7 @@
/* sce conains a copy of the cache entry.
** Convert shared memory format to local format
*/
- sid = ConvertToSID(&sce, pcce ? &cce : 0, dbHandle);
+ sid = ConvertToSID(&sce, pcce ? &cce : 0, psnce ? &snce : 0, dbHandle);
}
return sid;
}
@@ -796,9 +919,14 @@
ConvertFromSID(&sce, sid);
- if ((version >= SSL_LIBRARY_VERSION_3_0) &&
- (sid->peerCert != NULL)) {
- now = CacheCert(cache, sid->peerCert, &sce);
+ if (version >= SSL_LIBRARY_VERSION_3_0) {
+ SECItem *name = &sid->u.ssl3.srvName;
+ if (name->len && name->data) {
+ now = CacheSrvName(cache, name, &sce);
+ }
+ if (sid->peerCert != NULL) {
+ now = CacheCert(cache, sid->peerCert, &sce);
+ }
}
set = SIDindex(cache, &sce.addr, sce.sessionID, sce.sessionIDLength);
@@ -924,7 +1052,8 @@
}
static SECStatus
-InitCache(cacheDesc *cache, int maxCacheEntries, PRUint32 ssl2_timeout,
+InitCache(cacheDesc *cache, int maxCacheEntries, int maxCertCacheEntries,
+ int maxSrvNameCacheEntries, PRUint32 ssl2_timeout,
PRUint32 ssl3_timeout, const char *directory, PRBool shared)
{
ptrdiff_t ptr;
@@ -973,6 +1102,11 @@
cache->numSIDCacheSetsPerLock =
SID_HOWMANY(cache->numSIDCacheSets, cache->numSIDCacheLocks);
+ cache->numCertCacheEntries = (maxCertCacheEntries > 0) ?
+ maxCertCacheEntries : 0;
+ cache->numSrvNameCacheEntries = (maxSrvNameCacheEntries > 0) ?
+ maxSrvNameCacheEntries : 0;
+
/* compute size of shared memory, and offsets of all pointers */
ptr = 0;
cache->cacheMem = (char *)ptr;
@@ -981,7 +1115,8 @@
cache->sidCacheLocks = (sidCacheLock *)ptr;
cache->keyCacheLock = cache->sidCacheLocks + cache->numSIDCacheLocks;
cache->certCacheLock = cache->keyCacheLock + 1;
- ptr = (ptrdiff_t)(cache->certCacheLock + 1);
+ cache->srvNameCacheLock = cache->certCacheLock + 1;
+ ptr = (ptrdiff_t)(cache->srvNameCacheLock + 1);
ptr = SID_ROUNDUP(ptr, SID_ALIGNMENT);
cache->sidCacheSets = (sidCacheSet *)ptr;
@@ -996,10 +1131,12 @@
cache->sidCacheSize =
(char *)cache->certCacheData - (char *)cache->sidCacheData;
- /* This is really a poor way to computer this! */
- cache->numCertCacheEntries = cache->sidCacheSize / sizeof(certCacheEntry);
- if (cache->numCertCacheEntries < MIN_CERT_CACHE_ENTRIES)
+ if (cache->numCertCacheEntries < MIN_CERT_CACHE_ENTRIES) {
+ /* This is really a poor way to computer this! */
+ cache->numCertCacheEntries = cache->sidCacheSize / sizeof(certCacheEntry);
+ if (cache->numCertCacheEntries < MIN_CERT_CACHE_ENTRIES)
cache->numCertCacheEntries = MIN_CERT_CACHE_ENTRIES;
+ }
ptr = (ptrdiff_t)(cache->certCacheData + cache->numCertCacheEntries);
ptr = SID_ROUNDUP(ptr, SID_ALIGNMENT);
@@ -1030,6 +1167,15 @@
ptr = (ptrdiff_t)(cache->ticketKeysValid + 1);
ptr = SID_ROUNDUP(ptr, SID_ALIGNMENT);
+ cache->srvNameCacheData = (srvNameCacheEntry *)ptr;
+ if (cache->numSrvNameCacheEntries < 0) {
+ cache->numSrvNameCacheEntries = DEF_NAME_CACHE_ENTRIES;
+ }
+ cache->srvNameCacheSize =
+ cache->numSrvNameCacheEntries * sizeof(srvNameCacheEntry);
+ ptr = (ptrdiff_t)(cache->srvNameCacheData + cache->numSrvNameCacheEntries);
+ ptr = SID_ROUNDUP(ptr, SID_ALIGNMENT);
+
cache->cacheMemSize = ptr;
if (ssl2_timeout) {
@@ -1113,6 +1259,7 @@
*(ptrdiff_t *)(&cache->sidCacheLocks) += ptr;
*(ptrdiff_t *)(&cache->keyCacheLock ) += ptr;
*(ptrdiff_t *)(&cache->certCacheLock) += ptr;
+ *(ptrdiff_t *)(&cache->srvNameCacheLock) += ptr;
*(ptrdiff_t *)(&cache->sidCacheSets ) += ptr;
*(ptrdiff_t *)(&cache->sidCacheData ) += ptr;
*(ptrdiff_t *)(&cache->certCacheData) += ptr;
@@ -1121,11 +1268,12 @@
*(ptrdiff_t *)(&cache->ticketEncKey ) += ptr;
*(ptrdiff_t *)(&cache->ticketMacKey ) += ptr;
*(ptrdiff_t *)(&cache->ticketKeysValid) += ptr;
+ *(ptrdiff_t *)(&cache->srvNameCacheData) += ptr;
/* initialize the locks */
init_time = ssl_Time();
pLock = cache->sidCacheLocks;
- for (locks_to_initialize = cache->numSIDCacheLocks + 2;
+ for (locks_to_initialize = cache->numSIDCacheLocks + 3;
locks_initialized < locks_to_initialize;
++locks_initialized, ++pLock ) {
@@ -1170,23 +1318,28 @@
return SECSuccess;
}
-SECStatus
-SSL_ConfigServerSessionIDCacheInstance( cacheDesc *cache,
- int maxCacheEntries,
- PRUint32 ssl2_timeout,
- PRUint32 ssl3_timeout,
- const char * directory, PRBool shared)
+static SECStatus
+ssl_ConfigServerSessionIDCacheInstanceWithOpt(cacheDesc *cache,
+ PRUint32 ssl2_timeout,
+ PRUint32 ssl3_timeout,
+ const char * directory,
+ PRBool shared,
+ int maxCacheEntries,
+ int maxCertCacheEntries,
+ int maxSrvNameCacheEntries)
{
SECStatus rv;
- PORT_Assert(sizeof(sidCacheEntry) == 192);
+ PORT_Assert(sizeof(sidCacheEntry) == 224);
PORT_Assert(sizeof(certCacheEntry) == 4096);
+ PORT_Assert(sizeof(srvNameCacheEntry) == 1072);
myPid = SSL_GETPID();
if (!directory) {
directory = DEFAULT_CACHE_DIRECTORY;
}
- rv = InitCache(cache, maxCacheEntries, ssl2_timeout, ssl3_timeout,
+ rv = InitCache(cache, maxCacheEntries, maxCertCacheEntries,
+ maxSrvNameCacheEntries, ssl2_timeout, ssl3_timeout,
directory, shared);
if (rv) {
SET_ERROR_CODE
@@ -1200,6 +1353,22 @@
}
SECStatus
+SSL_ConfigServerSessionIDCacheInstance( cacheDesc *cache,
+ int maxCacheEntries,
+ PRUint32 ssl2_timeout,
+ PRUint32 ssl3_timeout,
+ const char * directory, PRBool shared)
+{
+ return ssl_ConfigServerSessionIDCacheInstanceWithOpt(cache,
+ ssl2_timeout,
+ ssl3_timeout,
+ directory,
+ shared,
+ maxCacheEntries,
+ -1, -1);
+}
+
+SECStatus
SSL_ConfigServerSessionIDCache( int maxCacheEntries,
PRUint32 ssl2_timeout,
PRUint32 ssl3_timeout,
@@ -1231,11 +1400,13 @@
/* Use this function, instead of SSL_ConfigServerSessionIDCache,
* if the cache will be shared by multiple processes.
*/
-SECStatus
-SSL_ConfigMPServerSIDCache( int maxCacheEntries,
- PRUint32 ssl2_timeout,
- PRUint32 ssl3_timeout,
- const char * directory)
+static SECStatus
+ssl_ConfigMPServerSIDCacheWithOpt( PRUint32 ssl2_timeout,
+ PRUint32 ssl3_timeout,
+ const char * directory,
+ int maxCacheEntries,
+ int maxCertCacheEntries,
+ int maxSrvNameCacheEntries)
{
char * envValue;
char * inhValue;
@@ -1248,8 +1419,9 @@
char fmString[PR_FILEMAP_STRING_BUFSIZE];
isMultiProcess = PR_TRUE;
- result = SSL_ConfigServerSessionIDCacheInstance(cache, maxCacheEntries,
- ssl2_timeout, ssl3_timeout, directory, PR_TRUE);
+ result = ssl_ConfigServerSessionIDCacheInstanceWithOpt(cache,
+ ssl2_timeout, ssl3_timeout, directory, PR_TRUE,
+ maxCacheEntries, maxCacheEntries, maxSrvNameCacheEntries);
if (result != SECSuccess)
return result;
@@ -1289,6 +1461,44 @@
return result;
}
+/* Use this function, instead of SSL_ConfigServerSessionIDCache,
+ * if the cache will be shared by multiple processes.
+ */
+SECStatus
+SSL_ConfigMPServerSIDCache( int maxCacheEntries,
+ PRUint32 ssl2_timeout,
+ PRUint32 ssl3_timeout,
+ const char * directory)
+{
+ return ssl_ConfigMPServerSIDCacheWithOpt(ssl2_timeout,
+ ssl3_timeout,
+ directory,
+ maxCacheEntries,
+ -1, -1);
+}
+
+SECStatus
+SSL_ConfigServerSessionIDCacheWithOpt(
+ PRUint32 ssl2_timeout,
+ PRUint32 ssl3_timeout,
+ const char * directory,
+ int maxCacheEntries,
+ int maxCertCacheEntries,
+ int maxSrvNameCacheEntries,
+ PRBool enableMPCache)
+{
+ if (!enableMPCache) {
+ ssl_InitSessionCacheLocks(PR_FALSE);
+ return ssl_ConfigServerSessionIDCacheInstanceWithOpt(&globalCache,
+ ssl2_timeout, ssl3_timeout, directory, PR_FALSE,
+ maxCacheEntries, maxCertCacheEntries, maxSrvNameCacheEntries);
+ } else {
+ return ssl_ConfigMPServerSIDCacheWithOpt(ssl2_timeout, ssl3_timeout,
+ directory, maxCacheEntries, maxCertCacheEntries,
+ maxSrvNameCacheEntries);
+ }
+}
+
SECStatus
SSL_InheritMPServerSIDCacheInstance(cacheDesc *cache, const char * envString)
{
@@ -1391,6 +1601,7 @@
*(ptrdiff_t *)(&cache->sidCacheLocks) += ptr;
*(ptrdiff_t *)(&cache->keyCacheLock ) += ptr;
*(ptrdiff_t *)(&cache->certCacheLock) += ptr;
+ *(ptrdiff_t *)(&cache->srvNameCacheLock) += ptr;
*(ptrdiff_t *)(&cache->sidCacheSets ) += ptr;
*(ptrdiff_t *)(&cache->sidCacheData ) += ptr;
*(ptrdiff_t *)(&cache->certCacheData) += ptr;
@@ -1399,6 +1610,7 @@
*(ptrdiff_t *)(&cache->ticketEncKey ) += ptr;
*(ptrdiff_t *)(&cache->ticketMacKey ) += ptr;
*(ptrdiff_t *)(&cache->ticketKeysValid) += ptr;
+ *(ptrdiff_t *)(&cache->srvNameCacheData) += ptr;
cache->cacheMemMap = my.cacheMemMap;
cache->cacheMem = my.cacheMem;
@@ -1420,7 +1632,7 @@
/* note from jpierre : this should be free'd in child processes when
** a function is added to delete the SSL session cache in the future.
*/
- locks_to_initialize = cache->numSIDCacheLocks + 2;
+ locks_to_initialize = cache->numSIDCacheLocks + 3;
newLocks = PORT_NewArray(sidCacheLock, locks_to_initialize);
if (!newLocks)
goto loser;
@@ -1443,6 +1655,7 @@
/* also fix the key and cert cache which use the last 2 lock entries */
cache->keyCacheLock = cache->sidCacheLocks + cache->numSIDCacheLocks;
cache->certCacheLock = cache->keyCacheLock + 1;
+ cache->srvNameCacheLock = cache->certCacheLock + 1;
#endif
PORT_Free(myEnvString);
diff --git a/net/third_party/nss/ssl/sslsock.c b/net/third_party/nss/ssl/sslsock.c
index 2275800..c4611a0 100644
--- a/net/third_party/nss/ssl/sslsock.c
+++ b/net/third_party/nss/ssl/sslsock.c
@@ -40,7 +40,7 @@
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
-/* $Id: sslsock.c,v 1.60 2009/11/25 05:24:25 wtc%google.com Exp $ */
+/* $Id: sslsock.c,v 1.64 2010/01/28 06:19:13 nelson%bolyard.com Exp $ */
#include "seccomon.h"
#include "cert.h"
#include "keyhi.h"
@@ -182,8 +182,9 @@
PR_FALSE, /* noLocks */
PR_FALSE, /* enableSessionTickets */
PR_FALSE, /* enableDeflate */
- 0, /* enableRenegotiation (default: never) */
+ 2, /* enableRenegotiation (default: requires extension) */
PR_FALSE, /* requireSafeNegotiation */
+ PR_FALSE, /* enableFalseStart */
};
sslSessionIDLookupFunc ssl_sid_lookup;
@@ -199,6 +200,7 @@
char ssl_debug;
char ssl_trace;
FILE * ssl_trace_iob;
+FILE * ssl_keylog_iob;
char lockStatus[] = "Locks are ENABLED. ";
#define LOCKSTATUS_OFFSET 10 /* offset of ENABLED */
@@ -305,7 +307,7 @@
int i;
sslServerCerts * oc = os->serverCerts;
sslServerCerts * sc = ss->serverCerts;
-
+
for (i=kt_null; i < kt_kea_size; i++, oc++, sc++) {
if (oc->serverCert && oc->serverCertChain) {
sc->serverCert = CERT_DupCertificate(oc->serverCert);
@@ -334,6 +336,8 @@
ss->authCertificateArg = os->authCertificateArg;
ss->getClientAuthData = os->getClientAuthData;
ss->getClientAuthDataArg = os->getClientAuthDataArg;
+ ss->sniSocketConfig = os->sniSocketConfig;
+ ss->sniSocketConfigArg = os->sniSocketConfigArg;
ss->handleBadCert = os->handleBadCert;
ss->badCertArg = os->badCertArg;
ss->handshakeCallback = os->handshakeCallback;
@@ -439,6 +443,11 @@
PORT_Free(ss->opt.nextProtoNego.data);
ss->opt.nextProtoNego.data = NULL;
}
+ PORT_Assert(!ss->xtnData.sniNameArr);
+ if (ss->xtnData.sniNameArr) {
+ PORT_Free(ss->xtnData.sniNameArr);
+ ss->xtnData.sniNameArr = NULL;
+ }
}
/*
@@ -725,6 +734,10 @@
ss->opt.requireSafeNegotiation = on;
break;
+ case SSL_ENABLE_FALSE_START:
+ ss->opt.enableFalseStart = on;
+ break;
+
default:
PORT_SetError(SEC_ERROR_INVALID_ARGS);
rv = SECFailure;
@@ -788,6 +801,7 @@
on = ss->opt.enableRenegotiation; break;
case SSL_REQUIRE_SAFE_NEGOTIATION:
on = ss->opt.requireSafeNegotiation; break;
+ case SSL_ENABLE_FALSE_START: on = ss->opt.enableFalseStart; break;
default:
PORT_SetError(SEC_ERROR_INVALID_ARGS);
@@ -838,6 +852,7 @@
case SSL_REQUIRE_SAFE_NEGOTIATION:
on = ssl_defaults.requireSafeNegotiation;
break;
+ case SSL_ENABLE_FALSE_START: on = ssl_defaults.enableFalseStart; break;
default:
PORT_SetError(SEC_ERROR_INVALID_ARGS);
@@ -981,6 +996,10 @@
ssl_defaults.requireSafeNegotiation = on;
break;
+ case SSL_ENABLE_FALSE_START:
+ ssl_defaults.enableFalseStart = on;
+ break;
+
default:
PORT_SetError(SEC_ERROR_INVALID_ARGS);
return SECFailure;
@@ -1321,6 +1340,119 @@
return SECSuccess;
}
+PRFileDesc *
+SSL_ReconfigFD(PRFileDesc *model, PRFileDesc *fd)
+{
+ PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
+ PR_NOT_REACHED("not implemented");
+ return NULL;
+
+#if 0
+ sslSocket * sm = NULL, *ss = NULL;
+ int i;
+ sslServerCerts * mc = sm->serverCerts;
+ sslServerCerts * sc = ss->serverCerts;
+
+ if (model == NULL) {
+ PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
+ return NULL;
+ }
+ sm = ssl_FindSocket(model);
+ if (sm == NULL) {
+ SSL_DBG(("%d: SSL[%d]: bad model socket in ssl_ReconfigFD",
+ SSL_GETPID(), model));
+ return NULL;
+ }
+ ss = ssl_FindSocket(fd);
+ PORT_Assert(ss);
+ if (ss == NULL) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return NULL;
+ }
+
+ ss->opt = sm->opt;
+ PORT_Memcpy(ss->cipherSuites, sm->cipherSuites, sizeof sm->cipherSuites);
+
+ if (!ss->opt.useSecurity) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return NULL;
+ }
+ /* This int should be SSLKEAType, but CC on Irix complains,
+ * during the for loop.
+ */
+ for (i=kt_null; i < kt_kea_size; i++, mc++, sc++) {
+ if (mc->serverCert && mc->serverCertChain) {
+ if (sc->serverCert) {
+ CERT_DestroyCertificate(sc->serverCert);
+ }
+ sc->serverCert = CERT_DupCertificate(mc->serverCert);
+ if (sc->serverCertChain) {
+ CERT_DestroyCertificateList(sc->serverCertChain);
+ }
+ sc->serverCertChain = CERT_DupCertList(mc->serverCertChain);
+ if (!sc->serverCertChain)
+ goto loser;
+ }
+ if (mc->serverKeyPair) {
+ if (sc->serverKeyPair) {
+ ssl3_FreeKeyPair(sc->serverKeyPair);
+ }
+ sc->serverKeyPair = ssl3_GetKeyPairRef(mc->serverKeyPair);
+ sc->serverKeyBits = mc->serverKeyBits;
+ }
+ }
+ if (sm->stepDownKeyPair) {
+ if (ss->stepDownKeyPair) {
+ ssl3_FreeKeyPair(ss->stepDownKeyPair);
+ }
+ ss->stepDownKeyPair = ssl3_GetKeyPairRef(sm->stepDownKeyPair);
+ }
+ if (sm->ephemeralECDHKeyPair) {
+ if (ss->ephemeralECDHKeyPair) {
+ ssl3_FreeKeyPair(ss->ephemeralECDHKeyPair);
+ }
+ ss->ephemeralECDHKeyPair =
+ ssl3_GetKeyPairRef(sm->ephemeralECDHKeyPair);
+ }
+ /* copy trust anchor names */
+ if (sm->ssl3.ca_list) {
+ if (ss->ssl3.ca_list) {
+ CERT_FreeDistNames(ss->ssl3.ca_list);
+ }
+ ss->ssl3.ca_list = CERT_DupDistNames(sm->ssl3.ca_list);
+ if (!ss->ssl3.ca_list) {
+ goto loser;
+ }
+ }
+
+ if (sm->authCertificate)
+ ss->authCertificate = sm->authCertificate;
+ if (sm->authCertificateArg)
+ ss->authCertificateArg = sm->authCertificateArg;
+ if (sm->getClientAuthData)
+ ss->getClientAuthData = sm->getClientAuthData;
+ if (sm->getClientAuthDataArg)
+ ss->getClientAuthDataArg = sm->getClientAuthDataArg;
+ if (sm->sniSocketConfig)
+ ss->sniSocketConfig = sm->sniSocketConfig;
+ if (sm->sniSocketConfigArg)
+ ss->sniSocketConfigArg = sm->sniSocketConfigArg;
+ if (sm->handleBadCert)
+ ss->handleBadCert = sm->handleBadCert;
+ if (sm->badCertArg)
+ ss->badCertArg = sm->badCertArg;
+ if (sm->handshakeCallback)
+ ss->handshakeCallback = sm->handshakeCallback;
+ if (sm->handshakeCallbackData)
+ ss->handshakeCallbackData = sm->handshakeCallbackData;
+ if (sm->pkcs11PinArg)
+ ss->pkcs11PinArg = sm->pkcs11PinArg;
+ return fd;
+loser:
+ return NULL;
+#endif
+}
+
/************************************************************************/
/* The following functions are the TOP LEVEL SSL functions.
** They all get called through the NSPRIOMethods table below.
@@ -2223,6 +2355,15 @@
ssl_trace = atoi(ev);
SSL_TRACE(("SSL: tracing set to %d", ssl_trace));
}
+ ev = getenv("SSLKEYLOGFILE");
+ if (ev && ev[0]) {
+ ssl_keylog_iob = fopen(ev, "a");
+ if (ftell(ssl_keylog_iob) == 0) {
+ fputs("# pre-master secret log file, generated by NSS\n",
+ ssl_keylog_iob);
+ }
+ SSL_TRACE(("SSL: logging pre-master secrets to %s", ev));
+ }
#endif /* TRACE */
ev = getenv("SSLDEBUG");
if (ev && ev[0]) {
@@ -2247,19 +2388,12 @@
if (ev) {
if (ev[0] == '1' || LOWER(ev[0]) == 'u')
ssl_defaults.enableRenegotiation = SSL_RENEGOTIATE_UNRESTRICTED;
-#ifdef LATER
- /* When SSL_RENEGOTIATE_REQUIRES_XTN is implemented, it will be
- * the default. Until then, NEVER will be the default.
- */
else if (ev[0] == '0' || LOWER(ev[0]) == 'n')
ssl_defaults.enableRenegotiation = SSL_RENEGOTIATE_NEVER;
+ else if (ev[0] == '3' || LOWER(ev[0]) == 'c')
+ ssl_defaults.enableRenegotiation = SSL_RENEGOTIATE_CLIENT_ONLY;
else
ssl_defaults.enableRenegotiation = SSL_RENEGOTIATE_REQUIRES_XTN;
-#else
- else
- ssl_defaults.enableRenegotiation = SSL_RENEGOTIATE_NEVER;
-#endif
-
SSL_TRACE(("SSL: enableRenegotiation set to %d",
ssl_defaults.enableRenegotiation));
}
@@ -2309,6 +2443,8 @@
/* Provide default implementation of hooks */
ss->authCertificate = SSL_AuthCertificate;
ss->authCertificateArg = (void *)ss->dbHandle;
+ ss->sniSocketConfig = NULL;
+ ss->sniSocketConfigArg = NULL;
ss->getClientAuthData = NULL;
ss->handleBadCert = NULL;
ss->badCertArg = NULL;
diff --git a/net/third_party/nss/ssl/sslt.h b/net/third_party/nss/ssl/sslt.h
index 80f1dc2..f6e0b62 100644
--- a/net/third_party/nss/ssl/sslt.h
+++ b/net/third_party/nss/ssl/sslt.h
@@ -37,7 +37,7 @@
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
-/* $Id: sslt.h,v 1.13 2009/11/07 18:23:06 wtc%google.com Exp $ */
+/* $Id: sslt.h,v 1.16 2010/02/04 03:21:11 wtc%google.com Exp $ */
#ifndef __sslt_h_
#define __sslt_h_
@@ -189,4 +189,24 @@
} SSLCipherSuiteInfo;
+typedef enum {
+ SSL_sni_host_name = 0,
+ SSL_sni_type_total
+} SSLSniNameType;
+
+/* Supported extensions. */
+/* Update SSL_MAX_EXTENSIONS whenever a new extension type is added. */
+typedef enum {
+ ssl_server_name_xtn = 0,
+#ifdef NSS_ENABLE_ECC
+ ssl_elliptic_curves_xtn = 10,
+ ssl_ec_point_formats_xtn = 11,
+#endif
+ ssl_session_ticket_xtn = 35,
+ ssl_next_proto_neg_xtn = 13172,
+ ssl_renegotiation_info_xtn = 0xff01 /* experimental number */
+} SSLExtensionType;
+
+#define SSL_MAX_EXTENSIONS 6
+
#endif /* __sslt_h_ */
diff --git a/net/tld_cleanup.target.mk b/net/tld_cleanup.target.mk
new file mode 100644
index 0000000..982ef8a
--- /dev/null
+++ b/net/tld_cleanup.target.mk
@@ -0,0 +1,182 @@
+# This file is generated by gyp; do not edit.
+
+TOOLSET := target
+TARGET := tld_cleanup
+DEFS_Debug := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-D__STDC_FORMAT_MACROS' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=1' \
+ '-D_DEBUG'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Debug := -Werror \
+ -pthread \
+ -fno-exceptions \
+ -Wall \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/atk-1.0 \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -O0 \
+ -g
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Debug :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Debug := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Debug := -I.
+
+DEFS_Release := '-DNO_HEAPCHECKER' \
+ '-DCHROMIUM_BUILD' \
+ '-DENABLE_REMOTING=1' \
+ '-DENABLE_GPU=1' \
+ '-D__STDC_FORMAT_MACROS' \
+ '-DNDEBUG' \
+ '-DNVALGRIND' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=0'
+
+# Flags passed to both C and C++ files.
+CFLAGS_Release := -Werror \
+ -pthread \
+ -fno-exceptions \
+ -Wall \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -D_FILE_OFFSET_BITS=64 \
+ -fvisibility=hidden \
+ -fno-strict-aliasing \
+ -pthread \
+ -D_REENTRANT \
+ -I/usr/include/gtk-2.0 \
+ -I/usr/lib/gtk-2.0/include \
+ -I/usr/include/atk-1.0 \
+ -I/usr/include/cairo \
+ -I/usr/include/pango-1.0 \
+ -I/usr/include/gio-unix-2.0/ \
+ -I/usr/include/glib-2.0 \
+ -I/usr/lib/glib-2.0/include \
+ -I/usr/include/pixman-1 \
+ -I/usr/include/freetype2 \
+ -I/usr/include/directfb \
+ -I/usr/include/libpng12 \
+ -O2 \
+ -fno-ident \
+ -fdata-sections \
+ -ffunction-sections
+
+# Flags passed to only C (and not C++) files.
+CFLAGS_C_Release :=
+
+# Flags passed to only C++ (and not C) files.
+CFLAGS_CC_Release := -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden
+
+INCS_Release := -I.
+
+OBJS := $(obj).target/$(TARGET)/net/tools/tld_cleanup/tld_cleanup.o
+
+# Add to the list of files we specially track dependencies for.
+all_deps += $(OBJS)
+
+# Make sure our dependencies are built before any of us.
+$(OBJS): | $(obj).target/base/libbase.a $(obj).target/base/libbase_i18n.a $(obj).target/build/temp_gyp/libgoogleurl.a $(obj).target/third_party/modp_b64/libmodp_b64.a $(obj).target/base/third_party/dynamic_annotations/libdynamic_annotations.a $(obj).target/base/libsymbolize.a $(obj).target/net/third_party/nss/libssl.a $(obj).target/third_party/zlib/libzlib.a $(obj).target/base/libxdg_mime.a $(obj).target/base/allocator/liballocator.a $(obj).target/third_party/libevent/libevent.a $(obj).target/third_party/icu/libicui18n.a $(obj).target/third_party/icu/libicuuc.a $(obj).target/third_party/icu/libicudata.a
+
+# CFLAGS et al overrides must be target-local.
+# See "Target-specific Variable Values" in the GNU Make manual.
+$(OBJS): TOOLSET := $(TOOLSET)
+$(OBJS): GYP_CFLAGS := $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_C_$(BUILDTYPE)) $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE))
+$(OBJS): GYP_CXXFLAGS := $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_CC_$(BUILDTYPE)) $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE))
+
+# Suffix rules, putting all outputs into $(obj).
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(srcdir)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+# Try building from generated source, too.
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj).$(TOOLSET)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj)/%.cc FORCE_DO_CMD
+ @$(call do_cmd,cxx,1)
+
+# End of this set of suffix rules
+### Rules for final target.
+LDFLAGS_Debug := -pthread \
+ -Wl,-z,noexecstack \
+ -Wl,-uIsHeapProfilerRunning,-uProfilerStart \
+ -Wl,-u_Z21InitialMallocHook_NewPKvj,-u_Z22InitialMallocHook_MMapPKvS0_jiiix,-u_Z22InitialMallocHook_SbrkPKvi \
+ -Wl,-u_Z21InitialMallocHook_NewPKvm,-u_Z22InitialMallocHook_MMapPKvS0_miiil,-u_Z22InitialMallocHook_SbrkPKvl \
+ -rdynamic
+
+LDFLAGS_Release := -pthread \
+ -Wl,-z,noexecstack \
+ -Wl,-uIsHeapProfilerRunning,-uProfilerStart \
+ -Wl,-u_Z21InitialMallocHook_NewPKvj,-u_Z22InitialMallocHook_MMapPKvS0_jiiix,-u_Z22InitialMallocHook_SbrkPKvi \
+ -Wl,-u_Z21InitialMallocHook_NewPKvm,-u_Z22InitialMallocHook_MMapPKvS0_miiil,-u_Z22InitialMallocHook_SbrkPKvl \
+ -Wl,--gc-sections
+
+LIBS := -lrt \
+ -ldl \
+ -lgtk-x11-2.0 \
+ -lgdk-x11-2.0 \
+ -latk-1.0 \
+ -lgio-2.0 \
+ -lpangoft2-1.0 \
+ -lgdk_pixbuf-2.0 \
+ -lm \
+ -lpangocairo-1.0 \
+ -lcairo \
+ -lpango-1.0 \
+ -lfreetype \
+ -lfontconfig \
+ -lgobject-2.0 \
+ -lgmodule-2.0 \
+ -lgthread-2.0 \
+ -lglib-2.0 \
+ -lnss3 \
+ -lnssutil3 \
+ -lsmime3 \
+ -lplds4 \
+ -lplc4 \
+ -lnspr4 \
+ -lpthread \
+ -lz
+
+$(builddir)/tld_cleanup: GYP_LDFLAGS := $(LDFLAGS_$(BUILDTYPE))
+$(builddir)/tld_cleanup: LIBS := $(LIBS)
+$(builddir)/tld_cleanup: TOOLSET := $(TOOLSET)
+$(builddir)/tld_cleanup: $(OBJS) $(obj).target/base/libbase.a $(obj).target/base/libbase_i18n.a $(obj).target/build/temp_gyp/libgoogleurl.a $(obj).target/third_party/modp_b64/libmodp_b64.a $(obj).target/base/third_party/dynamic_annotations/libdynamic_annotations.a $(obj).target/base/libsymbolize.a $(obj).target/net/third_party/nss/libssl.a $(obj).target/third_party/zlib/libzlib.a $(obj).target/base/libxdg_mime.a $(obj).target/base/allocator/liballocator.a $(obj).target/third_party/libevent/libevent.a $(obj).target/third_party/icu/libicui18n.a $(obj).target/third_party/icu/libicuuc.a $(obj).target/third_party/icu/libicudata.a FORCE_DO_CMD
+ $(call do_cmd,link)
+
+all_deps += $(builddir)/tld_cleanup
+# Add target alias
+.PHONY: tld_cleanup
+tld_cleanup: $(builddir)/tld_cleanup
+
+# Add executable to "all" target.
+.PHONY: all
+all: $(builddir)/tld_cleanup
+
diff --git a/net/tools/crash_cache/crash_cache.cc b/net/tools/crash_cache/crash_cache.cc
index 4686d13..1d11013 100644
--- a/net/tools/crash_cache/crash_cache.cc
+++ b/net/tools/crash_cache/crash_cache.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Copyright (c) 2006-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.
@@ -17,7 +17,9 @@
#include "base/path_service.h"
#include "base/process_util.h"
#include "base/string_util.h"
-
+#include "base/thread.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
#include "net/disk_cache/backend_impl.h"
#include "net/disk_cache/disk_cache.h"
#include "net/disk_cache/disk_cache_test_util.h"
@@ -116,11 +118,23 @@
return file_util::CreateDirectory(*full_path);
}
+// Makes sure that any pending task is processed.
+void FlushQueue(disk_cache::Backend* cache) {
+ TestCompletionCallback cb;
+ int rv =
+ reinterpret_cast<disk_cache::BackendImpl*>(cache)->FlushQueueForTest(&cb);
+ cb.GetResult(rv); // Ignore the result;
+}
+
// Generates the files for an empty and one item cache.
-int SimpleInsert(const FilePath& path, RankCrashes action) {
- disk_cache::Backend* cache = disk_cache::CreateCacheBackend(path, false, 0,
- net::DISK_CACHE);
- if (!cache || cache->GetEntryCount())
+int SimpleInsert(const FilePath& path, RankCrashes action,
+ base::Thread* cache_thread) {
+ TestCompletionCallback cb;
+ disk_cache::Backend* cache;
+ int rv = disk_cache::CreateCacheBackend(net::DISK_CACHE, path, 0, false,
+ cache_thread->message_loop_proxy(),
+ &cache, &cb);
+ if (cb.GetResult(rv) != net::OK || cache->GetEntryCount())
return GENERIC;
const char* test_name = "some other key";
@@ -131,92 +145,122 @@
}
disk_cache::Entry* entry;
- if (!cache->CreateEntry(test_name, &entry))
+ rv = cache->CreateEntry(test_name, &entry, &cb);
+ if (cb.GetResult(rv) != net::OK)
return GENERIC;
entry->Close();
+ FlushQueue(cache);
DCHECK(action <= disk_cache::INSERT_ONE_3);
g_rankings_crash = action;
test_name = kCrashEntryName;
- if (!cache->CreateEntry(test_name, &entry))
+ rv = cache->CreateEntry(test_name, &entry, &cb);
+ if (cb.GetResult(rv) != net::OK)
return GENERIC;
return NOT_REACHED;
}
// Generates the files for a one item cache, and removing the head.
-int SimpleRemove(const FilePath& path, RankCrashes action) {
+int SimpleRemove(const FilePath& path, RankCrashes action,
+ base::Thread* cache_thread) {
DCHECK(action >= disk_cache::REMOVE_ONE_1);
DCHECK(action <= disk_cache::REMOVE_TAIL_3);
- disk_cache::Backend* cache = disk_cache::CreateCacheBackend(path, false, 0,
- net::DISK_CACHE);
- if (!cache || cache->GetEntryCount())
+ TestCompletionCallback cb;
+ disk_cache::Backend* cache;
+ // Use a simple LRU for eviction.
+ int rv = disk_cache::CreateCacheBackend(net::MEDIA_CACHE, path, 0, false,
+ cache_thread->message_loop_proxy(),
+ &cache, &cb);
+ if (cb.GetResult(rv) != net::OK || cache->GetEntryCount())
return GENERIC;
disk_cache::Entry* entry;
- if (!cache->CreateEntry(kCrashEntryName, &entry))
+ rv = cache->CreateEntry(kCrashEntryName, &entry, &cb);
+ if (cb.GetResult(rv) != net::OK)
return GENERIC;
entry->Close();
+ FlushQueue(cache);
if (action >= disk_cache::REMOVE_TAIL_1) {
- if (!cache->CreateEntry("some other key", &entry))
+ rv = cache->CreateEntry("some other key", &entry, &cb);
+ if (cb.GetResult(rv) != net::OK)
return GENERIC;
entry->Close();
+ FlushQueue(cache);
}
- if (!cache->OpenEntry(kCrashEntryName, &entry))
+ rv = cache->OpenEntry(kCrashEntryName, &entry, &cb);
+ if (cb.GetResult(rv) != net::OK)
return GENERIC;
g_rankings_crash = action;
entry->Doom();
entry->Close();
+ FlushQueue(cache);
return NOT_REACHED;
}
-int HeadRemove(const FilePath& path, RankCrashes action) {
+int HeadRemove(const FilePath& path, RankCrashes action,
+ base::Thread* cache_thread) {
DCHECK(action >= disk_cache::REMOVE_HEAD_1);
DCHECK(action <= disk_cache::REMOVE_HEAD_4);
- disk_cache::Backend* cache = disk_cache::CreateCacheBackend(path, false, 0,
- net::DISK_CACHE);
- if (!cache || cache->GetEntryCount())
+ TestCompletionCallback cb;
+ disk_cache::Backend* cache;
+ // Use a simple LRU for eviction.
+ int rv = disk_cache::CreateCacheBackend(net::MEDIA_CACHE, path, 0, false,
+ cache_thread->message_loop_proxy(),
+ &cache, &cb);
+ if (cb.GetResult(rv) != net::OK || cache->GetEntryCount())
return GENERIC;
disk_cache::Entry* entry;
- if (!cache->CreateEntry("some other key", &entry))
+ rv = cache->CreateEntry("some other key", &entry, &cb);
+ if (cb.GetResult(rv) != net::OK)
return GENERIC;
entry->Close();
- if (!cache->CreateEntry(kCrashEntryName, &entry))
+ FlushQueue(cache);
+ rv = cache->CreateEntry(kCrashEntryName, &entry, &cb);
+ if (cb.GetResult(rv) != net::OK)
return GENERIC;
entry->Close();
+ FlushQueue(cache);
- if (!cache->OpenEntry(kCrashEntryName, &entry))
+ rv = cache->OpenEntry(kCrashEntryName, &entry, &cb);
+ if (cb.GetResult(rv) != net::OK)
return GENERIC;
g_rankings_crash = action;
entry->Doom();
entry->Close();
+ FlushQueue(cache);
return NOT_REACHED;
}
// Generates the files for insertion and removals on heavy loaded caches.
-int LoadOperations(const FilePath& path, RankCrashes action) {
+int LoadOperations(const FilePath& path, RankCrashes action,
+ base::Thread* cache_thread) {
DCHECK(action >= disk_cache::INSERT_LOAD_1);
- // Work with a tiny index table (16 entries)
- disk_cache::BackendImpl* cache =
- new disk_cache::BackendImpl(path, 0xf);
- if (!cache || !cache->SetMaxSize(0x100000) || !cache->Init() ||
- cache->GetEntryCount())
+ // Work with a tiny index table (16 entries).
+ disk_cache::BackendImpl* cache = new disk_cache::BackendImpl(
+ path, 0xf, cache_thread->message_loop_proxy());
+ if (!cache || !cache->SetMaxSize(0x100000))
+ return GENERIC;
+
+ TestCompletionCallback cb;
+ int rv = cache->Init(&cb);
+ if (cb.GetResult(rv) != net::OK || cache->GetEntryCount())
return GENERIC;
int seed = static_cast<int>(Time::Now().ToInternalValue());
@@ -225,37 +269,44 @@
disk_cache::Entry* entry;
for (int i = 0; i < 100; i++) {
std::string key = GenerateKey(true);
- if (!cache->CreateEntry(key, &entry))
+ rv = cache->CreateEntry(key, &entry, &cb);
+ if (cb.GetResult(rv) != net::OK)
return GENERIC;
entry->Close();
+ FlushQueue(cache);
if (50 == i && action >= disk_cache::REMOVE_LOAD_1) {
- if (!cache->CreateEntry(kCrashEntryName, &entry))
+ rv = cache->CreateEntry(kCrashEntryName, &entry, &cb);
+ if (cb.GetResult(rv) != net::OK)
return GENERIC;
entry->Close();
+ FlushQueue(cache);
}
}
if (action <= disk_cache::INSERT_LOAD_2) {
g_rankings_crash = action;
- if (!cache->CreateEntry(kCrashEntryName, &entry))
+ rv = cache->CreateEntry(kCrashEntryName, &entry, &cb);
+ if (cb.GetResult(rv) != net::OK)
return GENERIC;
}
- if (!cache->OpenEntry(kCrashEntryName, &entry))
+ rv = cache->OpenEntry(kCrashEntryName, &entry, &cb);
+ if (cb.GetResult(rv) != net::OK)
return GENERIC;
g_rankings_crash = action;
entry->Doom();
entry->Close();
+ FlushQueue(cache);
return NOT_REACHED;
}
// Main function on the child process.
int SlaveCode(const FilePath& path, RankCrashes action) {
- MessageLoop message_loop;
+ MessageLoopForIO message_loop;
FilePath full_path;
if (!CreateTargetFolder(path, action, &full_path)) {
@@ -263,23 +314,28 @@
return CRASH_OVERWRITE;
}
+ base::Thread cache_thread("CacheThread");
+ if (!cache_thread.StartWithOptions(
+ base::Thread::Options(MessageLoop::TYPE_IO, 0)))
+ return GENERIC;
+
if (action <= disk_cache::INSERT_ONE_3)
- return SimpleInsert(full_path, action);
+ return SimpleInsert(full_path, action, &cache_thread);
if (action <= disk_cache::INSERT_LOAD_2)
- return LoadOperations(full_path, action);
+ return LoadOperations(full_path, action, &cache_thread);
if (action <= disk_cache::REMOVE_ONE_4)
- return SimpleRemove(full_path, action);
+ return SimpleRemove(full_path, action, &cache_thread);
if (action <= disk_cache::REMOVE_HEAD_4)
- return HeadRemove(full_path, action);
+ return HeadRemove(full_path, action, &cache_thread);
if (action <= disk_cache::REMOVE_TAIL_3)
- return SimpleRemove(full_path, action);
+ return SimpleRemove(full_path, action, &cache_thread);
if (action <= disk_cache::REMOVE_LOAD_3)
- return LoadOperations(full_path, action);
+ return LoadOperations(full_path, action, &cache_thread);
return NOT_REACHED;
}
diff --git a/net/tools/dump_cache/cache_dumper.cc b/net/tools/dump_cache/cache_dumper.cc
index 1120817..f573b2a 100644
--- a/net/tools/dump_cache/cache_dumper.cc
+++ b/net/tools/dump_cache/cache_dumper.cc
@@ -1,25 +1,27 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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 "net/tools/dump_cache/cache_dumper.h"
#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
#include "net/disk_cache/entry_impl.h"
#include "net/http/http_cache.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_response_info.h"
#include "net/tools/dump_cache/url_to_filename_encoder.h"
-bool CacheDumper::CreateEntry(const std::string& key,
- disk_cache::Entry** entry) {
- return cache_->CreateEntry(key, entry);
+int CacheDumper::CreateEntry(const std::string& key,
+ disk_cache::Entry** entry,
+ net::CompletionCallback* callback) {
+ return cache_->CreateEntry(key, entry, callback);
}
-bool CacheDumper::WriteEntry(disk_cache::Entry* entry, int index, int offset,
- net::IOBuffer* buf, int buf_len) {
- int written = entry->WriteData(index, offset, buf, buf_len, NULL, false);
- return written == buf_len;
+int CacheDumper::WriteEntry(disk_cache::Entry* entry, int index, int offset,
+ net::IOBuffer* buf, int buf_len,
+ net::CompletionCallback* callback) {
+ return entry->WriteData(index, offset, buf, buf_len, callback, false);
}
void CacheDumper::CloseEntry(disk_cache::Entry* entry, base::Time last_used,
@@ -48,7 +50,7 @@
pos = 4;
// Create the subdirectories individually
- while((pos = path.find(backslash, pos)) != std::wstring::npos) {
+ while ((pos = path.find(backslash, pos)) != std::wstring::npos) {
std::wstring subdir = path.substr(0, pos);
CreateDirectoryW(subdir.c_str(), NULL);
// we keep going even if directory creation failed.
@@ -61,10 +63,17 @@
#endif
}
-bool DiskDumper::CreateEntry(const std::string& key,
- disk_cache::Entry** entry) {
+int DiskDumper::CreateEntry(const std::string& key,
+ disk_cache::Entry** entry,
+ net::CompletionCallback* callback) {
FilePath path(path_);
- entry_path_ = net::UrlToFilenameEncoder::Encode(key, path);
+ // The URL may not start with a valid protocol; search for it.
+ int urlpos = key.find("http");
+ std::string url = urlpos > 0 ? key.substr(urlpos) : key;
+ std::string base_path = WideToASCII(path_);
+ std::string new_path =
+ net::UrlToFilenameEncoder::Encode(url, base_path, false);
+ entry_path_ = FilePath(ASCIIToWide(new_path));
#ifdef WIN32_LARGE_FILENAME_SUPPORT
// In order for long filenames to work, we'll need to prepend
@@ -86,10 +95,12 @@
#ifdef WIN32_LARGE_FILENAME_SUPPORT
entry_ = CreateFileW(file.c_str(), GENERIC_WRITE|GENERIC_READ, 0, 0,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
- return entry_ != INVALID_HANDLE_VALUE;
+ if (entry_ == INVALID_HANDLE_VALUE)
+ wprintf(L"CreateFileW (%s) failed: %d\n", file.c_str(), GetLastError());
+ return (entry_ != INVALID_HANDLE_VALUE) ? net::OK : net::ERR_FAILED;
#else
entry_ = file_util::OpenFile(entry_path_, "w+");
- return entry_ != NULL;
+ return (entry_ != NULL) ? net::OK : net::ERR_FAILED;
#endif
}
@@ -133,24 +144,25 @@
output->append("\r\n");
}
-bool DiskDumper::WriteEntry(disk_cache::Entry* entry, int index, int offset,
- net::IOBuffer* buf, int buf_len) {
+int DiskDumper::WriteEntry(disk_cache::Entry* entry, int index, int offset,
+ net::IOBuffer* buf, int buf_len,
+ net::CompletionCallback* callback) {
if (!entry_)
- return false;
+ return 0;
std::string headers;
const char *data;
- int len;
+ size_t len;
if (index == 0) { // Stream 0 is the headers.
net::HttpResponseInfo response_info;
bool truncated;
if (!net::HttpCache::ParseResponseInfo(buf->data(), buf_len,
&response_info, &truncated))
- return false;
+ return 0;
// Skip this entry if it was truncated (results in an empty file).
if (truncated)
- return true;
+ return buf_len;
// Remove the size headers.
response_info.headers->RemoveHeader("transfer-encoding");
@@ -180,11 +192,12 @@
}
#ifdef WIN32_LARGE_FILENAME_SUPPORT
DWORD bytes;
- DWORD rv = WriteFile(entry_, data, len, &bytes, 0);
- return rv == TRUE && bytes == len;
+ if (!WriteFile(entry_, data, len, &bytes, 0))
+ return 0;
+
+ return bytes;
#else
- int bytes = fwrite(data, 1, len, entry_);
- return bytes == len;
+ return fwrite(data, 1, len, entry_);
#endif
}
diff --git a/net/tools/dump_cache/cache_dumper.h b/net/tools/dump_cache/cache_dumper.h
index 916b1b4..d2e6034 100644
--- a/net/tools/dump_cache/cache_dumper.h
+++ b/net/tools/dump_cache/cache_dumper.h
@@ -1,9 +1,9 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
-#ifndef NET_TOOLS_DUMP_CACHE_DUMPER_H_
-#define NET_TOOLS_DUMP_CACHE_DUMPER_H_
+#ifndef NET_TOOLS_DUMP_CACHE_CACHE_DUMPER_H_
+#define NET_TOOLS_DUMP_CACHE_CACHE_DUMPER_H_
#include <string>
#include "base/file_path.h"
@@ -22,16 +22,20 @@
// An abstract class for writing cache dump data.
class CacheDumpWriter {
public:
+ virtual ~CacheDumpWriter() {}
+
// Creates an entry to be written.
// On success, populates the |entry|.
- // Returns true on success, false otherwise.
- virtual bool CreateEntry(const std::string& key,
- disk_cache::Entry** entry) = 0;
+ // Returns a net error code.
+ virtual int CreateEntry(const std::string& key,
+ disk_cache::Entry** entry,
+ net::CompletionCallback* callback) = 0;
// Write to the current entry.
- // Returns true on success, false otherwise.
- virtual bool WriteEntry(disk_cache::Entry* entry, int stream, int offset,
- net::IOBuffer* buf, int buf_len) = 0;
+ // Returns a net error code.
+ virtual int WriteEntry(disk_cache::Entry* entry, int stream, int offset,
+ net::IOBuffer* buf, int buf_len,
+ net::CompletionCallback* callback) = 0;
// Close the current entry.
virtual void CloseEntry(disk_cache::Entry* entry, base::Time last_used,
@@ -41,27 +45,31 @@
// Writes data to a cache.
class CacheDumper : public CacheDumpWriter {
public:
- CacheDumper(disk_cache::BackendImpl* cache) : cache_(cache) {};
+ explicit CacheDumper(disk_cache::Backend* cache) : cache_(cache) {}
- virtual bool CreateEntry(const std::string& key, disk_cache::Entry** entry);
- virtual bool WriteEntry(disk_cache::Entry* entry, int stream, int offset,
- net::IOBuffer* buf, int buf_len);
+ virtual int CreateEntry(const std::string& key, disk_cache::Entry** entry,
+ net::CompletionCallback* callback);
+ virtual int WriteEntry(disk_cache::Entry* entry, int stream, int offset,
+ net::IOBuffer* buf, int buf_len,
+ net::CompletionCallback* callback);
virtual void CloseEntry(disk_cache::Entry* entry, base::Time last_used,
base::Time last_modified);
private:
- disk_cache::BackendImpl* cache_;
+ disk_cache::Backend* cache_;
};
// Writes data to a disk.
class DiskDumper : public CacheDumpWriter {
public:
- DiskDumper(const std::wstring& path) : path_(path), entry_(NULL) {
+ explicit DiskDumper(const std::wstring& path) : path_(path), entry_(NULL) {
file_util::CreateDirectory(FilePath(path));
- };
- virtual bool CreateEntry(const std::string& key, disk_cache::Entry** entry);
- virtual bool WriteEntry(disk_cache::Entry* entry, int stream, int offset,
- net::IOBuffer* buf, int buf_len);
+ }
+ virtual int CreateEntry(const std::string& key, disk_cache::Entry** entry,
+ net::CompletionCallback* callback);
+ virtual int WriteEntry(disk_cache::Entry* entry, int stream, int offset,
+ net::IOBuffer* buf, int buf_len,
+ net::CompletionCallback* callback);
virtual void CloseEntry(disk_cache::Entry* entry, base::Time last_used,
base::Time last_modified);
@@ -79,4 +87,4 @@
#endif
};
-#endif // NET_TOOLS_DUMP_CACHE_DUMPER_H_
+#endif // NET_TOOLS_DUMP_CACHE_CACHE_DUMPER_H_
diff --git a/net/tools/dump_cache/upgrade.cc b/net/tools/dump_cache/upgrade.cc
index 566eb0b..60099b2 100644
--- a/net/tools/dump_cache/upgrade.cc
+++ b/net/tools/dump_cache/upgrade.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -7,8 +7,10 @@
#include "base/message_loop.h"
#include "base/scoped_ptr.h"
#include "base/string_util.h"
+#include "base/thread.h"
#include "googleurl/src/gurl.h"
#include "net/base/io_buffer.h"
+#include "net/base/test_completion_callback.h"
#include "net/disk_cache/backend_impl.h"
#include "net/disk_cache/entry_impl.h"
#include "net/http/http_cache.h"
@@ -102,14 +104,15 @@
RESULT_OK = 0,
RESULT_UNKNOWN_COMMAND,
RESULT_INVALID_PARAMETER,
- RESULT_NAME_OVERFLOW
+ RESULT_NAME_OVERFLOW,
+ RESULT_PENDING // This error code is NOT expected by the master process.
};
// -----------------------------------------------------------------------
class BaseSM : public MessageLoopForIO::IOHandler {
public:
- BaseSM(HANDLE channel);
+ explicit BaseSM(HANDLE channel);
virtual ~BaseSM();
protected:
@@ -128,11 +131,14 @@
scoped_array<char> out_buffer_;
IoBuffer* input_;
IoBuffer* output_;
+ base::Thread cache_thread_;
+
DISALLOW_COPY_AND_ASSIGN(BaseSM);
};
BaseSM::BaseSM(HANDLE channel)
- : entry_(NULL), channel_(channel), state_(0), pending_count_(0) {
+ : entry_(NULL), channel_(channel), state_(0), pending_count_(0),
+ cache_thread_("cache") {
in_buffer_.reset(new char[kChannelSize]);
out_buffer_.reset(new char[kChannelSize]);
input_ = reinterpret_cast<IoBuffer*>(in_buffer_.get());
@@ -143,6 +149,8 @@
in_context_.handler = this;
out_context_.handler = this;
MessageLoopForIO::current()->RegisterIOHandler(channel_, this);
+ CHECK(cache_thread_.StartWithOptions(
+ base::Thread::Options(MessageLoop::TYPE_IO, 0)));
}
BaseSM::~BaseSM() {
@@ -200,8 +208,12 @@
class MasterSM : public BaseSM {
public:
- MasterSM(const std::wstring& path, HANDLE channel, bool dump_to_disk)
- : BaseSM(channel), path_(path), dump_to_disk_(dump_to_disk) {
+ MasterSM(const std::wstring& path, HANDLE channel, bool dump_to_disk)
+ : BaseSM(channel), path_(path), dump_to_disk_(dump_to_disk),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ create_callback_(this, &MasterSM::DoCreateEntryComplete)),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ write_callback_(this, &MasterSM::DoReadDataComplete)) {
}
virtual ~MasterSM() {
delete writer_;
@@ -227,12 +239,14 @@
void SendGetPrevEntry();
void DoGetEntry();
void DoGetKey(int bytes_read);
+ void DoCreateEntryComplete(int result);
void DoGetUseTimes();
void SendGetDataSize();
void DoGetDataSize();
void CloseEntry();
void SendReadData();
void DoReadData(int bytes_read);
+ void DoReadDataComplete(int ret);
void SendQuit();
void DoEnd();
void Fail();
@@ -244,10 +258,13 @@
int bytes_remaining_;
int offset_;
int copied_entries_;
- scoped_ptr<disk_cache::BackendImpl> cache_;
+ int read_size_;
+ scoped_ptr<disk_cache::Backend> cache_;
CacheDumpWriter* writer_;
const std::wstring& path_;
bool dump_to_disk_;
+ net::CompletionCallbackImpl<MasterSM> create_callback_;
+ net::CompletionCallbackImpl<MasterSM> write_callback_;
};
void MasterSM::OnIOCompleted(MessageLoopForIO::IOContext* context,
@@ -302,11 +319,18 @@
if (dump_to_disk_) {
writer_ = new DiskDumper(path_);
} else {
- cache_.reset(new disk_cache::BackendImpl(FilePath::FromWStringHack(path_)));
- if (!cache_->Init()) {
+ disk_cache::Backend* cache;
+ TestCompletionCallback cb;
+ int rv = disk_cache::CreateCacheBackend(net::DISK_CACHE,
+ FilePath::FromWStringHack(path_), 0,
+ false,
+ cache_thread_.message_loop_proxy(),
+ &cache, &cb);
+ if (cb.GetResult(rv) != net::OK) {
printf("Unable to initialize new files\n");
return false;
}
+ cache_.reset(cache);
writer_ = new CacheDumper(cache_.get());
}
if (!writer_)
@@ -367,11 +391,20 @@
return Fail();
std::string key(input_->buffer);
- DCHECK(key.size() == input_->msg.buffer_bytes - 1);
+ DCHECK(key.size() == static_cast<size_t>(input_->msg.buffer_bytes - 1));
- if (!writer_->CreateEntry(key,
- reinterpret_cast<disk_cache::Entry**>(&entry_))) {
- printf("Skipping entry \"%s\" (name conflict!)\n", key.c_str());
+ int rv = writer_->CreateEntry(key,
+ reinterpret_cast<disk_cache::Entry**>(&entry_),
+ &create_callback_);
+
+ if (rv != net::ERR_IO_PENDING)
+ DoCreateEntryComplete(rv);
+}
+
+void MasterSM::DoCreateEntryComplete(int result) {
+ std::string key(input_->buffer);
+ if (result != net::OK) {
+ printf("Skipping entry \"%s\": %d\n", key.c_str(), GetLastError());
return SendGetPrevEntry();
}
@@ -474,7 +507,15 @@
scoped_refptr<net::WrappedIOBuffer> buf =
new net::WrappedIOBuffer(input_->buffer);
- if (!writer_->WriteEntry(entry_, stream_, offset_, buf, read_size))
+ int rv = writer_->WriteEntry(entry_, stream_, offset_, buf, read_size,
+ &write_callback_);
+ if (rv == net::ERR_IO_PENDING) {
+ // We'll continue in DoReadDataComplete.
+ read_size_ = read_size;
+ return;
+ }
+
+ if (rv <= 0)
return Fail();
offset_ += read_size;
@@ -483,6 +524,16 @@
SendReadData();
}
+void MasterSM::DoReadDataComplete(int ret) {
+ if (ret != read_size_)
+ return Fail();
+
+ offset_ += ret;
+ bytes_remaining_ -= ret;
+ // Read some more.
+ SendReadData();
+}
+
void MasterSM::SendQuit() {
DEBUGMSG("Master SendQuit\n");
state_ = MASTER_END;
@@ -508,15 +559,7 @@
class SlaveSM : public BaseSM {
public:
- SlaveSM(const std::wstring& path, HANDLE channel)
- : BaseSM(channel), iterator_(NULL) {
- cache_.reset(new disk_cache::BackendImpl(FilePath::FromWStringHack(path)));
- if (!cache_->Init()) {
- printf("Unable to open cache files\n");
- return;
- }
- cache_->SetUpgradeMode();
- }
+ SlaveSM(const std::wstring& path, HANDLE channel);
virtual ~SlaveSM();
bool DoInit();
@@ -533,19 +576,45 @@
void DoGetNextEntry();
void DoGetPrevEntry();
int32 GetEntryFromList();
+ void DoGetEntryComplete(int result);
void DoCloseEntry();
void DoGetKey();
void DoGetUseTimes();
void DoGetDataSize();
void DoReadData();
+ void DoReadDataComplete(int ret);
void DoEnd();
void Fail();
void* iterator_;
+ Message msg_; // Used for DoReadDataComplete and DoGetEntryComplete.
+ net::CompletionCallbackImpl<SlaveSM> read_callback_;
+ net::CompletionCallbackImpl<SlaveSM> next_callback_;
scoped_ptr<disk_cache::BackendImpl> cache_;
};
+SlaveSM::SlaveSM(const std::wstring& path, HANDLE channel)
+ : BaseSM(channel), iterator_(NULL),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ read_callback_(this, &SlaveSM::DoReadDataComplete)),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ next_callback_(this, &SlaveSM::DoGetEntryComplete)) {
+ disk_cache::Backend* cache;
+ TestCompletionCallback cb;
+ int rv = disk_cache::CreateCacheBackend(net::DISK_CACHE,
+ FilePath::FromWStringHack(path), 0,
+ false,
+ cache_thread_.message_loop_proxy(),
+ &cache, &cb);
+ if (cb.GetResult(rv) != net::OK) {
+ printf("Unable to open cache files\n");
+ return;
+ }
+ cache_.reset(reinterpret_cast<disk_cache::BackendImpl*>(cache));
+ cache_->SetUpgradeMode();
+}
+
SlaveSM::~SlaveSM() {
if (iterator_)
cache_->EndEnumeration(&iterator_);
@@ -608,6 +677,9 @@
DEBUGMSG("\t\t\tSlave DoInit\n");
DCHECK(state_ == SLAVE_INITIAL);
state_ = SLAVE_WAITING;
+ if (!cache_.get())
+ return false;
+
return ReceiveMsg();
}
@@ -636,6 +708,11 @@
msg.result = RESULT_UNKNOWN_COMMAND;
} else {
msg.result = GetEntryFromList();
+ if (msg.result == RESULT_PENDING) {
+ // We are not done yet.
+ msg_ = msg;
+ return;
+ }
msg.long_arg1 = reinterpret_cast<int64>(entry_);
}
SendMsg(msg);
@@ -651,23 +728,31 @@
if (entry_)
entry_->Close();
- bool ret;
+ int rv;
if (input_->msg.command == GET_NEXT_ENTRY) {
- ret = cache_->OpenNextEntry(&iterator_,
- reinterpret_cast<disk_cache::Entry**>(&entry_));
+ rv = cache_->OpenNextEntry(&iterator_,
+ reinterpret_cast<disk_cache::Entry**>(&entry_),
+ &next_callback_);
} else {
DCHECK(input_->msg.command == GET_PREV_ENTRY);
- ret = cache_->OpenPrevEntry(&iterator_,
- reinterpret_cast<disk_cache::Entry**>(&entry_));
+ rv = cache_->OpenPrevEntry(&iterator_,
+ reinterpret_cast<disk_cache::Entry**>(&entry_),
+ &next_callback_);
+ }
+ DCHECK_EQ(net::ERR_IO_PENDING, rv);
+ return RESULT_PENDING;
+}
+
+void SlaveSM::DoGetEntryComplete(int result) {
+ DEBUGMSG("\t\t\tSlave DoGetEntryComplete\n");
+ if (result != net::OK) {
+ entry_ = NULL;
+ DEBUGMSG("\t\t\tSlave end of list\n");
}
- if (!ret)
- entry_ = NULL;
-
- if (!entry_)
- DEBUGMSG("\t\t\tSlave end of list\n");
-
- return RESULT_OK;
+ msg_.result = RESULT_OK;
+ msg_.long_arg1 = reinterpret_cast<int64>(entry_);
+ SendMsg(msg_);
}
void SlaveSM::DoCloseEntry() {
@@ -698,7 +783,7 @@
msg.buffer_bytes = std::min(key.size() + 1,
static_cast<size_t>(kBufferSize));
memcpy(output_->buffer, key.c_str(), msg.buffer_bytes);
- if (msg.buffer_bytes != key.size() + 1) {
+ if (msg.buffer_bytes != static_cast<int32>(key.size() + 1)) {
// We don't support moving this entry. Just tell the master.
msg.result = RESULT_NAME_OVERFLOW;
} else {
@@ -753,7 +838,13 @@
} else {
scoped_refptr<net::WrappedIOBuffer> buf =
new net::WrappedIOBuffer(output_->buffer);
- int ret = entry_->ReadData(stream, input_->msg.arg3, buf, size, NULL);
+ int ret = entry_->ReadData(stream, input_->msg.arg3, buf, size,
+ &read_callback_);
+ if (ret == net::ERR_IO_PENDING) {
+ // Save the message so we can continue were we left.
+ msg_ = msg;
+ return;
+ }
msg.buffer_bytes = (ret < 0) ? 0 : ret;
msg.result = RESULT_OK;
@@ -761,6 +852,14 @@
SendMsg(msg);
}
+void SlaveSM::DoReadDataComplete(int ret) {
+ DEBUGMSG("\t\t\tSlave DoReadDataComplete\n");
+ DCHECK_EQ(READ_DATA, msg_.command);
+ msg_.buffer_bytes = (ret < 0) ? 0 : ret;
+ msg_.result = RESULT_OK;
+ SendMsg(msg_);
+}
+
void SlaveSM::DoEnd() {
DEBUGMSG("\t\t\tSlave DoEnd\n");
MessageLoop::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask());
diff --git a/net/tools/dump_cache/url_to_filename_encoder.cc b/net/tools/dump_cache/url_to_filename_encoder.cc
new file mode 100644
index 0000000..89a1ca4
--- /dev/null
+++ b/net/tools/dump_cache/url_to_filename_encoder.cc
@@ -0,0 +1,302 @@
+// 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/logging.h"
+#include "base/string_util.h"
+#include "net/base/net_util.h"
+#include "net/tools/dump_cache/url_to_filename_encoder.h"
+
+using std::string;
+
+namespace {
+
+inline bool IsHexDigit(unsigned char c) {
+ return (('0' <= c && c <= '9') || ('A' <= c && c <= 'F') ||
+ ('a' <= c && c <= 'f'));
+}
+
+// Returns 1 if buf is prefixed by "num_digits" of hex digits
+// Teturns 0 otherwise.
+// The function checks for '\0' for string termination.
+int HexDigitsPrefix(const char* buf, int num_digits) {
+ for (int i = 0; i < num_digits; i++)
+ if (!IsHexDigit(buf[i]))
+ return 0; // This also detects end of string as '\0' is not xdigit.
+ return 1;
+}
+
+#ifdef WIN32
+#define strtoull _strtoui64
+#endif
+
+// A simple parser for long long values. Returns the parsed value if a
+// valid integer is found; else returns deflt
+// UInt64 and Int64 cannot handle decimal numbers with leading 0s.
+uint64 ParseLeadingHex64Value(const char *str, uint64 deflt) {
+ char *error = NULL;
+ const uint64 value = strtoull(str, &error, 16);
+ return (error == str) ? deflt : value;
+}
+
+}
+
+namespace net {
+
+// The escape character choice is made here -- all code and tests in this
+// directory are based off of this constant. However, our test ata
+// has tons of dependencies on this, so it cannot be changed without
+// re-running those tests and fixing them.
+const char kTruncationChar = '-';
+const char kEscapeChar = ',';
+const size_t kMaximumSubdirectoryLength = 128;
+
+void UrlToFilenameEncoder::AppendSegment(
+ char dir_separator, string* segment, string* dest) {
+ if (segment->empty() || (*segment == ".") || (*segment == "..")) {
+ dest->append(1, kEscapeChar);
+ dest->append(*segment);
+ segment->clear();
+ } else {
+ size_t segment_size = segment->size();
+ if (segment_size > kMaximumSubdirectoryLength) {
+ // We need to inject ",-" at the end of the segment to signify that
+ // we are inserting an artificial '/'. This means we have to chop
+ // off at least two characters to make room.
+ segment_size = kMaximumSubdirectoryLength - 2;
+
+ // But we don't want to break up an escape sequence that happens to lie at
+ // the end. Escape sequences are at most 2 characters.
+ if ((*segment)[segment_size - 1] == kEscapeChar) {
+ segment_size -= 1;
+ } else if ((*segment)[segment_size - 2] == kEscapeChar) {
+ segment_size -= 2;
+ }
+ dest->append(segment->data(), segment_size);
+ dest->append(1, kEscapeChar);
+ dest->append(1, kTruncationChar);
+ segment->erase(0, segment_size);
+
+ // At this point, if we had segment_size=3, and segment="abcd",
+ // then after this erase, we will have written "abc,-" and set segment="d"
+ } else {
+ dest->append(*segment);
+ segment->clear();
+ }
+ }
+}
+
+void UrlToFilenameEncoder::EncodeSegment(const string& filename_prefix,
+ const string& filename_ending,
+ char dir_separator,
+ string* encoded_filename) {
+ char encoded[3];
+ int encoded_len;
+ string segment;
+
+ // TODO(jmarantz): This code would be a bit simpler if we disallowed
+ // Instaweb allowing filename_prefix to not end in "/". We could
+ // then change the is routine to just take one input string.
+ size_t start_of_segment = filename_prefix.find_last_of(dir_separator);
+ if (start_of_segment == string::npos) {
+ segment = filename_prefix;
+ } else {
+ segment = filename_prefix.substr(start_of_segment + 1);
+ *encoded_filename = filename_prefix.substr(0, start_of_segment + 1);
+ }
+
+ size_t index = 0;
+ // Special case the first / to avoid adding a leading kEscapeChar.
+ if (!filename_ending.empty() && (filename_ending[0] == dir_separator)) {
+ encoded_filename->append(segment);
+ segment.clear();
+ encoded_filename->append(1, dir_separator);
+ ++index;
+ }
+
+ for (; index < filename_ending.length(); ++index) {
+ unsigned char ch = static_cast<unsigned char>(filename_ending[index]);
+
+ if (ch == dir_separator) {
+ AppendSegment(dir_separator, &segment, encoded_filename);
+ encoded_filename->append(1, dir_separator);
+ segment.clear();
+ } else {
+ // & is common in URLs and is legal filename syntax, but is also
+ // a special Unix shell character, so let's avoid making
+ // filenames with &, as well as ?. It's probably better to
+ // blow up query-params than it is to make it hard to work with
+ // the files in shell-scripts.
+ if ((ch == 0x5F) || (ch == 0x2E) || // underscore period
+ (ch == 0x25) || (ch == 0x3D) || // percent equals
+ (ch == 0x2B) || (ch == 0x2D) || // plus dash
+ ((0x30 <= ch) && (ch <= 0x39)) || // Digits [0-9]
+ ((0x41 <= ch) && (ch <= 0x5A)) || // Uppercase [A-Z]
+ ((0x61 <= ch) && (ch <= 0x7A))) { // Lowercase [a-z]
+ encoded[0] = ch;
+ encoded_len = 1;
+ } else {
+ encoded[0] = kEscapeChar;
+ encoded[1] = ch / 16;
+ encoded[1] += (encoded[1] >= 10) ? 'A' - 10 : '0';
+ encoded[2] = ch % 16;
+ encoded[2] += (encoded[2] >= 10) ? 'A' - 10 : '0';
+ encoded_len = 3;
+ }
+ segment.append(encoded, encoded_len);
+
+ // Note: We chop paths into medium sized 'chunks'.
+ // This is due to filename limits on Windows and Unix.
+ // The Windows limit appears to be 128 characters, and
+ // Unix is larger, but not as large as URLs with large
+ // numbers of query params.
+ if (segment.size() > kMaximumSubdirectoryLength) {
+ AppendSegment(dir_separator, &segment, encoded_filename);
+ encoded_filename->append(1, dir_separator);
+ }
+ }
+ }
+
+ // Append "," to the leaf filename so the leaf can also be a branch., e.g.
+ // allow http://a/b/c and http://a/b/c/d to co-exist as files "/a/b/c," and
+ // /a/b/c/d". So we will rename the "d" here to "d,". If doing that pushed
+ // us over the 128 char limit, then we will need to append "/" and the
+ // remaining chars.
+ segment += kEscapeChar;
+ AppendSegment(dir_separator, &segment, encoded_filename);
+ if (!segment.empty()) {
+ // The last overflow segment is special, because we appended in
+ // kEscapeChar above. We won't need to check it again for size
+ // or further escaping.
+ encoded_filename->append(1, dir_separator);
+ encoded_filename->append(segment);
+ }
+}
+
+// Note: this decoder is not the exact inverse of the EncodeSegment above,
+// because it does not take into account a prefix.
+bool UrlToFilenameEncoder::Decode(const string& encoded_filename,
+ char dir_separator,
+ string* decoded_url) {
+ enum State {
+ kStart,
+ kEscape,
+ kFirstDigit,
+ kTruncate,
+ kEscapeDot
+ };
+ State state = kStart;
+ int char_code = 0;
+ char hex_buffer[3];
+ hex_buffer[2] = '\0';
+ for (size_t i = 0; i < encoded_filename.size(); ++i) {
+ char ch = encoded_filename[i];
+ switch (state) {
+ case kStart:
+ if (ch == kEscapeChar) {
+ state = kEscape;
+ } else {
+ decoded_url->append(1, ch);
+ }
+ break;
+ case kEscape:
+ if (HexDigitsPrefix(&ch, 1) == 1) {
+ hex_buffer[0] = ch;
+ state = kFirstDigit;
+ } else if (ch == kTruncationChar) {
+ state = kTruncate;
+ } else if (ch == '.') {
+ decoded_url->append(1, '.');
+ state = kEscapeDot; // Look for at most one more dot.
+ } else if (ch == dir_separator) {
+ // Consider url "//x". This will get encoded to "/,/x,".
+ // This code is what skips the first Escape.
+ decoded_url->append(1, ch);
+ state = kStart;
+ } else {
+ return false;
+ }
+ break;
+ case kFirstDigit:
+ if (HexDigitsPrefix(&ch, 1) == 1) {
+ hex_buffer[1] = ch;
+ uint64 hex_value = ParseLeadingHex64Value(hex_buffer, 0);
+ decoded_url->append(1, static_cast<char>(hex_value));
+ char_code = 0;
+ state = kStart;
+ } else {
+ return false;
+ }
+ break;
+ case kTruncate:
+ if (ch == dir_separator) {
+ // Skip this separator, it was only put in to break up long
+ // path segments, but is not part of the URL.
+ state = kStart;
+ } else {
+ return false;
+ }
+ break;
+ case kEscapeDot:
+ decoded_url->append(1, ch);
+ state = kStart;
+ break;
+ }
+ }
+
+ // All legal encoded filenames end in kEscapeChar.
+ return (state == kEscape);
+}
+
+// Escapes the given input |path| and chop any individual components
+// of the path which are greater than kMaximumSubdirectoryLength characters
+// into two chunks.
+//
+// This legacy version has several issues with aliasing of different URLs,
+// inability to represent both /a/b/c and /a/b/c/d, and inability to decode
+// the filenames back into URLs.
+//
+// But there is a large body of slurped data which depends on this format,
+// so leave it as the default for spdy_in_mem_edsm_server.
+string UrlToFilenameEncoder::LegacyEscape(const string& path) {
+ string output;
+
+ // Note: We also chop paths into medium sized 'chunks'.
+ // This is due to the incompetence of the windows
+ // filesystem, which still hasn't figured out how
+ // to deal with long filenames.
+ int last_slash = 0;
+ for (size_t index = 0; index < path.length(); index++) {
+ char ch = path[index];
+ if (ch == 0x5C)
+ last_slash = index;
+ if ((ch == 0x2D) || // hyphen
+ (ch == 0x5C) || (ch == 0x5F) || // backslash, underscore
+ ((0x30 <= ch) && (ch <= 0x39)) || // Digits [0-9]
+ ((0x41 <= ch) && (ch <= 0x5A)) || // Uppercase [A-Z]
+ ((0x61 <= ch) && (ch <= 0x7A))) { // Lowercase [a-z]
+ output.append(&path[index], 1);
+ } else {
+ char encoded[3];
+ encoded[0] = 'x';
+ encoded[1] = ch / 16;
+ encoded[1] += (encoded[1] >= 10) ? 'A' - 10 : '0';
+ encoded[2] = ch % 16;
+ encoded[2] += (encoded[2] >= 10) ? 'A' - 10 : '0';
+ output.append(encoded, 3);
+ }
+ if (index - last_slash > kMaximumSubdirectoryLength) {
+#ifdef WIN32
+ char slash = '\\';
+#else
+ char slash = '/';
+#endif
+ output.append(&slash, 1);
+ last_slash = index;
+ }
+ }
+ return output;
+}
+
+} // namespace net
+
diff --git a/net/tools/dump_cache/url_to_filename_encoder.h b/net/tools/dump_cache/url_to_filename_encoder.h
index 4b9e6c5..b5cac37 100644
--- a/net/tools/dump_cache/url_to_filename_encoder.h
+++ b/net/tools/dump_cache/url_to_filename_encoder.h
@@ -1,7 +1,77 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
+// URL filename encoder goals:
+//
+// 1. Allow URLs with arbitrary path-segment length, generating filenames
+// with a maximum of 128 characters.
+// 2. Provide a somewhat human readable filenames, for easy debugging flow.
+// 3. Provide reverse-mapping from filenames back to URLs.
+// 4. Be able to distinguish http://x from http://x/ from http://x/index.html.
+// Those can all be different URLs.
+// 5. Be able to represent http://a/b/c and http://a/b/c/d, a pattern seen
+// with Facebook Connect.
+//
+// We need an escape-character for representing characters that are legal
+// in URL paths, but not in filenames, such as '?'. Illegal characters
+// in Windows are <>:"/\|?*. For reference, see
+// http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx
+//
+// We can pick any legal character as an escape, as long as we escape it too.
+// But as we have a goal of having filenames that humans can correlate with
+// URLs, we should pick one that doesn't show up frequently in URLs. Candidates
+// are ~`!@#$%^&()-=_+{}[],. but we would prefer to avoid characters that are
+// shell escapes, and characters that occur frequently in URLs.
+//
+// .#&%-=_+ occur frequently in URLs.
+// ~`!$^&(){}[] are special to Unix shells
+//
+// @ might seem like a reasonble option, but some build tools don't appreciate
+// filenames with @ in testdata. Perforce does not appreciate # in a filename.
+//
+// Though a web-site http://www.vias.org/linux-knowhow/lnag_05_05_09.html
+// identifies ^ as a special shell character, it did not appear to be an
+// issue to use it unquoted as a filename in bash or tcsh.
+//
+// Here are some frequencies of some special characters in a data set from Fall
+// '09. We find only 3 occurences of "x5E" (^ is ascii 0x53):
+// ^ 3 build tools don't like ^ in testdata filenames
+// @ 10 build tools don't like @ in testdata filenames
+// . 1676 too frequent in URLs
+// , 76 THE WINNER
+// # 0 build tools doesn't like it
+// & 487 Prefer to avoid shell escapes
+// % 374 g4 doesn't like it
+// = 579 very frequent in URLs -- leave unmodified
+// - 464 very frequent in URLs -- leave unmodified
+// _ 798 very frequent in URLs -- leave unmodified
+//
+// It is interesting that there were no slurped URLs with #, but I suspect this
+// might be due to the slurping methdology. So let's stick with the relatively
+// rare ','.
+//
+// Here's the escaping methodology:
+//
+// URL File
+// / /,
+// /. /.,
+// // /,/,
+// /./ /,./,
+// /../ /,../,
+// /, /,2C,
+// /,/ /,2C/,
+// /a/b /a/b, (, at the end of a name indicates a leaf).
+// /a/b/ /a/b/,
+//
+// path segments greater than 128 characters (after escape expansion) are
+// suffixed with ,- so we can know that the next "/" is not part of the URL:
+//
+// /verylongname/ /verylong,-/name
+
+// NOTE: we avoid using some classes here (like FilePath and GURL) because we
+// share this code with other projects externally.
+
#ifndef NET_TOOLS_DUMP_CACHE_URL_TO_FILE_ENCODER_H_
#define NET_TOOLS_DUMP_CACHE_URL_TO_FILE_ENCODER_H_
@@ -10,25 +80,31 @@
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/string_util.h"
-#include "googleurl/src/gurl.h"
+#include "net/tools/dump_cache/url_utilities.h"
namespace net {
// Helper class for converting a URL into a filename.
class UrlToFilenameEncoder {
public:
- // Given a |url| and a |base_path|, returns a FilePath which represents this
+ // Given a |url| and a |base_path|, returns a string which represents this
// |url|.
- static FilePath Encode(const std::string& url, FilePath base_path) {
+ // |legacy_escape| indicates that this function should use the old-style
+ // of encoding.
+ // TODO(mbelshe): delete the legacy_escape code.
+ static std::string Encode(const std::string& url, std::string base_path,
+ bool legacy_escape) {
std::string clean_url(url);
if (clean_url.length() && clean_url[clean_url.length()-1] == '/')
clean_url.append("index.html");
- GURL gurl(clean_url);
- FilePath filename(base_path);
- filename = filename.AppendASCII(gurl.host());
+ std::string host = UrlUtilities::GetUrlHost(clean_url);
+ std::string filename(base_path);
+ filename.append("\\");
+ filename = filename.append(host);
+ filename.append("\\");
- std::string url_filename = gurl.PathForRequest();
+ std::string url_filename = UrlUtilities::GetUrlPath(clean_url);
// Strip the leading '/'
if (url_filename[0] == '/')
url_filename = url_filename.substr(1);
@@ -40,59 +116,71 @@
StripDoubleSlashes(&url_filename);
// Save path as filesystem-safe characters
- url_filename = Escape(url_filename);
- filename = filename.AppendASCII(url_filename);
+ if (legacy_escape) {
+ url_filename = LegacyEscape(url_filename);
+ } else {
+ url_filename = Escape(url_filename);
+ }
+ filename = filename.append(url_filename);
+
+#ifndef WIN32
+ // Last step - convert to native slashes!
+ const std::string slash("/");
+ const std::string backslash("\\");
+ ReplaceAll(&filename, backslash, slash);
+#endif
return filename;
}
- private:
- // This is the length at which we chop individual subdirectories.
- // Technically, we shouldn't need to do this, but I found that
- // even with long-filename support, windows had trouble creating
- // long subdirectories, and making them shorter helps.
- static const size_t kMaximumSubdirectoryLength = 128;
+ // Rewrite HTML in a form that the SPDY in-memory server
+ // can read.
+ // |filename_prefix| is prepended without escaping.
+ // |filename_ending| is the URL to be encoded into a filename.
+ // |dir_separator| is "/" on Unix, "\" on Windows.
+ // |encoded_filename| is the resultant filename.
+ static void EncodeSegment(
+ const std::string& filename_prefix,
+ const std::string& filename_ending,
+ char dir_separator,
+ std::string* encoded_filename);
- // Escape the given input |path| and chop any individual components
+ // Decodes a filename that was encoded with EncodeSegment,
+ // yielding back the original URL.
+ static bool Decode(const std::string& encoded_filename,
+ char dir_separator,
+ std::string* decoded_url);
+
+ private:
+ // Appends a segment of the path, special-casing ".", "..", and "", and
+ // ensuring that the segment does not exceed the path length. If it does,
+ // it chops the end off the segment, writes the segment with a separator of
+ // ",-/", and then rewrites segment to contain just the truncated piece so
+ // it can be used in the next iteration.
+ // |dir_separator| is "/" on Unix, "\" on Windows.
+ // |segment| is a read/write parameter containing segment to write
+ static void AppendSegment(
+ char dir_separator,
+ std::string* segment,
+ std::string* dest);
+
+ // Escapes the given input |path| and chop any individual components
// of the path which are greater than kMaximumSubdirectoryLength characters
// into two chunks.
static std::string Escape(const std::string& path) {
std::string output;
- int last_slash = 0;
- for (size_t index = 0; index < path.length(); index++) {
- char ch = path[index];
- if (ch == 0x5C)
- last_slash = index;
- if ((ch == 0x2D) || // hyphen
- (ch == 0x5C) || (ch == 0x5F) || // backslash, underscore
- ((0x30 <= ch) && (ch <= 0x39)) || // Digits [0-9]
- ((0x41 <= ch) && (ch <= 0x5A)) || // Uppercase [A-Z]
- ((0x61 <= ch) && (ch <= 0x7A))) { // Lowercase [a-z]
- output.append(&path[index],1);
- } else {
- char encoded[3];
- encoded[0] = 'x';
- encoded[1] = ch / 16;
- encoded[1] += (encoded[1] >= 10) ? 'A' - 10 : '0';
- encoded[2] = ch % 16;
- encoded[2] += (encoded[2] >= 10) ? 'A' - 10 : '0';
- output.append(encoded, 3);
- }
- if (index - last_slash > kMaximumSubdirectoryLength) {
- char backslash = '\\';
- output.append(&backslash, 1);
- last_slash = index;
- }
- }
+ EncodeSegment("", path, '\\', &output);
return output;
}
+ // Allow reading of old slurped files.
+ static std::string LegacyEscape(const std::string& path);
+
// Replace all instances of |from| within |str| as |to|.
- static void ReplaceAll(const std::string& from,
- const std::string& to,
- std::string* str) {
+ static void ReplaceAll(std::string* str, const std::string& from,
+ const std::string& to) {
std::string::size_type pos(0);
- while((pos = str->find(from, pos)) != std::string::npos) {
+ while ((pos = str->find(from, pos)) != std::string::npos) {
str->replace(pos, from.size(), to);
pos += from.size();
}
@@ -100,21 +188,20 @@
// Replace all instances of "/" with "\" in |path|.
static void ConvertToSlashes(std::string* path) {
- static const char slash[] = { '/', '\0' };
- static const char backslash[] = { '\\', '\0' };
- ReplaceAll(slash, backslash, path);
+ const std::string slash("/");
+ const std::string backslash("\\");
+ ReplaceAll(path, slash, backslash);
}
// Replace all instances of "\\" with "%5C%5C" in |path|.
static void StripDoubleSlashes(std::string* path) {
- static const char doubleslash[] = { '\\', '\\', '\0' };
- static const char escaped_doubleslash[] =
- { '%', '5', 'C', '%', '5', 'C','\0' };
- ReplaceAll(doubleslash, escaped_doubleslash, path);
+ const std::string doubleslash("\\\\");
+ const std::string escaped_doubleslash("%5C%5C");
+ ReplaceAll(path, doubleslash, escaped_doubleslash);
}
};
-} // namespace net
+} // namespace net
-#endif // NET_TOOLS_DUMP_CACHE_URL_TO_FILE_ENCODER_H__
+#endif // NET_TOOLS_DUMP_CACHE_URL_TO_FILE_ENCODER_H_
diff --git a/net/tools/dump_cache/url_to_filename_encoder_unittest.cc b/net/tools/dump_cache/url_to_filename_encoder_unittest.cc
new file mode 100644
index 0000000..32cef99
--- /dev/null
+++ b/net/tools/dump_cache/url_to_filename_encoder_unittest.cc
@@ -0,0 +1,268 @@
+// 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 "net/tools/dump_cache/url_to_filename_encoder.h"
+
+#include <string>
+#include <vector>
+#include "base/string_piece.h"
+#include "base/string_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+
+// The escape character choice is made here -- all code and tests in this
+// directory are based off of this constant. However, our test ata
+// has tons of dependencies on this, so it cannot be changed without
+// re-running those tests and fixing them.
+const char kTruncationChar = '-';
+const char kEscapeChar = ',';
+const size_t kMaximumSubdirectoryLength = 128;
+
+class UrlToFilenameEncoderTest : public ::testing::Test {
+ protected:
+ UrlToFilenameEncoderTest() : escape_(1, kEscapeChar) {}
+
+ void CheckSegmentLength(const StringPiece& escaped_word) {
+ std::vector<StringPiece> components;
+ Tokenize(escaped_word, StringPiece("/"), &components);
+ for (size_t i = 0; i < components.size(); ++i) {
+ EXPECT_GE(kMaximumSubdirectoryLength,
+ components[i].size());
+ }
+ }
+
+ void CheckValidChars(const StringPiece& escaped_word) {
+ // These characters are invalid in Windows. We will
+ // ignore / for this test, but add in ', as that's pretty
+ // inconvenient in a Unix filename.
+ //
+ // See http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx
+ static const char kInvalidChars[] = "<>:\"\\|?*'";
+ for (size_t i = 0; i < escaped_word.size(); ++i) {
+ char c = escaped_word[i];
+ EXPECT_EQ(NULL, strchr(kInvalidChars, c));
+ EXPECT_NE('\0', c); // only invalid character in Posix
+ EXPECT_GT(0x7E, c); // only English printable characters
+ }
+ }
+
+ void Validate(const string& in_word, const string& gold_word) {
+ string escaped_word, url;
+ UrlToFilenameEncoder::EncodeSegment("", in_word, '/', &escaped_word);
+ EXPECT_EQ(gold_word, escaped_word);
+ CheckSegmentLength(escaped_word);
+ CheckValidChars(escaped_word);
+ UrlToFilenameEncoder::Decode(escaped_word, '/', &url);
+ EXPECT_EQ(in_word, url);
+ }
+
+ void ValidateAllSegmentsSmall(const string& in_word) {
+ string escaped_word, url;
+ UrlToFilenameEncoder::EncodeSegment("", in_word, '/', &escaped_word);
+ CheckSegmentLength(escaped_word);
+ CheckValidChars(escaped_word);
+ UrlToFilenameEncoder::Decode(escaped_word, '/', &url);
+ EXPECT_EQ(in_word, url);
+ }
+
+ void ValidateNoChange(const string& word) {
+ // We always suffix the leaf with kEscapeChar, unless the leaf is empty.
+ Validate(word, word + escape_);
+ }
+
+ void ValidateEscaped(unsigned char ch) {
+ // We always suffix the leaf with kEscapeChar, unless the leaf is empty.
+ char escaped[100];
+ const char escape = kEscapeChar;
+ base::snprintf(escaped, sizeof(escaped), "%c%02X%c", escape, ch, escape);
+ Validate(string(1, ch), escaped);
+ }
+
+ string escape_;
+};
+
+TEST_F(UrlToFilenameEncoderTest, DoesNotEscape) {
+ ValidateNoChange("");
+ ValidateNoChange("abcdefg");
+ ValidateNoChange("abcdefghijklmnopqrstuvwxyz");
+ ValidateNoChange("ZYXWVUT");
+ ValidateNoChange("ZYXWVUTSRQPONMLKJIHGFEDCBA");
+ ValidateNoChange("01234567689");
+ ValidateNoChange("/-_");
+ ValidateNoChange("abcdefghijklmnopqrstuvwxyzZYXWVUTSRQPONMLKJIHGFEDCBA"
+ "01234567689/-_");
+ ValidateNoChange("index.html");
+ ValidateNoChange("/");
+ ValidateNoChange("/.");
+ ValidateNoChange(".");
+ ValidateNoChange("..");
+ ValidateNoChange("%");
+ ValidateNoChange("=");
+ ValidateNoChange("+");
+ ValidateNoChange("_");
+}
+
+TEST_F(UrlToFilenameEncoderTest, Escapes) {
+ ValidateEscaped('!');
+ ValidateEscaped('"');
+ ValidateEscaped('#');
+ ValidateEscaped('$');
+ ValidateEscaped('&');
+ ValidateEscaped('(');
+ ValidateEscaped(')');
+ ValidateEscaped('*');
+ ValidateEscaped(',');
+ ValidateEscaped(':');
+ ValidateEscaped(';');
+ ValidateEscaped('<');
+ ValidateEscaped('>');
+ ValidateEscaped('@');
+ ValidateEscaped('[');
+ ValidateEscaped('\'');
+ ValidateEscaped('\\');
+ ValidateEscaped(']');
+ ValidateEscaped('^');
+ ValidateEscaped('`');
+ ValidateEscaped('{');
+ ValidateEscaped('|');
+ ValidateEscaped('}');
+ ValidateEscaped('~');
+
+ // check non-printable characters
+ ValidateEscaped('\0');
+ for (int i = 127; i < 256; ++i) {
+ ValidateEscaped(static_cast<char>(i));
+ }
+}
+
+TEST_F(UrlToFilenameEncoderTest, DoesEscapeCorrectly) {
+ Validate("mysite.com&x", "mysite.com" + escape_ + "26x" + escape_);
+ Validate("/./", "/" + escape_ + "./" + escape_);
+ Validate("/../", "/" + escape_ + "../" + escape_);
+ Validate("//", "/" + escape_ + "/" + escape_);
+ Validate("/./leaf", "/" + escape_ + "./leaf" + escape_);
+ Validate("/../leaf", "/" + escape_ + "../leaf" + escape_);
+ Validate("//leaf", "/" + escape_ + "/leaf" + escape_);
+ Validate("mysite/u?param1=x¶m2=y",
+ "mysite/u" + escape_ + "3Fparam1=x" + escape_ + "26param2=y" +
+ escape_);
+ Validate("search?q=dogs&go=&form=QBLH&qs=n", // from Latency Labs bing test.
+ "search" + escape_ + "3Fq=dogs" + escape_ + "26go=" + escape_ +
+ "26form=QBLH" + escape_ + "26qs=n" + escape_);
+ Validate("~joebob/my_neeto-website+with_stuff.asp?id=138&content=true",
+ "" + escape_ + "7Ejoebob/my_neeto-website+with_stuff.asp" + escape_ +
+ "3Fid=138" + escape_ + "26content=true" + escape_);
+}
+
+TEST_F(UrlToFilenameEncoderTest, LongTail) {
+ static char long_word[] =
+ "~joebob/briggs/12345678901234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890123456789012345678901234567890";
+
+ // the long lines in the string below are 64 characters, so we can see
+ // the slashes every 128.
+ string gold_long_word =
+ escape_ + "7Ejoebob/briggs/"
+ "1234567890123456789012345678901234567890123456789012345678901234"
+ "56789012345678901234567890123456789012345678901234567890123456" +
+ escape_ + "-/"
+ "7890123456789012345678901234567890123456789012345678901234567890"
+ "12345678901234567890123456789012345678901234567890123456789012" +
+ escape_ + "-/"
+ "3456789012345678901234567890123456789012345678901234567890123456"
+ "78901234567890123456789012345678901234567890123456789012345678" +
+ escape_ + "-/"
+ "9012345678901234567890" + escape_;
+ EXPECT_LT(kMaximumSubdirectoryLength,
+ sizeof(long_word));
+ Validate(long_word, gold_long_word);
+}
+
+TEST_F(UrlToFilenameEncoderTest, LongTailQuestion) {
+ // Here the '?' in the last path segment expands to @3F, making
+ // it hit 128 chars before the input segment gets that big.
+ static char long_word[] =
+ "~joebob/briggs/1234567?1234567?1234567?1234567?1234567?"
+ "1234567?1234567?1234567?1234567?1234567?1234567?1234567?"
+ "1234567?1234567?1234567?1234567?1234567?1234567?1234567?"
+ "1234567?1234567?1234567?1234567?1234567?1234567?1234567?"
+ "1234567?1234567?1234567?1234567?1234567?1234567?1234567?"
+ "1234567?1234567?1234567?1234567?1234567?1234567?1234567?";
+
+ // Notice that at the end of the third segment, we avoid splitting
+ // the (escape_ + "3F") that was generated from the "?", so that segment is
+ // only 127 characters.
+ string pattern = "1234567" + escape_ + "3F"; // 10 characters
+ string gold_long_word =
+ escape_ + "7Ejoebob/briggs/" +
+ pattern + pattern + pattern + pattern + pattern + pattern + "1234"
+ "567" + escape_ + "3F" + pattern + pattern + pattern + pattern + pattern +
+ "123456" + escape_ + "-/"
+ "7" + escape_ + "3F" + pattern + pattern + pattern + pattern + pattern +
+ pattern + pattern + pattern + pattern + pattern + pattern + pattern +
+ "12" +
+ escape_ + "-/"
+ "34567" + escape_ + "3F" + pattern + pattern + pattern + pattern + pattern
+ + "1234567" + escape_ + "3F" + pattern + pattern + pattern + pattern
+ + pattern + "1234567" +
+ escape_ + "-/" +
+ escape_ + "3F" + pattern + pattern + escape_;
+ EXPECT_LT(kMaximumSubdirectoryLength,
+ sizeof(long_word));
+ Validate(long_word, gold_long_word);
+}
+
+TEST_F(UrlToFilenameEncoderTest, CornerCasesNearMaxLenNoEscape) {
+ // hit corner cases, +/- 4 characters from kMaxLen
+ for (int i = -4; i <= 4; ++i) {
+ string input;
+ input.append(i + kMaximumSubdirectoryLength, 'x');
+ ValidateAllSegmentsSmall(input);
+ }
+}
+
+TEST_F(UrlToFilenameEncoderTest, CornerCasesNearMaxLenWithEscape) {
+ // hit corner cases, +/- 4 characters from kMaxLen. This time we
+ // leave off the last 'x' and put in a '.', which ensures that we
+ // are truncating with '/' *after* the expansion.
+ for (int i = -4; i <= 4; ++i) {
+ string input;
+ input.append(i + kMaximumSubdirectoryLength - 1, 'x');
+ input.append(1, '.'); // this will expand to 3 characters.
+ ValidateAllSegmentsSmall(input);
+ }
+}
+
+TEST_F(UrlToFilenameEncoderTest, LeafBranchAlias) {
+ Validate("/a/b/c", "/a/b/c" + escape_); // c is leaf file "c,"
+ Validate("/a/b/c/d", "/a/b/c/d" + escape_); // c is directory "c"
+ Validate("/a/b/c/d/", "/a/b/c/d/" + escape_);
+}
+
+
+TEST_F(UrlToFilenameEncoderTest, BackslashSeparator) {
+ string long_word;
+ string escaped_word;
+ long_word.append(kMaximumSubdirectoryLength + 1, 'x');
+ UrlToFilenameEncoder::EncodeSegment("", long_word, '\\', &escaped_word);
+
+ // check that one backslash, plus the escape ",-", and the ending , got added.
+ EXPECT_EQ(long_word.size() + 4, escaped_word.size());
+ ASSERT_LT(kMaximumSubdirectoryLength,
+ escaped_word.size());
+ // Check that the backslash got inserted at the correct spot.
+ EXPECT_EQ('\\', escaped_word[
+ kMaximumSubdirectoryLength]);
+}
+
+} // namespace
+
diff --git a/net/tools/dump_cache/url_utilities.h b/net/tools/dump_cache/url_utilities.h
new file mode 100644
index 0000000..4de95dc
--- /dev/null
+++ b/net/tools/dump_cache/url_utilities.h
@@ -0,0 +1,64 @@
+// 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.
+
+#ifndef NET_TOOLS_DUMP_CACHE_URL_UTILITIES_H_
+#define NET_TOOLS_DUMP_CACHE_URL_UTILITIES_H_
+
+#include <string>
+
+namespace net {
+
+namespace UrlUtilities {
+
+// Gets the host from an url, strips the port number as well if the url
+// has one.
+// For example: calling GetUrlHost(www.foo.com:8080/boo) returns www.foo.com
+static std::string GetUrlHost(const std::string& url) {
+ size_t b = url.find("//");
+ if (b == std::string::npos)
+ b = 0;
+ else
+ b += 2;
+ size_t next_slash = url.find_first_of('/', b);
+ size_t next_colon = url.find_first_of(':', b);
+ if (next_slash != std::string::npos
+ && next_colon != std::string::npos
+ && next_colon < next_slash) {
+ return std::string(url, b, next_colon - b);
+ }
+ if (next_slash == std::string::npos) {
+ if (next_colon != std::string::npos) {
+ return std::string(url, next_colon - b);
+ } else {
+ next_slash = url.size();
+ }
+ }
+ return std::string(url, b, next_slash - b);
+}
+
+// Gets the path portion of an url.
+// e.g http://www.foo.com/path
+// returns /path
+static std::string GetUrlPath(const std::string& url) {
+ size_t b = url.find("//");
+ if (b == std::string::npos)
+ b = 0;
+ else
+ b += 2;
+ b = url.find("/", b);
+ if (b == std::string::npos)
+ return "/";
+
+ size_t e = url.find("#", b+1);
+ if (e != std::string::npos)
+ return std::string(url, b, (e - b));
+ return std::string(url, b);
+}
+
+} // namespace UrlUtilities
+
+} // namespace net
+
+#endif // NET_TOOLS_DUMP_CACHE_URL_UTILITIES_H_
+
diff --git a/net/tools/fetch/fetch_client.cc b/net/tools/fetch/fetch_client.cc
index 120b5ca..8863dd7 100644
--- a/net/tools/fetch/fetch_client.cc
+++ b/net/tools/fetch/fetch_client.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -15,6 +15,7 @@
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/base/ssl_config_service.h"
+#include "net/http/http_auth_handler_factory.h"
#include "net/http/http_cache.h"
#include "net/http/http_network_layer.h"
#include "net/http/http_request_info.h"
@@ -62,7 +63,7 @@
request_info_.url = url_;
request_info_.method = "GET";
int state = transaction_->Start(
- &request_info_, &connect_callback_, NULL);
+ &request_info_, &connect_callback_, net::BoundNetLog());
DCHECK(state == net::ERR_IO_PENDING);
};
@@ -132,20 +133,24 @@
MessageLoop loop(MessageLoop::TYPE_IO);
scoped_refptr<net::HostResolver> host_resolver(
- net::CreateSystemHostResolver(NULL));
+ net::CreateSystemHostResolver(net::HostResolver::kDefaultParallelism));
scoped_refptr<net::ProxyService> proxy_service(
net::ProxyService::CreateNull());
scoped_refptr<net::SSLConfigService> ssl_config_service(
net::SSLConfigService::CreateSystemSSLConfigService());
net::HttpTransactionFactory* factory = NULL;
+ scoped_ptr<net::HttpAuthHandlerFactory> http_auth_handler_factory(
+ net::HttpAuthHandlerFactory::CreateDefault());
if (use_cache) {
- factory = new net::HttpCache(NULL, host_resolver, proxy_service,
- ssl_config_service, 0);
+ factory = new net::HttpCache(host_resolver, proxy_service,
+ ssl_config_service, http_auth_handler_factory.get(), NULL, NULL,
+ net::HttpCache::DefaultBackend::InMemory(0));
} else {
factory = new net::HttpNetworkLayer(
- net::ClientSocketFactory::GetDefaultFactory(), NULL, host_resolver,
- proxy_service, ssl_config_service);
+ net::ClientSocketFactory::GetDefaultFactory(), host_resolver,
+ proxy_service, ssl_config_service, http_auth_handler_factory.get(),
+ NULL, NULL);
}
{
diff --git a/net/tools/fetch/http_listen_socket.cc b/net/tools/fetch/http_listen_socket.cc
index 033afc4..d0d6b97 100644
--- a/net/tools/fetch/http_listen_socket.cc
+++ b/net/tools/fetch/http_listen_socket.cc
@@ -2,11 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "net/tools/fetch/http_listen_socket.h"
+
+#include <map>
+
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/message_loop.h"
#include "base/string_util.h"
-#include "net/tools/fetch/http_listen_socket.h"
#include "net/tools/fetch/http_server_request_info.h"
#include "net/tools/fetch/http_server_response_info.h"
@@ -182,8 +185,9 @@
}
void HttpListenSocket::DidRead(ListenSocket* connection,
- const std::string& data) {
- recv_data_ += data;
+ const char* data,
+ int len) {
+ recv_data_.append(data, len);
while (recv_data_.length()) {
HttpServerRequestInfo* request = ParseHeaders();
if (!request)
diff --git a/net/tools/fetch/http_listen_socket.h b/net/tools/fetch/http_listen_socket.h
index 73f09f6..a1b77c5 100644
--- a/net/tools/fetch/http_listen_socket.h
+++ b/net/tools/fetch/http_listen_socket.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -36,7 +36,7 @@
// ListenSocketDelegate
virtual void DidAccept(ListenSocket* server, ListenSocket* connection);
- virtual void DidRead(ListenSocket* connection, const std::string& data);
+ virtual void DidRead(ListenSocket* connection, const char* data, int len);
virtual void DidClose(ListenSocket* sock);
private:
@@ -54,7 +54,7 @@
HttpListenSocket::Delegate* delegate_;
std::string recv_data_;
- DISALLOW_EVIL_CONSTRUCTORS(HttpListenSocket);
+ DISALLOW_COPY_AND_ASSIGN(HttpListenSocket);
};
#endif // NET_BASE_TOOLS_HTTP_LISTEN_SOCKET_H_
diff --git a/net/tools/fetch/http_server.h b/net/tools/fetch/http_server.h
index 6b8e719..1a3d402 100644
--- a/net/tools/fetch/http_server.h
+++ b/net/tools/fetch/http_server.h
@@ -1,25 +1,25 @@
-// Copyright (c) 2009 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.
-
-#ifndef NET_BASE_TOOLS_HTTP_SERVER_H_
-#define NET_BASE_TOOLS_HTTP_SERVER_H_
-
-#include "base/basictypes.h"
-#include "net/tools/fetch/http_session.h"
-
-// Implements a simple, single-threaded HttpServer.
-// Right now, this class implements no functionality above and beyond
-// the HttpSession.
-class HttpServer {
-public:
- HttpServer(std::string ip, int port);
- ~HttpServer();
-
-private:
- scoped_ptr<HttpSession> session_;
- DISALLOW_EVIL_CONSTRUCTORS(HttpServer);
-};
-
-#endif // NET_BASE_TOOLS_HTTP_SERVER_H_
-
+// 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.
+
+#ifndef NET_BASE_TOOLS_HTTP_SERVER_H_
+#define NET_BASE_TOOLS_HTTP_SERVER_H_
+
+#include "base/basictypes.h"
+#include "net/tools/fetch/http_session.h"
+
+// Implements a simple, single-threaded HttpServer.
+// Right now, this class implements no functionality above and beyond
+// the HttpSession.
+class HttpServer {
+public:
+ HttpServer(std::string ip, int port);
+ ~HttpServer();
+
+private:
+ scoped_ptr<HttpSession> session_;
+ DISALLOW_COPY_AND_ASSIGN(HttpServer);
+};
+
+#endif // NET_BASE_TOOLS_HTTP_SERVER_H_
+
diff --git a/net/tools/fetch/http_server_request_info.h b/net/tools/fetch/http_server_request_info.h
index 7af161f..fb45bdb 100644
--- a/net/tools/fetch/http_server_request_info.h
+++ b/net/tools/fetch/http_server_request_info.h
@@ -1,24 +1,25 @@
-// Copyright (c) 2009 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.
-
-#ifndef NET_BASE_TOOLS_HTTP_SERVER_REQUEST_INFO_H_
-#define NET_BASE_TOOLS_HTTP_SERVER_REQUEST_INFO_H_
-
-#include <string>
-
-#include "net/http/http_request_info.h"
-
-// Meta information about an HTTP request.
-// This is geared toward servers in that it keeps a map of the headers and
-// values rather than just a list of header strings (which net::HttpRequestInfo
-// does).
-class HttpServerRequestInfo : public net::HttpRequestInfo {
- public:
- HttpServerRequestInfo() : net::HttpRequestInfo() {}
-
- // A map of the names -> values for HTTP headers.
- std::map<std::string, std::string> headers;
-};
-
-#endif // NET_BASE_TOOLS_HTTP_SERVER_REQUEST_INFO_H_
+// Copyright (c) 2009 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.
+
+#ifndef NET_BASE_TOOLS_HTTP_SERVER_REQUEST_INFO_H_
+#define NET_BASE_TOOLS_HTTP_SERVER_REQUEST_INFO_H_
+
+#include <map>
+#include <string>
+
+#include "net/http/http_request_info.h"
+
+// Meta information about an HTTP request.
+// This is geared toward servers in that it keeps a map of the headers and
+// values rather than just a list of header strings (which net::HttpRequestInfo
+// does).
+class HttpServerRequestInfo : public net::HttpRequestInfo {
+ public:
+ HttpServerRequestInfo() : net::HttpRequestInfo() {}
+
+ // A map of the names -> values for HTTP headers.
+ std::map<std::string, std::string> headers;
+};
+
+#endif // NET_BASE_TOOLS_HTTP_SERVER_REQUEST_INFO_H_
diff --git a/net/tools/fetch/http_session.h b/net/tools/fetch/http_session.h
index 4d9e84b..a1d57bf 100644
--- a/net/tools/fetch/http_session.h
+++ b/net/tools/fetch/http_session.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -20,7 +20,7 @@
private:
scoped_refptr<HttpListenSocket> socket_;
- DISALLOW_EVIL_CONSTRUCTORS(HttpSession);
+ DISALLOW_COPY_AND_ASSIGN(HttpSession);
};
#endif // NET_BASE_TOOLS_HTTP_SESSION_H_
diff --git a/net/tools/flip_server/balsa_frame.cc b/net/tools/flip_server/balsa_frame.cc
index 90631ce..9b8173e 100644
--- a/net/tools/flip_server/balsa_frame.cc
+++ b/net/tools/flip_server/balsa_frame.cc
@@ -9,7 +9,6 @@
#include <strings.h>
#include <limits>
-#include <iostream>
#include <string>
#include <utility>
#include <vector>
diff --git a/net/tools/flip_server/balsa_headers.cc b/net/tools/flip_server/balsa_headers.cc
index 40e446b..b58fb03 100644
--- a/net/tools/flip_server/balsa_headers.cc
+++ b/net/tools/flip_server/balsa_headers.cc
@@ -71,6 +71,12 @@
const size_t BalsaBuffer::kDefaultBlocksize;
+std::ostream& BalsaHeaders::iterator_base::operator<<(std::ostream& os,
+ const iterator_base& it) {
+ os << "[" << it.headers_ << ", " << it.idx_ << "]";
+ return os;
+ }
+
void BalsaHeaders::Clear() {
balsa_buffer_.Clear();
transfer_encoding_is_chunked_ = false;
@@ -756,4 +762,3 @@
}
} // namespace net
-
diff --git a/net/tools/flip_server/balsa_headers.h b/net/tools/flip_server/balsa_headers.h
index 6f590c2..24e7940 100644
--- a/net/tools/flip_server/balsa_headers.h
+++ b/net/tools/flip_server/balsa_headers.h
@@ -6,7 +6,7 @@
#define NET_TOOLS_FLIP_SERVER_BALSA_HEADERS_H_
#include <algorithm>
-#include <iostream>
+#include <iosfwd>
#include <iterator>
#include <string>
#include <utility>
@@ -470,10 +470,7 @@
// operator<< work for the classes it sees. It would be better if there
// was an additional traits-like system for the gUnit output... but oh
// well.
- friend std::ostream& operator<<(std::ostream& os, const iterator_base& it) {
- os << "[" << it.headers_ << ", " << it.idx_ << "]";
- return os;
- }
+ std::ostream& operator<<(std::ostream& os, const iterator_base& it);
protected:
iterator_base(const BalsaHeaders* headers, HeaderLines::size_type index) :
@@ -1117,7 +1114,7 @@
protected:
friend class BalsaFrame;
- friend class FlipFrame;
+ friend class SpdyFrame;
friend class HTTPMessage;
friend class BalsaHeadersTokenUtils;
diff --git a/net/tools/flip_server/create_listener.cc b/net/tools/flip_server/create_listener.cc
index 7ea5efa..3538261 100644
--- a/net/tools/flip_server/create_listener.cc
+++ b/net/tools/flip_server/create_listener.cc
@@ -10,6 +10,7 @@
#include <sys/socket.h> // for getaddrinfo and getnameinfo
#include <sys/types.h> // "
#include <unistd.h> // for exit()
+#include <ostream>
#include "net/tools/flip_server/create_listener.h"
diff --git a/net/tools/flip_server/create_listener.h b/net/tools/flip_server/create_listener.h
index 06979d9..32758ee 100644
--- a/net/tools/flip_server/create_listener.h
+++ b/net/tools/flip_server/create_listener.h
@@ -5,7 +5,7 @@
#ifndef NET_TOOLS_FLIP_SERVER_CREATE_LISTENER_H__
#define NET_TOOLS_FLIP_SERVER_CREATE_LISTENER_H__
-#include <iostream>
+#include <iosfwd>
#include <string>
namespace net {
diff --git a/net/tools/flip_server/flip_in_mem_edsm_server.cc b/net/tools/flip_server/flip_in_mem_edsm_server.cc
index 9ffc86f..974994d 100644
--- a/net/tools/flip_server/flip_in_mem_edsm_server.cc
+++ b/net/tools/flip_server/flip_in_mem_edsm_server.cc
@@ -7,6 +7,7 @@
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
+#include <openssl/err.h>
#include <openssl/ssl.h>
#include <deque>
@@ -19,9 +20,9 @@
#include "base/simple_thread.h"
#include "base/timer.h"
#include "base/lock.h"
-#include "net/flip/flip_frame_builder.h"
-#include "net/flip/flip_framer.h"
-#include "net/flip/flip_protocol.h"
+#include "net/spdy/spdy_frame_builder.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
#include "net/tools/flip_server/balsa_enums.h"
#include "net/tools/flip_server/balsa_frame.h"
#include "net/tools/flip_server/balsa_headers.h"
@@ -29,7 +30,6 @@
#include "net/tools/flip_server/buffer_interface.h"
#include "net/tools/flip_server/create_listener.h"
#include "net/tools/flip_server/epoll_server.h"
-#include "net/tools/flip_server/loadtime_measurement.h"
#include "net/tools/flip_server/other_defines.h"
#include "net/tools/flip_server/ring_buffer.h"
#include "net/tools/flip_server/simple_buffer.h"
@@ -39,54 +39,20 @@
////////////////////////////////////////////////////////////////////////////////
-using base::StringPiece;
-using base::SimpleThread;
-// using base::Lock; // heh, this isn't in base namespace?!
-// using base::AutoLock; // ditto!
-using flip::CONTROL_FLAG_NONE;
-using flip::DATA_FLAG_COMPRESSED;
-using flip::DATA_FLAG_FIN;
-using flip::FIN_STREAM;
-using flip::FlipControlFrame;
-using flip::FlipDataFlags;
-using flip::FlipDataFrame;
-using flip::FlipFinStreamControlFrame;
-using flip::FlipFrame;
-using flip::FlipFrameBuilder;
-using flip::FlipFramer;
-using flip::FlipFramerVisitorInterface;
-using flip::FlipHeaderBlock;
-using flip::FlipStreamId;
-using flip::FlipSynReplyControlFrame;
-using flip::FlipSynStreamControlFrame;
-using flip::SYN_REPLY;
-using flip::SYN_STREAM;
-using net::BalsaFrame;
-using net::BalsaFrameEnums;
-using net::BalsaHeaders;
-using net::BalsaHeadersEnums;
-using net::BalsaVisitorInterface;
-using net::EpollAlarmCallbackInterface;
-using net::EpollCallbackInterface;
-using net::EpollEvent;
-using net::EpollServer;
-using net::RingBuffer;
-using net::SimpleBuffer;
-using net::SplitStringPieceToVector;
-using net::UrlUtilities;
+using std::cerr;
using std::deque;
+using std::list;
using std::map;
+using std::ostream;
using std::pair;
using std::string;
using std::vector;
-using std::list;
-using std::ostream;
-using std::cerr;
////////////////////////////////////////////////////////////////////////////////
+
// If set to true, then the server will act as an SSL server for both
-// HTTP and FLIP);
+// HTTP and SPDY);
bool FLAGS_use_ssl = true;
// The name of the cert .pem file);
@@ -108,8 +74,8 @@
// is completely drained and the accept() call returns an error);
int32 FLAGS_accepts_per_wake = 0;
-// The port on which the flip server listens);
-int32 FLAGS_flip_port = 10040;
+// The port on which the spdy server listens);
+int32 FLAGS_spdy_port = 10040;
// The port on which the http server listens);
int32 FLAGS_port = 16002;
@@ -145,18 +111,55 @@
// Does the server compress data frames);
bool FLAGS_use_compression = false;
-// The path to the urls file which includes the urls for testing);
-string FLAGS_urls_file = "experimental/users/fenix/flip/urls.txt";
+////////////////////////////////////////////////////////////////////////////////
-// The path to the html that does the pageload in iframe);
-string FLAGS_pageload_html_file =
- "experimental/users/fenix/flip/loadtime_measurement.html";
+using base::StringPiece;
+using base::SimpleThread;
+// using base::Lock; // heh, this isn't in base namespace?!
+// using base::AutoLock; // ditto!
+using net::BalsaFrame;
+using net::BalsaFrameEnums;
+using net::BalsaHeaders;
+using net::BalsaHeadersEnums;
+using net::BalsaVisitorInterface;
+using net::EpollAlarmCallbackInterface;
+using net::EpollCallbackInterface;
+using net::EpollEvent;
+using net::EpollServer;
+using net::RingBuffer;
+using net::SimpleBuffer;
+using net::SplitStringPieceToVector;
+using net::UrlUtilities;
+using spdy::CONTROL_FLAG_NONE;
+using spdy::DATA_FLAG_COMPRESSED;
+using spdy::DATA_FLAG_FIN;
+using spdy::RST_STREAM;
+using spdy::SYN_REPLY;
+using spdy::SYN_STREAM;
+using spdy::SpdyControlFrame;
+using spdy::SpdyDataFlags;
+using spdy::SpdyDataFrame;
+using spdy::SpdyRstStreamControlFrame;
+using spdy::SpdyFrame;
+using spdy::SpdyFrameBuilder;
+using spdy::SpdyFramer;
+using spdy::SpdyFramerVisitorInterface;
+using spdy::SpdyHeaderBlock;
+using spdy::SpdyStreamId;
+using spdy::SpdySynReplyControlFrame;
+using spdy::SpdySynStreamControlFrame;
-// If set to true, record requests in file named as fd used);
-bool FLAGS_record_mode = false;
-// The path to save the record files);
-string FLAGS_record_path = ".";
+////////////////////////////////////////////////////////////////////////////////
+
+void PrintSslError() {
+ char buf[128]; // this buffer must be at least 120 chars long.
+ int error_num = ERR_get_error();
+ while (error_num != 0) {
+ LOG(INFO)<< ERR_error_string(error_num, buf);
+ error_num = ERR_get_error();
+ }
+}
////////////////////////////////////////////////////////////////////////////////
@@ -189,13 +192,27 @@
<< " errno=" << errno;
}
-////////////////////////////////////////////////////////////////////////////////
+// Encode the URL.
+string EncodeURL(string uri, string host, string method) {
+ if (!FLAGS_need_to_encode_url) {
+ // TODO(mbelshe): if uri is fully qualified, need to strip protocol/host.
+ return string(method + "_" + uri);
+ }
-LoadtimeMeasurement global_loadtime_measurement(FLAGS_urls_file,
- FLAGS_pageload_html_file);
+ string filename;
+ if (uri[0] == '/') {
+ // uri is not fully qualified.
+ filename = net::UrlToFilenameEncoder::Encode(
+ "http://" + host + uri, method + "_/");
+ } else {
+ filename = net::UrlToFilenameEncoder::Encode(uri, method + "_/");
+ }
+ return filename;
+}
////////////////////////////////////////////////////////////////////////////////
+
struct GlobalSSLState {
SSL_METHOD* ssl_method;
SSL_CTX* ssl_ctx;
@@ -208,40 +225,53 @@
////////////////////////////////////////////////////////////////////////////////
// SSL stuff
-void flip_init_ssl(GlobalSSLState* state) {
+void spdy_init_ssl(GlobalSSLState* state) {
SSL_library_init();
- SSL_load_error_strings();
+ PrintSslError();
- state->ssl_method = TLSv1_server_method();
+ SSL_load_error_strings();
+ PrintSslError();
+
+ state->ssl_method = SSLv23_method();
state->ssl_ctx = SSL_CTX_new(state->ssl_method);
if (!state->ssl_ctx) {
+ PrintSslError();
LOG(FATAL) << "Unable to create SSL context";
}
+ // Disable SSLv2 support.
+ SSL_CTX_set_options(state->ssl_ctx, SSL_OP_NO_SSLv2);
if (SSL_CTX_use_certificate_file(state->ssl_ctx,
FLAGS_ssl_cert_name.c_str(),
SSL_FILETYPE_PEM) <= 0) {
+ PrintSslError();
LOG(FATAL) << "Unable to use cert.pem as SSL cert.";
}
if (SSL_CTX_use_PrivateKey_file(state->ssl_ctx,
FLAGS_ssl_key_name.c_str(),
SSL_FILETYPE_PEM) <= 0) {
+ PrintSslError();
LOG(FATAL) << "Unable to use key.pem as SSL key.";
}
if (!SSL_CTX_check_private_key(state->ssl_ctx)) {
+ PrintSslError();
LOG(FATAL) << "The cert.pem and key.pem files don't match";
}
}
-SSL* flip_new_ssl(SSL_CTX* ssl_ctx) {
+SSL* spdy_new_ssl(SSL_CTX* ssl_ctx) {
SSL* ssl = SSL_new(ssl_ctx);
+ PrintSslError();
+
SSL_set_accept_state(ssl);
+ PrintSslError();
return ssl;
}
////////////////////////////////////////////////////////////////////////////////
-const int kInitialDataSendersThreshold = (2 * 1460) - FlipFrame::size();
-const int kNormalSegmentSize = (2 * 1460) - FlipFrame::size();
+const int kMSS = 1460;
+const int kInitialDataSendersThreshold = (2 * kMSS) - SpdyFrame::size();
+const int kNormalSegmentSize = (2 * kMSS) - SpdyFrame::size();
////////////////////////////////////////////////////////////////////////////////
@@ -481,7 +511,7 @@
// versions of content.
// TODO(mbelshe) REMOVE ME
#if 0
- // TODO(mbelshe): append current date.
+ // TODO(mbelshe) append current date.
visitor.headers.RemoveAllOfHeader("date");
if (visitor.headers.HasHeader("expires")) {
visitor.headers.RemoveAllOfHeader("expires");
@@ -491,8 +521,7 @@
#endif
BalsaHeaders* headers = new BalsaHeaders;
headers->CopyFrom(visitor.headers);
- string filename_stripped =
- string(filename).substr(cwd_.size() + 1);
+ string filename_stripped = string(filename).substr(cwd_.size() + 1);
// LOG(INFO) << "Adding file (" << visitor.body.length() << " bytes): "
// << filename_stripped;
files_[filename_stripped] = FileData();
@@ -501,8 +530,7 @@
fd.filename = string(filename_stripped,
filename_stripped.find_first_of('/'));
if (headers->HasHeader("X-Associated-Content")) {
- string content =
- headers->GetHeader("X-Associated-Content").as_string();
+ string content = headers->GetHeader("X-Associated-Content").as_string();
vector<StringPiece> urls_and_priorities;
SplitStringPieceToVector(content, "||", &urls_and_priorities, true);
VLOG(1) << "Examining X-Associated-Content header";
@@ -516,7 +544,7 @@
url_and_priority[0].size());
string filename_string(url_and_priority[1].data(),
url_and_priority[1].size());
- int priority;
+ long priority;
char* last_eaten_char;
priority = strtol(priority_string.c_str(), &last_eaten_char, 0);
if (last_eaten_char ==
@@ -684,7 +712,7 @@
////////////////////////////////////////////////////////////////////////////////
class SMServerConnection;
-typedef SMInterface*(SMInterfaceFactory)(SMServerConnection* conn);
+typedef SMInterface*(SMInterfaceFactory)(SMServerConnection*);
////////////////////////////////////////////////////////////////////////////////
@@ -698,7 +726,7 @@
public:
virtual ~SMServerConnectionPoolInterface() {}
// SMServerConnections will use this:
- virtual void SMServerConnectionDone(SMServerConnection* conn) = 0;
+ virtual void SMServerConnectionDone(SMServerConnection* connection) = 0;
};
////////////////////////////////////////////////////////////////////////////////
@@ -710,7 +738,6 @@
MemoryCache* memory_cache,
EpollServer* epoll_server) :
fd_(-1),
- record_fd_(-1),
events_(0),
registered_in_epoll_server_(false),
@@ -723,12 +750,11 @@
memory_cache_(memory_cache),
sm_interface_(sm_interface_factory(this)),
- max_bytes_sent_per_dowrite_(128),
+ max_bytes_sent_per_dowrite_(4096),
ssl_(NULL) {}
int fd_;
- int record_fd_;
int events_;
bool registered_in_epoll_server_;
@@ -750,13 +776,6 @@
EpollServer* epoll_server() { return epoll_server_; }
OutputList* output_list() { return &output_list_; }
MemoryCache* memory_cache() { return memory_cache_; }
- int record_fd() { return record_fd_; }
- void close_record_fd() {
- if (record_fd_ != -1) {
- close(record_fd_);
- record_fd_ = -1;
- }
- }
void ReadyToSend() {
epoll_server_->SetFDReady(fd_, EPOLLIN | EPOLLOUT);
}
@@ -795,16 +814,6 @@
close(fd_);
fd_ = -1;
}
- if (FLAGS_record_mode) {
- char record_file_name[1024];
- snprintf(record_file_name, sizeof(record_file_name), "%s/%d_%ld",
- FLAGS_record_path.c_str(), fd, epoll_server->NowInUsec()/1000);
- record_fd_ = open(record_file_name, O_CREAT|O_APPEND|O_WRONLY, S_IRWXU);
- if (record_fd_ < 0) {
- LOG(ERROR) << "Open record file for fd " << fd << " failed";
- record_fd_ = -1;
- }
- }
fd_ = fd;
@@ -820,8 +829,9 @@
epoll_server_->RegisterFD(fd_, this, EPOLLIN | EPOLLOUT | EPOLLET);
if (global_ssl_state) {
- ssl_ = flip_new_ssl(global_ssl_state->ssl_ctx);
+ ssl_ = spdy_new_ssl(global_ssl_state->ssl_ctx);
SSL_set_fd(ssl_, fd_);
+ PrintSslError();
}
sm_interface_->PostAcceptHook();
}
@@ -887,6 +897,7 @@
ssize_t bytes_read = 0;
if (ssl_) {
bytes_read = SSL_read(ssl_, bytes, size);
+ PrintSslError();
} else {
bytes_read = recv(fd_, bytes, size, MSG_DONTWAIT);
}
@@ -1004,6 +1015,7 @@
ssize_t bytes_written = 0;
if (ssl_) {
bytes_written = SSL_write(ssl_, bytes, size);
+ PrintSslError();
} else {
bytes_written = send(fd_, bytes, size, flags);
}
@@ -1050,7 +1062,9 @@
VLOG(2) << "Resetting";
if (ssl_) {
SSL_shutdown(ssl_);
+ PrintSslError();
SSL_free(ssl_);
+ PrintSslError();
}
if (registered_in_epoll_server_) {
epoll_server_->UnregisterFD(fd_);
@@ -1177,6 +1191,19 @@
if (ExistsInPriorityMaps(mci.stream_id))
LOG(FATAL) << "OOps, already was inserted here?!";
+ double think_time_in_s = FLAGS_server_think_time_in_s;
+ string x_server_latency =
+ mci.file_data->headers->GetHeader("X-Server-Latency").as_string();
+ if (x_server_latency.size() != 0) {
+ char* endp;
+ double tmp_think_time_in_s = strtod(x_server_latency.c_str(), &endp);
+ if (endp != x_server_latency.c_str() + x_server_latency.size()) {
+ LOG(ERROR) << "Unable to understand X-Server-Latency of: "
+ << x_server_latency << " for resource: " << mci.file_data->filename;
+ } else {
+ think_time_in_s = tmp_think_time_in_s;
+ }
+ }
StreamIdToPriorityMap::iterator sitpmi;
sitpmi = stream_ids_.insert(
pair<uint32, PriorityMapPointer>(mci.stream_id,
@@ -1184,8 +1211,9 @@
PriorityMapPointer& pmp = sitpmi->second;
BeginOutputtingAlarm* boa = new BeginOutputtingAlarm(this, &pmp, mci);
+ VLOG(2) << "Server think time: " << think_time_in_s;
epoll_server_->RegisterAlarmApproximateDelta(
- FLAGS_server_think_time_in_s * 1000000, boa);
+ think_time_in_s * 1000000, boa);
}
void SpliceToPriorityRing(PriorityRing::iterator pri) {
@@ -1249,10 +1277,10 @@
////////////////////////////////////////////////////////////////////////////////
-class FlipSM : public FlipFramerVisitorInterface, public SMInterface {
+class SpdySM : public SpdyFramerVisitorInterface, public SMInterface {
private:
uint64 seq_num_;
- FlipFramer* framer_;
+ SpdyFramer* framer_;
SMServerConnection* connection_;
OutputList* output_list_;
@@ -1260,9 +1288,9 @@
MemoryCache* memory_cache_;
uint32 next_outgoing_stream_id_;
public:
- explicit FlipSM(SMServerConnection* connection) :
+ explicit SpdySM(SMServerConnection* connection) :
seq_num_(0),
- framer_(new FlipFramer),
+ framer_(new SpdyFramer),
connection_(connection),
output_list_(connection->output_list()),
output_ordering_(connection),
@@ -1271,97 +1299,73 @@
framer_->set_visitor(this);
}
private:
- virtual void OnError(FlipFramer* framer) {
+ virtual void OnError(SpdyFramer* framer) {
/* do nothing with this right now */
}
- virtual void OnControl(const FlipControlFrame* frame) {
- FlipHeaderBlock headers;
+ virtual void OnControl(const SpdyControlFrame* frame) {
+ SpdyHeaderBlock headers;
bool parsed_headers = false;
switch (frame->type()) {
case SYN_STREAM:
{
+ const SpdySynStreamControlFrame* syn_stream =
+ reinterpret_cast<const SpdySynStreamControlFrame*>(frame);
parsed_headers = framer_->ParseHeaderBlock(frame, &headers);
- VLOG(2) << "OnSyn(" << frame->stream_id() << ")";
+ VLOG(2) << "OnSyn(" << syn_stream->stream_id() << ")";
VLOG(2) << "headers parsed?: " << (parsed_headers? "yes": "no");
if (parsed_headers) {
VLOG(2) << "# headers: " << headers.size();
}
- unsigned int j = 0;
- for (FlipHeaderBlock::iterator i = headers.begin();
+ for (SpdyHeaderBlock::iterator i = headers.begin();
i != headers.end();
++i) {
VLOG(2) << i->first << ": " << i->second;
- if (FLAGS_record_mode && connection_->record_fd() > 0) {
- // If record mode is enabled and corresponding server connection
- // has file opened, then save the request headers into the file.
- // All the requests from the same connection is save in one file.
- // This file will be used to replay and generate FLIP requests
- // load.
- string header = i->first + ": " + i->second + "\n";
- ++j;
- if (j == headers.size()) {
- header += "\n"; // add an additional empty lime
- }
- int r = write(
- connection_->record_fd(), header.c_str(), header.size());
- if (r < 0) {
- perror("unable to write to record file:");
- }
- }
}
- FlipHeaderBlock::iterator method = headers.find("method");
- FlipHeaderBlock::iterator url = headers.find("url");
+ SpdyHeaderBlock::iterator method = headers.find("method");
+ SpdyHeaderBlock::iterator url = headers.find("url");
if (url == headers.end() || method == headers.end()) {
VLOG(2) << "didn't find method or url or method. Not creating stream";
break;
}
- FlipHeaderBlock::iterator referer = headers.find("referer");
+ SpdyHeaderBlock::iterator referer = headers.find("referer");
if (referer != headers.end() && method->second == "GET") {
memory_cache_->UpdateHeaders(referer->second, url->second);
}
string uri = UrlUtilities::GetUrlPath(url->second);
string host = UrlUtilities::GetUrlHost(url->second);
- // requests started with /testing are loadtime measurement related
- // urls, use LoadtimeMeasurement class to handle them.
- if (uri.find("/testing") == 0) {
- string output;
- global_loadtime_measurement.ProcessRequest(uri, output);
- SendOKResponse(frame->stream_id(), &output);
- } else {
- string filename;
- if (FLAGS_need_to_encode_url) {
- filename = net::UrlToFilenameEncoder::Encode(
- "http://" + host + uri, method->second + "_/");
- } else {
- filename = string(method->second + "_" + url->second);
- }
- NewStream(frame->stream_id(),
- reinterpret_cast<const FlipSynStreamControlFrame*>(frame)->
- priority(),
- filename);
- }
+ string filename = EncodeURL(uri, host, method->second);
+ NewStream(syn_stream->stream_id(),
+ reinterpret_cast<const SpdySynStreamControlFrame*>(frame)->
+ priority(),
+ filename);
}
break;
case SYN_REPLY:
parsed_headers = framer_->ParseHeaderBlock(frame, &headers);
- VLOG(2) << "OnSynReply(" << frame->stream_id() << ")";
+ VLOG(2) << "OnSynReply("
+ << reinterpret_cast<const SpdySynReplyControlFrame*>(
+ frame)->stream_id() << ")";
break;
- case FIN_STREAM:
- VLOG(2) << "OnFin(" << frame->stream_id() << ")";
- output_ordering_.RemoveStreamId(frame->stream_id());
+ case RST_STREAM:
+ {
+ const SpdyRstStreamControlFrame* rst_stream =
+ reinterpret_cast<const SpdyRstStreamControlFrame*>(frame);
+ VLOG(2) << "OnRst(" << rst_stream->stream_id() << ")";
+ output_ordering_.RemoveStreamId(rst_stream ->stream_id());
+ }
+ break;
- break;
default:
LOG(DFATAL) << "Unknown control frame type";
}
}
virtual void OnStreamFrameData(
- FlipStreamId stream_id,
+ SpdyStreamId stream_id,
const char* data, size_t len) {
VLOG(2) << "StreamData(" << stream_id << ", [" << len << "])";
/* do nothing with this right now */
@@ -1371,7 +1375,7 @@
}
public:
- ~FlipSM() {
+ ~SpdySM() {
Reset();
}
size_t ProcessInput(const char* data, size_t len) {
@@ -1387,14 +1391,14 @@
}
const char* ErrorAsString() const {
- return FlipFramer::ErrorCodeToString(framer_->error_code());
+ return SpdyFramer::ErrorCodeToString(framer_->error_code());
}
void Reset() {}
void ResetForNewConnection() {
// seq_num is not cleared, intentionally.
delete framer_;
- framer_ = new FlipFramer;
+ framer_ = new SpdyFramer;
framer_->set_visitor(this);
output_ordering_.Reset();
next_outgoing_stream_id_ = 2;
@@ -1412,12 +1416,12 @@
LOG(ERROR) << "Sending NOP FRAMES";
- scoped_ptr<FlipControlFrame> frame(FlipFramer::CreateNopFrame());
+ scoped_ptr<SpdyControlFrame> frame(SpdyFramer::CreateNopFrame());
for (int i = 0; i < kPkts; ++i) {
char* bytes = frame->data();
- size_t size = FlipFrame::size();
+ size_t size = SpdyFrame::size();
ssize_t bytes_written = connection_->Send(bytes, size, MSG_DONTWAIT);
- if (bytes_written > 0 && static_cast<size_t>(bytes_written) != size) {
+ if (static_cast<size_t>(bytes_written) != size) {
LOG(ERROR) << "Trouble sending Nop packet! (" << errno << ")";
if (errno == EAGAIN)
break;
@@ -1485,11 +1489,11 @@
void SendDataFrame(uint32 stream_id, const char* data, int64 len,
uint32 flags, bool compress) {
- FlipDataFlags flip_flags = static_cast<FlipDataFlags>(flags);
- SendDataFrameImpl(stream_id, data, len, flip_flags, compress);
+ SpdyDataFlags spdy_flags = static_cast<SpdyDataFlags>(flags);
+ SendDataFrameImpl(stream_id, data, len, spdy_flags, compress);
}
- FlipFramer* flip_framer() { return framer_; }
+ SpdyFramer* spdy_framer() { return framer_; }
private:
void SendEOFImpl(uint32 stream_id) {
@@ -1519,12 +1523,12 @@
output_ordering_.RemoveStreamId(stream_id);
}
- void CopyHeaders(FlipHeaderBlock& dest, const BalsaHeaders& headers) {
+ void CopyHeaders(SpdyHeaderBlock& dest, const BalsaHeaders& headers) {
for (BalsaHeaders::const_header_lines_iterator hi =
headers.header_lines_begin();
hi != headers.header_lines_end();
++hi) {
- FlipHeaderBlock::iterator fhi = dest.find(hi->first.as_string());
+ SpdyHeaderBlock::iterator fhi = dest.find(hi->first.as_string());
if (fhi == dest.end()) {
dest[hi->first.as_string()] = hi->second.as_string();
} else {
@@ -1540,7 +1544,7 @@
}
size_t SendSynStreamImpl(uint32 stream_id, const BalsaHeaders& headers) {
- FlipHeaderBlock block;
+ SpdyHeaderBlock block;
block["method"] = headers.request_method().as_string();
if (!headers.HasHeader("status"))
block["status"] = headers.response_code().as_string();
@@ -1554,10 +1558,11 @@
}
CopyHeaders(block, headers);
- FlipSynStreamControlFrame* fsrcf =
- framer_->CreateSynStream(stream_id, 0, CONTROL_FLAG_NONE, true, &block);
+ SpdySynStreamControlFrame* fsrcf =
+ framer_->CreateSynStream(stream_id, 0, 0, CONTROL_FLAG_NONE, true,
+ &block);
DataFrame df;
- df.size = fsrcf->length() + FlipFrame::size();
+ df.size = fsrcf->length() + SpdyFrame::size();
size_t df_size = df.size;
df.data = fsrcf->data();
df.delete_when_done = true;
@@ -1568,16 +1573,16 @@
}
size_t SendSynReplyImpl(uint32 stream_id, const BalsaHeaders& headers) {
- FlipHeaderBlock block;
+ SpdyHeaderBlock block;
CopyHeaders(block, headers);
block["status"] = headers.response_code().as_string() + " " +
headers.response_reason_phrase().as_string();
block["version"] = headers.response_version().as_string();
- FlipSynReplyControlFrame* fsrcf =
+ SpdySynReplyControlFrame* fsrcf =
framer_->CreateSynReply(stream_id, CONTROL_FLAG_NONE, true, &block);
DataFrame df;
- df.size = fsrcf->length() + FlipFrame::size();
+ df.size = fsrcf->length() + SpdyFrame::size();
size_t df_size = df.size;
df.data = fsrcf->data();
df.delete_when_done = true;
@@ -1588,18 +1593,18 @@
}
void SendDataFrameImpl(uint32 stream_id, const char* data, int64 len,
- FlipDataFlags flags, bool compress) {
+ SpdyDataFlags flags, bool compress) {
// Force compression off if disabled via command line.
if (!FLAGS_use_compression)
- flags = static_cast<FlipDataFlags>(flags & ~DATA_FLAG_COMPRESSED);
+ flags = static_cast<SpdyDataFlags>(flags & ~DATA_FLAG_COMPRESSED);
// TODO(mbelshe): We can't compress here - before going into the
// priority queue. Compression needs to be done
// with late binding.
- FlipDataFrame* fdf = framer_->CreateDataFrame(stream_id, data, len,
+ SpdyDataFrame* fdf = framer_->CreateDataFrame(stream_id, data, len,
flags);
DataFrame df;
- df.size = fdf->length() + FlipFrame::size();
+ df.size = fdf->length() + SpdyFrame::size();
df.data = fdf->data();
df.delete_when_done = true;
EnqueueDataFrame(df);
@@ -1710,28 +1715,13 @@
virtual void ProcessTrailerInput(const char *input, size_t size) {}
virtual void ProcessHeaders(const BalsaHeaders& headers) {
VLOG(2) << "Got new request!";
- // requests started with /testing are loadtime measurement related
- // urls, use LoadtimeMeasurement class to handle them.
- if (headers.request_uri().as_string().find("/testing") == 0) {
- string output;
- global_loadtime_measurement.ProcessRequest(
- headers.request_uri().as_string(), output);
- SendOKResponse(stream_id_, &output);
- stream_id_ += 2;
- } else {
- string filename;
- if (FLAGS_need_to_encode_url) {
- filename = net::UrlToFilenameEncoder::Encode(
- headers.GetHeader("Host").as_string() +
- headers.request_uri().as_string(),
- headers.request_method().as_string() + "_/");
- } else {
- filename = headers.request_method().as_string() + "_" +
- headers.request_uri().as_string();
- }
- NewStream(stream_id_, 0, filename);
- stream_id_ += 2;
- }
+ string host = UrlUtilities::GetUrlHost(
+ headers.GetHeader("Host").as_string());
+ string method = headers.request_method().as_string();
+ string filename = EncodeURL(headers.request_uri().as_string(), host,
+ method);
+ NewStream(stream_id_, 0, filename);
+ stream_id_ += 2;
}
virtual void ProcessRequestFirstLine(const char* line_input,
size_t line_length,
@@ -1843,7 +1833,7 @@
SendDataFrameImpl(stream_id, data, len, flags, compress);
}
- BalsaFrame* flip_framer() { return framer_; }
+ BalsaFrame* spdy_framer() { return framer_; }
private:
void SendEOFImpl(uint32 stream_id) {
@@ -1858,7 +1848,7 @@
BalsaHeaders my_headers;
my_headers.SetFirstlineFromStringPieces("HTTP/1.1", "404", "Not Found");
my_headers.RemoveAllOfHeader("content-length");
- my_headers.HackHeader("transfer-encoding", "chunked");
+ my_headers.AppendHeader("transfer-encoding", "chunked");
SendSynReplyImpl(stream_id, my_headers);
SendDataFrame(stream_id, "wtf?", 4, 0, false);
SendEOFImpl(stream_id);
@@ -1869,7 +1859,7 @@
BalsaHeaders my_headers;
my_headers.SetFirstlineFromStringPieces("HTTP/1.1", "200", "OK");
my_headers.RemoveAllOfHeader("content-length");
- my_headers.HackHeader("transfer-encoding", "chunked");
+ my_headers.AppendHeader("transfer-encoding", "chunked");
SendSynReplyImpl(stream_id, my_headers);
SendDataFrame(stream_id, output->c_str(), output->size(), 0, false);
SendEOFImpl(stream_id);
@@ -2110,15 +2100,14 @@
// SMServerConnections will use this:
virtual void SMServerConnectionDone(SMServerConnection* sc) {
VLOG(3) << "Done with server connection: " << sc;
- sc->close_record_fd();
tmp_unused_server_connections_.push_back(sc);
}
};
////////////////////////////////////////////////////////////////////////////////
-SMInterface* NewFlipSM(SMServerConnection* connection) {
- return new FlipSM(connection);
+SMInterface* NewSpdySM(SMServerConnection* connection) {
+ return new SpdySM(connection);
}
SMInterface* NewHTTPSM(SMServerConnection* connection) {
@@ -2148,7 +2137,7 @@
int on = 1;
int rc;
rc = setsockopt(listening_socket, IPPROTO_TCP, TCP_NODELAY,
- reinterpret_cast<char *>(&on), sizeof(on));
+ reinterpret_cast<char*>(&on), sizeof(on));
if (rc < 0) {
close(listening_socket);
LOG(FATAL) << "setsockopt() failed fd=" << listening_socket << "\n";
@@ -2189,7 +2178,7 @@
int main(int argc, char**argv) {
bool use_ssl = FLAGS_use_ssl;
int response_count_until_close = FLAGS_response_count_until_close;
- int flip_port = FLAGS_flip_port;
+ int spdy_port = FLAGS_spdy_port;
int port = FLAGS_port;
int backlog_size = FLAGS_accept_backlog_size;
bool reuseport = FLAGS_reuseport;
@@ -2198,18 +2187,19 @@
int accepts_per_wake = FLAGS_accepts_per_wake;
int num_threads = 1;
- MemoryCache flip_memory_cache;
- flip_memory_cache.AddFiles();
+
+ MemoryCache spdy_memory_cache;
+ spdy_memory_cache.AddFiles();
MemoryCache http_memory_cache;
- http_memory_cache.CloneFrom(flip_memory_cache);
+ http_memory_cache.CloneFrom(spdy_memory_cache);
LOG(INFO) <<
"Starting up with the following state: \n"
" use_ssl: " << use_ssl << "\n"
" response_count_until_close: " << response_count_until_close << "\n"
" port: " << port << "\n"
- " flip_port: " << flip_port << "\n"
+ " spdy_port: " << spdy_port << "\n"
" backlog_size: " << backlog_size << "\n"
" reuseport: " << BoolToStr(reuseport) << "\n"
" no_nagle: " << BoolToStr(no_nagle) << "\n"
@@ -2221,7 +2211,7 @@
if (use_ssl) {
global_ssl_state = new GlobalSSLState;
- flip_init_ssl(global_ssl_state);
+ spdy_init_ssl(global_ssl_state);
} else {
global_ssl_state = NULL;
}
@@ -2229,31 +2219,31 @@
vector<SMAcceptorThread*> sm_worker_threads_;
{
- // flip
+ // spdy
int listen_fd = -1;
if (reuseport || listen_fd == -1) {
- listen_fd = CreateListeningSocket(flip_port, backlog_size,
+ listen_fd = CreateListeningSocket(spdy_port, backlog_size,
reuseport, no_nagle);
if (listen_fd < 0) {
- LOG(FATAL) << "Unable to open listening socket on flip_port: "
- << flip_port;
+ LOG(FATAL) << "Unable to open listening socket on spdy_port: "
+ << spdy_port;
} else {
- LOG(INFO) << "Listening for flip on port: " << flip_port;
+ LOG(INFO) << "Listening for spdy on port: " << spdy_port;
}
}
sm_worker_threads_.push_back(
new SMAcceptorThread(listen_fd,
accepts_per_wake,
- &NewFlipSM,
- &flip_memory_cache));
- // Note that flip_memory_cache is not threadsafe, it is merely
+ &NewSpdySM,
+ &spdy_memory_cache));
+ // Note that spdy_memory_cache is not threadsafe, it is merely
// thread compatible. Thus, if ever we are to spawn multiple threads,
// we either must make the MemoryCache threadsafe, or use
// a separate MemoryCache for each thread.
//
// The latter is what is currently being done as we spawn
- // two threads (one for flip, one for http).
+ // two threads (one for spdy, one for http).
sm_worker_threads_.back()->InitWorker();
sm_worker_threads_.back()->Start();
}
@@ -2275,13 +2265,13 @@
accepts_per_wake,
&NewHTTPSM,
&http_memory_cache));
- // Note that flip_memory_cache is not threadsafe, it is merely
+ // Note that spdy_memory_cache is not threadsafe, it is merely
// thread compatible. Thus, if ever we are to spawn multiple threads,
// we either must make the MemoryCache threadsafe, or use
// a separate MemoryCache for each thread.
//
// The latter is what is currently being done as we spawn
- // two threads (one for flip, one for http).
+ // two threads (one for spdy, one for http).
sm_worker_threads_.back()->InitWorker();
sm_worker_threads_.back()->Start();
}
diff --git a/net/tools/flip_server/other_defines.h b/net/tools/flip_server/other_defines.h
index 05545e1..ed824e4 100644
--- a/net/tools/flip_server/other_defines.h
+++ b/net/tools/flip_server/other_defines.h
@@ -1,13 +1,6 @@
#ifndef NET_TOOLS_FLIP_SERVER_OTHER_DEFINES
#define NET_TOOLS_FLIP_SERVER_OTHER_DEFINES
-#define CHECK_EQ(X, Y) CHECK((X) == (Y))
-#define CHECK_NE(X, Y) CHECK((X) != (Y))
-#define CHECK_GE(X, Y) CHECK((X) >= (Y))
-#define CHECK_GT(X, Y) CHECK((X) > (Y))
-#define CHECK_LE(X, Y) CHECK((X) <= (Y))
-#define CHECK_LT(X, Y) CHECK((X) < (Y))
-
class NullStream {
public:
NullStream() {}
diff --git a/net/tools/hresolv/hresolv.cc b/net/tools/hresolv/hresolv.cc
index f092703..d524bd7 100644
--- a/net/tools/hresolv/hresolv.cc
+++ b/net/tools/hresolv/hresolv.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -27,6 +27,7 @@
#include "base/condition_variable.h"
#include "base/file_path.h"
#include "base/file_util.h"
+#include "base/message_loop.h"
#include "base/string_util.h"
#include "base/thread.h"
#include "base/time.h"
@@ -50,7 +51,7 @@
{AI_V4MAPPED, "AI_V4MAPPED"},
{AI_ALL, "AI_ALL"},
{AI_ADDRCONFIG, "AI_ADDRCONFIG"},
-#if defined(OS_LINUX) || defined(OS_WIN)
+#if !defined(OS_MACOSX)
{AI_NUMERICSERV, "AI_NUMERICSERV"},
#endif
};
@@ -185,7 +186,7 @@
invoker_(invoker),
ALLOW_THIS_IN_INITIALIZER_LIST(
io_callback_(this, &DelayedResolve::OnResolveComplete)) {
- }
+ }
void Start() {
net::CompletionCallback* callback = (is_async_) ? &io_callback_ : NULL;
@@ -194,7 +195,7 @@
&address_list_,
callback,
NULL,
- NULL);
+ net::BoundNetLog());
if (rv != net::ERR_IO_PENDING) {
OnResolveComplete(rv);
}
@@ -353,14 +354,18 @@
}
bool ReadHostsAndTimesFromLooseValues(
- const std::vector<std::wstring>& loose_args,
+ const std::vector<CommandLine::StringType>& args,
std::vector<HostAndTime>* hosts_and_times) {
- std::vector<std::wstring>::const_iterator loose_args_end = loose_args.end();
- for (std::vector<std::wstring>::const_iterator it = loose_args.begin();
- it != loose_args_end;
+ for (std::vector<CommandLine::StringType>::const_iterator it =
+ args.begin();
+ it != args.end();
++it) {
// TODO(cbentzel): Read time offset.
+#if defined(OS_WIN)
HostAndTime host_and_time = {WideToASCII(*it), 0};
+#else
+ HostAndTime host_and_time = {*it, 0};
+#endif
hosts_and_times->push_back(host_and_time);
}
return true;
@@ -430,7 +435,7 @@
// file into memory.
std::vector<HostAndTime> hosts_and_times;
if (options.input_path.empty()) {
- if (!ReadHostsAndTimesFromLooseValues(command_line->GetLooseValues(),
+ if (!ReadHostsAndTimesFromLooseValues(command_line->args(),
&hosts_and_times)) {
exit(1);
}
@@ -446,7 +451,7 @@
base::TimeDelta::FromSeconds(0));
scoped_refptr<net::HostResolver> host_resolver(
- new net::HostResolverImpl(NULL, cache, NULL, 100u));
+ new net::HostResolverImpl(NULL, cache, 100u));
ResolverInvoker invoker(host_resolver.get());
invoker.ResolveAll(hosts_and_times, options.async);
diff --git a/net/tools/spdyshark/AUTHORS b/net/tools/spdyshark/AUTHORS
new file mode 100644
index 0000000..5643b20
--- /dev/null
+++ b/net/tools/spdyshark/AUTHORS
@@ -0,0 +1,2 @@
+Author:
+Eric Shienbrood <ers@google.com>
diff --git a/net/tools/spdyshark/COPYING b/net/tools/spdyshark/COPYING
new file mode 100644
index 0000000..d60c31a
--- /dev/null
+++ b/net/tools/spdyshark/COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/net/tools/spdyshark/ChangeLog b/net/tools/spdyshark/ChangeLog
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/net/tools/spdyshark/ChangeLog
diff --git a/net/tools/spdyshark/INSTALL b/net/tools/spdyshark/INSTALL
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/net/tools/spdyshark/INSTALL
diff --git a/net/tools/spdyshark/Makefile.am b/net/tools/spdyshark/Makefile.am
new file mode 100644
index 0000000..b707bae
--- /dev/null
+++ b/net/tools/spdyshark/Makefile.am
@@ -0,0 +1,126 @@
+# Makefile.am
+# Automake file for SPDY plugin
+#
+# $Id$
+#
+# Wireshark - Network traffic analyzer
+# By Gerald Combs <gerald@wireshark.org>
+# Copyright 1998 Gerald Combs
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+
+INCLUDES = -I$(top_srcdir) -I$(includedir)
+
+include Makefile.common
+
+if HAVE_WARNINGS_AS_ERRORS
+AM_CFLAGS = -Werror
+endif
+
+plugin_LTLIBRARIES = spdy.la
+spdy_la_SOURCES = \
+ plugin.c \
+ moduleinfo.h \
+ $(DISSECTOR_SRC) \
+ $(DISSECTOR_SUPPORT_SRC) \
+ $(DISSECTOR_INCLUDES)
+spdy_la_LDFLAGS = -module -avoid-version
+spdy_la_LIBADD = @PLUGIN_LIBS@
+
+# Libs must be cleared, or else libtool won't create a shared module.
+# If your module needs to be linked against any particular libraries,
+# add them here.
+LIBS =
+
+#
+# Build plugin.c, which contains the plugin version[] string, a
+# function plugin_register() that calls the register routines for all
+# protocols, and a function plugin_reg_handoff() that calls the handoff
+# registration routines for all protocols.
+#
+# We do this by scanning sources. If that turns out to be too slow,
+# maybe we could just require every .o file to have an register routine
+# of a given name (packet-aarp.o -> proto_register_aarp, etc.).
+#
+# Formatting conventions: The name of the proto_register_* routines an
+# proto_reg_handoff_* routines must start in column zero, or must be
+# preceded only by "void " starting in column zero, and must not be
+# inside #if.
+#
+# DISSECTOR_SRC is assumed to have all the files that need to be scanned.
+#
+# For some unknown reason, having a big "for" loop in the Makefile
+# to scan all the files doesn't work with some "make"s; they seem to
+# pass only the first few names in the list to the shell, for some
+# reason.
+#
+# Therefore, we have a script to generate the plugin.c file.
+# The shell script runs slowly, as multiple greps and seds are run
+# for each input file; this is especially slow on Windows. Therefore,
+# if Python is present (as indicated by PYTHON being defined), we run
+# a faster Python script to do that work instead.
+#
+# The first argument is the directory in which the source files live.
+# The second argument is "plugin", to indicate that we should build
+# a plugin.c file for a plugin.
+# All subsequent arguments are the files to scan.
+#
+plugin.c: $(DISSECTOR_SRC) $(top_srcdir)/tools/make-dissector-reg \
+ $(top_srcdir)/tools/make-dissector-reg.py
+ @if test -n "$(PYTHON)"; then \
+ echo Making plugin.c with python ; \
+ $(PYTHON) $(top_srcdir)/tools/make-dissector-reg.py $(srcdir) \
+ plugin $(DISSECTOR_SRC) ; \
+ else \
+ echo Making plugin.c with shell script ; \
+ $(top_srcdir)/tools/make-dissector-reg $(srcdir) \
+ $(plugin_src) plugin $(DISSECTOR_SRC) ; \
+ fi
+
+#
+# Currently plugin.c can be included in the distribution because
+# we always build all protocol dissectors. We used to have to check
+# whether or not to build the snmp dissector. If we again need to
+# variably build something, making plugin.c non-portable, uncomment
+# the dist-hook line below.
+#
+# Oh, yuk. We don't want to include "plugin.c" in the distribution, as
+# its contents depend on the configuration, and therefore we want it
+# to be built when the first "make" is done; however, Automake insists
+# on putting *all* source into the distribution.
+#
+# We work around this by having a "dist-hook" rule that deletes
+# "plugin.c", so that "dist" won't pick it up.
+#
+#dist-hook:
+# @rm -f $(distdir)/plugin.c
+
+CLEANFILES = \
+ spdy \
+ *~
+
+MAINTAINERCLEANFILES = \
+ Makefile.in \
+ plugin.c
+
+EXTRA_DIST = \
+ Makefile.common \
+ Makefile.nmake \
+ moduleinfo.nmake \
+ plugin.rc.in
+
+checkapi:
+ $(PERL) $(top_srcdir)/tools/checkAPIs.pl -g abort -g termoutput $(DISSECTOR_SRC)
diff --git a/net/tools/spdyshark/Makefile.common b/net/tools/spdyshark/Makefile.common
new file mode 100644
index 0000000..9386f46
--- /dev/null
+++ b/net/tools/spdyshark/Makefile.common
@@ -0,0 +1,40 @@
+# Makefile.common for SPDY plugin
+# Contains the stuff from Makefile.am and Makefile.nmake that is
+# a) common to both files and
+# b) portable between both files
+#
+# $Id$
+#
+# Wireshark - Network traffic analyzer
+# By Gerald Combs <gerald@wireshark.org>
+# Copyright 1998 Gerald Combs
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+# the name of the plugin
+PLUGIN_NAME = spdy
+
+# the dissector sources (without any helpers)
+DISSECTOR_SRC = \
+ packet-spdy.c
+
+# corresponding headers
+DISSECTOR_INCLUDES = \
+ packet-spdy.h
+
+# Dissector helpers. They're included in the source files in this
+# directory, but they're not dissectors themselves, i.e. they're not
+# used to generate "register.c").
+#DISSECTOR_SUPPORT_SRC =
diff --git a/net/tools/spdyshark/Makefile.nmake b/net/tools/spdyshark/Makefile.nmake
new file mode 100644
index 0000000..d554918
--- /dev/null
+++ b/net/tools/spdyshark/Makefile.nmake
@@ -0,0 +1,104 @@
+# Makefile.nmake
+# nmake file for Wireshark plugin
+#
+# $Id: Makefile.nmake 27579 2009-03-02 18:57:35Z gerald $
+#
+
+include ..\..\config.nmake
+include moduleinfo.nmake
+
+include Makefile.common
+
+CFLAGS=/WX /Zi /DHAVE_CONFIG_H /I../.. $(GLIB_CFLAGS) $(ZLIB_CFLAGS) \
+ /I$(PCAP_DIR)\include -D_U_="" $(LOCAL_CFLAGS)
+
+.c.obj::
+ $(CC) $(CFLAGS) -Fd.\ -c $<
+
+LDFLAGS = $(PLUGIN_LDFLAGS)
+
+!IFDEF ENABLE_LIBWIRESHARK
+LINK_PLUGIN_WITH=..\..\epan\libwireshark.lib $(ZLIB_LIBS)
+CFLAGS=/DHAVE_WIN32_LIBWIRESHARK_LIB /D_NEED_VAR_IMPORT_ $(CFLAGS)
+
+DISSECTOR_OBJECTS = $(DISSECTOR_SRC:.c=.obj)
+
+DISSECTOR_SUPPORT_OBJECTS = $(DISSECTOR_SUPPORT_SRC:.c=.obj)
+
+OBJECTS = $(DISSECTOR_OBJECTS) $(DISSECTOR_SUPPORT_OBJECTS) plugin.obj
+
+RESOURCE=$(PLUGIN_NAME).res
+
+all: $(PLUGIN_NAME).dll
+
+$(PLUGIN_NAME).rc : moduleinfo.nmake
+ sed -e s/@PLUGIN_NAME@/$(PLUGIN_NAME)/ \
+ -e s/@RC_MODULE_VERSION@/$(RC_MODULE_VERSION)/ \
+ -e s/@RC_VERSION@/$(RC_VERSION)/ \
+ -e s/@MODULE_VERSION@/$(MODULE_VERSION)/ \
+ -e s/@PACKAGE@/$(PACKAGE)/ \
+ -e s/@VERSION@/$(VERSION)/ \
+ -e s/@MSVC_VARIANT@/$(MSVC_VARIANT)/ \
+ < plugin.rc.in > $@
+
+$(PLUGIN_NAME).dll $(PLUGIN_NAME).exp $(PLUGIN_NAME).lib : $(OBJECTS) $(LINK_PLUGIN_WITH) $(RESOURCE)
+ link -dll /out:$(PLUGIN_NAME).dll $(LDFLAGS) $(OBJECTS) $(LINK_PLUGIN_WITH) \
+ $(GLIB_LIBS) $(RESOURCE)
+
+#
+# Build plugin.c, which contains the plugin version[] string, a
+# function plugin_register() that calls the register routines for all
+# protocols, and a function plugin_reg_handoff() that calls the handoff
+# registration routines for all protocols.
+#
+# We do this by scanning sources. If that turns out to be too slow,
+# maybe we could just require every .o file to have an register routine
+# of a given name (packet-aarp.o -> proto_register_aarp, etc.).
+#
+# Formatting conventions: The name of the proto_register_* routines an
+# proto_reg_handoff_* routines must start in column zero, or must be
+# preceded only by "void " starting in column zero, and must not be
+# inside #if.
+#
+# DISSECTOR_SRC is assumed to have all the files that need to be scanned.
+#
+# For some unknown reason, having a big "for" loop in the Makefile
+# to scan all the files doesn't work with some "make"s; they seem to
+# pass only the first few names in the list to the shell, for some
+# reason.
+#
+# Therefore, we have a script to generate the plugin.c file.
+# The shell script runs slowly, as multiple greps and seds are run
+# for each input file; this is especially slow on Windows. Therefore,
+# if Python is present (as indicated by PYTHON being defined), we run
+# a faster Python script to do that work instead.
+#
+# The first argument is the directory in which the source files live.
+# The second argument is "plugin", to indicate that we should build
+# a plugin.c file for a plugin.
+# All subsequent arguments are the files to scan.
+#
+!IFDEF PYTHON
+plugin.c: $(DISSECTOR_SRC) moduleinfo.h ../../tools/make-dissector-reg.py
+ @echo Making plugin.c (using python)
+ @$(PYTHON) "../../tools/make-dissector-reg.py" . plugin $(DISSECTOR_SRC)
+!ELSE
+plugin.c: $(DISSECTOR_SRC) moduleinfo.h ../../tools/make-dissector-reg
+ @echo Making plugin.c (using sh)
+ @$(SH) ../../tools/make-dissector-reg . plugin $(DISSECTOR_SRC)
+!ENDIF
+
+!ENDIF
+
+clean:
+ rm -f $(OBJECTS) $(RESOURCE) plugin.c *.pdb \
+ $(PLUGIN_NAME).dll $(PLUGIN_NAME).dll.manifest $(PLUGIN_NAME).lib \
+ $(PLUGIN_NAME).exp $(PLUGIN_NAME).rc
+
+distclean: clean
+
+maintainer-clean: distclean
+
+checkapi:
+# TODO: Fix api's :)
+# $(PERL) ../../tools/checkAPIs.pl -g abort -g termoutput $(DISSECTOR_SRC)
diff --git a/net/tools/spdyshark/NEWS b/net/tools/spdyshark/NEWS
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/net/tools/spdyshark/NEWS
diff --git a/net/tools/spdyshark/moduleinfo.h b/net/tools/spdyshark/moduleinfo.h
new file mode 100644
index 0000000..9e5f7c8
--- /dev/null
+++ b/net/tools/spdyshark/moduleinfo.h
@@ -0,0 +1,16 @@
+/* Included *after* config.h, in order to re-define these macros */
+
+#ifdef PACKAGE
+#undef PACKAGE
+#endif
+
+/* Name of package */
+#define PACKAGE "spdy"
+
+
+#ifdef VERSION
+#undef VERSION
+#endif
+
+/* Version number of package */
+#define VERSION "0.1.0"
diff --git a/net/tools/spdyshark/moduleinfo.nmake b/net/tools/spdyshark/moduleinfo.nmake
new file mode 100644
index 0000000..bbdf766
--- /dev/null
+++ b/net/tools/spdyshark/moduleinfo.nmake
@@ -0,0 +1,28 @@
+#
+# $Id$
+#
+
+# The name
+PACKAGE=spdy
+
+# The version
+MODULE_VERSION_MAJOR=0
+MODULE_VERSION_MINOR=1
+MODULE_VERSION_MICRO=0
+MODULE_VERSION_EXTRA=0
+
+#
+# The RC_VERSION should be comma-separated, not dot-separated,
+# as per Graham Bloice's message in
+#
+# http://www.ethereal.com/lists/ethereal-dev/200303/msg00283.html
+#
+# "The RC_VERSION variable in config.nmake should be comma separated.
+# This allows the resources to be built correctly and the version
+# number to be correctly displayed in the explorer properties dialog
+# for the executables, and XP's tooltip, rather than 0.0.0.0."
+#
+
+MODULE_VERSION=$(MODULE_VERSION_MAJOR).$(MODULE_VERSION_MINOR).$(MODULE_VERSION_MICRO).$(MODULE_VERSION_EXTRA)
+RC_MODULE_VERSION=$(MODULE_VERSION_MAJOR),$(MODULE_VERSION_MINOR),$(MODULE_VERSION_MICRO),$(MODULE_VERSION_EXTRA)
+
diff --git a/net/tools/spdyshark/packet-spdy.c b/net/tools/spdyshark/packet-spdy.c
new file mode 100644
index 0000000..becc585
--- /dev/null
+++ b/net/tools/spdyshark/packet-spdy.c
@@ -0,0 +1,1532 @@
+/* packet-spdy.c
+ * Routines for SPDY packet disassembly
+ * For now, the protocol spec can be found at
+ * http://dev.chromium.org/spdy/spdy-protocol
+ *
+ * Copyright 2010, Google Inc.
+ * Eric Shienbrood <ers@google.com>
+ *
+ * $Id$
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * Originally based on packet-http.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+#include <ctype.h>
+
+#include <glib.h>
+#include <epan/conversation.h>
+#include <epan/packet.h>
+#include <epan/strutil.h>
+#include <epan/base64.h>
+#include <epan/emem.h>
+#include <epan/stats_tree.h>
+
+#include <epan/req_resp_hdrs.h>
+#include "packet-spdy.h"
+#include <epan/dissectors/packet-tcp.h>
+#include <epan/dissectors/packet-ssl.h>
+#include <epan/prefs.h>
+#include <epan/expert.h>
+#include <epan/uat.h>
+
+#define SPDY_FIN 0x01
+
+/* The types of SPDY control frames */
+typedef enum _spdy_type {
+ SPDY_DATA,
+ SPDY_SYN_STREAM,
+ SPDY_SYN_REPLY,
+ SPDY_FIN_STREAM,
+ SPDY_HELLO,
+ SPDY_NOOP,
+ SPDY_PING,
+ SPDY_INVALID
+} spdy_frame_type_t;
+
+static const char *frame_type_names[] = {
+ "DATA", "SYN_STREAM", "SYN_REPLY", "FIN_STREAM", "HELLO", "NOOP",
+ "PING", "INVALID"
+};
+
+/*
+ * This structure will be tied to each SPDY frame.
+ * Note that there may be multiple SPDY frames
+ * in one packet.
+ */
+typedef struct _spdy_frame_info_t {
+ guint32 stream_id;
+ guint8 *header_block;
+ guint header_block_len;
+ guint16 frame_type;
+} spdy_frame_info_t;
+
+/*
+ * This structures keeps track of all the data frames
+ * associated with a stream, so that they can be
+ * reassembled into a single chunk.
+ */
+typedef struct _spdy_data_frame_t {
+ guint8 *data;
+ guint32 length;
+ guint32 framenum;
+} spdy_data_frame_t;
+
+typedef struct _spdy_stream_info_t {
+ gchar *content_type;
+ gchar *content_type_parameters;
+ gchar *content_encoding;
+ GSList *data_frames;
+ tvbuff_t *assembled_data;
+ guint num_data_frames;
+} spdy_stream_info_t;
+
+#include <epan/tap.h>
+
+
+static int spdy_tap = -1;
+static int spdy_eo_tap = -1;
+
+static int proto_spdy = -1;
+static int hf_spdy_syn_stream = -1;
+static int hf_spdy_syn_reply = -1;
+static int hf_spdy_control_bit = -1;
+static int hf_spdy_version = -1;
+static int hf_spdy_type = -1;
+static int hf_spdy_flags = -1;
+static int hf_spdy_flags_fin = -1;
+static int hf_spdy_length = -1;
+static int hf_spdy_header = -1;
+static int hf_spdy_header_name = -1;
+static int hf_spdy_header_name_text = -1;
+static int hf_spdy_header_value = -1;
+static int hf_spdy_header_value_text = -1;
+static int hf_spdy_streamid = -1;
+static int hf_spdy_associated_streamid = -1;
+static int hf_spdy_priority = -1;
+static int hf_spdy_num_headers = -1;
+static int hf_spdy_num_headers_string = -1;
+
+static gint ett_spdy = -1;
+static gint ett_spdy_syn_stream = -1;
+static gint ett_spdy_syn_reply = -1;
+static gint ett_spdy_fin_stream = -1;
+static gint ett_spdy_flags = -1;
+static gint ett_spdy_header = -1;
+static gint ett_spdy_header_name = -1;
+static gint ett_spdy_header_value = -1;
+
+static gint ett_spdy_encoded_entity = -1;
+
+static dissector_handle_t data_handle;
+static dissector_handle_t media_handle;
+static dissector_handle_t spdy_handle;
+
+/* Stuff for generation/handling of fields for custom HTTP headers */
+typedef struct _header_field_t {
+ gchar* header_name;
+ gchar* header_desc;
+} header_field_t;
+
+/*
+ * desegmentation of SPDY control frames
+ * (when we are over TCP or another protocol providing the desegmentation API)
+ */
+static gboolean spdy_desegment_control_frames = TRUE;
+
+/*
+ * desegmentation of SPDY data frames bodies
+ * (when we are over TCP or another protocol providing the desegmentation API)
+ * TODO let the user filter on content-type the bodies he wants desegmented
+ */
+static gboolean spdy_desegment_data_frames = TRUE;
+
+static gboolean spdy_assemble_entity_bodies = TRUE;
+
+/*
+ * Decompression of zlib encoded entities.
+ */
+#ifdef HAVE_LIBZ
+static gboolean spdy_decompress_body = TRUE;
+static gboolean spdy_decompress_headers = TRUE;
+#else
+static gboolean spdy_decompress_body = FALSE;
+static gboolean spdy_decompress_headers = FALSE;
+#endif
+static gboolean spdy_debug = FALSE;
+
+#define TCP_PORT_DAAP 3689
+
+/*
+ * SSDP is implemented atop HTTP (yes, it really *does* run over UDP).
+ */
+#define TCP_PORT_SSDP 1900
+#define UDP_PORT_SSDP 1900
+
+/*
+ * tcp and ssl ports
+ */
+
+#define TCP_DEFAULT_RANGE "80,8080"
+#define SSL_DEFAULT_RANGE "443"
+
+static range_t *global_spdy_tcp_range = NULL;
+static range_t *global_spdy_ssl_range = NULL;
+
+static range_t *spdy_tcp_range = NULL;
+static range_t *spdy_ssl_range = NULL;
+
+static const value_string vals_status_code[] = {
+ { 100, "Continue" },
+ { 101, "Switching Protocols" },
+ { 102, "Processing" },
+ { 199, "Informational - Others" },
+
+ { 200, "OK"},
+ { 201, "Created"},
+ { 202, "Accepted"},
+ { 203, "Non-authoritative Information"},
+ { 204, "No Content"},
+ { 205, "Reset Content"},
+ { 206, "Partial Content"},
+ { 207, "Multi-Status"},
+ { 299, "Success - Others"},
+
+ { 300, "Multiple Choices"},
+ { 301, "Moved Permanently"},
+ { 302, "Found"},
+ { 303, "See Other"},
+ { 304, "Not Modified"},
+ { 305, "Use Proxy"},
+ { 307, "Temporary Redirect"},
+ { 399, "Redirection - Others"},
+
+ { 400, "Bad Request"},
+ { 401, "Unauthorized"},
+ { 402, "Payment Required"},
+ { 403, "Forbidden"},
+ { 404, "Not Found"},
+ { 405, "Method Not Allowed"},
+ { 406, "Not Acceptable"},
+ { 407, "Proxy Authentication Required"},
+ { 408, "Request Time-out"},
+ { 409, "Conflict"},
+ { 410, "Gone"},
+ { 411, "Length Required"},
+ { 412, "Precondition Failed"},
+ { 413, "Request Entity Too Large"},
+ { 414, "Request-URI Too Long"},
+ { 415, "Unsupported Media Type"},
+ { 416, "Requested Range Not Satisfiable"},
+ { 417, "Expectation Failed"},
+ { 418, "I'm a teapot"}, /* RFC 2324 */
+ { 422, "Unprocessable Entity"},
+ { 423, "Locked"},
+ { 424, "Failed Dependency"},
+ { 499, "Client Error - Others"},
+
+ { 500, "Internal Server Error"},
+ { 501, "Not Implemented"},
+ { 502, "Bad Gateway"},
+ { 503, "Service Unavailable"},
+ { 504, "Gateway Time-out"},
+ { 505, "HTTP Version not supported"},
+ { 507, "Insufficient Storage"},
+ { 599, "Server Error - Others"},
+
+ { 0, NULL}
+};
+
+static const char spdy_dictionary[] =
+ "optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-"
+ "languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi"
+ "f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser"
+ "-agent10010120020120220320420520630030130230330430530630740040140240340440"
+ "5406407408409410411412413414415416417500501502503504505accept-rangesageeta"
+ "glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic"
+ "ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran"
+ "sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati"
+ "oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo"
+ "ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe"
+ "pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic"
+ "ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1"
+ ".1statusversionurl";
+
+static void reset_decompressors(void)
+{
+ if (spdy_debug) printf("Should reset SPDY decompressors\n");
+}
+
+static spdy_conv_t *
+get_spdy_conversation_data(packet_info *pinfo)
+{
+ conversation_t *conversation;
+ spdy_conv_t *conv_data;
+ int retcode;
+
+ conversation = find_conversation(pinfo->fd->num, &pinfo->src, &pinfo->dst, pinfo->ptype, pinfo->srcport, pinfo->destport, 0);
+ if (spdy_debug) {
+ printf("\n===========================================\n\n");
+ printf("Conversation for frame #%d is %p\n", pinfo->fd->num, conversation);
+ if (conversation)
+ printf(" conv_data=%p\n", conversation_get_proto_data(conversation, proto_spdy));
+ }
+
+ if(!conversation) /* Conversation does not exist yet - create it */
+ conversation = conversation_new(pinfo->fd->num, &pinfo->src, &pinfo->dst, pinfo->ptype, pinfo->srcport, pinfo->destport, 0);
+
+ /* Retrieve information from conversation
+ */
+ conv_data = conversation_get_proto_data(conversation, proto_spdy);
+ if(!conv_data) {
+ /* Setup the conversation structure itself */
+ conv_data = se_alloc0(sizeof(spdy_conv_t));
+
+ conv_data->streams = NULL;
+ if (spdy_decompress_headers) {
+ conv_data->rqst_decompressor = se_alloc0(sizeof(z_stream));
+ conv_data->rply_decompressor = se_alloc0(sizeof(z_stream));
+ retcode = inflateInit(conv_data->rqst_decompressor);
+ if (retcode == Z_OK)
+ retcode = inflateInit(conv_data->rply_decompressor);
+ if (retcode != Z_OK)
+ printf("frame #%d: inflateInit() failed: %d\n", pinfo->fd->num, retcode);
+ else if (spdy_debug)
+ printf("created decompressor\n");
+ conv_data->dictionary_id = adler32(0L, Z_NULL, 0);
+ conv_data->dictionary_id = adler32(conv_data->dictionary_id,
+ spdy_dictionary,
+ sizeof(spdy_dictionary));
+ }
+
+ conversation_add_proto_data(conversation, proto_spdy, conv_data);
+ register_postseq_cleanup_routine(reset_decompressors);
+ }
+ return conv_data;
+}
+
+static void
+spdy_save_stream_info(spdy_conv_t *conv_data,
+ guint32 stream_id,
+ gchar *content_type,
+ gchar *content_type_params,
+ gchar *content_encoding)
+{
+ spdy_stream_info_t *si;
+
+ if (conv_data->streams == NULL)
+ conv_data->streams = g_array_new(FALSE, TRUE, sizeof(spdy_stream_info_t *));
+ if (stream_id < conv_data->streams->len)
+ DISSECTOR_ASSERT(g_array_index(conv_data->streams, spdy_stream_info_t*, stream_id) == NULL);
+ else
+ g_array_set_size(conv_data->streams, stream_id+1);
+ si = se_alloc(sizeof(spdy_stream_info_t));
+ si->content_type = content_type;
+ si->content_type_parameters = content_type_params;
+ si->content_encoding = content_encoding;
+ si->data_frames = NULL;
+ si->num_data_frames = 0;
+ si->assembled_data = NULL;
+ g_array_index(conv_data->streams, spdy_stream_info_t*, stream_id) = si;
+ if (spdy_debug)
+ printf("Saved stream info for ID %u, content type %s\n", stream_id, content_type);
+}
+
+static spdy_stream_info_t *
+spdy_get_stream_info(spdy_conv_t *conv_data, guint32 stream_id)
+{
+ if (conv_data->streams == NULL || stream_id >= conv_data->streams->len)
+ return NULL;
+ else
+ return g_array_index(conv_data->streams, spdy_stream_info_t*, stream_id);
+}
+
+static void
+spdy_add_data_chunk(spdy_conv_t *conv_data, guint32 stream_id, guint32 frame,
+ guint8 *data, guint32 length)
+{
+ spdy_stream_info_t *si = spdy_get_stream_info(conv_data, stream_id);
+
+ if (si == NULL) {
+ if (spdy_debug) printf("No stream_info found for stream %d\n", stream_id);
+ } else {
+ spdy_data_frame_t *df = g_malloc(sizeof(spdy_data_frame_t));
+ df->data = data;
+ df->length = length;
+ df->framenum = frame;
+ si->data_frames = g_slist_append(si->data_frames, df);
+ ++si->num_data_frames;
+ if (spdy_debug)
+ printf("Saved %u bytes of data for stream %u frame %u\n",
+ length, stream_id, df->framenum);
+ }
+}
+
+static void
+spdy_increment_data_chunk_count(spdy_conv_t *conv_data, guint32 stream_id)
+{
+ spdy_stream_info_t *si = spdy_get_stream_info(conv_data, stream_id);
+ if (si != NULL)
+ ++si->num_data_frames;
+}
+
+/*
+ * Return the number of data frames saved so far for the specified stream.
+ */
+static guint
+spdy_get_num_data_frames(spdy_conv_t *conv_data, guint32 stream_id)
+{
+ spdy_stream_info_t *si = spdy_get_stream_info(conv_data, stream_id);
+
+ return si == NULL ? 0 : si->num_data_frames;
+}
+
+static spdy_stream_info_t *
+spdy_assemble_data_frames(spdy_conv_t *conv_data, guint32 stream_id)
+{
+ spdy_stream_info_t *si = spdy_get_stream_info(conv_data, stream_id);
+ tvbuff_t *tvb;
+
+ if (si == NULL)
+ return NULL;
+
+ /*
+ * Compute the total amount of data and concatenate the
+ * data chunks, if it hasn't already been done.
+ */
+ if (si->assembled_data == NULL) {
+ spdy_data_frame_t *df;
+ guint8 *data;
+ guint32 datalen;
+ guint32 offset;
+ guint32 framenum;
+ GSList *dflist = si->data_frames;
+ if (dflist == NULL)
+ return si;
+ dflist = si->data_frames;
+ datalen = 0;
+ /*
+ * I'd like to use a composite tvbuff here, but since
+ * only a real-data tvbuff can be the child of another
+ * tvb, I can't. It would be nice if this limitation
+ * could be fixed.
+ */
+ while (dflist != NULL) {
+ df = dflist->data;
+ datalen += df->length;
+ dflist = g_slist_next(dflist);
+ }
+ if (datalen != 0) {
+ data = se_alloc(datalen);
+ dflist = si->data_frames;
+ offset = 0;
+ framenum = 0;
+ while (dflist != NULL) {
+ df = dflist->data;
+ memcpy(data+offset, df->data, df->length);
+ offset += df->length;
+ dflist = g_slist_next(dflist);
+ }
+ tvb = tvb_new_real_data(data, datalen, datalen);
+ si->assembled_data = tvb;
+ }
+ }
+ return si;
+}
+
+static void
+spdy_discard_data_frames(spdy_stream_info_t *si)
+{
+ GSList *dflist = si->data_frames;
+ spdy_data_frame_t *df;
+
+ if (dflist == NULL)
+ return;
+ while (dflist != NULL) {
+ df = dflist->data;
+ if (df->data != NULL) {
+ g_free(df->data);
+ df->data = NULL;
+ }
+ dflist = g_slist_next(dflist);
+ }
+ /*g_slist_free(si->data_frames);
+ si->data_frames = NULL; */
+}
+
+// TODO(cbentzel): tvb_child_uncompress should be exported by wireshark.
+static tvbuff_t* spdy_tvb_child_uncompress(tvbuff_t *parent _U_, tvbuff_t *tvb,
+ int offset, int comprlen)
+{
+ tvbuff_t *new_tvb = tvb_uncompress(tvb, offset, comprlen);
+ if (new_tvb)
+ tvb_set_child_real_data_tvbuff (parent, new_tvb);
+ return new_tvb;
+}
+
+static int
+dissect_spdy_data_frame(tvbuff_t *tvb, int offset,
+ packet_info *pinfo,
+ proto_tree *top_level_tree,
+ proto_tree *spdy_tree,
+ proto_item *spdy_proto,
+ spdy_conv_t *conv_data)
+{
+ guint32 stream_id;
+ guint8 flags;
+ guint32 frame_length;
+ proto_item *ti;
+ proto_tree *flags_tree;
+ guint32 reported_datalen;
+ guint32 datalen;
+ dissector_table_t media_type_subdissector_table;
+ dissector_table_t port_subdissector_table;
+ dissector_handle_t handle;
+ guint num_data_frames;
+ gboolean dissected;
+
+ stream_id = tvb_get_bits32(tvb, (offset << 3) + 1, 31, FALSE);
+ flags = tvb_get_guint8(tvb, offset+4);
+ frame_length = tvb_get_ntoh24(tvb, offset+5);
+
+ if (spdy_debug)
+ printf("Data frame [stream_id=%u flags=0x%x length=%d]\n",
+ stream_id, flags, frame_length);
+ if (spdy_tree) proto_item_append_text(spdy_tree, ", data frame");
+ col_add_fstr(pinfo->cinfo, COL_INFO, "DATA[%u] length=%d",
+ stream_id, frame_length);
+
+ proto_item_append_text(spdy_proto, ":%s stream=%d length=%d",
+ flags & SPDY_FIN ? " [FIN]" : "",
+ stream_id, frame_length);
+
+ proto_tree_add_boolean(spdy_tree, hf_spdy_control_bit, tvb, offset, 1, 0);
+ proto_tree_add_uint(spdy_tree, hf_spdy_streamid, tvb, offset, 4, stream_id);
+ ti = proto_tree_add_uint_format(spdy_tree, hf_spdy_flags, tvb, offset+4, 1, flags,
+ "Flags: 0x%02x%s", flags, flags&SPDY_FIN ? " (FIN)" : "");
+
+ flags_tree = proto_item_add_subtree(ti, ett_spdy_flags);
+ proto_tree_add_boolean(flags_tree, hf_spdy_flags_fin, tvb, offset+4, 1, flags);
+ proto_tree_add_uint(spdy_tree, hf_spdy_length, tvb, offset+5, 3, frame_length);
+
+ datalen = tvb_length_remaining(tvb, offset);
+ if (datalen > frame_length)
+ datalen = frame_length;
+
+ reported_datalen = tvb_reported_length_remaining(tvb, offset);
+ if (reported_datalen > frame_length)
+ reported_datalen = frame_length;
+
+ num_data_frames = spdy_get_num_data_frames(conv_data, stream_id);
+ if (datalen != 0 || num_data_frames != 0) {
+ /*
+ * There's stuff left over; process it.
+ */
+ tvbuff_t *next_tvb = NULL;
+ tvbuff_t *data_tvb = NULL;
+ spdy_stream_info_t *si = NULL;
+ void *save_private_data = NULL;
+ guint8 *copied_data;
+ gboolean private_data_changed = FALSE;
+ gboolean is_single_chunk = FALSE;
+ gboolean have_entire_body;
+
+ /*
+ * Create a tvbuff for the payload.
+ */
+ if (datalen != 0) {
+ next_tvb = tvb_new_subset(tvb, offset+8, datalen,
+ reported_datalen);
+ is_single_chunk = num_data_frames == 0 && (flags & SPDY_FIN) != 0;
+ if (!pinfo->fd->flags.visited) {
+ if (!is_single_chunk) {
+ if (spdy_assemble_entity_bodies) {
+ copied_data = tvb_memdup(next_tvb, 0, datalen);
+ spdy_add_data_chunk(conv_data, stream_id, pinfo->fd->num,
+ copied_data, datalen);
+ } else
+ spdy_increment_data_chunk_count(conv_data, stream_id);
+ }
+ }
+ } else
+ is_single_chunk = (num_data_frames == 1);
+
+ if (!(flags & SPDY_FIN)) {
+ col_set_fence(pinfo->cinfo, COL_INFO);
+ col_add_fstr(pinfo->cinfo, COL_INFO, " (partial entity)");
+ proto_item_append_text(spdy_proto, " (partial entity body)");
+ /* would like the proto item to say */
+ /* " (entity body fragment N of M)" */
+ goto body_dissected;
+ }
+ have_entire_body = is_single_chunk;
+ /*
+ * On seeing the last data frame in a stream, we can
+ * reassemble the frames into one data block.
+ */
+ si = spdy_assemble_data_frames(conv_data, stream_id);
+ if (si == NULL)
+ goto body_dissected;
+ data_tvb = si->assembled_data;
+ if (spdy_assemble_entity_bodies)
+ have_entire_body = TRUE;
+
+ if (!have_entire_body)
+ goto body_dissected;
+
+ if (data_tvb == NULL)
+ data_tvb = next_tvb;
+ else
+ add_new_data_source(pinfo, data_tvb, "Assembled entity body");
+
+ if (have_entire_body && si->content_encoding != NULL &&
+ g_ascii_strcasecmp(si->content_encoding, "identity") != 0) {
+ /*
+ * We currently can't handle, for example, "compress";
+ * just handle them as data for now.
+ *
+ * After July 7, 2004 the LZW patent expires, so support
+ * might be added then. However, I don't think that
+ * anybody ever really implemented "compress", due to
+ * the aforementioned patent.
+ */
+ tvbuff_t *uncomp_tvb = NULL;
+ proto_item *e_ti = NULL;
+ proto_item *ce_ti = NULL;
+ proto_tree *e_tree = NULL;
+
+ if (spdy_decompress_body &&
+ (g_ascii_strcasecmp(si->content_encoding, "gzip") == 0 ||
+ g_ascii_strcasecmp(si->content_encoding, "deflate")
+ == 0)) {
+ uncomp_tvb = spdy_tvb_child_uncompress(tvb, data_tvb, 0,
+ tvb_length(data_tvb));
+ }
+ /*
+ * Add the encoded entity to the protocol tree
+ */
+ e_ti = proto_tree_add_text(top_level_tree, data_tvb,
+ 0, tvb_length(data_tvb),
+ "Content-encoded entity body (%s): %u bytes",
+ si->content_encoding,
+ tvb_length(data_tvb));
+ e_tree = proto_item_add_subtree(e_ti, ett_spdy_encoded_entity);
+ if (si->num_data_frames > 1) {
+ GSList *dflist;
+ spdy_data_frame_t *df;
+ guint32 framenum;
+ ce_ti = proto_tree_add_text(e_tree, data_tvb, 0,
+ tvb_length(data_tvb),
+ "Assembled from %d frames in packet(s)", si->num_data_frames);
+ dflist = si->data_frames;
+ framenum = 0;
+ while (dflist != NULL) {
+ df = dflist->data;
+ if (framenum != df->framenum) {
+ proto_item_append_text(ce_ti, " #%u", df->framenum);
+ framenum = df->framenum;
+ }
+ dflist = g_slist_next(dflist);
+ }
+ }
+
+ if (uncomp_tvb != NULL) {
+ /*
+ * Decompression worked
+ */
+
+ /* XXX - Don't free this, since it's possible
+ * that the data was only partially
+ * decompressed, such as when desegmentation
+ * isn't enabled.
+ *
+ tvb_free(next_tvb);
+ */
+ proto_item_append_text(e_ti, " -> %u bytes", tvb_length(uncomp_tvb));
+ data_tvb = uncomp_tvb;
+ add_new_data_source(pinfo, data_tvb, "Uncompressed entity body");
+ } else {
+ if (spdy_decompress_body)
+ proto_item_append_text(e_ti, " [Error: Decompression failed]");
+ call_dissector(data_handle, data_tvb, pinfo, e_tree);
+
+ goto body_dissected;
+ }
+ }
+ if (si != NULL)
+ spdy_discard_data_frames(si);
+ /*
+ * Do subdissector checks.
+ *
+ * First, check whether some subdissector asked that they
+ * be called if something was on some particular port.
+ */
+
+ port_subdissector_table = find_dissector_table("http.port");
+ media_type_subdissector_table = find_dissector_table("media_type");
+ if (have_entire_body && port_subdissector_table != NULL)
+ handle = dissector_get_port_handle(port_subdissector_table,
+ pinfo->match_port);
+ else
+ handle = NULL;
+ if (handle == NULL && have_entire_body && si->content_type != NULL &&
+ media_type_subdissector_table != NULL) {
+ /*
+ * We didn't find any subdissector that
+ * registered for the port, and we have a
+ * Content-Type value. Is there any subdissector
+ * for that content type?
+ */
+ save_private_data = pinfo->private_data;
+ private_data_changed = TRUE;
+
+ if (si->content_type_parameters)
+ pinfo->private_data = ep_strdup(si->content_type_parameters);
+ else
+ pinfo->private_data = NULL;
+ /*
+ * Calling the string handle for the media type
+ * dissector table will set pinfo->match_string
+ * to si->content_type for us.
+ */
+ pinfo->match_string = si->content_type;
+ handle = dissector_get_string_handle(
+ media_type_subdissector_table,
+ si->content_type);
+ }
+ if (handle != NULL) {
+ /*
+ * We have a subdissector - call it.
+ */
+ dissected = call_dissector(handle, data_tvb, pinfo, top_level_tree);
+ } else
+ dissected = FALSE;
+
+ if (dissected) {
+ /*
+ * The subdissector dissected the body.
+ * Fix up the top-level item so that it doesn't
+ * include the stuff for that protocol.
+ */
+ if (ti != NULL)
+ proto_item_set_len(ti, offset);
+ } else if (have_entire_body && si->content_type != NULL) {
+ /*
+ * Calling the default media handle if there is a content-type that
+ * wasn't handled above.
+ */
+ call_dissector(media_handle, next_tvb, pinfo, top_level_tree);
+ } else {
+ /* Call the default data dissector */
+ call_dissector(data_handle, next_tvb, pinfo, top_level_tree);
+ }
+
+body_dissected:
+ /*
+ * Do *not* attempt at freeing the private data;
+ * it may be in use by subdissectors.
+ */
+ if (private_data_changed) /*restore even NULL value*/
+ pinfo->private_data = save_private_data;
+ /*
+ * We've processed "datalen" bytes worth of data
+ * (which may be no data at all); advance the
+ * offset past whatever data we've processed.
+ */
+ }
+ return frame_length + 8;
+}
+
+static guint8 *
+spdy_decompress_header_block(tvbuff_t *tvb, z_streamp decomp,
+ guint32 dictionary_id, int offset,
+ guint32 length, guint *uncomp_length)
+{
+ int retcode;
+ size_t bufsize = 16384;
+ const guint8 *hptr = tvb_get_ptr(tvb, offset, length);
+ guint8 *uncomp_block = ep_alloc(bufsize);
+ decomp->next_in = (Bytef *)hptr;
+ decomp->avail_in = length;
+ decomp->next_out = uncomp_block;
+ decomp->avail_out = bufsize;
+ retcode = inflate(decomp, Z_SYNC_FLUSH);
+ if (retcode == Z_NEED_DICT) {
+ if (decomp->adler != dictionary_id) {
+ printf("decompressor wants dictionary %#x, but we have %#x\n",
+ (guint)decomp->adler, dictionary_id);
+ } else {
+ retcode = inflateSetDictionary(decomp,
+ spdy_dictionary,
+ sizeof(spdy_dictionary));
+ if (retcode == Z_OK)
+ retcode = inflate(decomp, Z_SYNC_FLUSH);
+ }
+ }
+
+ if (retcode != Z_OK) {
+ return NULL;
+ } else {
+ *uncomp_length = bufsize - decomp->avail_out;
+ if (spdy_debug)
+ printf("Inflation SUCCEEDED. uncompressed size=%d\n", *uncomp_length);
+ if (decomp->avail_in != 0)
+ if (spdy_debug)
+ printf(" but there were %d input bytes left over\n", decomp->avail_in);
+ }
+ return se_memdup(uncomp_block, *uncomp_length);
+}
+
+/*
+ * Try to determine heuristically whether the header block is
+ * compressed. For an uncompressed block, the first two bytes
+ * gives the number of headers. Each header name and value is
+ * a two-byte length followed by ASCII characters.
+ */
+static gboolean
+spdy_check_header_compression(tvbuff_t *tvb,
+ int offset,
+ guint32 frame_length)
+{
+ guint16 length;
+ if (!tvb_bytes_exist(tvb, offset, 6))
+ return 1;
+ length = tvb_get_ntohs(tvb, offset);
+ if (length > frame_length)
+ return 1;
+ length = tvb_get_ntohs(tvb, offset+2);
+ if (length > frame_length)
+ return 1;
+ if (spdy_debug) printf("Looks like the header block is not compressed\n");
+ return 0;
+}
+
+// TODO(cbentzel): Change wireshark to export p_remove_proto_data, rather
+// than duplicating code here.
+typedef struct _spdy_frame_proto_data {
+ int proto;
+ void *proto_data;
+} spdy_frame_proto_data;
+
+static gint spdy_p_compare(gconstpointer a, gconstpointer b)
+{
+ const spdy_frame_proto_data *ap = (const spdy_frame_proto_data *)a;
+ const spdy_frame_proto_data *bp = (const spdy_frame_proto_data *)b;
+
+ if (ap -> proto > bp -> proto)
+ return 1;
+ else if (ap -> proto == bp -> proto)
+ return 0;
+ else
+ return -1;
+
+}
+
+static void spdy_p_remove_proto_data(frame_data *fd, int proto)
+{
+ spdy_frame_proto_data temp;
+ GSList *item;
+
+ temp.proto = proto;
+ temp.proto_data = NULL;
+
+ item = g_slist_find_custom(fd->pfd, (gpointer *)&temp, spdy_p_compare);
+
+ if (item) {
+ fd->pfd = g_slist_remove(fd->pfd, item->data);
+ }
+}
+
+static spdy_frame_info_t *
+spdy_save_header_block(frame_data *fd,
+ guint32 stream_id,
+ guint frame_type,
+ guint8 *header,
+ guint length)
+{
+ GSList *filist = p_get_proto_data(fd, proto_spdy);
+ spdy_frame_info_t *frame_info = se_alloc(sizeof(spdy_frame_info_t));
+ if (filist != NULL)
+ spdy_p_remove_proto_data(fd, proto_spdy);
+ frame_info->stream_id = stream_id;
+ frame_info->header_block = header;
+ frame_info->header_block_len = length;
+ frame_info->frame_type = frame_type;
+ filist = g_slist_append(filist, frame_info);
+ p_add_proto_data(fd, proto_spdy, filist);
+ return frame_info;
+ /* TODO(ers) these need to get deleted when no longer needed */
+}
+
+static spdy_frame_info_t *
+spdy_find_saved_header_block(frame_data *fd,
+ guint32 stream_id,
+ guint16 frame_type)
+{
+ GSList *filist = p_get_proto_data(fd, proto_spdy);
+ while (filist != NULL) {
+ spdy_frame_info_t *fi = filist->data;
+ if (fi->stream_id == stream_id && fi->frame_type == frame_type)
+ return fi;
+ filist = g_slist_next(filist);
+ }
+ return NULL;
+}
+
+/*
+ * Given a content type string that may contain optional parameters,
+ * return the parameter string, if any, otherwise return NULL. This
+ * also has the side effect of null terminating the content type
+ * part of the original string.
+ */
+static gchar *
+spdy_parse_content_type(gchar *content_type)
+{
+ gchar *cp = content_type;
+
+ while (*cp != '\0' && *cp != ';' && !isspace(*cp)) {
+ *cp = tolower(*cp);
+ ++cp;
+ }
+ if (*cp == '\0')
+ cp = NULL;
+
+ if (cp != NULL) {
+ *cp++ = '\0';
+ while (*cp == ';' || isspace(*cp))
+ ++cp;
+ if (*cp != '\0')
+ return cp;
+ }
+ return NULL;
+}
+
+static int
+dissect_spdy_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
+ proto_tree *tree, spdy_conv_t *conv_data)
+{
+ guint8 control_bit;
+ guint16 version;
+ guint16 frame_type;
+ guint8 flags;
+ guint32 frame_length;
+ guint32 stream_id;
+ guint32 associated_stream_id;
+ gint priority;
+ guint16 num_headers;
+ guint32 fin_status;
+ guint8 *frame_header;
+ const char *proto_tag;
+ const char *frame_type_name;
+ proto_tree *spdy_tree = NULL;
+ proto_item *ti = NULL;
+ proto_item *spdy_proto = NULL;
+ int orig_offset;
+ int hoffset;
+ int hdr_offset = 0;
+ spdy_frame_type_t spdy_type;
+ proto_tree *sub_tree;
+ proto_tree *flags_tree;
+ tvbuff_t *header_tvb = NULL;
+ gboolean headers_compressed;
+ gchar *hdr_verb = NULL;
+ gchar *hdr_url = NULL;
+ gchar *hdr_version = NULL;
+ gchar *content_type = NULL;
+ gchar *content_encoding = NULL;
+
+ /*
+ * Minimum size for a SPDY frame is 8 bytes.
+ */
+ if (tvb_reported_length_remaining(tvb, offset) < 8)
+ return -1;
+
+ proto_tag = "SPDY";
+
+ if (check_col(pinfo->cinfo, COL_PROTOCOL))
+ col_set_str(pinfo->cinfo, COL_PROTOCOL, proto_tag);
+
+ /*
+ * Is this a control frame or a data frame?
+ */
+ orig_offset = offset;
+ control_bit = tvb_get_bits8(tvb, offset << 3, 1);
+ if (control_bit) {
+ version = tvb_get_bits16(tvb, (offset << 3) + 1, 15, FALSE);
+ frame_type = tvb_get_ntohs(tvb, offset+2);
+ if (frame_type >= SPDY_INVALID) {
+ return -1;
+ }
+ frame_header = ep_tvb_memdup(tvb, offset, 16);
+ } else {
+ version = 1; /* avoid gcc warning */
+ frame_type = SPDY_DATA;
+ frame_header = NULL; /* avoid gcc warning */
+ }
+ frame_type_name = frame_type_names[frame_type];
+ offset += 4;
+ flags = tvb_get_guint8(tvb, offset);
+ frame_length = tvb_get_ntoh24(tvb, offset+1);
+ offset += 4;
+ /*
+ * Make sure there's as much data as the frame header says there is.
+ */
+ if ((guint)tvb_reported_length_remaining(tvb, offset) < frame_length) {
+ if (spdy_debug)
+ printf("Not enough header data: %d vs. %d\n",
+ frame_length, tvb_reported_length_remaining(tvb, offset));
+ return -1;
+ }
+ if (tree) {
+ spdy_proto = proto_tree_add_item(tree, proto_spdy, tvb, orig_offset, frame_length+8, FALSE);
+ spdy_tree = proto_item_add_subtree(spdy_proto, ett_spdy);
+ }
+
+ if (control_bit) {
+ if (spdy_debug)
+ printf("Control frame [version=%d type=%d flags=0x%x length=%d]\n",
+ version, frame_type, flags, frame_length);
+ if (tree) proto_item_append_text(spdy_tree, ", control frame");
+ } else {
+ return dissect_spdy_data_frame(tvb, orig_offset, pinfo, tree,
+ spdy_tree, spdy_proto, conv_data);
+ }
+ num_headers = 0;
+ sub_tree = NULL; /* avoid gcc warning */
+ switch (frame_type) {
+ case SPDY_SYN_STREAM:
+ case SPDY_SYN_REPLY:
+ if (tree) {
+ int hf;
+ hf = frame_type == SPDY_SYN_STREAM ? hf_spdy_syn_stream : hf_spdy_syn_reply;
+ ti = proto_tree_add_bytes(spdy_tree, hf, tvb,
+ orig_offset, 16, frame_header);
+ sub_tree = proto_item_add_subtree(ti, ett_spdy_syn_stream);
+ }
+ stream_id = tvb_get_bits32(tvb, (offset << 3) + 1, 31, FALSE);
+ offset += 4;
+ if (frame_type == SPDY_SYN_STREAM) {
+ associated_stream_id = tvb_get_bits32(tvb, (offset << 3) + 1, 31, FALSE);
+ offset += 4;
+ priority = tvb_get_bits8(tvb, offset << 3, 2);
+ offset += 2;
+ } else {
+ // The next two bytes have no meaning in SYN_REPLY
+ offset += 2;
+ }
+ if (tree) {
+ proto_tree_add_boolean(sub_tree, hf_spdy_control_bit, tvb, orig_offset, 1, control_bit);
+ proto_tree_add_uint(sub_tree, hf_spdy_version, tvb, orig_offset, 2, version);
+ proto_tree_add_uint(sub_tree, hf_spdy_type, tvb, orig_offset+2, 2, frame_type);
+ ti = proto_tree_add_uint_format(sub_tree, hf_spdy_flags, tvb, orig_offset+4, 1, flags,
+ "Flags: 0x%02x%s", flags, flags&SPDY_FIN ? " (FIN)" : "");
+ flags_tree = proto_item_add_subtree(ti, ett_spdy_flags);
+ proto_tree_add_boolean(flags_tree, hf_spdy_flags_fin, tvb, orig_offset+4, 1, flags);
+ proto_tree_add_uint(sub_tree, hf_spdy_length, tvb, orig_offset+5, 3, frame_length);
+ proto_tree_add_uint(sub_tree, hf_spdy_streamid, tvb, orig_offset+8, 4, stream_id);
+ if (frame_type == SPDY_SYN_STREAM) {
+ proto_tree_add_uint(sub_tree, hf_spdy_associated_streamid, tvb, orig_offset+12, 4, associated_stream_id);
+ proto_tree_add_uint(sub_tree, hf_spdy_priority, tvb, orig_offset+16, 1, priority);
+ }
+ proto_item_append_text(spdy_proto, ": %s%s stream=%d length=%d",
+ frame_type_name,
+ flags & SPDY_FIN ? " [FIN]" : "",
+ stream_id, frame_length);
+ if (spdy_debug)
+ printf(" stream ID=%u priority=%d\n", stream_id, priority);
+ }
+ break;
+
+ case SPDY_FIN_STREAM:
+ stream_id = tvb_get_bits32(tvb, (offset << 3) + 1, 31, FALSE);
+ fin_status = tvb_get_ntohl(tvb, offset);
+ // TODO(ers) fill in tree and summary
+ offset += 8;
+ break;
+
+ case SPDY_HELLO:
+ // TODO(ers) fill in tree and summary
+ stream_id = 0; /* avoid gcc warning */
+ break;
+
+ default:
+ stream_id = 0; /* avoid gcc warning */
+ return -1;
+ break;
+ }
+
+ /*
+ * Process the name-value pairs one at a time, after possibly
+ * decompressing the header block.
+ */
+ if (frame_type == SPDY_SYN_STREAM || frame_type == SPDY_SYN_REPLY) {
+ headers_compressed = spdy_check_header_compression(tvb, offset, frame_length);
+ if (!spdy_decompress_headers || !headers_compressed) {
+ header_tvb = tvb;
+ hdr_offset = offset;
+ } else {
+ spdy_frame_info_t *per_frame_info =
+ spdy_find_saved_header_block(pinfo->fd,
+ stream_id,
+ frame_type == SPDY_SYN_REPLY);
+ if (per_frame_info == NULL) {
+ guint uncomp_length;
+ z_streamp decomp = frame_type == SPDY_SYN_STREAM ?
+ conv_data->rqst_decompressor : conv_data->rply_decompressor;
+ guint8 *uncomp_ptr =
+ spdy_decompress_header_block(tvb, decomp,
+ conv_data->dictionary_id,
+ offset,
+ frame_length + 8 - (offset - orig_offset),
+ &uncomp_length);
+ if (uncomp_ptr == NULL) { /* decompression failed */
+ if (spdy_debug)
+ printf("Frame #%d: Inflation failed\n", pinfo->fd->num);
+ proto_item_append_text(spdy_proto, " [Error: Header decompression failed]");
+ // Should we just bail here?
+ } else {
+ if (spdy_debug)
+ printf("Saving %u bytes of uncomp hdr\n", uncomp_length);
+ per_frame_info =
+ spdy_save_header_block(pinfo->fd, stream_id, frame_type == SPDY_SYN_REPLY,
+ uncomp_ptr, uncomp_length);
+ }
+ } else if (spdy_debug) {
+ printf("Found uncompressed header block len %u for stream %u frame_type=%d\n",
+ per_frame_info->header_block_len,
+ per_frame_info->stream_id,
+ per_frame_info->frame_type);
+ }
+ if (per_frame_info != NULL) {
+ header_tvb = tvb_new_child_real_data(tvb,
+ per_frame_info->header_block,
+ per_frame_info->header_block_len,
+ per_frame_info->header_block_len);
+ add_new_data_source(pinfo, header_tvb, "Uncompressed headers");
+ hdr_offset = 0;
+ }
+ }
+ offset = orig_offset + 8 + frame_length;
+ num_headers = tvb_get_ntohs(header_tvb, hdr_offset);
+ hdr_offset += 2;
+ if (header_tvb == NULL ||
+ (headers_compressed && !spdy_decompress_headers)) {
+ num_headers = 0;
+ ti = proto_tree_add_string(sub_tree, hf_spdy_num_headers_string,
+ tvb,
+ frame_type == SPDY_SYN_STREAM ? orig_offset+18 : orig_offset + 14,
+ 2,
+ "Unknown (header block is compressed)");
+ } else
+ ti = proto_tree_add_uint(sub_tree, hf_spdy_num_headers,
+ tvb,
+ frame_type == SPDY_SYN_STREAM ? orig_offset+18 : orig_offset +14,
+ 2, num_headers);
+ }
+ spdy_type = SPDY_INVALID; /* type not known yet */
+ if (spdy_debug)
+ printf(" %d Headers:\n", num_headers);
+ if (num_headers > frame_length) {
+ printf("Number of headers is greater than frame length!\n");
+ proto_item_append_text(ti, " [Error: Number of headers is larger than frame length]");
+ col_add_fstr(pinfo->cinfo, COL_INFO, "%s[%d]", frame_type_name, stream_id);
+ return frame_length+8;
+ }
+ hdr_verb = hdr_url = hdr_version = content_type = content_encoding = NULL;
+ while (num_headers-- && tvb_reported_length_remaining(header_tvb, hdr_offset) != 0) {
+ gchar *header_name;
+ gchar *header_value;
+ proto_tree *header_tree;
+ proto_tree *name_tree;
+ proto_tree *value_tree;
+ proto_item *header;
+ gint16 length;
+ gint header_length = 0;
+
+ hoffset = hdr_offset;
+
+ header = proto_tree_add_item(spdy_tree, hf_spdy_header, header_tvb,
+ hdr_offset, frame_length, FALSE);
+ header_tree = proto_item_add_subtree(header, ett_spdy_header);
+
+ length = tvb_get_ntohs(header_tvb, hdr_offset);
+ hdr_offset += 2;
+ header_name = (gchar *)tvb_get_ephemeral_string(header_tvb, hdr_offset, length);
+ hdr_offset += length;
+ header_length += hdr_offset - hoffset;
+ if (tree) {
+ ti = proto_tree_add_text(header_tree, header_tvb, hoffset, length+2, "Name: %s",
+ header_name);
+ name_tree = proto_item_add_subtree(ti, ett_spdy_header_name);
+ proto_tree_add_uint(name_tree, hf_spdy_length, header_tvb, hoffset, 2, length);
+ proto_tree_add_string_format(name_tree, hf_spdy_header_name_text, header_tvb, hoffset+2, length,
+ header_name, "Text: %s", format_text(header_name, length));
+ }
+
+ hoffset = hdr_offset;
+ length = tvb_get_ntohs(header_tvb, hdr_offset);
+ hdr_offset += 2;
+ header_value = (gchar *)tvb_get_ephemeral_string(header_tvb, hdr_offset, length);
+ hdr_offset += length;
+ header_length += hdr_offset - hoffset;
+ if (tree) {
+ ti = proto_tree_add_text(header_tree, header_tvb, hoffset, length+2, "Value: %s",
+ header_value);
+ value_tree = proto_item_add_subtree(ti, ett_spdy_header_value);
+ proto_tree_add_uint(value_tree, hf_spdy_length, header_tvb, hoffset, 2, length);
+ proto_tree_add_string_format(value_tree, hf_spdy_header_value_text, header_tvb, hoffset+2, length,
+ header_value, "Text: %s", format_text(header_value, length));
+ proto_item_append_text(header, ": %s: %s", header_name, header_value);
+ proto_item_set_len(header, header_length);
+ }
+ if (spdy_debug) printf(" %s: %s\n", header_name, header_value);
+ /*
+ * TODO(ers) check that the header name contains only legal characters.
+ */
+ if (g_ascii_strcasecmp(header_name, "method") == 0 ||
+ g_ascii_strcasecmp(header_name, "status") == 0) {
+ hdr_verb = header_value;
+ } else if (g_ascii_strcasecmp(header_name, "url") == 0) {
+ hdr_url = header_value;
+ } else if (g_ascii_strcasecmp(header_name, "version") == 0) {
+ hdr_version = header_value;
+ } else if (g_ascii_strcasecmp(header_name, "content-type") == 0) {
+ content_type = se_strdup(header_value);
+ } else if (g_ascii_strcasecmp(header_name, "content-encoding") == 0) {
+ content_encoding = se_strdup(header_value);
+ }
+ }
+ if (hdr_version != NULL) {
+ if (hdr_url != NULL) {
+ col_add_fstr(pinfo->cinfo, COL_INFO, "%s[%d]: %s %s %s",
+ frame_type_name, stream_id, hdr_verb, hdr_url, hdr_version);
+ } else {
+ col_add_fstr(pinfo->cinfo, COL_INFO, "%s[%d]: %s %s",
+ frame_type_name, stream_id, hdr_verb, hdr_version);
+ }
+ } else {
+ col_add_fstr(pinfo->cinfo, COL_INFO, "%s[%d]", frame_type_name, stream_id);
+ }
+ /*
+ * If we expect data on this stream, we need to remember the content
+ * type and content encoding.
+ */
+ if (content_type != NULL && !pinfo->fd->flags.visited) {
+ gchar *content_type_params = spdy_parse_content_type(content_type);
+ spdy_save_stream_info(conv_data, stream_id, content_type,
+ content_type_params, content_encoding);
+ }
+
+ return offset - orig_offset;
+}
+
+static int
+dissect_spdy(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
+{
+ spdy_conv_t *conv_data;
+ int offset = 0;
+ int len;
+ int firstpkt = 1;
+
+ /*
+ * The first byte of a SPDY packet must be either 0 or
+ * 0x80. If it's not, assume that this is not SPDY.
+ * (In theory, a data frame could have a stream ID
+ * >= 2^24, in which case it won't have 0 for a first
+ * byte, but this is a pretty reliable heuristic for
+ * now.)
+ */
+ guint8 first_byte = tvb_get_guint8(tvb, 0);
+ if (first_byte != 0x80 && first_byte != 0x0)
+ return 0;
+
+ conv_data = get_spdy_conversation_data(pinfo);
+
+ while (tvb_reported_length_remaining(tvb, offset) != 0) {
+ if (!firstpkt) {
+ col_add_fstr(pinfo->cinfo, COL_INFO, " >> ");
+ col_set_fence(pinfo->cinfo, COL_INFO);
+ }
+ len = dissect_spdy_message(tvb, offset, pinfo, tree, conv_data);
+ if (len <= 0)
+ return 0;
+ offset += len;
+ /*
+ * OK, we've set the Protocol and Info columns for the
+ * first SPDY message; set a fence so that subsequent
+ * SPDY messages don't overwrite the Info column.
+ */
+ col_set_fence(pinfo->cinfo, COL_INFO);
+ firstpkt = 0;
+ }
+ return 1;
+}
+
+static gboolean
+dissect_spdy_heur(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
+{
+ if (!value_is_in_range(global_spdy_tcp_range, pinfo->destport) &&
+ !value_is_in_range(global_spdy_tcp_range, pinfo->srcport))
+ return FALSE;
+ return dissect_spdy(tvb, pinfo, tree) != 0;
+}
+
+static void reinit_spdy(void)
+{
+}
+
+// NMAKE complains about flags_set_truth not being constant. Duplicate
+// the values inside of it.
+static const true_false_string tfs_spdy_set_notset = { "Set", "Not set" };
+
+void
+proto_register_spdy(void)
+{
+ static hf_register_info hf[] = {
+ { &hf_spdy_syn_stream,
+ { "Syn Stream", "spdy.syn_stream",
+ FT_BYTES, BASE_NONE, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_syn_reply,
+ { "Syn Reply", "spdy.syn_reply",
+ FT_BYTES, BASE_NONE, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_control_bit,
+ { "Control bit", "spdy.control_bit",
+ FT_BOOLEAN, BASE_NONE, NULL, 0x0,
+ "TRUE if SPDY control frame", HFILL }},
+ { &hf_spdy_version,
+ { "Version", "spdy.version",
+ FT_UINT16, BASE_DEC, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_type,
+ { "Type", "spdy.type",
+ FT_UINT16, BASE_DEC, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_flags,
+ { "Flags", "spdy.flags",
+ FT_UINT8, BASE_HEX, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_flags_fin,
+ { "Fin", "spdy.flags.fin",
+ FT_BOOLEAN, 8, TFS(&tfs_spdy_set_notset),
+ SPDY_FIN, "", HFILL }},
+ { &hf_spdy_length,
+ { "Length", "spdy.length",
+ FT_UINT24, BASE_DEC, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_header,
+ { "Header", "spdy.header",
+ FT_NONE, BASE_NONE, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_header_name,
+ { "Name", "spdy.header.name",
+ FT_NONE, BASE_NONE, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_header_name_text,
+ { "Text", "spdy.header.name.text",
+ FT_STRING, BASE_NONE, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_header_value,
+ { "Value", "spdy.header.value",
+ FT_NONE, BASE_NONE, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_header_value_text,
+ { "Text", "spdy.header.value.text",
+ FT_STRING, BASE_NONE, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_streamid,
+ { "Stream ID", "spdy.streamid",
+ FT_UINT32, BASE_DEC, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_associated_streamid,
+ { "Associated Stream ID", "spdy.associated.streamid",
+ FT_UINT32, BASE_DEC, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_priority,
+ { "Priority", "spdy.priority",
+ FT_UINT8, BASE_DEC, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_num_headers,
+ { "Number of headers", "spdy.numheaders",
+ FT_UINT16, BASE_DEC, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_num_headers_string,
+ { "Number of headers", "spdy.numheaders",
+ FT_STRING, BASE_NONE, NULL, 0x0,
+ "", HFILL }},
+ };
+ static gint *ett[] = {
+ &ett_spdy,
+ &ett_spdy_syn_stream,
+ &ett_spdy_syn_reply,
+ &ett_spdy_fin_stream,
+ &ett_spdy_flags,
+ &ett_spdy_header,
+ &ett_spdy_header_name,
+ &ett_spdy_header_value,
+ &ett_spdy_encoded_entity,
+ };
+
+ module_t *spdy_module;
+
+ proto_spdy = proto_register_protocol("SPDY", "SPDY", "spdy");
+ proto_register_field_array(proto_spdy, hf, array_length(hf));
+ proto_register_subtree_array(ett, array_length(ett));
+ new_register_dissector("spdy", dissect_spdy, proto_spdy);
+ spdy_module = prefs_register_protocol(proto_spdy, reinit_spdy);
+ prefs_register_bool_preference(spdy_module, "desegment_headers",
+ "Reassemble SPDY control frames spanning multiple TCP segments",
+ "Whether the SPDY dissector should reassemble control frames "
+ "spanning multiple TCP segments. "
+ "To use this option, you must also enable "
+ "\"Allow subdissectors to reassemble TCP streams\" in the TCP protocol settings.",
+ &spdy_desegment_control_frames);
+ prefs_register_bool_preference(spdy_module, "desegment_body",
+ "Reassemble SPDY bodies spanning multiple TCP segments",
+ "Whether the SPDY dissector should reassemble "
+ "data frames spanning multiple TCP segments. "
+ "To use this option, you must also enable "
+ "\"Allow subdissectors to reassemble TCP streams\" in the TCP protocol settings.",
+ &spdy_desegment_data_frames);
+ prefs_register_bool_preference(spdy_module, "assemble_data_frames",
+ "Assemble SPDY bodies that consist of multiple DATA frames",
+ "Whether the SPDY dissector should reassemble multiple "
+ "data frames into an entity body.",
+ &spdy_assemble_entity_bodies);
+#ifdef HAVE_LIBZ
+ prefs_register_bool_preference(spdy_module, "decompress_headers",
+ "Uncompress SPDY headers",
+ "Whether to uncompress SPDY headers.",
+ &spdy_decompress_headers);
+ prefs_register_bool_preference(spdy_module, "decompress_body",
+ "Uncompress entity bodies",
+ "Whether to uncompress entity bodies that are compressed "
+ "using \"Content-Encoding: \"",
+ &spdy_decompress_body);
+#endif
+ prefs_register_bool_preference(spdy_module, "debug_output",
+ "Print debug info on stdout",
+ "Print debug info on stdout",
+ &spdy_debug);
+#if 0
+ prefs_register_string_preference(ssl_module, "debug_file", "SPDY debug file",
+ "Redirect SPDY debug to file name; "
+ "leave empty to disable debugging, "
+ "or use \"" SPDY_DEBUG_USE_STDOUT "\""
+ " to redirect output to stdout\n",
+ (const gchar **)&sdpy_debug_file_name);
+#endif
+ prefs_register_obsolete_preference(spdy_module, "tcp_alternate_port");
+
+ range_convert_str(&global_spdy_tcp_range, TCP_DEFAULT_RANGE, 65535);
+ spdy_tcp_range = range_empty();
+ prefs_register_range_preference(spdy_module, "tcp.port", "TCP Ports",
+ "TCP Ports range",
+ &global_spdy_tcp_range, 65535);
+
+ range_convert_str(&global_spdy_ssl_range, SSL_DEFAULT_RANGE, 65535);
+ spdy_ssl_range = range_empty();
+ prefs_register_range_preference(spdy_module, "ssl.port", "SSL/TLS Ports",
+ "SSL/TLS Ports range",
+ &global_spdy_ssl_range, 65535);
+
+ spdy_handle = new_create_dissector_handle(dissect_spdy, proto_spdy);
+ /*
+ * Register for tapping
+ */
+ spdy_tap = register_tap("spdy"); /* SPDY statistics tap */
+ spdy_eo_tap = register_tap("spdy_eo"); /* SPDY Export Object tap */
+}
+
+void
+proto_reg_handoff_spdy(void)
+{
+ data_handle = find_dissector("data");
+ media_handle = find_dissector("media");
+ heur_dissector_add("tcp", dissect_spdy_heur, proto_spdy);
+}
+
+/*
+ * Content-Type: message/http
+ */
+
+static gint proto_message_spdy = -1;
+static gint ett_message_spdy = -1;
+
+static void
+dissect_message_spdy(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
+{
+ proto_tree *subtree;
+ proto_item *ti;
+ gint offset = 0, next_offset;
+ gint len;
+
+ if (check_col(pinfo->cinfo, COL_INFO))
+ col_append_str(pinfo->cinfo, COL_INFO, " (message/spdy)");
+ if (tree) {
+ ti = proto_tree_add_item(tree, proto_message_spdy,
+ tvb, 0, -1, FALSE);
+ subtree = proto_item_add_subtree(ti, ett_message_spdy);
+ while (tvb_reported_length_remaining(tvb, offset) != 0) {
+ len = tvb_find_line_end(tvb, offset,
+ tvb_ensure_length_remaining(tvb, offset),
+ &next_offset, FALSE);
+ if (len == -1)
+ break;
+ proto_tree_add_text(subtree, tvb, offset, next_offset - offset,
+ "%s", tvb_format_text(tvb, offset, len));
+ offset = next_offset;
+ }
+ }
+}
+
+void
+proto_register_message_spdy(void)
+{
+ static gint *ett[] = {
+ &ett_message_spdy,
+ };
+
+ proto_message_spdy = proto_register_protocol(
+ "Media Type: message/spdy",
+ "message/spdy",
+ "message-spdy"
+ );
+ proto_register_subtree_array(ett, array_length(ett));
+}
+
+void
+proto_reg_handoff_message_spdy(void)
+{
+ dissector_handle_t message_spdy_handle;
+
+ message_spdy_handle = create_dissector_handle(dissect_message_spdy,
+ proto_message_spdy);
+
+ dissector_add_string("media_type", "message/spdy", message_spdy_handle);
+
+ reinit_spdy();
+}
diff --git a/net/tools/spdyshark/packet-spdy.h b/net/tools/spdyshark/packet-spdy.h
new file mode 100644
index 0000000..02b586b
--- /dev/null
+++ b/net/tools/spdyshark/packet-spdy.h
@@ -0,0 +1,46 @@
+/* packet-spdy.h
+ *
+ * Copyright 2010, Google Inc.
+ * Eric Shienbrood <ers@google.com>
+ *
+ * $Id$
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __PACKET_SPDY_H__
+#define __PACKET_SPDY_H__
+
+#include <epan/packet.h>
+#ifdef HAVE_LIBZ
+#include <zlib.h>
+#endif
+
+/*
+ * Conversation data - used for assembling multi-data-frame
+ * entities and for decompressing request & reply header blocks.
+ */
+typedef struct _spdy_conv_t {
+ z_streamp rqst_decompressor;
+ z_streamp rply_decompressor;
+ guint32 dictionary_id;
+ GArray *streams;
+} spdy_conv_t;
+
+#endif /* __PACKET_SPDY_H__ */
diff --git a/net/tools/spdyshark/plugin.rc.in b/net/tools/spdyshark/plugin.rc.in
new file mode 100644
index 0000000..568dc07
--- /dev/null
+++ b/net/tools/spdyshark/plugin.rc.in
@@ -0,0 +1,34 @@
+#include "winver.h"
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION @RC_MODULE_VERSION@
+ PRODUCTVERSION @RC_VERSION@
+ FILEFLAGSMASK 0x0L
+#ifdef _DEBUG
+ FILEFLAGS VS_FF_DEBUG
+#else
+ FILEFLAGS 0
+#endif
+ FILEOS VOS_NT_WINDOWS32
+ FILETYPE VFT_DLL
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "CompanyName", "The Wireshark developer community, http://www.wireshark.org/\0"
+ VALUE "FileDescription", "@PACKAGE@ dissector\0"
+ VALUE "FileVersion", "@MODULE_VERSION@\0"
+ VALUE "InternalName", "@PACKAGE@ @MODULE_VERSION@\0"
+ VALUE "LegalCopyright", "Copyright © 1998 Gerald Combs <gerald@wireshark.org>, Gilbert Ramirez <gram@alumni.rice.edu> and others\0"
+ VALUE "OriginalFilename", "@PLUGIN_NAME@.dll\0"
+ VALUE "ProductName", "Wireshark\0"
+ VALUE "ProductVersion", "@VERSION@\0"
+ VALUE "Comments", "Build with @MSVC_VARIANT@\0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
diff --git a/net/tools/testserver/chromiumsync.py b/net/tools/testserver/chromiumsync.py
new file mode 100755
index 0000000..a15d265
--- /dev/null
+++ b/net/tools/testserver/chromiumsync.py
@@ -0,0 +1,667 @@
+#!/usr/bin/python2.4
+# 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.
+
+"""An implementation of the server side of the Chromium sync protocol.
+
+The details of the protocol are described mostly by comments in the protocol
+buffer definition at chrome/browser/sync/protocol/sync.proto.
+"""
+
+import operator
+import random
+import threading
+
+import autofill_specifics_pb2
+import bookmark_specifics_pb2
+import extension_specifics_pb2
+import nigori_specifics_pb2
+import password_specifics_pb2
+import preference_specifics_pb2
+import theme_specifics_pb2
+import typed_url_specifics_pb2
+import sync_pb2
+
+# An enumeration of the various kinds of data that can be synced.
+# Over the wire, this enumeration is not used: a sync object's type is
+# inferred by which EntitySpecifics extension it has. But in the context
+# of a program, it is useful to have an enumeration.
+ALL_TYPES = (
+ TOP_LEVEL, # The type of the 'Google Chrome' folder.
+ AUTOFILL,
+ BOOKMARK,
+ EXTENSIONS,
+ NIGORI,
+ PASSWORD,
+ PREFERENCE,
+ # SESSION,
+ THEME,
+ TYPED_URL) = range(9)
+
+# Given a sync type from ALL_TYPES, find the extension token corresponding
+# to that datatype. Note that TOP_LEVEL has no such token.
+SYNC_TYPE_TO_EXTENSION = {
+ AUTOFILL: autofill_specifics_pb2.autofill,
+ BOOKMARK: bookmark_specifics_pb2.bookmark,
+ EXTENSIONS: extension_specifics_pb2.extension,
+ NIGORI: nigori_specifics_pb2.nigori,
+ PASSWORD: password_specifics_pb2.password,
+ PREFERENCE: preference_specifics_pb2.preference,
+ # SESSION: session_specifics_pb2.session, # Disabled
+ THEME: theme_specifics_pb2.theme,
+ TYPED_URL: typed_url_specifics_pb2.typed_url,
+ }
+
+# The parent ID used to indicate a top-level node.
+ROOT_ID = '0'
+
+def GetEntryType(entry):
+ """Extract the sync type from a SyncEntry.
+
+ Args:
+ entry: A SyncEntity protobuf object whose type to determine.
+ Returns:
+ A value from ALL_TYPES if the entry's type can be determined, or None
+ if the type cannot be determined.
+ """
+ if entry.server_defined_unique_tag == 'google_chrome':
+ return TOP_LEVEL
+ entry_types = GetEntryTypesFromSpecifics(entry.specifics)
+ if not entry_types:
+ return None
+ # It is presupposed that the entry has at most one specifics extension
+ # present. If there is more than one, either there's a bug, or else
+ # the caller should use GetEntryTypes.
+ if len(entry_types) > 1:
+ raise 'GetEntryType called with multiple extensions present.'
+ return entry_types[0]
+
+def GetEntryTypesFromSpecifics(specifics):
+ """Determine the sync types indicated by an EntitySpecifics's extension(s).
+
+ If the specifics have more than one recognized extension (as commonly
+ happens with the requested_types field of GetUpdatesMessage), all types
+ will be returned. Callers must handle the possibility of the returned
+ value having more than one item.
+
+ Args:
+ specifics: A EntitySpecifics protobuf message whose extensions to
+ enumerate.
+ Returns:
+ A list of the sync types (values from ALL_TYPES) assocated with each
+ recognized extension of the specifics message.
+ """
+ entry_types = []
+ for data_type, extension in SYNC_TYPE_TO_EXTENSION.iteritems():
+ if specifics.HasExtension(extension):
+ entry_types.append(data_type)
+ return entry_types
+
+def GetRequestedTypes(get_updates_message):
+ """Determine the sync types requested by a client GetUpdates operation."""
+ types = GetEntryTypesFromSpecifics(
+ get_updates_message.requested_types)
+ if types:
+ types.append(TOP_LEVEL)
+ return types
+
+def GetDefaultEntitySpecifics(data_type):
+ """Get an EntitySpecifics having a sync type's default extension value.
+ """
+ specifics = sync_pb2.EntitySpecifics()
+ if data_type in SYNC_TYPE_TO_EXTENSION:
+ extension_handle = SYNC_TYPE_TO_EXTENSION[data_type]
+ specifics.Extensions[extension_handle].SetInParent()
+ return specifics
+
+def DeepCopyOfProto(proto):
+ """Return a deep copy of a protocol buffer."""
+ new_proto = type(proto)()
+ new_proto.MergeFrom(proto)
+ return new_proto
+
+
+class PermanentItem(object):
+ """A specification of one server-created permanent item.
+
+ Attributes:
+ tag: A known-to-the-client value that uniquely identifies a server-created
+ permanent item.
+ name: The human-readable display name for this item.
+ parent_tag: The tag of the permanent item's parent. If ROOT_ID, indicates
+ a top-level item. Otherwise, this must be the tag value of some other
+ server-created permanent item.
+ sync_type: A value from ALL_TYPES, giving the datatype of this permanent
+ item. This controls which types of client GetUpdates requests will
+ cause the permanent item to be created and returned.
+ """
+
+ def __init__(self, tag, name, parent_tag, sync_type):
+ self.tag = tag
+ self.name = name
+ self.parent_tag = parent_tag
+ self.sync_type = sync_type
+
+class SyncDataModel(object):
+ """Models the account state of one sync user.
+ """
+ _BATCH_SIZE = 100
+
+ # Specify all the permanent items that a model might need.
+ _PERMANENT_ITEM_SPECS = [
+ PermanentItem('google_chrome', name='Google Chrome',
+ parent_tag=ROOT_ID, sync_type=TOP_LEVEL),
+ PermanentItem('google_chrome_bookmarks', name='Bookmarks',
+ parent_tag='google_chrome', sync_type=BOOKMARK),
+ PermanentItem('bookmark_bar', name='Bookmark Bar',
+ parent_tag='google_chrome_bookmarks', sync_type=BOOKMARK),
+ PermanentItem('other_bookmarks', name='Other Bookmarks',
+ parent_tag='google_chrome_bookmarks', sync_type=BOOKMARK),
+ PermanentItem('google_chrome_preferences', name='Preferences',
+ parent_tag='google_chrome', sync_type=PREFERENCE),
+ PermanentItem('google_chrome_autofill', name='Autofill',
+ parent_tag='google_chrome', sync_type=AUTOFILL),
+ PermanentItem('google_chrome_extensions', name='Extensions',
+ parent_tag='google_chrome', sync_type=EXTENSIONS),
+ PermanentItem('google_chrome_passwords', name='Passwords',
+ parent_tag='google_chrome', sync_type=PASSWORD),
+ # TODO(rsimha): Disabled since the protocol does not support it yet.
+ # PermanentItem('google_chrome_sessions', name='Sessions',
+ # parent_tag='google_chrome', SESSION),
+ PermanentItem('google_chrome_themes', name='Themes',
+ parent_tag='google_chrome', sync_type=THEME),
+ PermanentItem('google_chrome_typed_urls', name='Typed URLs',
+ parent_tag='google_chrome', sync_type=TYPED_URL),
+ PermanentItem('google_chrome_nigori', name='Nigori',
+ parent_tag='google_chrome', sync_type=NIGORI),
+ ]
+
+ def __init__(self):
+ self._version = 0
+
+ # Monotonically increasing version number. The next object change will
+ # take on this value + 1.
+ self._entries = {}
+
+ # TODO(nick): uuid.uuid1() is better, but python 2.5 only.
+ self.store_birthday = '%0.30f' % random.random()
+
+ def _SaveEntry(self, entry):
+ """Insert or update an entry in the change log, and give it a new version.
+
+ The ID fields of this entry are assumed to be valid server IDs. This
+ entry will be updated with a new version number and sync_timestamp.
+
+ Args:
+ entry: The entry to be added or updated.
+ """
+ self._version = self._version + 1
+ entry.version = self._version
+ entry.sync_timestamp = self._version
+
+ # Preserve the originator info, which the client is not required to send
+ # when updating.
+ base_entry = self._entries.get(entry.id_string)
+ if base_entry:
+ entry.originator_cache_guid = base_entry.originator_cache_guid
+ entry.originator_client_item_id = base_entry.originator_client_item_id
+
+ self._entries[entry.id_string] = DeepCopyOfProto(entry)
+
+ def _ServerTagToId(self, tag):
+ """Determine the server ID from a server-unique tag.
+
+ The resulting value is guaranteed not to collide with the other ID
+ generation methods.
+
+ Args:
+ tag: The unique, known-to-the-client tag of a server-generated item.
+ """
+ if tag and tag != ROOT_ID:
+ return '<server tag>%s' % tag
+ else:
+ return tag
+
+ def _ClientTagToId(self, tag):
+ """Determine the server ID from a client-unique tag.
+
+ The resulting value is guaranteed not to collide with the other ID
+ generation methods.
+
+ Args:
+ tag: The unique, opaque-to-the-server tag of a client-tagged item.
+ """
+ return '<client tag>%s' % tag
+
+ def _ClientIdToId(self, client_guid, client_item_id):
+ """Compute a unique server ID from a client-local ID tag.
+
+ The resulting value is guaranteed not to collide with the other ID
+ generation methods.
+
+ Args:
+ client_guid: A globally unique ID that identifies the client which
+ created this item.
+ client_item_id: An ID that uniquely identifies this item on the client
+ which created it.
+ """
+ # Using the client ID info is not required here (we could instead generate
+ # a random ID), but it's useful for debugging.
+ return '<server ID originally>%s/%s' % (client_guid, client_item_id)
+
+ def _WritePosition(self, entry, parent_id, prev_id=None):
+ """Convert from a relative position into an absolute, numeric position.
+
+ Clients specify positions using the predecessor-based references; the
+ server stores and reports item positions using sparse integer values.
+ This method converts from the former to the latter.
+
+ Args:
+ entry: The entry for which to compute a position. Its ID field are
+ assumed to be server IDs. This entry will have its parent_id_string
+ and position_in_parent fields updated; its insert_after_item_id field
+ will be cleared.
+ parent_id: The ID of the entry intended as the new parent.
+ prev_id: The ID of the entry intended as the new predecessor. If this
+ is None, or an ID of an object which is not a child of the new parent,
+ the entry will be positioned at the end (right) of the ordering. If
+ the empty ID (''), this will be positioned at the front (left) of the
+ ordering. Otherwise, the entry will be given a position_in_parent
+ value placing it just after (to the right of) the new predecessor.
+ """
+ PREFERRED_GAP = 2 ** 20
+ # Compute values at the beginning or end.
+ def ExtendRange(current_limit_entry, sign_multiplier):
+ if current_limit_entry.id_string == entry.id_string:
+ step = 0
+ else:
+ step = sign_multiplier * PREFERRED_GAP
+ return current_limit_entry.position_in_parent + step
+
+ siblings = [x for x in self._entries.values()
+ if x.parent_id_string == parent_id and not x.deleted]
+ siblings = sorted(siblings, key=operator.attrgetter('position_in_parent'))
+ if prev_id == entry.id_string:
+ prev_id = ''
+ if not siblings:
+ # First item in this container; start in the middle.
+ entry.position_in_parent = 0
+ elif prev_id == '':
+ # A special value in the protocol. Insert at first position.
+ entry.position_in_parent = ExtendRange(siblings[0], -1)
+ else:
+ # Consider items along with their successors.
+ for a, b in zip(siblings, siblings[1:]):
+ if a.id_string != prev_id:
+ continue
+ elif b.id_string == entry.id_string:
+ # We're already in place; don't change anything.
+ entry.position_in_parent = b.position_in_parent
+ else:
+ # Interpolate new position between two others.
+ entry.position_in_parent = (
+ a.position_in_parent * 7 + b.position_in_parent) / 8
+ break
+ else:
+ # Insert at end. Includes the case where prev_id is None.
+ entry.position_in_parent = ExtendRange(siblings[-1], +1)
+
+ entry.parent_id_string = parent_id
+ entry.ClearField('insert_after_item_id')
+
+ def _ItemExists(self, id_string):
+ """Determine whether an item exists in the changelog."""
+ return id_string in self._entries
+
+ def _CreatePermanentItem(self, spec):
+ """Create one permanent item from its spec, if it doesn't exist.
+
+ The resulting item is added to the changelog.
+
+ Args:
+ spec: A PermanentItem object holding the properties of the item to create.
+ """
+ id_string = self._ServerTagToId(spec.tag)
+ if self._ItemExists(id_string):
+ return
+ print 'Creating permanent item: %s' % spec.name
+ entry = sync_pb2.SyncEntity()
+ entry.id_string = id_string
+ entry.non_unique_name = spec.name
+ entry.name = spec.name
+ entry.server_defined_unique_tag = spec.tag
+ entry.folder = True
+ entry.deleted = False
+ entry.specifics.CopyFrom(GetDefaultEntitySpecifics(spec.sync_type))
+ self._WritePosition(entry, self._ServerTagToId(spec.parent_tag))
+ self._SaveEntry(entry)
+
+ def _CreatePermanentItems(self, requested_types):
+ """Ensure creation of all permanent items for a given set of sync types.
+
+ Args:
+ requested_types: A list of sync data types from ALL_TYPES.
+ Permanent items of only these types will be created.
+ """
+ for spec in self._PERMANENT_ITEM_SPECS:
+ if spec.sync_type in requested_types:
+ self._CreatePermanentItem(spec)
+
+ def GetChangesFromTimestamp(self, requested_types, timestamp):
+ """Get entries which have changed since a given timestamp, oldest first.
+
+ The returned entries are limited to being _BATCH_SIZE many. The entries
+ are returned in strict version order.
+
+ Args:
+ requested_types: A list of sync data types from ALL_TYPES.
+ Only items of these types will be retrieved; others will be filtered
+ out.
+ timestamp: A timestamp / version number. Only items that have changed
+ more recently than this value will be retrieved; older items will
+ be filtered out.
+ Returns:
+ A tuple of (version, entries). Version is a new timestamp value, which
+ should be used as the starting point for the next query. Entries is the
+ batch of entries meeting the current timestamp query.
+ """
+ if timestamp == 0:
+ self._CreatePermanentItems(requested_types)
+ change_log = sorted(self._entries.values(),
+ key=operator.attrgetter('version'))
+ new_changes = [x for x in change_log if x.version > timestamp]
+ # Pick batch_size new changes, and then filter them. This matches
+ # the RPC behavior of the production sync server.
+ batch = new_changes[:self._BATCH_SIZE]
+ if not batch:
+ # Client is up to date.
+ return (timestamp, [])
+
+ # Restrict batch to requested types. Tombstones are untyped
+ # and will always get included.
+ filtered = []
+ for x in batch:
+ if (GetEntryType(x) in requested_types) or x.deleted:
+ filtered.append(DeepCopyOfProto(x))
+ # The new client timestamp is the timestamp of the last item in the
+ # batch, even if that item was filtered out.
+ return (batch[-1].version, filtered)
+
+ def _CheckVersionForCommit(self, entry):
+ """Perform an optimistic concurrency check on the version number.
+
+ Clients are only allowed to commit if they report having seen the most
+ recent version of an object.
+
+ Args:
+ entry: A sync entity from the client. It is assumed that ID fields
+ have been converted to server IDs.
+ Returns:
+ A boolean value indicating whether the client's version matches the
+ newest server version for the given entry.
+ """
+ if entry.id_string in self._entries:
+ if (self._entries[entry.id_string].version != entry.version and
+ not self._entries[entry.id_string].deleted):
+ # Version mismatch that is not a tombstone recreation.
+ return False
+ else:
+ if entry.version != 0:
+ # Edit to an item that does not exist.
+ return False
+ return True
+
+ def _CheckParentIdForCommit(self, entry):
+ """Check that the parent ID referenced in a SyncEntity actually exists.
+
+ Args:
+ entry: A sync entity from the client. It is assumed that ID fields
+ have been converted to server IDs.
+ Returns:
+ A boolean value indicating whether the entity's parent ID is an object
+ that actually exists (and is not deleted) in the current account state.
+ """
+ if entry.parent_id_string == ROOT_ID:
+ # This is generally allowed.
+ return True
+ if entry.parent_id_string not in self._entries:
+ print 'Warning: Client sent unknown ID. Should never happen.'
+ return False
+ if entry.parent_id_string == entry.id_string:
+ print 'Warning: Client sent circular reference. Should never happen.'
+ return False
+ if self._entries[entry.parent_id_string].deleted:
+ # This can happen in a race condition between two clients.
+ return False
+ if not self._entries[entry.parent_id_string].folder:
+ print 'Warning: Client sent non-folder parent. Should never happen.'
+ return False
+ return True
+
+ def _RewriteIdsAsServerIds(self, entry, cache_guid, commit_session):
+ """Convert ID fields in a client sync entry to server IDs.
+
+ A commit batch sent by a client may contain new items for which the
+ server has not generated IDs yet. And within a commit batch, later
+ items are allowed to refer to earlier items. This method will
+ generate server IDs for new items, as well as rewrite references
+ to items whose server IDs were generated earlier in the batch.
+
+ Args:
+ entry: The client sync entry to modify.
+ cache_guid: The globally unique ID of the client that sent this
+ commit request.
+ commit_session: A dictionary mapping the original IDs to the new server
+ IDs, for any items committed earlier in the batch.
+ """
+ if entry.version == 0:
+ if entry.HasField('client_defined_unique_tag'):
+ # When present, this should determine the item's ID.
+ new_id = self._ClientTagToId(entry.client_defined_unique_tag)
+ else:
+ new_id = self._ClientIdToId(cache_guid, entry.id_string)
+ entry.originator_cache_guid = cache_guid
+ entry.originator_client_item_id = entry.id_string
+ commit_session[entry.id_string] = new_id # Remember the remapping.
+ entry.id_string = new_id
+ if entry.parent_id_string in commit_session:
+ entry.parent_id_string = commit_session[entry.parent_id_string]
+ if entry.insert_after_item_id in commit_session:
+ entry.insert_after_item_id = commit_session[entry.insert_after_item_id]
+
+ def CommitEntry(self, entry, cache_guid, commit_session):
+ """Attempt to commit one entry to the user's account.
+
+ Args:
+ entry: A SyncEntity protobuf representing desired object changes.
+ cache_guid: A string value uniquely identifying the client; this
+ is used for ID generation and will determine the originator_cache_guid
+ if the entry is new.
+ commit_session: A dictionary mapping client IDs to server IDs for any
+ objects committed earlier this session. If the entry gets a new ID
+ during commit, the change will be recorded here.
+ Returns:
+ A SyncEntity reflecting the post-commit value of the entry, or None
+ if the entry was not committed due to an error.
+ """
+ entry = DeepCopyOfProto(entry)
+
+ # Generate server IDs for this entry, and write generated server IDs
+ # from earlier entries into the message's fields, as appropriate. The
+ # ID generation state is stored in 'commit_session'.
+ self._RewriteIdsAsServerIds(entry, cache_guid, commit_session)
+
+ # Perform the optimistic concurrency check on the entry's version number.
+ # Clients are not allowed to commit unless they indicate that they've seen
+ # the most recent version of an object.
+ if not self._CheckVersionForCommit(entry):
+ return None
+
+ # Check the validity of the parent ID; it must exist at this point.
+ # TODO(nick): Implement cycle detection and resolution.
+ if not self._CheckParentIdForCommit(entry):
+ return None
+
+ # At this point, the commit is definitely going to happen.
+
+ # Deletion works by storing a limited record for an entry, called a
+ # tombstone. A sync server must track deleted IDs forever, since it does
+ # not keep track of client knowledge (there's no deletion ACK event).
+ if entry.deleted:
+ # Only the ID, version and deletion state are preserved on a tombstone.
+ # TODO(nick): Does the production server not preserve the type? Not
+ # doing so means that tombstones cannot be filtered based on
+ # requested_types at GetUpdates time.
+ tombstone = sync_pb2.SyncEntity()
+ tombstone.id_string = entry.id_string
+ tombstone.deleted = True
+ tombstone.name = ''
+ entry = tombstone
+ else:
+ # Comments in sync.proto detail how the representation of positional
+ # ordering works: the 'insert_after_item_id' field specifies a
+ # predecessor during Commit operations, but the 'position_in_parent'
+ # field provides an absolute ordering in GetUpdates contexts. Here
+ # we convert from the former to the latter. Specifically, we'll
+ # generate a numeric position placing the item just after the object
+ # identified by 'insert_after_item_id', and then clear the
+ # 'insert_after_item_id' field so that it's not sent back to the client
+ # during later GetUpdates requests.
+ if entry.HasField('insert_after_item_id'):
+ self._WritePosition(entry, entry.parent_id_string,
+ entry.insert_after_item_id)
+ else:
+ self._WritePosition(entry, entry.parent_id_string)
+
+ # Preserve the originator info, which the client is not required to send
+ # when updating.
+ base_entry = self._entries.get(entry.id_string)
+ if base_entry and not entry.HasField("originator_cache_guid"):
+ entry.originator_cache_guid = base_entry.originator_cache_guid
+ entry.originator_client_item_id = base_entry.originator_client_item_id
+
+ # Commit the change. This also updates the version number.
+ self._SaveEntry(entry)
+ # TODO(nick): Handle recursive deletion.
+ return entry
+
+class TestServer(object):
+ """An object to handle requests for one (and only one) Chrome Sync account.
+
+ TestServer consumes the sync command messages that are the outermost
+ layers of the protocol, performs the corresponding actions on its
+ SyncDataModel, and constructs an appropropriate response message.
+ """
+
+ def __init__(self):
+ # The implementation supports exactly one account; its state is here.
+ self.account = SyncDataModel()
+ self.account_lock = threading.Lock()
+
+ def HandleCommand(self, raw_request):
+ """Decode and handle a sync command from a raw input of bytes.
+
+ This is the main entry point for this class. It is safe to call this
+ method from multiple threads.
+
+ Args:
+ raw_request: An iterable byte sequence to be interpreted as a sync
+ protocol command.
+ Returns:
+ A tuple (response_code, raw_response); the first value is an HTTP
+ result code, while the second value is a string of bytes which is the
+ serialized reply to the command.
+ """
+ self.account_lock.acquire()
+ try:
+ request = sync_pb2.ClientToServerMessage()
+ request.MergeFromString(raw_request)
+ contents = request.message_contents
+
+ response = sync_pb2.ClientToServerResponse()
+ response.error_code = sync_pb2.ClientToServerResponse.SUCCESS
+ response.store_birthday = self.account.store_birthday
+
+ if contents == sync_pb2.ClientToServerMessage.AUTHENTICATE:
+ print 'Authenticate'
+ # We accept any authentication token, and support only one account.
+ # TODO(nick): Mock out the GAIA authentication as well; hook up here.
+ response.authenticate.user.email = 'syncjuser@chromium'
+ response.authenticate.user.display_name = 'Sync J User'
+ elif contents == sync_pb2.ClientToServerMessage.COMMIT:
+ print 'Commit'
+ self.HandleCommit(request.commit, response.commit)
+ elif contents == sync_pb2.ClientToServerMessage.GET_UPDATES:
+ print ('GetUpdates from timestamp %d' %
+ request.get_updates.from_timestamp)
+ self.HandleGetUpdates(request.get_updates, response.get_updates)
+ return (200, response.SerializeToString())
+ finally:
+ self.account_lock.release()
+
+ def HandleCommit(self, commit_message, commit_response):
+ """Respond to a Commit request by updating the user's account state.
+
+ Commit attempts stop after the first error, returning a CONFLICT result
+ for any unattempted entries.
+
+ Args:
+ commit_message: A sync_pb.CommitMessage protobuf holding the content
+ of the client's request.
+ commit_response: A sync_pb.CommitResponse protobuf into which a reply
+ to the client request will be written.
+ """
+ commit_response.SetInParent()
+ batch_failure = False
+ session = {} # Tracks ID renaming during the commit operation.
+ guid = commit_message.cache_guid
+ for entry in commit_message.entries:
+ server_entry = None
+ if not batch_failure:
+ # Try to commit the change to the account.
+ server_entry = self.account.CommitEntry(entry, guid, session)
+
+ # An entryresponse is returned in both success and failure cases.
+ reply = commit_response.entryresponse.add()
+ if not server_entry:
+ reply.response_type = sync_pb2.CommitResponse.CONFLICT
+ reply.error_message = 'Conflict.'
+ batch_failure = True # One failure halts the batch.
+ else:
+ reply.response_type = sync_pb2.CommitResponse.SUCCESS
+ # These are the properties that the server is allowed to override
+ # during commit; the client wants to know their values at the end
+ # of the operation.
+ reply.id_string = server_entry.id_string
+ if not server_entry.deleted:
+ # Note: the production server doesn't actually send the
+ # parent_id_string on commit responses, so we don't either.
+ reply.position_in_parent = server_entry.position_in_parent
+ reply.version = server_entry.version
+ reply.name = server_entry.name
+ reply.non_unique_name = server_entry.non_unique_name
+ else:
+ reply.version = entry.version + 1
+
+ def HandleGetUpdates(self, update_request, update_response):
+ """Respond to a GetUpdates request by querying the user's account.
+
+ Args:
+ update_request: A sync_pb.GetUpdatesMessage protobuf holding the content
+ of the client's request.
+ update_response: A sync_pb.GetUpdatesResponse protobuf into which a reply
+ to the client request will be written.
+ """
+ update_response.SetInParent()
+ requested_types = GetRequestedTypes(update_request)
+ new_timestamp, entries = self.account.GetChangesFromTimestamp(
+ requested_types, update_request.from_timestamp)
+
+ # If the client is up to date, we are careful not to set the
+ # new_timestamp field.
+ if new_timestamp != update_request.from_timestamp:
+ update_response.new_timestamp = new_timestamp
+ for e in entries:
+ reply = update_response.entries.add()
+ reply.CopyFrom(e)
diff --git a/net/tools/testserver/chromiumsync_test.py b/net/tools/testserver/chromiumsync_test.py
new file mode 100755
index 0000000..bb73d05
--- /dev/null
+++ b/net/tools/testserver/chromiumsync_test.py
@@ -0,0 +1,317 @@
+#!/usr/bin/python2.4
+# 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.
+
+"""Tests exercising chromiumsync and SyncDataModel."""
+
+import unittest
+
+from google.protobuf import text_format
+
+import chromiumsync
+import sync_pb2
+
+class SyncDataModelTest(unittest.TestCase):
+ def setUp(self):
+ self.model = chromiumsync.SyncDataModel()
+
+ def AddToModel(self, proto):
+ self.model._entries[proto.id_string] = proto
+
+ def testPermanentItemSpecs(self):
+ SPECS = chromiumsync.SyncDataModel._PERMANENT_ITEM_SPECS
+ # parent_tags must be declared before use.
+ declared_specs = set(['0'])
+ for spec in SPECS:
+ self.assertTrue(spec.parent_tag in declared_specs)
+ declared_specs.add(spec.tag)
+ # Every sync datatype should have a permanent folder associated with it.
+ unique_datatypes = set([x.sync_type for x in SPECS])
+ self.assertEqual(unique_datatypes,
+ set(chromiumsync.ALL_TYPES))
+
+ def testSaveEntry(self):
+ proto = sync_pb2.SyncEntity()
+ proto.id_string = 'abcd';
+ proto.version = 0;
+ self.assertFalse(self.model._ItemExists(proto.id_string))
+ self.model._SaveEntry(proto)
+ self.assertEqual(1, proto.version)
+ self.assertTrue(self.model._ItemExists(proto.id_string))
+ self.model._SaveEntry(proto)
+ self.assertEqual(2, proto.version)
+ proto.version = 0
+ self.assertTrue(self.model._ItemExists(proto.id_string))
+ self.assertEqual(2, self.model._entries[proto.id_string].version)
+
+ def testWritePosition(self):
+ def MakeProto(id_string, parent, position):
+ proto = sync_pb2.SyncEntity()
+ proto.id_string = id_string
+ proto.position_in_parent = position
+ proto.parent_id_string = parent
+ self.AddToModel(proto)
+
+ MakeProto('a', 'X', 1000)
+ MakeProto('b', 'X', 1800)
+ MakeProto('c', 'X', 2600)
+ MakeProto('a1', 'Z', 1007)
+ MakeProto('a2', 'Z', 1807)
+ MakeProto('a3', 'Z', 2607)
+ MakeProto('s', 'Y', 10000)
+
+ def AssertPositionResult(my_id, parent_id, prev_id, expected_position):
+ entry = sync_pb2.SyncEntity()
+ entry.id_string = my_id
+ self.model._WritePosition(entry, parent_id, prev_id)
+ self.assertEqual(expected_position, entry.position_in_parent)
+ self.assertEqual(parent_id, entry.parent_id_string)
+ self.assertFalse(entry.HasField('insert_after_item_id'))
+
+ AssertPositionResult('new', 'new_parent', '', 0)
+ AssertPositionResult('new', 'Y', '', 10000 - (2 ** 20))
+ AssertPositionResult('new', 'Y', 's', 10000 + (2 ** 20))
+ AssertPositionResult('s', 'Y', '', 10000)
+ AssertPositionResult('s', 'Y', 's', 10000)
+ AssertPositionResult('a1', 'Z', '', 1007)
+
+ AssertPositionResult('new', 'X', '', 1000 - (2 ** 20))
+ AssertPositionResult('new', 'X', 'a', 1100)
+ AssertPositionResult('new', 'X', 'b', 1900)
+ AssertPositionResult('new', 'X', 'c', 2600 + (2 ** 20))
+
+ AssertPositionResult('a1', 'X', '', 1000 - (2 ** 20))
+ AssertPositionResult('a1', 'X', 'a', 1100)
+ AssertPositionResult('a1', 'X', 'b', 1900)
+ AssertPositionResult('a1', 'X', 'c', 2600 + (2 ** 20))
+
+ AssertPositionResult('a', 'X', '', 1000)
+ AssertPositionResult('a', 'X', 'b', 1900)
+ AssertPositionResult('a', 'X', 'c', 2600 + (2 ** 20))
+
+ AssertPositionResult('b', 'X', '', 1000 - (2 ** 20))
+ AssertPositionResult('b', 'X', 'a', 1800)
+ AssertPositionResult('b', 'X', 'c', 2600 + (2 ** 20))
+
+ AssertPositionResult('c', 'X', '', 1000 - (2 ** 20))
+ AssertPositionResult('c', 'X', 'a', 1100)
+ AssertPositionResult('c', 'X', 'b', 2600)
+
+ def testCreatePermanentItems(self):
+ self.model._CreatePermanentItems(chromiumsync.ALL_TYPES)
+ self.assertEqual(len(chromiumsync.ALL_TYPES) + 2,
+ len(self.model._entries))
+
+ def ExpectedPermanentItemCount(self, sync_type):
+ if sync_type == chromiumsync.BOOKMARK:
+ return 4
+ elif sync_type == chromiumsync.TOP_LEVEL:
+ return 1
+ else:
+ return 2
+
+ def testGetChangesFromTimestampZeroForEachType(self):
+ for sync_type in chromiumsync.ALL_TYPES:
+ self.model = chromiumsync.SyncDataModel()
+ request_types = [sync_type, chromiumsync.TOP_LEVEL]
+
+ version, changes = self.model.GetChangesFromTimestamp(request_types, 0)
+
+ expected_count = self.ExpectedPermanentItemCount(sync_type)
+ self.assertEqual(expected_count, version)
+ self.assertEqual(expected_count, len(changes))
+ self.assertEqual('google_chrome', changes[0].server_defined_unique_tag)
+ for change in changes:
+ self.assertTrue(change.HasField('server_defined_unique_tag'))
+ self.assertEqual(change.version, change.sync_timestamp)
+ self.assertTrue(change.version <= version)
+
+ # Test idempotence: another GetUpdates from ts=0 shouldn't recreate.
+ version, changes = self.model.GetChangesFromTimestamp(request_types, 0)
+ self.assertEqual(expected_count, version)
+ self.assertEqual(expected_count, len(changes))
+
+ # Doing a wider GetUpdates from timestamp zero shouldn't recreate either.
+ new_version, changes = self.model.GetChangesFromTimestamp(
+ chromiumsync.ALL_TYPES, 0)
+ self.assertEqual(len(chromiumsync.SyncDataModel._PERMANENT_ITEM_SPECS),
+ new_version)
+ self.assertEqual(new_version, len(changes))
+ version, changes = self.model.GetChangesFromTimestamp(request_types, 0)
+ self.assertEqual(new_version, version)
+ self.assertEqual(expected_count, len(changes))
+
+ def testBatchSize(self):
+ for sync_type in chromiumsync.ALL_TYPES[1:]:
+ specifics = chromiumsync.GetDefaultEntitySpecifics(sync_type)
+ self.model = chromiumsync.SyncDataModel()
+ request_types = [sync_type, chromiumsync.TOP_LEVEL]
+
+ for i in range(self.model._BATCH_SIZE*3):
+ entry = sync_pb2.SyncEntity()
+ entry.id_string = 'batch test %d' % i
+ entry.specifics.CopyFrom(specifics)
+ self.model._SaveEntry(entry)
+ version, changes = self.model.GetChangesFromTimestamp(request_types, 0)
+ self.assertEqual(self.model._BATCH_SIZE, version)
+ version, changes = self.model.GetChangesFromTimestamp(request_types,
+ version)
+ self.assertEqual(self.model._BATCH_SIZE*2, version)
+ version, changes = self.model.GetChangesFromTimestamp(request_types,
+ version)
+ self.assertEqual(self.model._BATCH_SIZE*3, version)
+ expected_dingleberry = self.ExpectedPermanentItemCount(sync_type)
+ version, changes = self.model.GetChangesFromTimestamp(request_types,
+ version)
+ self.assertEqual(self.model._BATCH_SIZE*3 + expected_dingleberry,
+ version)
+
+ # Now delete a third of the items.
+ for i in xrange(self.model._BATCH_SIZE*3 - 1, 0, -3):
+ entry = sync_pb2.SyncEntity()
+ entry.id_string = 'batch test %d' % i
+ entry.deleted = True
+ self.model._SaveEntry(entry)
+
+ # The batch counts shouldn't change.
+ version, changes = self.model.GetChangesFromTimestamp(request_types, 0)
+ self.assertEqual(self.model._BATCH_SIZE, len(changes))
+ version, changes = self.model.GetChangesFromTimestamp(request_types,
+ version)
+ self.assertEqual(self.model._BATCH_SIZE, len(changes))
+ version, changes = self.model.GetChangesFromTimestamp(request_types,
+ version)
+ self.assertEqual(self.model._BATCH_SIZE, len(changes))
+ expected_dingleberry = self.ExpectedPermanentItemCount(sync_type)
+ version, changes = self.model.GetChangesFromTimestamp(request_types,
+ version)
+ self.assertEqual(expected_dingleberry, len(changes))
+ self.assertEqual(self.model._BATCH_SIZE*4 + expected_dingleberry, version)
+
+ def testCommitEachDataType(self):
+ for sync_type in chromiumsync.ALL_TYPES[1:]:
+ specifics = chromiumsync.GetDefaultEntitySpecifics(sync_type)
+ self.model = chromiumsync.SyncDataModel()
+ my_cache_guid = '112358132134'
+ parent = 'foobar'
+ commit_session = {}
+
+ # Start with a GetUpdates from timestamp 0, to populate permanent items.
+ original_version, original_changes = (
+ self.model.GetChangesFromTimestamp([sync_type], 0))
+
+ def DoCommit(original=None, id='', name=None, parent=None, prev=None):
+ proto = sync_pb2.SyncEntity()
+ if original is not None:
+ proto.version = original.version
+ proto.id_string = original.id_string
+ proto.parent_id_string = original.parent_id_string
+ proto.name = original.name
+ else:
+ proto.id_string = id
+ proto.version = 0
+ proto.specifics.CopyFrom(specifics)
+ if name is not None:
+ proto.name = name
+ if parent:
+ proto.parent_id_string = parent.id_string
+ if prev:
+ proto.insert_after_item_id = prev.id_string
+ else:
+ proto.insert_after_item_id = ''
+ proto.folder = True
+ proto.deleted = False
+ result = self.model.CommitEntry(proto, my_cache_guid, commit_session)
+ self.assertTrue(result)
+ return (proto, result)
+
+ # Commit a new item.
+ proto1, result1 = DoCommit(name='namae', id='Foo',
+ parent=original_changes[-1])
+ # Commit an item whose parent is another item (referenced via the
+ # pre-commit ID).
+ proto2, result2 = DoCommit(name='Secondo', id='Bar',
+ parent=proto1)
+ # Commit a sibling of the second item.
+ proto3, result3 = DoCommit(name='Third!', id='Baz',
+ parent=proto1, prev=proto2)
+
+ self.assertEqual(3, len(commit_session))
+ for p, r in [(proto1, result1), (proto2, result2), (proto3, result3)]:
+ self.assertNotEqual(r.id_string, p.id_string)
+ self.assertEqual(r.originator_client_item_id, p.id_string)
+ self.assertEqual(r.originator_cache_guid, my_cache_guid)
+ self.assertTrue(r is not self.model._entries[r.id_string],
+ "Commit result didn't make a defensive copy.")
+ self.assertTrue(p is not self.model._entries[r.id_string],
+ "Commit result didn't make a defensive copy.")
+ self.assertEqual(commit_session.get(p.id_string), r.id_string)
+ self.assertTrue(r.version > original_version)
+ self.assertEqual(result1.parent_id_string, proto1.parent_id_string)
+ self.assertEqual(result2.parent_id_string, result1.id_string)
+ version, changes = self.model.GetChangesFromTimestamp([sync_type],
+ original_version)
+ self.assertEqual(3, len(changes))
+ self.assertEqual(original_version + 3, version)
+ self.assertEqual([result1, result2, result3], changes)
+ for c in changes:
+ self.assertTrue(c is not self.model._entries[c.id_string],
+ "GetChanges didn't make a defensive copy.")
+ self.assertTrue(result2.position_in_parent < result3.position_in_parent)
+ self.assertEqual(0, result2.position_in_parent)
+
+ # Now update the items so that the second item is the parent of the
+ # first; with the first sandwiched between two new items (4 and 5).
+ # Do this in a new commit session, meaning we'll reference items from
+ # the first batch by their post-commit, server IDs.
+ commit_session = {}
+ old_cache_guid = my_cache_guid
+ my_cache_guid = 'A different GUID'
+ proto2b, result2b = DoCommit(original=result2,
+ parent=original_changes[-1])
+ proto4, result4 = DoCommit(id='ID4', name='Four',
+ parent=result2, prev=None)
+ proto1b, result1b = DoCommit(original=result1,
+ parent=result2, prev=proto4)
+ proto5, result5 = DoCommit(id='ID5', name='Five', parent=result2,
+ prev=result1)
+
+ self.assertEqual(2, len(commit_session),
+ 'Only new items in second batch should be in the session')
+ for p, r, original in [(proto2b, result2b, proto2),
+ (proto4, result4, proto4),
+ (proto1b, result1b, proto1),
+ (proto5, result5, proto5)]:
+ self.assertEqual(r.originator_client_item_id, original.id_string)
+ if original is not p:
+ self.assertEqual(r.id_string, p.id_string,
+ 'Ids should be stable after first commit')
+ self.assertEqual(r.originator_cache_guid, old_cache_guid)
+ else:
+ self.assertNotEqual(r.id_string, p.id_string)
+ self.assertEqual(r.originator_cache_guid, my_cache_guid)
+ self.assertEqual(commit_session.get(p.id_string), r.id_string)
+ self.assertTrue(r is not self.model._entries[r.id_string],
+ "Commit result didn't make a defensive copy.")
+ self.assertTrue(p is not self.model._entries[r.id_string],
+ "Commit didn't make a defensive copy.")
+ self.assertTrue(r.version > p.version)
+ version, changes = self.model.GetChangesFromTimestamp([sync_type],
+ original_version)
+ self.assertEqual(5, len(changes))
+ self.assertEqual(original_version + 7, version)
+ self.assertEqual([result3, result2b, result4, result1b, result5], changes)
+ for c in changes:
+ self.assertTrue(c is not self.model._entries[c.id_string],
+ "GetChanges didn't make a defensive copy.")
+ self.assertTrue(result4.parent_id_string ==
+ result1b.parent_id_string ==
+ result5.parent_id_string ==
+ result2b.id_string)
+ self.assertTrue(result4.position_in_parent <
+ result1b.position_in_parent <
+ result5.position_in_parent)
+
+if __name__ == '__main__':
+ unittest.main()
\ No newline at end of file
diff --git a/net/tools/testserver/run_testserver.cc b/net/tools/testserver/run_testserver.cc
new file mode 100644
index 0000000..716e320
--- /dev/null
+++ b/net/tools/testserver/run_testserver.cc
@@ -0,0 +1,70 @@
+// 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 <stdio.h>
+
+#include "base/at_exit.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "net/url_request/url_request_unittest.h"
+
+static void PrintUsage() {
+ printf("run_testserver --doc-root=relpath [--http|--https|--ftp]\n");
+ printf("(NOTE: relpath should be relative to the 'src' directory)\n");
+}
+
+int main(int argc, const char* argv[]) {
+ base::AtExitManager at_exit_manager;
+ MessageLoopForIO message_loop;
+
+ // Process command line
+ CommandLine::Init(argc, argv);
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+
+ if (command_line->GetSwitchCount() == 0 ||
+ command_line->HasSwitch("help")) {
+ PrintUsage();
+ return -1;
+ }
+
+ std::string protocol;
+ int port;
+ if (command_line->HasSwitch("https")) {
+ protocol = "https";
+ port = net::TestServerLauncher::kOKHTTPSPort;
+ } else if (command_line->HasSwitch("ftp")) {
+ protocol = "ftp";
+ port = kFTPDefaultPort;
+ } else {
+ protocol = "http";
+ port = kHTTPDefaultPort;
+ }
+ std::wstring doc_root = command_line->GetSwitchValue("doc-root");
+ if (doc_root.empty()) {
+ printf("Error: --doc-root must be specified\n");
+ PrintUsage();
+ return -1;
+ }
+
+ // Launch testserver
+ scoped_refptr<BaseTestServer> test_server;
+ if (protocol == "https") {
+ test_server = HTTPSTestServer::CreateGoodServer(doc_root);
+ } else if (protocol == "ftp") {
+ test_server = FTPTestServer::CreateServer(doc_root);
+ } else if (protocol == "http") {
+ test_server = HTTPTestServer::CreateServer(doc_root, NULL);
+ } else {
+ NOTREACHED();
+ }
+
+ printf("testserver running at %s://%s:%d (type ctrl+c to exit)\n",
+ protocol.c_str(),
+ net::TestServerLauncher::kHostName,
+ port);
+
+ message_loop.Run();
+ return 0;
+}
diff --git a/net/tools/testserver/testserver.py b/net/tools/testserver/testserver.py
index a14a3ac..8e3df5e 100644
--- a/net/tools/testserver/testserver.py
+++ b/net/tools/testserver/testserver.py
@@ -1,5 +1,5 @@
#!/usr/bin/python2.4
-# Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+# Copyright (c) 2006-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.
@@ -22,9 +22,13 @@
import SocketServer
import sys
import time
+import urllib2
+
+import pyftpdlib.ftpserver
import tlslite
import tlslite.api
-import pyftpdlib.ftpserver
+
+import chromiumsync
try:
import hashlib
@@ -47,7 +51,7 @@
def serve_forever(self):
self.stop = False
- self.nonce = None
+ self.nonce_time = None
while not self.stop:
self.handle_request()
self.socket.close()
@@ -125,11 +129,14 @@
self.ContentTypeHandler,
self.ServerRedirectHandler,
self.ClientRedirectHandler,
+ self.ChromiumSyncTimeHandler,
+ self.MultipartHandler,
self.DefaultResponseHandler]
self._post_handlers = [
self.WriteFile,
self.EchoTitleHandler,
self.EchoAllHandler,
+ self.ChromiumSyncCommandHandler,
self.EchoHandler] + self._get_handlers
self._put_handlers = [
self.WriteFile,
@@ -148,6 +155,8 @@
BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request,
client_address,
socket_server)
+ # Class variable; shared across requests.
+ _sync_handler = chromiumsync.TestServer()
def _ShouldHandleRequest(self, handler_name):
"""Determines if the path can be handled by the handler.
@@ -813,27 +822,34 @@
return True
- def AuthDigestHandler(self):
- """This handler tests 'Digest' authentication. It just sends a page with
- title 'user/pass' if you succeed."""
+ def GetNonce(self, force_reset=False):
+ """Returns a nonce that's stable per request path for the server's lifetime.
+ This is a fake implementation. A real implementation would only use a given
+ nonce a single time (hence the name n-once). However, for the purposes of
+ unittesting, we don't care about the security of the nonce.
+
+ Args:
+ force_reset: Iff set, the nonce will be changed. Useful for testing the
+ "stale" response.
+ """
+ if force_reset or not self.server.nonce_time:
+ self.server.nonce_time = time.time()
+ return _new_md5('privatekey%s%d' %
+ (self.path, self.server.nonce_time)).hexdigest()
+
+ def AuthDigestHandler(self):
+ """This handler tests 'Digest' authentication.
+
+ It just sends a page with title 'user/pass' if you succeed.
+
+ A stale response is sent iff "stale" is present in the request path.
+ """
if not self._ShouldHandleRequest("/auth-digest"):
return False
- # Periodically generate a new nonce. Technically we should incorporate
- # the request URL into this, but we don't care for testing.
- nonce_life = 10
- stale = False
- if (not self.server.nonce or
- (time.time() - self.server.nonce_time > nonce_life)):
- if self.server.nonce:
- stale = True
- self.server.nonce_time = time.time()
- self.server.nonce = \
- _new_md5(time.ctime(self.server.nonce_time) +
- 'privatekey').hexdigest()
-
- nonce = self.server.nonce
+ stale = 'stale' in self.path
+ nonce = self.GetNonce(force_reset=stale)
opaque = _new_md5('opaque').hexdigest()
password = 'secret'
realm = 'testrealm'
@@ -988,6 +1004,61 @@
return True
+ def ChromiumSyncTimeHandler(self):
+ """Handle Chromium sync .../time requests.
+
+ The syncer sometimes checks server reachability by examining /time.
+ """
+ test_name = "/chromiumsync/time"
+ if not self._ShouldHandleRequest(test_name):
+ return False
+
+ self.send_response(200)
+ self.send_header('Content-type', 'text/html')
+ self.end_headers()
+ return True
+
+ def ChromiumSyncCommandHandler(self):
+ """Handle a chromiumsync command arriving via http.
+
+ This covers all sync protocol commands: authentication, getupdates, and
+ commit.
+ """
+ test_name = "/chromiumsync/command"
+ if not self._ShouldHandleRequest(test_name):
+ return False
+
+ length = int(self.headers.getheader('content-length'))
+ raw_request = self.rfile.read(length)
+
+ http_response, raw_reply = self._sync_handler.HandleCommand(raw_request)
+ self.send_response(http_response)
+ self.end_headers()
+ self.wfile.write(raw_reply)
+ return True
+
+ def MultipartHandler(self):
+ """Send a multipart response (10 text/html pages)."""
+ test_name = "/multipart"
+ if not self._ShouldHandleRequest(test_name):
+ return False
+
+ num_frames = 10
+ bound = '12345'
+ self.send_response(200)
+ self.send_header('Content-type',
+ 'multipart/x-mixed-replace;boundary=' + bound)
+ self.end_headers()
+
+ for i in xrange(num_frames):
+ self.wfile.write('--' + bound + '\r\n')
+ self.wfile.write('Content-type: text/html\r\n\r\n')
+ self.wfile.write('<title>page ' + str(i) + '</title>')
+ self.wfile.write('page ' + str(i))
+
+ self.wfile.write('--' + bound + '--')
+ return True
+
def DefaultResponseHandler(self):
"""This is the catch-all response handler for requests that aren't handled
by one of the special handlers above.
@@ -1095,13 +1166,24 @@
# Create the default path to our data dir, relative to the exe dir.
my_data_dir = os.path.dirname(sys.argv[0])
my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
- "test", "data")
+ "test", "data")
#TODO(ibrar): Must use Find* funtion defined in google\tools
#i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
return my_data_dir
+def TryKillingOldServer(port):
+ # Note that an HTTP /kill request to the FTP server has the effect of
+ # killing it.
+ for protocol in ["http", "https"]:
+ try:
+ urllib2.urlopen("%s://localhost:%d/kill" % (protocol, port)).read()
+ print "Killed old server instance on port %d (via %s)" % (port, protocol)
+ except urllib2.URLError:
+ # Common case, indicates no server running.
+ pass
+
def main(options, args):
# redirect output to a log file so it doesn't spam the unit test output
logfile = open('testserver.log', 'w')
@@ -1109,6 +1191,9 @@
port = options.port
+ # Try to free up the port if there's an orphaned old instance.
+ TryKillingOldServer(port)
+
if options.server_type == SERVER_HTTP:
if options.cert:
# let's make sure the cert file exists.
diff --git a/net/tools/tld_cleanup/README b/net/tools/tld_cleanup/README
index 8eab5ee..f696843 100644
--- a/net/tools/tld_cleanup/README
+++ b/net/tools/tld_cleanup/README
@@ -1,3 +1,12 @@
-When updating src/net/base/effective_tld_names.dat, re-run tld_cleanup which
-will re-generate src/net/base/effective_tld_names.cc. Check in the updated
-effective_tld_names.dat and effective_tld_names.cc together.
\ No newline at end of file
+When updating src/net/base/effective_tld_names.dat:
+
+1. Build tld_cleanup.exe (the "(net)" > "tld_cleanup" project)
+2. Run it (no arguments needed), typically from src/chrome/Release or
+ src/chrome/Debug. It will re-generate
+ src/net/base/effective_tld_names.gperf.
+3. Run gperf on the new effective_tld_names.gperf:
+ gperf -a -L "C++" -C -c -o -t -k '*' -NFindDomain -D -m 5 \
+ effective_tld_names.gperf > effective_tld_names.cc
+ It will produce a new effective_tld_names.cc.
+4. Check in the updated effective_tld_names.dat, effective_tld_names.gperf,
+ and effective_tld_names.cc together.
diff --git a/net/tools/tld_cleanup/tld_cleanup.cc b/net/tools/tld_cleanup/tld_cleanup.cc
index 8a427ca..e98b95d 100644
--- a/net/tools/tld_cleanup/tld_cleanup.cc
+++ b/net/tools/tld_cleanup/tld_cleanup.cc
@@ -8,9 +8,8 @@
// generate a perfect hash map. The benefit of this approach is that no time is
// spent on program initialization to generate the map of this data.
//
-// After running this program, "effective_tld_names.gperf" is generated. Run
-// gperf using the following command line:
-// gperf -a -L "C++" -C -c -o -t -k '*' -NFindDomain -D -m 5 effective_tld_names.gperf > effective_tld_names.c
+// Running this program finds "effective_tld_names.cc" in the expected location
+// in the source checkout and generates "effective_tld_names.gperf" next to it.
//
// Any errors or warnings from this program are recorded in tld_cleanup.log.
//
diff --git a/net/url_request/https_prober.cc b/net/url_request/https_prober.cc
new file mode 100644
index 0000000..a7163ba
--- /dev/null
+++ b/net/url_request/https_prober.cc
@@ -0,0 +1,80 @@
+// Copyright (c) 2009 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 "net/url_request/https_prober.h"
+
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+
+namespace net {
+
+bool HTTPSProber::HaveProbed(const std::string& host) const {
+ return probed_.find(host) != probed_.end();
+}
+
+bool HTTPSProber::InFlight(const std::string& host) const {
+ return inflight_probes_.find(host) != inflight_probes_.end();
+}
+
+bool HTTPSProber::ProbeHost(const std::string& host, URLRequestContext* ctx,
+ HTTPSProberDelegate* delegate) {
+ if (HaveProbed(host) || InFlight(host)) {
+ return false;
+ }
+
+ inflight_probes_[host] = delegate;
+
+ GURL url("https://" + host);
+ DCHECK_EQ(url.host(), host);
+
+ URLRequest* req = new URLRequest(url, this);
+ req->set_context(ctx);
+ req->Start();
+ return true;
+}
+
+void HTTPSProber::Success(URLRequest* request) {
+ DoCallback(request, true);
+}
+
+void HTTPSProber::Failure(URLRequest* request) {
+ DoCallback(request, false);
+}
+
+void HTTPSProber::DoCallback(URLRequest* request, bool result) {
+ std::map<std::string, HTTPSProberDelegate*>::iterator i =
+ inflight_probes_.find(request->original_url().host());
+ DCHECK(i != inflight_probes_.end());
+
+ HTTPSProberDelegate* delegate = i->second;
+ inflight_probes_.erase(i);
+ probed_.insert(request->original_url().host());
+ delete request;
+ delegate->ProbeComplete(result);
+}
+
+void HTTPSProber::OnAuthRequired(URLRequest* request,
+ net::AuthChallengeInfo* auth_info) {
+ Success(request);
+}
+
+void HTTPSProber::OnSSLCertificateError(URLRequest* request,
+ int cert_error,
+ net::X509Certificate* cert) {
+ request->ContinueDespiteLastError();
+}
+
+void HTTPSProber::OnResponseStarted(URLRequest* request) {
+ if (request->status().status() == URLRequestStatus::SUCCESS) {
+ Success(request);
+ } else {
+ Failure(request);
+ }
+}
+
+void HTTPSProber::OnReadCompleted(URLRequest* request, int bytes_read) {
+ NOTREACHED();
+}
+
+} // namespace net
diff --git a/net/url_request/https_prober.h b/net/url_request/https_prober.h
new file mode 100644
index 0000000..c1c9941
--- /dev/null
+++ b/net/url_request/https_prober.h
@@ -0,0 +1,75 @@
+// 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.
+
+#ifndef NET_BASE_HTTPS_PROBER_H_
+#define NET_BASE_HTTPS_PROBER_H_
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/singleton.h"
+#include "base/task.h"
+#include "net/url_request/url_request.h"
+
+class URLRequestContext;
+
+namespace net {
+
+// This should be scoped inside HTTPSProber, but VC cannot compile
+// HTTPProber::Delegate when HTTPSProber also inherits from
+// URLRequest::Delegate.
+class HTTPSProberDelegate {
+ public:
+ virtual void ProbeComplete(bool result) = 0;
+ protected:
+ virtual ~HTTPSProberDelegate() {}
+};
+
+// HTTPSProber is a singleton object that manages HTTPS probes. A HTTPS probe
+// determines if we can connect to a given host over HTTPS. It's used when
+// transparently upgrading from HTTP to HTTPS (for example, for SPDY).
+class HTTPSProber : public URLRequest::Delegate {
+ public:
+ HTTPSProber() {}
+
+ // HaveProbed returns true if the given host is known to have been probed
+ // since the browser was last started.
+ bool HaveProbed(const std::string& host) const;
+
+ // InFlight returns true iff a probe for the given host is currently active.
+ bool InFlight(const std::string& host) const;
+
+ // ProbeHost starts a new probe for the given host. If the host is known to
+ // have been probed since the browser was started, false is returned and no
+ // other action is taken. If a probe to the given host in currently inflight,
+ // false will be returned, and no other action is taken. Otherwise, a new
+ // probe is started, true is returned and the Delegate will be called with the
+ // results (true means a successful handshake).
+ bool ProbeHost(const std::string& host, URLRequestContext* ctx,
+ HTTPSProberDelegate* delegate);
+
+ // Implementation of URLRequest::Delegate
+ void OnAuthRequired(URLRequest* request,
+ net::AuthChallengeInfo* auth_info);
+ void OnSSLCertificateError(URLRequest* request,
+ int cert_error,
+ net::X509Certificate* cert);
+ void OnResponseStarted(URLRequest* request);
+ void OnReadCompleted(URLRequest* request, int bytes_read);
+
+ private:
+ void Success(URLRequest* request);
+ void Failure(URLRequest* request);
+ void DoCallback(URLRequest* request, bool result);
+
+ std::map<std::string, HTTPSProberDelegate*> inflight_probes_;
+ std::set<std::string> probed_;
+
+ friend struct DefaultSingletonTraits<HTTPSProber>;
+ DISALLOW_COPY_AND_ASSIGN(HTTPSProber);
+};
+
+} // namespace net
+#endif
diff --git a/net/url_request/url_request.cc b/net/url_request/url_request.cc
index 7a09123..9f226c2 100644
--- a/net/url_request/url_request.cc
+++ b/net/url_request/url_request.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 20010 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.
@@ -9,8 +9,8 @@
#include "base/singleton.h"
#include "base/stats_counters.h"
#include "net/base/load_flags.h"
-#include "net/base/load_log.h"
#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
#include "net/base/ssl_cert_request_info.h"
#include "net/base/upload_data.h"
#include "net/http/http_response_headers.h"
@@ -18,19 +18,33 @@
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_job.h"
#include "net/url_request/url_request_job_manager.h"
+#include "net/url_request/url_request_netlog_params.h"
using base::Time;
using net::UploadData;
using std::string;
using std::wstring;
-// Max number of http redirects to follow. Same number as gecko.
-static const int kMaxRedirects = 20;
+namespace {
-static URLRequestJobManager* GetJobManager() {
+// Max number of http redirects to follow. Same number as gecko.
+const int kMaxRedirects = 20;
+
+URLRequestJobManager* GetJobManager() {
return Singleton<URLRequestJobManager>::get();
}
+// Discard headers which have meaning in POST (Content-Length, Content-Type,
+// Origin).
+void StripPostSpecificHeaders(net::HttpRequestHeaders* headers) {
+ // These are headers that may be attached to a POST.
+ headers->RemoveHeader(net::HttpRequestHeaders::kContentLength);
+ headers->RemoveHeader(net::HttpRequestHeaders::kContentType);
+ headers->RemoveHeader(net::HttpRequestHeaders::kOrigin);
+}
+
+} // namespace
+
///////////////////////////////////////////////////////////////////////////////
// URLRequest
@@ -44,8 +58,7 @@
enable_profiling_(false),
redirect_limit_(kMaxRedirects),
final_upload_progress_(0),
- priority_(net::LOWEST),
- ALLOW_THIS_IN_INITIALIZER_LIST(request_tracker_node_(this)) {
+ priority_(net::LOWEST) {
SIMPLE_STATS_COUNTER("URLRequestCount");
// Sanity check out environment.
@@ -87,12 +100,16 @@
upload_->AppendBytes(bytes, bytes_len);
}
-void URLRequest::AppendFileRangeToUpload(const FilePath& file_path,
- uint64 offset, uint64 length) {
+void URLRequest::AppendFileRangeToUpload(
+ const FilePath& file_path,
+ uint64 offset,
+ uint64 length,
+ const base::Time& expected_modification_time) {
DCHECK(file_path.value().length() > 0 && length > 0);
if (!upload_)
upload_ = new UploadData();
- upload_->AppendFileRange(file_path, offset, length);
+ upload_->AppendFileRange(file_path, offset, length,
+ expected_modification_time);
}
void URLRequest::set_upload(net::UploadData* upload) {
@@ -121,17 +138,10 @@
NOTREACHED() << "implement me!";
}
-void URLRequest::SetExtraRequestHeaders(const string& headers) {
+void URLRequest::SetExtraRequestHeaders(
+ const net::HttpRequestHeaders& headers) {
DCHECK(!is_pending_);
- if (headers.empty()) {
- extra_request_headers_.clear();
- } else {
-#ifndef NDEBUG
- size_t crlf = headers.rfind("\r\n", headers.size() - 1);
- DCHECK(crlf != headers.size() - 2) << "headers must not end with CRLF";
-#endif
- extra_request_headers_ = headers + "\r\n";
- }
+ extra_request_headers_ = headers;
// NOTE: This method will likely become non-trivial once the other setters
// for request headers are implemented.
@@ -256,7 +266,10 @@
DCHECK(!is_pending_);
DCHECK(!job_);
- net::LoadLog::BeginEvent(load_log_, net::LoadLog::TYPE_URL_REQUEST_START);
+ net_log_.BeginEvent(
+ net::NetLog::TYPE_URL_REQUEST_START_JOB,
+ new URLRequestStartEventParameters(
+ url_, method_, load_flags_, priority_));
job_ = job;
job_->SetExtraRequestHeaders(extra_request_headers_);
@@ -327,7 +340,7 @@
// about being called recursively.
}
-bool URLRequest::Read(net::IOBuffer* dest, int dest_size, int *bytes_read) {
+bool URLRequest::Read(net::IOBuffer* dest, int dest_size, int* bytes_read) {
DCHECK(job_);
DCHECK(bytes_read);
DCHECK(!job_->is_done());
@@ -347,6 +360,11 @@
return job_->Read(dest, dest_size, bytes_read);
}
+void URLRequest::StopCaching() {
+ DCHECK(job_);
+ job_->StopCaching();
+}
+
void URLRequest::ReceivedRedirect(const GURL& location, bool* defer_redirect) {
URLRequestJob* job = GetJobManager()->MaybeInterceptRedirect(this, location);
if (job) {
@@ -357,10 +375,10 @@
}
void URLRequest::ResponseStarted() {
+ scoped_refptr<net::NetLog::EventParameters> params;
if (!status_.is_success())
- net::LoadLog::AddErrorCode(load_log_, status_.os_error());
-
- net::LoadLog::EndEvent(load_log_, net::LoadLog::TYPE_URL_REQUEST_START);
+ params = new net::NetLogIntegerParameter("net_error", status_.os_error());
+ net_log_.EndEvent(net::NetLog::TYPE_URL_REQUEST_START_JOB, params);
URLRequestJob* job = GetJobManager()->MaybeInterceptResponse(this);
if (job) {
@@ -371,8 +389,8 @@
}
void URLRequest::FollowDeferredRedirect() {
- DCHECK(job_);
- DCHECK(status_.is_success());
+ CHECK(job_);
+ CHECK(status_.is_success());
job_->FollowDeferredRedirect();
}
@@ -406,6 +424,10 @@
void URLRequest::PrepareToRestart() {
DCHECK(job_);
+ // Close the current URL_REQUEST_START_JOB, since we will be starting a new
+ // one.
+ net_log_.EndEvent(net::NetLog::TYPE_URL_REQUEST_START_JOB, NULL);
+
job_->Kill();
OrphanJob();
@@ -420,22 +442,12 @@
job_ = NULL;
}
-// static
-std::string URLRequest::StripPostSpecificHeaders(const std::string& headers) {
- // These are headers that may be attached to a POST.
- static const char* const kPostHeaders[] = {
- "content-type",
- "content-length",
- "origin"
- };
- return net::HttpUtil::StripHeaders(
- headers, kPostHeaders, arraysize(kPostHeaders));
-}
-
int URLRequest::Redirect(const GURL& location, int http_status_code) {
- if (net::LoadLog::IsUnbounded(load_log_)) {
- net::LoadLog::AddString(load_log_, StringPrintf("Redirected (%d) to %s",
- http_status_code, location.spec().c_str()));
+ if (net_log_.HasListener()) {
+ net_log_.AddEvent(
+ net::NetLog::TYPE_URL_REQUEST_REDIRECTED,
+ new net::NetLogStringParameter(
+ "location", location.possibly_invalid_spec()));
}
if (redirect_limit_ <= 0) {
DLOG(INFO) << "disallowing redirect: exceeds limit";
@@ -476,10 +488,7 @@
// the inclusion of a multipart Content-Type header in GET can cause
// problems with some servers:
// http://code.google.com/p/chromium/issues/detail?id=843
- //
- // TODO(eroman): It would be better if this data was structured into
- // specific fields/flags, rather than a stew of extra headers.
- extra_request_headers_ = StripPostSpecificHeaders(extra_request_headers_);
+ StripPostSpecificHeaders(&extra_request_headers_);
}
if (!final_upload_progress_)
@@ -499,18 +508,15 @@
context_ = context;
- // If the context this request belongs to has changed, update the tracker(s).
+ // If the context this request belongs to has changed, update the tracker.
if (prev_context != context) {
- if (prev_context)
- prev_context->url_request_tracker()->Remove(this);
- if (context) {
- if (!load_log_) {
- // Create the LoadLog -- we waited until now to create it so we know
- // what constraints the URLRequestContext is enforcing on log levels.
- load_log_ = context->url_request_tracker()->CreateLoadLog();
- }
+ net_log_.EndEvent(net::NetLog::TYPE_REQUEST_ALIVE, NULL);
+ net_log_ = net::BoundNetLog();
- context->url_request_tracker()->Add(this);
+ if (context) {
+ net_log_ = net::BoundNetLog::Make(context->net_log(),
+ net::NetLog::SOURCE_URL_REQUEST);
+ net_log_.BeginEvent(net::NetLog::TYPE_REQUEST_ALIVE, NULL);
}
}
}
@@ -533,10 +539,3 @@
void URLRequest::SetUserData(const void* key, UserData* data) {
user_data_[key] = linked_ptr<UserData>(data);
}
-
-void URLRequest::GetInfoForTracker(
- RequestTracker<URLRequest>::RecentRequestInfo* info) const {
- DCHECK(info);
- info->original_url = original_url_;
- info->load_log = load_log_;
-}
diff --git a/net/url_request/url_request.h b/net/url_request/url_request.h
index e586e28..7256ec0 100644
--- a/net/url_request/url_request.h
+++ b/net/url_request/url_request.h
@@ -10,17 +10,16 @@
#include <vector>
#include "base/leak_tracker.h"
-#include "base/linked_list.h"
#include "base/linked_ptr.h"
#include "base/logging.h"
+#include "base/non_thread_safe.h"
#include "base/ref_counted.h"
-#include "base/scoped_ptr.h"
#include "googleurl/src/gurl.h"
-#include "net/base/load_log.h"
#include "net/base/load_states.h"
+#include "net/base/net_log.h"
#include "net/base/request_priority.h"
+#include "net/http/http_request_headers.h"
#include "net/http/http_response_info.h"
-#include "net/url_request/request_tracker.h"
#include "net/url_request/url_request_status.h"
namespace base {
@@ -54,7 +53,7 @@
//
// NOTE: All usage of all instances of this class should be on the same thread.
//
-class URLRequest {
+class URLRequest : public NonThreadSafe {
public:
// Derive from this class and add your own data members to associate extra
// information with a URLRequest. Use GetUserData(key) and SetUserData()
@@ -187,6 +186,20 @@
request->Cancel();
}
+ // Called when reading cookies. |blocked_by_policy| is true if access to
+ // cookies was denied due to content settings. This method will never be
+ // invoked when LOAD_DO_NOT_SEND_COOKIES is specified.
+ virtual void OnGetCookies(URLRequest* request, bool blocked_by_policy) {
+ }
+
+ // Called when a cookie is set. |blocked_by_policy| is true if the cookie
+ // was rejected due to content settings. This method will never be invoked
+ // when LOAD_DO_NOT_SAVE_COOKIES is specified.
+ virtual void OnSetCookie(URLRequest* request,
+ const std::string& cookie_line,
+ bool blocked_by_policy) {
+ }
+
// After calling Start(), the delegate will receive an OnResponseStarted
// callback when the request has completed. If an error occurred, the
// request->status() will be set. On success, all redirects have been
@@ -292,12 +305,16 @@
//
// When uploading data, bytes_len must be non-zero.
// When uploading a file range, length must be non-zero. If length
- // exceeds the end-of-file, the upload is clipped at end-of-file.
+ // exceeds the end-of-file, the upload is clipped at end-of-file. If the
+ // expected modification time is provided (non-zero), it will be used to
+ // check if the underlying file has been changed or not. The granularity of
+ // the time comparison is 1 second since time_t precision is used in WebKit.
void AppendBytesToUpload(const char* bytes, int bytes_len);
void AppendFileRangeToUpload(const FilePath& file_path,
- uint64 offset, uint64 length);
+ uint64 offset, uint64 length,
+ const base::Time& expected_modification_time);
void AppendFileToUpload(const FilePath& file_path) {
- AppendFileRangeToUpload(file_path, 0, kuint64max);
+ AppendFileRangeToUpload(file_path, 0, kuint64max, base::Time());
}
// Set the upload data directly.
@@ -316,17 +333,14 @@
void SetExtraRequestHeaderByName(const std::string& name,
const std::string& value, bool overwrite);
- // Sets all extra request headers, from a \r\n-delimited string. Any extra
- // request headers set by other methods are overwritten by this method. This
- // method may only be called before Start() is called. It is an error to
- // call it later.
- //
- // Note: \r\n is only used to separate the headers in the string if there
- // are multiple headers. The last header in the string must not be followed
- // by \r\n.
- void SetExtraRequestHeaders(const std::string& headers);
+ // Sets all extra request headers. Any extra request headers set by other
+ // methods are overwritten by this method. This method may only be called
+ // before Start() is called. It is an error to call it later.
+ void SetExtraRequestHeaders(const net::HttpRequestHeaders& headers);
- const std::string& extra_request_headers() { return extra_request_headers_; }
+ const net::HttpRequestHeaders& extra_request_headers() const {
+ return extra_request_headers_;
+ }
// Returns the current load state for the request.
net::LoadState GetLoadState() const;
@@ -362,11 +376,29 @@
// Indicate if this response was fetched from disk cache.
bool was_cached() const { return response_info_.was_cached; }
- // Returns true if the URLRequest was delivered with SPDY.
+ // True if response could use alternate protocol. However, browser will
+ // ingore the alternate protocol if spdy is not enabled.
bool was_fetched_via_spdy() const {
return response_info_.was_fetched_via_spdy;
}
+ // Returns true if the URLRequest was delivered after NPN is negotiated,
+ // using either SPDY or HTTP.
+ bool was_npn_negotiated() const {
+ return response_info_.was_npn_negotiated;
+ }
+
+ // Returns true if the URLRequest was delivered when the alertnate protocol
+ // is available.
+ bool was_alternate_protocol_available() const {
+ return response_info_.was_alternate_protocol_available;
+ }
+
+ // Returns true if the URLRequest was delivered through a proxy.
+ bool was_fetched_via_proxy() const {
+ return response_info_.was_fetched_via_proxy;
+ }
+
// Get all response headers, as a HttpResponseHeaders object. See comments
// in HttpResponseHeaders class as to the format of the data.
net::HttpResponseHeaders* response_headers() const;
@@ -454,7 +486,14 @@
//
// If a read error occurs, Read returns false and the request->status
// will be set to an error.
- bool Read(net::IOBuffer* buf, int max_bytes, int *bytes_read);
+ bool Read(net::IOBuffer* buf, int max_bytes, int* bytes_read);
+
+ // If this request is being cached by the HTTP cache, stop subsequent caching.
+ // Note that this method has no effect on other (simultaneous or not) requests
+ // for the same resource. The typical example is a request that results in
+ // the data being stored to disk (downloaded instead of rendered) so we don't
+ // want to store it twice.
+ void StopCaching();
// This method may be called to follow a redirect that was deferred in
// response to an OnReceivedRedirect call.
@@ -495,7 +534,7 @@
URLRequestContext* context();
void set_context(URLRequestContext* context);
- net::LoadLog* load_log() { return load_log_; }
+ const net::BoundNetLog& net_log() const { return net_log_; }
// Returns the expected content size if available
int64 GetExpectedContentSize() const;
@@ -536,13 +575,12 @@
private:
friend class URLRequestJob;
- friend class RequestTracker<URLRequest>;
void StartJob(URLRequestJob* job);
// Restarting involves replacing the current job with a new one such as what
// happens when following a HTTP redirect.
- void RestartWithJob(URLRequestJob *job);
+ void RestartWithJob(URLRequestJob* job);
void PrepareToRestart();
// Detaches the job from this request in preparation for this object going
@@ -554,22 +592,13 @@
// passed values.
void DoCancel(int os_error, const net::SSLInfo& ssl_info);
- // Discard headers which have meaning in POST (Content-Length, Content-Type,
- // Origin).
- static std::string StripPostSpecificHeaders(const std::string& headers);
-
- // Gets the goodies out of this that we want to show the user later on the
- // chrome://net-internals/ page.
- void GetInfoForTracker(
- RequestTracker<URLRequest>::RecentRequestInfo* info) const;
-
// Contextual information used for this request (can be NULL). This contains
// most of the dependencies which are shared between requests (disk cache,
// cookie store, socket poool, etc.)
scoped_refptr<URLRequestContext> context_;
// Tracks the time spent in various load states throughout this request.
- scoped_refptr<net::LoadLog> load_log_;
+ net::BoundNetLog net_log_;
scoped_refptr<URLRequestJob> job_;
scoped_refptr<net::UploadData> upload_;
@@ -578,7 +607,7 @@
GURL first_party_for_cookies_;
std::string method_; // "GET", "POST", etc. Should be all uppercase.
std::string referrer_;
- std::string extra_request_headers_;
+ net::HttpRequestHeaders extra_request_headers_;
int load_flags_; // Flags indicating the request type for the load;
// expected values are LOAD_* enums above.
@@ -616,7 +645,6 @@
// this to determine which URLRequest to allocate sockets to first.
net::RequestPriority priority_;
- RequestTracker<URLRequest>::Node request_tracker_node_;
base::LeakTracker<URLRequest> leak_tracker_;
DISALLOW_COPY_AND_ASSIGN(URLRequest);
diff --git a/net/url_request/url_request_context.h b/net/url_request/url_request_context.h
index d72f84d..0f7fd51 100644
--- a/net/url_request/url_request_context.h
+++ b/net/url_request/url_request_context.h
@@ -10,35 +10,46 @@
#ifndef NET_URL_REQUEST_URL_REQUEST_CONTEXT_H_
#define NET_URL_REQUEST_URL_REQUEST_CONTEXT_H_
+#include "base/non_thread_safe.h"
#include "base/ref_counted.h"
#include "base/string_util.h"
#include "net/base/cookie_store.h"
#include "net/base/host_resolver.h"
+#include "net/base/net_log.h"
#include "net/base/ssl_config_service.h"
#include "net/base/transport_security_state.h"
#include "net/ftp/ftp_auth_cache.h"
#include "net/proxy/proxy_service.h"
-#include "net/url_request/request_tracker.h"
namespace net {
class CookiePolicy;
class FtpTransactionFactory;
+class HttpAuthHandlerFactory;
+class HttpNetworkDelegate;
class HttpTransactionFactory;
class SocketStream;
}
class URLRequest;
// Subclass to provide application-specific context for URLRequest instances.
-class URLRequestContext :
- public base::RefCountedThreadSafe<URLRequestContext> {
+class URLRequestContext
+ : public base::RefCountedThreadSafe<URLRequestContext>,
+ public NonThreadSafe {
public:
URLRequestContext()
- : http_transaction_factory_(NULL),
+ : net_log_(NULL),
+ http_transaction_factory_(NULL),
ftp_transaction_factory_(NULL),
+ http_auth_handler_factory_(NULL),
+ network_delegate_(NULL),
cookie_policy_(NULL),
transport_security_state_(NULL) {
}
+ net::NetLog* net_log() const {
+ return net_log_;
+ }
+
net::HostResolver* host_resolver() const {
return host_resolver_;
}
@@ -77,22 +88,18 @@
// Gets the FTP authentication cache for this context.
net::FtpAuthCache* ftp_auth_cache() { return &ftp_auth_cache_; }
+ // Gets the HTTP Authentication Handler Factory for this context.
+ // The factory is only valid for the lifetime of this URLRequestContext
+ net::HttpAuthHandlerFactory* http_auth_handler_factory() {
+ return http_auth_handler_factory_;
+ }
+
// Gets the value of 'Accept-Charset' header field.
const std::string& accept_charset() const { return accept_charset_; }
// Gets the value of 'Accept-Language' header field.
const std::string& accept_language() const { return accept_language_; }
- // Gets the tracker for URLRequests associated with this context.
- RequestTracker<URLRequest>* url_request_tracker() {
- return &url_request_tracker_;
- }
-
- // Gets the tracker for SocketStreams associated with this context.
- RequestTracker<net::SocketStream>* socket_stream_tracker() {
- return &socket_stream_tracker_;
- }
-
// Gets the UA string to use for the given URL. Pass an invalid URL (such as
// GURL()) to get the default UA string. Subclasses should override this
// method to provide a UA string.
@@ -107,20 +114,6 @@
referrer_charset_ = charset;
}
- // Called before adding cookies to requests. Returns true if cookie can
- // be added to the request. The cookie might still be modified though.
- virtual bool InterceptRequestCookies(const URLRequest* request,
- const std::string& cookies) const {
- return true;
- }
-
- // Called before adding cookies from respones to the cookie monster. Returns
- // true if the cookie can be added. The cookie might still be modified though.
- virtual bool InterceptResponseCookie(const URLRequest* request,
- const std::string& cookie) const {
- return true;
- }
-
protected:
friend class base::RefCountedThreadSafe<URLRequestContext>;
@@ -128,11 +121,14 @@
// The following members are expected to be initialized and owned by
// subclasses.
+ net::NetLog* net_log_;
scoped_refptr<net::HostResolver> host_resolver_;
scoped_refptr<net::ProxyService> proxy_service_;
scoped_refptr<net::SSLConfigService> ssl_config_service_;
net::HttpTransactionFactory* http_transaction_factory_;
net::FtpTransactionFactory* ftp_transaction_factory_;
+ net::HttpAuthHandlerFactory* http_auth_handler_factory_;
+ net::HttpNetworkDelegate* network_delegate_;
scoped_refptr<net::CookieStore> cookie_store_;
net::CookiePolicy* cookie_policy_;
scoped_refptr<net::TransportSecurityState> transport_security_state_;
@@ -144,12 +140,6 @@
// filename for file download.
std::string referrer_charset_;
- // Tracks the requests associated with this context.
- RequestTracker<URLRequest> url_request_tracker_;
-
- // Trakcs the socket streams associated with this context.
- RequestTracker<net::SocketStream> socket_stream_tracker_;
-
private:
DISALLOW_COPY_AND_ASSIGN(URLRequestContext);
};
diff --git a/net/url_request/url_request_file_dir_job.cc b/net/url_request/url_request_file_dir_job.cc
index 07977b5..19a1aaf 100644
--- a/net/url_request/url_request_file_dir_job.cc
+++ b/net/url_request/url_request_file_dir_job.cc
@@ -6,8 +6,8 @@
#include "base/file_util.h"
#include "base/message_loop.h"
-#include "base/string_util.h"
#include "base/sys_string_conversions.h"
+#include "base/utf_string_conversions.h"
#include "base/time.h"
#include "googleurl/src/gurl.h"
#include "net/base/io_buffer.h"
@@ -211,24 +211,3 @@
}
}
}
-
-bool URLRequestFileDirJob::IsRedirectResponse(
- GURL* location, int* http_status_code) {
- // If the URL did not have a trailing slash, treat the response as a redirect
- // to the URL with a trailing slash appended.
- std::string path = request_->url().path();
- if (path.empty() || (path[path.size() - 1] != '/')) {
- // This happens when we discovered the file is a directory, so needs a
- // slash at the end of the path.
- std::string new_path = path;
- new_path.push_back('/');
- GURL::Replacements replacements;
- replacements.SetPathStr(new_path);
-
- *location = request_->url().ReplaceComponents(replacements);
- *http_status_code = 301; // simulate a permanent redirect
- return true;
- }
-
- return false;
-}
diff --git a/net/url_request/url_request_file_dir_job.h b/net/url_request/url_request_file_dir_job.h
index 24a6c72..0322f10 100644
--- a/net/url_request/url_request_file_dir_job.h
+++ b/net/url_request/url_request_file_dir_job.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -25,7 +25,6 @@
virtual bool ReadRawData(net::IOBuffer* buf, int buf_size, int *bytes_read);
virtual bool GetMimeType(std::string* mime_type) const;
virtual bool GetCharset(std::string* charset);
- virtual bool IsRedirectResponse(GURL* location, int* http_status_code);
// DirectoryLister::DirectoryListerDelegate methods:
virtual void OnListFile(const file_util::FileEnumerator::FindInfo& data);
@@ -63,7 +62,7 @@
scoped_refptr<net::IOBuffer> read_buffer_;
int read_buffer_length_;
- DISALLOW_EVIL_CONSTRUCTORS(URLRequestFileDirJob);
+ DISALLOW_COPY_AND_ASSIGN(URLRequestFileDirJob);
};
#endif // NET_URL_REQUEST_URL_REQUEST_FILE_DIR_JOB_H__
diff --git a/net/url_request/url_request_file_job.cc b/net/url_request/url_request_file_job.cc
index 8e1b7dc..003a29d 100644
--- a/net/url_request/url_request_file_job.cc
+++ b/net/url_request/url_request_file_job.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2006-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.
@@ -80,8 +80,15 @@
URLRequestJob* URLRequestFileJob::Factory(
URLRequest* request, const std::string& scheme) {
FilePath file_path;
+
+ // We need to decide whether to create URLRequestFileJob for file access or
+ // URLRequestFileDirJob for directory access. To avoid accessing the
+ // filesystem, we only look at the path string here.
+ // The code in the URLRequestFileJob::Start() method discovers that a path,
+ // which doesn't end with a slash, should really be treated as a directory,
+ // and it then redirects to the URLRequestFileDirJob.
if (net::FileURLToFilePath(request->url(), &file_path) &&
- file_util::EnsureEndsWithSeparator(&file_path) &&
+ file_util::EndsWithSeparator(file_path) &&
file_path.IsAbsolute())
return new URLRequestFileDirJob(request, file_path);
@@ -188,18 +195,23 @@
return net::GetMimeTypeFromFile(file_path_, mime_type);
}
-void URLRequestFileJob::SetExtraRequestHeaders(const std::string& headers) {
- // We only care about "Range" header here.
- std::vector<net::HttpByteRange> ranges;
- if (net::HttpUtil::ParseRanges(headers, &ranges)) {
- if (ranges.size() == 1) {
- byte_range_ = ranges[0];
- } else {
- // We don't support multiple range requests in one single URL request,
- // because we need to do multipart encoding here.
- // TODO(hclam): decide whether we want to support multiple range requests.
- NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
- net::ERR_REQUEST_RANGE_NOT_SATISFIABLE));
+void URLRequestFileJob::SetExtraRequestHeaders(
+ const net::HttpRequestHeaders& headers) {
+ std::string range_header;
+ if (headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header)) {
+ // We only care about "Range" header here.
+ std::vector<net::HttpByteRange> ranges;
+ if (net::HttpUtil::ParseRangeHeader(range_header, &ranges)) {
+ if (ranges.size() == 1) {
+ byte_range_ = ranges[0];
+ } else {
+ // We don't support multiple range requests in one single URL request,
+ // because we need to do multipart encoding here.
+ // TODO(hclam): decide whether we want to support multiple range
+ // requests.
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
+ net::ERR_REQUEST_RANGE_NOT_SATISFIABLE));
+ }
}
}
}
@@ -214,15 +226,20 @@
if (!request_)
return;
+ is_directory_ = file_info.is_directory;
+
int rv = net::OK;
- // We use URLRequestFileJob to handle valid and invalid files as well as
- // invalid directories. For a directory to be invalid, it must either not
- // exist, or be "\" on Windows. (Windows resolves "\" to "C:\", thus
- // reporting it as existent.) On POSIX, we don't count any existent
- // directory as invalid.
- if (!exists || file_info.is_directory) {
+ // We use URLRequestFileJob to handle files as well as directories without
+ // trailing slash.
+ // If a directory does not exist, we return ERR_FILE_NOT_FOUND. Otherwise,
+ // we will append trailing slash and redirect to FileDirJob.
+ // A special case is "\" on Windows. We should resolve as invalid.
+ // However, Windows resolves "\" to "C:\", thus reports it as existent.
+ // So what happens is we append it with trailing slash and redirect it to
+ // FileDirJob where it is resolved as invalid.
+ if (!exists) {
rv = net::ERR_FILE_NOT_FOUND;
- } else {
+ } else if (!is_directory_) {
int flags = base::PLATFORM_FILE_OPEN |
base::PLATFORM_FILE_READ |
base::PLATFORM_FILE_ASYNC;
@@ -273,15 +290,25 @@
NotifyReadComplete(result);
}
-bool URLRequestFileJob::IsRedirectResponse(
- GURL* location, int* http_status_code) {
-#if defined(OS_WIN)
- std::wstring extension =
- file_util::GetFileExtensionFromPath(file_path_.value());
+bool URLRequestFileJob::IsRedirectResponse(GURL* location,
+ int* http_status_code) {
+ if (is_directory_) {
+ // This happens when we discovered the file is a directory, so needs a
+ // slash at the end of the path.
+ std::string new_path = request_->url().path();
+ new_path.push_back('/');
+ GURL::Replacements replacements;
+ replacements.SetPathStr(new_path);
+ *location = request_->url().ReplaceComponents(replacements);
+ *http_status_code = 301; // simulate a permanent redirect
+ return true;
+ }
+
+#if defined(OS_WIN)
// Follow a Windows shortcut.
// We just resolve .lnk file, ignore others.
- if (!LowerCaseEqualsASCII(extension, "lnk"))
+ if (!LowerCaseEqualsASCII(file_path_.Extension(), ".lnk"))
return false;
FilePath new_path = file_path_;
diff --git a/net/url_request/url_request_file_job.h b/net/url_request/url_request_file_job.h
index 8cb28bd..aed6859 100644
--- a/net/url_request/url_request_file_job.h
+++ b/net/url_request/url_request_file_job.h
@@ -30,7 +30,7 @@
virtual bool GetContentEncodings(
std::vector<Filter::FilterType>* encoding_type);
virtual bool GetMimeType(std::string* mime_type) const;
- virtual void SetExtraRequestHeaders(const std::string& headers);
+ virtual void SetExtraRequestHeaders(const net::HttpRequestHeaders& headers);
static URLRequest::ProtocolFactory Factory;
diff --git a/net/url_request/url_request_filter.h b/net/url_request/url_request_filter.h
index 3f255b8..d81e68c 100644
--- a/net/url_request/url_request_filter.h
+++ b/net/url_request/url_request_filter.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
//
@@ -79,7 +79,7 @@
// Singleton instance.
static URLRequestFilter* shared_instance_;
- DISALLOW_EVIL_CONSTRUCTORS(URLRequestFilter);
+ DISALLOW_COPY_AND_ASSIGN(URLRequestFilter);
};
#endif // NET_URL_REQUEST_URL_REQUEST_FILTER_H_
diff --git a/net/url_request/url_request_ftp_job.cc b/net/url_request/url_request_ftp_job.cc
new file mode 100644
index 0000000..f8746fc
--- /dev/null
+++ b/net/url_request/url_request_ftp_job.cc
@@ -0,0 +1,244 @@
+// Copyright (c) 2009 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 "net/url_request/url_request_ftp_job.h"
+
+#include "base/compiler_specific.h"
+#include "base/message_loop.h"
+#include "net/base/auth.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/ftp/ftp_response_info.h"
+#include "net/ftp/ftp_transaction_factory.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_error_job.h"
+
+URLRequestFtpJob::URLRequestFtpJob(URLRequest* request)
+ : URLRequestJob(request),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ start_callback_(this, &URLRequestFtpJob::OnStartCompleted)),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ read_callback_(this, &URLRequestFtpJob::OnReadCompleted)),
+ read_in_progress_(false),
+ context_(request->context()) {
+}
+
+URLRequestFtpJob::~URLRequestFtpJob() {
+}
+
+// static
+URLRequestJob* URLRequestFtpJob::Factory(URLRequest* request,
+ const std::string& scheme) {
+ DCHECK_EQ(scheme, "ftp");
+
+ int port = request->url().IntPort();
+ if (request->url().has_port() &&
+ !net::IsPortAllowedByFtp(port) && !net::IsPortAllowedByOverride(port))
+ return new URLRequestErrorJob(request, net::ERR_UNSAFE_PORT);
+
+ DCHECK(request->context());
+ DCHECK(request->context()->ftp_transaction_factory());
+ return new URLRequestFtpJob(request);
+}
+
+bool URLRequestFtpJob::GetMimeType(std::string* mime_type) const {
+ if (transaction_->GetResponseInfo()->is_directory_listing) {
+ *mime_type = "text/vnd.chromium.ftp-dir";
+ return true;
+ }
+ return false;
+}
+
+void URLRequestFtpJob::Start() {
+ DCHECK(!transaction_.get());
+ request_info_.url = request_->url();
+ StartTransaction();
+}
+
+void URLRequestFtpJob::Kill() {
+ if (!transaction_.get())
+ return;
+ DestroyTransaction();
+ URLRequestJob::Kill();
+}
+
+net::LoadState URLRequestFtpJob::GetLoadState() const {
+ return transaction_.get() ?
+ transaction_->GetLoadState() : net::LOAD_STATE_IDLE;
+}
+
+bool URLRequestFtpJob::NeedsAuth() {
+ // Note that we only have to worry about cases where an actual FTP server
+ // requires auth (and not a proxy), because connecting to FTP via proxy
+ // effectively means the browser communicates via HTTP, and uses HTTP's
+ // Proxy-Authenticate protocol when proxy servers require auth.
+ return server_auth_ && server_auth_->state == net::AUTH_STATE_NEED_AUTH;
+}
+
+void URLRequestFtpJob::GetAuthChallengeInfo(
+ scoped_refptr<net::AuthChallengeInfo>* result) {
+ DCHECK((server_auth_ != NULL) &&
+ (server_auth_->state == net::AUTH_STATE_NEED_AUTH));
+ scoped_refptr<net::AuthChallengeInfo> auth_info = new net::AuthChallengeInfo;
+ auth_info->is_proxy = false;
+ auth_info->host_and_port = ASCIIToWide(
+ net::GetHostAndPort(request_->url()));
+ auth_info->scheme = L"";
+ auth_info->realm = L"";
+ result->swap(auth_info);
+}
+
+void URLRequestFtpJob::SetAuth(const std::wstring& username,
+ const std::wstring& password) {
+ DCHECK(NeedsAuth());
+ server_auth_->state = net::AUTH_STATE_HAVE_AUTH;
+ server_auth_->username = username;
+ server_auth_->password = password;
+
+ request_->context()->ftp_auth_cache()->Add(request_->url().GetOrigin(),
+ username, password);
+
+ RestartTransactionWithAuth();
+}
+
+void URLRequestFtpJob::CancelAuth() {
+ DCHECK(NeedsAuth());
+ server_auth_->state = net::AUTH_STATE_CANCELED;
+
+ // Once the auth is cancelled, we proceed with the request as though
+ // there were no auth. Schedule this for later so that we don't cause
+ // any recursing into the caller as a result of this call.
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &URLRequestFtpJob::OnStartCompleted, net::OK));
+}
+
+bool URLRequestFtpJob::ReadRawData(net::IOBuffer* buf,
+ int buf_size,
+ int *bytes_read) {
+ DCHECK_NE(buf_size, 0);
+ DCHECK(bytes_read);
+ DCHECK(!read_in_progress_);
+
+ int rv = transaction_->Read(buf, buf_size, &read_callback_);
+ if (rv >= 0) {
+ *bytes_read = rv;
+ return true;
+ }
+
+ if (rv == net::ERR_IO_PENDING) {
+ read_in_progress_ = true;
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+ } else {
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv));
+ }
+ return false;
+}
+
+void URLRequestFtpJob::OnStartCompleted(int result) {
+ // If the request was destroyed, then there is no more work to do.
+ if (!request_ || !request_->delegate())
+ return;
+ // If the transaction was destroyed, then the job was cancelled, and
+ // we can just ignore this notification.
+ if (!transaction_.get())
+ return;
+ // Clear the IO_PENDING status
+ SetStatus(URLRequestStatus());
+
+ // FTP obviously doesn't have HTTP Content-Length header. We have to pass
+ // the content size information manually.
+ set_expected_content_size(
+ transaction_->GetResponseInfo()->expected_content_size);
+
+ if (result == net::OK) {
+ NotifyHeadersComplete();
+ } else if (transaction_->GetResponseInfo()->needs_auth) {
+ GURL origin = request_->url().GetOrigin();
+ if (server_auth_ && server_auth_->state == net::AUTH_STATE_HAVE_AUTH) {
+ request_->context()->ftp_auth_cache()->Remove(origin,
+ server_auth_->username,
+ server_auth_->password);
+ } else if (!server_auth_) {
+ server_auth_ = new net::AuthData();
+ }
+ server_auth_->state = net::AUTH_STATE_NEED_AUTH;
+
+ net::FtpAuthCache::Entry* cached_auth =
+ request_->context()->ftp_auth_cache()->Lookup(origin);
+
+ if (cached_auth) {
+ // Retry using cached auth data.
+ SetAuth(cached_auth->username, cached_auth->password);
+ } else {
+ // Prompt for a username/password.
+ NotifyHeadersComplete();
+ }
+ } else {
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result));
+ }
+}
+
+void URLRequestFtpJob::OnReadCompleted(int result) {
+ read_in_progress_ = false;
+ if (result == 0) {
+ NotifyDone(URLRequestStatus());
+ } else if (result < 0) {
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result));
+ } else {
+ // Clear the IO_PENDING status
+ SetStatus(URLRequestStatus());
+ }
+ NotifyReadComplete(result);
+}
+
+void URLRequestFtpJob::RestartTransactionWithAuth() {
+ DCHECK(server_auth_ && server_auth_->state == net::AUTH_STATE_HAVE_AUTH);
+
+ // No matter what, we want to report our status as IO pending since we will
+ // be notifying our consumer asynchronously via OnStartCompleted.
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+
+ int rv = transaction_->RestartWithAuth(server_auth_->username,
+ server_auth_->password,
+ &start_callback_);
+ if (rv == net::ERR_IO_PENDING)
+ return;
+
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &URLRequestFtpJob::OnStartCompleted, rv));
+}
+
+void URLRequestFtpJob::StartTransaction() {
+ // Create a transaction.
+ DCHECK(!transaction_.get());
+ DCHECK(request_->context());
+ DCHECK(request_->context()->ftp_transaction_factory());
+
+ transaction_.reset(
+ request_->context()->ftp_transaction_factory()->CreateTransaction());
+
+ // No matter what, we want to report our status as IO pending since we will
+ // be notifying our consumer asynchronously via OnStartCompleted.
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+ int rv;
+ if (transaction_.get()) {
+ rv = transaction_->Start(
+ &request_info_, &start_callback_, request_->net_log());
+ if (rv == net::ERR_IO_PENDING)
+ return;
+ } else {
+ rv = net::ERR_FAILED;
+ }
+ // The transaction started synchronously, but we need to notify the
+ // URLRequest delegate via the message loop.
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &URLRequestFtpJob::OnStartCompleted, rv));
+}
+
+void URLRequestFtpJob::DestroyTransaction() {
+ DCHECK(transaction_.get());
+
+ transaction_.reset();
+}
diff --git a/net/url_request/url_request_ftp_job.h b/net/url_request/url_request_ftp_job.h
new file mode 100644
index 0000000..453543f
--- /dev/null
+++ b/net/url_request/url_request_ftp_job.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2009 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_FTP_JOB_H_
+#define NET_URL_REQUEST_URL_REQUEST_FTP_JOB_H_
+
+#include <string>
+
+#include "net/base/auth.h"
+#include "net/base/completion_callback.h"
+#include "net/ftp/ftp_request_info.h"
+#include "net/ftp/ftp_transaction.h"
+#include "net/url_request/url_request_job.h"
+
+class URLRequestContext;
+
+namespace net {
+struct list_state;
+}
+
+// A URLRequestJob subclass that is built on top of FtpTransaction. It
+// provides an implementation for FTP.
+class URLRequestFtpJob : public URLRequestJob {
+ public:
+
+ explicit URLRequestFtpJob(URLRequest* request);
+
+ static URLRequestJob* Factory(URLRequest* request, const std::string& scheme);
+
+ // URLRequestJob methods:
+ virtual bool GetMimeType(std::string* mime_type) const;
+
+ private:
+ virtual ~URLRequestFtpJob();
+
+ // URLRequestJob methods:
+ virtual void Start();
+ virtual void Kill();
+ virtual net::LoadState GetLoadState() const;
+ virtual bool NeedsAuth();
+ virtual void GetAuthChallengeInfo(
+ scoped_refptr<net::AuthChallengeInfo>* auth_info);
+ virtual void SetAuth(const std::wstring& username,
+ const std::wstring& password);
+ virtual void CancelAuth();
+
+ // TODO(ibrar): Yet to give another look at this function.
+ virtual uint64 GetUploadProgress() const { return 0; }
+ virtual bool ReadRawData(net::IOBuffer* buf, int buf_size, int *bytes_read);
+
+ void DestroyTransaction();
+ void StartTransaction();
+
+ void OnStartCompleted(int result);
+ void OnReadCompleted(int result);
+
+ void RestartTransactionWithAuth();
+
+ void LogFtpServerType(char server_type);
+
+ net::FtpRequestInfo request_info_;
+ scoped_ptr<net::FtpTransaction> transaction_;
+
+ net::CompletionCallbackImpl<URLRequestFtpJob> start_callback_;
+ net::CompletionCallbackImpl<URLRequestFtpJob> read_callback_;
+
+ bool read_in_progress_;
+
+ scoped_refptr<net::AuthData> server_auth_;
+
+ // Keep a reference to the url request context to be sure it's not deleted
+ // before us.
+ scoped_refptr<URLRequestContext> context_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestFtpJob);
+};
+
+#endif // NET_URL_REQUEST_URL_REQUEST_FTP_JOB_H_
diff --git a/net/url_request/url_request_http_job.cc b/net/url_request/url_request_http_job.cc
index 25b0f33..9a03213 100644
--- a/net/url_request/url_request_http_job.cc
+++ b/net/url_request/url_request_http_job.cc
@@ -15,23 +15,26 @@
#include "net/base/cert_status_flags.h"
#include "net/base/cookie_policy.h"
#include "net/base/filter.h"
-#include "net/base/https_prober.h"
#include "net/base/transport_security_state.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/base/net_util.h"
#include "net/base/sdch_manager.h"
#include "net/base/ssl_cert_request_info.h"
+#include "net/http/http_request_headers.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_response_info.h"
#include "net/http/http_transaction.h"
#include "net/http/http_transaction_factory.h"
#include "net/http/http_util.h"
+#include "net/url_request/https_prober.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_error_job.h"
#include "net/url_request/url_request_redirect_job.h"
+static const char kAvailDictionaryHeader[] = "Avail-Dictionary";
+
// TODO(darin): make sure the port blocking code is not lost
// static
URLRequestJob* URLRequestHttpJob::Factory(URLRequest* request,
@@ -128,9 +131,9 @@
}
void URLRequestHttpJob::SetExtraRequestHeaders(
- const std::string& headers) {
+ const net::HttpRequestHeaders& headers) {
DCHECK(!transaction_.get()) << "cannot change once started";
- request_info_.extra_headers = headers;
+ request_info_.extra_headers.CopyFrom(headers);
}
void URLRequestHttpJob::Start() {
@@ -146,8 +149,9 @@
request_info_.priority = request_->priority();
if (request_->context()) {
- request_info_.user_agent =
- request_->context()->GetUserAgent(request_->url());
+ request_info_.extra_headers.SetHeader(
+ net::HttpRequestHeaders::kUserAgent,
+ request_->context()->GetUserAgent(request_->url()));
}
AddExtraHeaders();
@@ -334,9 +338,8 @@
// Update the cookies, since the cookie store may have been updated from the
// headers in the 401/407. Since cookies were already appended to
// extra_headers, we need to strip them out before adding them again.
- static const char* const cookie_name[] = { "cookie" };
- request_info_.extra_headers = net::HttpUtil::StripHeaders(
- request_info_.extra_headers, cookie_name, arraysize(cookie_name));
+ request_info_.extra_headers.RemoveHeader(
+ net::HttpRequestHeaders::kCookie);
AddCookieHeaderAndStart();
}
@@ -429,20 +432,34 @@
return false;
}
+void URLRequestHttpJob::StopCaching() {
+ if (transaction_.get())
+ transaction_->StopCaching();
+}
+
void URLRequestHttpJob::OnCanGetCookiesCompleted(int policy) {
// If the request was destroyed, then there is no more work to do.
if (request_ && request_->delegate()) {
- if (policy == net::OK && request_->context()->cookie_store()) {
+ if (policy == net::ERR_ACCESS_DENIED) {
+ request_->delegate()->OnGetCookies(request_, true);
+ } else if (policy == net::OK && request_->context()->cookie_store()) {
+ request_->delegate()->OnGetCookies(request_, false);
net::CookieOptions options;
options.set_include_httponly();
std::string cookies =
request_->context()->cookie_store()->GetCookiesWithOptions(
request_->url(), options);
- if (request_->context()->InterceptRequestCookies(request_, cookies) &&
- !cookies.empty())
- request_info_.extra_headers += "Cookie: " + cookies + "\r\n";
+ if (!cookies.empty()) {
+ request_info_.extra_headers.SetHeader(
+ net::HttpRequestHeaders::kCookie, cookies);
+ }
}
- StartTransaction();
+ // We may have been canceled within OnGetCookies.
+ if (GetStatus().is_success()) {
+ StartTransaction();
+ } else {
+ NotifyCanceled();
+ }
}
Release(); // Balance AddRef taken in AddCookieHeaderAndStart
}
@@ -450,16 +467,33 @@
void URLRequestHttpJob::OnCanSetCookieCompleted(int policy) {
// If the request was destroyed, then there is no more work to do.
if (request_ && request_->delegate()) {
- if (policy == net::OK && request_->context()->cookie_store()) {
+ if (policy == net::ERR_ACCESS_DENIED) {
+ request_->delegate()->OnSetCookie(
+ request_,
+ response_cookies_[response_cookies_save_index_],
+ true);
+ } else if ((policy == net::OK || policy == net::OK_FOR_SESSION_ONLY) &&
+ request_->context()->cookie_store()) {
// OK to save the current response cookie now.
net::CookieOptions options;
options.set_include_httponly();
+ if (policy == net::OK_FOR_SESSION_ONLY)
+ options.set_force_session();
request_->context()->cookie_store()->SetCookieWithOptions(
request_->url(), response_cookies_[response_cookies_save_index_],
options);
+ request_->delegate()->OnSetCookie(
+ request_,
+ response_cookies_[response_cookies_save_index_],
+ false);
}
response_cookies_save_index_++;
- SaveNextCookie();
+ // We may have been canceled within OnSetCookie.
+ if (GetStatus().is_success()) {
+ SaveNextCookie();
+ } else {
+ NotifyCanceled();
+ }
}
Release(); // Balance AddRef taken in SaveNextCookie
}
@@ -569,23 +603,12 @@
URLRequestJob::NotifyHeadersComplete();
}
-#if defined(OS_WIN)
-#pragma optimize("", off)
-#pragma warning(disable:4748)
-#endif
void URLRequestHttpJob::DestroyTransaction() {
- CHECK(transaction_.get());
- // TODO(rvargas): remove this after finding the cause for bug 31723.
- char local_obj[sizeof(*this)];
- memcpy(local_obj, this, sizeof(local_obj));
+ DCHECK(transaction_.get());
transaction_.reset();
response_info_ = NULL;
}
-#if defined(OS_WIN)
-#pragma warning(default:4748)
-#pragma optimize("", on)
-#endif
void URLRequestHttpJob::StartTransaction() {
// NOTE: This method assumes that request_info_ is already setup properly.
@@ -606,7 +629,7 @@
&transaction_);
if (rv == net::OK) {
rv = transaction_->Start(
- &request_info_, &start_callback_, request_->load_log());
+ &request_info_, &start_callback_, request_->net_log());
}
}
@@ -654,14 +677,16 @@
// these headers. Some proxies deliberately corrupt Accept-Encoding headers.
if (!advertise_sdch) {
// Tell the server what compression formats we support (other than SDCH).
- request_info_.extra_headers += "Accept-Encoding: gzip,deflate\r\n";
+ request_info_.extra_headers.SetHeader(
+ net::HttpRequestHeaders::kAcceptEncoding, "gzip,deflate");
} else {
// Include SDCH in acceptable list.
- request_info_.extra_headers += "Accept-Encoding: "
- "gzip,deflate,sdch\r\n";
+ request_info_.extra_headers.SetHeader(
+ net::HttpRequestHeaders::kAcceptEncoding, "gzip,deflate,sdch");
if (!avail_dictionaries.empty()) {
- request_info_.extra_headers += "Avail-Dictionary: "
- + avail_dictionaries + "\r\n";
+ request_info_.extra_headers.SetHeader(
+ kAvailDictionaryHeader,
+ avail_dictionaries);
sdch_dictionary_advertised_ = true;
// Since we're tagging this transaction as advertising a dictionary, we'll
// definately employ an SDCH filter (or tentative sdch filter) when we get
@@ -675,12 +700,18 @@
if (context) {
// Only add default Accept-Language and Accept-Charset if the request
// didn't have them specified.
- net::HttpUtil::AppendHeaderIfMissing("Accept-Language",
- context->accept_language(),
- &request_info_.extra_headers);
- net::HttpUtil::AppendHeaderIfMissing("Accept-Charset",
- context->accept_charset(),
- &request_info_.extra_headers);
+ if (!request_info_.extra_headers.HasHeader(
+ net::HttpRequestHeaders::kAcceptLanguage)) {
+ request_info_.extra_headers.SetHeader(
+ net::HttpRequestHeaders::kAcceptLanguage,
+ context->accept_language());
+ }
+ if (!request_info_.extra_headers.HasHeader(
+ net::HttpRequestHeaders::kAcceptCharset)) {
+ request_info_.extra_headers.SetHeader(
+ net::HttpRequestHeaders::kAcceptCharset,
+ context->accept_charset());
+ }
}
}
@@ -694,7 +725,7 @@
int policy = net::OK;
if (request_info_.load_flags & net::LOAD_DO_NOT_SEND_COOKIES) {
- policy = net::ERR_ACCESS_DENIED;
+ policy = net::ERR_FAILED;
} else if (request_->context()->cookie_policy()) {
policy = request_->context()->cookie_policy()->CanGetCookies(
request_->url(),
@@ -740,7 +771,7 @@
int policy = net::OK;
if (request_info_.load_flags & net::LOAD_DO_NOT_SAVE_COOKIES) {
- policy = net::ERR_ACCESS_DENIED;
+ policy = net::ERR_FAILED;
} else if (request_->context()->cookie_policy()) {
policy = request_->context()->cookie_policy()->CanSetCookie(
request_->url(),
@@ -761,10 +792,8 @@
std::string value;
void* iter = NULL;
- while (response_info->headers->EnumerateHeader(&iter, name, &value)) {
- if (request_->context()->InterceptResponseCookie(request_, value))
- cookies->push_back(value);
- }
+ while (response_info->headers->EnumerateHeader(&iter, name, &value))
+ cookies->push_back(value);
}
class HTTPSProberDelegate : public net::HTTPSProberDelegate {
@@ -810,8 +839,7 @@
const bool https = response_info_->ssl_info.is_valid();
const bool valid_https =
- https &&
- !(response_info_->ssl_info.cert_status & net::CERT_STATUS_ALL_ERRORS);
+ https && !net::IsCertStatusError(response_info_->ssl_info.cert_status);
std::string name = "Strict-Transport-Security";
std::string value;
@@ -879,7 +907,7 @@
continue;
}
- net::HTTPSProberDelegate* delegate =
+ HTTPSProberDelegate* delegate =
new HTTPSProberDelegate(request_info_.url.host(), max_age,
include_subdomains,
ctx->transport_security_state());
diff --git a/net/url_request/url_request_http_job.h b/net/url_request/url_request_http_job.h
index e00e3c4..279cdd4 100644
--- a/net/url_request/url_request_http_job.h
+++ b/net/url_request/url_request_http_job.h
@@ -32,7 +32,7 @@
// URLRequestJob methods:
virtual void SetUpload(net::UploadData* upload);
- virtual void SetExtraRequestHeaders(const std::string& headers);
+ virtual void SetExtraRequestHeaders(const net::HttpRequestHeaders& headers);
virtual void Start();
virtual void Kill();
virtual net::LoadState GetLoadState() const;
@@ -55,6 +55,7 @@
virtual void ContinueWithCertificate(net::X509Certificate* client_cert);
virtual void ContinueDespiteLastError();
virtual bool ReadRawData(net::IOBuffer* buf, int buf_size, int *bytes_read);
+ virtual void StopCaching();
// Shadows URLRequestJob's version of this method so we can grab cookies.
void NotifyHeadersComplete();
diff --git a/net/url_request/url_request_job.cc b/net/url_request/url_request_job.cc
index 63611fd..23a7d85 100644
--- a/net/url_request/url_request_job.cc
+++ b/net/url_request/url_request_job.cc
@@ -1,14 +1,16 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// 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 "net/url_request/url_request_job.h"
+#include "base/histogram.h"
#include "base/message_loop.h"
#include "base/string_util.h"
#include "net/base/auth.h"
#include "net/base/io_buffer.h"
#include "net/base/load_flags.h"
+#include "net/base/mime_util.h"
#include "net/base/net_errors.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/url_request.h"
@@ -24,10 +26,13 @@
URLRequestJob::URLRequestJob(URLRequest* request)
: request_(request),
+ prefilter_bytes_read_(0),
+ postfilter_bytes_read_(0),
+ is_compressible_content_(false),
+ is_compressed_(false),
done_(false),
filter_needs_more_output_space_(false),
- read_buffer_(NULL),
- read_buffer_len_(0),
+ filtered_read_buffer_len_(0),
has_handled_response_(false),
expected_content_size_(-1),
deferred_redirect_status_code_(-1),
@@ -49,6 +54,13 @@
g_url_request_job_tracker.RemoveJob(this);
}
+void URLRequestJob::SetUpload(net::UploadData* upload) {
+}
+
+void URLRequestJob::SetExtraRequestHeaders(
+ const net::HttpRequestHeaders& headers) {
+}
+
void URLRequestJob::Kill() {
// Make sure the request is notified that we are done. We assume that the
// request took care of setting its error status before calling Kill.
@@ -60,10 +72,6 @@
request_ = NULL;
}
-bool URLRequestJob::IsDownload() const {
- return (load_flags_ & net::LOAD_IS_DOWNLOAD) != 0;
-}
-
void URLRequestJob::SetupFilter() {
std::vector<Filter::FilterType> encoding_types;
if (GetContentEncodings(&encoding_types)) {
@@ -87,6 +95,14 @@
return true;
}
+bool URLRequestJob::IsSafeRedirect(const GURL& location) {
+ return true;
+}
+
+bool URLRequestJob::NeedsAuth() {
+ return false;
+}
+
void URLRequestJob::GetAuthChallengeInfo(
scoped_refptr<net::AuthChallengeInfo>* auth_info) {
// This will only be called if NeedsAuth() returns true, in which
@@ -143,6 +159,10 @@
return filter_input_byte_count_;
}
+bool URLRequestJob::GetMimeType(std::string* mime_type) const {
+ return false;
+}
+
bool URLRequestJob::GetURL(GURL* gurl) const {
if (!request_)
return false;
@@ -156,6 +176,18 @@
return request_->request_time();
};
+bool URLRequestJob::IsCachedContent() const {
+ return false;
+}
+
+int URLRequestJob::GetResponseCode() const {
+ return -1;
+}
+
+int URLRequestJob::GetInputStreamBufferSize() const {
+ return kFilterBufSize;
+}
+
// This function calls ReadData to get stream data. If a filter exists, passes
// the data to the attached filter. Then returns the output from filter back to
// the caller.
@@ -165,19 +197,19 @@
DCHECK_LT(buf_size, 1000000); // sanity check
DCHECK(buf);
DCHECK(bytes_read);
+ DCHECK(filtered_read_buffer_ == NULL);
+ DCHECK_EQ(0, filtered_read_buffer_len_);
*bytes_read = 0;
// Skip Filter if not present
if (!filter_.get()) {
- rv = ReadRawData(buf, buf_size, bytes_read);
- if (rv && *bytes_read > 0)
- RecordBytesRead(*bytes_read);
+ rv = ReadRawDataHelper(buf, buf_size, bytes_read);
} else {
// Save the caller's buffers while we do IO
// in the filter's buffers.
- read_buffer_ = buf;
- read_buffer_len_ = buf_size;
+ filtered_read_buffer_ = buf;
+ filtered_read_buffer_len_ = buf_size;
if (ReadFilteredData(bytes_read)) {
rv = true; // we have data to return
@@ -190,7 +222,43 @@
return rv;
}
-bool URLRequestJob::ReadRawDataForFilter(int *bytes_read) {
+void URLRequestJob::StopCaching() {
+ // Nothing to do here.
+}
+
+net::LoadState URLRequestJob::GetLoadState() const {
+ return net::LOAD_STATE_IDLE;
+}
+
+uint64 URLRequestJob::GetUploadProgress() const {
+ return 0;
+}
+
+bool URLRequestJob::GetCharset(std::string* charset) {
+ return false;
+}
+
+void URLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) {
+}
+
+bool URLRequestJob::GetResponseCookies(std::vector<std::string>* cookies) {
+ return false;
+}
+
+bool URLRequestJob::GetContentEncodings(
+ std::vector<Filter::FilterType>* encoding_types) {
+ return false;
+}
+
+bool URLRequestJob::IsDownload() const {
+ return (load_flags_ & net::LOAD_IS_DOWNLOAD) != 0;
+}
+
+bool URLRequestJob::IsSdchResponse() const {
+ return false;
+}
+
+bool URLRequestJob::ReadRawDataForFilter(int* bytes_read) {
bool rv = false;
DCHECK(bytes_read);
@@ -204,9 +272,7 @@
if (!filter_->stream_data_len() && !is_done()) {
net::IOBuffer* stream_buffer = filter_->stream_buffer();
int stream_buffer_size = filter_->stream_buffer_size();
- rv = ReadRawData(stream_buffer, stream_buffer_size, bytes_read);
- if (rv && *bytes_read > 0)
- RecordBytesRead(*bytes_read);
+ rv = ReadRawDataHelper(stream_buffer, stream_buffer_size, bytes_read);
}
return rv;
}
@@ -224,11 +290,12 @@
filter_->FlushStreamBuffer(bytes_read);
}
-bool URLRequestJob::ReadFilteredData(int *bytes_read) {
+bool URLRequestJob::ReadFilteredData(int* bytes_read) {
DCHECK(filter_.get()); // don't add data if there is no filter
- DCHECK(read_buffer_ != NULL); // we need to have a buffer to fill
- DCHECK_GT(read_buffer_len_, 0); // sanity check
- DCHECK_LT(read_buffer_len_, 1000000); // sanity check
+ DCHECK(filtered_read_buffer_ != NULL); // we need to have a buffer to fill
+ DCHECK_GT(filtered_read_buffer_len_, 0); // sanity check
+ DCHECK_LT(filtered_read_buffer_len_, 1000000); // sanity check
+ DCHECK(raw_read_buffer_ == NULL); // there should be no raw read buffer yet
bool rv = false;
*bytes_read = 0;
@@ -254,10 +321,11 @@
if ((filter_->stream_data_len() || filter_needs_more_output_space_)
&& !is_done()) {
// Get filtered data.
- int filtered_data_len = read_buffer_len_;
+ int filtered_data_len = filtered_read_buffer_len_;
Filter::FilterStatus status;
int output_buffer_size = filtered_data_len;
- status = filter_->ReadData(read_buffer_->data(), &filtered_data_len);
+ status = filter_->ReadData(filtered_read_buffer_->data(),
+ &filtered_data_len);
if (filter_needs_more_output_space_ && 0 == filtered_data_len) {
// filter_needs_more_output_space_ was mistaken... there are no more bytes
@@ -321,10 +389,29 @@
if (rv) {
// When we successfully finished a read, we no longer need to
- // save the caller's buffers. For debugging purposes, we clear
- // them out.
- read_buffer_ = NULL;
- read_buffer_len_ = 0;
+ // save the caller's buffers. Release our reference.
+ filtered_read_buffer_ = NULL;
+ filtered_read_buffer_len_ = 0;
+ }
+ return rv;
+}
+
+bool URLRequestJob::ReadRawDataHelper(net::IOBuffer* buf, int buf_size,
+ int* bytes_read) {
+ DCHECK(!request_->status().is_io_pending());
+ DCHECK(raw_read_buffer_ == NULL);
+
+ // Keep a pointer to the read buffer, so we have access to it in the
+ // OnRawReadComplete() callback in the event that the read completes
+ // asynchronously.
+ raw_read_buffer_ = buf;
+ bool rv = ReadRawData(buf, buf_size, bytes_read);
+
+ if (!request_->status().is_io_pending()) {
+ // If the read completes synchronously, either success or failure,
+ // invoke the OnRawReadComplete callback so we can account for the
+ // completed read.
+ OnRawReadComplete(*bytes_read);
}
return rv;
}
@@ -412,14 +499,28 @@
}
has_handled_response_ = true;
- if (request_->status().is_success())
+ if (request_->status().is_success()) {
SetupFilter();
+ // Check if this content appears to be compressible.
+ std::string mime_type;
+ if (GetMimeType(&mime_type) &&
+ (net::IsSupportedJavascriptMimeType(mime_type.c_str()) ||
+ net::IsSupportedNonImageMimeType(mime_type.c_str()))) {
+ is_compressible_content_ = true;
+ }
+ }
+
if (!filter_.get()) {
std::string content_length;
request_->GetResponseHeaderByName("content-length", &content_length);
if (!content_length.empty())
expected_content_size_ = StringToInt64(content_length);
+ } else {
+ // Chrome today only sends "Accept-Encoding" for compression schemes.
+ // So, if there is a filter on the response, we know that the content
+ // was compressed.
+ is_compressed_ = true;
}
request_->ResponseStarted();
@@ -445,8 +546,7 @@
// The headers should be complete before reads complete
DCHECK(has_handled_response_);
- if (bytes_read > 0)
- RecordBytesRead(bytes_read);
+ OnRawReadComplete(bytes_read);
// Don't notify if we had an error.
if (!request_->status().is_success())
@@ -459,15 +559,19 @@
// survival until we can get out of this method.
scoped_refptr<URLRequestJob> self_preservation = this;
+ prefilter_bytes_read_ += bytes_read;
if (filter_.get()) {
// Tell the filter that it has more data
FilteredDataRead(bytes_read);
// Filter the data.
int filter_bytes_read = 0;
- if (ReadFilteredData(&filter_bytes_read))
+ if (ReadFilteredData(&filter_bytes_read)) {
+ postfilter_bytes_read_ += filter_bytes_read;
request_->delegate()->OnReadCompleted(request_, filter_bytes_read);
+ }
} else {
+ postfilter_bytes_read_ += bytes_read;
request_->delegate()->OnReadCompleted(request_, bytes_read);
}
}
@@ -478,6 +582,8 @@
return;
done_ = true;
+ RecordCompressionHistograms();
+
if (is_profiling() && metrics_->total_bytes_read_ > 0) {
// There are valid IO statistics. Fill in other fields of metrics for
// profiling consumers to retrieve information.
@@ -553,6 +659,14 @@
return filter_.get() && filter_->stream_data_len();
}
+void URLRequestJob::OnRawReadComplete(int bytes_read) {
+ DCHECK(raw_read_buffer_);
+ if (bytes_read > 0) {
+ RecordBytesRead(bytes_read);
+ }
+ raw_read_buffer_ = NULL;
+}
+
void URLRequestJob::RecordBytesRead(int bytes_read) {
if (is_profiling()) {
++(metrics_->number_of_read_IO_);
@@ -560,7 +674,8 @@
}
filter_input_byte_count_ += bytes_read;
UpdatePacketReadTimes(); // Facilitate stats recording if it is active.
- g_url_request_job_tracker.OnBytesRead(this, bytes_read);
+ g_url_request_job_tracker.OnBytesRead(this, raw_read_buffer_->data(),
+ bytes_read);
}
const URLRequestStatus URLRequestJob::GetStatus() {
@@ -743,3 +858,74 @@
return;
}
}
+
+// The common type of histogram we use for all compression-tracking histograms.
+#define COMPRESSION_HISTOGRAM(name, sample) \
+ do { \
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.Compress." name, sample, \
+ 500, 1000000, 100); \
+ } while(0)
+
+void URLRequestJob::RecordCompressionHistograms() {
+ if (IsCachedContent() || // Don't record cached content
+ !GetStatus().is_success() || // Don't record failed content
+ !is_compressible_content_ || // Only record compressible content
+ !prefilter_bytes_read_) // Zero-byte responses aren't useful.
+ return;
+
+ // Miniature requests aren't really compressible. Don't count them.
+ const int kMinSize = 16;
+ if (prefilter_bytes_read_ < kMinSize)
+ return;
+
+ // Only record for http or https urls.
+ bool is_http = request_->url().SchemeIs("http");
+ bool is_https = request_->url().SchemeIs("https");
+ if (!is_http && !is_https)
+ return;
+
+ const net::HttpResponseInfo& response = request_->response_info_;
+ int compressed_B = prefilter_bytes_read_;
+ int decompressed_B = postfilter_bytes_read_;
+
+ // We want to record how often downloaded resources are compressed.
+ // But, we recognize that different protocols may have different
+ // properties. So, for each request, we'll put it into one of 3
+ // groups:
+ // a) SSL resources
+ // Proxies cannot tamper with compression headers with SSL.
+ // b) Non-SSL, loaded-via-proxy resources
+ // In this case, we know a proxy might have interfered.
+ // c) Non-SSL, loaded-without-proxy resources
+ // In this case, we know there was no explicit proxy. However,
+ // it is possible that a transparent proxy was still interfering.
+ //
+ // For each group, we record the same 3 histograms.
+
+ if (is_https) {
+ if (is_compressed_) {
+ COMPRESSION_HISTOGRAM("SSL.BytesBeforeCompression", compressed_B);
+ COMPRESSION_HISTOGRAM("SSL.BytesAfterCompression", decompressed_B);
+ } else {
+ COMPRESSION_HISTOGRAM("SSL.ShouldHaveBeenCompressed", decompressed_B);
+ }
+ return;
+ }
+
+ if (response.was_fetched_via_proxy) {
+ if (is_compressed_) {
+ COMPRESSION_HISTOGRAM("Proxy.BytesBeforeCompression", compressed_B);
+ COMPRESSION_HISTOGRAM("Proxy.BytesAfterCompression", decompressed_B);
+ } else {
+ COMPRESSION_HISTOGRAM("Proxy.ShouldHaveBeenCompressed", decompressed_B);
+ }
+ return;
+ }
+
+ if (is_compressed_) {
+ COMPRESSION_HISTOGRAM("NoProxy.BytesBeforeCompression", compressed_B);
+ COMPRESSION_HISTOGRAM("NoProxy.BytesAfterCompression", decompressed_B);
+ } else {
+ COMPRESSION_HISTOGRAM("NoProxy.ShouldHaveBeenCompressed", decompressed_B);
+ }
+}
diff --git a/net/url_request/url_request_job.h b/net/url_request/url_request_job.h
index 2aeb8ff..da6fc16 100644
--- a/net/url_request/url_request_job.h
+++ b/net/url_request/url_request_job.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -17,6 +17,7 @@
namespace net {
class AuthChallengeInfo;
+class HttpRequestHeaders;
class HttpResponseInfo;
class IOBuffer;
class UploadData;
@@ -50,10 +51,10 @@
// Sets the upload data, most requests have no upload data, so this is a NOP.
// Job types supporting upload data will override this.
- virtual void SetUpload(net::UploadData* upload) { }
+ virtual void SetUpload(net::UploadData* upload);
// Sets extra request headers for Job types that support request headers.
- virtual void SetExtraRequestHeaders(const std::string& headers) { }
+ virtual void SetExtraRequestHeaders(const net::HttpRequestHeaders& headers);
// If any error occurs while starting the Job, NotifyStartError should be
// called.
@@ -90,29 +91,31 @@
// bytes read, 0 when there is no more data, or -1 if there was an error.
// This is just the backend for URLRequest::Read, see that function for more
// info.
- bool Read(net::IOBuffer* buf, int buf_size, int *bytes_read);
+ bool Read(net::IOBuffer* buf, int buf_size, int* bytes_read);
+
+ // Stops further caching of this request, if any. For more info, see
+ // URLRequest::StopCaching().
+ virtual void StopCaching();
// Called to fetch the current load state for the job.
- virtual net::LoadState GetLoadState() const { return net::LOAD_STATE_IDLE; }
+ virtual net::LoadState GetLoadState() const;
// Called to get the upload progress in bytes.
- virtual uint64 GetUploadProgress() const { return 0; }
+ virtual uint64 GetUploadProgress() const;
// Called to fetch the charset for this request. Only makes sense for some
// types of requests. Returns true on success. Calling this on a type that
// doesn't have a charset will return false.
- virtual bool GetCharset(std::string* charset) { return false; }
+ virtual bool GetCharset(std::string* charset);
// Called to get response info.
- virtual void GetResponseInfo(net::HttpResponseInfo* info) {}
+ virtual void GetResponseInfo(net::HttpResponseInfo* info);
// Returns the cookie values included in the response, if applicable.
// Returns true if applicable.
// NOTE: This removes the cookies from the job, so it will only return
// useful results once per job.
- virtual bool GetResponseCookies(std::vector<std::string>* cookies) {
- return false;
- }
+ virtual bool GetResponseCookies(std::vector<std::string>* cookies);
// Called to fetch the encoding types for this request. Only makes sense for
// some types of requests. Returns true on success. Calling this on a request
@@ -125,16 +128,14 @@
// in the reverse order (in the above example, ungzip first, and then sdch
// expand).
virtual bool GetContentEncodings(
- std::vector<Filter::FilterType>* encoding_types) {
- return false;
- }
+ std::vector<Filter::FilterType>* encoding_types);
// Find out if this is a download.
virtual bool IsDownload() const;
// Find out if this is a response to a request that advertised an SDCH
// dictionary. Only makes sense for some types of requests.
- virtual bool IsSdchResponse() const { return false; }
+ virtual bool IsSdchResponse() const;
// Called to setup stream filter for this request. An example of filter is
// content encoding/decoding.
@@ -157,14 +158,12 @@
// location. This may be used to implement protocol-specific restrictions.
// If this function returns false, then the URLRequest will fail reporting
// net::ERR_UNSAFE_REDIRECT.
- virtual bool IsSafeRedirect(const GURL& location) {
- return true;
- }
+ virtual bool IsSafeRedirect(const GURL& location);
// Called to determine if this response is asking for authentication. Only
// makes sense for some types of requests. The caller is responsible for
// obtaining the credentials passing them to SetAuth.
- virtual bool NeedsAuth() { return false; }
+ virtual bool NeedsAuth();
// Fills the authentication info with the server's response.
virtual void GetAuthChallengeInfo(
@@ -207,13 +206,13 @@
// FilterContext methods:
// These methods are not applicable to all connections.
- virtual bool GetMimeType(std::string* mime_type) const { return false; }
+ virtual bool GetMimeType(std::string* mime_type) const;
virtual bool GetURL(GURL* gurl) const;
virtual base::Time GetRequestTime() const;
- virtual bool IsCachedContent() const { return false; }
+ virtual bool IsCachedContent() const;
virtual int64 GetByteReadCount() const;
- virtual int GetResponseCode() const { return -1; }
- virtual int GetInputStreamBufferSize() const { return kFilterBufSize; }
+ virtual int GetResponseCode() const;
+ virtual int GetInputStreamBufferSize() const;
virtual void RecordPacketStats(StatisticSelector statistic) const;
protected:
@@ -294,6 +293,15 @@
// Contains IO performance measurement when profiling is enabled.
scoped_ptr<URLRequestJobMetrics> metrics_;
+ // The number of bytes read before passing to the filter.
+ int prefilter_bytes_read_;
+ // The number of bytes read after passing through the filter.
+ int postfilter_bytes_read_;
+ // True when (we believe) the content in this URLRequest was compressible.
+ bool is_compressible_content_;
+ // True when the content in this URLRequest was compressed.
+ bool is_compressed_;
+
private:
// Size of filter input buffers used by this class.
static const int kFilterBufSize;
@@ -303,13 +311,23 @@
// an error occurred (or we are waiting for IO to complete).
bool ReadRawDataForFilter(int *bytes_read);
+ // Invokes ReadRawData and records bytes read if the read completes
+ // synchronously.
+ bool ReadRawDataHelper(net::IOBuffer* buf, int buf_size, int* bytes_read);
+
// Called in response to a redirect that was not canceled to follow the
// redirect. The current job will be replaced with a new job loading the
// given redirect destination.
void FollowRedirect(const GURL& location, int http_status_code);
- // Updates the profiling info and notifies observers that bytes_read bytes
- // have been read.
+ // Called after every raw read. If |bytes_read| is > 0, this indicates
+ // a successful read of |bytes_read| unfiltered bytes. If |bytes_read|
+ // is 0, this indicates that there is no additional data to read. If
+ // |bytes_read| is < 0, an error occurred and no bytes were read.
+ void OnRawReadComplete(int bytes_read);
+
+ // Updates the profiling info and notifies observers that an additional
+ // |bytes_read| unfiltered bytes have been read for this job.
void RecordBytesRead(int bytes_read);
// Called to query whether there is data available in the filter to be read
@@ -319,6 +337,8 @@
// Record packet arrival times for possible use in histograms.
void UpdatePacketReadTimes();
+ void RecordCompressionHistograms();
+
// Indicates that the job is done producing data, either it has completed
// all the data or an error has been encountered. Set exclusively by
// NotifyDone so that it is kept in sync with the request.
@@ -338,8 +358,12 @@
// processing the filtered data, we return the data in the caller's buffer.
// While the async IO is in progress, we save the user buffer here, and
// when the IO completes, we fill this in.
- net::IOBuffer *read_buffer_;
- int read_buffer_len_;
+ scoped_refptr<net::IOBuffer> filtered_read_buffer_;
+ int filtered_read_buffer_len_;
+
+ // We keep a pointer to the read buffer while asynchronous reads are
+ // in progress, so we are able to pass those bytes to job observers.
+ scoped_refptr<net::IOBuffer> raw_read_buffer_;
// Used by HandleResponseIfNecessary to track whether we've sent the
// OnResponseStarted callback and potentially redirect callbacks as well.
@@ -366,7 +390,7 @@
// as gathered here is post-SSL, and post-cache-fetch, and does not reflect
// true packet arrival times in such cases.
- // Total number of bytes read from network (or cache) and and typically handed
+ // Total number of bytes read from network (or cache) and typically handed
// to filter to process. Used to histogram compression ratios, and error
// recovery scenarios in filters.
int64 filter_input_byte_count_;
diff --git a/net/url_request/url_request_job_manager.cc b/net/url_request/url_request_job_manager.cc
index 7cd934e..0615280 100644
--- a/net/url_request/url_request_job_manager.cc
+++ b/net/url_request/url_request_job_manager.cc
@@ -14,7 +14,7 @@
#include "net/url_request/url_request_data_job.h"
#include "net/url_request/url_request_error_job.h"
#include "net/url_request/url_request_file_job.h"
-#include "net/url_request/url_request_new_ftp_job.h"
+#include "net/url_request/url_request_ftp_job.h"
#include "net/url_request/url_request_http_job.h"
// The built-in set of protocol factories
@@ -31,7 +31,7 @@
{ "http", URLRequestHttpJob::Factory },
{ "https", URLRequestHttpJob::Factory },
{ "file", URLRequestFileJob::Factory },
- { "ftp", URLRequestNewFtpJob::Factory },
+ { "ftp", URLRequestFtpJob::Factory },
{ "about", URLRequestAboutJob::Factory },
{ "data", URLRequestDataJob::Factory },
};
diff --git a/net/url_request/url_request_job_manager.h b/net/url_request/url_request_job_manager.h
index 9930e17..d8934e6 100644
--- a/net/url_request/url_request_job_manager.h
+++ b/net/url_request/url_request_job_manager.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -97,7 +97,7 @@
}
#endif
- DISALLOW_EVIL_CONSTRUCTORS(URLRequestJobManager);
+ DISALLOW_COPY_AND_ASSIGN(URLRequestJobManager);
};
#endif // NET_URL_REQUEST_URL_REQUEST_JOB_MANAGER_H__
diff --git a/net/url_request/url_request_job_metrics.cc b/net/url_request/url_request_job_metrics.cc
index eea21fa..e0726da 100644
--- a/net/url_request/url_request_job_metrics.cc
+++ b/net/url_request/url_request_job_metrics.cc
@@ -6,6 +6,7 @@
#include "base/basictypes.h"
#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
using base::TimeDelta;
diff --git a/net/url_request/url_request_job_tracker.cc b/net/url_request/url_request_job_tracker.cc
index 1f5b33c..e3e5d36 100644
--- a/net/url_request/url_request_job_tracker.cc
+++ b/net/url_request/url_request_job_tracker.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -51,7 +51,8 @@
}
void URLRequestJobTracker::OnBytesRead(URLRequestJob* job,
+ const char* buf,
int byte_count) {
FOR_EACH_OBSERVER(JobObserver, observers_,
- OnBytesRead(job, byte_count));
+ OnBytesRead(job, buf, byte_count));
}
diff --git a/net/url_request/url_request_job_tracker.h b/net/url_request/url_request_job_tracker.h
index 5f12fc7..e03b71f 100644
--- a/net/url_request/url_request_job_tracker.h
+++ b/net/url_request/url_request_job_tracker.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -43,9 +43,13 @@
virtual void OnJobRedirect(URLRequestJob* job, const GURL& location,
int status_code) = 0;
- // Called when a new chunk of bytes has been read for the given job. The
- // byte count is the number of bytes for that read event only.
- virtual void OnBytesRead(URLRequestJob* job, int byte_count) = 0;
+ // Called when a new chunk of unfiltered bytes has been read for
+ // the given job. |byte_count| is the number of bytes for that
+ // read event only. |buf| is a pointer to the data buffer that
+ // contains those bytes. The data in |buf| is only valid for the
+ // duration of the OnBytesRead callback.
+ virtual void OnBytesRead(URLRequestJob* job, const char* buf,
+ int byte_count) = 0;
virtual ~JobObserver() {}
};
@@ -74,7 +78,7 @@
int status_code);
// Bytes read notifications.
- void OnBytesRead(URLRequestJob* job, int byte_count);
+ void OnBytesRead(URLRequestJob* job, const char* buf, int byte_count);
// allows iteration over all active jobs
JobIterator begin() const {
diff --git a/net/url_request/url_request_job_tracker_unittest.cc b/net/url_request/url_request_job_tracker_unittest.cc
new file mode 100644
index 0000000..3ddbcc2
--- /dev/null
+++ b/net/url_request/url_request_job_tracker_unittest.cc
@@ -0,0 +1,232 @@
+// 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 <string.h>
+#include <algorithm>
+#include <string>
+#include <vector>
+#include "base/message_loop.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/filter.h"
+#include "net/base/io_buffer.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_job.h"
+#include "net/url_request/url_request_job_tracker.h"
+#include "net/url_request/url_request_status.h"
+#include "net/url_request/url_request_unittest.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+using testing::Eq;
+using testing::InSequence;
+using testing::NotNull;
+using testing::StrictMock;
+
+namespace {
+
+const char kBasic[] = "Hello\n";
+
+// The above string "Hello\n", gzip compressed.
+const unsigned char kCompressed[] = {
+ 0x1f, 0x8b, 0x08, 0x08, 0x38, 0x18, 0x2e, 0x4c, 0x00, 0x03, 0x63,
+ 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x2e, 0x68,
+ 0x74, 0x6d, 0x6c, 0x00, 0xf3, 0x48, 0xcd, 0xc9, 0xc9, 0xe7, 0x02,
+ 0x00, 0x16, 0x35, 0x96, 0x31, 0x06, 0x00, 0x00, 0x00
+};
+
+bool GetResponseBody(const GURL& url, std::string* out_body) {
+ if (url.spec() == "test:basic") {
+ *out_body = kBasic;
+ } else if (url.spec() == "test:compressed") {
+ out_body->assign(reinterpret_cast<const char*>(kCompressed),
+ sizeof(kCompressed));
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+class MockJobObserver : public URLRequestJobTracker::JobObserver {
+ public:
+ MOCK_METHOD1(OnJobAdded, void(URLRequestJob* job));
+ MOCK_METHOD1(OnJobRemoved, void(URLRequestJob* job));
+ MOCK_METHOD2(OnJobDone, void(URLRequestJob* job,
+ const URLRequestStatus& status));
+ MOCK_METHOD3(OnJobRedirect, void(URLRequestJob* job,
+ const GURL& location,
+ int status_code));
+ MOCK_METHOD3(OnBytesRead, void(URLRequestJob* job,
+ const char* buf,
+ int byte_count));
+};
+
+// A URLRequestJob that returns static content for given URLs. We do
+// not use URLRequestTestJob here because URLRequestTestJob fakes
+// async operations by calling ReadRawData synchronously in an async
+// callback. This test requires a URLRequestJob that returns false for
+// async reads, in order to exercise the real async read codepath.
+class URLRequestJobTrackerTestJob : public URLRequestJob {
+ public:
+ URLRequestJobTrackerTestJob(URLRequest* request, bool async_reads)
+ : URLRequestJob(request), async_reads_(async_reads) {}
+
+ void Start() {
+ ASSERT_TRUE(GetResponseBody(request_->url(), &response_data_));
+
+ // Start reading asynchronously so that all error reporting and data
+ // callbacks happen as they would for network requests.
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &URLRequestJobTrackerTestJob::NotifyHeadersComplete));
+ }
+
+ bool ReadRawData(net::IOBuffer* buf, int buf_size,
+ int *bytes_read) {
+ const size_t bytes_to_read = std::min(
+ response_data_.size(), static_cast<size_t>(buf_size));
+
+ // Regardless of whether we're performing a sync or async read,
+ // copy the data into the caller's buffer now. That way we don't
+ // have to hold on to the buffers in the async case.
+ memcpy(buf->data(), response_data_.data(), bytes_to_read);
+ response_data_.erase(0, bytes_to_read);
+
+ if (async_reads_) {
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &URLRequestJobTrackerTestJob::OnReadCompleted,
+ bytes_to_read));
+ } else {
+ SetStatus(URLRequestStatus());
+ *bytes_read = bytes_to_read;
+ }
+ return !async_reads_;
+ }
+
+ void OnReadCompleted(int status) {
+ if (status == 0) {
+ NotifyDone(URLRequestStatus());
+ } else if (status > 0) {
+ SetStatus(URLRequestStatus());
+ } else {
+ ASSERT_FALSE(true) << "Unexpected OnReadCompleted callback.";
+ }
+
+ NotifyReadComplete(status);
+ }
+
+ bool GetContentEncodings(
+ std::vector<Filter::FilterType>* encoding_types) {
+ if (request_->url().spec() == "test:basic") {
+ return false;
+ } else if (request_->url().spec() == "test:compressed") {
+ encoding_types->push_back(Filter::FILTER_TYPE_GZIP);
+ return true;
+ } else {
+ return URLRequestJob::GetContentEncodings(encoding_types);
+ }
+ }
+
+ // The data to send, will be set in Start().
+ std::string response_data_;
+
+ // Should reads be synchronous or asynchronous?
+ const bool async_reads_;
+};
+
+// Google Mock Matcher to check two URLRequestStatus instances for
+// equality.
+MATCHER_P(StatusEq, other, "") {
+ return (arg.status() == other.status() &&
+ arg.os_error() == other.os_error());
+}
+
+// Google Mock Matcher to check that two blocks of memory are equal.
+MATCHER_P2(MemEq, other, len, "") {
+ return memcmp(arg, other, len) == 0;
+}
+
+class URLRequestJobTrackerTest : public PlatformTest {
+ protected:
+ static void SetUpTestCase() {
+ URLRequest::RegisterProtocolFactory("test", &Factory);
+ }
+
+ virtual void SetUp() {
+ g_async_reads = true;
+ }
+
+ void AssertJobTrackerCallbacks(const char* url) {
+ InSequence seq;
+ testing::StrictMock<MockJobObserver> observer;
+
+ const GURL gurl(url);
+ std::string body;
+ ASSERT_TRUE(GetResponseBody(gurl, &body));
+
+ // We expect to receive one call for each method on the JobObserver,
+ // in the following order:
+ EXPECT_CALL(observer, OnJobAdded(NotNull()));
+ EXPECT_CALL(observer, OnBytesRead(NotNull(),
+ MemEq(body.data(), body.size()),
+ Eq(static_cast<int>(body.size()))));
+ EXPECT_CALL(observer, OnJobDone(NotNull(), StatusEq(URLRequestStatus())));
+ EXPECT_CALL(observer, OnJobRemoved(NotNull()));
+
+ // Attach our observer and perform the resource fetch.
+ g_url_request_job_tracker.AddObserver(&observer);
+ Fetch(gurl);
+ g_url_request_job_tracker.RemoveObserver(&observer);
+ }
+
+ void Fetch(const GURL& url) {
+ TestDelegate d;
+ {
+ URLRequest request(url, &d);
+ request.Start();
+ MessageLoop::current()->RunAllPending();
+ }
+
+ // A few sanity checks to make sure that the delegate also
+ // receives the expected callbacks.
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_STREQ(kBasic, d.data_received().c_str());
+ }
+
+ static URLRequest::ProtocolFactory Factory;
+ static bool g_async_reads;
+};
+
+// static
+URLRequestJob* URLRequestJobTrackerTest::Factory(URLRequest* request,
+ const std::string& scheme) {
+ return new URLRequestJobTrackerTestJob(request, g_async_reads);
+}
+
+// static
+bool URLRequestJobTrackerTest::g_async_reads = true;
+
+TEST_F(URLRequestJobTrackerTest, BasicAsync) {
+ g_async_reads = true;
+ AssertJobTrackerCallbacks("test:basic");
+}
+
+TEST_F(URLRequestJobTrackerTest, BasicSync) {
+ g_async_reads = false;
+ AssertJobTrackerCallbacks("test:basic");
+}
+
+TEST_F(URLRequestJobTrackerTest, CompressedAsync) {
+ g_async_reads = true;
+ AssertJobTrackerCallbacks("test:compressed");
+}
+
+TEST_F(URLRequestJobTrackerTest, CompressedSync) {
+ g_async_reads = false;
+ AssertJobTrackerCallbacks("test:compressed");
+}
+
+} // namespace
diff --git a/net/url_request/url_request_netlog_params.cc b/net/url_request/url_request_netlog_params.cc
new file mode 100644
index 0000000..3693ee9
--- /dev/null
+++ b/net/url_request/url_request_netlog_params.cc
@@ -0,0 +1,27 @@
+// 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 "net/url_request/url_request_netlog_params.h"
+
+#include "base/values.h"
+
+URLRequestStartEventParameters::URLRequestStartEventParameters(
+ const GURL& url,
+ const std::string& method,
+ int load_flags,
+ net::RequestPriority priority)
+ : url_(url),
+ method_(method),
+ load_flags_(load_flags),
+ priority_(priority) {
+}
+
+Value* URLRequestStartEventParameters::ToValue() const {
+ DictionaryValue* dict = new DictionaryValue();
+ dict->SetString(L"url", url_.possibly_invalid_spec());
+ dict->SetString(L"method", method_);
+ dict->SetInteger(L"load_flags", load_flags_);
+ dict->SetInteger(L"priority", static_cast<int>(priority_));
+ return dict;
+}
diff --git a/net/url_request/url_request_netlog_params.h b/net/url_request/url_request_netlog_params.h
new file mode 100644
index 0000000..d84052a
--- /dev/null
+++ b/net/url_request/url_request_netlog_params.h
@@ -0,0 +1,42 @@
+// 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_NETLOG_PARAMS_H_
+#define NET_URL_REQUEST_URL_REQUEST_NETLOG_PARAMS_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/net_log.h"
+#include "net/base/request_priority.h"
+
+// Holds the parameters to emit to the NetLog when starting a URLRequest.
+class URLRequestStartEventParameters : public net::NetLog::EventParameters {
+ public:
+ URLRequestStartEventParameters(const GURL& url,
+ const std::string& method,
+ int load_flags,
+ net::RequestPriority priority);
+
+ const GURL& url() const {
+ return url_;
+ }
+
+ int load_flags() const {
+ return load_flags_;
+ }
+
+ virtual Value* ToValue() const;
+
+ private:
+ const GURL url_;
+ const std::string method_;
+ const int load_flags_;
+ const net::RequestPriority priority_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestStartEventParameters);
+};
+
+#endif // NET_URL_REQUEST_URL_REQUEST_NETLOG_PARAMS_H_
diff --git a/net/url_request/url_request_simple_job.h b/net/url_request/url_request_simple_job.h
index 786d2e4..4ea856c 100644
--- a/net/url_request/url_request_simple_job.h
+++ b/net/url_request/url_request_simple_job.h
@@ -28,9 +28,10 @@
std::string* charset,
std::string* data) const = 0;
- private:
+ protected:
void StartAsync();
+ private:
std::string mime_type_;
std::string charset_;
std::string data_;
diff --git a/net/url_request/url_request_test_job.cc b/net/url_request/url_request_test_job.cc
index b1e3b5c..363d178 100644
--- a/net/url_request/url_request_test_job.cc
+++ b/net/url_request/url_request_test_job.cc
@@ -183,6 +183,12 @@
info->headers = response_headers_;
}
+int URLRequestTestJob::GetResponseCode() const {
+ if (response_headers_)
+ return response_headers_->response_code();
+ return -1;
+}
+
bool URLRequestTestJob::IsRedirectResponse(GURL* location,
int* http_status_code) {
if (!response_headers_)
diff --git a/net/url_request/url_request_test_job.h b/net/url_request/url_request_test_job.h
index 4daabf6..f618c07 100644
--- a/net/url_request/url_request_test_job.h
+++ b/net/url_request/url_request_test_job.h
@@ -93,6 +93,7 @@
virtual void Kill();
virtual bool GetMimeType(std::string* mime_type) const;
virtual void GetResponseInfo(net::HttpResponseInfo* info);
+ virtual int GetResponseCode() const;
virtual bool IsRedirectResponse(GURL* location, int* http_status_code);
protected:
diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc
index a94158a..bd4e56a 100644
--- a/net/url_request/url_request_unittest.cc
+++ b/net/url_request/url_request_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2006-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.
@@ -26,8 +26,8 @@
#include "net/base/cookie_monster.h"
#include "net/base/cookie_policy.h"
#include "net/base/load_flags.h"
-#include "net/base/load_log.h"
-#include "net/base/load_log_unittest.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
#include "net/base/net_errors.h"
#include "net/base/net_module.h"
#include "net/base/net_util.h"
@@ -36,9 +36,10 @@
#include "net/ftp/ftp_network_layer.h"
#include "net/http/http_cache.h"
#include "net/http/http_network_layer.h"
+#include "net/http/http_request_headers.h"
#include "net/http/http_response_headers.h"
#include "net/proxy/proxy_service.h"
-#include "net/socket/ssl_test_util.h"
+#include "net/test/test_server.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_file_dir_job.h"
#include "net/url_request/url_request_http_job.h"
@@ -122,7 +123,7 @@
}
uploadBytes[kMsgSize] = '\0';
- scoped_refptr<URLRequestContext> context = new URLRequestTestContext();
+ scoped_refptr<URLRequestContext> context = new TestURLRequestContext();
for (int i = 0; i < kIterations; ++i) {
TestDelegate d;
@@ -217,16 +218,7 @@
EXPECT_FALSE(d.received_data_before_response());
EXPECT_NE(0, d.bytes_received());
- // The first part of the log will be for URL_REQUEST_START.
- // After that, there should be an HTTP_TRANSACTION_READ_BODY
- EXPECT_TRUE(net::LogContainsBeginEvent(
- *r.load_log(), 0, net::LoadLog::TYPE_URL_REQUEST_START));
- EXPECT_TRUE(net::LogContainsEndEvent(
- *r.load_log(), -3, net::LoadLog::TYPE_URL_REQUEST_START));
- EXPECT_TRUE(net::LogContainsBeginEvent(
- *r.load_log(), -2, net::LoadLog::TYPE_HTTP_TRANSACTION_READ_BODY));
- EXPECT_TRUE(net::LogContainsEndEvent(
- *r.load_log(), -1, net::LoadLog::TYPE_HTTP_TRANSACTION_READ_BODY));
+ // TODO(eroman): Add back the NetLog tests...
}
}
@@ -283,42 +275,8 @@
class HTTPSRequestTest : public testing::Test {
};
-#if defined(OS_MACOSX)
-// Status 6/19/09:
-//
-// If these tests are enabled on OSX, the first one (HTTPSGetTest)
-// will fail. I didn't track it down explicitly, but did observe that
-// the testserver.py kills itself (e.g. "process_util_posix.cc(84)]
-// Unable to terminate process."). tlslite and testserver.py are hard
-// to debug (redirection of stdout/stderr to a file so you can't see
-// errors; lots of naked "except:" statements, etc), but I did track
-// down an SSL auth failure as one cause of it deciding to die
-// silently.
-//
-// The next test, HTTPSMismatchedTest, will look like it hangs by
-// looping over calls to SSLHandshake() (Security framework call) as
-// called from SSLClientSocketMac::DoHandshake(). Return values are a
-// repeating pattern of -9803 (come back later) and -9812 (cert valid
-// but root not trusted). If you don't have the cert in your keychain
-// as documented on http://dev.chromium.org/developers/testing, the
-// -9812 becomes a -9813 (no root cert). Interestingly, the handshake
-// also appears to be a failure point for other disabled tests, such
-// as (SSLClientSocketTest,Connect) in
-// net/base/ssl_client_socket_unittest.cc.
-//
-// Old comment (not sure if obsolete):
-// ssl_client_socket_mac.cc crashes currently in GetSSLInfo
-// when called on a connection with an unrecognized certificate
-#define MAYBE_HTTPSGetTest DISABLED_HTTPSGetTest
-#define MAYBE_HTTPSMismatchedTest DISABLED_HTTPSMismatchedTest
-#define MAYBE_HTTPSExpiredTest DISABLED_HTTPSExpiredTest
-#else
-#define MAYBE_HTTPSGetTest HTTPSGetTest
-#define MAYBE_HTTPSMismatchedTest HTTPSMismatchedTest
-#define MAYBE_HTTPSExpiredTest HTTPSExpiredTest
-#endif
-TEST_F(HTTPSRequestTest, MAYBE_HTTPSGetTest) {
+TEST_F(HTTPSRequestTest, HTTPSGetTest) {
// Note: tools/testserver/testserver.py does not need
// a working document root to server the pages / and /hello.html,
// so this test doesn't really need to specify a document root.
@@ -342,7 +300,7 @@
}
}
-TEST_F(HTTPSRequestTest, MAYBE_HTTPSMismatchedTest) {
+TEST_F(HTTPSRequestTest, HTTPSMismatchedTest) {
scoped_refptr<HTTPSTestServer> server =
HTTPSTestServer::CreateMismatchedServer(L"net/data/ssl");
ASSERT_TRUE(NULL != server.get());
@@ -370,7 +328,7 @@
}
}
-TEST_F(HTTPSRequestTest, MAYBE_HTTPSExpiredTest) {
+TEST_F(HTTPSRequestTest, HTTPSExpiredTest) {
scoped_refptr<HTTPSTestServer> server =
HTTPSTestServer::CreateExpiredServer(L"net/data/ssl");
ASSERT_TRUE(NULL != server.get());
@@ -493,7 +451,7 @@
TEST_F(URLRequestTestHTTP, CancelTest5) {
ASSERT_TRUE(NULL != server_.get());
- scoped_refptr<URLRequestContext> context = new URLRequestTestContext();
+ scoped_refptr<URLRequestContext> context = new TestURLRequestContext();
// populate cache
{
@@ -696,9 +654,11 @@
{
TestURLRequest r(temp_url, &d);
- r.SetExtraRequestHeaders(
- StringPrintf("Range: bytes=%" PRIuS "-%" PRIuS "\n",
- first_byte_position, last_byte_position));
+ net::HttpRequestHeaders headers;
+ headers.SetHeader(net::HttpRequestHeaders::kRange,
+ StringPrintf("bytes=%" PRIuS "-%" PRIuS,
+ first_byte_position, last_byte_position));
+ r.SetExtraRequestHeaders(headers);
r.Start();
EXPECT_TRUE(r.is_pending());
@@ -737,8 +697,11 @@
{
TestURLRequest r(temp_url, &d);
- r.SetExtraRequestHeaders(StringPrintf("Range: bytes=%" PRIuS "-\n",
- first_byte_position));
+ net::HttpRequestHeaders headers;
+ headers.SetHeader(net::HttpRequestHeaders::kRange,
+ StringPrintf("bytes=%" PRIuS "-",
+ first_byte_position));
+ r.SetExtraRequestHeaders(headers);
r.Start();
EXPECT_TRUE(r.is_pending());
@@ -771,7 +734,10 @@
{
TestURLRequest r(temp_url, &d);
- r.SetExtraRequestHeaders(StringPrintf("Range: bytes=0-0,10-200,200-300\n"));
+ net::HttpRequestHeaders headers;
+ headers.SetHeader(net::HttpRequestHeaders::kRange,
+ "bytes=0-0,10-200,200-300");
+ r.SetExtraRequestHeaders(headers);
r.Start();
EXPECT_TRUE(r.is_pending());
@@ -795,21 +761,6 @@
}
}
-// This test is disabled because it fails on some computers due to proxies
-// returning a page in response to this request rather than reporting failure.
-TEST_F(URLRequestTest, DISABLED_DnsFailureTest) {
- TestDelegate d;
- {
- URLRequest r(GURL("http://thisisnotavalidurl0123456789foo.com/"), &d);
-
- r.Start();
- EXPECT_TRUE(r.is_pending());
-
- MessageLoop::current()->Run();
- EXPECT_TRUE(d.request_failed());
- }
-}
-
TEST_F(URLRequestTestHTTP, ResponseHeadersTest) {
ASSERT_TRUE(NULL != server_.get());
TestDelegate d;
@@ -962,26 +913,29 @@
path = path.Append(FILE_PATH_LITERAL("url_request_unittest"));
TestDelegate d;
- d.set_quit_on_redirect(true);
TestURLRequest req(net::FilePathToFileURL(path), &d);
req.Start();
MessageLoop::current()->Run();
- // Let the directory lister have time to finish its work, which will
- // cause the URLRequestFileDirJob's ref count to drop to 1.
- URLRequestFileDirJob* job = static_cast<URLRequestFileDirJob*>(req.job());
- while (!job->list_complete()) {
- PlatformThread::Sleep(10);
- MessageLoop::current()->RunAllPending();
- }
-
- // Should not crash during this call!
- req.FollowDeferredRedirect();
-
- // Flush event queue.
- MessageLoop::current()->RunAllPending();
+ ASSERT_EQ(1, d.received_redirect_count());
+ ASSERT_LT(0, d.bytes_received());
+ ASSERT_FALSE(d.request_failed());
+ ASSERT_TRUE(req.status().is_success());
}
+#if defined(OS_WIN)
+// Don't accept the url "file:///" on windows. See http://crbug.com/1474.
+TEST_F(URLRequestTest, FileDirRedirectSingleSlash) {
+ TestDelegate d;
+ TestURLRequest req(GURL("file:///"), &d);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ ASSERT_EQ(1, d.received_redirect_count());
+ ASSERT_FALSE(req.status().is_success());
+}
+#endif
+
TEST_F(URLRequestTestHTTP, RestrictRedirects) {
ASSERT_TRUE(NULL != server_.get());
@@ -1094,14 +1048,16 @@
TEST_F(URLRequestTestHTTP, VaryHeader) {
ASSERT_TRUE(NULL != server_.get());
- scoped_refptr<URLRequestContext> context = new URLRequestTestContext();
+ scoped_refptr<URLRequestContext> context = new TestURLRequestContext();
// populate the cache
{
TestDelegate d;
URLRequest req(server_->TestServerPage("echoheader?foo"), &d);
req.set_context(context);
- req.SetExtraRequestHeaders("foo:1");
+ net::HttpRequestHeaders headers;
+ headers.SetHeader("foo", "1");
+ req.SetExtraRequestHeaders(headers);
req.Start();
MessageLoop::current()->Run();
}
@@ -1111,7 +1067,9 @@
TestDelegate d;
URLRequest req(server_->TestServerPage("echoheader?foo"), &d);
req.set_context(context);
- req.SetExtraRequestHeaders("foo:1");
+ net::HttpRequestHeaders headers;
+ headers.SetHeader("foo", "1");
+ req.SetExtraRequestHeaders(headers);
req.Start();
MessageLoop::current()->Run();
@@ -1123,7 +1081,9 @@
TestDelegate d;
URLRequest req(server_->TestServerPage("echoheader?foo"), &d);
req.set_context(context);
- req.SetExtraRequestHeaders("foo:2");
+ net::HttpRequestHeaders headers;
+ headers.SetHeader("foo", "2");
+ req.SetExtraRequestHeaders(headers);
req.Start();
MessageLoop::current()->Run();
@@ -1132,7 +1092,7 @@
}
TEST_F(URLRequestTestHTTP, BasicAuth) {
- scoped_refptr<URLRequestContext> context = new URLRequestTestContext();
+ scoped_refptr<URLRequestContext> context = new TestURLRequestContext();
ASSERT_TRUE(NULL != server_.get());
// populate the cache
@@ -1183,7 +1143,7 @@
// Request a page that will give a 401 containing a Set-Cookie header.
// Verify that when the transaction is restarted, it includes the new cookie.
{
- scoped_refptr<URLRequestContext> context = new URLRequestTestContext();
+ scoped_refptr<URLRequestContext> context = new TestURLRequestContext();
TestDelegate d;
d.set_username(L"user");
d.set_password(L"secret");
@@ -1204,7 +1164,7 @@
// Same test as above, except this time the restart is initiated earlier
// (without user intervention since identity is embedded in the URL).
{
- scoped_refptr<URLRequestContext> context = new URLRequestTestContext();
+ scoped_refptr<URLRequestContext> context = new TestURLRequestContext();
TestDelegate d;
GURL::Replacements replacements;
@@ -1232,7 +1192,7 @@
scoped_refptr<HTTPTestServer> server =
HTTPTestServer::CreateServer(L"", NULL);
ASSERT_TRUE(NULL != server.get());
- scoped_refptr<URLRequestContext> context = new URLRequestTestContext();
+ scoped_refptr<URLRequestContext> context = new TestURLRequestContext();
// Set up a cookie.
{
@@ -1241,6 +1201,8 @@
req.set_context(context);
req.Start();
MessageLoop::current()->Run();
+ EXPECT_EQ(0, d.blocked_get_cookies_count());
+ EXPECT_EQ(0, d.blocked_set_cookie_count());
}
// Verify that the cookie is set.
@@ -1253,6 +1215,8 @@
EXPECT_TRUE(d.data_received().find("CookieToNotSend=1")
!= std::string::npos);
+ EXPECT_EQ(0, d.blocked_get_cookies_count());
+ EXPECT_EQ(0, d.blocked_set_cookie_count());
}
// Verify that the cookie isn't sent when LOAD_DO_NOT_SEND_COOKIES is set.
@@ -1266,6 +1230,10 @@
EXPECT_TRUE(d.data_received().find("Cookie: CookieToNotSend=1")
== std::string::npos);
+
+ // LOAD_DO_NOT_SEND_COOKIES does not trigger OnGetCookies.
+ EXPECT_EQ(0, d.blocked_get_cookies_count());
+ EXPECT_EQ(0, d.blocked_set_cookie_count());
}
}
@@ -1273,7 +1241,7 @@
scoped_refptr<HTTPTestServer> server =
HTTPTestServer::CreateServer(L"", NULL);
ASSERT_TRUE(NULL != server.get());
- scoped_refptr<URLRequestContext> context = new URLRequestTestContext();
+ scoped_refptr<URLRequestContext> context = new TestURLRequestContext();
// Set up a cookie.
{
@@ -1283,6 +1251,10 @@
req.set_context(context);
req.Start();
MessageLoop::current()->Run();
+
+ EXPECT_EQ(0, d.blocked_get_cookies_count());
+ EXPECT_EQ(0, d.blocked_set_cookie_count());
+ EXPECT_EQ(1, d.set_cookie_count());
}
// Try to set-up another cookie and update the previous cookie.
@@ -1295,6 +1267,11 @@
req.Start();
MessageLoop::current()->Run();
+
+ // LOAD_DO_NOT_SAVE_COOKIES does not trigger OnSetCookie.
+ EXPECT_EQ(0, d.blocked_get_cookies_count());
+ EXPECT_EQ(0, d.blocked_set_cookie_count());
+ EXPECT_EQ(0, d.set_cookie_count());
}
// Verify the cookies weren't saved or updated.
@@ -1309,6 +1286,10 @@
== std::string::npos);
EXPECT_TRUE(d.data_received().find("CookieToNotUpdate=2")
!= std::string::npos);
+
+ EXPECT_EQ(0, d.blocked_get_cookies_count());
+ EXPECT_EQ(0, d.blocked_set_cookie_count());
+ EXPECT_EQ(0, d.set_cookie_count());
}
}
@@ -1316,7 +1297,7 @@
scoped_refptr<HTTPTestServer> server =
HTTPTestServer::CreateServer(L"", NULL);
ASSERT_TRUE(NULL != server.get());
- scoped_refptr<URLRequestTestContext> context = new URLRequestTestContext();
+ scoped_refptr<TestURLRequestContext> context = new TestURLRequestContext();
// Set up a cookie.
{
@@ -1325,6 +1306,9 @@
req.set_context(context);
req.Start();
MessageLoop::current()->Run();
+
+ EXPECT_EQ(0, d.blocked_get_cookies_count());
+ EXPECT_EQ(0, d.blocked_set_cookie_count());
}
// Verify that the cookie is set.
@@ -1337,6 +1321,9 @@
EXPECT_TRUE(d.data_received().find("CookieToNotSend=1")
!= std::string::npos);
+
+ EXPECT_EQ(0, d.blocked_get_cookies_count());
+ EXPECT_EQ(0, d.blocked_set_cookie_count());
}
// Verify that the cookie isn't sent.
@@ -1354,6 +1341,9 @@
== std::string::npos);
context->set_cookie_policy(NULL);
+
+ EXPECT_EQ(1, d.blocked_get_cookies_count());
+ EXPECT_EQ(0, d.blocked_set_cookie_count());
}
}
@@ -1361,7 +1351,7 @@
scoped_refptr<HTTPTestServer> server =
HTTPTestServer::CreateServer(L"", NULL);
ASSERT_TRUE(NULL != server.get());
- scoped_refptr<URLRequestTestContext> context = new URLRequestTestContext();
+ scoped_refptr<TestURLRequestContext> context = new TestURLRequestContext();
// Set up a cookie.
{
@@ -1371,6 +1361,9 @@
req.set_context(context);
req.Start();
MessageLoop::current()->Run();
+
+ EXPECT_EQ(0, d.blocked_get_cookies_count());
+ EXPECT_EQ(0, d.blocked_set_cookie_count());
}
// Try to set-up another cookie and update the previous cookie.
@@ -1387,6 +1380,9 @@
MessageLoop::current()->Run();
context->set_cookie_policy(NULL);
+
+ EXPECT_EQ(0, d.blocked_get_cookies_count());
+ EXPECT_EQ(2, d.blocked_set_cookie_count());
}
@@ -1402,6 +1398,9 @@
== std::string::npos);
EXPECT_TRUE(d.data_received().find("CookieToNotUpdate=2")
!= std::string::npos);
+
+ EXPECT_EQ(0, d.blocked_get_cookies_count());
+ EXPECT_EQ(0, d.blocked_set_cookie_count());
}
}
@@ -1409,7 +1408,7 @@
scoped_refptr<HTTPTestServer> server =
HTTPTestServer::CreateServer(L"", NULL);
ASSERT_TRUE(NULL != server.get());
- scoped_refptr<URLRequestTestContext> context = new URLRequestTestContext();
+ scoped_refptr<TestURLRequestContext> context = new TestURLRequestContext();
// Set up a cookie.
{
@@ -1418,6 +1417,9 @@
req.set_context(context);
req.Start();
MessageLoop::current()->Run();
+
+ EXPECT_EQ(0, d.blocked_get_cookies_count());
+ EXPECT_EQ(0, d.blocked_set_cookie_count());
}
// Verify that the cookie is set.
@@ -1430,6 +1432,9 @@
EXPECT_TRUE(d.data_received().find("CookieToNotSend=1")
!= std::string::npos);
+
+ EXPECT_EQ(0, d.blocked_get_cookies_count());
+ EXPECT_EQ(0, d.blocked_set_cookie_count());
}
// Verify that the cookie isn't sent.
@@ -1448,6 +1453,9 @@
== std::string::npos);
context->set_cookie_policy(NULL);
+
+ EXPECT_EQ(1, d.blocked_get_cookies_count());
+ EXPECT_EQ(0, d.blocked_set_cookie_count());
}
}
@@ -1455,7 +1463,7 @@
scoped_refptr<HTTPTestServer> server =
HTTPTestServer::CreateServer(L"", NULL);
ASSERT_TRUE(NULL != server.get());
- scoped_refptr<URLRequestTestContext> context = new URLRequestTestContext();
+ scoped_refptr<TestURLRequestContext> context = new TestURLRequestContext();
// Set up a cookie.
{
@@ -1465,6 +1473,9 @@
req.set_context(context);
req.Start();
MessageLoop::current()->Run();
+
+ EXPECT_EQ(0, d.blocked_get_cookies_count());
+ EXPECT_EQ(0, d.blocked_set_cookie_count());
}
// Try to set-up another cookie and update the previous cookie.
@@ -1482,6 +1493,9 @@
MessageLoop::current()->Run();
context->set_cookie_policy(NULL);
+
+ EXPECT_EQ(0, d.blocked_get_cookies_count());
+ EXPECT_EQ(2, d.blocked_set_cookie_count());
}
// Verify the cookies weren't saved or updated.
@@ -1496,14 +1510,17 @@
== std::string::npos);
EXPECT_TRUE(d.data_received().find("CookieToNotUpdate=2")
!= std::string::npos);
+
+ EXPECT_EQ(0, d.blocked_get_cookies_count());
+ EXPECT_EQ(0, d.blocked_set_cookie_count());
}
}
-TEST_F(URLRequestTest, CancelTest_DuringCookiePolicy) {
+TEST_F(URLRequestTest, CancelTest_During_CookiePolicy) {
scoped_refptr<HTTPTestServer> server =
HTTPTestServer::CreateServer(L"", NULL);
ASSERT_TRUE(NULL != server.get());
- scoped_refptr<URLRequestTestContext> context = new URLRequestTestContext();
+ scoped_refptr<TestURLRequestContext> context = new TestURLRequestContext();
TestCookiePolicy cookie_policy(TestCookiePolicy::ASYNC);
context->set_cookie_policy(&cookie_policy);
@@ -1516,10 +1533,113 @@
req.set_context(context);
req.Start(); // Triggers an asynchronous cookie policy check.
- // But, now we cancel the request. This should not cause a crash.
+ // But, now we cancel the request by letting it go out of scope. This
+ // should not cause a crash.
+
+ EXPECT_EQ(0, d.blocked_get_cookies_count());
+ EXPECT_EQ(0, d.blocked_set_cookie_count());
}
context->set_cookie_policy(NULL);
+
+ // Let the cookie policy complete. Make sure it handles the destruction of
+ // the URLRequest properly.
+ MessageLoop::current()->RunAllPending();
+}
+
+TEST_F(URLRequestTest, CancelTest_During_OnGetCookies) {
+ scoped_refptr<HTTPTestServer> server =
+ HTTPTestServer::CreateServer(L"", NULL);
+ ASSERT_TRUE(NULL != server.get());
+ scoped_refptr<TestURLRequestContext> context = new TestURLRequestContext();
+
+ TestCookiePolicy cookie_policy(TestCookiePolicy::NO_GET_COOKIES);
+ context->set_cookie_policy(&cookie_policy);
+
+ // Set up a cookie.
+ {
+ TestDelegate d;
+ d.set_cancel_in_get_cookies_blocked(true);
+ URLRequest req(server->TestServerPage("set-cookie?A=1&B=2&C=3"),
+ &d);
+ req.set_context(context);
+ req.Start(); // Triggers an asynchronous cookie policy check.
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::CANCELED, req.status().status());
+
+ EXPECT_EQ(1, d.blocked_get_cookies_count());
+ EXPECT_EQ(0, d.blocked_set_cookie_count());
+ }
+
+ context->set_cookie_policy(NULL);
+}
+
+TEST_F(URLRequestTest, CancelTest_During_OnSetCookie) {
+ scoped_refptr<HTTPTestServer> server =
+ HTTPTestServer::CreateServer(L"", NULL);
+ ASSERT_TRUE(NULL != server.get());
+ scoped_refptr<TestURLRequestContext> context = new TestURLRequestContext();
+
+ TestCookiePolicy cookie_policy(TestCookiePolicy::NO_SET_COOKIE);
+ context->set_cookie_policy(&cookie_policy);
+
+ // Set up a cookie.
+ {
+ TestDelegate d;
+ d.set_cancel_in_set_cookie_blocked(true);
+ URLRequest req(server->TestServerPage("set-cookie?A=1&B=2&C=3"),
+ &d);
+ req.set_context(context);
+ req.Start(); // Triggers an asynchronous cookie policy check.
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::CANCELED, req.status().status());
+
+ // Even though the response will contain 3 set-cookie headers, we expect
+ // only one to be blocked as that first one will cause OnSetCookie to be
+ // called, which will cancel the request. Once canceled, it should not
+ // attempt to set further cookies.
+
+ EXPECT_EQ(0, d.blocked_get_cookies_count());
+ EXPECT_EQ(1, d.blocked_set_cookie_count());
+ }
+
+ context->set_cookie_policy(NULL);
+}
+
+TEST_F(URLRequestTest, CookiePolicy_ForceSession) {
+ scoped_refptr<HTTPTestServer> server =
+ HTTPTestServer::CreateServer(L"", NULL);
+ ASSERT_TRUE(NULL != server.get());
+ scoped_refptr<TestURLRequestContext> context = new TestURLRequestContext();
+
+ TestCookiePolicy cookie_policy(TestCookiePolicy::FORCE_SESSION);
+ context->set_cookie_policy(&cookie_policy);
+
+ // Set up a cookie.
+ {
+ TestDelegate d;
+ URLRequest req(server->TestServerPage(
+ "set-cookie?A=1;expires=\"Fri, 05 Feb 2010 23:42:01 GMT\""), &d);
+ req.set_context(context);
+ req.Start(); // Triggers an asynchronous cookie policy check.
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(0, d.blocked_get_cookies_count());
+ EXPECT_EQ(0, d.blocked_set_cookie_count());
+ }
+
+ // Now, check the cookie store.
+ net::CookieMonster::CookieList cookies =
+ context->cookie_store()->GetCookieMonster()->GetAllCookies();
+ EXPECT_EQ(1U, cookies.size());
+ EXPECT_FALSE(cookies[0].IsPersistent());
+
+ context->set_cookie_policy(NULL);
}
// In this test, we do a POST which the server will 302 redirect.
@@ -1535,7 +1655,8 @@
req.set_upload(CreateSimpleUploadData(kData));
// Set headers (some of which are specific to the POST).
- req.SetExtraRequestHeaders(
+ net::HttpRequestHeaders headers;
+ headers.AddHeadersFromString(
"Content-Type: multipart/form-data; "
"boundary=----WebKitFormBoundaryAADeAA+NAAWMAAwZ\r\n"
"Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,"
@@ -1544,6 +1665,7 @@
"Accept-Charset: ISO-8859-1,*,utf-8\r\n"
"Content-Length: 11\r\n"
"Origin: http://localhost:1337/");
+ req.SetExtraRequestHeaders(headers);
req.Start();
MessageLoop::current()->Run();
@@ -1572,8 +1694,10 @@
&d);
req.set_method("POST");
req.set_upload(CreateSimpleUploadData(kData).get());
- req.SetExtraRequestHeaders(
- "Content-Length: " + UintToString(sizeof(kData) - 1));
+ net::HttpRequestHeaders headers;
+ headers.SetHeader(net::HttpRequestHeaders::kContentLength,
+ UintToString(arraysize(kData) - 1));
+ req.SetExtraRequestHeaders(headers);
req.Start();
MessageLoop::current()->Run();
EXPECT_EQ("POST", req.method());
@@ -2330,7 +2454,7 @@
ASSERT_TRUE(NULL != server_.get());
TestDelegate d;
TestURLRequest req(server_->TestServerPage("echoheader?Accept-Language"), &d);
- req.set_context(new URLRequestTestContext());
+ req.set_context(new TestURLRequestContext());
req.Start();
MessageLoop::current()->Run();
EXPECT_EQ(req.context()->accept_language(), d.data_received());
@@ -2343,8 +2467,10 @@
TestDelegate d;
TestURLRequest
req(server_->TestServerPage("echoheaderoverride?Accept-Language"), &d);
- req.set_context(new URLRequestTestContext());
- req.SetExtraRequestHeaders("Accept-Language: ru");
+ req.set_context(new TestURLRequestContext());
+ net::HttpRequestHeaders headers;
+ headers.SetHeader(net::HttpRequestHeaders::kAcceptLanguage, "ru");
+ req.SetExtraRequestHeaders(headers);
req.Start();
MessageLoop::current()->Run();
EXPECT_EQ(std::string("ru"), d.data_received());
@@ -2355,7 +2481,7 @@
ASSERT_TRUE(NULL != server_.get());
TestDelegate d;
TestURLRequest req(server_->TestServerPage("echoheader?Accept-Charset"), &d);
- req.set_context(new URLRequestTestContext());
+ req.set_context(new TestURLRequestContext());
req.Start();
MessageLoop::current()->Run();
EXPECT_EQ(req.context()->accept_charset(), d.data_received());
@@ -2368,8 +2494,10 @@
TestDelegate d;
TestURLRequest
req(server_->TestServerPage("echoheaderoverride?Accept-Charset"), &d);
- req.set_context(new URLRequestTestContext());
- req.SetExtraRequestHeaders("Accept-Charset: koi-8r");
+ req.set_context(new TestURLRequestContext());
+ net::HttpRequestHeaders headers;
+ headers.SetHeader(net::HttpRequestHeaders::kAcceptCharset, "koi-8r");
+ req.SetExtraRequestHeaders(headers);
req.Start();
MessageLoop::current()->Run();
EXPECT_EQ(std::string("koi-8r"), d.data_received());
diff --git a/net/url_request/url_request_unittest.h b/net/url_request/url_request_unittest.h
index 6c1be78..8f090ef 100644
--- a/net/url_request/url_request_unittest.h
+++ b/net/url_request/url_request_unittest.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -17,9 +17,9 @@
#include "base/message_loop.h"
#include "base/path_service.h"
#include "base/process_util.h"
-#include "base/string_util.h"
#include "base/thread.h"
#include "base/time.h"
+#include "base/utf_string_conversions.h"
#include "base/waitable_event.h"
#include "net/base/cookie_monster.h"
#include "net/base/cookie_policy.h"
@@ -30,9 +30,10 @@
#include "net/base/ssl_config_service_defaults.h"
#include "net/disk_cache/disk_cache.h"
#include "net/ftp/ftp_network_layer.h"
+#include "net/http/http_auth_handler_factory.h"
#include "net/http/http_cache.h"
#include "net/http/http_network_layer.h"
-#include "net/socket/ssl_test_util.h"
+#include "net/test/test_server.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
#include "net/proxy/proxy_service.h"
@@ -53,7 +54,8 @@
enum Options {
NO_GET_COOKIES = 1 << 0,
NO_SET_COOKIE = 1 << 1,
- ASYNC = 1 << 2
+ ASYNC = 1 << 2,
+ FORCE_SESSION = 1 << 3,
};
explicit TestCookiePolicy(int options_bit_mask)
@@ -93,6 +95,9 @@
if (options_ & NO_SET_COOKIE)
return net::ERR_ACCESS_DENIED;
+ if (options_ & FORCE_SESSION)
+ return net::OK_FOR_SESSION_ONLY;
+
return net::OK;
}
@@ -126,15 +131,17 @@
class TestURLRequestContext : public URLRequestContext {
public:
TestURLRequestContext() {
- host_resolver_ = net::CreateSystemHostResolver(NULL);
+ host_resolver_ =
+ net::CreateSystemHostResolver(net::HostResolver::kDefaultParallelism);
proxy_service_ = net::ProxyService::CreateNull();
Init();
}
explicit TestURLRequestContext(const std::string& proxy) {
- host_resolver_ = net::CreateSystemHostResolver(NULL);
+ host_resolver_ =
+ net::CreateSystemHostResolver(net::HostResolver::kDefaultParallelism);
net::ProxyConfig proxy_config;
- proxy_config.proxy_rules.ParseFromString(proxy);
+ proxy_config.proxy_rules().ParseFromString(proxy);
proxy_service_ = net::ProxyService::CreateFixed(proxy_config);
Init();
}
@@ -147,28 +154,29 @@
virtual ~TestURLRequestContext() {
delete ftp_transaction_factory_;
delete http_transaction_factory_;
+ delete http_auth_handler_factory_;
}
private:
void Init() {
ftp_transaction_factory_ = new net::FtpNetworkLayer(host_resolver_);
ssl_config_service_ = new net::SSLConfigServiceDefaults;
- http_transaction_factory_ =
- new net::HttpCache(
- net::HttpNetworkLayer::CreateFactory(NULL, host_resolver_,
- proxy_service_,
- ssl_config_service_),
- disk_cache::CreateInMemoryCacheBackend(0));
+ http_auth_handler_factory_ = net::HttpAuthHandlerFactory::CreateDefault();
+ http_transaction_factory_ = new net::HttpCache(
+ net::HttpNetworkLayer::CreateFactory(host_resolver_,
+ proxy_service_,
+ ssl_config_service_,
+ http_auth_handler_factory_,
+ network_delegate_,
+ NULL),
+ net::HttpCache::DefaultBackend::InMemory(0));
// In-memory cookie store.
- cookie_store_ = new net::CookieMonster();
+ cookie_store_ = new net::CookieMonster(NULL, NULL);
accept_language_ = "en-us,fr";
accept_charset_ = "iso-8859-1,*,utf-8";
}
};
-// TODO(phajdan.jr): Migrate callers to the new name and remove the typedef.
-typedef TestURLRequestContext URLRequestTestContext;
-
//-----------------------------------------------------------------------------
class TestURLRequest : public URLRequest {
@@ -188,12 +196,17 @@
cancel_in_rs_(false),
cancel_in_rd_(false),
cancel_in_rd_pending_(false),
+ cancel_in_getcookiesblocked_(false),
+ cancel_in_setcookieblocked_(false),
quit_on_complete_(true),
quit_on_redirect_(false),
allow_certificate_errors_(false),
response_started_count_(0),
received_bytes_count_(0),
received_redirect_count_(0),
+ blocked_get_cookies_count_(0),
+ blocked_set_cookie_count_(0),
+ set_cookie_count_(0),
received_data_before_response_(false),
request_failed_(false),
have_certificate_errors_(false),
@@ -296,12 +309,38 @@
request->Cancel();
}
+ virtual void OnGetCookies(URLRequest* request, bool blocked_by_policy) {
+ if (blocked_by_policy) {
+ blocked_get_cookies_count_++;
+ if (cancel_in_getcookiesblocked_)
+ request->Cancel();
+ }
+ }
+
+ virtual void OnSetCookie(URLRequest* request,
+ const std::string& cookie_line,
+ bool blocked_by_policy) {
+ if (blocked_by_policy) {
+ blocked_set_cookie_count_++;
+ if (cancel_in_setcookieblocked_)
+ request->Cancel();
+ } else {
+ set_cookie_count_++;
+ }
+ }
+
void set_cancel_in_received_redirect(bool val) { cancel_in_rr_ = val; }
void set_cancel_in_response_started(bool val) { cancel_in_rs_ = val; }
void set_cancel_in_received_data(bool val) { cancel_in_rd_ = val; }
void set_cancel_in_received_data_pending(bool val) {
cancel_in_rd_pending_ = val;
}
+ void set_cancel_in_get_cookies_blocked(bool val) {
+ cancel_in_getcookiesblocked_ = val;
+ }
+ void set_cancel_in_set_cookie_blocked(bool val) {
+ cancel_in_setcookieblocked_ = val;
+ }
void set_quit_on_complete(bool val) { quit_on_complete_ = val; }
void set_quit_on_redirect(bool val) { quit_on_redirect_ = val; }
void set_allow_certificate_errors(bool val) {
@@ -315,6 +354,9 @@
int bytes_received() const { return static_cast<int>(data_received_.size()); }
int response_started_count() const { return response_started_count_; }
int received_redirect_count() const { return received_redirect_count_; }
+ int blocked_get_cookies_count() const { return blocked_get_cookies_count_; }
+ int blocked_set_cookie_count() const { return blocked_set_cookie_count_; }
+ int set_cookie_count() const { return set_cookie_count_; }
bool received_data_before_response() const {
return received_data_before_response_;
}
@@ -328,6 +370,8 @@
bool cancel_in_rs_;
bool cancel_in_rd_;
bool cancel_in_rd_pending_;
+ bool cancel_in_getcookiesblocked_;
+ bool cancel_in_setcookieblocked_;
bool quit_on_complete_;
bool quit_on_redirect_;
bool allow_certificate_errors_;
@@ -339,6 +383,9 @@
int response_started_count_;
int received_bytes_count_;
int received_redirect_count_;
+ int blocked_get_cookies_count_;
+ int blocked_set_cookie_count_;
+ int set_cookie_count_;
bool received_data_before_response_;
bool request_failed_;
bool have_certificate_errors_;
@@ -395,12 +442,6 @@
"@" + host_name_ + ":" + port_str_ + "/" + path);
}
- // Deprecated in favor of TestServerPage.
- // TODO(phajdan.jr): Remove TestServerPageW.
- GURL TestServerPageW(const std::wstring& path) {
- return TestServerPage(WideToUTF8(path));
- }
-
virtual bool MakeGETRequest(const std::string& page_name) = 0;
FilePath GetDataDirectory() {
diff --git a/net/url_request/view_cache_helper.cc b/net/url_request/view_cache_helper.cc
index b818ba5..92701b7 100644
--- a/net/url_request/view_cache_helper.cc
+++ b/net/url_request/view_cache_helper.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Copyright (c) 2006-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.
@@ -7,6 +7,7 @@
#include "base/string_util.h"
#include "net/base/escape.h"
#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
#include "net/disk_cache/disk_cache.h"
#include "net/http/http_cache.h"
#include "net/http/http_response_headers.h"
@@ -19,7 +20,9 @@
#define VIEW_CACHE_TAIL \
"</table></body></html>"
-static void HexDump(const char *buf, size_t buf_len, std::string* result) {
+namespace {
+
+void HexDump(const char *buf, size_t buf_len, std::string* result) {
const size_t kMaxRows = 16;
int offset = 0;
@@ -56,8 +59,8 @@
}
}
-static std::string FormatEntryInfo(disk_cache::Entry* entry,
- const std::string& url_prefix) {
+std::string FormatEntryInfo(disk_cache::Entry* entry,
+ const std::string& url_prefix) {
std::string key = entry->GetKey();
GURL url = GURL(url_prefix + key);
std::string row =
@@ -66,115 +69,271 @@
return row;
}
-static std::string FormatEntryDetails(disk_cache::Entry* entry) {
- std::string result = EscapeForHTML(entry->GetKey());
+} // namespace.
- net::HttpResponseInfo response;
- bool truncated;
- if (net::HttpCache::ReadResponseInfo(entry, &response, &truncated) &&
- response.headers) {
- if (truncated)
- result.append("<pre>RESPONSE_INFO_TRUNCATED</pre>");
+namespace net {
- result.append("<hr><pre>");
- result.append(EscapeForHTML(response.headers->GetStatusLine()));
- result.push_back('\n');
+ViewCacheHelper::~ViewCacheHelper() {
+ if (entry_)
+ entry_->Close();
- void* iter = NULL;
- std::string name, value;
- while (response.headers->EnumerateHeaderLines(&iter, &name, &value)) {
- result.append(EscapeForHTML(name));
- result.append(": ");
- result.append(EscapeForHTML(value));
- result.push_back('\n');
- }
- result.append("</pre>");
- }
-
- for (int i = 0; i < 2; ++i) {
- result.append("<hr><pre>");
-
- int data_size = entry->GetDataSize(i);
-
- if (data_size) {
- scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer(data_size);
- if (entry->ReadData(i, 0, buffer, data_size, NULL) == data_size)
- HexDump(buffer->data(), data_size, &result);
- }
-
- result.append("</pre>");
- }
-
- return result;
+ // Cancel any pending entry callback.
+ entry_callback_->Cancel();
}
-static disk_cache::Backend* GetDiskCache(URLRequestContext* context) {
- if (!context)
- return NULL;
+int ViewCacheHelper::GetEntryInfoHTML(const std::string& key,
+ URLRequestContext* context,
+ std::string* out,
+ CompletionCallback* callback) {
+ return GetInfoHTML(key, context, std::string(), out, callback);
+}
- if (!context->http_transaction_factory())
- return NULL;
+int ViewCacheHelper::GetContentsHTML(URLRequestContext* context,
+ const std::string& url_prefix,
+ std::string* out,
+ CompletionCallback* callback) {
+ return GetInfoHTML(std::string(), context, url_prefix, out, callback);
+}
- net::HttpCache* http_cache = context->http_transaction_factory()->GetCache();
+//-----------------------------------------------------------------------------
+
+int ViewCacheHelper::GetInfoHTML(const std::string& key,
+ URLRequestContext* context,
+ const std::string& url_prefix,
+ std::string* out,
+ CompletionCallback* callback) {
+ DCHECK(!callback_);
+ DCHECK(context);
+ key_ = key;
+ context_ = context;
+ url_prefix_ = url_prefix;
+ data_ = out;
+ next_state_ = STATE_GET_BACKEND;
+ int rv = DoLoop(OK);
+
+ if (rv == ERR_IO_PENDING)
+ callback_ = callback;
+
+ return rv;
+}
+
+void ViewCacheHelper::DoCallback(int rv) {
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ DCHECK(callback_);
+
+ CompletionCallback* c = callback_;
+ callback_ = NULL;
+ c->Run(rv);
+}
+
+void ViewCacheHelper::HandleResult(int rv) {
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ DCHECK_NE(ERR_FAILED, rv);
+ context_ = NULL;
+ if (callback_)
+ DoCallback(rv);
+}
+
+int ViewCacheHelper::DoLoop(int result) {
+ DCHECK(next_state_ != STATE_NONE);
+
+ int rv = result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_GET_BACKEND:
+ DCHECK_EQ(OK, rv);
+ rv = DoGetBackend();
+ break;
+ case STATE_GET_BACKEND_COMPLETE:
+ rv = DoGetBackendComplete(rv);
+ break;
+ case STATE_OPEN_NEXT_ENTRY:
+ DCHECK_EQ(OK, rv);
+ rv = DoOpenNextEntry();
+ break;
+ case STATE_OPEN_NEXT_ENTRY_COMPLETE:
+ rv = DoOpenNextEntryComplete(rv);
+ break;
+ case STATE_OPEN_ENTRY:
+ DCHECK_EQ(OK, rv);
+ rv = DoOpenEntry();
+ break;
+ case STATE_OPEN_ENTRY_COMPLETE:
+ rv = DoOpenEntryComplete(rv);
+ break;
+ case STATE_READ_RESPONSE:
+ DCHECK_EQ(OK, rv);
+ rv = DoReadResponse();
+ break;
+ case STATE_READ_RESPONSE_COMPLETE:
+ rv = DoReadResponseComplete(rv);
+ break;
+ case STATE_READ_DATA:
+ DCHECK_EQ(OK, rv);
+ rv = DoReadData();
+ break;
+ case STATE_READ_DATA_COMPLETE:
+ rv = DoReadDataComplete(rv);
+ break;
+
+ default:
+ NOTREACHED() << "bad state";
+ rv = ERR_FAILED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+
+ if (rv != ERR_IO_PENDING)
+ HandleResult(rv);
+
+ return rv;
+}
+
+int ViewCacheHelper::DoGetBackend() {
+ next_state_ = STATE_GET_BACKEND_COMPLETE;
+
+ if (!context_->http_transaction_factory())
+ return ERR_FAILED;
+
+ net::HttpCache* http_cache = context_->http_transaction_factory()->GetCache();
if (!http_cache)
- return NULL;
+ return ERR_FAILED;
- return http_cache->GetBackend();
+ return http_cache->GetBackend(&disk_cache_, &cache_callback_);
}
-static std::string FormatStatistics(disk_cache::Backend* disk_cache) {
- std::vector<std::pair<std::string, std::string> > stats;
- disk_cache->GetStats(&stats);
- std::string result;
-
- for (size_t index = 0; index < stats.size(); index++) {
- result.append(stats[index].first);
- result.append(": ");
- result.append(stats[index].second);
- result.append("<br/>\n");
+int ViewCacheHelper::DoGetBackendComplete(int result) {
+ if (result == ERR_FAILED) {
+ data_->append("no disk cache");
+ return OK;
}
- return result;
+ DCHECK_EQ(OK, result);
+ if (key_.empty()) {
+ data_->assign(VIEW_CACHE_HEAD);
+ DCHECK(!iter_);
+ next_state_ = STATE_OPEN_NEXT_ENTRY;
+ return OK;
+ }
+
+ next_state_ = STATE_OPEN_ENTRY;
+ return OK;
}
-// static
-void ViewCacheHelper::GetEntryInfoHTML(const std::string& key,
- URLRequestContext* context,
- const std::string& url_prefix,
- std::string* data) {
- disk_cache::Backend* disk_cache = GetDiskCache(context);
- if (!disk_cache) {
- data->assign("no disk cache");
- return;
+int ViewCacheHelper::DoOpenNextEntry() {
+ next_state_ = STATE_OPEN_NEXT_ENTRY_COMPLETE;
+ return disk_cache_->OpenNextEntry(&iter_, &entry_, &cache_callback_);
+}
+
+int ViewCacheHelper::DoOpenNextEntryComplete(int result) {
+ if (result == ERR_FAILED) {
+ data_->append(VIEW_CACHE_TAIL);
+ return OK;
}
- if (key.empty()) {
- data->assign(VIEW_CACHE_HEAD);
- void* iter = NULL;
- disk_cache::Entry* entry;
- while (disk_cache->OpenNextEntry(&iter, &entry)) {
- data->append(FormatEntryInfo(entry, url_prefix));
- entry->Close();
+ DCHECK_EQ(OK, result);
+ data_->append(FormatEntryInfo(entry_, url_prefix_));
+ entry_->Close();
+ entry_ = NULL;
+
+ next_state_ = STATE_OPEN_NEXT_ENTRY;
+ return OK;
+}
+
+int ViewCacheHelper::DoOpenEntry() {
+ next_state_ = STATE_OPEN_ENTRY_COMPLETE;
+ return disk_cache_->OpenEntry(key_, &entry_, &cache_callback_);
+}
+
+int ViewCacheHelper::DoOpenEntryComplete(int result) {
+ if (result == ERR_FAILED) {
+ data_->append("no matching cache entry for: " + EscapeForHTML(key_));
+ return OK;
+ }
+
+ data_->assign(VIEW_CACHE_HEAD);
+ data_->append(EscapeForHTML(entry_->GetKey()));
+ next_state_ = STATE_READ_RESPONSE;
+ return OK;
+}
+
+int ViewCacheHelper::DoReadResponse() {
+ next_state_ = STATE_READ_RESPONSE_COMPLETE;
+ buf_len_ = entry_->GetDataSize(0);
+ entry_callback_->AddRef();
+ if (!buf_len_)
+ return buf_len_;
+
+ buf_ = new net::IOBuffer(buf_len_);
+ return entry_->ReadData(0, 0, buf_, buf_len_, entry_callback_);
+}
+
+int ViewCacheHelper::DoReadResponseComplete(int result) {
+ entry_callback_->Release();
+ if (result && result == buf_len_) {
+ net::HttpResponseInfo response;
+ bool truncated;
+ if (net::HttpCache::ParseResponseInfo(buf_->data(), buf_len_, &response,
+ &truncated) &&
+ response.headers) {
+ if (truncated)
+ data_->append("<pre>RESPONSE_INFO_TRUNCATED</pre>");
+
+ data_->append("<hr><pre>");
+ data_->append(EscapeForHTML(response.headers->GetStatusLine()));
+ data_->push_back('\n');
+
+ void* iter = NULL;
+ std::string name, value;
+ while (response.headers->EnumerateHeaderLines(&iter, &name, &value)) {
+ data_->append(EscapeForHTML(name));
+ data_->append(": ");
+ data_->append(EscapeForHTML(value));
+ data_->push_back('\n');
+ }
+ data_->append("</pre>");
}
- data->append(VIEW_CACHE_TAIL);
+ }
+
+ index_ = 0;
+ next_state_ = STATE_READ_DATA;
+ return OK;
+}
+
+int ViewCacheHelper::DoReadData() {
+ data_->append("<hr><pre>");
+
+ next_state_ = STATE_READ_DATA_COMPLETE;
+ buf_len_ = entry_->GetDataSize(index_);
+ entry_callback_->AddRef();
+ if (!buf_len_)
+ return buf_len_;
+
+ buf_ = new net::IOBuffer(buf_len_);
+ return entry_->ReadData(index_, 0, buf_, buf_len_, entry_callback_);
+}
+
+int ViewCacheHelper::DoReadDataComplete(int result) {
+ entry_callback_->Release();
+ if (result && result == buf_len_) {
+ HexDump(buf_->data(), buf_len_, data_);
+ }
+ data_->append("</pre>");
+ index_++;
+ if (index_ < net::HttpCache::kNumCacheEntryDataIndices) {
+ next_state_ = STATE_READ_DATA;
} else {
- disk_cache::Entry* entry;
- if (disk_cache->OpenEntry(key, &entry)) {
- data->assign(FormatEntryDetails(entry));
- entry->Close();
- } else {
- data->assign("no matching cache entry for: " + key);
- }
+ data_->append(VIEW_CACHE_TAIL);
+ entry_->Close();
+ entry_ = NULL;
}
+ return OK;
}
-// static
-void ViewCacheHelper::GetStatisticsHTML(URLRequestContext* context,
- std::string* data) {
- disk_cache::Backend* disk_cache = GetDiskCache(context);
- if (!disk_cache) {
- data->append("no disk cache");
- return;
- }
- data->append(FormatStatistics(disk_cache));
+void ViewCacheHelper::OnIOComplete(int result) {
+ DoLoop(result);
}
+
+} // namespace net.
diff --git a/net/url_request/view_cache_helper.h b/net/url_request/view_cache_helper.h
index 2648699..777775a 100644
--- a/net/url_request/view_cache_helper.h
+++ b/net/url_request/view_cache_helper.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Copyright (c) 2006-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.
@@ -7,18 +7,113 @@
#include <string>
+#include "net/base/completion_callback.h"
+#include "net/base/io_buffer.h"
+
class URLRequestContext;
+namespace disk_cache {
+class Backend;
+class Entry;
+}
+
+namespace net {
+
class ViewCacheHelper {
public:
- // Formats the cache information for |key| as HTML.
- static void GetEntryInfoHTML(const std::string& key,
- URLRequestContext* context,
- const std::string& url_prefix,
- std::string* out);
+ ViewCacheHelper()
+ : disk_cache_(NULL), entry_(NULL), iter_(NULL), buf_len_(0), index_(0),
+ data_(NULL), callback_(NULL), next_state_(STATE_NONE),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ cache_callback_(this, &ViewCacheHelper::OnIOComplete)),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ entry_callback_(new CancelableCompletionCallback<ViewCacheHelper>(
+ this, &ViewCacheHelper::OnIOComplete))) {}
+ ~ViewCacheHelper();
- static void GetStatisticsHTML(URLRequestContext* context,
- std::string* out);
+ // Formats the cache information for |key| as HTML. Returns a net error code.
+ // If this method returns ERR_IO_PENDING, |callback| will be notified when the
+ // operation completes. |out| must remain valid until this operation completes
+ // or the object is destroyed.
+ int GetEntryInfoHTML(const std::string& key, URLRequestContext* context,
+ std::string* out, CompletionCallback* callback);
+
+ // Formats the cache contents as HTML. Returns a net error code.
+ // If this method returns ERR_IO_PENDING, |callback| will be notified when the
+ // operation completes. |out| must remain valid until this operation completes
+ // or the object is destroyed. |url_prefix| will be prepended to each entry
+ // key as a link to the entry.
+ int GetContentsHTML(URLRequestContext* context, const std::string& url_prefix,
+ std::string* out, CompletionCallback* callback);
+
+ private:
+ enum State {
+ STATE_NONE,
+ STATE_GET_BACKEND,
+ STATE_GET_BACKEND_COMPLETE,
+ STATE_OPEN_NEXT_ENTRY,
+ STATE_OPEN_NEXT_ENTRY_COMPLETE,
+ STATE_OPEN_ENTRY,
+ STATE_OPEN_ENTRY_COMPLETE,
+ STATE_READ_RESPONSE,
+ STATE_READ_RESPONSE_COMPLETE,
+ STATE_READ_DATA,
+ STATE_READ_DATA_COMPLETE
+ };
+
+ // Implements GetEntryInfoHTML and GetContentsHTML.
+ int GetInfoHTML(const std::string& key, URLRequestContext* context,
+ const std::string& url_prefix, std::string* out,
+ CompletionCallback* callback);
+
+ // This is a helper function used to trigger a completion callback. It may
+ // only be called if callback_ is non-null.
+ void DoCallback(int rv);
+
+ // This will trigger the completion callback if appropriate.
+ void HandleResult(int rv);
+
+ // Runs the state transition loop.
+ int DoLoop(int result);
+
+ // Each of these methods corresponds to a State value. If there is an
+ // argument, the value corresponds to the return of the previous state or
+ // corresponding callback.
+ int DoGetBackend();
+ int DoGetBackendComplete(int result);
+ int DoOpenNextEntry();
+ int DoOpenNextEntryComplete(int result);
+ int DoOpenEntry();
+ int DoOpenEntryComplete(int result);
+ int DoReadResponse();
+ int DoReadResponseComplete(int result);
+ int DoReadData();
+ int DoReadDataComplete(int result);
+
+ // Called to signal completion of asynchronous IO.
+ void OnIOComplete(int result);
+
+ scoped_refptr<URLRequestContext> context_;
+ disk_cache::Backend* disk_cache_;
+ disk_cache::Entry* entry_;
+ void* iter_;
+ scoped_refptr<net::IOBuffer> buf_;
+ int buf_len_;
+ int index_;
+
+ std::string key_;
+ std::string url_prefix_;
+ std::string* data_;
+ CompletionCallback* callback_;
+
+ State next_state_;
+
+ CompletionCallbackImpl<ViewCacheHelper> cache_callback_;
+ scoped_refptr<CancelableCompletionCallback<ViewCacheHelper> > entry_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewCacheHelper);
};
+} // namespace net.
+
#endif // NET_URL_REQUEST_VIEW_CACHE_HELPER_H_
diff --git a/net/url_request/view_cache_helper_unittest.cc b/net/url_request/view_cache_helper_unittest.cc
new file mode 100644
index 0000000..e82ff15
--- /dev/null
+++ b/net/url_request/view_cache_helper_unittest.cc
@@ -0,0 +1,201 @@
+// 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 "net/url_request/view_cache_helper.h"
+
+#include "base/pickle.h"
+#include "net/base/test_completion_callback.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/http/http_cache.h"
+#include "net/url_request/url_request_context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class TestURLRequestContext : public URLRequestContext {
+ public:
+ TestURLRequestContext();
+
+ // Gets a pointer to the cache backend.
+ disk_cache::Backend* GetBackend();
+
+ private:
+ net::HttpCache cache_;
+};
+
+TestURLRequestContext::TestURLRequestContext()
+ : cache_(reinterpret_cast<net::HttpTransactionFactory*>(NULL),
+ net::HttpCache::DefaultBackend::InMemory(0)) {
+ http_transaction_factory_ = &cache_;
+}
+
+void WriteHeaders(disk_cache::Entry* entry, int flags, const std::string data) {
+ if (data.empty())
+ return;
+
+ Pickle pickle;
+ pickle.WriteInt(flags | 1); // Version 1.
+ pickle.WriteInt64(0);
+ pickle.WriteInt64(0);
+ pickle.WriteString(data);
+
+ scoped_refptr<net::WrappedIOBuffer> buf = new net::WrappedIOBuffer(
+ reinterpret_cast<const char*>(pickle.data()));
+ int len = static_cast<int>(pickle.size());
+
+ TestCompletionCallback cb;
+ int rv = entry->WriteData(0, 0, buf, len, &cb, true);
+ ASSERT_EQ(len, cb.GetResult(rv));
+}
+
+void WriteData(disk_cache::Entry* entry, int index, const std::string data) {
+ if (data.empty())
+ return;
+
+ int len = data.length();
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(len));
+ memcpy(buf->data(), data.data(), data.length());
+
+ TestCompletionCallback cb;
+ int rv = entry->WriteData(index, 0, buf, len, &cb, true);
+ ASSERT_EQ(len, cb.GetResult(rv));
+}
+
+void WriteToEntry(disk_cache::Backend* cache, const std::string key,
+ const std::string data0, const std::string data1,
+ const std::string data2) {
+ TestCompletionCallback cb;
+ disk_cache::Entry* entry;
+ int rv = cache->CreateEntry(key, &entry, &cb);
+ rv = cb.GetResult(rv);
+ if (rv != net::OK) {
+ rv = cache->OpenEntry(key, &entry, &cb);
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
+ }
+
+ WriteHeaders(entry, 0, data0);
+ WriteData(entry, 1, data1);
+ WriteData(entry, 2, data2);
+
+ entry->Close();
+}
+
+void FillCache(URLRequestContext* context) {
+ TestCompletionCallback cb;
+ disk_cache::Backend* cache;
+ int rv =
+ context->http_transaction_factory()->GetCache()->GetBackend(&cache, &cb);
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
+
+ std::string empty;
+ WriteToEntry(cache, "first", "some", empty, empty);
+ WriteToEntry(cache, "second", "only hex_dumped", "same", "kind");
+ WriteToEntry(cache, "third", empty, "another", "thing");
+}
+
+} // namespace.
+
+TEST(ViewCacheHelper, EmptyCache) {
+ scoped_refptr<TestURLRequestContext> context(new TestURLRequestContext());
+ net::ViewCacheHelper helper;
+
+ TestCompletionCallback cb;
+ std::string prefix, data;
+ int rv = helper.GetContentsHTML(context, prefix, &data, &cb);
+ EXPECT_EQ(net::OK, cb.GetResult(rv));
+ EXPECT_FALSE(data.empty());
+}
+
+TEST(ViewCacheHelper, ListContents) {
+ scoped_refptr<TestURLRequestContext> context(new TestURLRequestContext());
+ net::ViewCacheHelper helper;
+
+ FillCache(context);
+
+ std::string prefix, data;
+ TestCompletionCallback cb;
+ int rv = helper.GetContentsHTML(context, prefix, &data, &cb);
+ EXPECT_EQ(net::OK, cb.GetResult(rv));
+
+ EXPECT_EQ(0U, data.find("<html>"));
+ EXPECT_NE(std::string::npos, data.find("</html>"));
+ EXPECT_NE(std::string::npos, data.find("first"));
+ EXPECT_NE(std::string::npos, data.find("second"));
+ EXPECT_NE(std::string::npos, data.find("third"));
+
+ EXPECT_EQ(std::string::npos, data.find("some"));
+ EXPECT_EQ(std::string::npos, data.find("same"));
+ EXPECT_EQ(std::string::npos, data.find("thing"));
+}
+
+TEST(ViewCacheHelper, DumpEntry) {
+ scoped_refptr<TestURLRequestContext> context(new TestURLRequestContext());
+ net::ViewCacheHelper helper;
+
+ FillCache(context);
+
+ std::string data;
+ TestCompletionCallback cb;
+ int rv = helper.GetEntryInfoHTML("second", context, &data, &cb);
+ EXPECT_EQ(net::OK, cb.GetResult(rv));
+
+ EXPECT_EQ(0U, data.find("<html>"));
+ EXPECT_NE(std::string::npos, data.find("</html>"));
+
+ EXPECT_NE(std::string::npos, data.find("hex_dumped"));
+ EXPECT_NE(std::string::npos, data.find("same"));
+ EXPECT_NE(std::string::npos, data.find("kind"));
+
+ EXPECT_EQ(std::string::npos, data.find("first"));
+ EXPECT_EQ(std::string::npos, data.find("third"));
+ EXPECT_EQ(std::string::npos, data.find("some"));
+ EXPECT_EQ(std::string::npos, data.find("another"));
+}
+
+// Makes sure the links are correct.
+TEST(ViewCacheHelper, Prefix) {
+ scoped_refptr<TestURLRequestContext> context(new TestURLRequestContext());
+ net::ViewCacheHelper helper;
+
+ FillCache(context);
+
+ std::string key, data;
+ std::string prefix("prefix:");
+ TestCompletionCallback cb;
+ int rv = helper.GetContentsHTML(context, prefix, &data, &cb);
+ EXPECT_EQ(net::OK, cb.GetResult(rv));
+
+ EXPECT_EQ(0U, data.find("<html>"));
+ EXPECT_NE(std::string::npos, data.find("</html>"));
+ EXPECT_NE(std::string::npos, data.find("<a href=\"prefix:first\">"));
+ EXPECT_NE(std::string::npos, data.find("<a href=\"prefix:second\">"));
+ EXPECT_NE(std::string::npos, data.find("<a href=\"prefix:third\">"));
+}
+
+TEST(ViewCacheHelper, TruncatedFlag) {
+ scoped_refptr<TestURLRequestContext> context(new TestURLRequestContext());
+ net::ViewCacheHelper helper;
+
+ TestCompletionCallback cb;
+ disk_cache::Backend* cache;
+ int rv =
+ context->http_transaction_factory()->GetCache()->GetBackend(&cache, &cb);
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
+
+ std::string key("the key");
+ disk_cache::Entry* entry;
+ rv = cache->CreateEntry(key, &entry, &cb);
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
+
+ // RESPONSE_INFO_TRUNCATED defined on response_info.cc
+ int flags = 1 << 12;
+ WriteHeaders(entry, flags, "something");
+ entry->Close();
+
+ std::string data;
+ rv = helper.GetEntryInfoHTML(key, context, &data, &cb);
+ EXPECT_EQ(net::OK, cb.GetResult(rv));
+
+ EXPECT_NE(std::string::npos, data.find("RESPONSE_INFO_TRUNCATED"));
+}
diff --git a/net/websockets/websocket.cc b/net/websockets/websocket.cc
index ad7acaf..fa6f180 100644
--- a/net/websockets/websocket.cc
+++ b/net/websockets/websocket.cc
@@ -8,40 +8,30 @@
#include "net/websockets/websocket.h"
#include "base/message_loop.h"
-#include "net/http/http_response_headers.h"
-#include "net/http/http_util.h"
+#include "net/websockets/websocket_handshake.h"
+#include "net/websockets/websocket_handshake_draft75.h"
namespace net {
-static const int kWebSocketPort = 80;
-static const int kSecureWebSocketPort = 443;
-
-static const char kServerHandshakeHeader[] =
- "HTTP/1.1 101 Web Socket Protocol Handshake\r\n";
-static const size_t kServerHandshakeHeaderLength =
- sizeof(kServerHandshakeHeader) - 1;
-
-static const char kUpgradeHeader[] = "Upgrade: WebSocket\r\n";
-static const size_t kUpgradeHeaderLength = sizeof(kUpgradeHeader) - 1;
-
-static const char kConnectionHeader[] = "Connection: Upgrade\r\n";
-static const size_t kConnectionHeaderLength = sizeof(kConnectionHeader) - 1;
-
-bool WebSocket::Request::is_secure() const {
- return url_.SchemeIs("wss");
-}
+static const char kClosingFrame[2] = {'\xff', '\x00'};
+static int64 kClosingHandshakeTimeout = 1000; // msec.
WebSocket::WebSocket(Request* request, WebSocketDelegate* delegate)
: ready_state_(INITIALIZED),
- mode_(MODE_INCOMPLETE),
request_(request),
+ handshake_(NULL),
delegate_(delegate),
origin_loop_(MessageLoop::current()),
socket_stream_(NULL),
max_pending_send_allowed_(0),
current_read_buf_(NULL),
read_consumed_len_(0),
- current_write_buf_(NULL) {
+ current_write_buf_(NULL),
+ server_closing_handshake_(false),
+ client_closing_handshake_(false),
+ closing_handshake_started_(false),
+ force_close_task_(NULL),
+ closing_handshake_timeout_(kClosingHandshakeTimeout) {
DCHECK(request_.get());
DCHECK(delegate_);
DCHECK(origin_loop_);
@@ -74,6 +64,13 @@
}
void WebSocket::Send(const std::string& msg) {
+ if (ready_state_ == CLOSING || ready_state_ == CLOSED) {
+ return;
+ }
+ if (client_closing_handshake_) {
+ // We must not send any data after we start the WebSocket closing handshake.
+ return;
+ }
DCHECK(ready_state_ == OPEN);
DCHECK(MessageLoop::current() == origin_loop_);
@@ -89,6 +86,50 @@
void WebSocket::Close() {
DCHECK(MessageLoop::current() == origin_loop_);
+ // If connection has not yet started, do nothing.
+ if (ready_state_ == INITIALIZED) {
+ DCHECK(!socket_stream_);
+ ready_state_ = CLOSED;
+ return;
+ }
+
+ // If the readyState attribute is in the CLOSING or CLOSED state, do nothing
+ if (ready_state_ == CLOSING || ready_state_ == CLOSED)
+ return;
+
+ if (request_->version() == DRAFT75) {
+ DCHECK(socket_stream_);
+ socket_stream_->Close();
+ return;
+ }
+
+ // If the WebSocket connection is not yet established, fail the WebSocket
+ // connection and set the readyState attribute's value to CLOSING.
+ if (ready_state_ == CONNECTING) {
+ ready_state_ = CLOSING;
+ origin_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &WebSocket::FailConnection));
+ }
+
+ // If the WebSocket closing handshake has not yet been started, start
+ // the WebSocket closing handshake and set the readyState attribute's value
+ // to CLOSING.
+ if (!closing_handshake_started_) {
+ ready_state_ = CLOSING;
+ origin_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &WebSocket::StartClosingHandshake));
+ }
+
+ // Otherwise, set the readyState attribute's value to CLOSING.
+ ready_state_ = CLOSING;
+}
+
+void WebSocket::DetachDelegate() {
+ if (!delegate_)
+ return;
+ delegate_ = NULL;
if (ready_state_ == INITIALIZED) {
DCHECK(!socket_stream_);
ready_state_ = CLOSED;
@@ -97,17 +138,9 @@
if (ready_state_ != CLOSED) {
DCHECK(socket_stream_);
socket_stream_->Close();
- return;
}
}
-void WebSocket::DetachDelegate() {
- if (!delegate_)
- return;
- delegate_ = NULL;
- Close();
-}
-
void WebSocket::OnConnected(SocketStream* socket_stream,
int max_pending_send_allowed) {
DCHECK(socket_stream == socket_stream_);
@@ -119,7 +152,23 @@
read_consumed_len_ = 0;
DCHECK(!current_write_buf_);
- const std::string msg = request_->CreateClientHandshakeMessage();
+ DCHECK(!handshake_.get());
+ switch (request_->version()) {
+ case DEFAULT_VERSION:
+ handshake_.reset(new WebSocketHandshake(
+ request_->url(), request_->origin(), request_->location(),
+ request_->protocol()));
+ break;
+ case DRAFT75:
+ handshake_.reset(new WebSocketHandshakeDraft75(
+ request_->url(), request_->origin(), request_->location(),
+ request_->protocol()));
+ break;
+ default:
+ NOTREACHED() << "Unexpected protocol version:" << request_->version();
+ }
+
+ const std::string msg = handshake_->CreateClientHandshakeMessage();
IOBufferWithSize* buf = new IOBufferWithSize(msg.size());
memcpy(buf->data(), msg.data(), msg.size());
pending_write_bufs_.push_back(buf);
@@ -154,191 +203,38 @@
}
void WebSocket::OnError(const SocketStream* socket_stream, int error) {
- origin_loop_->PostTask(FROM_HERE,
- NewRunnableMethod(this, &WebSocket::DoError, error));
-}
-
-std::string WebSocket::Request::CreateClientHandshakeMessage() const {
- std::string msg;
- msg = "GET ";
- msg += url_.path();
- if (url_.has_query()) {
- msg += "?";
- msg += url_.query();
- }
- msg += " HTTP/1.1\r\n";
- msg += kUpgradeHeader;
- msg += kConnectionHeader;
- msg += "Host: ";
- msg += StringToLowerASCII(url_.host());
- if (url_.has_port()) {
- bool secure = is_secure();
- int port = url_.EffectiveIntPort();
- if ((!secure &&
- port != kWebSocketPort && port != url_parse::PORT_UNSPECIFIED) ||
- (secure &&
- port != kSecureWebSocketPort && port != url_parse::PORT_UNSPECIFIED)) {
- msg += ":";
- msg += IntToString(port);
- }
- }
- msg += "\r\n";
- msg += "Origin: ";
- // It's OK to lowercase the origin as the Origin header does not contain
- // the path or query portions, as per
- // http://tools.ietf.org/html/draft-abarth-origin-00.
- //
- // TODO(satorux): Should we trim the port portion here if it's 80 for
- // http:// or 443 for https:// ? Or can we assume it's done by the
- // client of the library?
- msg += StringToLowerASCII(origin_);
- msg += "\r\n";
- if (!protocol_.empty()) {
- msg += "WebSocket-Protocol: ";
- msg += protocol_;
- msg += "\r\n";
- }
- // TODO(ukai): Add cookie if necessary.
- msg += "\r\n";
- return msg;
-}
-
-int WebSocket::CheckHandshake() {
- DCHECK(current_read_buf_);
- DCHECK(ready_state_ == CONNECTING);
- mode_ = MODE_INCOMPLETE;
- const char *start = current_read_buf_->StartOfBuffer() + read_consumed_len_;
- const char *p = start;
- size_t len = current_read_buf_->offset() - read_consumed_len_;
- if (len < kServerHandshakeHeaderLength) {
- return -1;
- }
- if (!memcmp(p, kServerHandshakeHeader, kServerHandshakeHeaderLength)) {
- mode_ = MODE_NORMAL;
- } else {
- int eoh = HttpUtil::LocateEndOfHeaders(p, len);
- if (eoh < 0)
- return -1;
- scoped_refptr<HttpResponseHeaders> headers(
- new HttpResponseHeaders(HttpUtil::AssembleRawHeaders(p, eoh)));
- if (headers->response_code() == 407) {
- mode_ = MODE_AUTHENTICATE;
- // TODO(ukai): Implement authentication handlers.
- }
- DLOG(INFO) << "non-normal websocket connection. "
- << "response_code=" << headers->response_code()
- << " mode=" << mode_;
- // Invalid response code.
- ready_state_ = CLOSED;
- return eoh;
- }
- const char* end = p + len + 1;
- p += kServerHandshakeHeaderLength;
-
- if (mode_ == MODE_NORMAL) {
- size_t header_size = end - p;
- if (header_size < kUpgradeHeaderLength)
- return -1;
- if (memcmp(p, kUpgradeHeader, kUpgradeHeaderLength)) {
- DLOG(INFO) << "Bad Upgrade Header "
- << std::string(p, kUpgradeHeaderLength);
- ready_state_ = CLOSED;
- return p - start;
- }
- p += kUpgradeHeaderLength;
-
- header_size = end - p;
- if (header_size < kConnectionHeaderLength)
- return -1;
- if (memcmp(p, kConnectionHeader, kConnectionHeaderLength)) {
- DLOG(INFO) << "Bad Connection Header "
- << std::string(p, kConnectionHeaderLength);
- ready_state_ = CLOSED;
- return p - start;
- }
- p += kConnectionHeaderLength;
- }
- int eoh = HttpUtil::LocateEndOfHeaders(start, len);
- if (eoh == -1)
- return eoh;
- scoped_refptr<HttpResponseHeaders> headers(
- new HttpResponseHeaders(HttpUtil::AssembleRawHeaders(start, eoh)));
- if (!ProcessHeaders(*headers)) {
- DLOG(INFO) << "Process Headers failed: "
- << std::string(start, eoh);
- ready_state_ = CLOSED;
- return eoh;
- }
- switch (mode_) {
- case MODE_NORMAL:
- if (CheckResponseHeaders()) {
- ready_state_ = OPEN;
- } else {
- ready_state_ = CLOSED;
- }
- break;
- default:
- ready_state_ = CLOSED;
- break;
- }
- if (ready_state_ == CLOSED)
- DLOG(INFO) << "CheckHandshake mode=" << mode_
- << " " << std::string(start, eoh);
- return eoh;
-}
-
-// Gets the value of the specified header.
-// It assures only one header of |name| in |headers|.
-// Returns true iff single header of |name| is found in |headers|
-// and |value| is filled with the value.
-// Returns false otherwise.
-static bool GetSingleHeader(const HttpResponseHeaders& headers,
- const std::string& name,
- std::string* value) {
- std::string first_value;
- void* iter = NULL;
- if (!headers.EnumerateHeader(&iter, name, &first_value))
- return false;
-
- // Checks no more |name| found in |headers|.
- // Second call of EnumerateHeader() must return false.
- std::string second_value;
- if (headers.EnumerateHeader(&iter, name, &second_value))
- return false;
- *value = first_value;
- return true;
-}
-
-bool WebSocket::ProcessHeaders(const HttpResponseHeaders& headers) {
- if (!GetSingleHeader(headers, "websocket-origin", &ws_origin_))
- return false;
-
- if (!GetSingleHeader(headers, "websocket-location", &ws_location_))
- return false;
-
- if (!request_->protocol().empty()
- && !GetSingleHeader(headers, "websocket-protocol", &ws_protocol_))
- return false;
- return true;
-}
-
-bool WebSocket::CheckResponseHeaders() const {
- DCHECK(mode_ == MODE_NORMAL);
- if (!LowerCaseEqualsASCII(request_->origin(), ws_origin_.c_str()))
- return false;
- if (request_->location() != ws_location_)
- return false;
- if (request_->protocol() != ws_protocol_)
- return false;
- return true;
+ origin_loop_->PostTask(
+ FROM_HERE, NewRunnableMethod(this, &WebSocket::DoSocketError, error));
}
void WebSocket::SendPending() {
DCHECK(MessageLoop::current() == origin_loop_);
- DCHECK(socket_stream_);
+ if (!socket_stream_) {
+ DCHECK_EQ(CLOSED, ready_state_);
+ return;
+ }
if (!current_write_buf_) {
- if (pending_write_bufs_.empty())
+ if (pending_write_bufs_.empty()) {
+ if (client_closing_handshake_) {
+ // Already sent 0xFF and 0x00 bytes.
+ // *The WebSocket closing handshake has started.*
+ closing_handshake_started_ = true;
+ if (server_closing_handshake_) {
+ // 4.2 3-8-3 If the WebSocket connection is not already closed,
+ // then close the WebSocket connection.
+ // *The WebSocket closing handshake has finished*
+ socket_stream_->Close();
+ } else {
+ // 5. Wait a user-agent-determined length of time, or until the
+ // WebSocket connection is closed.
+ force_close_task_ =
+ NewRunnableMethod(this, &WebSocket::DoForceCloseConnection);
+ origin_loop_->PostDelayedTask(
+ FROM_HERE, force_close_task_, closing_handshake_timeout_);
+ }
+ }
return;
+ }
current_write_buf_ = new DrainableIOBuffer(
pending_write_bufs_.front(), pending_write_bufs_.front()->size());
}
@@ -352,21 +248,28 @@
void WebSocket::DoReceivedData() {
DCHECK(MessageLoop::current() == origin_loop_);
+ scoped_refptr<WebSocket> protect(this);
switch (ready_state_) {
case CONNECTING:
{
- int eoh = CheckHandshake();
+ DCHECK(handshake_.get());
+ DCHECK(current_read_buf_);
+ const char* data =
+ current_read_buf_->StartOfBuffer() + read_consumed_len_;
+ size_t len = current_read_buf_->offset() - read_consumed_len_;
+ int eoh = handshake_->ReadServerHandshake(data, len);
if (eoh < 0) {
// Not enough data, Retry when more data is available.
return;
}
SkipReadBuffer(eoh);
}
- if (ready_state_ != OPEN) {
+ if (handshake_->mode() != WebSocketHandshake::MODE_CONNECTED) {
// Handshake failed.
socket_stream_->Close();
return;
}
+ ready_state_ = OPEN;
if (delegate_)
delegate_->OnOpen(this);
if (current_read_buf_->offset() == read_consumed_len_) {
@@ -375,6 +278,7 @@
}
// FALL THROUGH
case OPEN:
+ case CLOSING: // need to process closing-frame from server.
ProcessFrameData();
break;
@@ -389,6 +293,11 @@
void WebSocket::ProcessFrameData() {
DCHECK(current_read_buf_);
+ if (server_closing_handshake_) {
+ // Any data on the connection after the 0xFF frame is discarded.
+ return;
+ }
+ scoped_refptr<WebSocket> protect(this);
const char* start_frame =
current_read_buf_->StartOfBuffer() + read_consumed_len_;
const char* next_frame = start_frame;
@@ -396,6 +305,10 @@
const char* end =
current_read_buf_->StartOfBuffer() + current_read_buf_->offset();
while (p < end) {
+ // Let /error/ be false.
+ bool error = false;
+
+ // Handle the /frame type/ byte as follows.
unsigned char frame_byte = static_cast<unsigned char>(*p++);
if ((frame_byte & 0x80) == 0x80) {
int length = 0;
@@ -417,7 +330,30 @@
if (p + length < end) {
p += length;
next_frame = p;
+ if (request_->version() != DRAFT75 &&
+ frame_byte == 0xFF && length == 0) {
+ // 4.2 Data framing 3. Handle the /frame type/ byte.
+ // 8. If the /frame type/ is 0xFF and the /length/ was 0, then
+ // run the following substeps:
+ // 1. If the WebSocket closing handshake has not yet started, then
+ // start the WebSocket closing handshake.
+ server_closing_handshake_ = true;
+ if (!closing_handshake_started_) {
+ origin_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &WebSocket::StartClosingHandshake));
+ } else {
+ // If the WebSocket closing handshake has been started and
+ // the WebSocket connection is not already closed, then close
+ // the WebSocket connection.
+ socket_stream_->Close();
+ }
+ return;
+ }
+ // 4.2 3-8 Otherwise, let /error/ be true.
+ error = true;
} else {
+ // Not enough data in buffer.
break;
}
} else {
@@ -425,12 +361,21 @@
while (p < end && *p != '\xff')
++p;
if (p < end && *p == '\xff') {
- if (frame_byte == 0x00 && delegate_)
- delegate_->OnMessage(this, std::string(msg_start, p - msg_start));
+ if (frame_byte == 0x00) {
+ if (delegate_) {
+ delegate_->OnMessage(this, std::string(msg_start, p - msg_start));
+ }
+ } else {
+ // Otherwise, discard the data and let /error/ to be true.
+ error = true;
+ }
++p;
next_frame = p;
}
}
+ // If /error/ is true, then *a WebSocket error has been detected.*
+ if (error && delegate_)
+ delegate_->OnError(this);
}
SkipReadBuffer(next_frame - start_frame);
}
@@ -473,8 +418,50 @@
}
}
+void WebSocket::StartClosingHandshake() {
+ // 4.2 *start the WebSocket closing handshake*.
+ if (closing_handshake_started_ || client_closing_handshake_) {
+ // 1. If the WebSocket closing handshake has started, then abort these
+ // steps.
+ return;
+ }
+ // 2.,3. Send a 0xFF and 0x00 byte to the server.
+ client_closing_handshake_ = true;
+ IOBufferWithSize* buf = new IOBufferWithSize(2);
+ memcpy(buf->data(), kClosingFrame, 2);
+ pending_write_bufs_.push_back(buf);
+ SendPending();
+}
+
+void WebSocket::DoForceCloseConnection() {
+ // 4.2 *start the WebSocket closing handshake*
+ // 6. If the WebSocket connection is not already closed, then close the
+ // WebSocket connection. (If this happens, then the closing handshake
+ // doesn't finish.)
+ DCHECK(MessageLoop::current() == origin_loop_);
+ force_close_task_ = NULL;
+ FailConnection();
+}
+
+void WebSocket::FailConnection() {
+ DCHECK(MessageLoop::current() == origin_loop_);
+ // 6.1 Client-initiated closure.
+ // *fail the WebSocket connection*.
+ // the user agent must close the WebSocket connection, and may report the
+ // problem to the user.
+ if (!socket_stream_)
+ return;
+ socket_stream_->Close();
+}
+
void WebSocket::DoClose() {
DCHECK(MessageLoop::current() == origin_loop_);
+ if (force_close_task_) {
+ // WebSocket connection is closed while waiting a user-agent-determined
+ // length of time after *The WebSocket closing handshake has started*.
+ force_close_task_->Cancel();
+ force_close_task_ = NULL;
+ }
WebSocketDelegate* delegate = delegate_;
delegate_ = NULL;
ready_state_ = CLOSED;
@@ -482,14 +469,15 @@
return;
socket_stream_ = NULL;
if (delegate)
- delegate->OnClose(this);
+ delegate->OnClose(this,
+ server_closing_handshake_ && closing_handshake_started_);
Release();
}
-void WebSocket::DoError(int error) {
+void WebSocket::DoSocketError(int error) {
DCHECK(MessageLoop::current() == origin_loop_);
if (delegate_)
- delegate_->OnError(this, error);
+ delegate_->OnSocketError(this, error);
}
} // namespace net
diff --git a/net/websockets/websocket.h b/net/websockets/websocket.h
index 0cf95db..373c5e4 100644
--- a/net/websockets/websocket.h
+++ b/net/websockets/websocket.h
@@ -15,6 +15,7 @@
#include <string>
#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
#include "googleurl/src/gurl.h"
#include "net/base/io_buffer.h"
#include "net/socket_stream/socket_stream.h"
@@ -26,9 +27,9 @@
class ClientSocketFactory;
class HostResolver;
-class HttpResponseHeaders;
class WebSocket;
+class WebSocketHandshake;
// Delegate methods will be called on the same message loop as
// WebSocket is constructed.
@@ -43,11 +44,14 @@
// |msg| should be in UTF-8.
virtual void OnMessage(WebSocket* socket, const std::string& msg) = 0;
+ // Called when WebSocket error has been detected.
+ virtual void OnError(WebSocket* socket) {}
+
// Called when |socket| is closed.
- virtual void OnClose(WebSocket* socket) = 0;
+ virtual void OnClose(WebSocket* socket, bool was_clean) = 0;
// Called when an error occured on |socket|.
- virtual void OnError(const WebSocket* socket, int error) {}
+ virtual void OnSocketError(const WebSocket* socket, int error) {}
};
class WebSocket : public base::RefCountedThreadSafe<WebSocket>,
@@ -57,27 +61,34 @@
INITIALIZED = -1,
CONNECTING = 0,
OPEN = 1,
- CLOSED = 2,
+ CLOSING = 2,
+ CLOSED = 3,
+ };
+ enum ProtocolVersion {
+ DEFAULT_VERSION = 0,
+ DRAFT75 = 1,
};
class Request {
public:
Request(const GURL& url, const std::string protocol,
const std::string origin, const std::string location,
+ ProtocolVersion version,
URLRequestContext* context)
: url_(url),
protocol_(protocol),
origin_(origin),
location_(location),
+ version_(version),
context_(context),
host_resolver_(NULL),
client_socket_factory_(NULL) {}
~Request() {}
const GURL& url() const { return url_; }
- bool is_secure() const;
const std::string& protocol() const { return protocol_; }
const std::string& origin() const { return origin_; }
const std::string& location() const { return location_; }
+ ProtocolVersion version() const { return version_; }
URLRequestContext* context() const { return context_; }
// Sets an alternative HostResolver. For testing purposes only.
@@ -95,14 +106,12 @@
return client_socket_factory_;
}
- // Creates the client handshake message from |this|.
- std::string CreateClientHandshakeMessage() const;
-
private:
GURL url_;
std::string protocol_;
std::string origin_;
std::string location_;
+ ProtocolVersion version_;
scoped_refptr<URLRequestContext> context_;
scoped_refptr<HostResolver> host_resolver_;
@@ -147,9 +156,6 @@
virtual void OnError(const SocketStream* socket, int error);
private:
- enum Mode {
- MODE_INCOMPLETE, MODE_NORMAL, MODE_AUTHENTICATE,
- };
typedef std::deque< scoped_refptr<IOBufferWithSize> > PendingDataQueue;
friend class WebSocketTest;
@@ -157,24 +163,6 @@
friend class base::RefCountedThreadSafe<WebSocket>;
virtual ~WebSocket();
- // Checks handshake.
- // Prerequisite: Server handshake message is received in |current_read_buf_|.
- // Returns number of bytes for server handshake message,
- // or negative if server handshake message is not received fully yet.
- int CheckHandshake();
-
- // Processes server handshake message, parsed as |headers|, and updates
- // |ws_origin_|, |ws_location_| and |ws_protocol_|.
- // Returns true if it's ok.
- // Returns false otherwise (e.g. duplicate WebSocket-Origin: header, etc.)
- bool ProcessHeaders(const HttpResponseHeaders& headers);
-
- // Checks |ws_origin_|, |ws_location_| and |ws_protocol_| are valid
- // against |request_|.
- // Returns true if it's ok.
- // Returns false otherwise (e.g. origin mismatch, etc.)
- bool CheckResponseHeaders() const;
-
// Sends pending data in |current_write_buf_| and/or |pending_write_bufs_|.
void SendPending();
@@ -190,23 +178,21 @@
// Skips |len| bytes in |current_read_buf_|.
void SkipReadBuffer(int len);
+ void StartClosingHandshake();
+ void DoForceCloseConnection();
+ void FailConnection();
// Handles closed connection.
void DoClose();
- // Handles error report.
- void DoError(int error);
+ // Handles socket error report.
+ void DoSocketError(int error);
State ready_state_;
- Mode mode_;
scoped_ptr<Request> request_;
+ scoped_ptr<WebSocketHandshake> handshake_;
WebSocketDelegate* delegate_;
MessageLoop* origin_loop_;
- // Handshake messages that server sent.
- std::string ws_origin_;
- std::string ws_location_;
- std::string ws_protocol_;
-
scoped_refptr<SocketStream> socket_stream_;
int max_pending_send_allowed_;
@@ -226,6 +212,17 @@
// Front IOBuffer is being sent via |current_write_buf_|.
PendingDataQueue pending_write_bufs_;
+ // True when the 0xFF frame with length 0x00 is received.
+ bool server_closing_handshake_;
+ // True when trying to send 0xFF and 0x00 bytes.
+ bool client_closing_handshake_;
+ // True when send 0xFF and 0x00 bytes.
+ bool closing_handshake_started_;
+ // Task to close the connection after closing handshake has started and
+ // |closing_handshake_timeout_|.
+ CancelableTask* force_close_task_;
+ int64 closing_handshake_timeout_;
+
DISALLOW_COPY_AND_ASSIGN(WebSocket);
};
diff --git a/net/websockets/websocket_frame_handler.cc b/net/websockets/websocket_frame_handler.cc
new file mode 100644
index 0000000..5ac395e
--- /dev/null
+++ b/net/websockets/websocket_frame_handler.cc
@@ -0,0 +1,140 @@
+// 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 <algorithm>
+#include <limits>
+
+#include "net/websockets/websocket_frame_handler.h"
+
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+WebSocketFrameHandler::WebSocketFrameHandler()
+ : current_buffer_size_(0),
+ original_current_buffer_size_(0) {
+}
+
+WebSocketFrameHandler::~WebSocketFrameHandler() {
+}
+
+void WebSocketFrameHandler::AppendData(const char* data, int length) {
+ scoped_refptr<IOBufferWithSize> buffer = new IOBufferWithSize(length);
+ memcpy(buffer->data(), data, length);
+ pending_buffers_.push_back(buffer);
+}
+
+int WebSocketFrameHandler::UpdateCurrentBuffer(bool buffered) {
+ if (current_buffer_)
+ return 0;
+ DCHECK(!current_buffer_size_);
+ DCHECK(!original_current_buffer_size_);
+
+ if (pending_buffers_.empty())
+ return 0;
+ scoped_refptr<IOBufferWithSize> buffer = pending_buffers_.front();
+
+ int buffer_size = 0;
+ if (buffered) {
+ std::vector<FrameInfo> frame_info;
+ buffer_size =
+ ParseWebSocketFrame(buffer->data(), buffer->size(), &frame_info);
+ if (buffer_size <= 0)
+ return buffer_size;
+
+ original_current_buffer_size_ = buffer_size;
+
+ // TODO(ukai): filter(e.g. compress or decompress) frame messages.
+ } else {
+ original_current_buffer_size_ = buffer->size();
+ buffer_size = buffer->size();
+ }
+
+ current_buffer_ = buffer;
+ current_buffer_size_ = buffer_size;
+ return buffer_size;
+}
+
+void WebSocketFrameHandler::ReleaseCurrentBuffer() {
+ DCHECK(!pending_buffers_.empty());
+ scoped_refptr<IOBufferWithSize> front_buffer = pending_buffers_.front();
+ pending_buffers_.pop_front();
+ int remaining_size = front_buffer->size() - original_current_buffer_size_;
+ if (remaining_size > 0) {
+ scoped_refptr<IOBufferWithSize> next_buffer = NULL;
+ int buffer_size = remaining_size;
+ if (!pending_buffers_.empty()) {
+ next_buffer = pending_buffers_.front();
+ buffer_size += next_buffer->size();
+ pending_buffers_.pop_front();
+ }
+ // TODO(ukai): don't copy data.
+ scoped_refptr<IOBufferWithSize> buffer = new IOBufferWithSize(buffer_size);
+ memcpy(buffer->data(), front_buffer->data() + original_current_buffer_size_,
+ remaining_size);
+ if (next_buffer)
+ memcpy(buffer->data() + remaining_size,
+ next_buffer->data(), next_buffer->size());
+ pending_buffers_.push_front(buffer);
+ }
+ current_buffer_ = NULL;
+ current_buffer_size_ = 0;
+ original_current_buffer_size_ = 0;
+}
+
+/* static */
+int WebSocketFrameHandler::ParseWebSocketFrame(
+ const char* buffer, int size, std::vector<FrameInfo>* frame_info) {
+ const char* end = buffer + size;
+ const char* p = buffer;
+ int buffer_size = 0;
+ while (p < end) {
+ FrameInfo frame;
+ frame.frame_start = p;
+ frame.message_length = -1;
+ unsigned char frame_byte = static_cast<unsigned char>(*p++);
+ if ((frame_byte & 0x80) == 0x80) {
+ int length = 0;
+ while (p < end) {
+ // Note: might overflow later if numeric_limits<int>::max() is not
+ // n*128-1.
+ if (length > std::numeric_limits<int>::max() / 128) {
+ // frame length overflow.
+ return ERR_INSUFFICIENT_RESOURCES;
+ }
+ unsigned char c = static_cast<unsigned char>(*p);
+ length = length * 128 + (c & 0x7f);
+ ++p;
+ if ((c & 0x80) != 0x80)
+ break;
+ }
+ if (end - p >= length) {
+ frame.message_start = p;
+ frame.message_length = length;
+ p += length;
+ } else {
+ break;
+ }
+ } else {
+ frame.message_start = p;
+ while (p < end && *p != '\xff')
+ ++p;
+ if (p < end && *p == '\xff') {
+ frame.message_length = p - frame.message_start;
+ ++p;
+ } else {
+ break;
+ }
+ }
+ if (frame.message_length >= 0 && p <= end) {
+ frame.frame_length = p - frame.frame_start;
+ buffer_size += frame.frame_length;
+ frame_info->push_back(frame);
+ }
+ }
+ return buffer_size;
+}
+
+} // namespace net
diff --git a/net/websockets/websocket_frame_handler.h b/net/websockets/websocket_frame_handler.h
new file mode 100644
index 0000000..d6c38da
--- /dev/null
+++ b/net/websockets/websocket_frame_handler.h
@@ -0,0 +1,81 @@
+// 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.
+
+#ifndef NET_WEBSOCKETS_WEBSOCKET_FRAME_HANDLER_H_
+#define NET_WEBSOCKETS_WEBSOCKET_FRAME_HANDLER_H_
+
+#include <deque>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/ref_counted.h"
+
+namespace net {
+
+class IOBuffer;
+class IOBufferWithSize;
+
+// Handles WebSocket frame messages.
+class WebSocketFrameHandler {
+ public:
+ struct FrameInfo {
+ const char* frame_start;
+ int frame_length;
+ const char* message_start;
+ int message_length;
+ };
+
+ WebSocketFrameHandler();
+ ~WebSocketFrameHandler();
+
+ // Appends WebSocket raw data on connection.
+ // For sending, this is data from WebKit.
+ // For receiving, this is data from network.
+ void AppendData(const char* data, int len);
+
+ // Updates current IOBuffer.
+ // If |buffered| is true, it tries to find WebSocket frames.
+ // Otherwise, it just picks the first buffer in |pending_buffers_|.
+ // Returns available size of data, 0 if no more data or current buffer was
+ // not released, and negative if some error occurred.
+ int UpdateCurrentBuffer(bool buffered);
+
+ // Gets current IOBuffer.
+ // For sending, this is data to network.
+ // For receiving, this is data to WebKit.
+ // Returns NULL just after ReleaseCurrentBuffer() was called.
+ IOBuffer* GetCurrentBuffer() { return current_buffer_.get(); }
+ int GetCurrentBufferSize() const { return current_buffer_size_; }
+
+ // Returns original buffer size of current IOBuffer.
+ // This might differ from GetCurrentBufferSize() if frame message is
+ // compressed or decompressed.
+ int GetOriginalBufferSize() const { return original_current_buffer_size_; }
+
+ // Releases current IOBuffer.
+ void ReleaseCurrentBuffer();
+
+ // Parses WebSocket frame in [|buffer|, |buffer|+|size|), fills frame
+ // information in |frame_info|, and returns number of bytes for
+ // complete WebSocket frames.
+ static int ParseWebSocketFrame(const char* buffer, int size,
+ std::vector<FrameInfo>* frame_info);
+
+ private:
+ typedef std::deque< scoped_refptr<IOBufferWithSize> > PendingDataQueue;
+
+ scoped_refptr<IOBuffer> current_buffer_;
+ int current_buffer_size_;
+
+ int original_current_buffer_size_;
+
+ // Deque of IOBuffers in pending.
+ PendingDataQueue pending_buffers_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebSocketFrameHandler);
+};
+
+} // namespace net
+
+#endif // NET_WEBSOCKETS_WEBSOCKET_FRAME_HANDLER_H_
diff --git a/net/websockets/websocket_frame_handler_unittest.cc b/net/websockets/websocket_frame_handler_unittest.cc
new file mode 100644
index 0000000..52d70de
--- /dev/null
+++ b/net/websockets/websocket_frame_handler_unittest.cc
@@ -0,0 +1,125 @@
+// 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/scoped_ptr.h"
+#include "net/base/io_buffer.h"
+#include "net/websockets/websocket_frame_handler.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+TEST(WebSocketFrameHandlerTest, Basic) {
+ const char kInputData[] = "\0hello, world\xff";
+ const int kInputDataLen = sizeof(kInputData) - 1; // no terminating NUL.
+
+ scoped_ptr<WebSocketFrameHandler> handler(new WebSocketFrameHandler);
+
+ // No data.
+ EXPECT_EQ(0, handler->UpdateCurrentBuffer(true));
+ EXPECT_TRUE(handler->GetCurrentBuffer() == NULL);
+ EXPECT_EQ(0, handler->GetCurrentBufferSize());
+
+ // WebKit sends data (WebSocketJob::SendData),
+ // or data is received from network (WebSocketJob::OnReceivedData)
+ handler->AppendData(kInputData, kInputDataLen);
+ EXPECT_TRUE(handler->GetCurrentBuffer() == NULL);
+ EXPECT_GT(handler->UpdateCurrentBuffer(true), 0);
+ // Get data to send to the socket (send),
+ // or to send to WebKit (receive).
+ IOBuffer* buf = handler->GetCurrentBuffer();
+ ASSERT_TRUE(buf != NULL);
+ EXPECT_TRUE(memcmp(buf->data(), kInputData, kInputDataLen) == 0);
+ EXPECT_EQ(kInputDataLen, handler->GetCurrentBufferSize());
+ EXPECT_EQ(kInputDataLen, handler->GetOriginalBufferSize());
+ // Data was sent. (WebSocketJob::OnSentData)
+ buf = NULL;
+ handler->ReleaseCurrentBuffer();
+ EXPECT_TRUE(handler->GetCurrentBuffer() == NULL);
+ EXPECT_EQ(0, handler->GetCurrentBufferSize());
+ EXPECT_EQ(0, handler->UpdateCurrentBuffer(true));
+}
+
+TEST(WebSocketFrameHandlerTest, ParseFrame) {
+ std::vector<WebSocketFrameHandler::FrameInfo> frames;
+ const char kInputData[] = "\0hello, world\xff\xff\0";
+ const int kInputDataLen = sizeof(kInputData) - 1;
+ const int kHelloWorldFrameLen = 14;
+
+ EXPECT_EQ(kInputDataLen,
+ WebSocketFrameHandler::ParseWebSocketFrame(
+ kInputData, kInputDataLen, &frames));
+ EXPECT_EQ(2UL, frames.size());
+
+ EXPECT_EQ(kInputData, frames[0].frame_start);
+ EXPECT_EQ(kHelloWorldFrameLen, frames[0].frame_length);
+ EXPECT_EQ(kInputData + 1, frames[0].message_start);
+ EXPECT_EQ(kHelloWorldFrameLen - 2, frames[0].message_length);
+
+ EXPECT_EQ(kInputData + kHelloWorldFrameLen, frames[1].frame_start);
+ EXPECT_EQ(2, frames[1].frame_length);
+ EXPECT_EQ(0, frames[1].message_length);
+}
+
+TEST(WebSocketFrameHandlerTest, ParseFrameLength) {
+ std::vector<WebSocketFrameHandler::FrameInfo> frames;
+ const char kHelloWorldFrame[] = "\0hello, world\xff";
+ const int kHelloWorldFrameLen = sizeof(kHelloWorldFrame) - 1;
+ const char kLengthFrame[3 + 129] = "\x80\x81\x01\x01\0should be skipped\xff";
+ const int kLengthFrameLen = sizeof(kLengthFrame);
+ const int kInputDataLen = kHelloWorldFrameLen +
+ kLengthFrameLen +
+ kHelloWorldFrameLen;
+ char inputData[kInputDataLen];
+ memcpy(inputData, kHelloWorldFrame, kHelloWorldFrameLen);
+ memcpy(inputData + kHelloWorldFrameLen, kLengthFrame, kLengthFrameLen);
+ memcpy(inputData + kHelloWorldFrameLen + kLengthFrameLen,
+ kHelloWorldFrame, kHelloWorldFrameLen);
+
+ EXPECT_EQ(kInputDataLen,
+ WebSocketFrameHandler::ParseWebSocketFrame(
+ inputData, kInputDataLen, &frames));
+ ASSERT_EQ(3UL, frames.size());
+
+ EXPECT_EQ(inputData, frames[0].frame_start);
+ EXPECT_EQ(kHelloWorldFrameLen, frames[0].frame_length);
+ EXPECT_EQ(inputData + 1, frames[0].message_start);
+ EXPECT_EQ(kHelloWorldFrameLen - 2, frames[0].message_length);
+
+ EXPECT_EQ(inputData + kHelloWorldFrameLen, frames[1].frame_start);
+ EXPECT_EQ(kLengthFrameLen, frames[1].frame_length);
+ EXPECT_EQ(inputData + kHelloWorldFrameLen + 3, frames[1].message_start);
+ EXPECT_EQ(kLengthFrameLen - 3, frames[1].message_length);
+
+ EXPECT_EQ(inputData + kHelloWorldFrameLen + kLengthFrameLen,
+ frames[2].frame_start);
+ EXPECT_EQ(kHelloWorldFrameLen, frames[2].frame_length);
+ EXPECT_EQ(inputData + kHelloWorldFrameLen + kLengthFrameLen + 1,
+ frames[2].message_start);
+ EXPECT_EQ(kHelloWorldFrameLen - 2, frames[2].message_length);
+}
+
+TEST(WebSocketFrameHandlerTest, ParsePartialFrame) {
+ std::vector<WebSocketFrameHandler::FrameInfo> frames;
+ const char kInputData[] = "\0hello, world\xff"
+ "\x80\x81\x01" // skip 1*128+1 bytes.
+ "\x01\xff"
+ "\0should be skipped\xff";
+ const int kInputDataLen = sizeof(kInputData) - 1;
+ const int kHelloWorldFrameLen = 14;
+
+ EXPECT_EQ(kHelloWorldFrameLen,
+ WebSocketFrameHandler::ParseWebSocketFrame(
+ kInputData, kInputDataLen, &frames));
+ ASSERT_EQ(1UL, frames.size());
+
+ EXPECT_EQ(kInputData, frames[0].frame_start);
+ EXPECT_EQ(kHelloWorldFrameLen, frames[0].frame_length);
+ EXPECT_EQ(kInputData + 1, frames[0].message_start);
+ EXPECT_EQ(kHelloWorldFrameLen - 2, frames[0].message_length);
+}
+
+} // namespace net
diff --git a/net/websockets/websocket_handshake.cc b/net/websockets/websocket_handshake.cc
new file mode 100644
index 0000000..5adfa67
--- /dev/null
+++ b/net/websockets/websocket_handshake.cc
@@ -0,0 +1,303 @@
+// 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 "net/websockets/websocket_handshake.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "base/md5.h"
+#include "base/rand_util.h"
+#include "base/ref_counted.h"
+#include "base/string_util.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_util.h"
+
+namespace net {
+
+const int WebSocketHandshake::kWebSocketPort = 80;
+const int WebSocketHandshake::kSecureWebSocketPort = 443;
+
+WebSocketHandshake::WebSocketHandshake(
+ const GURL& url,
+ const std::string& origin,
+ const std::string& location,
+ const std::string& protocol)
+ : url_(url),
+ origin_(origin),
+ location_(location),
+ protocol_(protocol),
+ mode_(MODE_INCOMPLETE) {
+}
+
+WebSocketHandshake::~WebSocketHandshake() {
+}
+
+bool WebSocketHandshake::is_secure() const {
+ return url_.SchemeIs("wss");
+}
+
+std::string WebSocketHandshake::CreateClientHandshakeMessage() {
+ if (!parameter_.get()) {
+ parameter_.reset(new Parameter);
+ parameter_->GenerateKeys();
+ }
+ std::string msg;
+
+ // WebSocket protocol 4.1 Opening handshake.
+
+ msg = "GET ";
+ msg += GetResourceName();
+ msg += " HTTP/1.1\r\n";
+
+ std::vector<std::string> fields;
+
+ fields.push_back("Upgrade: WebSocket");
+ fields.push_back("Connection: Upgrade");
+
+ fields.push_back("Host: " + GetHostFieldValue());
+
+ fields.push_back("Origin: " + GetOriginFieldValue());
+
+ if (!protocol_.empty())
+ fields.push_back("Sec-WebSocket-Protocol: " + protocol_);
+
+ // TODO(ukai): Add cookie if necessary.
+
+ fields.push_back("Sec-WebSocket-Key1: " + parameter_->GetSecWebSocketKey1());
+ fields.push_back("Sec-WebSocket-Key2: " + parameter_->GetSecWebSocketKey2());
+
+ std::random_shuffle(fields.begin(), fields.end());
+
+ for (size_t i = 0; i < fields.size(); i++) {
+ msg += fields[i] + "\r\n";
+ }
+ msg += "\r\n";
+
+ msg.append(parameter_->GetKey3());
+ return msg;
+}
+
+int WebSocketHandshake::ReadServerHandshake(const char* data, size_t len) {
+ mode_ = MODE_INCOMPLETE;
+ int eoh = HttpUtil::LocateEndOfHeaders(data, len);
+ if (eoh < 0)
+ return -1;
+
+ scoped_refptr<HttpResponseHeaders> headers(
+ new HttpResponseHeaders(HttpUtil::AssembleRawHeaders(data, eoh)));
+
+ if (headers->response_code() != 101) {
+ mode_ = MODE_FAILED;
+ DLOG(INFO) << "Bad response code: " << headers->response_code();
+ return eoh;
+ }
+ mode_ = MODE_NORMAL;
+ if (!ProcessHeaders(*headers) || !CheckResponseHeaders()) {
+ DLOG(INFO) << "Process Headers failed: "
+ << std::string(data, eoh);
+ mode_ = MODE_FAILED;
+ return eoh;
+ }
+ if (len < static_cast<size_t>(eoh + Parameter::kExpectedResponseSize)) {
+ mode_ = MODE_INCOMPLETE;
+ return -1;
+ }
+ uint8 expected[Parameter::kExpectedResponseSize];
+ parameter_->GetExpectedResponse(expected);
+ if (memcmp(&data[eoh], expected, Parameter::kExpectedResponseSize)) {
+ mode_ = MODE_FAILED;
+ return eoh + Parameter::kExpectedResponseSize;
+ }
+ mode_ = MODE_CONNECTED;
+ return eoh + Parameter::kExpectedResponseSize;
+}
+
+std::string WebSocketHandshake::GetResourceName() const {
+ std::string resource_name = url_.path();
+ if (url_.has_query()) {
+ resource_name += "?";
+ resource_name += url_.query();
+ }
+ return resource_name;
+}
+
+std::string WebSocketHandshake::GetHostFieldValue() const {
+ // url_.host() is expected to be encoded in punnycode here.
+ std::string host = StringToLowerASCII(url_.host());
+ if (url_.has_port()) {
+ bool secure = is_secure();
+ int port = url_.EffectiveIntPort();
+ if ((!secure &&
+ port != kWebSocketPort && port != url_parse::PORT_UNSPECIFIED) ||
+ (secure &&
+ port != kSecureWebSocketPort && port != url_parse::PORT_UNSPECIFIED)) {
+ host += ":";
+ host += IntToString(port);
+ }
+ }
+ return host;
+}
+
+std::string WebSocketHandshake::GetOriginFieldValue() const {
+ // It's OK to lowercase the origin as the Origin header does not contain
+ // the path or query portions, as per
+ // http://tools.ietf.org/html/draft-abarth-origin-00.
+ //
+ // TODO(satorux): Should we trim the port portion here if it's 80 for
+ // http:// or 443 for https:// ? Or can we assume it's done by the
+ // client of the library?
+ return StringToLowerASCII(origin_);
+}
+
+/* static */
+bool WebSocketHandshake::GetSingleHeader(const HttpResponseHeaders& headers,
+ const std::string& name,
+ std::string* value) {
+ std::string first_value;
+ void* iter = NULL;
+ if (!headers.EnumerateHeader(&iter, name, &first_value))
+ return false;
+
+ // Checks no more |name| found in |headers|.
+ // Second call of EnumerateHeader() must return false.
+ std::string second_value;
+ if (headers.EnumerateHeader(&iter, name, &second_value))
+ return false;
+ *value = first_value;
+ return true;
+}
+
+bool WebSocketHandshake::ProcessHeaders(const HttpResponseHeaders& headers) {
+ std::string value;
+ if (!GetSingleHeader(headers, "upgrade", &value) ||
+ value != "WebSocket")
+ return false;
+
+ if (!GetSingleHeader(headers, "connection", &value) ||
+ !LowerCaseEqualsASCII(value, "upgrade"))
+ return false;
+
+ if (!GetSingleHeader(headers, "sec-websocket-origin", &ws_origin_))
+ return false;
+
+ if (!GetSingleHeader(headers, "sec-websocket-location", &ws_location_))
+ return false;
+
+ // If |protocol_| is not specified by client, we don't care if there's
+ // protocol field or not as specified in the spec.
+ if (!protocol_.empty()
+ && !GetSingleHeader(headers, "sec-websocket-protocol", &ws_protocol_))
+ return false;
+ return true;
+}
+
+bool WebSocketHandshake::CheckResponseHeaders() const {
+ DCHECK(mode_ == MODE_NORMAL);
+ if (!LowerCaseEqualsASCII(origin_, ws_origin_.c_str()))
+ return false;
+ if (location_ != ws_location_)
+ return false;
+ if (!protocol_.empty() && protocol_ != ws_protocol_)
+ return false;
+ return true;
+}
+
+namespace {
+
+// unsigned int version of base::RandInt().
+// we can't use base::RandInt(), because max would be negative if it is
+// represented as int, so DCHECK(min <= max) fails.
+uint32 RandUint32(uint32 min, uint32 max) {
+ DCHECK(min <= max);
+
+ uint64 range = static_cast<int64>(max) - min + 1;
+ uint64 number = base::RandUint64();
+ // TODO(ukai): fix to be uniform.
+ // the distribution of the result of modulo will be biased.
+ uint32 result = min + static_cast<uint32>(number % range);
+ DCHECK(result >= min && result <= max);
+ return result;
+}
+
+}
+
+uint32 (*WebSocketHandshake::Parameter::rand_)(uint32 min, uint32 max) =
+ RandUint32;
+uint8 randomCharacterInSecWebSocketKey[0x2F - 0x20 + 0x7E - 0x39];
+
+WebSocketHandshake::Parameter::Parameter()
+ : number_1_(0), number_2_(0) {
+ if (randomCharacterInSecWebSocketKey[0] == '\0') {
+ int i = 0;
+ for (int ch = 0x21; ch <= 0x2F; ch++, i++)
+ randomCharacterInSecWebSocketKey[i] = ch;
+ for (int ch = 0x3A; ch <= 0x7E; ch++, i++)
+ randomCharacterInSecWebSocketKey[i] = ch;
+ }
+}
+
+WebSocketHandshake::Parameter::~Parameter() {}
+
+void WebSocketHandshake::Parameter::GenerateKeys() {
+ GenerateSecWebSocketKey(&number_1_, &key_1_);
+ GenerateSecWebSocketKey(&number_2_, &key_2_);
+ GenerateKey3();
+}
+
+static void SetChallengeNumber(uint8* buf, uint32 number) {
+ uint8* p = buf + 3;
+ for (int i = 0; i < 4; i++) {
+ *p = (uint8)(number & 0xFF);
+ --p;
+ number >>= 8;
+ }
+}
+
+void WebSocketHandshake::Parameter::GetExpectedResponse(uint8 *expected) const {
+ uint8 challenge[kExpectedResponseSize];
+ SetChallengeNumber(&challenge[0], number_1_);
+ SetChallengeNumber(&challenge[4], number_2_);
+ memcpy(&challenge[8], key_3_.data(), kKey3Size);
+ MD5Digest digest;
+ MD5Sum(challenge, kExpectedResponseSize, &digest);
+ memcpy(expected, digest.a, kExpectedResponseSize);
+}
+
+/* static */
+void WebSocketHandshake::Parameter::SetRandomNumberGenerator(
+ uint32 (*rand)(uint32 min, uint32 max)) {
+ rand_ = rand;
+}
+
+void WebSocketHandshake::Parameter::GenerateSecWebSocketKey(
+ uint32* number, std::string* key) {
+ uint32 space = rand_(1, 12);
+ uint32 max = 4294967295U / space;
+ *number = rand_(0, max);
+ uint32 product = *number * space;
+
+ std::string s = StringPrintf("%u", product);
+ int n = rand_(1, 12);
+ for (int i = 0; i < n; i++) {
+ int pos = rand_(0, s.length());
+ int chpos = rand_(0, sizeof(randomCharacterInSecWebSocketKey) - 1);
+ s = s.substr(0, pos).append(1, randomCharacterInSecWebSocketKey[chpos]) +
+ s.substr(pos);
+ }
+ for (uint32 i = 0; i < space; i++) {
+ int pos = rand_(1, s.length() - 1);
+ s = s.substr(0, pos) + " " + s.substr(pos);
+ }
+ *key = s;
+}
+
+void WebSocketHandshake::Parameter::GenerateKey3() {
+ key_3_.clear();
+ for (int i = 0; i < 8; i++) {
+ key_3_.append(1, rand_(0, 255));
+ }
+}
+
+} // namespace net
diff --git a/net/websockets/websocket_handshake.h b/net/websockets/websocket_handshake.h
new file mode 100644
index 0000000..3f64b8b
--- /dev/null
+++ b/net/websockets/websocket_handshake.h
@@ -0,0 +1,117 @@
+// 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.
+
+#ifndef NET_WEBSOCKETS_WEBSOCKET_HANDSHAKE_H_
+#define NET_WEBSOCKETS_WEBSOCKET_HANDSHAKE_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "googleurl/src/gurl.h"
+
+namespace net {
+
+class HttpResponseHeaders;
+
+class WebSocketHandshake {
+ public:
+ static const int kWebSocketPort;
+ static const int kSecureWebSocketPort;
+
+ enum Mode {
+ MODE_INCOMPLETE, MODE_NORMAL, MODE_FAILED, MODE_CONNECTED
+ };
+ WebSocketHandshake(const GURL& url,
+ const std::string& origin,
+ const std::string& location,
+ const std::string& protocol);
+ virtual ~WebSocketHandshake();
+
+ bool is_secure() const;
+ // Creates the client handshake message from |this|.
+ virtual std::string CreateClientHandshakeMessage();
+
+ // Reads server handshake message in |len| of |data|, updates |mode_| and
+ // returns number of bytes of the server handshake message.
+ // Once connection is established, |mode_| will be MODE_CONNECTED.
+ // If connection establishment failed, |mode_| will be MODE_FAILED.
+ // Returns negative if the server handshake message is incomplete.
+ virtual int ReadServerHandshake(const char* data, size_t len);
+ Mode mode() const { return mode_; }
+
+ protected:
+ std::string GetResourceName() const;
+ std::string GetHostFieldValue() const;
+ std::string GetOriginFieldValue() const;
+
+ // Gets the value of the specified header.
+ // It assures only one header of |name| in |headers|.
+ // Returns true iff single header of |name| is found in |headers|
+ // and |value| is filled with the value.
+ // Returns false otherwise.
+ static bool GetSingleHeader(const HttpResponseHeaders& headers,
+ const std::string& name,
+ std::string* value);
+
+ GURL url_;
+ // Handshake messages that the client is going to send out.
+ std::string origin_;
+ std::string location_;
+ std::string protocol_;
+
+ Mode mode_;
+
+ // Handshake messages that server sent.
+ std::string ws_origin_;
+ std::string ws_location_;
+ std::string ws_protocol_;
+
+ private:
+ friend class WebSocketHandshakeTest;
+
+ class Parameter {
+ public:
+ static const int kKey3Size = 8;
+ static const int kExpectedResponseSize = 16;
+ Parameter();
+ ~Parameter();
+
+ void GenerateKeys();
+ const std::string& GetSecWebSocketKey1() const { return key_1_; }
+ const std::string& GetSecWebSocketKey2() const { return key_2_; }
+ const std::string& GetKey3() const { return key_3_; }
+
+ void GetExpectedResponse(uint8* expected) const;
+
+ private:
+ friend class WebSocketHandshakeTest;
+
+ // Set random number generator. |rand| should return a random number
+ // between min and max (inclusive).
+ static void SetRandomNumberGenerator(
+ uint32 (*rand)(uint32 min, uint32 max));
+ void GenerateSecWebSocketKey(uint32* number, std::string* key);
+ void GenerateKey3();
+
+ uint32 number_1_;
+ uint32 number_2_;
+ std::string key_1_;
+ std::string key_2_;
+ std::string key_3_;
+
+ static uint32 (*rand_)(uint32 min, uint32 max);
+ };
+
+ virtual bool ProcessHeaders(const HttpResponseHeaders& headers);
+ virtual bool CheckResponseHeaders() const;
+
+ scoped_ptr<Parameter> parameter_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebSocketHandshake);
+};
+
+} // namespace net
+
+#endif // NET_WEBSOCKETS_WEBSOCKET_HANDSHAKE_H_
diff --git a/net/websockets/websocket_handshake_draft75.cc b/net/websockets/websocket_handshake_draft75.cc
new file mode 100644
index 0000000..78805fb
--- /dev/null
+++ b/net/websockets/websocket_handshake_draft75.cc
@@ -0,0 +1,156 @@
+// 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 "net/websockets/websocket_handshake_draft75.h"
+
+#include "base/ref_counted.h"
+#include "base/string_util.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_util.h"
+
+namespace net {
+
+const char WebSocketHandshakeDraft75::kServerHandshakeHeader[] =
+ "HTTP/1.1 101 Web Socket Protocol Handshake\r\n";
+const size_t WebSocketHandshakeDraft75::kServerHandshakeHeaderLength =
+ sizeof(kServerHandshakeHeader) - 1;
+
+const char WebSocketHandshakeDraft75::kUpgradeHeader[] =
+ "Upgrade: WebSocket\r\n";
+const size_t WebSocketHandshakeDraft75::kUpgradeHeaderLength =
+ sizeof(kUpgradeHeader) - 1;
+
+const char WebSocketHandshakeDraft75::kConnectionHeader[] =
+ "Connection: Upgrade\r\n";
+const size_t WebSocketHandshakeDraft75::kConnectionHeaderLength =
+ sizeof(kConnectionHeader) - 1;
+
+WebSocketHandshakeDraft75::WebSocketHandshakeDraft75(
+ const GURL& url,
+ const std::string& origin,
+ const std::string& location,
+ const std::string& protocol)
+ : WebSocketHandshake(url, origin, location, protocol) {
+}
+
+WebSocketHandshakeDraft75::~WebSocketHandshakeDraft75() {
+}
+
+std::string WebSocketHandshakeDraft75::CreateClientHandshakeMessage() {
+ std::string msg;
+ msg = "GET ";
+ msg += GetResourceName();
+ msg += " HTTP/1.1\r\n";
+ msg += kUpgradeHeader;
+ msg += kConnectionHeader;
+ msg += "Host: ";
+ msg += GetHostFieldValue();
+ msg += "\r\n";
+ msg += "Origin: ";
+ msg += GetOriginFieldValue();
+ msg += "\r\n";
+ if (!protocol_.empty()) {
+ msg += "WebSocket-Protocol: ";
+ msg += protocol_;
+ msg += "\r\n";
+ }
+ // TODO(ukai): Add cookie if necessary.
+ msg += "\r\n";
+ return msg;
+}
+
+int WebSocketHandshakeDraft75::ReadServerHandshake(
+ const char* data, size_t len) {
+ mode_ = MODE_INCOMPLETE;
+ if (len < kServerHandshakeHeaderLength) {
+ return -1;
+ }
+ if (!memcmp(data, kServerHandshakeHeader, kServerHandshakeHeaderLength)) {
+ mode_ = MODE_NORMAL;
+ } else {
+ int eoh = HttpUtil::LocateEndOfHeaders(data, len);
+ if (eoh < 0)
+ return -1;
+ return eoh;
+ }
+ const char* p = data + kServerHandshakeHeaderLength;
+ const char* end = data + len;
+
+ if (mode_ == MODE_NORMAL) {
+ size_t header_size = end - p;
+ if (header_size < kUpgradeHeaderLength)
+ return -1;
+ if (memcmp(p, kUpgradeHeader, kUpgradeHeaderLength)) {
+ mode_ = MODE_FAILED;
+ DLOG(INFO) << "Bad Upgrade Header "
+ << std::string(p, kUpgradeHeaderLength);
+ return p - data;
+ }
+ p += kUpgradeHeaderLength;
+ header_size = end - p;
+ if (header_size < kConnectionHeaderLength)
+ return -1;
+ if (memcmp(p, kConnectionHeader, kConnectionHeaderLength)) {
+ mode_ = MODE_FAILED;
+ DLOG(INFO) << "Bad Connection Header "
+ << std::string(p, kConnectionHeaderLength);
+ return p - data;
+ }
+ p += kConnectionHeaderLength;
+ }
+
+ int eoh = HttpUtil::LocateEndOfHeaders(data, len);
+ if (eoh == -1)
+ return eoh;
+
+ scoped_refptr<HttpResponseHeaders> headers(
+ new HttpResponseHeaders(HttpUtil::AssembleRawHeaders(data, eoh)));
+ if (!ProcessHeaders(*headers)) {
+ DLOG(INFO) << "Process Headers failed: "
+ << std::string(data, eoh);
+ mode_ = MODE_FAILED;
+ }
+ switch (mode_) {
+ case MODE_NORMAL:
+ if (CheckResponseHeaders()) {
+ mode_ = MODE_CONNECTED;
+ } else {
+ mode_ = MODE_FAILED;
+ }
+ break;
+ default:
+ mode_ = MODE_FAILED;
+ break;
+ }
+ return eoh;
+}
+
+bool WebSocketHandshakeDraft75::ProcessHeaders(
+ const HttpResponseHeaders& headers) {
+ if (!GetSingleHeader(headers, "websocket-origin", &ws_origin_))
+ return false;
+
+ if (!GetSingleHeader(headers, "websocket-location", &ws_location_))
+ return false;
+
+ // If |protocol_| is not specified by client, we don't care if there's
+ // protocol field or not as specified in the spec.
+ if (!protocol_.empty()
+ && !GetSingleHeader(headers, "websocket-protocol", &ws_protocol_))
+ return false;
+ return true;
+}
+
+bool WebSocketHandshakeDraft75::CheckResponseHeaders() const {
+ DCHECK(mode_ == MODE_NORMAL);
+ if (!LowerCaseEqualsASCII(origin_, ws_origin_.c_str()))
+ return false;
+ if (location_ != ws_location_)
+ return false;
+ if (!protocol_.empty() && protocol_ != ws_protocol_)
+ return false;
+ return true;
+}
+
+} // namespace net
diff --git a/net/websockets/websocket_handshake_draft75.h b/net/websockets/websocket_handshake_draft75.h
new file mode 100644
index 0000000..6cc0506
--- /dev/null
+++ b/net/websockets/websocket_handshake_draft75.h
@@ -0,0 +1,63 @@
+// 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.
+
+#ifndef NET_WEBSOCKETS_WEBSOCKET_HANDSHAKE_DRAFT75_H_
+#define NET_WEBSOCKETS_WEBSOCKET_HANDSHAKE_DRAFT75_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "googleurl/src/gurl.h"
+#include "net/websockets/websocket_handshake.h"
+
+namespace net {
+
+class HttpResponseHeaders;
+
+class WebSocketHandshakeDraft75 : public WebSocketHandshake {
+ public:
+ static const int kWebSocketPort;
+ static const int kSecureWebSocketPort;
+ static const char kServerHandshakeHeader[];
+ static const size_t kServerHandshakeHeaderLength;
+ static const char kUpgradeHeader[];
+ static const size_t kUpgradeHeaderLength;
+ static const char kConnectionHeader[];
+ static const size_t kConnectionHeaderLength;
+
+ WebSocketHandshakeDraft75(const GURL& url,
+ const std::string& origin,
+ const std::string& location,
+ const std::string& protocol);
+ virtual ~WebSocketHandshakeDraft75();
+
+ // Creates the client handshake message from |this|.
+ virtual std::string CreateClientHandshakeMessage();
+
+ // Reads server handshake message in |len| of |data|, updates |mode_| and
+ // returns number of bytes of the server handshake message.
+ // Once connection is established, |mode_| will be MODE_CONNECTED.
+ // If connection establishment failed, |mode_| will be MODE_FAILED.
+ // Returns negative if the server handshake message is incomplete.
+ virtual int ReadServerHandshake(const char* data, size_t len);
+
+ private:
+ // Processes server handshake message, parsed as |headers|, and updates
+ // |ws_origin_|, |ws_location_| and |ws_protocol_|.
+ // Returns true if it's ok.
+ // Returns false otherwise (e.g. duplicate WebSocket-Origin: header, etc.)
+ virtual bool ProcessHeaders(const HttpResponseHeaders& headers);
+
+ // Checks |ws_origin_|, |ws_location_| and |ws_protocol_| are valid
+ // against |origin_|, |location_| and |protocol_|.
+ // Returns true if it's ok.
+ // Returns false otherwise (e.g. origin mismatch, etc.)
+ virtual bool CheckResponseHeaders() const;
+
+ DISALLOW_COPY_AND_ASSIGN(WebSocketHandshakeDraft75);
+};
+
+} // namespace net
+
+#endif // NET_WEBSOCKETS_WEBSOCKET_HANDSHAKE_DRAFT75_H_
diff --git a/net/websockets/websocket_handshake_draft75_unittest.cc b/net/websockets/websocket_handshake_draft75_unittest.cc
new file mode 100644
index 0000000..aff75ad
--- /dev/null
+++ b/net/websockets/websocket_handshake_draft75_unittest.cc
@@ -0,0 +1,217 @@
+// 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 <string>
+#include <vector>
+
+#include "base/scoped_ptr.h"
+#include "net/websockets/websocket_handshake_draft75.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+TEST(WebSocketHandshakeDraft75Test, Connect) {
+ const std::string kExpectedClientHandshakeMessage =
+ "GET /demo HTTP/1.1\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Host: example.com\r\n"
+ "Origin: http://example.com\r\n"
+ "WebSocket-Protocol: sample\r\n"
+ "\r\n";
+
+ scoped_ptr<WebSocketHandshakeDraft75> handshake(
+ new WebSocketHandshakeDraft75(GURL("ws://example.com/demo"),
+ "http://example.com",
+ "ws://example.com/demo",
+ "sample"));
+ EXPECT_EQ(WebSocketHandshake::MODE_INCOMPLETE, handshake->mode());
+ EXPECT_EQ(kExpectedClientHandshakeMessage,
+ handshake->CreateClientHandshakeMessage());
+
+ const char kResponse[] = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "WebSocket-Origin: http://example.com\r\n"
+ "WebSocket-Location: ws://example.com/demo\r\n"
+ "WebSocket-Protocol: sample\r\n"
+ "\r\n";
+
+ EXPECT_EQ(WebSocketHandshake::MODE_INCOMPLETE, handshake->mode());
+ // too short
+ EXPECT_EQ(-1, handshake->ReadServerHandshake(kResponse, 16));
+ EXPECT_EQ(WebSocketHandshake::MODE_INCOMPLETE, handshake->mode());
+ // only status line
+ EXPECT_EQ(-1, handshake->ReadServerHandshake(
+ kResponse,
+ WebSocketHandshakeDraft75::kServerHandshakeHeaderLength));
+ EXPECT_EQ(WebSocketHandshake::MODE_NORMAL, handshake->mode());
+ // by upgrade header
+ EXPECT_EQ(-1, handshake->ReadServerHandshake(
+ kResponse,
+ WebSocketHandshakeDraft75::kServerHandshakeHeaderLength +
+ WebSocketHandshakeDraft75::kUpgradeHeaderLength));
+ EXPECT_EQ(WebSocketHandshake::MODE_NORMAL, handshake->mode());
+ // by connection header
+ EXPECT_EQ(-1, handshake->ReadServerHandshake(
+ kResponse,
+ WebSocketHandshakeDraft75::kServerHandshakeHeaderLength +
+ WebSocketHandshakeDraft75::kUpgradeHeaderLength +
+ WebSocketHandshakeDraft75::kConnectionHeaderLength));
+ EXPECT_EQ(WebSocketHandshake::MODE_NORMAL, handshake->mode());
+
+ EXPECT_EQ(-1, handshake->ReadServerHandshake(
+ kResponse, sizeof(kResponse) - 2));
+ EXPECT_EQ(WebSocketHandshake::MODE_NORMAL, handshake->mode());
+
+ int handshake_length = strlen(kResponse);
+ EXPECT_EQ(handshake_length, handshake->ReadServerHandshake(
+ kResponse, sizeof(kResponse) - 1)); // -1 for terminating \0
+ EXPECT_EQ(WebSocketHandshake::MODE_CONNECTED, handshake->mode());
+}
+
+TEST(WebSocketHandshakeDraft75Test, ServerSentData) {
+ const std::string kExpectedClientHandshakeMessage =
+ "GET /demo HTTP/1.1\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Host: example.com\r\n"
+ "Origin: http://example.com\r\n"
+ "WebSocket-Protocol: sample\r\n"
+ "\r\n";
+ scoped_ptr<WebSocketHandshakeDraft75> handshake(
+ new WebSocketHandshakeDraft75(GURL("ws://example.com/demo"),
+ "http://example.com",
+ "ws://example.com/demo",
+ "sample"));
+ EXPECT_EQ(WebSocketHandshake::MODE_INCOMPLETE, handshake->mode());
+ EXPECT_EQ(kExpectedClientHandshakeMessage,
+ handshake->CreateClientHandshakeMessage());
+
+ const char kResponse[] ="HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "WebSocket-Origin: http://example.com\r\n"
+ "WebSocket-Location: ws://example.com/demo\r\n"
+ "WebSocket-Protocol: sample\r\n"
+ "\r\n"
+ "\0Hello\xff";
+
+ int handshake_length = strlen(kResponse);
+ EXPECT_EQ(handshake_length, handshake->ReadServerHandshake(
+ kResponse, sizeof(kResponse) - 1)); // -1 for terminating \0
+ EXPECT_EQ(WebSocketHandshake::MODE_CONNECTED, handshake->mode());
+}
+
+TEST(WebSocketHandshakeDraft75Test, CreateClientHandshakeMessage_Simple) {
+ scoped_ptr<WebSocketHandshakeDraft75> handshake(
+ new WebSocketHandshakeDraft75(GURL("ws://example.com/demo"),
+ "http://example.com",
+ "ws://example.com/demo",
+ "sample"));
+ EXPECT_EQ("GET /demo HTTP/1.1\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Host: example.com\r\n"
+ "Origin: http://example.com\r\n"
+ "WebSocket-Protocol: sample\r\n"
+ "\r\n",
+ handshake->CreateClientHandshakeMessage());
+}
+
+TEST(WebSocketHandshakeDraft75Test, CreateClientHandshakeMessage_PathAndQuery) {
+ scoped_ptr<WebSocketHandshakeDraft75> handshake(
+ new WebSocketHandshakeDraft75(GURL("ws://example.com/Test?q=xxx&p=%20"),
+ "http://example.com",
+ "ws://example.com/demo",
+ "sample"));
+ // Path and query should be preserved as-is.
+ EXPECT_THAT(handshake->CreateClientHandshakeMessage(),
+ testing::HasSubstr("GET /Test?q=xxx&p=%20 HTTP/1.1\r\n"));
+}
+
+TEST(WebSocketHandshakeDraft75Test, CreateClientHandshakeMessage_Host) {
+ scoped_ptr<WebSocketHandshakeDraft75> handshake(
+ new WebSocketHandshakeDraft75(GURL("ws://Example.Com/demo"),
+ "http://Example.Com",
+ "ws://Example.Com/demo",
+ "sample"));
+ // Host should be lowercased
+ EXPECT_THAT(handshake->CreateClientHandshakeMessage(),
+ testing::HasSubstr("Host: example.com\r\n"));
+ EXPECT_THAT(handshake->CreateClientHandshakeMessage(),
+ testing::HasSubstr("Origin: http://example.com\r\n"));
+}
+
+TEST(WebSocketHandshakeDraft75Test, CreateClientHandshakeMessage_TrimPort80) {
+ scoped_ptr<WebSocketHandshakeDraft75> handshake(
+ new WebSocketHandshakeDraft75(GURL("ws://example.com:80/demo"),
+ "http://example.com",
+ "ws://example.com/demo",
+ "sample"));
+ // :80 should be trimmed as it's the default port for ws://.
+ EXPECT_THAT(handshake->CreateClientHandshakeMessage(),
+ testing::HasSubstr("Host: example.com\r\n"));
+}
+
+TEST(WebSocketHandshakeDraft75Test, CreateClientHandshakeMessage_TrimPort443) {
+ scoped_ptr<WebSocketHandshakeDraft75> handshake(
+ new WebSocketHandshakeDraft75(GURL("wss://example.com:443/demo"),
+ "http://example.com",
+ "wss://example.com/demo",
+ "sample"));
+ // :443 should be trimmed as it's the default port for wss://.
+ EXPECT_THAT(handshake->CreateClientHandshakeMessage(),
+ testing::HasSubstr("Host: example.com\r\n"));
+}
+
+TEST(WebSocketHandshakeDraft75Test,
+ CreateClientHandshakeMessage_NonDefaultPortForWs) {
+ scoped_ptr<WebSocketHandshakeDraft75> handshake(
+ new WebSocketHandshakeDraft75(GURL("ws://example.com:8080/demo"),
+ "http://example.com",
+ "wss://example.com/demo",
+ "sample"));
+ // :8080 should be preserved as it's not the default port for ws://.
+ EXPECT_THAT(handshake->CreateClientHandshakeMessage(),
+ testing::HasSubstr("Host: example.com:8080\r\n"));
+}
+
+TEST(WebSocketHandshakeDraft75Test,
+ CreateClientHandshakeMessage_NonDefaultPortForWss) {
+ scoped_ptr<WebSocketHandshakeDraft75> handshake(
+ new WebSocketHandshakeDraft75(GURL("wss://example.com:4443/demo"),
+ "http://example.com",
+ "wss://example.com/demo",
+ "sample"));
+ // :4443 should be preserved as it's not the default port for wss://.
+ EXPECT_THAT(handshake->CreateClientHandshakeMessage(),
+ testing::HasSubstr("Host: example.com:4443\r\n"));
+}
+
+TEST(WebSocketHandshakeDraft75Test, CreateClientHandshakeMessage_WsBut443) {
+ scoped_ptr<WebSocketHandshakeDraft75> handshake(
+ new WebSocketHandshakeDraft75(GURL("ws://example.com:443/demo"),
+ "http://example.com",
+ "ws://example.com/demo",
+ "sample"));
+ // :443 should be preserved as it's not the default port for ws://.
+ EXPECT_THAT(handshake->CreateClientHandshakeMessage(),
+ testing::HasSubstr("Host: example.com:443\r\n"));
+}
+
+TEST(WebSocketHandshakeDraft75Test, CreateClientHandshakeMessage_WssBut80) {
+ scoped_ptr<WebSocketHandshakeDraft75> handshake(
+ new WebSocketHandshakeDraft75(GURL("wss://example.com:80/demo"),
+ "http://example.com",
+ "wss://example.com/demo",
+ "sample"));
+ // :80 should be preserved as it's not the default port for wss://.
+ EXPECT_THAT(handshake->CreateClientHandshakeMessage(),
+ testing::HasSubstr("Host: example.com:80\r\n"));
+}
+
+} // namespace net
diff --git a/net/websockets/websocket_handshake_handler.cc b/net/websockets/websocket_handshake_handler.cc
new file mode 100644
index 0000000..5278151
--- /dev/null
+++ b/net/websockets/websocket_handshake_handler.cc
@@ -0,0 +1,424 @@
+// 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 "net/websockets/websocket_handshake_handler.h"
+
+#include "base/md5.h"
+#include "base/string_piece.h"
+#include "base/string_util.h"
+#include "googleurl/src/gurl.h"
+#include "net/http/http_util.h"
+
+namespace {
+
+const size_t kRequestKey3Size = 8U;
+const size_t kResponseKeySize = 16U;
+
+void ParseHandshakeHeader(
+ const char* handshake_message, int len,
+ std::string* status_line,
+ std::string* headers) {
+ size_t i = base::StringPiece(handshake_message, len).find_first_of("\r\n");
+ if (i == base::StringPiece::npos) {
+ *status_line = std::string(handshake_message, len);
+ *headers = "";
+ return;
+ }
+ // |status_line| includes \r\n.
+ *status_line = std::string(handshake_message, i + 2);
+
+ int header_len = len - (i + 2) - 2;
+ if (header_len > 0) {
+ // |handshake_message| includes tailing \r\n\r\n.
+ // |headers| doesn't include 2nd \r\n.
+ *headers = std::string(handshake_message + i + 2, header_len);
+ } else {
+ *headers = "";
+ }
+}
+
+void FetchHeaders(const std::string& headers,
+ const char* const headers_to_get[],
+ size_t headers_to_get_len,
+ std::vector<std::string>* values) {
+ net::HttpUtil::HeadersIterator iter(headers.begin(), headers.end(), "\r\n");
+ while (iter.GetNext()) {
+ for (size_t i = 0; i < headers_to_get_len; i++) {
+ if (LowerCaseEqualsASCII(iter.name_begin(), iter.name_end(),
+ headers_to_get[i])) {
+ values->push_back(iter.values());
+ }
+ }
+ }
+}
+
+bool GetHeaderName(std::string::const_iterator line_begin,
+ std::string::const_iterator line_end,
+ std::string::const_iterator* name_begin,
+ std::string::const_iterator* name_end) {
+ std::string::const_iterator colon = std::find(line_begin, line_end, ':');
+ if (colon == line_end) {
+ return false;
+ }
+ *name_begin = line_begin;
+ *name_end = colon;
+ if (*name_begin == *name_end || net::HttpUtil::IsLWS(**name_begin))
+ return false;
+ net::HttpUtil::TrimLWS(name_begin, name_end);
+ return true;
+}
+
+// Similar to HttpUtil::StripHeaders, but it preserves malformed headers, that
+// is, lines that are not formatted as "<name>: <value>\r\n".
+std::string FilterHeaders(
+ const std::string& headers,
+ const char* const headers_to_remove[],
+ size_t headers_to_remove_len) {
+ std::string filtered_headers;
+
+ StringTokenizer lines(headers.begin(), headers.end(), "\r\n");
+ while (lines.GetNext()) {
+ std::string::const_iterator line_begin = lines.token_begin();
+ std::string::const_iterator line_end = lines.token_end();
+ std::string::const_iterator name_begin;
+ std::string::const_iterator name_end;
+ bool should_remove = false;
+ if (GetHeaderName(line_begin, line_end, &name_begin, &name_end)) {
+ for (size_t i = 0; i < headers_to_remove_len; ++i) {
+ if (LowerCaseEqualsASCII(name_begin, name_end, headers_to_remove[i])) {
+ should_remove = true;
+ break;
+ }
+ }
+ }
+ if (!should_remove) {
+ filtered_headers.append(line_begin, line_end);
+ filtered_headers.append("\r\n");
+ }
+ }
+ return filtered_headers;
+}
+
+// Gets a key number from |key| and appends the number to |challenge|.
+// The key number (/part_N/) is extracted as step 4.-8. in
+// 5.2. Sending the server's opening handshake of
+// http://www.ietf.org/id/draft-ietf-hybi-thewebsocketprotocol-00.txt
+void GetKeyNumber(const std::string& key, std::string* challenge) {
+ uint32 key_number = 0;
+ uint32 spaces = 0;
+ for (size_t i = 0; i < key.size(); ++i) {
+ if (isdigit(key[i])) {
+ // key_number should not overflow. (it comes from
+ // WebCore/websockets/WebSocketHandshake.cpp).
+ key_number = key_number * 10 + key[i] - '0';
+ } else if (key[i] == ' ') {
+ ++spaces;
+ }
+ }
+ // spaces should not be zero in valid handshake request.
+ if (spaces == 0)
+ return;
+ key_number /= spaces;
+
+ char part[4];
+ for (int i = 0; i < 4; i++) {
+ part[3 - i] = key_number & 0xFF;
+ key_number >>= 8;
+ }
+ challenge->append(part, 4);
+}
+
+} // anonymous namespace
+
+namespace net {
+
+WebSocketHandshakeRequestHandler::WebSocketHandshakeRequestHandler()
+ : original_length_(0),
+ raw_length_(0) {}
+
+bool WebSocketHandshakeRequestHandler::ParseRequest(
+ const char* data, int length) {
+ DCHECK_GT(length, 0);
+ std::string input(data, length);
+ int input_header_length =
+ HttpUtil::LocateEndOfHeaders(input.data(), input.size(), 0);
+ if (input_header_length <= 0 ||
+ input_header_length + kRequestKey3Size > input.size())
+ return false;
+
+ ParseHandshakeHeader(input.data(),
+ input_header_length,
+ &status_line_,
+ &headers_);
+
+ // draft-hixie-thewebsocketprotocol-76 or later will send /key3/
+ // after handshake request header.
+ // Assumes WebKit doesn't send any data after handshake request message
+ // until handshake is finished.
+ // Thus, |key3_| is part of handshake message, and not in part
+ // of WebSocket frame stream.
+ DCHECK_EQ(kRequestKey3Size,
+ input.size() -
+ input_header_length);
+ key3_ = std::string(input.data() + input_header_length,
+ input.size() - input_header_length);
+ original_length_ = input.size();
+ return true;
+}
+
+size_t WebSocketHandshakeRequestHandler::original_length() const {
+ return original_length_;
+}
+
+void WebSocketHandshakeRequestHandler::AppendHeaderIfMissing(
+ const std::string& name, const std::string& value) {
+ DCHECK(headers_.size() > 0);
+ HttpUtil::AppendHeaderIfMissing(name.c_str(), value, &headers_);
+}
+
+void WebSocketHandshakeRequestHandler::RemoveHeaders(
+ const char* const headers_to_remove[],
+ size_t headers_to_remove_len) {
+ DCHECK(headers_.size() > 0);
+ headers_ = FilterHeaders(
+ headers_, headers_to_remove, headers_to_remove_len);
+}
+
+HttpRequestInfo WebSocketHandshakeRequestHandler::GetRequestInfo(
+ const GURL& url, std::string* challenge) {
+ HttpRequestInfo request_info;
+ request_info.url = url;
+ base::StringPiece method = status_line_.data();
+ size_t method_end = base::StringPiece(
+ status_line_.data(), status_line_.size()).find_first_of(" ");
+ if (method_end != base::StringPiece::npos)
+ request_info.method = std::string(status_line_.data(), method_end);
+
+ request_info.extra_headers.Clear();
+ request_info.extra_headers.AddHeadersFromString(headers_);
+
+ request_info.extra_headers.RemoveHeader("Upgrade");
+ request_info.extra_headers.RemoveHeader("Connection");
+
+ challenge->clear();
+ std::string key;
+ request_info.extra_headers.GetHeader("Sec-WebSocket-Key1", &key);
+ request_info.extra_headers.RemoveHeader("Sec-WebSocket-Key1");
+ GetKeyNumber(key, challenge);
+
+ request_info.extra_headers.GetHeader("Sec-WebSocket-Key2", &key);
+ request_info.extra_headers.RemoveHeader("Sec-WebSocket-Key2");
+ GetKeyNumber(key, challenge);
+
+ challenge->append(key3_);
+
+ return request_info;
+}
+
+bool WebSocketHandshakeRequestHandler::GetRequestHeaderBlock(
+ const GURL& url, spdy::SpdyHeaderBlock* headers, std::string* challenge) {
+ // We don't set "method" and "version". These are fixed value in WebSocket
+ // protocol.
+ (*headers)["url"] = url.spec();
+
+ std::string key1;
+ std::string key2;
+ HttpUtil::HeadersIterator iter(headers_.begin(), headers_.end(), "\r\n");
+ while (iter.GetNext()) {
+ if (LowerCaseEqualsASCII(iter.name_begin(), iter.name_end(),
+ "connection")) {
+ // Ignore "Connection" header.
+ continue;
+ } else if (LowerCaseEqualsASCII(iter.name_begin(), iter.name_end(),
+ "upgrade")) {
+ // Ignore "Upgrade" header.
+ continue;
+ } else if (LowerCaseEqualsASCII(iter.name_begin(), iter.name_end(),
+ "sec-websocket-key1")) {
+ // Use only for generating challenge.
+ key1 = iter.values();
+ continue;
+ } else if (LowerCaseEqualsASCII(iter.name_begin(), iter.name_end(),
+ "sec-websocket-key2")) {
+ // Use only for generating challenge.
+ key2 = iter.values();
+ continue;
+ }
+ // Others should be sent out to |headers|.
+ std::string name = StringToLowerASCII(iter.name());
+ spdy::SpdyHeaderBlock::iterator found = headers->find(name);
+ if (found == headers->end()) {
+ (*headers)[name] = iter.values();
+ } else {
+ // For now, websocket doesn't use multiple headers, but follows to http.
+ found->second.append(1, '\0'); // +=() doesn't append 0's
+ found->second.append(iter.values());
+ }
+ }
+
+ challenge->clear();
+ GetKeyNumber(key1, challenge);
+ GetKeyNumber(key2, challenge);
+ challenge->append(key3_);
+
+ return true;
+}
+
+std::string WebSocketHandshakeRequestHandler::GetRawRequest() {
+ DCHECK(status_line_.size() > 0);
+ DCHECK(headers_.size() > 0);
+ DCHECK_EQ(kRequestKey3Size, key3_.size());
+ std::string raw_request = status_line_ + headers_ + "\r\n" + key3_;
+ raw_length_ = raw_request.size();
+ return raw_request;
+}
+
+size_t WebSocketHandshakeRequestHandler::raw_length() const {
+ DCHECK_GT(raw_length_, 0);
+ return raw_length_;
+}
+
+WebSocketHandshakeResponseHandler::WebSocketHandshakeResponseHandler()
+ : original_header_length_(0) {
+}
+
+size_t WebSocketHandshakeResponseHandler::ParseRawResponse(
+ const char* data, int length) {
+ DCHECK_GT(length, 0);
+ if (HasResponse()) {
+ DCHECK(status_line_.size() > 0);
+ DCHECK(headers_.size() > 0);
+ DCHECK_EQ(kResponseKeySize, key_.size());
+ return 0;
+ }
+
+ size_t old_original_length = original_.size();
+
+ original_.append(data, length);
+ // TODO(ukai): fail fast when response gives wrong status code.
+ original_header_length_ = HttpUtil::LocateEndOfHeaders(
+ original_.data(), original_.size(), 0);
+ if (!HasResponse())
+ return length;
+
+ ParseHandshakeHeader(original_.data(),
+ original_header_length_,
+ &status_line_,
+ &headers_);
+ int header_size = status_line_.size() + headers_.size();
+ DCHECK_GE(original_header_length_, header_size);
+ header_separator_ = std::string(original_.data() + header_size,
+ original_header_length_ - header_size);
+ key_ = std::string(original_.data() + original_header_length_,
+ kResponseKeySize);
+
+ return original_header_length_ + kResponseKeySize - old_original_length;
+}
+
+bool WebSocketHandshakeResponseHandler::HasResponse() const {
+ return original_header_length_ > 0 &&
+ original_header_length_ + kResponseKeySize <= original_.size();
+}
+
+bool WebSocketHandshakeResponseHandler::ParseResponseInfo(
+ const HttpResponseInfo& response_info,
+ const std::string& challenge) {
+ if (!response_info.headers.get())
+ return false;
+
+ std::string response_message;
+ response_message = response_info.headers->GetStatusLine();
+ response_message += "\r\n";
+ response_message += "Upgrade: WebSocket\r\n";
+ response_message += "Connection: Upgrade\r\n";
+ void* iter = NULL;
+ std::string name;
+ std::string value;
+ while (response_info.headers->EnumerateHeaderLines(&iter, &name, &value)) {
+ response_message += name + ": " + value + "\r\n";
+ }
+ response_message += "\r\n";
+
+ MD5Digest digest;
+ MD5Sum(challenge.data(), challenge.size(), &digest);
+
+ const char* digest_data = reinterpret_cast<char*>(digest.a);
+ response_message.append(digest_data, sizeof(digest.a));
+
+ return ParseRawResponse(response_message.data(),
+ response_message.size()) == response_message.size();
+}
+
+bool WebSocketHandshakeResponseHandler::ParseResponseHeaderBlock(
+ const spdy::SpdyHeaderBlock& headers,
+ const std::string& challenge) {
+ std::string response_message;
+ response_message = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n";
+ response_message += "Upgrade: WebSocket\r\n";
+ response_message += "Connection: Upgrade\r\n";
+ for (spdy::SpdyHeaderBlock::const_iterator iter = headers.begin();
+ iter != headers.end();
+ ++iter) {
+ // For each value, if the server sends a NUL-separated list of values,
+ // we separate that back out into individual headers for each value
+ // in the list.
+ const std::string& value = iter->second;
+ size_t start = 0;
+ size_t end = 0;
+ do {
+ end = value.find('\0', start);
+ std::string tval;
+ if (end != std::string::npos)
+ tval = value.substr(start, (end - start));
+ else
+ tval = value.substr(start);
+ response_message += iter->first + ": " + tval + "\r\n";
+ start = end + 1;
+ } while (end != std::string::npos);
+ }
+ response_message += "\r\n";
+
+ MD5Digest digest;
+ MD5Sum(challenge.data(), challenge.size(), &digest);
+
+ const char* digest_data = reinterpret_cast<char*>(digest.a);
+ response_message.append(digest_data, sizeof(digest.a));
+
+ return ParseRawResponse(response_message.data(),
+ response_message.size()) == response_message.size();
+}
+
+void WebSocketHandshakeResponseHandler::GetHeaders(
+ const char* const headers_to_get[],
+ size_t headers_to_get_len,
+ std::vector<std::string>* values) {
+ DCHECK(HasResponse());
+ DCHECK(status_line_.size() > 0);
+ DCHECK(headers_.size() > 0);
+ DCHECK_EQ(kResponseKeySize, key_.size());
+
+ FetchHeaders(headers_, headers_to_get, headers_to_get_len, values);
+}
+
+void WebSocketHandshakeResponseHandler::RemoveHeaders(
+ const char* const headers_to_remove[],
+ size_t headers_to_remove_len) {
+ DCHECK(HasResponse());
+ DCHECK(status_line_.size() > 0);
+ DCHECK(headers_.size() > 0);
+ DCHECK_EQ(kResponseKeySize, key_.size());
+
+ headers_ = FilterHeaders(headers_, headers_to_remove, headers_to_remove_len);
+}
+
+std::string WebSocketHandshakeResponseHandler::GetResponse() {
+ DCHECK(HasResponse());
+ DCHECK(status_line_.size() > 0);
+ // headers_ might be empty for wrong response from server.
+ DCHECK_EQ(kResponseKeySize, key_.size());
+
+ return status_line_ + headers_ + header_separator_ + key_;
+}
+
+} // namespace net
diff --git a/net/websockets/websocket_handshake_handler.h b/net/websockets/websocket_handshake_handler.h
new file mode 100644
index 0000000..cf5700b
--- /dev/null
+++ b/net/websockets/websocket_handshake_handler.h
@@ -0,0 +1,116 @@
+// 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.
+//
+// WebSocketHandshake*Handler handles WebSocket handshake request message
+// from WebKit renderer process, and WebSocket handshake response message
+// from WebSocket server.
+// It modifies messages for the following reason:
+// - We don't trust WebKit renderer process, so we'll not expose HttpOnly
+// cookies to the renderer process, so handles HttpOnly cookies in
+// browser process.
+
+#ifndef NET_WEBSOCKETS_WEBSOCKET_HANDSHAKE_HANDLER_H_
+#define NET_WEBSOCKETS_WEBSOCKET_HANDSHAKE_HANDLER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/ref_counted.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_info.h"
+#include "net/spdy/spdy_framer.h"
+
+namespace net {
+
+class WebSocketHandshakeRequestHandler {
+ public:
+ WebSocketHandshakeRequestHandler();
+ ~WebSocketHandshakeRequestHandler() {}
+
+ // Parses WebSocket handshake request from renderer process.
+ // It assumes a WebSocket handshake request message is given at once, and
+ // no other data is added to the request message.
+ bool ParseRequest(const char* data, int length);
+
+ size_t original_length() const;
+
+ // Appends the header value pair for |name| and |value|, if |name| doesn't
+ // exist.
+ void AppendHeaderIfMissing(const std::string& name,
+ const std::string& value);
+ // Removes the headers that matches (case insensitive).
+ void RemoveHeaders(const char* const headers_to_remove[],
+ size_t headers_to_remove_len);
+
+ // Gets request info to open WebSocket connection.
+ // Also, fill challange data in |challenge|.
+ HttpRequestInfo GetRequestInfo(const GURL& url, std::string* challenge);
+ // Gets request as SpdyHeaderBlock.
+ // Also, fill challenge data in |challenge|.
+ bool GetRequestHeaderBlock(const GURL& url,
+ spdy::SpdyHeaderBlock* headers,
+ std::string* challenge);
+ // Gets WebSocket handshake raw request message to open WebSocket
+ // connection.
+ std::string GetRawRequest();
+ // Calling raw_length is valid only after GetRawRquest() call.
+ size_t raw_length() const;
+
+ private:
+ std::string status_line_;
+ std::string headers_;
+ std::string key3_;
+ int original_length_;
+ int raw_length_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebSocketHandshakeRequestHandler);
+};
+
+class WebSocketHandshakeResponseHandler {
+ public:
+ WebSocketHandshakeResponseHandler();
+ ~WebSocketHandshakeResponseHandler() {}
+
+ // Parses WebSocket handshake response from WebSocket server.
+ // Returns number of bytes in |data| used for WebSocket handshake response
+ // message, including response key. If it already got whole WebSocket
+ // handshake response message, returns zero. In other words,
+ // [data + returned value, data + length) will be WebSocket frame data
+ // after handshake response message.
+ // TODO(ukai): fail fast when response gives wrong status code.
+ size_t ParseRawResponse(const char* data, int length);
+ // Returns true if it already parses full handshake response message.
+ bool HasResponse() const;
+ // Parses WebSocket handshake response info given as HttpResponseInfo.
+ bool ParseResponseInfo(const HttpResponseInfo& response_info,
+ const std::string& challenge);
+ // Parses WebSocket handshake response as SpdyHeaderBlock.
+ bool ParseResponseHeaderBlock(const spdy::SpdyHeaderBlock& headers,
+ const std::string& challenge);
+
+ // Gets the headers value.
+ void GetHeaders(const char* const headers_to_get[],
+ size_t headers_to_get_len,
+ std::vector<std::string>* values);
+ // Removes the headers that matches (case insensitive).
+ void RemoveHeaders(const char* const headers_to_remove[],
+ size_t headers_to_remove_len);
+
+ // Gets WebSocket handshake response message sent to renderer process.
+ std::string GetResponse();
+
+ private:
+ std::string original_;
+ int original_header_length_;
+ std::string status_line_;
+ std::string headers_;
+ std::string header_separator_;
+ std::string key_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebSocketHandshakeResponseHandler);
+};
+
+} // namespace net
+
+#endif // NET_WEBSOCKETS_WEBSOCKET_HANDSHAKE_HANDLER_H_
diff --git a/net/websockets/websocket_handshake_handler_unittest.cc b/net/websockets/websocket_handshake_handler_unittest.cc
new file mode 100644
index 0000000..65e0712
--- /dev/null
+++ b/net/websockets/websocket_handshake_handler_unittest.cc
@@ -0,0 +1,404 @@
+// 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 <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/string_util.h"
+#include "googleurl/src/gurl.h"
+#include "net/http/http_util.h"
+#include "net/websockets/websocket_handshake_handler.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace {
+
+const char* const kCookieHeaders[] = {
+ "cookie", "cookie2"
+};
+
+const char* const kSetCookieHeaders[] = {
+ "set-cookie", "set-cookie2"
+};
+
+}
+
+namespace net {
+
+TEST(WebSocketHandshakeRequestHandlerTest, SimpleRequest) {
+ WebSocketHandshakeRequestHandler handler;
+
+ static const char* kHandshakeRequestMessage =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n"
+ "Origin: http://example.com\r\n"
+ "\r\n"
+ "^n:ds[4U";
+
+ EXPECT_TRUE(handler.ParseRequest(kHandshakeRequestMessage,
+ strlen(kHandshakeRequestMessage)));
+
+ handler.RemoveHeaders(kCookieHeaders, arraysize(kCookieHeaders));
+
+ EXPECT_EQ(kHandshakeRequestMessage, handler.GetRawRequest());
+}
+
+TEST(WebSocketHandshakeRequestHandlerTest, ReplaceRequestCookies) {
+ WebSocketHandshakeRequestHandler handler;
+
+ static const char* kHandshakeRequestMessage =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n"
+ "Origin: http://example.com\r\n"
+ "Cookie: WK-websocket-test=1\r\n"
+ "\r\n"
+ "^n:ds[4U";
+
+ EXPECT_TRUE(handler.ParseRequest(kHandshakeRequestMessage,
+ strlen(kHandshakeRequestMessage)));
+
+ handler.RemoveHeaders(kCookieHeaders, arraysize(kCookieHeaders));
+
+ handler.AppendHeaderIfMissing("Cookie",
+ "WK-websocket-test=1; "
+ "WK-websocket-test-httponly=1");
+
+ static const char* kHandshakeRequestExpectedMessage =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n"
+ "Origin: http://example.com\r\n"
+ "Cookie: WK-websocket-test=1; WK-websocket-test-httponly=1\r\n"
+ "\r\n"
+ "^n:ds[4U";
+
+ EXPECT_EQ(kHandshakeRequestExpectedMessage, handler.GetRawRequest());
+}
+
+TEST(WebSocketHandshakeResponseHandlerTest, SimpleResponse) {
+ WebSocketHandshakeResponseHandler handler;
+
+ static const char* kHandshakeResponseMessage =
+ "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Location: ws://example.com/demo\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "\r\n"
+ "8jKS'y:G*Co,Wxa-";
+
+ EXPECT_EQ(strlen(kHandshakeResponseMessage),
+ handler.ParseRawResponse(kHandshakeResponseMessage,
+ strlen(kHandshakeResponseMessage)));
+ EXPECT_TRUE(handler.HasResponse());
+
+ handler.RemoveHeaders(kCookieHeaders, arraysize(kCookieHeaders));
+
+ EXPECT_EQ(kHandshakeResponseMessage, handler.GetResponse());
+}
+
+TEST(WebSocketHandshakeResponseHandlerTest, ReplaceResponseCookies) {
+ WebSocketHandshakeResponseHandler handler;
+
+ static const char* kHandshakeResponseMessage =
+ "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Location: ws://example.com/demo\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Set-Cookie: WK-websocket-test-1\r\n"
+ "Set-Cookie: WK-websocket-test-httponly=1; HttpOnly\r\n"
+ "\r\n"
+ "8jKS'y:G*Co,Wxa-";
+
+ EXPECT_EQ(strlen(kHandshakeResponseMessage),
+ handler.ParseRawResponse(kHandshakeResponseMessage,
+ strlen(kHandshakeResponseMessage)));
+ EXPECT_TRUE(handler.HasResponse());
+ std::vector<std::string> cookies;
+ handler.GetHeaders(kSetCookieHeaders, arraysize(kSetCookieHeaders), &cookies);
+ ASSERT_EQ(2U, cookies.size());
+ EXPECT_EQ("WK-websocket-test-1", cookies[0]);
+ EXPECT_EQ("WK-websocket-test-httponly=1; HttpOnly", cookies[1]);
+ handler.RemoveHeaders(kSetCookieHeaders, arraysize(kSetCookieHeaders));
+
+ static const char* kHandshakeResponseExpectedMessage =
+ "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Location: ws://example.com/demo\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "\r\n"
+ "8jKS'y:G*Co,Wxa-";
+
+ EXPECT_EQ(kHandshakeResponseExpectedMessage, handler.GetResponse());
+}
+
+TEST(WebSocketHandshakeResponseHandlerTest, BadResponse) {
+ WebSocketHandshakeResponseHandler handler;
+
+ static const char* kBadMessage = "\n\n\r\net-Location: w";
+ EXPECT_EQ(strlen(kBadMessage),
+ handler.ParseRawResponse(kBadMessage, strlen(kBadMessage)));
+ EXPECT_TRUE(handler.HasResponse());
+ EXPECT_EQ(kBadMessage, handler.GetResponse());
+}
+
+TEST(WebSocketHandshakeResponseHandlerTest, BadResponse2) {
+ WebSocketHandshakeResponseHandler handler;
+
+ static const char* kBadMessage = "\n\r\n\r\net-Location: w";
+ EXPECT_EQ(strlen(kBadMessage),
+ handler.ParseRawResponse(kBadMessage, strlen(kBadMessage)));
+ EXPECT_TRUE(handler.HasResponse());
+ EXPECT_EQ(kBadMessage, handler.GetResponse());
+}
+
+TEST(WebSocketHandshakeHandlerTest, HttpRequestResponse) {
+ WebSocketHandshakeRequestHandler request_handler;
+
+ static const char* kHandshakeRequestMessage =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n"
+ "Origin: http://example.com\r\n"
+ "\r\n"
+ "^n:ds[4U";
+
+ EXPECT_TRUE(request_handler.ParseRequest(kHandshakeRequestMessage,
+ strlen(kHandshakeRequestMessage)));
+
+ GURL url("ws://example.com/demo");
+ std::string challenge;
+ const HttpRequestInfo& request_info =
+ request_handler.GetRequestInfo(url, &challenge);
+
+ EXPECT_EQ(url, request_info.url);
+ EXPECT_EQ("GET", request_info.method);
+ EXPECT_FALSE(request_info.extra_headers.HasHeader("Upgrade"));
+ EXPECT_FALSE(request_info.extra_headers.HasHeader("Connection"));
+ EXPECT_FALSE(request_info.extra_headers.HasHeader("Sec-WebSocket-Key1"));
+ EXPECT_FALSE(request_info.extra_headers.HasHeader("Sec-WebSocket-Key2"));
+ std::string value;
+ EXPECT_TRUE(request_info.extra_headers.GetHeader("Host", &value));
+ EXPECT_EQ("example.com", value);
+ EXPECT_TRUE(request_info.extra_headers.GetHeader("Origin", &value));
+ EXPECT_EQ("http://example.com", value);
+ EXPECT_TRUE(request_info.extra_headers.GetHeader("Sec-WebSocket-Protocol",
+ &value));
+ EXPECT_EQ("sample", value);
+
+ const char expected_challenge[] = "\x31\x6e\x41\x13\x0f\x7e\xd6\x3c^n:ds[4U";
+
+ EXPECT_EQ(expected_challenge, challenge);
+
+ static const char* kHandshakeResponseHeader =
+ "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Location: ws://example.com/demo\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n";
+
+ std::string raw_headers =
+ HttpUtil::AssembleRawHeaders(kHandshakeResponseHeader,
+ strlen(kHandshakeResponseHeader));
+ HttpResponseInfo response_info;
+ response_info.headers = new HttpResponseHeaders(raw_headers);
+
+ EXPECT_TRUE(StartsWithASCII(response_info.headers->GetStatusLine(),
+ "HTTP/1.1 101 ", false));
+ EXPECT_FALSE(response_info.headers->HasHeader("Upgrade"));
+ EXPECT_FALSE(response_info.headers->HasHeader("Connection"));
+ EXPECT_TRUE(response_info.headers->HasHeaderValue("Sec-WebSocket-Origin",
+ "http://example.com"));
+ EXPECT_TRUE(response_info.headers->HasHeaderValue("Sec-WebSocket-Location",
+ "ws://example.com/demo"));
+ EXPECT_TRUE(response_info.headers->HasHeaderValue("Sec-WebSocket-Protocol",
+ "sample"));
+
+ WebSocketHandshakeResponseHandler response_handler;
+ EXPECT_TRUE(response_handler.ParseResponseInfo(response_info, challenge));
+ EXPECT_TRUE(response_handler.HasResponse());
+
+ static const char* kHandshakeResponseExpectedMessage =
+ "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Location: ws://example.com/demo\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "\r\n"
+ "8jKS'y:G*Co,Wxa-";
+
+ EXPECT_EQ(kHandshakeResponseExpectedMessage, response_handler.GetResponse());
+}
+
+TEST(WebSocketHandshakeHandlerTest, SpdyRequestResponse) {
+ WebSocketHandshakeRequestHandler request_handler;
+
+ static const char* kHandshakeRequestMessage =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Upgrade: WebSocket\r\n"
+ "X-bogus-header: X\r\n"
+ "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n"
+ "Origin: http://example.com\r\n"
+ "X-bogus-header: Y\r\n"
+ "\r\n"
+ "^n:ds[4U";
+
+ EXPECT_TRUE(request_handler.ParseRequest(kHandshakeRequestMessage,
+ strlen(kHandshakeRequestMessage)));
+
+ GURL url("ws://example.com/demo");
+ std::string challenge;
+ spdy::SpdyHeaderBlock headers;
+ ASSERT_TRUE(request_handler.GetRequestHeaderBlock(url, &headers, &challenge));
+
+ EXPECT_EQ(url.spec(), headers["url"]);
+ EXPECT_TRUE(headers.find("upgrade") == headers.end());
+ EXPECT_TRUE(headers.find("Upgrade") == headers.end());
+ EXPECT_TRUE(headers.find("connection") == headers.end());
+ EXPECT_TRUE(headers.find("Connection") == headers.end());
+ EXPECT_TRUE(headers.find("Sec-WebSocket-Key1") == headers.end());
+ EXPECT_TRUE(headers.find("sec-websocket-key1") == headers.end());
+ EXPECT_TRUE(headers.find("Sec-WebSocket-Key2") == headers.end());
+ EXPECT_TRUE(headers.find("sec-websocket-key2") == headers.end());
+ EXPECT_EQ("example.com", headers["host"]);
+ EXPECT_EQ("http://example.com", headers["origin"]);
+ EXPECT_EQ("sample", headers["sec-websocket-protocol"]);
+ const char bogus_header[] = "X\0Y";
+ std::string bogus_header_str(bogus_header, sizeof(bogus_header) - 1);
+ EXPECT_EQ(bogus_header_str, headers["x-bogus-header"]);
+
+ const char expected_challenge[] = "\x31\x6e\x41\x13\x0f\x7e\xd6\x3c^n:ds[4U";
+
+ EXPECT_EQ(expected_challenge, challenge);
+
+ headers.clear();
+
+ headers["sec-websocket-origin"] = "http://example.com";
+ headers["sec-websocket-location"] = "ws://example.com/demo";
+ headers["sec-websocket-protocol"] = "sample";
+
+ WebSocketHandshakeResponseHandler response_handler;
+ EXPECT_TRUE(response_handler.ParseResponseHeaderBlock(headers, challenge));
+ EXPECT_TRUE(response_handler.HasResponse());
+
+ // Note that order of sec-websocket-* is sensitive with hash_map order.
+ static const char* kHandshakeResponseExpectedMessage =
+ "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "sec-websocket-location: ws://example.com/demo\r\n"
+ "sec-websocket-origin: http://example.com\r\n"
+ "sec-websocket-protocol: sample\r\n"
+ "\r\n"
+ "8jKS'y:G*Co,Wxa-";
+
+ EXPECT_EQ(kHandshakeResponseExpectedMessage, response_handler.GetResponse());
+}
+
+
+TEST(WebSocketHandshakeHandlerTest, SpdyRequestResponseWithCookies) {
+ WebSocketHandshakeRequestHandler request_handler;
+
+ // Note that websocket won't use multiple headers in request now.
+ static const char* kHandshakeRequestMessage =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n"
+ "Origin: http://example.com\r\n"
+ "Cookie: WK-websocket-test=1; WK-websocket-test-httponly=1\r\n"
+ "\r\n"
+ "^n:ds[4U";
+
+ EXPECT_TRUE(request_handler.ParseRequest(kHandshakeRequestMessage,
+ strlen(kHandshakeRequestMessage)));
+
+ GURL url("ws://example.com/demo");
+ std::string challenge;
+ spdy::SpdyHeaderBlock headers;
+ ASSERT_TRUE(request_handler.GetRequestHeaderBlock(url, &headers, &challenge));
+
+ EXPECT_EQ(url.spec(), headers["url"]);
+ EXPECT_TRUE(headers.find("upgrade") == headers.end());
+ EXPECT_TRUE(headers.find("Upgrade") == headers.end());
+ EXPECT_TRUE(headers.find("connection") == headers.end());
+ EXPECT_TRUE(headers.find("Connection") == headers.end());
+ EXPECT_TRUE(headers.find("Sec-WebSocket-Key1") == headers.end());
+ EXPECT_TRUE(headers.find("sec-websocket-key1") == headers.end());
+ EXPECT_TRUE(headers.find("Sec-WebSocket-Key2") == headers.end());
+ EXPECT_TRUE(headers.find("sec-websocket-key2") == headers.end());
+ EXPECT_EQ("example.com", headers["host"]);
+ EXPECT_EQ("http://example.com", headers["origin"]);
+ EXPECT_EQ("sample", headers["sec-websocket-protocol"]);
+ EXPECT_EQ("WK-websocket-test=1; WK-websocket-test-httponly=1",
+ headers["cookie"]);
+
+ const char expected_challenge[] = "\x31\x6e\x41\x13\x0f\x7e\xd6\x3c^n:ds[4U";
+
+ EXPECT_EQ(expected_challenge, challenge);
+
+ headers.clear();
+
+ headers["sec-websocket-origin"] = "http://example.com";
+ headers["sec-websocket-location"] = "ws://example.com/demo";
+ headers["sec-websocket-protocol"] = "sample";
+ std::string cookie = "WK-websocket-test=1";
+ cookie.append(1, '\0');
+ cookie += "WK-websocket-test-httponly=1; HttpOnly";
+ headers["set-cookie"] = cookie;
+
+ WebSocketHandshakeResponseHandler response_handler;
+ EXPECT_TRUE(response_handler.ParseResponseHeaderBlock(headers, challenge));
+ EXPECT_TRUE(response_handler.HasResponse());
+
+ // Note that order of sec-websocket-* is sensitive with hash_map order.
+ static const char* kHandshakeResponseExpectedMessage =
+ "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "sec-websocket-location: ws://example.com/demo\r\n"
+ "sec-websocket-origin: http://example.com\r\n"
+ "sec-websocket-protocol: sample\r\n"
+ "set-cookie: WK-websocket-test=1\r\n"
+ "set-cookie: WK-websocket-test-httponly=1; HttpOnly\r\n"
+ "\r\n"
+ "8jKS'y:G*Co,Wxa-";
+
+ EXPECT_EQ(kHandshakeResponseExpectedMessage, response_handler.GetResponse());
+}
+
+} // namespace net
diff --git a/net/websockets/websocket_handshake_unittest.cc b/net/websockets/websocket_handshake_unittest.cc
new file mode 100644
index 0000000..f688554
--- /dev/null
+++ b/net/websockets/websocket_handshake_unittest.cc
@@ -0,0 +1,328 @@
+// 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 <string>
+#include <vector>
+
+#include "base/scoped_ptr.h"
+#include "base/string_util.h"
+#include "net/websockets/websocket_handshake.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+class WebSocketHandshakeTest : public testing::Test {
+ public:
+ static void SetUpParameter(WebSocketHandshake* handshake,
+ uint32 number_1, uint32 number_2,
+ const std::string& key_1, const std::string& key_2,
+ const std::string& key_3) {
+ WebSocketHandshake::Parameter* parameter =
+ new WebSocketHandshake::Parameter;
+ parameter->number_1_ = number_1;
+ parameter->number_2_ = number_2;
+ parameter->key_1_ = key_1;
+ parameter->key_2_ = key_2;
+ parameter->key_3_ = key_3;
+ handshake->parameter_.reset(parameter);
+ }
+
+ static void ExpectHeaderEquals(const std::string& expected,
+ const std::string& actual) {
+ std::vector<std::string> expected_lines;
+ Tokenize(expected, "\r\n", &expected_lines);
+ std::vector<std::string> actual_lines;
+ Tokenize(actual, "\r\n", &actual_lines);
+ // Request lines.
+ EXPECT_EQ(expected_lines[0], actual_lines[0]);
+
+ std::vector<std::string> expected_headers;
+ for (size_t i = 1; i < expected_lines.size(); i++) {
+ // Finish at first CRLF CRLF. Note that /key_3/ might include CRLF.
+ if (expected_lines[i] == "")
+ break;
+ expected_headers.push_back(expected_lines[i]);
+ }
+ sort(expected_headers.begin(), expected_headers.end());
+
+ std::vector<std::string> actual_headers;
+ for (size_t i = 1; i < actual_lines.size(); i++) {
+ // Finish at first CRLF CRLF. Note that /key_3/ might include CRLF.
+ if (actual_lines[i] == "")
+ break;
+ actual_headers.push_back(actual_lines[i]);
+ }
+ sort(actual_headers.begin(), actual_headers.end());
+
+ EXPECT_EQ(expected_headers.size(), actual_headers.size())
+ << "expected:" << expected
+ << "\nactual:" << actual;
+ for (size_t i = 0; i < expected_headers.size(); i++) {
+ EXPECT_EQ(expected_headers[i], actual_headers[i]);
+ }
+ }
+
+ static void ExpectHandshakeMessageEquals(const std::string& expected,
+ const std::string& actual) {
+ // Headers.
+ ExpectHeaderEquals(expected, actual);
+ // Compare tailing \r\n\r\n<key3> (4 + 8 bytes).
+ ASSERT_GT(expected.size(), 12U);
+ const char* expected_key3 = expected.data() + expected.size() - 12;
+ EXPECT_GT(actual.size(), 12U);
+ if (actual.size() <= 12U)
+ return;
+ const char* actual_key3 = actual.data() + actual.size() - 12;
+ EXPECT_TRUE(memcmp(expected_key3, actual_key3, 12) == 0)
+ << "expected_key3:" << DumpKey(expected_key3, 12)
+ << ", actual_key3:" << DumpKey(actual_key3, 12);
+ }
+
+ static std::string DumpKey(const char* buf, int len) {
+ std::string s;
+ for (int i = 0; i < len; i++) {
+ if (isprint(buf[i]))
+ s += StringPrintf("%c", buf[i]);
+ else
+ s += StringPrintf("\\x%02x", buf[i]);
+ }
+ return s;
+ }
+
+ static std::string GetResourceName(WebSocketHandshake* handshake) {
+ return handshake->GetResourceName();
+ }
+ static std::string GetHostFieldValue(WebSocketHandshake* handshake) {
+ return handshake->GetHostFieldValue();
+ }
+ static std::string GetOriginFieldValue(WebSocketHandshake* handshake) {
+ return handshake->GetOriginFieldValue();
+ }
+};
+
+
+TEST_F(WebSocketHandshakeTest, Connect) {
+ const std::string kExpectedClientHandshakeMessage =
+ "GET /demo HTTP/1.1\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Host: example.com\r\n"
+ "Origin: http://example.com\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Sec-WebSocket-Key1: 388P O503D&ul7 {K%gX( %7 15\r\n"
+ "Sec-WebSocket-Key2: 1 N ?|k UT0or 3o 4 I97N 5-S3O 31\r\n"
+ "\r\n"
+ "\x47\x30\x22\x2D\x5A\x3F\x47\x58";
+
+ scoped_ptr<WebSocketHandshake> handshake(
+ new WebSocketHandshake(GURL("ws://example.com/demo"),
+ "http://example.com",
+ "ws://example.com/demo",
+ "sample"));
+ SetUpParameter(handshake.get(), 777007543U, 114997259U,
+ "388P O503D&ul7 {K%gX( %7 15",
+ "1 N ?|k UT0or 3o 4 I97N 5-S3O 31",
+ std::string("\x47\x30\x22\x2D\x5A\x3F\x47\x58", 8));
+ EXPECT_EQ(WebSocketHandshake::MODE_INCOMPLETE, handshake->mode());
+ ExpectHandshakeMessageEquals(
+ kExpectedClientHandshakeMessage,
+ handshake->CreateClientHandshakeMessage());
+
+ const char kResponse[] = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Location: ws://example.com/demo\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "\r\n"
+ "\x30\x73\x74\x33\x52\x6C\x26\x71\x2D\x32\x5A\x55\x5E\x77\x65\x75";
+ std::vector<std::string> response_lines;
+ SplitStringDontTrim(kResponse, '\n', &response_lines);
+
+ EXPECT_EQ(WebSocketHandshake::MODE_INCOMPLETE, handshake->mode());
+ // too short
+ EXPECT_EQ(-1, handshake->ReadServerHandshake(kResponse, 16));
+ EXPECT_EQ(WebSocketHandshake::MODE_INCOMPLETE, handshake->mode());
+
+ // only status line
+ std::string response = response_lines[0];
+ EXPECT_EQ(-1, handshake->ReadServerHandshake(
+ response.data(), response.size()));
+ EXPECT_EQ(WebSocketHandshake::MODE_INCOMPLETE, handshake->mode());
+ // by upgrade header
+ response += response_lines[1];
+ EXPECT_EQ(-1, handshake->ReadServerHandshake(
+ response.data(), response.size()));
+ EXPECT_EQ(WebSocketHandshake::MODE_INCOMPLETE, handshake->mode());
+ // by connection header
+ response += response_lines[2];
+ EXPECT_EQ(-1, handshake->ReadServerHandshake(
+ response.data(), response.size()));
+ EXPECT_EQ(WebSocketHandshake::MODE_INCOMPLETE, handshake->mode());
+
+ response += response_lines[3]; // Sec-WebSocket-Origin
+ response += response_lines[4]; // Sec-WebSocket-Location
+ response += response_lines[5]; // Sec-WebSocket-Protocol
+ EXPECT_EQ(-1, handshake->ReadServerHandshake(
+ response.data(), response.size()));
+ EXPECT_EQ(WebSocketHandshake::MODE_INCOMPLETE, handshake->mode());
+
+ response += response_lines[6]; // \r\n
+ EXPECT_EQ(-1, handshake->ReadServerHandshake(
+ response.data(), response.size()));
+ EXPECT_EQ(WebSocketHandshake::MODE_INCOMPLETE, handshake->mode());
+
+ int handshake_length = sizeof(kResponse) - 1; // -1 for terminating \0
+ EXPECT_EQ(handshake_length, handshake->ReadServerHandshake(
+ kResponse, handshake_length)); // -1 for terminating \0
+ EXPECT_EQ(WebSocketHandshake::MODE_CONNECTED, handshake->mode());
+}
+
+TEST_F(WebSocketHandshakeTest, ServerSentData) {
+ const std::string kExpectedClientHandshakeMessage =
+ "GET /demo HTTP/1.1\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Host: example.com\r\n"
+ "Origin: http://example.com\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Sec-WebSocket-Key1: 388P O503D&ul7 {K%gX( %7 15\r\n"
+ "Sec-WebSocket-Key2: 1 N ?|k UT0or 3o 4 I97N 5-S3O 31\r\n"
+ "\r\n"
+ "\x47\x30\x22\x2D\x5A\x3F\x47\x58";
+ scoped_ptr<WebSocketHandshake> handshake(
+ new WebSocketHandshake(GURL("ws://example.com/demo"),
+ "http://example.com",
+ "ws://example.com/demo",
+ "sample"));
+ SetUpParameter(handshake.get(), 777007543U, 114997259U,
+ "388P O503D&ul7 {K%gX( %7 15",
+ "1 N ?|k UT0or 3o 4 I97N 5-S3O 31",
+ std::string("\x47\x30\x22\x2D\x5A\x3F\x47\x58", 8));
+ EXPECT_EQ(WebSocketHandshake::MODE_INCOMPLETE, handshake->mode());
+ ExpectHandshakeMessageEquals(
+ kExpectedClientHandshakeMessage,
+ handshake->CreateClientHandshakeMessage());
+
+ const char kResponse[] = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Location: ws://example.com/demo\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "\r\n"
+ "\x30\x73\x74\x33\x52\x6C\x26\x71\x2D\x32\x5A\x55\x5E\x77\x65\x75"
+ "\0Hello\xff";
+
+ int handshake_length = strlen(kResponse); // key3 doesn't contain \0.
+ EXPECT_EQ(handshake_length, handshake->ReadServerHandshake(
+ kResponse, sizeof(kResponse) - 1)); // -1 for terminating \0
+ EXPECT_EQ(WebSocketHandshake::MODE_CONNECTED, handshake->mode());
+}
+
+TEST_F(WebSocketHandshakeTest, is_secure_false) {
+ scoped_ptr<WebSocketHandshake> handshake(
+ new WebSocketHandshake(GURL("ws://example.com/demo"),
+ "http://example.com",
+ "ws://example.com/demo",
+ "sample"));
+ EXPECT_FALSE(handshake->is_secure());
+}
+
+TEST_F(WebSocketHandshakeTest, is_secure_true) {
+ // wss:// is secure.
+ scoped_ptr<WebSocketHandshake> handshake(
+ new WebSocketHandshake(GURL("wss://example.com/demo"),
+ "http://example.com",
+ "wss://example.com/demo",
+ "sample"));
+ EXPECT_TRUE(handshake->is_secure());
+}
+
+TEST_F(WebSocketHandshakeTest, CreateClientHandshakeMessage_ResourceName) {
+ scoped_ptr<WebSocketHandshake> handshake(
+ new WebSocketHandshake(GURL("ws://example.com/Test?q=xxx&p=%20"),
+ "http://example.com",
+ "ws://example.com/demo",
+ "sample"));
+ // Path and query should be preserved as-is.
+ EXPECT_EQ("/Test?q=xxx&p=%20", GetResourceName(handshake.get()));
+}
+
+TEST_F(WebSocketHandshakeTest, CreateClientHandshakeMessage_Host) {
+ scoped_ptr<WebSocketHandshake> handshake(
+ new WebSocketHandshake(GURL("ws://Example.Com/demo"),
+ "http://Example.Com",
+ "ws://Example.Com/demo",
+ "sample"));
+ // Host should be lowercased
+ EXPECT_EQ("example.com", GetHostFieldValue(handshake.get()));
+ EXPECT_EQ("http://example.com", GetOriginFieldValue(handshake.get()));
+}
+
+TEST_F(WebSocketHandshakeTest, CreateClientHandshakeMessage_TrimPort80) {
+ scoped_ptr<WebSocketHandshake> handshake(
+ new WebSocketHandshake(GURL("ws://example.com:80/demo"),
+ "http://example.com",
+ "ws://example.com/demo",
+ "sample"));
+ // :80 should be trimmed as it's the default port for ws://.
+ EXPECT_EQ("example.com", GetHostFieldValue(handshake.get()));
+}
+
+TEST_F(WebSocketHandshakeTest, CreateClientHandshakeMessage_TrimPort443) {
+ scoped_ptr<WebSocketHandshake> handshake(
+ new WebSocketHandshake(GURL("wss://example.com:443/demo"),
+ "http://example.com",
+ "wss://example.com/demo",
+ "sample"));
+ // :443 should be trimmed as it's the default port for wss://.
+ EXPECT_EQ("example.com", GetHostFieldValue(handshake.get()));
+}
+
+TEST_F(WebSocketHandshakeTest,
+ CreateClientHandshakeMessage_NonDefaultPortForWs) {
+ scoped_ptr<WebSocketHandshake> handshake(
+ new WebSocketHandshake(GURL("ws://example.com:8080/demo"),
+ "http://example.com",
+ "wss://example.com/demo",
+ "sample"));
+ // :8080 should be preserved as it's not the default port for ws://.
+ EXPECT_EQ("example.com:8080", GetHostFieldValue(handshake.get()));
+}
+
+TEST_F(WebSocketHandshakeTest,
+ CreateClientHandshakeMessage_NonDefaultPortForWss) {
+ scoped_ptr<WebSocketHandshake> handshake(
+ new WebSocketHandshake(GURL("wss://example.com:4443/demo"),
+ "http://example.com",
+ "wss://example.com/demo",
+ "sample"));
+ // :4443 should be preserved as it's not the default port for wss://.
+ EXPECT_EQ("example.com:4443", GetHostFieldValue(handshake.get()));
+}
+
+TEST_F(WebSocketHandshakeTest, CreateClientHandshakeMessage_WsBut443) {
+ scoped_ptr<WebSocketHandshake> handshake(
+ new WebSocketHandshake(GURL("ws://example.com:443/demo"),
+ "http://example.com",
+ "ws://example.com/demo",
+ "sample"));
+ // :443 should be preserved as it's not the default port for ws://.
+ EXPECT_EQ("example.com:443", GetHostFieldValue(handshake.get()));
+}
+
+TEST_F(WebSocketHandshakeTest, CreateClientHandshakeMessage_WssBut80) {
+ scoped_ptr<WebSocketHandshake> handshake(
+ new WebSocketHandshake(GURL("wss://example.com:80/demo"),
+ "http://example.com",
+ "wss://example.com/demo",
+ "sample"));
+ // :80 should be preserved as it's not the default port for wss://.
+ EXPECT_EQ("example.com:80", GetHostFieldValue(handshake.get()));
+}
+
+} // namespace net
diff --git a/net/websockets/websocket_job.cc b/net/websockets/websocket_job.cc
new file mode 100644
index 0000000..17dc54b
--- /dev/null
+++ b/net/websockets/websocket_job.cc
@@ -0,0 +1,498 @@
+// 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 "net/websockets/websocket_job.h"
+
+#include <algorithm>
+
+#include "base/string_tokenizer.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/net_errors.h"
+#include "net/base/cookie_policy.h"
+#include "net/base/cookie_store.h"
+#include "net/base/io_buffer.h"
+#include "net/http/http_util.h"
+#include "net/url_request/url_request_context.h"
+#include "net/websockets/websocket_frame_handler.h"
+#include "net/websockets/websocket_handshake_handler.h"
+#include "net/websockets/websocket_throttle.h"
+
+namespace {
+
+// lower-case header names.
+const char* const kCookieHeaders[] = {
+ "cookie", "cookie2"
+};
+const char* const kSetCookieHeaders[] = {
+ "set-cookie", "set-cookie2"
+};
+
+net::SocketStreamJob* WebSocketJobFactory(
+ const GURL& url, net::SocketStream::Delegate* delegate) {
+ net::WebSocketJob* job = new net::WebSocketJob(delegate);
+ job->InitSocketStream(new net::SocketStream(url, job));
+ return job;
+}
+
+class WebSocketJobInitSingleton {
+ private:
+ friend struct DefaultSingletonTraits<WebSocketJobInitSingleton>;
+ WebSocketJobInitSingleton() {
+ net::SocketStreamJob::RegisterProtocolFactory("ws", WebSocketJobFactory);
+ net::SocketStreamJob::RegisterProtocolFactory("wss", WebSocketJobFactory);
+ }
+};
+
+} // anonymous namespace
+
+namespace net {
+
+// static
+void WebSocketJob::EnsureInit() {
+ Singleton<WebSocketJobInitSingleton>::get();
+}
+
+WebSocketJob::WebSocketJob(SocketStream::Delegate* delegate)
+ : delegate_(delegate),
+ state_(INITIALIZED),
+ waiting_(false),
+ callback_(NULL),
+ handshake_request_(new WebSocketHandshakeRequestHandler),
+ handshake_response_(new WebSocketHandshakeResponseHandler),
+ handshake_request_sent_(0),
+ response_cookies_save_index_(0),
+ ALLOW_THIS_IN_INITIALIZER_LIST(can_get_cookies_callback_(
+ this, &WebSocketJob::OnCanGetCookiesCompleted)),
+ ALLOW_THIS_IN_INITIALIZER_LIST(can_set_cookie_callback_(
+ this, &WebSocketJob::OnCanSetCookieCompleted)),
+ send_frame_handler_(new WebSocketFrameHandler),
+ receive_frame_handler_(new WebSocketFrameHandler) {
+}
+
+WebSocketJob::~WebSocketJob() {
+ DCHECK_EQ(CLOSED, state_);
+ DCHECK(!delegate_);
+ DCHECK(!socket_.get());
+}
+
+void WebSocketJob::Connect() {
+ DCHECK(socket_.get());
+ DCHECK_EQ(state_, INITIALIZED);
+ state_ = CONNECTING;
+ socket_->Connect();
+}
+
+bool WebSocketJob::SendData(const char* data, int len) {
+ switch (state_) {
+ case INITIALIZED:
+ return false;
+
+ case CONNECTING:
+ return SendHandshakeRequest(data, len);
+
+ case OPEN:
+ {
+ send_frame_handler_->AppendData(data, len);
+ // If current buffer is sending now, this data will be sent in
+ // SendPending() after current data was sent.
+ // Do not buffer sending data for now. Since
+ // WebCore::SocketStreamHandle controls traffic to keep number of
+ // pending bytes less than max_pending_send_allowed, so when sending
+ // larger message than max_pending_send_allowed should not be buffered.
+ // If we don't call OnSentData, WebCore::SocketStreamHandle would stop
+ // sending more data when pending data reaches max_pending_send_allowed.
+ // TODO(ukai): Fix this to support compression for larger message.
+ int err = 0;
+ if (!send_frame_handler_->GetCurrentBuffer() &&
+ (err = send_frame_handler_->UpdateCurrentBuffer(false)) > 0) {
+ DCHECK(!current_buffer_);
+ current_buffer_ = new DrainableIOBuffer(
+ send_frame_handler_->GetCurrentBuffer(),
+ send_frame_handler_->GetCurrentBufferSize());
+ return socket_->SendData(
+ current_buffer_->data(), current_buffer_->BytesRemaining());
+ }
+ return err >= 0;
+ }
+
+ case CLOSING:
+ case CLOSED:
+ return false;
+ }
+ return false;
+}
+
+void WebSocketJob::Close() {
+ state_ = CLOSING;
+ if (current_buffer_) {
+ // Will close in SendPending.
+ return;
+ }
+ state_ = CLOSED;
+ socket_->Close();
+}
+
+void WebSocketJob::RestartWithAuth(
+ const std::wstring& username,
+ const std::wstring& password) {
+ state_ = CONNECTING;
+ socket_->RestartWithAuth(username, password);
+}
+
+void WebSocketJob::DetachDelegate() {
+ state_ = CLOSED;
+ Singleton<WebSocketThrottle>::get()->RemoveFromQueue(this);
+ Singleton<WebSocketThrottle>::get()->WakeupSocketIfNecessary();
+
+ scoped_refptr<WebSocketJob> protect(this);
+
+ delegate_ = NULL;
+ if (socket_)
+ socket_->DetachDelegate();
+ socket_ = NULL;
+ if (callback_) {
+ waiting_ = false;
+ callback_ = NULL;
+ Release(); // Balanced with OnStartOpenConnection().
+ }
+}
+
+int WebSocketJob::OnStartOpenConnection(
+ SocketStream* socket, CompletionCallback* callback) {
+ DCHECK(!callback_);
+ state_ = CONNECTING;
+ addresses_.Copy(socket->address_list().head(), true);
+ Singleton<WebSocketThrottle>::get()->PutInQueue(this);
+ if (!waiting_)
+ return OK;
+ callback_ = callback;
+ AddRef(); // Balanced when callback_ becomes NULL.
+ return ERR_IO_PENDING;
+}
+
+void WebSocketJob::OnConnected(
+ SocketStream* socket, int max_pending_send_allowed) {
+ if (state_ == CLOSED)
+ return;
+ DCHECK_EQ(CONNECTING, state_);
+ if (delegate_)
+ delegate_->OnConnected(socket, max_pending_send_allowed);
+}
+
+void WebSocketJob::OnSentData(SocketStream* socket, int amount_sent) {
+ DCHECK_NE(INITIALIZED, state_);
+ if (state_ == CLOSED)
+ return;
+ if (state_ == CONNECTING) {
+ OnSentHandshakeRequest(socket, amount_sent);
+ return;
+ }
+ if (delegate_) {
+ DCHECK(state_ == OPEN || state_ == CLOSING);
+ DCHECK_GT(amount_sent, 0);
+ DCHECK(current_buffer_);
+ current_buffer_->DidConsume(amount_sent);
+ if (current_buffer_->BytesRemaining() > 0)
+ return;
+
+ // We need to report amount_sent of original buffer size, instead of
+ // amount sent to |socket|.
+ amount_sent = send_frame_handler_->GetOriginalBufferSize();
+ DCHECK_GT(amount_sent, 0);
+ current_buffer_ = NULL;
+ send_frame_handler_->ReleaseCurrentBuffer();
+ delegate_->OnSentData(socket, amount_sent);
+ MessageLoopForIO::current()->PostTask(
+ FROM_HERE, NewRunnableMethod(this, &WebSocketJob::SendPending));
+ }
+}
+
+void WebSocketJob::OnReceivedData(
+ SocketStream* socket, const char* data, int len) {
+ DCHECK_NE(INITIALIZED, state_);
+ if (state_ == CLOSED)
+ return;
+ if (state_ == CONNECTING) {
+ OnReceivedHandshakeResponse(socket, data, len);
+ return;
+ }
+ DCHECK(state_ == OPEN || state_ == CLOSING);
+ std::string received_data;
+ receive_frame_handler_->AppendData(data, len);
+ // Don't buffer receiving data for now.
+ // TODO(ukai): fix performance of WebSocketFrameHandler.
+ while (receive_frame_handler_->UpdateCurrentBuffer(false) > 0) {
+ received_data +=
+ std::string(receive_frame_handler_->GetCurrentBuffer()->data(),
+ receive_frame_handler_->GetCurrentBufferSize());
+ receive_frame_handler_->ReleaseCurrentBuffer();
+ }
+ if (delegate_ && received_data.size() > 0)
+ delegate_->OnReceivedData(
+ socket, received_data.data(), received_data.size());
+}
+
+void WebSocketJob::OnClose(SocketStream* socket) {
+ state_ = CLOSED;
+ Singleton<WebSocketThrottle>::get()->RemoveFromQueue(this);
+ Singleton<WebSocketThrottle>::get()->WakeupSocketIfNecessary();
+
+ scoped_refptr<WebSocketJob> protect(this);
+
+ SocketStream::Delegate* delegate = delegate_;
+ delegate_ = NULL;
+ socket_ = NULL;
+ if (callback_) {
+ waiting_ = false;
+ callback_ = NULL;
+ Release(); // Balanced with OnStartOpenConnection().
+ }
+ if (delegate)
+ delegate->OnClose(socket);
+}
+
+void WebSocketJob::OnAuthRequired(
+ SocketStream* socket, AuthChallengeInfo* auth_info) {
+ if (delegate_)
+ delegate_->OnAuthRequired(socket, auth_info);
+}
+
+void WebSocketJob::OnError(const SocketStream* socket, int error) {
+ if (delegate_)
+ delegate_->OnError(socket, error);
+}
+
+bool WebSocketJob::SendHandshakeRequest(const char* data, int len) {
+ DCHECK_EQ(state_, CONNECTING);
+ if (!handshake_request_->ParseRequest(data, len))
+ return false;
+
+ // handshake message is completed.
+ AddCookieHeaderAndSend();
+ // Just buffered in |handshake_request_|.
+ return true;
+}
+
+void WebSocketJob::AddCookieHeaderAndSend() {
+ AddRef(); // Balanced in OnCanGetCookiesCompleted
+
+ int policy = OK;
+ if (socket_->context()->cookie_policy()) {
+ GURL url_for_cookies = GetURLForCookies();
+ policy = socket_->context()->cookie_policy()->CanGetCookies(
+ url_for_cookies,
+ url_for_cookies,
+ &can_get_cookies_callback_);
+ if (policy == ERR_IO_PENDING)
+ return; // Wait for completion callback
+ }
+ OnCanGetCookiesCompleted(policy);
+}
+
+void WebSocketJob::OnCanGetCookiesCompleted(int policy) {
+ if (socket_ && delegate_ && state_ == CONNECTING) {
+ handshake_request_->RemoveHeaders(
+ kCookieHeaders, arraysize(kCookieHeaders));
+ if (policy == OK) {
+ // Add cookies, including HttpOnly cookies.
+ if (socket_->context()->cookie_store()) {
+ CookieOptions cookie_options;
+ cookie_options.set_include_httponly();
+ std::string cookie =
+ socket_->context()->cookie_store()->GetCookiesWithOptions(
+ GetURLForCookies(), cookie_options);
+ if (!cookie.empty())
+ handshake_request_->AppendHeaderIfMissing("Cookie", cookie);
+ }
+ }
+
+ const std::string& handshake_request = handshake_request_->GetRawRequest();
+ handshake_request_sent_ = 0;
+ socket_->SendData(handshake_request.data(),
+ handshake_request.size());
+ }
+ Release(); // Balance AddRef taken in AddCookieHeaderAndSend
+}
+
+void WebSocketJob::OnSentHandshakeRequest(
+ SocketStream* socket, int amount_sent) {
+ DCHECK_EQ(state_, CONNECTING);
+ handshake_request_sent_ += amount_sent;
+ DCHECK_LE(handshake_request_sent_, handshake_request_->raw_length());
+ if (handshake_request_sent_ >= handshake_request_->raw_length()) {
+ // handshake request has been sent.
+ // notify original size of handshake request to delegate.
+ if (delegate_)
+ delegate_->OnSentData(
+ socket,
+ handshake_request_->original_length());
+ handshake_request_.reset();
+ }
+}
+
+void WebSocketJob::OnReceivedHandshakeResponse(
+ SocketStream* socket, const char* data, int len) {
+ DCHECK_EQ(state_, CONNECTING);
+ if (handshake_response_->HasResponse()) {
+ // If we already has handshake response, received data should be frame
+ // data, not handshake message.
+ receive_frame_handler_->AppendData(data, len);
+ return;
+ }
+
+ size_t response_length = handshake_response_->ParseRawResponse(data, len);
+ if (!handshake_response_->HasResponse()) {
+ // not yet. we need more data.
+ return;
+ }
+ // handshake message is completed.
+ if (len - response_length > 0) {
+ // If we received extra data, it should be frame data.
+ receive_frame_handler_->AppendData(data + response_length,
+ len - response_length);
+ }
+ SaveCookiesAndNotifyHeaderComplete();
+}
+
+void WebSocketJob::SaveCookiesAndNotifyHeaderComplete() {
+ // handshake message is completed.
+ DCHECK(handshake_response_->HasResponse());
+
+ response_cookies_.clear();
+ response_cookies_save_index_ = 0;
+
+ handshake_response_->GetHeaders(
+ kSetCookieHeaders, arraysize(kSetCookieHeaders), &response_cookies_);
+
+ // Now, loop over the response cookies, and attempt to persist each.
+ SaveNextCookie();
+}
+
+void WebSocketJob::SaveNextCookie() {
+ if (response_cookies_save_index_ == response_cookies_.size()) {
+ response_cookies_.clear();
+ response_cookies_save_index_ = 0;
+
+ // Remove cookie headers, with malformed headers preserved.
+ // Actual handshake should be done in WebKit.
+ handshake_response_->RemoveHeaders(
+ kSetCookieHeaders, arraysize(kSetCookieHeaders));
+ std::string received_data = handshake_response_->GetResponse();
+ // Don't buffer receiving data for now.
+ // TODO(ukai): fix performance of WebSocketFrameHandler.
+ while (receive_frame_handler_->UpdateCurrentBuffer(false) > 0) {
+ received_data +=
+ std::string(receive_frame_handler_->GetCurrentBuffer()->data(),
+ receive_frame_handler_->GetCurrentBufferSize());
+ receive_frame_handler_->ReleaseCurrentBuffer();
+ }
+
+ state_ = OPEN;
+ if (delegate_)
+ delegate_->OnReceivedData(
+ socket_, received_data.data(), received_data.size());
+
+ handshake_response_.reset();
+
+ Singleton<WebSocketThrottle>::get()->RemoveFromQueue(this);
+ Singleton<WebSocketThrottle>::get()->WakeupSocketIfNecessary();
+ return;
+ }
+
+ AddRef(); // Balanced in OnCanSetCookieCompleted
+
+ int policy = OK;
+ if (socket_->context()->cookie_policy()) {
+ GURL url_for_cookies = GetURLForCookies();
+ policy = socket_->context()->cookie_policy()->CanSetCookie(
+ url_for_cookies,
+ url_for_cookies,
+ response_cookies_[response_cookies_save_index_],
+ &can_set_cookie_callback_);
+ if (policy == ERR_IO_PENDING)
+ return; // Wait for completion callback
+ }
+
+ OnCanSetCookieCompleted(policy);
+}
+
+void WebSocketJob::OnCanSetCookieCompleted(int policy) {
+ if (socket_ && delegate_ && state_ == CONNECTING) {
+ if ((policy == OK || policy == OK_FOR_SESSION_ONLY) &&
+ socket_->context()->cookie_store()) {
+ CookieOptions options;
+ options.set_include_httponly();
+ if (policy == OK_FOR_SESSION_ONLY)
+ options.set_force_session();
+ GURL url_for_cookies = GetURLForCookies();
+ socket_->context()->cookie_store()->SetCookieWithOptions(
+ url_for_cookies, response_cookies_[response_cookies_save_index_],
+ options);
+ }
+ response_cookies_save_index_++;
+ SaveNextCookie();
+ }
+ Release(); // Balance AddRef taken in SaveNextCookie
+}
+
+GURL WebSocketJob::GetURLForCookies() const {
+ GURL url = socket_->url();
+ std::string scheme = socket_->is_secure() ? "https" : "http";
+ url_canon::Replacements<char> replacements;
+ replacements.SetScheme(scheme.c_str(),
+ url_parse::Component(0, scheme.length()));
+ return url.ReplaceComponents(replacements);
+}
+
+const AddressList& WebSocketJob::address_list() const {
+ return addresses_;
+}
+
+void WebSocketJob::SetWaiting() {
+ waiting_ = true;
+}
+
+bool WebSocketJob::IsWaiting() const {
+ return waiting_;
+}
+
+void WebSocketJob::Wakeup() {
+ if (!waiting_)
+ return;
+ waiting_ = false;
+ DCHECK(callback_);
+ MessageLoopForIO::current()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this,
+ &WebSocketJob::DoCallback));
+}
+
+void WebSocketJob::DoCallback() {
+ // |callback_| may be NULL if OnClose() or DetachDelegate() was called.
+ if (callback_) {
+ net::CompletionCallback* callback = callback_;
+ callback_ = NULL;
+ callback->Run(net::OK);
+ Release(); // Balanced with OnStartOpenConnection().
+ }
+}
+
+void WebSocketJob::SendPending() {
+ if (current_buffer_)
+ return;
+ // Current buffer is done. Try next buffer if any.
+ // Don't buffer sending data. See comment on case OPEN in SendData().
+ if (send_frame_handler_->UpdateCurrentBuffer(false) <= 0) {
+ // No more data to send.
+ if (state_ == CLOSING)
+ socket_->Close();
+ return;
+ }
+ current_buffer_ = new DrainableIOBuffer(
+ send_frame_handler_->GetCurrentBuffer(),
+ send_frame_handler_->GetCurrentBufferSize());
+ socket_->SendData(current_buffer_->data(), current_buffer_->BytesRemaining());
+}
+
+} // namespace net
diff --git a/net/websockets/websocket_job.h b/net/websockets/websocket_job.h
new file mode 100644
index 0000000..833726b
--- /dev/null
+++ b/net/websockets/websocket_job.h
@@ -0,0 +1,120 @@
+// 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.
+
+#ifndef NET_WEBSOCKETS_WEBSOCKET_JOB_H_
+#define NET_WEBSOCKETS_WEBSOCKET_JOB_H_
+
+#include <string>
+#include <vector>
+
+#include "net/base/address_list.h"
+#include "net/base/completion_callback.h"
+#include "net/socket_stream/socket_stream_job.h"
+
+class GURL;
+
+namespace net {
+
+class DrainableIOBuffer;
+class WebSocketFrameHandler;
+class WebSocketHandshakeRequestHandler;
+class WebSocketHandshakeResponseHandler;
+
+// WebSocket protocol specific job on SocketStream.
+// It captures WebSocket handshake message and handles cookie operations.
+// Chrome security policy doesn't allow renderer process (except dev tools)
+// see HttpOnly cookies, so it injects cookie header in handshake request and
+// strips set-cookie headers in handshake response.
+// TODO(ukai): refactor websocket.cc to use this.
+class WebSocketJob : public SocketStreamJob, public SocketStream::Delegate {
+ public:
+ // This is state of WebSocket, not SocketStream.
+ enum State {
+ INITIALIZED = -1,
+ CONNECTING = 0,
+ OPEN = 1,
+ CLOSING = 2,
+ CLOSED = 3,
+ };
+ static void EnsureInit();
+
+ explicit WebSocketJob(SocketStream::Delegate* delegate);
+
+ State state() const { return state_; }
+ virtual void Connect();
+ virtual bool SendData(const char* data, int len);
+ virtual void Close();
+ virtual void RestartWithAuth(
+ const std::wstring& username,
+ const std::wstring& password);
+ virtual void DetachDelegate();
+
+ // SocketStream::Delegate methods.
+ virtual int OnStartOpenConnection(
+ SocketStream* socket, CompletionCallback* callback);
+ virtual void OnConnected(
+ SocketStream* socket, int max_pending_send_allowed);
+ virtual void OnSentData(
+ SocketStream* socket, int amount_sent);
+ virtual void OnReceivedData(
+ SocketStream* socket, const char* data, int len);
+ virtual void OnClose(SocketStream* socket);
+ virtual void OnAuthRequired(
+ SocketStream* socket, AuthChallengeInfo* auth_info);
+ virtual void OnError(
+ const SocketStream* socket, int error);
+
+ private:
+ friend class WebSocketThrottle;
+ friend class WebSocketJobTest;
+ virtual ~WebSocketJob();
+
+ bool SendHandshakeRequest(const char* data, int len);
+ void AddCookieHeaderAndSend();
+ void OnCanGetCookiesCompleted(int policy);
+
+ void OnSentHandshakeRequest(SocketStream* socket, int amount_sent);
+ void OnReceivedHandshakeResponse(
+ SocketStream* socket, const char* data, int len);
+ void SaveCookiesAndNotifyHeaderComplete();
+ void SaveNextCookie();
+ void OnCanSetCookieCompleted(int policy);
+
+ GURL GetURLForCookies() const;
+
+ const AddressList& address_list() const;
+ void SetWaiting();
+ bool IsWaiting() const;
+ void Wakeup();
+ void DoCallback();
+
+ void SendPending();
+
+ SocketStream::Delegate* delegate_;
+ State state_;
+ bool waiting_;
+ AddressList addresses_;
+ CompletionCallback* callback_; // for throttling.
+
+ scoped_ptr<WebSocketHandshakeRequestHandler> handshake_request_;
+ scoped_ptr<WebSocketHandshakeResponseHandler> handshake_response_;
+
+ size_t handshake_request_sent_;
+
+ std::vector<std::string> response_cookies_;
+ size_t response_cookies_save_index_;
+
+ CompletionCallbackImpl<WebSocketJob> can_get_cookies_callback_;
+ CompletionCallbackImpl<WebSocketJob> can_set_cookie_callback_;
+
+ scoped_ptr<WebSocketFrameHandler> send_frame_handler_;
+ scoped_refptr<DrainableIOBuffer> current_buffer_;
+ scoped_ptr<WebSocketFrameHandler> receive_frame_handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebSocketJob);
+};
+
+} // namespace
+
+#endif // NET_WEBSOCKETS_WEBSOCKET_JOB_H_
diff --git a/net/websockets/websocket_job_unittest.cc b/net/websockets/websocket_job_unittest.cc
new file mode 100644
index 0000000..0ec760c
--- /dev/null
+++ b/net/websockets/websocket_job_unittest.cc
@@ -0,0 +1,522 @@
+// 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 <string>
+#include <vector>
+
+#include "base/ref_counted.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/cookie_policy.h"
+#include "net/base/cookie_store.h"
+#include "net/base/net_errors.h"
+#include "net/base/sys_addrinfo.h"
+#include "net/socket_stream/socket_stream.h"
+#include "net/url_request/url_request_context.h"
+#include "net/websockets/websocket_job.h"
+#include "net/websockets/websocket_throttle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+class MockSocketStream : public SocketStream {
+ public:
+ MockSocketStream(const GURL& url, SocketStream::Delegate* delegate)
+ : SocketStream(url, delegate) {}
+ virtual ~MockSocketStream() {}
+
+ virtual void Connect() {}
+ virtual bool SendData(const char* data, int len) {
+ sent_data_ += std::string(data, len);
+ return true;
+ }
+
+ virtual void Close() {}
+ virtual void RestartWithAuth(
+ const std::wstring& username, std::wstring& password) {}
+ virtual void DetachDelegate() {
+ delegate_ = NULL;
+ }
+
+ const std::string& sent_data() const {
+ return sent_data_;
+ }
+
+ private:
+ std::string sent_data_;
+};
+
+class MockSocketStreamDelegate : public SocketStream::Delegate {
+ public:
+ MockSocketStreamDelegate()
+ : amount_sent_(0) {}
+ virtual ~MockSocketStreamDelegate() {}
+
+ virtual void OnConnected(SocketStream* socket, int max_pending_send_allowed) {
+ }
+ virtual void OnSentData(SocketStream* socket, int amount_sent) {
+ amount_sent_ += amount_sent;
+ }
+ virtual void OnReceivedData(SocketStream* socket,
+ const char* data, int len) {
+ received_data_ += std::string(data, len);
+ }
+ virtual void OnClose(SocketStream* socket) {
+ }
+
+ size_t amount_sent() const { return amount_sent_; }
+ const std::string& received_data() const { return received_data_; }
+
+ private:
+ int amount_sent_;
+ std::string received_data_;
+};
+
+class MockCookieStore : public CookieStore {
+ public:
+ struct Entry {
+ GURL url;
+ std::string cookie_line;
+ CookieOptions options;
+ };
+ MockCookieStore() {}
+
+ virtual bool SetCookieWithOptions(const GURL& url,
+ const std::string& cookie_line,
+ const CookieOptions& options) {
+ Entry entry;
+ entry.url = url;
+ entry.cookie_line = cookie_line;
+ entry.options = options;
+ entries_.push_back(entry);
+ return true;
+ }
+ virtual std::string GetCookiesWithOptions(const GURL& url,
+ const CookieOptions& options) {
+ std::string result;
+ for (size_t i = 0; i < entries_.size(); i++) {
+ Entry &entry = entries_[i];
+ if (url == entry.url) {
+ if (!result.empty()) {
+ result += "; ";
+ }
+ result += entry.cookie_line;
+ }
+ }
+ return result;
+ }
+ virtual void DeleteCookie(const GURL& url,
+ const std::string& cookie_name) {}
+ virtual CookieMonster* GetCookieMonster() { return NULL; }
+
+ const std::vector<Entry>& entries() const { return entries_; }
+
+ private:
+ friend class base::RefCountedThreadSafe<MockCookieStore>;
+ virtual ~MockCookieStore() {}
+
+ std::vector<Entry> entries_;
+};
+
+class MockCookiePolicy : public CookiePolicy,
+ public base::RefCountedThreadSafe<MockCookiePolicy> {
+ public:
+ MockCookiePolicy() : allow_all_cookies_(true), callback_(NULL) {}
+
+ void set_allow_all_cookies(bool allow_all_cookies) {
+ allow_all_cookies_ = allow_all_cookies;
+ }
+
+ virtual int CanGetCookies(const GURL& url,
+ const GURL& first_party_for_cookies,
+ CompletionCallback* callback) {
+ DCHECK(!callback_);
+ callback_ = callback;
+ MessageLoop::current()->PostTask(
+ FROM_HERE, NewRunnableMethod(this, &MockCookiePolicy::OnCanGetCookies));
+ return ERR_IO_PENDING;
+ }
+
+ virtual int CanSetCookie(const GURL& url,
+ const GURL& first_party_for_cookies,
+ const std::string& cookie_line,
+ CompletionCallback* callback) {
+ DCHECK(!callback_);
+ callback_ = callback;
+ MessageLoop::current()->PostTask(
+ FROM_HERE, NewRunnableMethod(this, &MockCookiePolicy::OnCanSetCookie));
+ return ERR_IO_PENDING;
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<MockCookiePolicy>;
+ virtual ~MockCookiePolicy() {}
+
+ void OnCanGetCookies() {
+ CompletionCallback* callback = callback_;
+ callback_ = NULL;
+ if (allow_all_cookies_)
+ callback->Run(OK);
+ else
+ callback->Run(ERR_ACCESS_DENIED);
+ }
+ void OnCanSetCookie() {
+ CompletionCallback* callback = callback_;
+ callback_ = NULL;
+ if (allow_all_cookies_)
+ callback->Run(OK);
+ else
+ callback->Run(ERR_ACCESS_DENIED);
+ }
+
+ bool allow_all_cookies_;
+ CompletionCallback* callback_;
+};
+
+class MockURLRequestContext : public URLRequestContext {
+ public:
+ MockURLRequestContext(CookieStore* cookie_store,
+ CookiePolicy* cookie_policy) {
+ cookie_store_ = cookie_store;
+ cookie_policy_ = cookie_policy;
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<MockURLRequestContext>;
+ virtual ~MockURLRequestContext() {}
+};
+
+class WebSocketJobTest : public PlatformTest {
+ public:
+ virtual void SetUp() {
+ cookie_store_ = new MockCookieStore;
+ cookie_policy_ = new MockCookiePolicy;
+ context_ = new MockURLRequestContext(
+ cookie_store_.get(), cookie_policy_.get());
+ }
+ virtual void TearDown() {
+ cookie_store_ = NULL;
+ cookie_policy_ = NULL;
+ context_ = NULL;
+ websocket_ = NULL;
+ socket_ = NULL;
+ }
+ protected:
+ void InitWebSocketJob(const GURL& url, MockSocketStreamDelegate* delegate) {
+ websocket_ = new WebSocketJob(delegate);
+ socket_ = new MockSocketStream(url, websocket_.get());
+ websocket_->InitSocketStream(socket_.get());
+ websocket_->set_context(context_.get());
+ websocket_->state_ = WebSocketJob::CONNECTING;
+ struct addrinfo addr;
+ memset(&addr, 0, sizeof(struct addrinfo));
+ addr.ai_family = AF_INET;
+ addr.ai_addrlen = sizeof(struct sockaddr_in);
+ struct sockaddr_in sa_in;
+ memset(&sa_in, 0, sizeof(struct sockaddr_in));
+ memcpy(&sa_in.sin_addr, "\x7f\0\0\1", 4);
+ addr.ai_addr = reinterpret_cast<sockaddr*>(&sa_in);
+ addr.ai_next = NULL;
+ websocket_->addresses_.Copy(&addr, true);
+ Singleton<WebSocketThrottle>::get()->PutInQueue(websocket_);
+ }
+ WebSocketJob::State GetWebSocketJobState() {
+ return websocket_->state_;
+ }
+ void CloseWebSocketJob() {
+ if (websocket_->socket_) {
+ websocket_->socket_->DetachDelegate();
+ Singleton<WebSocketThrottle>::get()->RemoveFromQueue(websocket_);
+ }
+ websocket_->state_ = WebSocketJob::CLOSED;
+ websocket_->delegate_ = NULL;
+ websocket_->socket_ = NULL;
+ }
+
+ scoped_refptr<MockCookieStore> cookie_store_;
+ scoped_refptr<MockCookiePolicy> cookie_policy_;
+ scoped_refptr<MockURLRequestContext> context_;
+ scoped_refptr<WebSocketJob> websocket_;
+ scoped_refptr<MockSocketStream> socket_;
+};
+
+TEST_F(WebSocketJobTest, SimpleHandshake) {
+ GURL url("ws://example.com/demo");
+ MockSocketStreamDelegate delegate;
+ InitWebSocketJob(url, &delegate);
+
+ static const char* kHandshakeRequestMessage =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n"
+ "Origin: http://example.com\r\n"
+ "\r\n"
+ "^n:ds[4U";
+
+ bool sent = websocket_->SendData(kHandshakeRequestMessage,
+ strlen(kHandshakeRequestMessage));
+ EXPECT_EQ(true, sent);
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(kHandshakeRequestMessage, socket_->sent_data());
+ EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
+ websocket_->OnSentData(socket_.get(), strlen(kHandshakeRequestMessage));
+ EXPECT_EQ(strlen(kHandshakeRequestMessage), delegate.amount_sent());
+
+ const char kHandshakeResponseMessage[] =
+ "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Location: ws://example.com/demo\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "\r\n"
+ "8jKS'y:G*Co,Wxa-";
+
+ websocket_->OnReceivedData(socket_.get(),
+ kHandshakeResponseMessage,
+ strlen(kHandshakeResponseMessage));
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(kHandshakeResponseMessage, delegate.received_data());
+ EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState());
+ CloseWebSocketJob();
+}
+
+TEST_F(WebSocketJobTest, SlowHandshake) {
+ GURL url("ws://example.com/demo");
+ MockSocketStreamDelegate delegate;
+ InitWebSocketJob(url, &delegate);
+
+ static const char* kHandshakeRequestMessage =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n"
+ "Origin: http://example.com\r\n"
+ "\r\n"
+ "^n:ds[4U";
+
+ bool sent = websocket_->SendData(kHandshakeRequestMessage,
+ strlen(kHandshakeRequestMessage));
+ EXPECT_EQ(true, sent);
+ // We assume request is sent in one data chunk (from WebKit)
+ // We don't support streaming request.
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(kHandshakeRequestMessage, socket_->sent_data());
+ EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
+ websocket_->OnSentData(socket_.get(), strlen(kHandshakeRequestMessage));
+ EXPECT_EQ(strlen(kHandshakeRequestMessage), delegate.amount_sent());
+
+ const char kHandshakeResponseMessage[] =
+ "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Location: ws://example.com/demo\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "\r\n"
+ "8jKS'y:G*Co,Wxa-";
+
+ std::vector<std::string> lines;
+ SplitString(kHandshakeResponseMessage, '\n', &lines);
+ for (size_t i = 0; i < lines.size() - 2; i++) {
+ std::string line = lines[i] + "\r\n";
+ SCOPED_TRACE("Line: " + line);
+ websocket_->OnReceivedData(socket_,
+ line.c_str(),
+ line.size());
+ MessageLoop::current()->RunAllPending();
+ EXPECT_TRUE(delegate.received_data().empty());
+ EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
+ }
+ websocket_->OnReceivedData(socket_.get(), "\r\n", 2);
+ MessageLoop::current()->RunAllPending();
+ EXPECT_TRUE(delegate.received_data().empty());
+ EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
+ websocket_->OnReceivedData(socket_.get(), "8jKS'y:G*Co,Wxa-", 16);
+ EXPECT_EQ(kHandshakeResponseMessage, delegate.received_data());
+ EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState());
+ CloseWebSocketJob();
+}
+
+TEST_F(WebSocketJobTest, HandshakeWithCookie) {
+ GURL url("ws://example.com/demo");
+ GURL cookieUrl("http://example.com/demo");
+ CookieOptions cookie_options;
+ cookie_store_->SetCookieWithOptions(
+ cookieUrl, "CR-test=1", cookie_options);
+ cookie_options.set_include_httponly();
+ cookie_store_->SetCookieWithOptions(
+ cookieUrl, "CR-test-httponly=1", cookie_options);
+
+ MockSocketStreamDelegate delegate;
+ InitWebSocketJob(url, &delegate);
+
+ static const char* kHandshakeRequestMessage =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n"
+ "Origin: http://example.com\r\n"
+ "Cookie: WK-test=1\r\n"
+ "\r\n"
+ "^n:ds[4U";
+
+ static const char* kHandshakeRequestExpected =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n"
+ "Origin: http://example.com\r\n"
+ "Cookie: CR-test=1; CR-test-httponly=1\r\n"
+ "\r\n"
+ "^n:ds[4U";
+
+ bool sent = websocket_->SendData(kHandshakeRequestMessage,
+ strlen(kHandshakeRequestMessage));
+ EXPECT_EQ(true, sent);
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(kHandshakeRequestExpected, socket_->sent_data());
+ EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
+ websocket_->OnSentData(socket_, strlen(kHandshakeRequestExpected));
+ EXPECT_EQ(strlen(kHandshakeRequestMessage), delegate.amount_sent());
+
+ const char kHandshakeResponseMessage[] =
+ "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Location: ws://example.com/demo\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Set-Cookie: CR-set-test=1\r\n"
+ "\r\n"
+ "8jKS'y:G*Co,Wxa-";
+
+ static const char* kHandshakeResponseExpected =
+ "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Location: ws://example.com/demo\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "\r\n"
+ "8jKS'y:G*Co,Wxa-";
+
+ websocket_->OnReceivedData(socket_.get(),
+ kHandshakeResponseMessage,
+ strlen(kHandshakeResponseMessage));
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(kHandshakeResponseExpected, delegate.received_data());
+ EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState());
+
+ EXPECT_EQ(3U, cookie_store_->entries().size());
+ EXPECT_EQ(cookieUrl, cookie_store_->entries()[0].url);
+ EXPECT_EQ("CR-test=1", cookie_store_->entries()[0].cookie_line);
+ EXPECT_EQ(cookieUrl, cookie_store_->entries()[1].url);
+ EXPECT_EQ("CR-test-httponly=1", cookie_store_->entries()[1].cookie_line);
+ EXPECT_EQ(cookieUrl, cookie_store_->entries()[2].url);
+ EXPECT_EQ("CR-set-test=1", cookie_store_->entries()[2].cookie_line);
+
+ CloseWebSocketJob();
+}
+
+TEST_F(WebSocketJobTest, HandshakeWithCookieButNotAllowed) {
+ GURL url("ws://example.com/demo");
+ GURL cookieUrl("http://example.com/demo");
+ CookieOptions cookie_options;
+ cookie_store_->SetCookieWithOptions(
+ cookieUrl, "CR-test=1", cookie_options);
+ cookie_options.set_include_httponly();
+ cookie_store_->SetCookieWithOptions(
+ cookieUrl, "CR-test-httponly=1", cookie_options);
+ cookie_policy_->set_allow_all_cookies(false);
+
+ MockSocketStreamDelegate delegate;
+ InitWebSocketJob(url, &delegate);
+
+ static const char* kHandshakeRequestMessage =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n"
+ "Origin: http://example.com\r\n"
+ "Cookie: WK-test=1\r\n"
+ "\r\n"
+ "^n:ds[4U";
+
+ static const char* kHandshakeRequestExpected =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n"
+ "Origin: http://example.com\r\n"
+ "\r\n"
+ "^n:ds[4U";
+
+ bool sent = websocket_->SendData(kHandshakeRequestMessage,
+ strlen(kHandshakeRequestMessage));
+ EXPECT_EQ(true, sent);
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(kHandshakeRequestExpected, socket_->sent_data());
+ EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
+ websocket_->OnSentData(socket_, strlen(kHandshakeRequestExpected));
+ EXPECT_EQ(strlen(kHandshakeRequestMessage), delegate.amount_sent());
+
+ const char kHandshakeResponseMessage[] =
+ "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Location: ws://example.com/demo\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Set-Cookie: CR-set-test=1\r\n"
+ "\r\n"
+ "8jKS'y:G*Co,Wxa-";
+
+ static const char* kHandshakeResponseExpected =
+ "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Location: ws://example.com/demo\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "\r\n"
+ "8jKS'y:G*Co,Wxa-";
+
+ websocket_->OnReceivedData(socket_.get(),
+ kHandshakeResponseMessage,
+ strlen(kHandshakeResponseMessage));
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(kHandshakeResponseExpected, delegate.received_data());
+ EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState());
+
+ EXPECT_EQ(2U, cookie_store_->entries().size());
+ EXPECT_EQ(cookieUrl, cookie_store_->entries()[0].url);
+ EXPECT_EQ("CR-test=1", cookie_store_->entries()[0].cookie_line);
+ EXPECT_EQ(cookieUrl, cookie_store_->entries()[1].url);
+ EXPECT_EQ("CR-test-httponly=1", cookie_store_->entries()[1].cookie_line);
+
+ CloseWebSocketJob();
+}
+
+} // namespace net
diff --git a/net/websockets/websocket_throttle.cc b/net/websockets/websocket_throttle.cc
index 8d0d1fb..9e33cad 100644
--- a/net/websockets/websocket_throttle.cc
+++ b/net/websockets/websocket_throttle.cc
@@ -6,6 +6,7 @@
#include <string>
+#include "base/hash_tables.h"
#include "base/message_loop.h"
#include "base/ref_counted.h"
#include "base/singleton.h"
@@ -13,6 +14,7 @@
#include "net/base/io_buffer.h"
#include "net/base/sys_addrinfo.h"
#include "net/socket_stream/socket_stream.h"
+#include "net/websockets/websocket_job.h"
namespace net {
@@ -41,119 +43,7 @@
}
}
-// State for WebSocket protocol on each SocketStream.
-// This is owned in SocketStream as UserData keyed by WebSocketState::kKeyName.
-// This is alive between connection starts and handshake is finished.
-// In this class, it doesn't check actual handshake finishes, but only checks
-// end of header is found in read data.
-class WebSocketThrottle::WebSocketState : public SocketStream::UserData {
- public:
- explicit WebSocketState(const AddressList& addrs)
- : address_list_(addrs),
- callback_(NULL),
- waiting_(false),
- handshake_finished_(false),
- buffer_(NULL) {
- }
- ~WebSocketState() {}
-
- int OnStartOpenConnection(CompletionCallback* callback) {
- DCHECK(!callback_);
- if (!waiting_)
- return OK;
- callback_ = callback;
- return ERR_IO_PENDING;
- }
-
- int OnRead(const char* data, int len, CompletionCallback* callback) {
- DCHECK(!waiting_);
- DCHECK(!callback_);
- DCHECK(!handshake_finished_);
- static const int kBufferSize = 8129;
-
- if (!buffer_) {
- // Fast path.
- int eoh = HttpUtil::LocateEndOfHeaders(data, len, 0);
- if (eoh > 0) {
- handshake_finished_ = true;
- return OK;
- }
- buffer_ = new GrowableIOBuffer();
- buffer_->SetCapacity(kBufferSize);
- } else if (buffer_->RemainingCapacity() < len) {
- buffer_->SetCapacity(buffer_->capacity() + kBufferSize);
- }
- memcpy(buffer_->data(), data, len);
- buffer_->set_offset(buffer_->offset() + len);
-
- int eoh = HttpUtil::LocateEndOfHeaders(buffer_->StartOfBuffer(),
- buffer_->offset(), 0);
- handshake_finished_ = (eoh > 0);
- return OK;
- }
-
- const AddressList& address_list() const { return address_list_; }
- void SetWaiting() { waiting_ = true; }
- bool IsWaiting() const { return waiting_; }
- bool HandshakeFinished() const { return handshake_finished_; }
- void Wakeup() {
- waiting_ = false;
- // We wrap |callback_| to keep this alive while this is released.
- scoped_refptr<CompletionCallbackRunner> runner =
- new CompletionCallbackRunner(callback_);
- callback_ = NULL;
- MessageLoopForIO::current()->PostTask(
- FROM_HERE,
- NewRunnableMethod(runner.get(),
- &CompletionCallbackRunner::Run));
- }
-
- static const char* kKeyName;
-
- private:
- class CompletionCallbackRunner
- : public base::RefCountedThreadSafe<CompletionCallbackRunner> {
- public:
- explicit CompletionCallbackRunner(CompletionCallback* callback)
- : callback_(callback) {
- DCHECK(callback_);
- }
- void Run() {
- callback_->Run(OK);
- }
- private:
- friend class base::RefCountedThreadSafe<CompletionCallbackRunner>;
-
- virtual ~CompletionCallbackRunner() {}
-
- CompletionCallback* callback_;
-
- DISALLOW_COPY_AND_ASSIGN(CompletionCallbackRunner);
- };
-
- const AddressList& address_list_;
-
- CompletionCallback* callback_;
- // True if waiting another websocket connection is established.
- // False if the websocket is performing handshaking.
- bool waiting_;
-
- // True if the websocket handshake is completed.
- // If true, it will be removed from queue and deleted from the SocketStream
- // UserData soon.
- bool handshake_finished_;
-
- // Buffer for read data to check handshake response message.
- scoped_refptr<GrowableIOBuffer> buffer_;
-
- DISALLOW_COPY_AND_ASSIGN(WebSocketState);
-};
-
-const char* WebSocketThrottle::WebSocketState::kKeyName = "WebSocketState";
-
WebSocketThrottle::WebSocketThrottle() {
- SocketStreamThrottle::RegisterSocketStreamThrottle("ws", this);
- SocketStreamThrottle::RegisterSocketStreamThrottle("wss", this);
}
WebSocketThrottle::~WebSocketThrottle() {
@@ -161,105 +51,87 @@
DCHECK(addr_map_.empty());
}
-int WebSocketThrottle::OnStartOpenConnection(
- SocketStream* socket, CompletionCallback* callback) {
- WebSocketState* state = new WebSocketState(socket->address_list());
- PutInQueue(socket, state);
- return state->OnStartOpenConnection(callback);
-}
-
-int WebSocketThrottle::OnRead(SocketStream* socket,
- const char* data, int len,
- CompletionCallback* callback) {
- WebSocketState* state = static_cast<WebSocketState*>(
- socket->GetUserData(WebSocketState::kKeyName));
- // If no state, handshake was already completed. Do nothing.
- if (!state)
- return OK;
-
- int result = state->OnRead(data, len, callback);
- if (state->HandshakeFinished()) {
- RemoveFromQueue(socket, state);
- WakeupSocketIfNecessary();
- }
- return result;
-}
-
-int WebSocketThrottle::OnWrite(SocketStream* socket,
- const char* data, int len,
- CompletionCallback* callback) {
- // Do nothing.
- return OK;
-}
-
-void WebSocketThrottle::OnClose(SocketStream* socket) {
- WebSocketState* state = static_cast<WebSocketState*>(
- socket->GetUserData(WebSocketState::kKeyName));
- if (!state)
- return;
- RemoveFromQueue(socket, state);
- WakeupSocketIfNecessary();
-}
-
-void WebSocketThrottle::PutInQueue(SocketStream* socket,
- WebSocketState* state) {
- socket->SetUserData(WebSocketState::kKeyName, state);
- queue_.push_back(state);
- const AddressList& address_list = socket->address_list();
+void WebSocketThrottle::PutInQueue(WebSocketJob* job) {
+ queue_.push_back(job);
+ const AddressList& address_list = job->address_list();
+ base::hash_set<std::string> address_set;
for (const struct addrinfo* addrinfo = address_list.head();
addrinfo != NULL;
addrinfo = addrinfo->ai_next) {
std::string addrkey = AddrinfoToHashkey(addrinfo);
+
+ // If |addrkey| is already processed, don't do it again.
+ if (address_set.find(addrkey) != address_set.end())
+ continue;
+ address_set.insert(addrkey);
+
ConnectingAddressMap::iterator iter = addr_map_.find(addrkey);
if (iter == addr_map_.end()) {
ConnectingQueue* queue = new ConnectingQueue();
- queue->push_back(state);
+ queue->push_back(job);
addr_map_[addrkey] = queue;
} else {
- iter->second->push_back(state);
- state->SetWaiting();
+ iter->second->push_back(job);
+ job->SetWaiting();
+ DLOG(INFO) << "Waiting on " << addrkey;
}
}
}
-void WebSocketThrottle::RemoveFromQueue(SocketStream* socket,
- WebSocketState* state) {
- const AddressList& address_list = socket->address_list();
+void WebSocketThrottle::RemoveFromQueue(WebSocketJob* job) {
+ bool in_queue = false;
+ for (ConnectingQueue::iterator iter = queue_.begin();
+ iter != queue_.end();
+ ++iter) {
+ if (*iter == job) {
+ queue_.erase(iter);
+ in_queue = true;
+ break;
+ }
+ }
+ if (!in_queue)
+ return;
+ const AddressList& address_list = job->address_list();
+ base::hash_set<std::string> address_set;
for (const struct addrinfo* addrinfo = address_list.head();
addrinfo != NULL;
addrinfo = addrinfo->ai_next) {
std::string addrkey = AddrinfoToHashkey(addrinfo);
+ // If |addrkey| is already processed, don't do it again.
+ if (address_set.find(addrkey) != address_set.end())
+ continue;
+ address_set.insert(addrkey);
+
ConnectingAddressMap::iterator iter = addr_map_.find(addrkey);
DCHECK(iter != addr_map_.end());
+
ConnectingQueue* queue = iter->second;
- DCHECK(state == queue->front());
- queue->pop_front();
+ // Job may not be front of queue when job is closed early while waiting.
+ for (ConnectingQueue::iterator iter = queue->begin();
+ iter != queue->end();
+ ++iter) {
+ if (*iter == job) {
+ queue->erase(iter);
+ break;
+ }
+ }
if (queue->empty()) {
delete queue;
addr_map_.erase(iter);
}
}
- for (ConnectingQueue::iterator iter = queue_.begin();
- iter != queue_.end();
- ++iter) {
- if (*iter == state) {
- queue_.erase(iter);
- break;
- }
- }
- socket->SetUserData(WebSocketState::kKeyName, NULL);
}
void WebSocketThrottle::WakeupSocketIfNecessary() {
for (ConnectingQueue::iterator iter = queue_.begin();
iter != queue_.end();
++iter) {
- WebSocketState* state = *iter;
- if (!state->IsWaiting())
+ WebSocketJob* job = *iter;
+ if (!job->IsWaiting())
continue;
bool should_wakeup = true;
- const AddressList& address_list = state->address_list();
+ const AddressList& address_list = job->address_list();
for (const struct addrinfo* addrinfo = address_list.head();
addrinfo != NULL;
addrinfo = addrinfo->ai_next) {
@@ -267,19 +139,14 @@
ConnectingAddressMap::iterator iter = addr_map_.find(addrkey);
DCHECK(iter != addr_map_.end());
ConnectingQueue* queue = iter->second;
- if (state != queue->front()) {
+ if (job != queue->front()) {
should_wakeup = false;
break;
}
}
if (should_wakeup)
- state->Wakeup();
+ job->Wakeup();
}
}
-/* static */
-void WebSocketThrottle::Init() {
- Singleton<WebSocketThrottle>::get();
-}
-
} // namespace net
diff --git a/net/websockets/websocket_throttle.h b/net/websockets/websocket_throttle.h
index 279aea2..d05b246 100644
--- a/net/websockets/websocket_throttle.h
+++ b/net/websockets/websocket_throttle.h
@@ -5,12 +5,17 @@
#ifndef NET_WEBSOCKETS_WEBSOCKET_THROTTLE_H_
#define NET_WEBSOCKETS_WEBSOCKET_THROTTLE_H_
+#include <deque>
+#include <string>
+
#include "base/hash_tables.h"
#include "base/singleton.h"
-#include "net/socket_stream/socket_stream_throttle.h"
namespace net {
+class SocketStream;
+class WebSocketJob;
+
// SocketStreamThrottle for WebSocket protocol.
// Implements the client-side requirements in the spec.
// http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol
@@ -19,35 +24,16 @@
// remote host (IP address) identified by /host/, even if known by
// another name, wait until that connection has been established or
// for that connection to have failed.
-class WebSocketThrottle : public SocketStreamThrottle {
+class WebSocketThrottle {
public:
- virtual int OnStartOpenConnection(SocketStream* socket,
- CompletionCallback* callback);
- virtual int OnRead(SocketStream* socket, const char* data, int len,
- CompletionCallback* callback);
- virtual int OnWrite(SocketStream* socket, const char* data, int len,
- CompletionCallback* callback);
- virtual void OnClose(SocketStream* socket);
+ // Puts |job| in |queue_| and queues for the destination addresses
+ // of |job|.
+ // If other job is using the same destination address, set |job| waiting.
+ void PutInQueue(WebSocketJob* job);
- static void Init();
-
- private:
- class WebSocketState;
- typedef std::deque<WebSocketState*> ConnectingQueue;
- typedef base::hash_map<std::string, ConnectingQueue*> ConnectingAddressMap;
-
- WebSocketThrottle();
- virtual ~WebSocketThrottle();
- friend struct DefaultSingletonTraits<WebSocketThrottle>;
-
- // Puts |socket| in |queue_| and queues for the destination addresses
- // of |socket|. Also sets |state| as UserData of |socket|.
- // If other socket is using the same destination address, set |state| waiting.
- void PutInQueue(SocketStream* socket, WebSocketState* state);
-
- // Removes |socket| from |queue_| and queues for the destination addresses
- // of |socket|. Also releases |state| from UserData of |socket|.
- void RemoveFromQueue(SocketStream* socket, WebSocketState* state);
+ // Removes |job| from |queue_| and queues for the destination addresses
+ // of |job|.
+ void RemoveFromQueue(WebSocketJob* job);
// Checks sockets waiting in |queue_| and check the socket is the front of
// every queue for the destination addresses of |socket|.
@@ -55,6 +41,14 @@
// the socket.
void WakeupSocketIfNecessary();
+ private:
+ typedef std::deque<WebSocketJob*> ConnectingQueue;
+ typedef base::hash_map<std::string, ConnectingQueue*> ConnectingAddressMap;
+
+ WebSocketThrottle();
+ virtual ~WebSocketThrottle();
+ friend struct DefaultSingletonTraits<WebSocketThrottle>;
+
// Key: string of host's address. Value: queue of sockets for the address.
ConnectingAddressMap addr_map_;
diff --git a/net/websockets/websocket_throttle_unittest.cc b/net/websockets/websocket_throttle_unittest.cc
index 55276e9..6d8c619 100644
--- a/net/websockets/websocket_throttle_unittest.cc
+++ b/net/websockets/websocket_throttle_unittest.cc
@@ -10,6 +10,7 @@
#include "net/base/sys_addrinfo.h"
#include "net/base/test_completion_callback.h"
#include "net/socket_stream/socket_stream.h"
+#include "net/websockets/websocket_job.h"
#include "net/websockets/websocket_throttle.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
@@ -60,100 +61,248 @@
}
}
- static void SetAddressList(SocketStream* socket, struct addrinfo* head) {
+ static void MockSocketStreamConnect(
+ SocketStream* socket, struct addrinfo* head) {
socket->CopyAddrInfo(head);
+ // In SocketStream::Connect(), it adds reference to socket, which is
+ // balanced with SocketStream::Finish() that is finally called from
+ // SocketStream::Close() or SocketStream::DetachDelegate(), when
+ // next_state_ is not STATE_NONE.
+ // If next_state_ is STATE_NONE, SocketStream::Close() or
+ // SocketStream::DetachDelegate() won't call SocketStream::Finish(),
+ // so Release() won't be called. Thus, we don't need socket->AddRef()
+ // here.
+ DCHECK_EQ(socket->next_state_, SocketStream::STATE_NONE);
}
};
TEST_F(WebSocketThrottleTest, Throttle) {
- WebSocketThrottle::Init();
DummySocketStreamDelegate delegate;
- WebSocketThrottle* throttle = Singleton<WebSocketThrottle>::get();
-
- EXPECT_EQ(throttle,
- SocketStreamThrottle::GetSocketStreamThrottleForScheme("ws"));
- EXPECT_EQ(throttle,
- SocketStreamThrottle::GetSocketStreamThrottleForScheme("wss"));
-
// For host1: 1.2.3.4, 1.2.3.5, 1.2.3.6
struct addrinfo* addr = AddAddr(1, 2, 3, 4, NULL);
addr = AddAddr(1, 2, 3, 5, addr);
addr = AddAddr(1, 2, 3, 6, addr);
+ scoped_refptr<WebSocketJob> w1 = new WebSocketJob(&delegate);
scoped_refptr<SocketStream> s1 =
- new SocketStream(GURL("ws://host1/"), &delegate);
- WebSocketThrottleTest::SetAddressList(s1, addr);
+ new SocketStream(GURL("ws://host1/"), w1.get());
+ w1->InitSocketStream(s1.get());
+ WebSocketThrottleTest::MockSocketStreamConnect(s1, addr);
DeleteAddrInfo(addr);
+ DLOG(INFO) << "socket1";
TestCompletionCallback callback_s1;
- EXPECT_EQ(OK, throttle->OnStartOpenConnection(s1, &callback_s1));
+ // Trying to open connection to host1 will start without wait.
+ EXPECT_EQ(OK, w1->OnStartOpenConnection(s1, &callback_s1));
+
+ // Now connecting to host1, so waiting queue looks like
+ // Address | head -> tail
+ // 1.2.3.4 | w1
+ // 1.2.3.5 | w1
+ // 1.2.3.6 | w1
// For host2: 1.2.3.4
addr = AddAddr(1, 2, 3, 4, NULL);
+ scoped_refptr<WebSocketJob> w2 = new WebSocketJob(&delegate);
scoped_refptr<SocketStream> s2 =
- new SocketStream(GURL("ws://host2/"), &delegate);
- WebSocketThrottleTest::SetAddressList(s2, addr);
+ new SocketStream(GURL("ws://host2/"), w2.get());
+ w2->InitSocketStream(s2.get());
+ WebSocketThrottleTest::MockSocketStreamConnect(s2, addr);
DeleteAddrInfo(addr);
+ DLOG(INFO) << "socket2";
TestCompletionCallback callback_s2;
- EXPECT_EQ(ERR_IO_PENDING, throttle->OnStartOpenConnection(s2, &callback_s2));
+ // Trying to open connection to host2 will wait for w1.
+ EXPECT_EQ(ERR_IO_PENDING, w2->OnStartOpenConnection(s2, &callback_s2));
+ // Now waiting queue looks like
+ // Address | head -> tail
+ // 1.2.3.4 | w1 w2
+ // 1.2.3.5 | w1
+ // 1.2.3.6 | w1
// For host3: 1.2.3.5
addr = AddAddr(1, 2, 3, 5, NULL);
+ scoped_refptr<WebSocketJob> w3 = new WebSocketJob(&delegate);
scoped_refptr<SocketStream> s3 =
- new SocketStream(GURL("ws://host3/"), &delegate);
- WebSocketThrottleTest::SetAddressList(s3, addr);
+ new SocketStream(GURL("ws://host3/"), w3.get());
+ w3->InitSocketStream(s3.get());
+ WebSocketThrottleTest::MockSocketStreamConnect(s3, addr);
DeleteAddrInfo(addr);
+ DLOG(INFO) << "socket3";
TestCompletionCallback callback_s3;
- EXPECT_EQ(ERR_IO_PENDING, throttle->OnStartOpenConnection(s3, &callback_s3));
+ // Trying to open connection to host3 will wait for w1.
+ EXPECT_EQ(ERR_IO_PENDING, w3->OnStartOpenConnection(s3, &callback_s3));
+ // Address | head -> tail
+ // 1.2.3.4 | w1 w2
+ // 1.2.3.5 | w1 w3
+ // 1.2.3.6 | w1
// For host4: 1.2.3.4, 1.2.3.6
addr = AddAddr(1, 2, 3, 4, NULL);
addr = AddAddr(1, 2, 3, 6, addr);
+ scoped_refptr<WebSocketJob> w4 = new WebSocketJob(&delegate);
scoped_refptr<SocketStream> s4 =
- new SocketStream(GURL("ws://host4/"), &delegate);
- WebSocketThrottleTest::SetAddressList(s4, addr);
+ new SocketStream(GURL("ws://host4/"), w4.get());
+ w4->InitSocketStream(s4.get());
+ WebSocketThrottleTest::MockSocketStreamConnect(s4, addr);
DeleteAddrInfo(addr);
+ DLOG(INFO) << "socket4";
TestCompletionCallback callback_s4;
- EXPECT_EQ(ERR_IO_PENDING, throttle->OnStartOpenConnection(s4, &callback_s4));
+ // Trying to open connection to host4 will wait for w1, w2.
+ EXPECT_EQ(ERR_IO_PENDING, w4->OnStartOpenConnection(s4, &callback_s4));
+ // Address | head -> tail
+ // 1.2.3.4 | w1 w2 w4
+ // 1.2.3.5 | w1 w3
+ // 1.2.3.6 | w1 w4
- static const char kHeader[] = "HTTP/1.1 101 Web Socket Protocol\r\n";
- EXPECT_EQ(OK,
- throttle->OnRead(s1.get(), kHeader, sizeof(kHeader) - 1, NULL));
+ // For host5: 1.2.3.6
+ addr = AddAddr(1, 2, 3, 6, NULL);
+ scoped_refptr<WebSocketJob> w5 = new WebSocketJob(&delegate);
+ scoped_refptr<SocketStream> s5 =
+ new SocketStream(GURL("ws://host5/"), w5.get());
+ w5->InitSocketStream(s5.get());
+ WebSocketThrottleTest::MockSocketStreamConnect(s5, addr);
+ DeleteAddrInfo(addr);
+
+ DLOG(INFO) << "socket5";
+ TestCompletionCallback callback_s5;
+ // Trying to open connection to host5 will wait for w1, w4
+ EXPECT_EQ(ERR_IO_PENDING, w5->OnStartOpenConnection(s5, &callback_s5));
+ // Address | head -> tail
+ // 1.2.3.4 | w1 w2 w4
+ // 1.2.3.5 | w1 w3
+ // 1.2.3.6 | w1 w4 w5
+
+ // For host6: 1.2.3.6
+ addr = AddAddr(1, 2, 3, 6, NULL);
+ scoped_refptr<WebSocketJob> w6 = new WebSocketJob(&delegate);
+ scoped_refptr<SocketStream> s6 =
+ new SocketStream(GURL("ws://host6/"), w6.get());
+ w6->InitSocketStream(s6.get());
+ WebSocketThrottleTest::MockSocketStreamConnect(s6, addr);
+ DeleteAddrInfo(addr);
+
+ DLOG(INFO) << "socket6";
+ TestCompletionCallback callback_s6;
+ // Trying to open connection to host6 will wait for w1, w4, w5
+ EXPECT_EQ(ERR_IO_PENDING, w6->OnStartOpenConnection(s6, &callback_s6));
+ // Address | head -> tail
+ // 1.2.3.4 | w1 w2 w4
+ // 1.2.3.5 | w1 w3
+ // 1.2.3.6 | w1 w4 w5 w6
+
+ // Receive partial response on w1, still connecting.
+ DLOG(INFO) << "socket1 1";
+ static const char kHeader[] = "HTTP/1.1 101 WebSocket Protocol\r\n";
+ w1->OnReceivedData(s1.get(), kHeader, sizeof(kHeader) - 1);
EXPECT_FALSE(callback_s2.have_result());
EXPECT_FALSE(callback_s3.have_result());
EXPECT_FALSE(callback_s4.have_result());
+ EXPECT_FALSE(callback_s5.have_result());
+ EXPECT_FALSE(callback_s6.have_result());
+ // Receive rest of handshake response on w1.
+ DLOG(INFO) << "socket1 2";
static const char kHeader2[] =
"Upgrade: WebSocket\r\n"
"Connection: Upgrade\r\n"
- "WebSocket-Origin: http://www.google.com\r\n"
- "WebSocket-Location: ws://websocket.chromium.org\r\n"
- "\r\n";
- EXPECT_EQ(OK,
- throttle->OnRead(s1.get(), kHeader2, sizeof(kHeader2) - 1, NULL));
+ "Sec-WebSocket-Origin: http://www.google.com\r\n"
+ "Sec-WebSocket-Location: ws://websocket.chromium.org\r\n"
+ "\r\n"
+ "8jKS'y:G*Co,Wxa-";
+ w1->OnReceivedData(s1.get(), kHeader2, sizeof(kHeader2) - 1);
MessageLoopForIO::current()->RunAllPending();
+ // Now, w1 is open.
+ EXPECT_EQ(WebSocketJob::OPEN, w1->state());
+ // So, w2 and w3 can start connecting. w4 needs to wait w2 (1.2.3.4)
EXPECT_TRUE(callback_s2.have_result());
EXPECT_TRUE(callback_s3.have_result());
EXPECT_FALSE(callback_s4.have_result());
+ // Address | head -> tail
+ // 1.2.3.4 | w2 w4
+ // 1.2.3.5 | w3
+ // 1.2.3.6 | w4 w5 w6
- throttle->OnClose(s1.get());
+ // Closing s1 doesn't change waiting queue.
+ DLOG(INFO) << "socket1 close";
+ w1->OnClose(s1.get());
MessageLoopForIO::current()->RunAllPending();
EXPECT_FALSE(callback_s4.have_result());
s1->DetachDelegate();
+ // Address | head -> tail
+ // 1.2.3.4 | w2 w4
+ // 1.2.3.5 | w3
+ // 1.2.3.6 | w4 w5 w6
- throttle->OnClose(s2.get());
+ // w5 can close while waiting in queue.
+ DLOG(INFO) << "socket5 close";
+ // w5 close() closes SocketStream that change state to STATE_CLOSE, calls
+ // DoLoop(), so OnClose() callback will be called.
+ w5->OnClose(s5.get());
+ MessageLoopForIO::current()->RunAllPending();
+ EXPECT_FALSE(callback_s4.have_result());
+ // Address | head -> tail
+ // 1.2.3.4 | w2 w4
+ // 1.2.3.5 | w3
+ // 1.2.3.6 | w4 w6
+ s5->DetachDelegate();
+
+ // w6 close abnormally (e.g. renderer finishes) while waiting in queue.
+ DLOG(INFO) << "socket6 close abnormally";
+ w6->DetachDelegate();
+ MessageLoopForIO::current()->RunAllPending();
+ EXPECT_FALSE(callback_s4.have_result());
+ // Address | head -> tail
+ // 1.2.3.4 | w2 w4
+ // 1.2.3.5 | w3
+ // 1.2.3.6 | w4
+
+ // Closing s2 kicks w4 to start connecting.
+ DLOG(INFO) << "socket2 close";
+ w2->OnClose(s2.get());
MessageLoopForIO::current()->RunAllPending();
EXPECT_TRUE(callback_s4.have_result());
+ // Address | head -> tail
+ // 1.2.3.4 | w4
+ // 1.2.3.5 | w3
+ // 1.2.3.6 | w4
s2->DetachDelegate();
- throttle->OnClose(s3.get());
+ DLOG(INFO) << "socket3 close";
+ w3->OnClose(s3.get());
MessageLoopForIO::current()->RunAllPending();
s3->DetachDelegate();
- throttle->OnClose(s4.get());
+ w4->OnClose(s4.get());
s4->DetachDelegate();
+ DLOG(INFO) << "Done";
+ MessageLoopForIO::current()->RunAllPending();
+}
+
+TEST_F(WebSocketThrottleTest, NoThrottleForDuplicateAddress) {
+ DummySocketStreamDelegate delegate;
+
+ // For localhost: 127.0.0.1, 127.0.0.1
+ struct addrinfo* addr = AddAddr(127, 0, 0, 1, NULL);
+ addr = AddAddr(127, 0, 0, 1, addr);
+ scoped_refptr<WebSocketJob> w1 = new WebSocketJob(&delegate);
+ scoped_refptr<SocketStream> s1 =
+ new SocketStream(GURL("ws://localhost/"), w1.get());
+ w1->InitSocketStream(s1.get());
+ WebSocketThrottleTest::MockSocketStreamConnect(s1, addr);
+ DeleteAddrInfo(addr);
+
+ DLOG(INFO) << "socket1";
+ TestCompletionCallback callback_s1;
+ // Trying to open connection to localhost will start without wait.
+ EXPECT_EQ(OK, w1->OnStartOpenConnection(s1, &callback_s1));
+
+ DLOG(INFO) << "socket1 close";
+ w1->OnClose(s1.get());
+ s1->DetachDelegate();
+ DLOG(INFO) << "Done";
+ MessageLoopForIO::current()->RunAllPending();
}
}
diff --git a/net/websockets/websocket_unittest.cc b/net/websockets/websocket_unittest.cc
index e3c5725..4b65ae9 100644
--- a/net/websockets/websocket_unittest.cc
+++ b/net/websockets/websocket_unittest.cc
@@ -5,7 +5,7 @@
#include <string>
#include <vector>
-#include "base/task.h"
+#include "base/callback.h"
#include "net/base/completion_callback.h"
#include "net/base/io_buffer.h"
#include "net/base/mock_host_resolver.h"
@@ -19,16 +19,18 @@
struct WebSocketEvent {
enum EventType {
- EVENT_OPEN, EVENT_MESSAGE, EVENT_CLOSE,
+ EVENT_OPEN, EVENT_MESSAGE, EVENT_ERROR, EVENT_CLOSE,
};
WebSocketEvent(EventType type, net::WebSocket* websocket,
- const std::string& websocket_msg)
- : event_type(type), socket(websocket), msg(websocket_msg) {}
+ const std::string& websocket_msg, bool websocket_flag)
+ : event_type(type), socket(websocket), msg(websocket_msg),
+ flag(websocket_flag) {}
EventType event_type;
net::WebSocket* socket;
std::string msg;
+ bool flag;
};
class WebSocketEventRecorder : public net::WebSocketDelegate {
@@ -36,11 +38,13 @@
explicit WebSocketEventRecorder(net::CompletionCallback* callback)
: onopen_(NULL),
onmessage_(NULL),
+ onerror_(NULL),
onclose_(NULL),
callback_(callback) {}
virtual ~WebSocketEventRecorder() {
delete onopen_;
delete onmessage_;
+ delete onerror_;
delete onclose_;
}
@@ -56,20 +60,29 @@
virtual void OnOpen(net::WebSocket* socket) {
events_.push_back(
- WebSocketEvent(WebSocketEvent::EVENT_OPEN, socket, std::string()));
+ WebSocketEvent(WebSocketEvent::EVENT_OPEN, socket,
+ std::string(), false));
if (onopen_)
onopen_->Run(&events_.back());
}
virtual void OnMessage(net::WebSocket* socket, const std::string& msg) {
events_.push_back(
- WebSocketEvent(WebSocketEvent::EVENT_MESSAGE, socket, msg));
+ WebSocketEvent(WebSocketEvent::EVENT_MESSAGE, socket, msg, false));
if (onmessage_)
onmessage_->Run(&events_.back());
}
- virtual void OnClose(net::WebSocket* socket) {
+ virtual void OnError(net::WebSocket* socket) {
events_.push_back(
- WebSocketEvent(WebSocketEvent::EVENT_CLOSE, socket, std::string()));
+ WebSocketEvent(WebSocketEvent::EVENT_ERROR, socket,
+ std::string(), false));
+ if (onerror_)
+ onerror_->Run(&events_.back());
+ }
+ virtual void OnClose(net::WebSocket* socket, bool was_clean) {
+ events_.push_back(
+ WebSocketEvent(WebSocketEvent::EVENT_CLOSE, socket,
+ std::string(), was_clean));
if (onclose_)
onclose_->Run(&events_.back());
if (callback_)
@@ -88,6 +101,7 @@
std::vector<WebSocketEvent> events_;
Callback1<WebSocketEvent*>::Type* onopen_;
Callback1<WebSocketEvent*>::Type* onmessage_;
+ Callback1<WebSocketEvent*>::Type* onerror_;
Callback1<WebSocketEvent*>::Type* onclose_;
net::CompletionCallback* callback_;
@@ -148,7 +162,8 @@
"WebSocket-Protocol: sample\r\n"
"\r\n"),
};
- StaticSocketDataProvider data(data_reads, data_writes);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
mock_socket_factory.AddSocketDataProvider(&data);
WebSocket::Request* request(
@@ -156,6 +171,7 @@
"sample",
"http://example.com",
"ws://example.com/demo",
+ WebSocket::DRAFT75,
new TestURLRequestContext()));
request->SetHostResolver(new MockHostResolver());
request->SetClientSocketFactory(&mock_socket_factory);
@@ -208,7 +224,8 @@
"WebSocket-Protocol: sample\r\n"
"\r\n"),
};
- StaticSocketDataProvider data(data_reads, data_writes);
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
mock_socket_factory.AddSocketDataProvider(&data);
WebSocket::Request* request(
@@ -216,6 +233,7 @@
"sample",
"http://example.com",
"ws://example.com/demo",
+ WebSocket::DRAFT75,
new TestURLRequestContext()));
request->SetHostResolver(new MockHostResolver());
request->SetClientSocketFactory(&mock_socket_factory);
@@ -250,6 +268,7 @@
"sample",
"http://example.com",
"ws://example.com/demo",
+ WebSocket::DRAFT75,
new TestURLRequestContext()));
TestCompletionCallback callback;
scoped_ptr<WebSocketEventRecorder> delegate(
@@ -274,7 +293,9 @@
kExpectedRemainingFrame, kExpectedRemainingLength);
// No onmessage event expected.
const std::vector<WebSocketEvent>& events = delegate->GetSeenEvents();
- EXPECT_EQ(0U, events.size());
+ EXPECT_EQ(1U, events.size());
+
+ EXPECT_EQ(WebSocketEvent::EVENT_ERROR, events[0].event_type);
websocket->DetachDelegate();
}
@@ -285,6 +306,7 @@
"sample",
"http://example.com",
"ws://example.com/demo",
+ WebSocket::DRAFT75,
new TestURLRequestContext()));
TestCompletionCallback callback;
scoped_ptr<WebSocketEventRecorder> delegate(
@@ -327,129 +349,4 @@
websocket->DetachDelegate();
}
-TEST(WebSocketRequestTest, is_secure_false) {
- WebSocket::Request request(GURL("ws://example.com/demo"),
- "sample",
- "http://example.com",
- "ws://example.com/demo",
- NULL);
- EXPECT_FALSE(request.is_secure());
-}
-
-TEST(WebSocketRequestTest, is_secure_true) {
- // wss:// is secure.
- WebSocket::Request request(GURL("wss://example.com/demo"),
- "sample",
- "http://example.com",
- "wss://example.com/demo",
- NULL);
- EXPECT_TRUE(request.is_secure());
-}
-
-TEST(WebSocketRequestTest, CreateClientHandshakeMessage_Simple) {
- WebSocket::Request request(GURL("ws://example.com/demo"),
- "sample",
- "http://example.com",
- "ws://example.com/demo",
- NULL);
- EXPECT_EQ("GET /demo HTTP/1.1\r\n"
- "Upgrade: WebSocket\r\n"
- "Connection: Upgrade\r\n"
- "Host: example.com\r\n"
- "Origin: http://example.com\r\n"
- "WebSocket-Protocol: sample\r\n"
- "\r\n",
- request.CreateClientHandshakeMessage());
-}
-
-TEST(WebSocketRequestTest, CreateClientHandshakeMessage_PathAndQuery) {
- WebSocket::Request request(GURL("ws://example.com/Test?q=xxx&p=%20"),
- "sample",
- "http://example.com",
- "ws://example.com/demo",
- NULL);
- // Path and query should be preserved as-is.
- EXPECT_THAT(request.CreateClientHandshakeMessage(),
- testing::HasSubstr("GET /Test?q=xxx&p=%20 HTTP/1.1\r\n"));
-}
-
-TEST(WebSocketRequestTest, CreateClientHandshakeMessage_Host) {
- WebSocket::Request request(GURL("ws://Example.Com/demo"),
- "sample",
- "http://Example.Com",
- "ws://Example.Com/demo",
- NULL);
- // Host should be lowercased
- EXPECT_THAT(request.CreateClientHandshakeMessage(),
- testing::HasSubstr("Host: example.com\r\n"));
- EXPECT_THAT(request.CreateClientHandshakeMessage(),
- testing::HasSubstr("Origin: http://example.com\r\n"));
-}
-
-TEST(WebSocketRequestTest, CreateClientHandshakeMessage_TrimPort80) {
- WebSocket::Request request(GURL("ws://example.com:80/demo"),
- "sample",
- "http://example.com",
- "ws://example.com/demo",
- NULL);
- // :80 should be trimmed as it's the default port for ws://.
- EXPECT_THAT(request.CreateClientHandshakeMessage(),
- testing::HasSubstr("Host: example.com\r\n"));
-}
-
-TEST(WebSocketRequestTest, CreateClientHandshakeMessage_TrimPort443) {
- WebSocket::Request request(GURL("wss://example.com:443/demo"),
- "sample",
- "http://example.com",
- "wss://example.com/demo",
- NULL);
- // :443 should be trimmed as it's the default port for wss://.
- EXPECT_THAT(request.CreateClientHandshakeMessage(),
- testing::HasSubstr("Host: example.com\r\n"));
-}
-
-TEST(WebSocketRequestTest, CreateClientHandshakeMessage_NonDefaultPortForWs) {
- WebSocket::Request request(GURL("ws://example.com:8080/demo"),
- "sample",
- "http://example.com",
- "wss://example.com/demo",
- NULL);
- // :8080 should be preserved as it's not the default port for ws://.
- EXPECT_THAT(request.CreateClientHandshakeMessage(),
- testing::HasSubstr("Host: example.com:8080\r\n"));
-}
-
-TEST(WebSocketRequestTest, CreateClientHandshakeMessage_NonDefaultPortForWss) {
- WebSocket::Request request(GURL("wss://example.com:4443/demo"),
- "sample",
- "http://example.com",
- "wss://example.com/demo",
- NULL);
- // :4443 should be preserved as it's not the default port for wss://.
- EXPECT_THAT(request.CreateClientHandshakeMessage(),
- testing::HasSubstr("Host: example.com:4443\r\n"));
-}
-
-TEST(WebSocketRequestTest, CreateClientHandshakeMessage_WsBut443) {
- WebSocket::Request request(GURL("ws://example.com:443/demo"),
- "sample",
- "http://example.com",
- "ws://example.com/demo",
- NULL);
- // :443 should be preserved as it's not the default port for ws://.
- EXPECT_THAT(request.CreateClientHandshakeMessage(),
- testing::HasSubstr("Host: example.com:443\r\n"));
-}
-
-TEST(WebSocketRequestTest, CreateClientHandshakeMessage_WssBut80) {
- WebSocket::Request request(GURL("wss://example.com:80/demo"),
- "sample",
- "http://example.com",
- "wss://example.com/demo",
- NULL);
- // :80 should be preserved as it's not the default port for wss://.
- EXPECT_THAT(request.CreateClientHandshakeMessage(),
- testing::HasSubstr("Host: example.com:80\r\n"));
-}
-
} // namespace net