From e868159922b97c5f7de4bdd83de65e135d16ff01 Mon Sep 17 00:00:00 2001 From: Monroe Linden Date: Wed, 31 Mar 2010 18:51:09 -0700 Subject: Added unit test for LLPluginCookieStore. --- indra/llplugin/tests/llplugincookiestore_test.cpp | 178 ++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 indra/llplugin/tests/llplugincookiestore_test.cpp (limited to 'indra/llplugin/tests') diff --git a/indra/llplugin/tests/llplugincookiestore_test.cpp b/indra/llplugin/tests/llplugincookiestore_test.cpp new file mode 100644 index 0000000000..e3e8ac9804 --- /dev/null +++ b/indra/llplugin/tests/llplugincookiestore_test.cpp @@ -0,0 +1,178 @@ +#include "linden_common.h" +#include "../test/lltut.h" + +#include "../llplugincookiestore.h" + + +namespace tut +{ + // Main Setup + struct LLPluginCookieStoreFixture + { + LLPluginCookieStoreFixture() + { + // We need dates definitively in the past and the future to properly test cookie expiration. + LLDate now = LLDate::now(); + LLDate past(now.secondsSinceEpoch() - (60.0 * 60.0 * 24.0)); // 1 day in the past + LLDate future(now.secondsSinceEpoch() + (60.0 * 60.0 * 24.0)); // 1 day in the future + + mPastString = past.asRFC1123(); + mFutureString = future.asRFC1123(); + } + + std::string mPastString; + std::string mFutureString; + LLPluginCookieStore mCookieStore; + + // List of cookies used for validation + std::list mCookies; + + // This sets up mCookies from a string returned by one of the functions in LLPluginCookieStore + void setCookies(const std::string &cookies) + { + mCookies.clear(); + std::string::size_type start = 0; + + while(start != std::string::npos) + { + std::string::size_type end = cookies.find_first_of("\r\n", start); + if(end > start) + { + std::string line(cookies, start, end - start); + if(line.find_first_not_of("\r\n\t ") != std::string::npos) + { + // The line has some non-whitespace characters. Save it to the list. + mCookies.push_back(std::string(cookies, start, end - start)); + } + } + start = cookies.find_first_not_of("\r\n ", end); + } + } + + // This ensures that a cookie matching the one passed is in the list. + void ensureCookie(const std::string &cookie) + { + std::list::iterator iter; + for(iter = mCookies.begin(); iter != mCookies.end(); iter++) + { + if(*iter == cookie) + { + // Found the cookie + // TODO: this should do a smarter equality comparison on the two cookies, instead of just a string compare. + return; + } + } + + // Didn't find this cookie + std::string message = "cookie not found: "; + message += cookie; + ensure(message, false); + } + + // This ensures that the number of cookies in the list matches what's expected. + void ensureSize(const std::string &message, size_t size) + { + if(mCookies.size() != size) + { + std::stringstream full_message; + + full_message << message << " (expected " << size << ", actual " << mCookies.size() << ")"; + ensure(full_message.str(), false); + } + } + }; + + typedef test_group factory; + typedef factory::object object; + factory tf("LLPluginCookieStore test"); + + // Tests + template<> template<> + void object::test<1>() + { + // Test 1: cookie uniqueness and update lists. + // Valid, distinct cookies: + + std::string cookie01 = "cookieA=value; domain=example.com; path=/"; + std::string cookie02 = "cookieB=value; domain=example.com; path=/"; // different name + std::string cookie03 = "cookieA=value; domain=foo.example.com; path=/"; // different domain + std::string cookie04 = "cookieA=value; domain=example.com; path=/bar/"; // different path + std::string cookie05 = "cookieC; domain=example.com; path=/"; // empty value + std::string cookie06 = "cookieD=value; domain=example.com; path=/; expires="; // different name, persistent cookie + cookie06 += mFutureString; + + mCookieStore.setCookies(cookie01); + mCookieStore.setCookies(cookie02); + mCookieStore.setCookies(cookie03); + mCookieStore.setCookies(cookie04); + mCookieStore.setCookies(cookie05); + mCookieStore.setCookies(cookie06); + + // Invalid cookies (these will get parse errors and not be added to the store) + + std::string badcookie01 = "cookieD=value; domain=example.com; path=/; foo=bar"; // invalid field name + std::string badcookie02 = "cookieE=value; path=/"; // no domain + + mCookieStore.setCookies(badcookie01); + mCookieStore.setCookies(badcookie02); + + // All cookies added so far should have been marked as "changed" + setCookies(mCookieStore.getChangedCookies()); + ensureSize("count of changed cookies", 6); + ensureCookie(cookie01); + ensureCookie(cookie02); + ensureCookie(cookie03); + ensureCookie(cookie04); + ensureCookie(cookie05); + ensureCookie(cookie06); + + // Save off the current state of the cookie store (we'll restore it later) + std::string savedCookies = mCookieStore.getAllCookies(); + + // Test replacing cookies + std::string cookie01a = "cookieA=newvalue; domain=example.com; path=/"; // updated value + std::string cookie02a = "cookieB=newvalue; domain=example.com; path=/; expires="; // remove cookie (by setting an expire date in the past) + cookie02a += mPastString; + + mCookieStore.setCookies(cookie01a); + mCookieStore.setCookies(cookie02a); + + // test for getting changed cookies + setCookies(mCookieStore.getChangedCookies()); + ensureSize("count of updated cookies", 2); + ensureCookie(cookie01a); + ensureCookie(cookie02a); + + // and for the state of the store after getting changed cookies + setCookies(mCookieStore.getAllCookies()); + ensureSize("count of valid cookies", 5); + ensureCookie(cookie01a); + ensureCookie(cookie03); + ensureCookie(cookie04); + ensureCookie(cookie05); + ensureCookie(cookie06); + + // Check that only the persistent cookie is returned here + setCookies(mCookieStore.getPersistentCookies()); + ensureSize("count of persistent cookies", 1); + ensureCookie(cookie06); + + // Restore the cookie store to a previous state and verify + mCookieStore.setAllCookies(savedCookies); + + // Since setAllCookies defaults to not marking cookies as changed, this list should be empty. + setCookies(mCookieStore.getChangedCookies()); + ensureSize("count of changed cookies after restore", 0); + + // Verify that the restore worked as it should have. + setCookies(mCookieStore.getAllCookies()); + ensureSize("count of restored cookies", 6); + ensureCookie(cookie01); + ensureCookie(cookie02); + ensureCookie(cookie03); + ensureCookie(cookie04); + ensureCookie(cookie05); + ensureCookie(cookie06); + } + +} -- cgit v1.3 From 68870a1f59c11a173353698b994f4540b214d57f Mon Sep 17 00:00:00 2001 From: Monroe Linden Date: Wed, 31 Mar 2010 18:53:50 -0700 Subject: Added copyright header to new unit test. --- indra/llplugin/tests/llplugincookiestore_test.cpp | 33 +++++++++++++++++++++++ 1 file changed, 33 insertions(+) (limited to 'indra/llplugin/tests') diff --git a/indra/llplugin/tests/llplugincookiestore_test.cpp b/indra/llplugin/tests/llplugincookiestore_test.cpp index e3e8ac9804..020d9c1977 100644 --- a/indra/llplugin/tests/llplugincookiestore_test.cpp +++ b/indra/llplugin/tests/llplugincookiestore_test.cpp @@ -1,3 +1,36 @@ +/** + * @file llplugincookiestore_test.cpp + * @brief Unit tests for LLPluginCookieStore. + * + * @cond + * $LicenseInfo:firstyear=2010&license=viewergpl$ + * + * Copyright (c) 2010, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + * @endcond + */ + #include "linden_common.h" #include "../test/lltut.h" -- cgit v1.3 From 2ba90ca87174a6f29ae467b4677e4876cd113e8f Mon Sep 17 00:00:00 2001 From: Monroe Linden Date: Wed, 7 Apr 2010 18:15:56 -0700 Subject: Fix for EXT-6756: google apps auth doesn't work right with shared media cookies Added "HttpOnly" to the allowed field names in LLPluginCookieStore::Cookie::parse(). (This was the actual cause of the failure -- cookies with this field in them were silently failing to parse.) Added some LL_WARNS logging on this sort of cookie parse failure, which will make similar problems much easier to track down in future. Also added tags to most of the logging in llplugincookiestore.cpp to make it easier to selectively enable it when debugging. Added a cookie with all allowable field names to the unit test. Reviewed by Sam at http://codereview.lindenlab.com/1247014 --- indra/llplugin/llplugincookiestore.cpp | 36 +++++++++++++++-------- indra/llplugin/tests/llplugincookiestore_test.cpp | 2 +- 2 files changed, 24 insertions(+), 14 deletions(-) (limited to 'indra/llplugin/tests') diff --git a/indra/llplugin/llplugincookiestore.cpp b/indra/llplugin/llplugincookiestore.cpp index 85b1e70d78..da770c5f2e 100644 --- a/indra/llplugin/llplugincookiestore.cpp +++ b/indra/llplugin/llplugincookiestore.cpp @@ -99,7 +99,7 @@ bool LLPluginCookieStore::Cookie::parse(const std::string &host) std::string::size_type cookie_end = mCookie.size(); std::string::size_type field_start = 0; - lldebugs << "parsing cookie: " << mCookie << llendl; + LL_DEBUGS("CookieStoreParse") << "parsing cookie: " << mCookie << LL_ENDL; while(field_start < cookie_end) { // Finding the start of the next field requires honoring special quoting rules @@ -167,10 +167,10 @@ bool LLPluginCookieStore::Cookie::parse(const std::string &host) ++value_end; } - lldebugs + LL_DEBUGS("CookieStoreParse") << " field name: \"" << mCookie.substr(name_start, name_end - name_start) << "\", value: \"" << mCookie.substr(value_start, value_end - value_start) << "\"" - << llendl; + << LL_ENDL; // See whether this field is one we know if(first_field) @@ -195,13 +195,13 @@ bool LLPluginCookieStore::Cookie::parse(const std::string &host) #if 1 time_t date = curl_getdate(date_string.c_str(), NULL ); mDate.secondsSinceEpoch((F64)date); - lldebugs << " expire date parsed to: " << mDate.asRFC1123() << llendl; + LL_DEBUGS("CookieStoreParse") << " expire date parsed to: " << mDate.asRFC1123() << LL_ENDL; #else // This doesn't work (rfc1123-format dates cause it to fail) if(!mDate.fromString(date_string)) { // Date failed to parse. - llwarns << "failed to parse cookie's expire date: " << date << llendl; + LL_WARNS("CookieStoreParse") << "failed to parse cookie's expire date: " << date << LL_ENDL; return false; } #endif @@ -232,9 +232,14 @@ bool LLPluginCookieStore::Cookie::parse(const std::string &host) { // We don't care about the value of this field (yet) } + else if(matchName(name_start, name_end, "httponly")) + { + // We don't care about the value of this field (yet) + } else { // An unknown field is a parse failure + LL_WARNS("CookieStoreParse") << "unexpected field name: " << mCookie.substr(name_start, name_end - name_start) << LL_ENDL; return false; } @@ -270,7 +275,7 @@ bool LLPluginCookieStore::Cookie::parse(const std::string &host) mCookie += host; mDomainEnd = mCookie.size(); - lldebugs << "added domain (" << mDomainStart << " to " << mDomainEnd << "), new cookie is: " << mCookie << llendl; + LL_DEBUGS("CookieStoreParse") << "added domain (" << mDomainStart << " to " << mDomainEnd << "), new cookie is: " << mCookie << LL_ENDL; } // If the cookie doesn't have a path, add "/". @@ -288,7 +293,7 @@ bool LLPluginCookieStore::Cookie::parse(const std::string &host) mCookie += "/"; mPathEnd = mCookie.size(); - lldebugs << "added path (" << mPathStart << " to " << mPathEnd << "), new cookie is: " << mCookie << llendl; + LL_DEBUGS("CookieStoreParse") << "added path (" << mPathStart << " to " << mPathEnd << "), new cookie is: " << mCookie << LL_ENDL; } @@ -566,7 +571,7 @@ void LLPluginCookieStore::setOneCookie(const std::string &s, std::string::size_t Cookie *cookie = Cookie::createFromString(s, cookie_start, cookie_end, host); if(cookie) { - lldebugs << "setting cookie: " << cookie->getCookie() << llendl; + LL_DEBUGS("CookieStoreUpdate") << "setting cookie: " << cookie->getCookie() << LL_ENDL; // Create a key for this cookie std::string key = cookie->getKey(); @@ -579,7 +584,7 @@ void LLPluginCookieStore::setOneCookie(const std::string &s, std::string::size_t { // If we're marking cookies as changed, we should keep it anyway since we'll need to send it out with deltas. cookie->setDead(true); - lldebugs << " marking dead" << llendl; + LL_DEBUGS("CookieStoreUpdate") << " marking dead" << LL_ENDL; } else { @@ -590,7 +595,7 @@ void LLPluginCookieStore::setOneCookie(const std::string &s, std::string::size_t delete cookie; cookie = NULL; - lldebugs << " removing" << llendl; + LL_DEBUGS("CookieStoreUpdate") << " removing" << LL_ENDL; } } @@ -607,7 +612,7 @@ void LLPluginCookieStore::setOneCookie(const std::string &s, std::string::size_t delete cookie; cookie = NULL; - lldebugs << " unchanged" << llendl; + LL_DEBUGS("CookieStoreUpdate") << " unchanged" << LL_ENDL; } else { @@ -619,7 +624,7 @@ void LLPluginCookieStore::setOneCookie(const std::string &s, std::string::size_t if(mark_changed) mHasChangedCookies = true; - lldebugs << " replacing" << llendl; + LL_DEBUGS("CookieStoreUpdate") << " replacing" << LL_ENDL; } } else @@ -631,10 +636,15 @@ void LLPluginCookieStore::setOneCookie(const std::string &s, std::string::size_t if(mark_changed) mHasChangedCookies = true; - lldebugs << " adding" << llendl; + LL_DEBUGS("CookieStoreUpdate") << " adding" << LL_ENDL; } } } + else + { + LL_WARNS("CookieStoreUpdate") << "failed to parse cookie: " << s.substr(cookie_start, cookie_end - cookie_start) << LL_ENDL; + } + } void LLPluginCookieStore::clearCookies() diff --git a/indra/llplugin/tests/llplugincookiestore_test.cpp b/indra/llplugin/tests/llplugincookiestore_test.cpp index 020d9c1977..c903464c64 100644 --- a/indra/llplugin/tests/llplugincookiestore_test.cpp +++ b/indra/llplugin/tests/llplugincookiestore_test.cpp @@ -127,7 +127,7 @@ namespace tut // Valid, distinct cookies: std::string cookie01 = "cookieA=value; domain=example.com; path=/"; - std::string cookie02 = "cookieB=value; domain=example.com; path=/"; // different name + std::string cookie02 = "cookieB=value; Domain=example.com; Path=/; Max-Age=10; Secure; Version=1; Comment=foo!; HTTPOnly"; // cookie with every supported field, in different cases. std::string cookie03 = "cookieA=value; domain=foo.example.com; path=/"; // different domain std::string cookie04 = "cookieA=value; domain=example.com; path=/bar/"; // different path std::string cookie05 = "cookieC; domain=example.com; path=/"; // empty value -- cgit v1.3