diff --git a/indra/llcommon/llassettype.cpp b/indra/llcommon/llassettype.cpp
index 3ea742957e8f8f231c97fef493048aae7deddafb..9707a05f162c52119124ca8ecbee8ac5f57d7492 100644
--- a/indra/llcommon/llassettype.cpp
+++ b/indra/llcommon/llassettype.cpp
@@ -45,12 +45,16 @@ struct AssetEntry : public LLDictionaryEntry
 	AssetEntry(const char *desc_name,
 			   const char *type_name, 	// 8 character limit!
 			   const char *human_name, 	// for decoding to human readable form; put any and as many printable characters you want in each one
-			   bool can_link) 			// can you create a link to this type?
+			   bool can_link, 			// can you create a link to this type?
+			   BOOL can_fetch, 			// can you fetch this asset by ID?
+			   BOOL can_know) 			// can you see this asset's ID?
 		:
 		LLDictionaryEntry(desc_name),
 		mTypeName(type_name),
 		mHumanName(human_name),
-		mCanLink(can_link)
+		mCanLink(can_link),
+		mCanFetch(can_fetch),
+		mCanKnow(can_know)
 	{
 		llassert(strlen(mTypeName) <= 8);
 	}
@@ -58,6 +62,8 @@ struct AssetEntry : public LLDictionaryEntry
 	const char *mTypeName;
 	const char *mHumanName;
 	bool mCanLink;
+	BOOL mCanFetch;
+	BOOL mCanKnow;
 };
 
 class LLAssetDictionary : public LLSingleton<LLAssetDictionary>,
@@ -69,34 +75,35 @@ class LLAssetDictionary : public LLSingleton<LLAssetDictionary>,
 
 LLAssetDictionary::LLAssetDictionary()
 {
-	//       												   DESCRIPTION			TYPE NAME	HUMAN NAME			CAN LINK?	
-	//      												  |--------------------|-----------|-------------------|-----------|
-	addEntry(LLAssetType::AT_TEXTURE, 			new AssetEntry("TEXTURE",			"texture",	"texture",			FALSE));
-	addEntry(LLAssetType::AT_SOUND, 			new AssetEntry("SOUND",				"sound",	"sound",			FALSE));
-	addEntry(LLAssetType::AT_CALLINGCARD, 		new AssetEntry("CALLINGCARD",		"callcard",	"calling card",		FALSE));
-	addEntry(LLAssetType::AT_LANDMARK, 			new AssetEntry("LANDMARK",			"landmark",	"landmark",			FALSE));
-	addEntry(LLAssetType::AT_SCRIPT, 			new AssetEntry("SCRIPT",			"script",	"legacy script",	FALSE));
-	addEntry(LLAssetType::AT_CLOTHING, 			new AssetEntry("CLOTHING",			"clothing",	"clothing",			TRUE));
-	addEntry(LLAssetType::AT_OBJECT, 			new AssetEntry("OBJECT",			"object",	"object",			TRUE));
-	addEntry(LLAssetType::AT_NOTECARD, 			new AssetEntry("NOTECARD",			"notecard",	"note card",		FALSE));
-	addEntry(LLAssetType::AT_CATEGORY, 			new AssetEntry("CATEGORY",			"category",	"folder",			TRUE));
-	addEntry(LLAssetType::AT_LSL_TEXT, 			new AssetEntry("LSL_TEXT",			"lsltext",	"lsl2 script",		FALSE));
-	addEntry(LLAssetType::AT_LSL_BYTECODE, 		new AssetEntry("LSL_BYTECODE",		"lslbyte",	"lsl bytecode",		FALSE));
-	addEntry(LLAssetType::AT_TEXTURE_TGA, 		new AssetEntry("TEXTURE_TGA",		"txtr_tga",	"tga texture",		FALSE));
-	addEntry(LLAssetType::AT_BODYPART, 			new AssetEntry("BODYPART",			"bodypart",	"body part",		TRUE));
-	addEntry(LLAssetType::AT_SOUND_WAV, 		new AssetEntry("SOUND_WAV",			"snd_wav",	"sound",			FALSE));
-	addEntry(LLAssetType::AT_IMAGE_TGA, 		new AssetEntry("IMAGE_TGA",			"img_tga",	"targa image",		FALSE));
-	addEntry(LLAssetType::AT_IMAGE_JPEG, 		new AssetEntry("IMAGE_JPEG",		"jpeg",		"jpeg image",		FALSE));
-	addEntry(LLAssetType::AT_ANIMATION, 		new AssetEntry("ANIMATION",			"animatn",	"animation",		FALSE));
-	addEntry(LLAssetType::AT_GESTURE, 			new AssetEntry("GESTURE",			"gesture",	"gesture",			TRUE));
-	addEntry(LLAssetType::AT_SIMSTATE, 			new AssetEntry("SIMSTATE",			"simstate",	"simstate",			FALSE));
-
-	addEntry(LLAssetType::AT_LINK, 				new AssetEntry("LINK",				"link",		"symbolic link",	FALSE));
-	addEntry(LLAssetType::AT_LINK_FOLDER, 		new AssetEntry("FOLDER_LINK",		"link_f", 	"symbolic folder link", FALSE));
-
-	addEntry(LLAssetType::AT_MESH,              new AssetEntry("MESH",              "mesh",     "mesh",             FALSE));
-
-	addEntry(LLAssetType::AT_NONE, 				new AssetEntry("NONE",				"-1",		NULL,		  		FALSE));
+	//       												   DESCRIPTION			TYPE NAME	HUMAN NAME			CAN LINK?   CAN FETCH?  CAN KNOW?	
+	//      												  |--------------------|-----------|-------------------|-----------|-----------|---------|
+	addEntry(LLAssetType::AT_TEXTURE, 			new AssetEntry("TEXTURE",			"texture",	"texture",			FALSE,		FALSE,		TRUE));
+	addEntry(LLAssetType::AT_SOUND, 			new AssetEntry("SOUND",				"sound",	"sound",			FALSE,		TRUE,		TRUE));
+	addEntry(LLAssetType::AT_CALLINGCARD, 		new AssetEntry("CALLINGCARD",		"callcard",	"calling card",		FALSE,		FALSE,		FALSE));
+	addEntry(LLAssetType::AT_LANDMARK, 			new AssetEntry("LANDMARK",			"landmark",	"landmark",			FALSE,		TRUE,		TRUE));
+	addEntry(LLAssetType::AT_SCRIPT, 			new AssetEntry("SCRIPT",			"script",	"legacy script",	FALSE,		FALSE,		FALSE));
+	addEntry(LLAssetType::AT_CLOTHING, 			new AssetEntry("CLOTHING",			"clothing",	"clothing",			TRUE,		TRUE,		TRUE));
+	addEntry(LLAssetType::AT_OBJECT, 			new AssetEntry("OBJECT",			"object",	"object",			TRUE,		FALSE,		FALSE));
+	addEntry(LLAssetType::AT_NOTECARD, 			new AssetEntry("NOTECARD",			"notecard",	"note card",		FALSE,		FALSE,		TRUE));
+	addEntry(LLAssetType::AT_CATEGORY, 			new AssetEntry("CATEGORY",			"category",	"folder",			TRUE,		FALSE,		FALSE));
+	addEntry(LLAssetType::AT_LSL_TEXT, 			new AssetEntry("LSL_TEXT",			"lsltext",	"lsl2 script",		FALSE,		FALSE,		FALSE));
+	addEntry(LLAssetType::AT_LSL_BYTECODE, 		new AssetEntry("LSL_BYTECODE",		"lslbyte",	"lsl bytecode",		FALSE,		FALSE,		FALSE));
+	addEntry(LLAssetType::AT_TEXTURE_TGA, 		new AssetEntry("TEXTURE_TGA",		"txtr_tga",	"tga texture",		FALSE,		FALSE,		FALSE));
+	addEntry(LLAssetType::AT_BODYPART, 			new AssetEntry("BODYPART",			"bodypart",	"body part",		TRUE,		TRUE,		TRUE));
+	addEntry(LLAssetType::AT_SOUND_WAV, 		new AssetEntry("SOUND_WAV",			"snd_wav",	"sound",			FALSE,		FALSE,		FALSE));
+	addEntry(LLAssetType::AT_IMAGE_TGA, 		new AssetEntry("IMAGE_TGA",			"img_tga",	"targa image",		FALSE,		FALSE,		FALSE));
+	addEntry(LLAssetType::AT_IMAGE_JPEG, 		new AssetEntry("IMAGE_JPEG",		"jpeg",		"jpeg image",		FALSE,		FALSE,		FALSE));
+	addEntry(LLAssetType::AT_ANIMATION, 		new AssetEntry("ANIMATION",			"animatn",	"animation",		FALSE,		TRUE,		TRUE));
+	addEntry(LLAssetType::AT_GESTURE, 			new AssetEntry("GESTURE",			"gesture",	"gesture",			TRUE,		TRUE,		TRUE));
+	addEntry(LLAssetType::AT_SIMSTATE, 			new AssetEntry("SIMSTATE",			"simstate",	"simstate",			FALSE,		FALSE,		FALSE));
+
+	addEntry(LLAssetType::AT_LINK, 				new AssetEntry("LINK",				"link",		"sym link",			FALSE,		FALSE,		TRUE));
+	addEntry(LLAssetType::AT_LINK_FOLDER, 		new AssetEntry("FOLDER_LINK",		"link_f", 	"sym folder link",	FALSE,		FALSE,		TRUE));
+
+	addEntry(LLAssetType::AT_MESH,              new AssetEntry("MESH",              "mesh",     "mesh",             FALSE, FALSE, FALSE));
+	
+	addEntry(LLAssetType::AT_NONE, 				new AssetEntry("NONE",				"-1",		NULL,		  		FALSE,		FALSE,		FALSE));
+
 };
 
 // static
@@ -211,13 +218,13 @@ bool LLAssetType::lookupCanLink(EType asset_type)
 
 // static
 // Not adding this to dictionary since we probably will only have these two types
-bool LLAssetType::lookupIsLinkType(EType asset_type)
+BOOL LLAssetType::lookupIsLinkType(EType asset_type)
 {
 	if (asset_type == AT_LINK || asset_type == AT_LINK_FOLDER)
 	{
-		return true;
+		return TRUE;
 	}
-	return false;
+	return FALSE;
 }
 
 // static
@@ -227,3 +234,27 @@ const std::string &LLAssetType::badLookup()
 	return sBadLookup;
 
 }
+
+// static
+BOOL LLAssetType::lookupIsAssetFetchByIDAllowed(EType asset_type)
+{
+	const LLAssetDictionary *dict = LLAssetDictionary::getInstance();
+	const AssetEntry *entry = dict->lookup(asset_type);
+	if (entry)
+	{
+		return entry->mCanFetch;
+	}
+	return FALSE;
+}
+
+// static
+BOOL LLAssetType::lookupIsAssetIDKnowable(EType asset_type)
+{
+	const LLAssetDictionary *dict = LLAssetDictionary::getInstance();
+	const AssetEntry *entry = dict->lookup(asset_type);
+	if (entry)
+	{
+		return entry->mCanKnow;
+	}
+	return FALSE;
+}
diff --git a/indra/llcommon/llassettype.h b/indra/llcommon/llassettype.h
index 3304258000a185e0f3c5ca0b3e879d3b36087c33..22bcf5c4981864b10fd075a7b67bee07995f0aeb 100644
--- a/indra/llcommon/llassettype.h
+++ b/indra/llcommon/llassettype.h
@@ -147,8 +147,11 @@ class LL_COMMON_API LLAssetType
 	static const std::string&	getDesc(EType asset_type);
 
 	static bool 				lookupCanLink(EType asset_type);
-	static bool 				lookupIsLinkType(EType asset_type);
+	static BOOL 				lookupIsLinkType(EType asset_type);
 
+	static BOOL 				lookupIsAssetFetchByIDAllowed(EType asset_type); // the asset allows direct download
+	static BOOL 				lookupIsAssetIDKnowable(EType asset_type); // asset data can be known by the viewer
+	
 	static const std::string&	badLookup(); // error string when a lookup fails
 
 protected:
diff --git a/indra/llcommon/llversionserver.h b/indra/llcommon/llversionserver.h
index 0f1e59a18cce8886fbb562add35ef5b1437d6ee5..e3663544db71565ff43c30ebfc78db26fb22f9d4 100644
--- a/indra/llcommon/llversionserver.h
+++ b/indra/llcommon/llversionserver.h
@@ -36,7 +36,7 @@
 const S32 LL_VERSION_MAJOR = 1;
 const S32 LL_VERSION_MINOR = 31;
 const S32 LL_VERSION_PATCH = 0;
-const S32 LL_VERSION_BUILD = 200030;
+const S32 LL_VERSION_BUILD = 203110;
 
 const char * const LL_CHANNEL = "Second Life Server";
 
diff --git a/indra/llcommon/llversionviewer.h b/indra/llcommon/llversionviewer.h
index 540aea42522a261e2ac1bd007999a97485ec5ed5..3ab4257fabe90f4e5d0dd31aead6095d308c3197 100644
--- a/indra/llcommon/llversionviewer.h
+++ b/indra/llcommon/llversionviewer.h
@@ -36,7 +36,7 @@
 const S32 LL_VERSION_MAJOR = 2;
 const S32 LL_VERSION_MINOR = 0;
 const S32 LL_VERSION_PATCH = 0;
-const S32 LL_VERSION_BUILD = 200030;
+const S32 LL_VERSION_BUILD = 203110;
 
 const char * const LL_CHANNEL = "Second Life Developer";
 
diff --git a/indra/llmessage/llassetstorage.cpp b/indra/llmessage/llassetstorage.cpp
index 047b99c86178b7bebdf93758a2dec1a3d4d23504..e7527b1e7812a2f0e899320741296d5106c55a93 100644
--- a/indra/llmessage/llassetstorage.cpp
+++ b/indra/llmessage/llassetstorage.cpp
@@ -408,6 +408,8 @@ void LLAssetStorage::getAssetData(const LLUUID uuid, LLAssetType::EType type, vo
 {
 	lldebugs << "LLAssetStorage::getAssetData() - " << uuid << "," << LLAssetType::lookup(type) << llendl;
 
+	llinfos << "ASSET_TRACE requesting " << uuid << " type " << LLAssetType::lookup(type) << llendl;
+
 	if (mShutDown)
 	{
 		return; // don't get the asset or do any callbacks, we are shutting down
@@ -471,6 +473,8 @@ void LLAssetStorage::getAssetData(const LLUUID uuid, LLAssetType::EType type, vo
 		// we've already got the file
 		// theoretically, partial files w/o a pending request shouldn't happen
 		// unless there's a weird error
+		llinfos << "ASSET_TRACE asset " << uuid << " found in VFS" << llendl;
+
 		if (callback)
 		{
 			callback(mVFS, uuid, type, user_data, LL_ERR_NOERR, LL_EXSTAT_VFS_CACHED);
@@ -528,6 +532,8 @@ void LLAssetStorage::downloadCompleteCallback(
 	LLAssetType::EType file_type,
 	void* user_data, LLExtStat ext_status)
 {
+	llinfos << "ASSET_TRACE asset " << file_id << " downloadCompleteCallback" << llendl;
+
 	lldebugs << "LLAssetStorage::downloadCompleteCallback() for " << file_id
 		 << "," << LLAssetType::lookup(file_type) << llendl;
 	LLAssetRequest* req = (LLAssetRequest*)user_data;
diff --git a/indra/llmessage/lltransfersourceasset.cpp b/indra/llmessage/lltransfersourceasset.cpp
index fc08ff726174179c538e6c3300a343bad5f7c61a..8c0520687a834cedb9351afad7df9db10981b5bf 100644
--- a/indra/llmessage/lltransfersourceasset.cpp
+++ b/indra/llmessage/lltransfersourceasset.cpp
@@ -60,7 +60,7 @@ void LLTransferSourceAsset::initTransfer()
 		// to the simulator. This is subset of assets we allow to be
 		// simply pulled straight from the asset system.
 		LLUUID* tidp;
-		if(is_asset_fetch_by_id_allowed(mParams.getAssetType()))
+		if(LLAssetType::lookupIsAssetFetchByIDAllowed(mParams.getAssetType()))
 		{
 			tidp = new LLUUID(getID());
 			gAssetStorage->getAssetData(
@@ -258,51 +258,3 @@ BOOL LLTransferSourceParamsAsset::unpackParams(LLDataPacker &dp)
 	return TRUE;
 }
 
-/**
- * Helper functions
- */
-bool is_asset_fetch_by_id_allowed(LLAssetType::EType type)
-{
-	// *FIX: Make this list smaller.
-	bool rv = false;
-	switch(type)
-	{
-		case LLAssetType::AT_SOUND:
-		case LLAssetType::AT_LANDMARK:
-		case LLAssetType::AT_CLOTHING:
-		case LLAssetType::AT_BODYPART:
-		case LLAssetType::AT_ANIMATION:
-		case LLAssetType::AT_GESTURE:
-		case LLAssetType::AT_MESH:
-			rv = true;
-			break;
-		default:
-			break;
-	}
-	return rv;
-}
-
-bool is_asset_id_knowable(LLAssetType::EType type)
-{
-	// *FIX: Make this list smaller.
-	bool rv = false;
-	switch(type)
-	{
-		case LLAssetType::AT_TEXTURE:
-		case LLAssetType::AT_SOUND:
-		case LLAssetType::AT_LANDMARK:
-		case LLAssetType::AT_CLOTHING:
-		case LLAssetType::AT_NOTECARD:
-		case LLAssetType::AT_BODYPART:
-		case LLAssetType::AT_ANIMATION:
-		case LLAssetType::AT_GESTURE:
-		case LLAssetType::AT_LINK:
-		case LLAssetType::AT_LINK_FOLDER:
-	    case LLAssetType::AT_MESH:
-			rv = true;
-			break;
-		default:
-			break;
-	}
-	return rv;
-}
diff --git a/indra/llmessage/lltransfersourceasset.h b/indra/llmessage/lltransfersourceasset.h
index 70b09b6aaf5d682babb1e30dc6d929ebdfbd6ee5..861659565436da6acf805e8bbfe89cbfe256a9bc 100644
--- a/indra/llmessage/lltransfersourceasset.h
+++ b/indra/llmessage/lltransfersourceasset.h
@@ -84,24 +84,4 @@ class LLTransferSourceAsset : public LLTransferSource
 	S32 mCurPos;
 };
 
-/**
- * @brief Quick check to see if the asset allows direct download.
- *
- * This might not be the right place for this function call, but it
- * originally started life inside the LLTransferSourceAsset code.
- * @param type The type of asset.
- * @return Returns true if the asset can be fetched by id.
- */
-bool is_asset_fetch_by_id_allowed(LLAssetType::EType type);
-
-/**
- * @brief Quick check to see if all asset data can be known by the viewer.
- *
- * This might not be the right place for this function call, but it
- * originally started life inside the LLTransferSourceAsset code.
- * @param type The type of asset.
- * @return Returns true if the asset id can be transmitted to the viewer.
- */
-bool is_asset_id_knowable(LLAssetType::EType type);
-
 #endif // LL_LLTRANSFERSOURCEASSET_H
diff --git a/indra/llplugin/CMakeLists.txt b/indra/llplugin/CMakeLists.txt
index def9fcbeaef484654016b37a6e061f36289915e6..441becbae00c5683b4770750fa1805ccf5a71abc 100644
--- a/indra/llplugin/CMakeLists.txt
+++ b/indra/llplugin/CMakeLists.txt
@@ -3,6 +3,7 @@
 project(llplugin)
 
 include(00-Common)
+include(CURL)
 include(LLCommon)
 include(LLImage)
 include(LLMath)
@@ -55,3 +56,19 @@ list(APPEND llplugin_SOURCE_FILES ${llplugin_HEADER_FILES})
 add_library (llplugin ${llplugin_SOURCE_FILES})
 
 add_subdirectory(slplugin)
+
+# Add tests
+include(LLAddBuildTest)
+# UNIT TESTS
+SET(llplugin_TEST_SOURCE_FILES
+  llplugincookiestore.cpp
+  )
+
+# llplugincookiestore has a dependency on curl, so we need to link the curl library into the test.
+set_source_files_properties(
+  llplugincookiestore.cpp
+  PROPERTIES
+    LL_TEST_ADDITIONAL_LIBRARIES "${CURL_LIBRARIES}"
+  )
+
+LL_ADD_PROJECT_UNIT_TESTS(llplugin "${llplugin_TEST_SOURCE_FILES}")
diff --git a/indra/llplugin/llplugincookiestore.cpp b/indra/llplugin/llplugincookiestore.cpp
index 1964b8d78965f5c3c9c64ad904efa5fb908fb015..85b1e70d7870d99827e163e1bee2d0b467b74a4d 100644
--- a/indra/llplugin/llplugincookiestore.cpp
+++ b/indra/llplugin/llplugincookiestore.cpp
@@ -53,7 +53,7 @@ LLPluginCookieStore::~LLPluginCookieStore()
 
 
 LLPluginCookieStore::Cookie::Cookie(const std::string &s, std::string::size_type cookie_start, std::string::size_type cookie_end):
-	mCookie(s, cookie_start, cookie_end),
+	mCookie(s, cookie_start, cookie_end - cookie_start),
 	mNameStart(0), mNameEnd(0),
 	mValueStart(0), mValueEnd(0),
 	mDomainStart(0), mDomainEnd(0),
@@ -62,11 +62,11 @@ LLPluginCookieStore::Cookie::Cookie(const std::string &s, std::string::size_type
 {
 }
 
-LLPluginCookieStore::Cookie *LLPluginCookieStore::Cookie::createFromString(const std::string &s, std::string::size_type cookie_start, std::string::size_type cookie_end)
+LLPluginCookieStore::Cookie *LLPluginCookieStore::Cookie::createFromString(const std::string &s, std::string::size_type cookie_start, std::string::size_type cookie_end, const std::string &host)
 {
 	Cookie *result = new Cookie(s, cookie_start, cookie_end);
 
-	if(!result->parse())
+	if(!result->parse(host))
 	{
 		delete result;
 		result = NULL;
@@ -92,7 +92,7 @@ std::string LLPluginCookieStore::Cookie::getKey() const
 	return result;
 }
 
-bool LLPluginCookieStore::Cookie::parse()
+bool LLPluginCookieStore::Cookie::parse(const std::string &host)
 {
 	bool first_field = true;
 
@@ -248,7 +248,50 @@ bool LLPluginCookieStore::Cookie::parse()
 	// The cookie MUST have a name
 	if(mNameEnd <= mNameStart)
 		return false;
+	
+	// If the cookie doesn't have a domain, add the current host as the domain.
+	if(mDomainEnd <= mDomainStart)
+	{
+		if(host.empty())
+		{
+			// no domain and no current host -- this is a parse failure.
+			return false;
+		}
+		
+		// Figure out whether this cookie ended with a ";" or not...
+		std::string::size_type last_char = mCookie.find_last_not_of(" ");
+		if((last_char != std::string::npos) && (mCookie[last_char] != ';'))
+		{
+			mCookie += ";";
+		}
+		
+		mCookie += " domain=";
+		mDomainStart = mCookie.size();
+		mCookie += host;
+		mDomainEnd = mCookie.size();
+		
+		lldebugs << "added domain (" << mDomainStart << " to " << mDomainEnd << "), new cookie is: " << mCookie << llendl;
+	}
+
+	// If the cookie doesn't have a path, add "/".
+	if(mPathEnd <= mPathStart)
+	{
+		// Figure out whether this cookie ended with a ";" or not...
+		std::string::size_type last_char = mCookie.find_last_not_of(" ");
+		if((last_char != std::string::npos) && (mCookie[last_char] != ';'))
+		{
+			mCookie += ";";
+		}
+		
+		mCookie += " path=";
+		mPathStart = mCookie.size();
+		mCookie += "/";
+		mPathEnd = mCookie.size();
 		
+		lldebugs << "added path (" << mPathStart << " to " << mPathEnd << "), new cookie is: " << mCookie << llendl;
+	}
+	
+	
 	return true;
 }
 
@@ -409,13 +452,29 @@ void LLPluginCookieStore::setCookies(const std::string &cookies, bool mark_chang
 
 	while(start != std::string::npos)
 	{
-		std::string::size_type end = cookies.find('\n', start);
+		std::string::size_type end = cookies.find_first_of("\r\n", start);
 		if(end > start)
 		{
 			// The line is non-empty.  Try to create a cookie from it.
 			setOneCookie(cookies, start, end, mark_changed);
 		}
-		start = cookies.find_first_not_of("\n ", end);
+		start = cookies.find_first_not_of("\r\n ", end);
+	}
+}
+
+void LLPluginCookieStore::setCookiesFromHost(const std::string &cookies, const std::string &host, bool mark_changed)
+{
+	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)
+		{
+			// The line is non-empty.  Try to create a cookie from it.
+			setOneCookie(cookies, start, end, mark_changed, host);
+		}
+		start = cookies.find_first_not_of("\r\n ", end);
 	}
 }
 			
@@ -502,9 +561,9 @@ std::string LLPluginCookieStore::unquoteString(const std::string &s)
 // When deleting with mark_changed set to true, this replaces the existing cookie in the list with an entry that's marked both dead and changed.
 // Some time later when writeChangedCookies() is called with clear_changed set to true, the dead cookie is deleted from the list after being returned, so that the
 // delete operation (in the form of the expired cookie) is passed along.
-void LLPluginCookieStore::setOneCookie(const std::string &s, std::string::size_type cookie_start, std::string::size_type cookie_end, bool mark_changed)
+void LLPluginCookieStore::setOneCookie(const std::string &s, std::string::size_type cookie_start, std::string::size_type cookie_end, bool mark_changed, const std::string &host)
 {
-	Cookie *cookie = Cookie::createFromString(s, cookie_start, cookie_end);
+	Cookie *cookie = Cookie::createFromString(s, cookie_start, cookie_end, host);
 	if(cookie)
 	{
 		lldebugs << "setting cookie: " << cookie->getCookie() << llendl;
diff --git a/indra/llplugin/llplugincookiestore.h b/indra/llplugin/llplugincookiestore.h
index 5250f008b674c551ac381140f2cf13d1c655ce25..a93f0c14f0d72529c72452d990c9be35cebb5d66 100644
--- a/indra/llplugin/llplugincookiestore.h
+++ b/indra/llplugin/llplugincookiestore.h
@@ -66,18 +66,21 @@ class LLPluginCookieStore
 	void setCookies(const std::string &cookies, bool mark_changed = true);
 	void readCookies(std::istream& s, bool mark_changed = true);
 
+	// sets one or more cookies (without reinitializing anything), supplying a hostname the cookies came from -- use when setting a cookie manually
+	void setCookiesFromHost(const std::string &cookies, const std::string &host, bool mark_changed = true);
+
 	// quote or unquote a string as per the definition of 'quoted-string' in rfc2616
 	static std::string quoteString(const std::string &s);
 	static std::string unquoteString(const std::string &s);
 	
 private:
 
-	void setOneCookie(const std::string &s, std::string::size_type cookie_start, std::string::size_type cookie_end, bool mark_changed);
+	void setOneCookie(const std::string &s, std::string::size_type cookie_start, std::string::size_type cookie_end, bool mark_changed, const std::string &host = LLStringUtil::null);
 
 	class Cookie
 	{
 	public:
-		static Cookie *createFromString(const std::string &s, std::string::size_type cookie_start = 0, std::string::size_type cookie_end = std::string::npos);
+		static Cookie *createFromString(const std::string &s, std::string::size_type cookie_start = 0, std::string::size_type cookie_end = std::string::npos, const std::string &host = LLStringUtil::null);
 		
 		// Construct a string from the cookie that uniquely represents it, to be used as a key in a std::map.
 		std::string getKey() const;
@@ -95,7 +98,7 @@ class LLPluginCookieStore
 		
 	private:
 		Cookie(const std::string &s, std::string::size_type cookie_start = 0, std::string::size_type cookie_end = std::string::npos);
-		bool parse();
+		bool parse(const std::string &host);
 		std::string::size_type findFieldEnd(std::string::size_type start = 0, std::string::size_type end = std::string::npos);
 		bool matchName(std::string::size_type start, std::string::size_type end, const char *name);
 		
diff --git a/indra/llplugin/tests/llplugincookiestore_test.cpp b/indra/llplugin/tests/llplugincookiestore_test.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..020d9c1977361957c37030ac6d3a4b92718027b1
--- /dev/null
+++ b/indra/llplugin/tests/llplugincookiestore_test.cpp
@@ -0,0 +1,211 @@
+/** 
+ * @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"
+
+#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<std::string> 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<std::string>::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<LLPluginCookieStoreFixture> 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);
+	}
+
+}
diff --git a/indra/newview/English.lproj/InfoPlist.strings b/indra/newview/English.lproj/InfoPlist.strings
index 879408d6e4f38567bae0d88ce2647706acd9a398..02c3dfc6e0c16d31b7079ce4cccbfa585f9e7dbc 100644
--- a/indra/newview/English.lproj/InfoPlist.strings
+++ b/indra/newview/English.lproj/InfoPlist.strings
@@ -2,6 +2,6 @@
 
 CFBundleName = "Second Life";
 
-CFBundleShortVersionString = "Second Life version 2.0.0.200030";
-CFBundleGetInfoString = "Second Life version 2.0.0.200030, Copyright 2004-2009 Linden Research, Inc.";
+CFBundleShortVersionString = "Second Life version 2.0.0.203110";
+CFBundleGetInfoString = "Second Life version 2.0.0.203110, Copyright 2004-2009 Linden Research, Inc.";
 
diff --git a/indra/newview/Info-SecondLife.plist b/indra/newview/Info-SecondLife.plist
index 38ebb22b8487637bb55c4d2e9dd3680f666a6b22..4cb01a0f338759a56d18093eadfb9a75691592b3 100644
--- a/indra/newview/Info-SecondLife.plist
+++ b/indra/newview/Info-SecondLife.plist
@@ -32,7 +32,7 @@
 		</dict>
 	</array>
 	<key>CFBundleVersion</key>
-	<string>2.0.0.200030</string>
+	<string>2.0.0.203110</string>
 	<key>CSResourcesFileMapped</key>
 	<true/>
 </dict>
diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp
index 9414f2e37b3f553a0ac674a7d38aeccefeb32967..e394b4dfc125495f8780d4d8afa098f9457ceec1 100644
--- a/indra/newview/llinventorybridge.cpp
+++ b/indra/newview/llinventorybridge.cpp
@@ -598,7 +598,7 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id,
 				LLViewerInventoryItem* inv_item = gInventory.getItem(mUUID);
 				if (inv_item)
 				{
-					is_asset_knowable = is_asset_id_knowable(inv_item->getType());
+					is_asset_knowable = LLAssetType::lookupIsAssetIDKnowable(inv_item->getType());
 				}
 				if ( !is_asset_knowable // disable menu item for Inventory items with unknown asset. EXT-5308
 					 || (! ( isItemPermissive() || gAgent.isGodlike() ) )
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index 1a1dffe85c4d042c753ff763302231bec68aa7f7..b5a73a3143fab6d8d1c380fcf2a329c8fab131a8 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -3087,6 +3087,13 @@ bool process_login_success_response()
 		}
 	}
 
+	// Start the process of fetching the OpenID session cookie for this user login
+	std::string openid_url = response["openid_url"];
+	if(!openid_url.empty())
+	{
+		std::string openid_token = response["openid_token"];
+		LLViewerMedia::openIDSetup(openid_url, openid_token);
+	}
 
 	bool success = false;
 	// JC: gesture loading done below, when we have an asset system
diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp
index 6f0d9cdd9510f760ecf213dc2010865961293018..d9fabc7d6414de0e9c1f6a34b9e920a6c0de9522 100644
--- a/indra/newview/llviewermedia.cpp
+++ b/indra/newview/llviewermedia.cpp
@@ -257,7 +257,43 @@ LOG_CLASS(LLMimeDiscoveryResponder);
 		LLViewerMediaImpl *mMediaImpl;
 		bool mInitialized;
 };
+
+class LLViewerMediaOpenIDResponder : public LLHTTPClient::Responder
+{
+LOG_CLASS(LLViewerMediaOpenIDResponder);
+public:
+	LLViewerMediaOpenIDResponder( )
+	{
+	}
+
+	~LLViewerMediaOpenIDResponder()
+	{
+	}
+
+	/* virtual */ void completedHeader(U32 status, const std::string& reason, const LLSD& content)
+	{
+		LL_DEBUGS("MediaAuth") << "status = " << status << ", reason = " << reason << LL_ENDL;
+		LL_DEBUGS("MediaAuth") << content << LL_ENDL;
+		std::string cookie = content["set-cookie"].asString();
+		
+		LLViewerMedia::openIDCookieResponse(cookie);
+	}
+
+	/* virtual */ void completedRaw(
+		U32 status,
+		const std::string& reason,
+		const LLChannelDescriptors& channels,
+		const LLIOPipe::buffer_ptr_t& buffer)
+	{
+		// This is just here to disable the default behavior (attempting to parse the response as llsd).
+		// We don't care about the content of the response, only the set-cookie header.
+	}
+
+};
+
 LLPluginCookieStore *LLViewerMedia::sCookieStore = NULL;
+LLURL LLViewerMedia::sOpenIDURL;
+std::string LLViewerMedia::sOpenIDCookie;
 static LLViewerMedia::impl_list sViewerMediaImplList;
 static LLViewerMedia::impl_id_map sViewerMediaTextureIDMap;
 static LLTimer sMediaCreateTimer;
@@ -1067,7 +1103,8 @@ void LLViewerMedia::clearAllCookies()
 		}
 	}
 	
-	
+	// If we have an OpenID cookie, re-add it to the cookie store.
+	setOpenIDCookie();
 }
 	
 /////////////////////////////////////////////////////////////////////////////////////////
@@ -1168,7 +1205,9 @@ void LLViewerMedia::loadCookieFile()
 			pimpl->mMediaSource->clear_cookies();
 		}
 	}
-
+	
+	// If we have an OpenID cookie, re-add it to the cookie store.
+	setOpenIDCookie();
 }
 
 
@@ -1241,6 +1280,62 @@ void LLViewerMedia::removeCookie(const std::string &name, const std::string &dom
 }
 
 
+/////////////////////////////////////////////////////////////////////////////////////////
+// static
+void LLViewerMedia::setOpenIDCookie()
+{
+	if(!sOpenIDCookie.empty())
+	{
+		getCookieStore()->setCookiesFromHost(sOpenIDCookie, sOpenIDURL.mAuthority);
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// static
+void LLViewerMedia::openIDSetup(const std::string &openid_url, const std::string &openid_token)
+{
+	LL_DEBUGS("MediaAuth") << "url = \"" << openid_url << "\", token = \"" << openid_token << "\"" << LL_ENDL;
+
+	// post the token to the url 
+	// the responder will need to extract the cookie(s).
+
+	// Save the OpenID URL for later -- we may need the host when adding the cookie.
+	sOpenIDURL.init(openid_url.c_str());
+	
+	// We shouldn't ever do this twice, but just in case this code gets repurposed later, clear existing cookies.
+	sOpenIDCookie.clear();
+
+	LLSD headers = LLSD::emptyMap();
+	// Keep LLHTTPClient from adding an "Accept: application/llsd+xml" header
+	headers["Accept"] = "*/*";
+	// and use the expected content-type for a post, instead of the LLHTTPClient::postRaw() default of "application/octet-stream"
+	headers["Content-Type"] = "application/x-www-form-urlencoded";
+
+	// postRaw() takes ownership of the buffer and releases it later, so we need to allocate a new buffer here.
+	size_t size = openid_token.size();
+	U8 *data = new U8[size];
+	memcpy(data, openid_token.data(), size);
+
+	LLHTTPClient::postRaw( 
+		openid_url, 
+		data, 
+		size, 
+		new LLViewerMediaOpenIDResponder(),
+		headers);
+			
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// static
+void LLViewerMedia::openIDCookieResponse(const std::string &cookie)
+{
+	LL_DEBUGS("MediaAuth") << "Cookie received: \"" << cookie << "\"" << LL_ENDL;
+	
+	sOpenIDCookie += cookie;
+
+	setOpenIDCookie();
+}
+
 bool LLViewerMedia::hasInWorldMedia()
 {
 	if (sInWorldMediaDisabled) return false;
diff --git a/indra/newview/llviewermedia.h b/indra/newview/llviewermedia.h
index 10dacf953267ae7f67506c65eea4d0317c0bbf49..e829d7a5b47bb9f15e07d9bee4198f4d5c126d79 100644
--- a/indra/newview/llviewermedia.h
+++ b/indra/newview/llviewermedia.h
@@ -44,6 +44,8 @@
 #include "llpluginclassmedia.h"
 #include "v4color.h"
 
+#include "llurl.h"
+
 class LLViewerMediaImpl;
 class LLUUID;
 class LLViewerMediaTexture;
@@ -152,11 +154,17 @@ class LLViewerMedia
 	static void addCookie(const std::string &name, const std::string &value, const std::string &domain, const LLDate &expires, const std::string &path = std::string("/"), bool secure = false );
 	static void addSessionCookie(const std::string &name, const std::string &value, const std::string &domain, const std::string &path = std::string("/"), bool secure = false );
 	static void removeCookie(const std::string &name, const std::string &domain, const std::string &path = std::string("/") );
+
+	static void openIDSetup(const std::string &openid_url, const std::string &openid_token);
+	static void openIDCookieResponse(const std::string &cookie);
 	
 private:
+	static void setOpenIDCookie();
 	static void onTeleportFinished();
 	
 	static LLPluginCookieStore *sCookieStore;
+	static LLURL sOpenIDURL;
+	static std::string sOpenIDCookie;
 };
 
 // Implementation functions not exported into header file
diff --git a/indra/newview/res/viewerRes.rc b/indra/newview/res/viewerRes.rc
index 7a965cf57e50bc3cf38a666484781fdf6db88996..ecdcacec4638f2c3ef639ca352b48dc3df86b45f 100644
--- a/indra/newview/res/viewerRes.rc
+++ b/indra/newview/res/viewerRes.rc
@@ -129,8 +129,8 @@ TOOLBUY                 CURSOR                  "toolbuy.cur"
 //
 
 VS_VERSION_INFO VERSIONINFO
- FILEVERSION 2,0,0,3422
- PRODUCTVERSION 2,0,0,3422
+ FILEVERSION 2,0,0,203110
+ PRODUCTVERSION 2,0,0,203110
  FILEFLAGSMASK 0x3fL
 #ifdef _DEBUG
  FILEFLAGS 0x1L
@@ -147,12 +147,12 @@ BEGIN
         BEGIN
             VALUE "CompanyName", "Linden Lab"
             VALUE "FileDescription", "Second Life"
-            VALUE "FileVersion", "2.0.0.200030"
+            VALUE "FileVersion", "2.0.0.203110"
             VALUE "InternalName", "Second Life"
             VALUE "LegalCopyright", "Copyright © 2001-2008, Linden Research, Inc."
             VALUE "OriginalFilename", "SecondLife.exe"
             VALUE "ProductName", "Second Life"
-            VALUE "ProductVersion", "2.0.0.200030"
+            VALUE "ProductVersion", "2.0.0.203110"
         END
     END
     BLOCK "VarFileInfo"