diff options
author | James Cook <james@lindenlab.com> | 2010-05-06 16:29:51 -0700 |
---|---|---|
committer | James Cook <james@lindenlab.com> | 2010-05-06 16:29:51 -0700 |
commit | c0257c7fff8dacc83e69d39afcf522b09bdf28e9 (patch) | |
tree | caadbabd739864deaa43871c111a51dafb0220ae /indra | |
parent | 4fca2fe33013186d269f4f4f7b43d2a36089e539 (diff) |
DEV-49633 fixed, use Cache-Control max-age for display name expiration
We have no C++ libraries for parsing RFC 1123 dates, so parsing "Expires"
headers is risky. max-age delta-seconds is easier to parse and equivalent
for this use. Also added unit tests for max-age parsing. Pair-programmed
with Huseby.
Diffstat (limited to 'indra')
-rw-r--r-- | indra/llmessage/CMakeLists.txt | 1 | ||||
-rw-r--r-- | indra/llmessage/llavatarnamecache.cpp | 113 | ||||
-rw-r--r-- | indra/llmessage/llavatarnamecache.h | 9 | ||||
-rw-r--r-- | indra/llmessage/tests/llavatarnamecache_test.cpp | 109 |
4 files changed, 206 insertions, 26 deletions
diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt index 0d07015f24..1cad0f6d22 100644 --- a/indra/llmessage/CMakeLists.txt +++ b/indra/llmessage/CMakeLists.txt @@ -250,6 +250,7 @@ if (LL_TESTS) "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_llsdmessage_peer.py" ) + LL_ADD_INTEGRATION_TEST(llavatarnamecache "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llhost "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llpartdata "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llxfer_file "" "${test_libs}") diff --git a/indra/llmessage/llavatarnamecache.cpp b/indra/llmessage/llavatarnamecache.cpp index 560d0f48cf..701f3931d0 100644 --- a/indra/llmessage/llavatarnamecache.cpp +++ b/indra/llmessage/llavatarnamecache.cpp @@ -40,6 +40,8 @@ #include "llsd.h" #include "llsdserialize.h" +#include <boost/tokenizer.hpp> + #include <map> #include <set> @@ -185,19 +187,8 @@ public: /*virtual*/ void result(const LLSD& content) { - const F64 DEFAULT_EXPIRES = 24.0 * 60.0 * 60.0; - F64 now = LLFrameTimer::getTotalSeconds(); - - // With no expiration info, default to a day - F64 expires = now + DEFAULT_EXPIRES; - - // Allow the header to override the default - LLSD expires_header = mHeaders["expires"]; - if (expires_header.isDefined()) - { - LLDate expires_date = expires_header.asDate(); - expires = expires_date.secondsSinceEpoch(); - } + // Pull expiration out of headers if available + F64 expires = LLAvatarNameCache::nameExpirationFromHeaders(mHeaders); LLSD agents = content["agents"]; LLSD::array_const_iterator it = agents.beginArray(); @@ -209,19 +200,8 @@ public: LLAvatarName av_name; av_name.fromLLSD(row); - // *TODO: Remove this once People API starts passing "Expires:" - // headers. - // Prefer per-row data for expiration times - LLSD expires_row = row["display_name_expires"]; - if (expires_row.isDefined()) - { - LLDate expires_date = expires_row.asDate(); - av_name.mExpires = expires_date.secondsSinceEpoch(); - } - else - { - av_name.mExpires = expires; - } + // Use expiration time from header + av_name.mExpires = expires; // Some avatars don't have explicit display names set if (av_name.mDisplayName.empty()) @@ -700,3 +680,84 @@ void LLAvatarNameCache::insert(const LLUUID& agent_id, const LLAvatarName& av_na // *TODO: update timestamp if zero? sCache[agent_id] = av_name; } + +F64 LLAvatarNameCache::nameExpirationFromHeaders(LLSD headers) +{ + // With no expiration info, default to a day + const F64 DEFAULT_EXPIRES = 24.0 * 60.0 * 60.0; + F64 now = LLFrameTimer::getTotalSeconds(); + F64 expires = now + DEFAULT_EXPIRES; + + // Allow the header to override the default + LLSD cache_control_header = headers["cache-control"]; + if (cache_control_header.isDefined()) + { + S32 max_age = 0; + std::string cache_control = cache_control_header.asString(); + if (max_age_from_cache_control(cache_control, &max_age)) + { + expires = now + (F64)max_age; + } + } + return expires; +} + +static const std::string MAX_AGE("max-age"); +static const boost::char_separator<char> EQUALS_SEPARATOR("="); +static const boost::char_separator<char> COMMA_SEPARATOR(","); + +bool max_age_from_cache_control(const std::string& cache_control, S32 *max_age) +{ + // Split the string on "," to get a list of directives + typedef boost::tokenizer<boost::char_separator<char> > tokenizer; + tokenizer directives(cache_control, COMMA_SEPARATOR); + + tokenizer::iterator token_it = directives.begin(); + for ( ; token_it != directives.end(); ++token_it) + { + // Tokens may have leading or trailing whitespace + std::string token = *token_it; + LLStringUtil::trim(token); + + if (token.compare(0, MAX_AGE.size(), MAX_AGE) == 0) + { + // ...this token starts with max-age, so let's chop it up by "=" + tokenizer subtokens(token, EQUALS_SEPARATOR); + tokenizer::iterator subtoken_it = subtokens.begin(); + + // Must have a token + if (subtoken_it == subtokens.end()) return false; + std::string subtoken = *subtoken_it; + + // Must exactly equal "max-age" + LLStringUtil::trim(subtoken); + if (subtoken != MAX_AGE) return false; + + // Must have another token + ++subtoken_it; + if (subtoken_it == subtokens.end()) return false; + subtoken = *subtoken_it; + + // Must be a valid integer + // *NOTE: atoi() returns 0 for invalid values, so we have to + // check the string first. + // *TODO: Do servers ever send "0000" for zero? We don't handle it + LLStringUtil::trim(subtoken); + if (subtoken == "0") + { + *max_age = 0; + return true; + } + S32 val = atoi( subtoken.c_str() ); + if (val > 0 && val < S32_MAX) + { + *max_age = val; + return true; + } + return false; + } + } + return false; +} + + diff --git a/indra/llmessage/llavatarnamecache.h b/indra/llmessage/llavatarnamecache.h index 26cecc5ab5..0a8b987b05 100644 --- a/indra/llmessage/llavatarnamecache.h +++ b/indra/llmessage/llavatarnamecache.h @@ -89,6 +89,15 @@ namespace LLAvatarNameCache void fetch(const LLUUID& agent_id); void insert(const LLUUID& agent_id, const LLAvatarName& av_name); + + // Compute name expiration time from HTTP Cache-Control header, + // or return default value, in seconds from epoch. + F64 nameExpirationFromHeaders(LLSD headers); } +// Parse a cache-control header to get the max-age delta-seconds. +// Returns true if header has max-age param and it parses correctly. +// Exported here to ease unit testing. +bool max_age_from_cache_control(const std::string& cache_control, S32 *max_age); + #endif diff --git a/indra/llmessage/tests/llavatarnamecache_test.cpp b/indra/llmessage/tests/llavatarnamecache_test.cpp new file mode 100644 index 0000000000..eeadb703d1 --- /dev/null +++ b/indra/llmessage/tests/llavatarnamecache_test.cpp @@ -0,0 +1,109 @@ +/** + * @file llhost_test.cpp + * @author Adroit + * @date 2007-02 + * @brief llhost test cases. + * + * $LicenseInfo:firstyear=2007&license=viewergpl$ + * + * Copyright (c) 2007-2009, 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://secondlifegrid.net/programs/open_source/licensing/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://secondlifegrid.net/programs/open_source/licensing/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$ + */ + +#include "linden_common.h" + +#include "../llavatarnamecache.h" + +#include "../test/lltut.h" + +namespace tut +{ + struct avatarnamecache_data + { + }; + typedef test_group<avatarnamecache_data> avatarnamecache_test; + typedef avatarnamecache_test::object avatarnamecache_object; + tut::avatarnamecache_test avatarnamecache_testcase("llavatarnamecache"); + + template<> template<> + void avatarnamecache_object::test<1>() + { + bool valid = false; + S32 max_age = 0; + + valid = max_age_from_cache_control("max-age=3600", &max_age); + ensure("typical input valid", valid); + ensure_equals("typical input parsed", max_age, 3600); + + valid = max_age_from_cache_control( + " max-age=600 , no-cache,private=\"stuff\" ", &max_age); + ensure("complex input valid", valid); + ensure_equals("complex input parsed", max_age, 600); + + valid = max_age_from_cache_control( + "no-cache, max-age = 123 ", &max_age); + ensure("complex input 2 valid", valid); + ensure_equals("complex input 2 parsed", max_age, 123); + } + + template<> template<> + void avatarnamecache_object::test<2>() + { + bool valid = false; + S32 max_age = -1; + + valid = max_age_from_cache_control("", &max_age); + ensure("empty input returns invalid", !valid); + ensure_equals("empty input doesn't change val", max_age, -1); + + valid = max_age_from_cache_control("no-cache", &max_age); + ensure("no max-age field returns invalid", !valid); + + valid = max_age_from_cache_control("max", &max_age); + ensure("just 'max' returns invalid", !valid); + + valid = max_age_from_cache_control("max-age", &max_age); + ensure("partial max-age is invalid", !valid); + + valid = max_age_from_cache_control("max-age=", &max_age); + ensure("longer partial max-age is invalid", !valid); + + valid = max_age_from_cache_control("max-age=FOO", &max_age); + ensure("invalid integer max-age is invalid", !valid); + + valid = max_age_from_cache_control("max-age 234", &max_age); + ensure("space separated max-age is invalid", !valid); + + valid = max_age_from_cache_control("max-age=0", &max_age); + ensure("zero max-age is valid", valid); + + // *TODO: Handle "0000" as zero + //valid = max_age_from_cache_control("max-age=0000", &max_age); + //ensure("multi-zero max-age is valid", valid); + + valid = max_age_from_cache_control("max-age=-123", &max_age); + ensure("less than zero max-age is invalid", !valid); + } +} |