diff options
| -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); +	} +} | 
