diff --git a/etc/message.xml b/etc/message.xml
index c9b9220ba211d21baf84d19ce332c7e0f93f9c08..bfbf1b15bec44ca7e4eb68baa0ef519a468d8552 100644
--- a/etc/message.xml
+++ b/etc/message.xml
@@ -439,6 +439,14 @@
 					<key>trusted-sender</key>
 					<boolean>false</boolean>
 				</map>
+
+				<key>FetchInventoryDescendents</key>
+				<map>
+					<key>flavor</key>
+					<string>llsd</string>
+					<key>trusted-sender</key>
+					<boolean>false</boolean>
+				</map>
 				
 				<key>GroupProposalBallot</key>
 				<map>
diff --git a/indra/llcommon/llsdserialize.cpp b/indra/llcommon/llsdserialize.cpp
index 02152cfe7950ed037f27f67d177d62658f2dfb97..6f4a49180dd86e34af1c944a1aa460b8e3e2cbed 100644
--- a/indra/llcommon/llsdserialize.cpp
+++ b/indra/llcommon/llsdserialize.cpp
@@ -227,7 +227,8 @@ F64 ll_ntohd(F64 netdouble)
  *
  * @param istr The stream to read from.
  * @param value [out] The string which was found.
- * @param max_bytes The maximum possible length of the string.
+ * @param max_bytes The maximum possible length of the string. Passing in
+ * a negative value will skip this check.
  * @return Returns number of bytes read off of the stream. Returns
  * PARSE_FAILURE (-1) on failure.
  */
@@ -251,7 +252,8 @@ int deserialize_string_delim(std::istream& istr, std::string& value, char d);
  * leading the stream.
  * @param value [out] The string which was found.
  * @param d The delimiter to use.
- * @param max_bytes The maximum possible length of the string.
+ * @param max_bytes The maximum possible length of the string. Passing in
+ * a negative value will skip this check.
  * @return Returns number of bytes read off of the stream. Returns
  * PARSE_FAILURE (-1) on failure.
  */
@@ -768,7 +770,7 @@ bool LLSDNotationParser::parseBinary(std::istream& istr, LLSD& data) const
 		// We probably have a valid raw binary stream. determine
 		// the size, and read it.
 		S32 len = strtol(buf + 2, NULL, 0);
-		if(len > mMaxBytesLeft) return false;
+		if(mCheckLimits && (len > mMaxBytesLeft)) return false;
 		std::vector<U8> value;
 		if(len)
 		{
@@ -1043,7 +1045,7 @@ S32 LLSDBinaryParser::doParse(std::istream& istr, LLSD& data) const
 		U32 size_nbo = 0;
 		read(istr, (char*)&size_nbo, sizeof(U32));	/*Flawfinder: ignore*/
 		S32 size = (S32)ntohl(size_nbo);
-		if(size > mMaxBytesLeft)
+		if(mCheckLimits && (size > mMaxBytesLeft))
 		{
 			parse_count = PARSE_FAILURE;
 		}
@@ -1179,7 +1181,7 @@ bool LLSDBinaryParser::parseString(
 	U32 value_nbo = 0;
 	read(istr, (char*)&value_nbo, sizeof(U32));		 /*Flawfinder: ignore*/
 	S32 size = (S32)ntohl(value_nbo);
-	if(size > mMaxBytesLeft) return false;
+	if(mCheckLimits && (size > mMaxBytesLeft)) return false;
 	std::vector<char> buf;
 	if(size)
 	{
@@ -1635,7 +1637,7 @@ int deserialize_string_raw(
 		// the size, and read it.
 		// *FIX: This is memory inefficient.
 		S32 len = strtol(buf + 1, NULL, 0);
-		if(len > max_bytes) return LLSDParser::PARSE_FAILURE;
+		if((max_bytes>0)&&(len>max_bytes)) return LLSDParser::PARSE_FAILURE;
 		std::vector<char> buf;
 		if(len)
 		{
diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp
index ea877bda8482b7f11cde0f5f8bc0099b2ac511bc..02ed0dcfc6461682a1f71f4b32c05584eebb4aa4 100644
--- a/indra/llcommon/llthread.cpp
+++ b/indra/llcommon/llthread.cpp
@@ -31,6 +31,8 @@
 #include "linden_common.h"
 #include "llapr.h"
 
+#include "apr-1/apr_portable.h"
+
 #include "llthread.h"
 
 #include "lltimer.h"
@@ -225,6 +227,11 @@ void LLThread::setQuitting()
 	wake();
 }
 
+// static
+U32 LLThread::currentID()
+{
+	return (U32)apr_os_thread_current();
+}
 
 // static
 void LLThread::yield()
diff --git a/indra/llcommon/llthread.h b/indra/llcommon/llthread.h
index 8106c08835dca3bdd71f890a662e7e53c6fa39e8..a07c64b8fc2139dc7da5f847fa83fd7059bd49e1 100644
--- a/indra/llcommon/llthread.h
+++ b/indra/llcommon/llthread.h
@@ -56,14 +56,14 @@ public:
 	virtual ~LLThread(); // Warning!  You almost NEVER want to destroy a thread unless it's in the STOPPED state.
 	virtual void shutdown(); // stops the thread
 	
-	static void yield(); // Static because it can be called by the main thread, which doesn't have an LLThread data structure.
-
-
 	bool isQuitting() const { return (QUITTING == mStatus); }
 	bool isStopped() const { return (STOPPED == mStatus); }
 	
-	// PAUSE / RESUME functionality. See source code for important usage notes.
+	static U32 currentID(); // Return ID of current thread
+	static void yield(); // Static because it can be called by the main thread, which doesn't have an LLThread data structure.
+	
 public:
+	// PAUSE / RESUME functionality. See source code for important usage notes.
 	// Called from MAIN THREAD.
 	void pause();
 	void unpause();
@@ -127,7 +127,7 @@ protected:
 class LLMutex
 {
 public:
-	LLMutex(apr_pool_t *apr_poolp); // Defaults to global pool, could use the thread pool as well.
+	LLMutex(apr_pool_t *apr_poolp); // NULL pool constructs a new pool for the mutex
 	~LLMutex();
 	
 	void lock();		// blocks
diff --git a/indra/llcommon/llversionserver.h b/indra/llcommon/llversionserver.h
index b7c525be983c28dc55e64538890032f02e636f5e..b9aa71b4dc5376150cc8846eccf1085e1a624f8e 100644
--- a/indra/llcommon/llversionserver.h
+++ b/indra/llcommon/llversionserver.h
@@ -35,7 +35,7 @@
 const S32 LL_VERSION_MAJOR = 1;
 const S32 LL_VERSION_MINOR = 19;
 const S32 LL_VERSION_PATCH = 1;
-const S32 LL_VERSION_BUILD = 80264;
+const S32 LL_VERSION_BUILD = 80913;
 
 const char * const LL_CHANNEL = "Second Life Server";
 
diff --git a/indra/llcrashlogger/llcrashlogger.cpp b/indra/llcrashlogger/llcrashlogger.cpp
index 9ea0cef7be4d1d6505e1a1fa19c550719b65097a..c8b5f06b4949e00dd42e9b2ad17b7a2f9d07bdc7 100755
--- a/indra/llcrashlogger/llcrashlogger.cpp
+++ b/indra/llcrashlogger/llcrashlogger.cpp
@@ -130,14 +130,14 @@ void LLCrashLogger::gatherFiles()
 		LLSDSerialize::fromXML(mDebugLog, debug_log_file);
 		mFileMap["SecondLifeLog"] = mDebugLog["SLLog"].asString();
 		mFileMap["SettingsXml"] = mDebugLog["SettingsFilename"].asString();
-		LLHTTPClient::setCABundle(mDebugLog["CAFilename"].asString());
+		LLCurl::setCAFile(mDebugLog["CAFilename"].asString());
 		llinfos << "Using log file from debug log " << mFileMap["SecondLifeLog"] << llendl;
 		llinfos << "Using settings file from debug log " << mFileMap["SettingsXml"] << llendl;
 	}
 	else
 	{
 		// Figure out the filename of the second life log
-		LLHTTPClient::setCABundle(gDirUtilp->getCAFile());
+		LLCurl::setCAFile(gDirUtilp->getCAFile());
 		mFileMap["SecondLifeLog"] = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"SecondLife.log");
 		mFileMap["SettingsXml"] = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS,"settings.xml");
 	}
diff --git a/indra/llinventory/llinventory.cpp b/indra/llinventory/llinventory.cpp
index 4d411e6b8dd8a72aedcbeb0f8523d98380b4a4a7..272e8ffba269682fddf2ffe3326f23fda30de3b5 100644
--- a/indra/llinventory/llinventory.cpp
+++ b/indra/llinventory/llinventory.cpp
@@ -54,6 +54,7 @@ static const std::string INV_INVENTORY_TYPE_LABEL("inv_type");
 static const std::string INV_NAME_LABEL("name");
 static const std::string INV_DESC_LABEL("desc");
 static const std::string INV_PERMISSIONS_LABEL("permissions");
+static const std::string INV_SHADOW_ID_LABEL("shadow_id");
 static const std::string INV_ASSET_ID_LABEL("asset_id");
 static const std::string INV_SALE_INFO_LABEL("sale_info");
 static const std::string INV_FLAGS_LABEL("flags");
@@ -927,34 +928,34 @@ BOOL LLInventoryItem::exportLegacyStream(std::ostream& output_stream, BOOL inclu
 LLSD LLInventoryItem::asLLSD() const
 {
 	LLSD sd = LLSD();
-	sd["item_id"] = mUUID;
-	sd["parent_id"] = mParentUUID;
-	sd["permissions"] = ll_create_sd_from_permissions(mPermissions);
+	sd[INV_ITEM_ID_LABEL] = mUUID;
+	sd[INV_PARENT_ID_LABEL] = mParentUUID;
+	sd[INV_PERMISSIONS_LABEL] = ll_create_sd_from_permissions(mPermissions);
 
 	U32 mask = mPermissions.getMaskBase();
 	if(((mask & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED)
 		|| (mAssetUUID.isNull()))
 	{
-		sd["asset_id"] = mAssetUUID;
+		sd[INV_ASSET_ID_LABEL] = mAssetUUID;
 	}
 	else
 	{
 		LLUUID shadow_id(mAssetUUID);
 		LLXORCipher cipher(MAGIC_ID.mData, UUID_BYTES);
 		cipher.encrypt(shadow_id.mData, UUID_BYTES);
-		sd["shadow_id"] = shadow_id;
+		sd[INV_SHADOW_ID_LABEL] = shadow_id;
 	}
-	sd["type"] = LLAssetType::lookup(mType);
+	sd[INV_ASSET_TYPE_LABEL] = LLAssetType::lookup(mType);
 	const char* inv_type_str = LLInventoryType::lookup(mInventoryType);
 	if(inv_type_str)
 	{
-		sd["inv_type"] = inv_type_str;
+		sd[INV_INVENTORY_TYPE_LABEL] = inv_type_str;
 	}
-	sd["flags"] = ll_sd_from_U32(mFlags);
-	sd["sale_info"] = mSaleInfo;
-	sd["name"] = mName;
-	sd["desc"] = mDescription;
-	sd["creation_date"] = mCreationDate;
+	sd[INV_FLAGS_LABEL] = ll_sd_from_U32(mFlags);
+	sd[INV_SALE_INFO_LABEL] = mSaleInfo;
+	sd[INV_NAME_LABEL] = mName;
+	sd[INV_DESC_LABEL] = mDescription;
+	sd[INV_CREATION_DATE_LABEL] = mCreationDate;
 
 	return sd;
 }
@@ -1007,7 +1008,7 @@ bool LLInventoryItem::fromLLSD(LLSD& sd)
 			mPermissions.setMaskNext(perm_mask);
 		}
 	}
-	w = "shadow_id";
+	w = INV_SHADOW_ID_LABEL;
 	if (sd.has(w))
 	{
 		mAssetUUID = sd[w];
@@ -1357,6 +1358,19 @@ void LLInventoryCategory::setPreferredType(LLAssetType::EType type)
 	mPreferredType = type;
 }
 
+LLSD LLInventoryCategory::asLLSD() const
+{
+    LLSD sd = LLSD();
+    sd["item_id"] = mUUID;
+    sd["parent_id"] = mParentUUID;
+    S8 type = static_cast<S8>(mPreferredType);
+    sd["type"]      = type;
+    sd["name"] = mName;
+
+    return sd;
+}
+
+
 // virtual
 void LLInventoryCategory::packMessage(LLMessageSystem* msg) const
 {
@@ -1367,6 +1381,36 @@ void LLInventoryCategory::packMessage(LLMessageSystem* msg) const
 	msg->addStringFast(_PREHASH_Name, mName);
 }
 
+bool LLInventoryCategory::fromLLSD(LLSD& sd)
+{
+    std::string w;
+
+    w = INV_ITEM_ID_LABEL;
+    if (sd.has(w))
+    {
+        mUUID = sd[w];
+    }
+    w = INV_PARENT_ID_LABEL;
+    if (sd.has(w))
+    {
+        mParentUUID = sd[w];
+    }
+    w = INV_ASSET_TYPE_LABEL;
+    if (sd.has(w))
+    {
+        S8 type = (U8)sd[w].asInteger();
+        mPreferredType = static_cast<LLAssetType::EType>(type);
+    }
+    w = INV_NAME_LABEL;
+    if (sd.has(w))
+    {
+        mName = sd[w].asString();
+        LLString::replaceNonstandardASCII(mName, ' ');
+        LLString::replaceChar(mName, '|', ' ');
+    }
+    return true;
+}
+
 // virtual
 void LLInventoryCategory::unpackMessage(LLMessageSystem* msg,
 										const char* block,
diff --git a/indra/llinventory/llinventory.h b/indra/llinventory/llinventory.h
index 5ff7a1e72bd176926677885f9a8cbe803a0a4748..db4843d8b54e51df1cb6dcf53a3e5b47f4028c0c 100644
--- a/indra/llinventory/llinventory.h
+++ b/indra/llinventory/llinventory.h
@@ -230,7 +230,6 @@ public:
 	// network ok. It uses a simple crc check which is defeatable, but
 	// we want to detect network mangling somehow.
 	virtual BOOL unpackMessage(LLMessageSystem* msg, const char* block, S32 block_num = 0);
-
 	// file support
 	virtual BOOL importFile(FILE* fp);
 	virtual BOOL exportFile(FILE* fp, BOOL include_asset_key = TRUE) const;
@@ -288,6 +287,9 @@ public:
 	virtual void packMessage(LLMessageSystem* msg) const;
 	virtual void unpackMessage(LLMessageSystem* msg, const char* block, S32 block_num = 0);
 
+	LLSD asLLSD() const;
+	bool fromLLSD(LLSD& sd);
+
 	// file support
 	virtual BOOL importFile(FILE* fp);
 	virtual BOOL exportFile(FILE* fp, BOOL include_asset_key = TRUE) const;
diff --git a/indra/llinventory/llpermissions.cpp b/indra/llinventory/llpermissions.cpp
index 8f9f73d0bdaf4daafed90d94e9bc828c0e418b09..f816d5418168e66157253ced7ba475cc6cf71094 100644
--- a/indra/llinventory/llpermissions.cpp
+++ b/indra/llinventory/llpermissions.cpp
@@ -37,6 +37,7 @@
 // library includes
 #include "message.h"
 #include "metapropertyt.h"
+#include "llsd.h"
 
 ///----------------------------------------------------------------------------
 /// Class LLPermissions
@@ -472,6 +473,25 @@ BOOL LLPermissions::allowOperationBy(PermissionBit op, const LLUUID& requester,
 	return (mMaskEveryone & op);
 }
 
+//
+// LLSD support for HTTP messages.
+//
+LLSD LLPermissions::packMessage() const
+{
+	LLSD result;
+	result["creator-id"]	= mCreator;
+	result["owner-id"]		= mOwner;
+	result["group-id"]		= mGroup;
+
+	result["base-mask"]		=	(S32)mMaskBase;
+	result["owner-mask"]	=	(S32)mMaskOwner;
+	result["group-mask"]	=	(S32)mMaskGroup;
+	result["everyone-mask"]	=	(S32)mMaskEveryone;
+	result["next-owner-mask"]=	(S32)mMaskNextOwner;
+	result["group-owned"]	= (BOOL)mIsGroupOwned;
+	return result;
+}
+
 //
 // Messaging support
 //
@@ -489,6 +509,19 @@ void LLPermissions::packMessage(LLMessageSystem* msg) const
 	msg->addBOOLFast(_PREHASH_GroupOwned, (BOOL)mIsGroupOwned);
 }
 
+void LLPermissions::unpackMessage(LLSD perms)
+{
+	mCreator	=	perms["creator-id"];
+	mOwner	=	perms["owner-id"];
+	mGroup	=	perms["group-id"];
+
+	mMaskBase	=	(U32)perms["base-mask"].asInteger();
+	mMaskOwner	=	(U32)perms["owner-mask"].asInteger();
+	mMaskGroup	=	(U32)perms["group-mask"].asInteger();
+	mMaskEveryone	=	(U32)perms["everyone-mask"].asInteger();
+	mMaskNextOwner	=	(U32)perms["next-owner-mask"].asInteger();
+	mIsGroupOwned	=	perms["group-owned"].asBoolean();
+}
 
 void LLPermissions::unpackMessage(LLMessageSystem* msg, const char* block, S32 block_num)
 {
diff --git a/indra/llinventory/llpermissions.h b/indra/llinventory/llpermissions.h
index 9370d6480b7d8b698900f65ea25916afb08c83b2..36acc438bebf6d58a609f4ebabaec9c7ecfc8249 100644
--- a/indra/llinventory/llpermissions.h
+++ b/indra/llinventory/llpermissions.h
@@ -294,6 +294,9 @@ public:
 	// MISC METHODS and OPERATORS
 	//
 
+	LLSD	packMessage() const;
+	void	unpackMessage(LLSD perms);
+
 	// For messaging system support
 	void	packMessage(LLMessageSystem* msg) const;
 	void	unpackMessage(LLMessageSystem* msg, const char* block, S32 block_num = 0);
diff --git a/indra/llinventory/llsaleinfo.cpp b/indra/llinventory/llsaleinfo.cpp
index d0c7c728f3aca0d7b0292cd5372ff88dcaab9fcf..c268544955e3d271b73c52c5e6672b708ad913c7 100644
--- a/indra/llinventory/llsaleinfo.cpp
+++ b/indra/llinventory/llsaleinfo.cpp
@@ -280,6 +280,17 @@ void LLSaleInfo::setSalePrice(S32 price)
 	mSalePrice = llclamp(mSalePrice, 0, S32_MAX);
 }
 
+LLSD LLSaleInfo::packMessage() const
+{
+	LLSD result;
+
+	U8 sale_type = static_cast<U8>(mSaleType);
+	result["sale-type"]		= (U8)sale_type;
+	result["sale-price"]	= (S32)mSalePrice;
+	//result[_PREHASH_NextOwnerMask] = mNextOwnerPermMask;
+	return result;
+}
+
 void LLSaleInfo::packMessage(LLMessageSystem* msg) const
 {
 	U8 sale_type = static_cast<U8>(mSaleType);
@@ -288,6 +299,16 @@ void LLSaleInfo::packMessage(LLMessageSystem* msg) const
 	//msg->addU32Fast(_PREHASH_NextOwnerMask, mNextOwnerPermMask);
 }
 
+void LLSaleInfo::unpackMessage(LLSD sales)
+{
+	U8 sale_type = (U8)sales["sale-type"].asInteger();
+	mSaleType = static_cast<EForSale>(sale_type);
+
+	mSalePrice = (S32)sales["sale-price"].asInteger();
+	mSalePrice = llclamp(mSalePrice, 0, S32_MAX);
+	//msg->getU32Fast(block, _PREHASH_NextOwnerMask, mNextOwnerPermMask);
+}
+
 void LLSaleInfo::unpackMessage(LLMessageSystem* msg, const char* block)
 {
 	U8 sale_type;
diff --git a/indra/llinventory/llsaleinfo.h b/indra/llinventory/llsaleinfo.h
index e22466bfa887ee72fe157da799be710bd436df5f..1c9db6e3469271efb406cc7737f3fd7bd5237c1a 100644
--- a/indra/llinventory/llsaleinfo.h
+++ b/indra/llinventory/llsaleinfo.h
@@ -103,6 +103,9 @@ public:
 	LLXMLNode *exportFileXML() const;
 	BOOL importXML(LLXMLNode* node);
 
+	LLSD packMessage() const;
+	void unpackMessage(LLSD sales);
+
 	// message serialization
 	void packMessage(LLMessageSystem* msg) const;
 	void unpackMessage(LLMessageSystem* msg, const char* block);
diff --git a/indra/llmessage/llcurl.cpp b/indra/llmessage/llcurl.cpp
index 9b5e6cd4e65ea657cd017f10ef0d86cd50f0c481..8b9a45ff3f406543e95159c3c1c68d72a8683d94 100644
--- a/indra/llmessage/llcurl.cpp
+++ b/indra/llmessage/llcurl.cpp
@@ -2,7 +2,7 @@
  * @file llcurl.h
  * @author Zero / Donovan
  * @date 2006-10-15
- * @brief Curl wrapper
+ * @brief Implementation of wrapper around libcurl.
  *
  * $LicenseInfo:firstyear=2006&license=viewergpl$
  * 
@@ -31,13 +31,29 @@
  * $/LicenseInfo$
  */
 
+#if LL_WINDOWS
+#define SAFE_SSL 1
+#elif LL_DARWIN
+#define SAFE_SSL 1
+#else
+#define SAFE_SSL 1
+#endif
+
 #include "linden_common.h"
 
 #include "llcurl.h"
 
+#include <algorithm>
 #include <iomanip>
+#include <curl/curl.h>
+#if SAFE_SSL
+#include <openssl/crypto.h>
+#endif
 
+#include "llbufferstream.h"
+#include "llstl.h"
 #include "llsdserialize.h"
+#include "llthread.h"
 
 //////////////////////////////////////////////////////////////////////////////
 /*
@@ -55,40 +71,112 @@
 	do this.
  */
 
-using namespace std;
-	
+//////////////////////////////////////////////////////////////////////////////
+
+static const S32 EASY_HANDLE_POOL_SIZE		= 5;
+static const S32 MULTI_PERFORM_CALL_REPEAT	= 5;
+static const S32 CURL_REQUEST_TIMEOUT = 30; // seconds
+static const S32 MAX_ACTIVE_REQUEST_COUNT = 100;
+
+// DEBUG //
+S32 gCurlEasyCount = 0;
+S32 gCurlMultiCount = 0;
+
+//////////////////////////////////////////////////////////////////////////////
+
+//static
+std::vector<LLMutex*> LLCurl::sSSLMutex;
+std::string LLCurl::sCAPath;
+std::string LLCurl::sCAFile;
+
+//static
+void LLCurl::setCAPath(const std::string& path)
+{
+	sCAPath = path;
+}
+
+//static
+void LLCurl::setCAFile(const std::string& file)
+{
+	sCAFile = file;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
 LLCurl::Responder::Responder()
 	: mReferenceCount(0)
 {
 }
+
 LLCurl::Responder::~Responder()
 {
 }
 
 // virtual
-void LLCurl::Responder::error(U32 status, const std::stringstream& content)
+void LLCurl::Responder::error(U32 status, const std::string& reason)
 {
-	llinfos << "LLCurl::Responder::error " << status << ": " << content.str() << llendl;
+	llinfos << status << ": " << reason << llendl;
 }
 
 // virtual
-void LLCurl::Responder::result(const std::stringstream& content)
+void LLCurl::Responder::result(const LLSD& content)
 {
+	llwarns << "Virtual Function not implemented" << llendl;
 }
 
 // virtual
-void LLCurl::Responder::completed(U32 status, const std::stringstream& content)
+void LLCurl::Responder::completedRaw(U32 status, const std::string& reason,
+									 const LLChannelDescriptors& channels,
+									 const LLIOPipe::buffer_ptr_t& buffer)
 {
-	if (200 <= status &&  status < 300)
+	if (isGoodStatus(status))
+	{
+		LLSD content;
+		LLBufferStream istr(channels, buffer.get());
+		LLSDSerialize::fromXML(content, istr);
+/*
+		const S32 parseError = -1;
+		if(LLSDSerialize::fromXML(content, istr) == parseError)
+		{
+			mStatus = 498;
+			mReason = "Client Parse Error";
+		}
+*/
+		completed(status, reason, content);
+	}
+	else if (status == 400)
+	{
+		// Get reason from buffer
+		char tbuf[4096];
+		S32 len = 4096;
+		buffer->readAfter(channels.in(), NULL, (U8*)tbuf, len);
+		tbuf[len] = 0;
+		completed(status, std::string(tbuf), LLSD());
+	}
+	else
+	{
+		completed(status, reason, LLSD());
+	}
+}
+
+// virtual
+void LLCurl::Responder::completed(U32 status, const std::string& reason, const LLSD& content)
+{
+	if (isGoodStatus(status))
 	{
 		result(content);
 	}
 	else
 	{
-		error(status, content);
+		error(status, reason);
 	}
 }
 
+//virtual
+void LLCurl::Responder::completedHeader(U32 status, const std::string& reason, const LLSD& content)
+{
+
+}
 
 namespace boost
 {
@@ -106,226 +194,456 @@ namespace boost
 	}
 };
 
+
 //////////////////////////////////////////////////////////////////////////////
 
-size_t
-curlOutputCallback(void* data, size_t size, size_t nmemb, void* user_data)
+
+class LLCurl::Easy
 {
-	stringstream& output = *(stringstream*)user_data;
+	LOG_CLASS(Easy);
+
+private:
+	Easy();
 	
-	size_t n = size * nmemb;
-	output.write((const char*)data, n);
-	if (!((istream&)output).good()) {
-		std::cerr << "WHAT!?!?!? istream side bad" << std::endl;
-	}
-	if (!((ostream&)output).good()) {
-		std::cerr << "WHAT!?!?!? ostream side bad" << std::endl;
-	}
+public:
+	static Easy* getEasy();
+	~Easy();
 
-	return n;
-}
+	CURL* getCurlHandle() const { return mCurlEasyHandle; }
 
-// Only used if request contained a body (post or put), Not currently implemented.
-// size_t
-// curlRequestCallback(void* data, size_t size, size_t nmemb, void* user_data)
-// {
-// 	stringstream& request = *(stringstream*)user_data;
+	void setErrorBuffer();
+	void setCA();
 	
-// 	size_t n = size * nmemb;
-// 	request.read((char*)data, n);
-// 	return request.gcount();
-// }
-
+	void setopt(CURLoption option, S32 value);
+	// These assume the setter does not free value!
+	void setopt(CURLoption option, void* value);
+	void setopt(CURLoption option, char* value);
+	// Copies the string so that it is gauranteed to stick around
+	void setoptString(CURLoption option, const std::string& value);
+	
+	void slist_append(const char* str);
+	void setHeaders();
+	
+	U32 report(CURLcode);
+	void getTransferInfo(LLCurl::TransferInfo* info);
 
+	void prepRequest(const std::string& url, ResponderPtr, bool post = false);
+	
+	const char* getErrorBuffer();
 
+	std::stringstream& getInput() { return mInput; }
+	std::stringstream& getHeaderOutput() { return mHeaderOutput; }
+	LLIOPipe::buffer_ptr_t& getOutput() { return mOutput; }
+	const LLChannelDescriptors& getChannels() { return mChannels; }
+	
+	void resetState();
 
+private:	
+	CURL*				mCurlEasyHandle;
+	struct curl_slist*	mHeaders;
+	
+	std::stringstream	mRequest;
+	LLChannelDescriptors mChannels;
+	LLIOPipe::buffer_ptr_t mOutput;
+	std::stringstream	mInput;
+	std::stringstream	mHeaderOutput;
+	char				mErrorBuffer[CURL_ERROR_SIZE];
+
+	// Note: char*'s not strings since we pass pointers to curl
+	std::vector<char*>	mStrings;
+	
+	ResponderPtr		mResponder;
+};
 
 LLCurl::Easy::Easy()
+	: mHeaders(NULL),
+	  mCurlEasyHandle(NULL)
 {
-	mHeaders = 0;
-	mHeaders = curl_slist_append(mHeaders, "Connection: keep-alive");
-	mHeaders = curl_slist_append(mHeaders, "Keep-alive: 300");
-	mHeaders = curl_slist_append(mHeaders, "Content-Type: application/xml");
-		// FIXME: shouldn't be there for GET/DELETE
-		// FIXME: should have ACCEPT headers
-		
-	mHandle = curl_easy_init();
+	mErrorBuffer[0] = 0;
+}
+
+LLCurl::Easy* LLCurl::Easy::getEasy()
+{
+	Easy* easy = new Easy();
+	easy->mCurlEasyHandle = curl_easy_init();
+	if (!easy->mCurlEasyHandle)
+	{
+		// this can happen if we have too many open files (fails in c-ares/ares_init.c)
+		llwarns << "curl_multi_init() returned NULL! Easy handles: " << gCurlEasyCount << " Multi handles: " << gCurlMultiCount << llendl;
+		delete easy;
+		return NULL;
+	}
+	++gCurlEasyCount;
+	return easy;
 }
 
 LLCurl::Easy::~Easy()
 {
-	curl_easy_cleanup(mHandle);
+	curl_easy_cleanup(mCurlEasyHandle);
+	--gCurlEasyCount;
 	curl_slist_free_all(mHeaders);
+	for_each(mStrings.begin(), mStrings.end(), DeletePointer());
 }
 
-void
-LLCurl::Easy::get(const string& url, ResponderPtr responder)
+void LLCurl::Easy::resetState()
 {
-	prep(url, responder);
-	curl_easy_setopt(mHandle, CURLOPT_HTTPGET, 1);
+ 	curl_easy_reset(mCurlEasyHandle);
+
+	if (mHeaders)
+	{
+		curl_slist_free_all(mHeaders);
+		mHeaders = NULL;
+	}
+
+	mRequest.str("");
+	mRequest.clear();
+
+	mOutput.reset();
+	
+	mInput.str("");
+	mInput.clear();
+	
+	mErrorBuffer[0] = 0;
+	
+	mHeaderOutput.str("");
+	mHeaderOutput.clear();
 }
 
-void
-LLCurl::Easy::getByteRange(const string& url, S32 offset, S32 length, ResponderPtr responder)
+void LLCurl::Easy::setErrorBuffer()
 {
-	mRange = llformat("Range: bytes=%d-%d", offset,offset+length-1);
-	mHeaders = curl_slist_append(mHeaders, mRange.c_str());
-	prep(url, responder);
-	curl_easy_setopt(mHandle, CURLOPT_HTTPGET, 1);
+	setopt(CURLOPT_ERRORBUFFER, &mErrorBuffer);
 }
 
-void
-LLCurl::Easy::perform()
+const char* LLCurl::Easy::getErrorBuffer()
 {
-	report(curl_easy_perform(mHandle));
+	return mErrorBuffer;
 }
 
-void
-LLCurl::Easy::prep(const std::string& url, ResponderPtr responder)
+void LLCurl::Easy::setCA()
 {
-#if !LL_DARWIN
- 	curl_easy_reset(mHandle); // SJB: doesn't exisit on OSX 10.3.9
-#else
-	// SJB: equivalent? fast?
- 	curl_easy_cleanup(mHandle);
- 	mHandle = curl_easy_init();
-#endif
-	
-	curl_easy_setopt(mHandle, CURLOPT_PRIVATE, this);
-
-//	curl_easy_setopt(mHandle, CURLOPT_VERBOSE, 1); // usefull for debugging
-	curl_easy_setopt(mHandle, CURLOPT_NOSIGNAL, 1);
-	curl_easy_setopt(mHandle, CURLOPT_WRITEFUNCTION, &curlOutputCallback);
-	curl_easy_setopt(mHandle, CURLOPT_WRITEDATA, &mOutput);
-#if 1 // For debug
-	curl_easy_setopt(mHandle, CURLOPT_HEADERFUNCTION, &curlOutputCallback);
-	curl_easy_setopt(mHandle, CURLOPT_HEADERDATA, &mHeaderOutput);
-#endif
-	curl_easy_setopt(mHandle, CURLOPT_ERRORBUFFER, &mErrorBuffer);
-	curl_easy_setopt(mHandle, CURLOPT_ENCODING, "");
-	curl_easy_setopt(mHandle, CURLOPT_SSL_VERIFYPEER, false);
-	curl_easy_setopt(mHandle, CURLOPT_HTTPHEADER, mHeaders);
-
-	mOutput.str("");
-	if (!((istream&)mOutput).good()) {
-		std::cerr << "WHAT!?!?!? istream side bad" << std::endl;
+	if(!sCAPath.empty())
+	{
+		setoptString(CURLOPT_CAPATH, sCAPath);
 	}
-	if (!((ostream&)mOutput).good()) {
-		std::cerr << "WHAT!?!?!? ostream side bad" << std::endl;
+	if(!sCAFile.empty())
+	{
+		setoptString(CURLOPT_CAINFO, sCAFile);
 	}
+}
 
-	mURL = url;
-	curl_easy_setopt(mHandle, CURLOPT_URL, mURL.c_str());
+void LLCurl::Easy::setHeaders()
+{
+	setopt(CURLOPT_HTTPHEADER, mHeaders);
+}
 
-	mResponder = responder;
+void LLCurl::Easy::getTransferInfo(LLCurl::TransferInfo* info)
+{
+	curl_easy_getinfo(mCurlEasyHandle, CURLINFO_SIZE_DOWNLOAD, &info->mSizeDownload);
+	curl_easy_getinfo(mCurlEasyHandle, CURLINFO_TOTAL_TIME, &info->mTotalTime);
+	curl_easy_getinfo(mCurlEasyHandle, CURLINFO_SPEED_DOWNLOAD, &info->mSpeedDownload);
 }
 
-void
-LLCurl::Easy::report(CURLcode code)
+U32 LLCurl::Easy::report(CURLcode code)
 {
-	if (!mResponder) return;
-	
-	long responseCode;
+	U32 responseCode = 0;	
+	std::string responseReason;
 	
 	if (code == CURLE_OK)
 	{
-		curl_easy_getinfo(mHandle, CURLINFO_RESPONSE_CODE, &responseCode);
+		curl_easy_getinfo(mCurlEasyHandle, CURLINFO_RESPONSE_CODE, &responseCode);
+		//*TODO: get reason from first line of mHeaderOutput
 	}
 	else
 	{
 		responseCode = 499;
+		responseReason = strerror(code) + " : " + mErrorBuffer;
+	}
+		
+	if (mResponder)
+	{	
+		mResponder->completedRaw(responseCode, responseReason, mChannels, mOutput);
+		mResponder = NULL;
 	}
 	
-	mResponder->completed(responseCode, mOutput);
-	mResponder = NULL;
+	resetState();
+	return responseCode;
 }
 
+// Note: these all assume the caller tracks the value (i.e. keeps it persistant)
+void LLCurl::Easy::setopt(CURLoption option, S32 value)
+{
+	curl_easy_setopt(mCurlEasyHandle, option, value);
+}
 
+void LLCurl::Easy::setopt(CURLoption option, void* value)
+{
+	curl_easy_setopt(mCurlEasyHandle, option, value);
+}
 
+void LLCurl::Easy::setopt(CURLoption option, char* value)
+{
+	curl_easy_setopt(mCurlEasyHandle, option, value);
+}
 
+// Note: this copies the string so that the caller does not have to keep it around
+void LLCurl::Easy::setoptString(CURLoption option, const std::string& value)
+{
+	char* tstring = new char[value.length()+1];
+	strcpy(tstring, value.c_str());
+	mStrings.push_back(tstring);
+	curl_easy_setopt(mCurlEasyHandle, option, tstring);
+}
 
+void LLCurl::Easy::slist_append(const char* str)
+{
+	mHeaders = curl_slist_append(mHeaders, str);
+}
 
-LLCurl::Multi::Multi()
+size_t curlReadCallback(char* data, size_t size, size_t nmemb, void* user_data)
 {
-	mHandle = curl_multi_init();
+	LLCurl::Easy* easy = (LLCurl::Easy*)user_data;
+	
+	S32 n = size * nmemb;
+	S32 startpos = easy->getInput().tellg();
+	easy->getInput().seekg(0, std::ios::end);
+	S32 endpos = easy->getInput().tellg();
+	easy->getInput().seekg(startpos, std::ios::beg);
+	S32 maxn = endpos - startpos;
+	n = llmin(n, maxn);
+	easy->getInput().read((char*)data, n);
+
+	return n;
 }
 
-LLCurl::Multi::~Multi()
+size_t curlWriteCallback(char* data, size_t size, size_t nmemb, void* user_data)
 {
-	// FIXME: should clean up excess handles in mFreeEasy
-	curl_multi_cleanup(mHandle);
+	LLCurl::Easy* easy = (LLCurl::Easy*)user_data;
+	
+	S32 n = size * nmemb;
+	easy->getOutput()->append(easy->getChannels().in(), (const U8*)data, n);
+
+	return n;
 }
 
+size_t curlHeaderCallback(void* data, size_t size, size_t nmemb, void* user_data)
+{
+	LLCurl::Easy* easy = (LLCurl::Easy*)user_data;
+	
+	size_t n = size * nmemb;
+	easy->getHeaderOutput().write((const char*)data, n);
 
-void
-LLCurl::Multi::get(const std::string& url, ResponderPtr responder)
+	return n;
+}
+
+void LLCurl::Easy::prepRequest(const std::string& url, ResponderPtr responder, bool post)
 {
-	LLCurl::Easy* easy = easyAlloc();
-	easy->get(url, responder);
-	curl_multi_add_handle(mHandle, easy->mHandle);
+	resetState();
+	
+	if (post) setoptString(CURLOPT_ENCODING, "");
+
+//	setopt(CURLOPT_VERBOSE, 1); // usefull for debugging
+	setopt(CURLOPT_NOSIGNAL, 1);
+
+	mOutput.reset(new LLBufferArray);
+	setopt(CURLOPT_WRITEFUNCTION, (void*)&curlWriteCallback);
+	setopt(CURLOPT_WRITEDATA, (void*)this);
+
+	setopt(CURLOPT_READFUNCTION, (void*)&curlReadCallback);
+	setopt(CURLOPT_READDATA, (void*)this);
+	
+	setopt(CURLOPT_HEADERFUNCTION, (void*)&curlHeaderCallback);
+	setopt(CURLOPT_HEADERDATA, (void*)this);
+
+	setErrorBuffer();
+	setCA();
+
+	setopt(CURLOPT_SSL_VERIFYPEER, true);
+	setopt(CURLOPT_TIMEOUT, CURL_REQUEST_TIMEOUT);
+
+	setoptString(CURLOPT_URL, url);
+
+	mResponder = responder;
+
+	if (!post)
+	{
+		slist_append("Connection: keep-alive");
+		slist_append("Keep-alive: 300");
+	}
+	// *FIX: should have ACCEPT headers
 }
+
+////////////////////////////////////////////////////////////////////////////
+
+class LLCurl::Multi
+{
+	LOG_CLASS(Multi);
+public:
+	
+	Multi();
+	~Multi();
+
+	Easy* allocEasy();
+	bool addEasy(Easy* easy);
 	
-void
-LLCurl::Multi::getByteRange(const std::string& url, S32 offset, S32 length, ResponderPtr responder)
+	void removeEasy(Easy* easy);
+
+	S32 process();
+	S32 perform();
+	
+	CURLMsg* info_read(S32* msgs_in_queue);
+
+	S32 mQueued;
+	S32 mErrorCount;
+	
+private:
+	void easyFree(Easy*);
+	
+	CURLM* mCurlMultiHandle;
+
+	typedef std::set<Easy*> easy_active_list_t;
+	easy_active_list_t mEasyActiveList;
+	typedef std::map<CURL*, Easy*> easy_active_map_t;
+	easy_active_map_t mEasyActiveMap;
+	typedef std::set<Easy*> easy_free_list_t;
+	easy_free_list_t mEasyFreeList;
+};
+
+LLCurl::Multi::Multi()
+	: mQueued(0),
+	  mErrorCount(0)
 {
-	LLCurl::Easy* easy = easyAlloc();
-	easy->getByteRange(url, offset, length, responder);
-	curl_multi_add_handle(mHandle, easy->mHandle);
+	mCurlMultiHandle = curl_multi_init();
+	if (!mCurlMultiHandle)
+	{
+		llwarns << "curl_multi_init() returned NULL! Easy handles: " << gCurlEasyCount << " Multi handles: " << gCurlMultiCount << llendl;
+		mCurlMultiHandle = curl_multi_init();
+	}
+	llassert_always(mCurlMultiHandle);
+	++gCurlMultiCount;
 }
+
+LLCurl::Multi::~Multi()
+{
+	// Clean up active
+	for(easy_active_list_t::iterator iter = mEasyActiveList.begin();
+		iter != mEasyActiveList.end(); ++iter)
+	{
+		Easy* easy = *iter;
+		curl_multi_remove_handle(mCurlMultiHandle, easy->getCurlHandle());
+		delete easy;
+	}
+	mEasyActiveList.clear();
+	mEasyActiveMap.clear();
 	
-void
-LLCurl::Multi::process()
+	// Clean up freed
+	for_each(mEasyFreeList.begin(), mEasyFreeList.end(), DeletePointer());	
+	mEasyFreeList.clear();
+
+	curl_multi_cleanup(mCurlMultiHandle);
+	--gCurlMultiCount;
+}
+
+CURLMsg* LLCurl::Multi::info_read(S32* msgs_in_queue)
 {
-	int count;
-	for (int call_count = 0; call_count < 5; call_count += 1)
+	CURLMsg* curlmsg = curl_multi_info_read(mCurlMultiHandle, msgs_in_queue);
+	return curlmsg;
+}
+
+
+S32 LLCurl::Multi::perform()
+{
+	S32 q = 0;
+	for (S32 call_count = 0;
+		 call_count < MULTI_PERFORM_CALL_REPEAT;
+		 call_count += 1)
 	{
-		if (CURLM_CALL_MULTI_PERFORM != curl_multi_perform(mHandle, &count))
+		CURLMcode code = curl_multi_perform(mCurlMultiHandle, &q);
+		if (CURLM_CALL_MULTI_PERFORM != code || q == 0)
 		{
 			break;
 		}
 	}
-		
+	mQueued = q;
+	return q;
+}
+
+S32 LLCurl::Multi::process()
+{
+	perform();
+	
 	CURLMsg* msg;
 	int msgs_in_queue;
-	while ((msg = curl_multi_info_read(mHandle, &msgs_in_queue)))
+
+	S32 processed = 0;
+	while ((msg = info_read(&msgs_in_queue)))
 	{
-		if (msg->msg != CURLMSG_DONE) continue;
-		Easy* easy = 0;
-		curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &easy);
-		if (!easy) continue;
-		easy->report(msg->data.result);
-		
-		curl_multi_remove_handle(mHandle, easy->mHandle);
-		easyFree(easy);
+		++processed;
+		if (msg->msg == CURLMSG_DONE)
+		{
+			U32 response = 0;
+			easy_active_map_t::iterator iter = mEasyActiveMap.find(msg->easy_handle);
+			if (iter != mEasyActiveMap.end())
+			{
+				Easy* easy = iter->second;
+				response = easy->report(msg->data.result);
+				removeEasy(easy);
+			}
+			else
+			{
+				response = 499;
+				//*TODO: change to llwarns
+				llerrs << "cleaned up curl request completed!" << llendl;
+			}
+			if (response >= 400)
+			{
+				// failure of some sort, inc mErrorCount for debugging and flagging multi for destruction
+				++mErrorCount;
+			}
+		}
 	}
+	return processed;
 }
 
-
-
-LLCurl::Easy*
-LLCurl::Multi::easyAlloc()
+LLCurl::Easy* LLCurl::Multi::allocEasy()
 {
 	Easy* easy = 0;
-	
-	if (mFreeEasy.empty())
+
+	if (mEasyFreeList.empty())
 	{
-		easy = new Easy();
+		easy = Easy::getEasy();
 	}
 	else
 	{
-		easy = mFreeEasy.back();
-		mFreeEasy.pop_back();
+		easy = *(mEasyFreeList.begin());
+		mEasyFreeList.erase(easy);
+	}
+	if (easy)
+	{
+		mEasyActiveList.insert(easy);
+		mEasyActiveMap[easy->getCurlHandle()] = easy;
 	}
-
 	return easy;
 }
 
-void
-LLCurl::Multi::easyFree(Easy* easy)
+bool LLCurl::Multi::addEasy(Easy* easy)
 {
-	if (mFreeEasy.size() < 5)
+	CURLMcode mcode = curl_multi_add_handle(mCurlMultiHandle, easy->getCurlHandle());
+	if (mcode != CURLM_OK)
 	{
-		mFreeEasy.push_back(easy);
+		llwarns << "Curl Error: " << curl_multi_strerror(mcode) << llendl;
+		return false;
+	}
+	return true;
+}
+
+void LLCurl::Multi::easyFree(Easy* easy)
+{
+	mEasyActiveList.erase(easy);
+	mEasyActiveMap.erase(easy->getCurlHandle());
+	if (mEasyFreeList.size() < EASY_HANDLE_POOL_SIZE)
+	{
+		easy->resetState();
+		mEasyFreeList.insert(easy);
 	}
 	else
 	{
@@ -333,53 +651,371 @@ LLCurl::Multi::easyFree(Easy* easy)
 	}
 }
 
+void LLCurl::Multi::removeEasy(Easy* easy)
+{
+	curl_multi_remove_handle(mCurlMultiHandle, easy->getCurlHandle());
+	easyFree(easy);
+}
+
+//static
+std::string LLCurl::strerror(CURLcode errorcode)
+{
+#if LL_DARWIN
+	// curl_easy_strerror was added in libcurl 7.12.0.  Unfortunately, the version in the Mac OS X 10.3.9 SDK is 7.10.2...
+	// There's a problem with the custom curl headers in our build that keeps me from #ifdefing this on the libcurl version number
+	// (the correct check would be #if LIBCURL_VERSION_NUM >= 0x070c00).  We'll fix the header problem soon, but for now
+	// just punt and print the numeric error code on the Mac.
+	return llformat("%d", errorcode);
+#else // LL_DARWIN
+	return std::string(curl_easy_strerror(errorcode));
+#endif // LL_DARWIN
+}
+
+////////////////////////////////////////////////////////////////////////////
+// For generating a simple request for data
+// using one multi and one easy per request 
+
+LLCurlRequest::LLCurlRequest()
+	: mActiveMulti(NULL)
+{
+}
+
+LLCurlRequest::~LLCurlRequest()
+{
+	for_each(mMultiSet.begin(), mMultiSet.end(), DeletePointer());
+}
+
+void LLCurlRequest::addMulti()
+{
+	LLCurl::Multi* multi = new LLCurl::Multi();
+	mMultiSet.insert(multi);
+	mActiveMulti = multi;
+	mActiveRequestCount = 0;
+}
+
+LLCurl::Easy* LLCurlRequest::allocEasy()
+{
+	if (!mActiveMulti ||
+		mActiveRequestCount	>= MAX_ACTIVE_REQUEST_COUNT ||
+		mActiveMulti->mErrorCount > 0)
+	{
+		addMulti();
+	}
+	llassert_always(mActiveMulti);
+	++mActiveRequestCount;
+	LLCurl::Easy* easy = mActiveMulti->allocEasy();
+	return easy;
+}
+
+bool LLCurlRequest::addEasy(LLCurl::Easy* easy)
+{
+	llassert_always(mActiveMulti);
+	bool res = mActiveMulti->addEasy(easy);
+	return res;
+}
 
+void LLCurlRequest::get(const std::string& url, LLCurl::ResponderPtr responder)
+{
+	getByteRange(url, 0, -1, responder);
+}
+	
+bool LLCurlRequest::getByteRange(const std::string& url, S32 offset, S32 length, LLCurl::ResponderPtr responder)
+{
+	LLCurl::Easy* easy = allocEasy();
+	if (!easy)
+	{
+		return false;
+	}
+	easy->prepRequest(url, responder);
+	easy->setopt(CURLOPT_HTTPGET, 1);
+	if (length > 0)
+	{
+		std::string range = llformat("Range: bytes=%d-%d", offset,offset+length-1);
+		easy->slist_append(range.c_str());
+	}
+	easy->setHeaders();
+	bool res = addEasy(easy);
+	return res;
+}
 
-namespace
+bool LLCurlRequest::post(const std::string& url, const LLSD& data, LLCurl::ResponderPtr responder)
 {
-	static LLCurl::Multi* sMainMulti = 0;
+	LLCurl::Easy* easy = allocEasy();
+	if (!easy)
+	{
+		return false;
+	}
+	easy->prepRequest(url, responder);
+
+	LLSDSerialize::toXML(data, easy->getInput());
+	S32 bytes = easy->getInput().str().length();
+	
+	easy->setopt(CURLOPT_POST, 1);
+	easy->setopt(CURLOPT_POSTFIELDS, (void*)NULL);
+	easy->setopt(CURLOPT_POSTFIELDSIZE, bytes);
+
+	easy->slist_append("Content-Type: application/xml");
+	easy->setHeaders();
+
+	lldebugs << "POSTING: " << bytes << " bytes." << llendl;
+	bool res = addEasy(easy);
+	return res;
+}
 	
-	LLCurl::Multi*
-	mainMulti()
+// Note: call once per frame
+S32 LLCurlRequest::process()
+{
+	S32 res = 0;
+	for (curlmulti_set_t::iterator iter = mMultiSet.begin();
+		 iter != mMultiSet.end(); )
 	{
-		if (!sMainMulti) {
-			sMainMulti = new LLCurl::Multi();
+		curlmulti_set_t::iterator curiter = iter++;
+		LLCurl::Multi* multi = *curiter;
+		S32 tres = multi->process();
+		res += tres;
+		if (multi != mActiveMulti && tres == 0 && multi->mQueued == 0)
+		{
+			mMultiSet.erase(curiter);
+			delete multi;
 		}
-		return sMainMulti;
 	}
+	return res;
+}
 
-	void freeMulti()
+S32 LLCurlRequest::getQueued()
+{
+	S32 queued = 0;
+	for (curlmulti_set_t::iterator iter = mMultiSet.begin();
+		 iter != mMultiSet.end(); )
 	{
-		delete sMainMulti;
-		sMainMulti = NULL;
+		curlmulti_set_t::iterator curiter = iter++;
+		LLCurl::Multi* multi = *curiter;
+		queued += multi->mQueued;
 	}
+	return queued;
 }
 
-void
-LLCurl::get(const std::string& url, ResponderPtr responder)
+////////////////////////////////////////////////////////////////////////////
+// For generating one easy request
+// associated with a single multi request
+
+LLCurlEasyRequest::LLCurlEasyRequest()
+	: mRequestSent(false),
+	  mResultReturned(false)
 {
-	mainMulti()->get(url, responder);
+	mMulti = new LLCurl::Multi();
+	mEasy = mMulti->allocEasy();
+	if (mEasy)
+	{
+		mEasy->setErrorBuffer();
+		mEasy->setCA();
+	}
 }
-	
-void
-LLCurl::getByteRange(const std::string& url, S32 offset, S32 length, ResponderPtr responder)
+
+LLCurlEasyRequest::~LLCurlEasyRequest()
 {
-	mainMulti()->getByteRange(url, offset, length, responder);
+	delete mMulti;
 }
 	
-void LLCurl::initClass()
+void LLCurlEasyRequest::setopt(CURLoption option, S32 value)
 {
-    curl_global_init(CURL_GLOBAL_ALL);
+	if (mEasy)
+	{
+		mEasy->setopt(option, value);
+	}
 }
 
-void
-LLCurl::process()
+void LLCurlEasyRequest::setoptString(CURLoption option, const std::string& value)
 {
-	mainMulti()->process();
+	if (mEasy)
+	{
+		mEasy->setoptString(option, value);
+	}
 }
 
-void LLCurl::cleanup()
+void LLCurlEasyRequest::setPost(char* postdata, S32 size)
 {
-	freeMulti();
+	if (mEasy)
+	{
+		mEasy->setopt(CURLOPT_POST, 1);
+		mEasy->setopt(CURLOPT_POSTFIELDS, postdata);
+		mEasy->setopt(CURLOPT_POSTFIELDSIZE, size);
+	}
+}
+
+void LLCurlEasyRequest::setHeaderCallback(curl_header_callback callback, void* userdata)
+{
+	if (mEasy)
+	{
+		mEasy->setopt(CURLOPT_HEADERFUNCTION, (void*)callback);
+		mEasy->setopt(CURLOPT_HEADERDATA, userdata); // aka CURLOPT_WRITEHEADER
+	}
+}
+
+void LLCurlEasyRequest::setWriteCallback(curl_write_callback callback, void* userdata)
+{
+	if (mEasy)
+	{
+		mEasy->setopt(CURLOPT_WRITEFUNCTION, (void*)callback);
+		mEasy->setopt(CURLOPT_WRITEDATA, userdata);
+	}
+}
+
+void LLCurlEasyRequest::setReadCallback(curl_read_callback callback, void* userdata)
+{
+	if (mEasy)
+	{
+		mEasy->setopt(CURLOPT_READFUNCTION, (void*)callback);
+		mEasy->setopt(CURLOPT_READDATA, userdata);
+	}
+}
+
+void LLCurlEasyRequest::slist_append(const char* str)
+{
+	if (mEasy)
+	{
+		mEasy->slist_append(str);
+	}
+}
+
+void LLCurlEasyRequest::sendRequest(const std::string& url)
+{
+	llassert_always(!mRequestSent);
+	mRequestSent = true;
+	if (mEasy)
+	{
+		mEasy->setHeaders();
+		mEasy->setoptString(CURLOPT_URL, url);
+		mMulti->addEasy(mEasy);
+	}
+}
+
+void LLCurlEasyRequest::requestComplete()
+{
+	llassert_always(mRequestSent);
+	mRequestSent = false;
+	if (mEasy)
+	{
+		mMulti->removeEasy(mEasy);
+	}
+}
+
+S32 LLCurlEasyRequest::perform()
+{
+	return mMulti->perform();
+}
+
+// Usage: Call getRestult until it returns false (no more messages)
+bool LLCurlEasyRequest::getResult(CURLcode* result, LLCurl::TransferInfo* info)
+{
+	if (!mEasy)
+	{
+		// Special case - we failed to initialize a curl_easy (can happen if too many open files)
+		//  Act as though the request failed to connect
+		if (mResultReturned)
+		{
+			return false;
+		}
+		else
+		{
+			*result = CURLE_FAILED_INIT;
+			mResultReturned = true;
+			return true;
+		}
+	}
+	// In theory, info_read might return a message with a status other than CURLMSG_DONE
+	// In practice for all messages returned, msg == CURLMSG_DONE
+	// Ignore other messages just in case
+	while(1)
+	{
+		S32 q;
+		CURLMsg* curlmsg = info_read(&q, info);
+		if (curlmsg)
+		{
+			if (curlmsg->msg == CURLMSG_DONE)
+			{
+				*result = curlmsg->data.result;			
+				return true;
+			}
+			// else continue
+		}
+		else
+		{
+			return false;
+		}
+	}
+}
+
+// private
+CURLMsg* LLCurlEasyRequest::info_read(S32* q, LLCurl::TransferInfo* info)
+{
+	if (mEasy)
+	{
+		CURLMsg* curlmsg = mMulti->info_read(q);
+		if (curlmsg && curlmsg->msg == CURLMSG_DONE)
+		{
+			if (info)
+			{
+				mEasy->getTransferInfo(info);
+			}
+		}
+		return curlmsg;
+	}
+	return NULL;
+}
+
+std::string LLCurlEasyRequest::getErrorString()
+{
+	return mEasy ? std::string(mEasy->getErrorBuffer()) : std::string();
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+#if SAFE_SSL
+//static
+void LLCurl::ssl_locking_callback(int mode, int type, const char *file, int line)
+{
+	if (mode & CRYPTO_LOCK)
+	{
+		LLCurl::sSSLMutex[type]->lock();
+	}
+	else
+	{
+		LLCurl::sSSLMutex[type]->unlock();
+	}
+}
+
+//static
+unsigned long LLCurl::ssl_thread_id(void)
+{
+	return LLThread::currentID();
+}
+#endif
+
+void LLCurl::initClass()
+{
+	// Do not change this "unless you are familiar with and mean to control 
+	// internal operations of libcurl"
+	// - http://curl.haxx.se/libcurl/c/curl_global_init.html
+	curl_global_init(CURL_GLOBAL_ALL);
+	
+#if SAFE_SSL
+	S32 mutex_count = CRYPTO_num_locks();
+	for (S32 i=0; i<mutex_count; i++)
+	{
+		sSSLMutex.push_back(new LLMutex(gAPRPoolp));
+	}
+	CRYPTO_set_id_callback(&LLCurl::ssl_thread_id);
+	CRYPTO_set_locking_callback(&LLCurl::ssl_locking_callback);
+#endif
+}
+
+void LLCurl::cleanupClass()
+{
+#if SAFE_SSL
+	CRYPTO_set_locking_callback(NULL);
+	for_each(sSSLMutex.begin(), sSSLMutex.end(), DeletePointer());
+#endif
 	curl_global_cleanup();
 }
+
diff --git a/indra/llmessage/llcurl.h b/indra/llmessage/llcurl.h
index 53287c298883f5e08c4f5a808e3288eb20c8c675..48c14d9460c03cb0d48d758366f6b4026edc7835 100644
--- a/indra/llmessage/llcurl.h
+++ b/indra/llmessage/llcurl.h
@@ -1,8 +1,8 @@
-/**
+/** 
  * @file llcurl.h
  * @author Zero / Donovan
  * @date 2006-10-15
- * @brief Curl wrapper
+ * @brief A wrapper around libcurl.
  *
  * $LicenseInfo:firstyear=2006&license=viewergpl$
  * 
@@ -41,104 +41,183 @@
 #include <vector>
 
 #include <boost/intrusive_ptr.hpp>
-#include <curl/curl.h>
+#include <curl/curl.h> // TODO: remove dependency
 
-// #include "llhttpclient.h"
+#include "llbuffer.h"
+#include "lliopipe.h"
+#include "llsd.h"
+
+class LLMutex;
+
+// For whatever reason, this is not typedef'd in curl.h
+typedef size_t (*curl_header_callback)(void *ptr, size_t size, size_t nmemb, void *stream);
 
 class LLCurl
 {
+	LOG_CLASS(LLCurl);
+	
 public:
+	class Easy;
 	class Multi;
 
+	struct TransferInfo
+	{
+		TransferInfo() : mSizeDownload(0.0), mTotalTime(0.0), mSpeedDownload(0.0) {}
+		F64 mSizeDownload;
+		F64 mTotalTime;
+		F64 mSpeedDownload;
+	};
+	
 	class Responder
 	{
+	//LOG_CLASS(Responder);
 	public:
+
 		Responder();
 		virtual ~Responder();
 
-		virtual void error(U32 status, const std::stringstream& content);	// called with bad status codes
+		/**
+		 * @brief return true if the status code indicates success.
+		 */
+		static bool isGoodStatus(U32 status)
+		{
+			return((200 <= status) && (status < 300));
+		}
+		
+		virtual void error(U32 status, const std::string& reason);
+			// called with non-200 status codes
 		
-		virtual void result(const std::stringstream& content);
+		virtual void result(const LLSD& content);
 		
-		virtual void completed(U32 status, const std::stringstream& content);
+		// Override point for clients that may want to use this class when the response is some other format besides LLSD
+		virtual void completedRaw(U32 status, const std::string& reason,
+								  const LLChannelDescriptors& channels,
+								  const LLIOPipe::buffer_ptr_t& buffer);
+
+		virtual void completed(U32 status, const std::string& reason, const LLSD& content);
 			/**< The default implemetnation calls
 				either:
 				* result(), or
 				* error() 
 			*/
 			
+			// Override to handle parsing of the header only.  Note: this is the only place where the contents
+			// of the header can be parsed.  In the ::completed call above only the body is contained in the LLSD.
+			virtual void completedHeader(U32 status, const std::string& reason, const LLSD& content);
+
 	public: /* but not really -- don't touch this */
 		U32 mReferenceCount;
 	};
 	typedef boost::intrusive_ptr<Responder>	ResponderPtr;
-	
-	class Easy
-	{
-	public:
-		Easy();
-		~Easy();
-		
-		void get(const std::string& url, ResponderPtr);
-		void getByteRange(const std::string& url, S32 offset, S32 length, ResponderPtr);
-
-		void perform();
 
-	private:
-		void prep(const std::string& url, ResponderPtr);
-		void report(CURLcode);
-		
-		CURL*				mHandle;
-		struct curl_slist*	mHeaders;
-		
-		std::string			mURL;
-		std::string			mRange;
-		std::stringstream	mRequest;
 
-		std::stringstream	mOutput;
-		char				mErrorBuffer[CURL_ERROR_SIZE];
-
-		std::stringstream	mHeaderOutput; // Debug
-		
-		ResponderPtr		mResponder;
-
-		friend class Multi;
-	};
+	/**
+	 * @ brief Set certificate authority file used to verify HTTPS certs.
+	 */
+	static void setCAFile(const std::string& file);
 
+	/**
+	 * @ brief Set certificate authority path used to verify HTTPS certs.
+	 */
+	static void setCAPath(const std::string& path);
+	
+	/**
+	 * @ brief Get certificate authority file used to verify HTTPS certs.
+	 */
+	static const std::string& getCAFile() { return sCAFile; }
+
+	/**
+	 * @ brief Get certificate authority path used to verify HTTPS certs.
+	 */
+	static const std::string& getCAPath() { return sCAPath; }
+
+	/**
+	 * @ brief Initialize LLCurl class
+	 */
+	static void initClass();
+
+	/**
+	 * @ brief Cleanup LLCurl class
+	 */
+	static void cleanupClass();
+
+	/**
+	 * @ brief curl error code -> string
+	 */
+	static std::string strerror(CURLcode errorcode);
+	
+	// For OpenSSL callbacks
+	static std::vector<LLMutex*> sSSLMutex;
 
-	class Multi
-	{
-	public:
-		Multi();
-		~Multi();
+	// OpenSSL callbacks
+	static void LLCurl::ssl_locking_callback(int mode, int type, const char *file, int line);
+	static unsigned long LLCurl::ssl_thread_id(void);
+	
+	
+	
+private:
 
-		void get(const std::string& url, ResponderPtr);
-		void getByteRange(const std::string& url, S32 offset, S32 length, ResponderPtr);
+	static std::string sCAPath;
+	static std::string sCAFile;
+};
 
-		void process();
-		
-	private:
-		Easy* easyAlloc();
-		void easyFree(Easy*);
-		
-		CURLM* mHandle;
-		
-		typedef std::vector<Easy*>	EasyList;
-		EasyList mFreeEasy;
-	};
+namespace boost
+{
+	void intrusive_ptr_add_ref(LLCurl::Responder* p);
+	void intrusive_ptr_release(LLCurl::Responder* p);
+};
 
 
-	static void get(const std::string& url, ResponderPtr);
-	static void getByteRange(const std::string& url, S32 offset, S32 length, ResponderPtr responder);
+class LLCurlRequest
+{
+public:
+	LLCurlRequest();
+	~LLCurlRequest();
+
+	void get(const std::string& url, LLCurl::ResponderPtr responder);
+	bool getByteRange(const std::string& url, S32 offset, S32 length, LLCurl::ResponderPtr responder);
+	bool post(const std::string& url, const LLSD& data, LLCurl::ResponderPtr responder);
+	S32  process();
+	S32  getQueued();
+
+private:
+	void addMulti();
+	LLCurl::Easy* allocEasy();
+	bool addEasy(LLCurl::Easy* easy);
 	
-    static void initClass(); // *NOTE:Mani - not thread safe!
-	static void process();
-	static void cleanup(); // *NOTE:Mani - not thread safe!
+private:
+	typedef std::set<LLCurl::Multi*> curlmulti_set_t;
+	curlmulti_set_t mMultiSet;
+	LLCurl::Multi* mActiveMulti;
+	S32 mActiveRequestCount;
 };
 
-namespace boost
+class LLCurlEasyRequest
 {
-	void intrusive_ptr_add_ref(LLCurl::Responder* p);
-	void intrusive_ptr_release(LLCurl::Responder* p);
+public:
+	LLCurlEasyRequest();
+	~LLCurlEasyRequest();
+	void setopt(CURLoption option, S32 value);
+	void setoptString(CURLoption option, const std::string& value);
+	void setPost(char* postdata, S32 size);
+	void setHeaderCallback(curl_header_callback callback, void* userdata);
+	void setWriteCallback(curl_write_callback callback, void* userdata);
+	void setReadCallback(curl_read_callback callback, void* userdata);
+	void slist_append(const char* str);
+	void sendRequest(const std::string& url);
+	void requestComplete();
+	S32 perform();
+	bool getResult(CURLcode* result, LLCurl::TransferInfo* info = NULL);
+	std::string getErrorString();
+
+private:
+	CURLMsg* info_read(S32* queue, LLCurl::TransferInfo* info);
+	
+private:
+	LLCurl::Multi* mMulti;
+	LLCurl::Easy* mEasy;
+	bool mRequestSent;
+	bool mResultReturned;
 };
 
 #endif // LL_LLCURL_H
diff --git a/indra/llmessage/llhttpassetstorage.cpp b/indra/llmessage/llhttpassetstorage.cpp
index cf9bde6fec081a909f835c47b4bb0ed2ecd6b621..21790648073c1690d4429bcb385d51e07cd8a1f8 100644
--- a/indra/llmessage/llhttpassetstorage.cpp
+++ b/indra/llmessage/llhttpassetstorage.cpp
@@ -422,11 +422,8 @@ void LLHTTPAssetStorage::_init(const char *web_host, const char *local_web_host,
 	mLocalBaseURL = local_web_host;
 	mHostName = host_name;
 
-	// Do not change this "unless you are familiar with and mean to control 
-	// internal operations of libcurl"
-	// - http://curl.haxx.se/libcurl/c/curl_global_init.html
-	curl_global_init(CURL_GLOBAL_ALL);
-
+	// curl_global_init moved to LLCurl::initClass()
+	
 	mCurlMultiHandle = curl_multi_init();
 }
 
@@ -435,7 +432,7 @@ LLHTTPAssetStorage::~LLHTTPAssetStorage()
 	curl_multi_cleanup(mCurlMultiHandle);
 	mCurlMultiHandle = NULL;
 	
-	curl_global_cleanup();
+	// curl_global_cleanup moved to LLCurl::initClass()
 }
 
 // storing data is simpler than getting it, so we just overload the whole method
diff --git a/indra/llmessage/llhttpclient.cpp b/indra/llmessage/llhttpclient.cpp
index 23295476ffde4e1e4ec9e5a27863a755e7d75224..3b892aec503c9a899a857fef70eb3c16ded0cfc1 100644
--- a/indra/llmessage/llhttpclient.cpp
+++ b/indra/llmessage/llhttpclient.cpp
@@ -1,4 +1,4 @@
-/** 
+ /** 
  * @file llhttpclient.cpp
  * @brief Implementation of classes for making HTTP requests.
  *
@@ -38,7 +38,6 @@
 #include "llurlrequest.h"
 #include "llbufferstream.h"
 #include "llsdserialize.h"
-#include "llsdutil.h"
 #include "llvfile.h"
 #include "llvfs.h"
 #include "lluri.h"
@@ -47,85 +46,18 @@
 #include <curl/curl.h>
 
 const F32 HTTP_REQUEST_EXPIRY_SECS = 60.0f;
-static std::string gCABundle;
+////////////////////////////////////////////////////////////////////////////
 
+// Responder class moved to LLCurl
 
-LLHTTPClient::Responder::Responder()
-	: mReferenceCount(0)
-{
-}
-
-LLHTTPClient::Responder::~Responder()
-{
-}
-
-// virtual
-void LLHTTPClient::Responder::error(U32 status, const std::string& reason)
-{
-	llinfos << "LLHTTPClient::Responder::error "
-		<< status << ": " << reason << llendl;
-}
-
-// virtual
-void LLHTTPClient::Responder::result(const LLSD& content)
-{
-}
-
-// virtual 
-void LLHTTPClient::Responder::completedRaw(
-	U32 status,
-	const std::string& reason,
-	const LLChannelDescriptors& channels,
-	const LLIOPipe::buffer_ptr_t& buffer)
-{
-	LLBufferStream istr(channels, buffer.get());
-	LLSD content;
-
-	if (isGoodStatus(status))
-	{
-		LLSDSerialize::fromXML(content, istr);
-/*
-		const S32 parseError = -1;
-		if(LLSDSerialize::fromXML(content, istr) == parseError)
-		{
-			mStatus = 498;
-			mReason = "Client Parse Error";
-		}
-*/
-	}
-	
-	completed(status, reason, content);
-}
-
-// virtual
-void LLHTTPClient::Responder::completed(
-	U32 status,
-	const std::string& reason,
-	const LLSD& content)
-{
-	if(isGoodStatus(status))
-	{
-		result(content);
-	}
-	else
-	{
-		error(status, reason);
-	}
-}
-
-// virtual
-void LLHTTPClient::Responder::completedHeader(U32 status, const std::string& reason, const LLSD& content)
-{
-
-}
 namespace
 {
 	class LLHTTPClientURLAdaptor : public LLURLRequestComplete
 	{
 	public:
-		LLHTTPClientURLAdaptor(LLHTTPClient::ResponderPtr responder)
-			: mResponder(responder),
-				mStatus(499), mReason("LLURLRequest complete w/no status")
+		LLHTTPClientURLAdaptor(LLCurl::ResponderPtr responder)
+			: mResponder(responder), mStatus(499),
+			  mReason("LLURLRequest complete w/no status")
 		{
 		}
 		
@@ -140,7 +72,7 @@ namespace
 		}
 
 		virtual void complete(const LLChannelDescriptors& channels,
-								const buffer_ptr_t& buffer)
+							  const buffer_ptr_t& buffer)
 		{
 			if (mResponder.get())
 			{
@@ -154,7 +86,7 @@ namespace
 		}
 
 	private:
-		LLHTTPClient::ResponderPtr mResponder;
+		LLCurl::ResponderPtr mResponder;
 		U32 mStatus;
 		std::string mReason;
 		LLSD mHeaderOutput;
@@ -267,13 +199,14 @@ namespace
 	LLPumpIO* theClientPump = NULL;
 }
 
-static void request(
-	const std::string& url,
-	LLURLRequest::ERequestAction method,
-	Injector* body_injector,
-	LLHTTPClient::ResponderPtr responder,
-    const LLSD& headers,
-	const F32 timeout=HTTP_REQUEST_EXPIRY_SECS)
+static void request(const std::string& url,
+					LLURLRequest::ERequestAction method,
+					Injector* body_injector,
+					LLCurl::ResponderPtr responder,
+					const LLSD& headers = LLSD(),
+					const F32 timeout = HTTP_REQUEST_EXPIRY_SECS,
+					S32 offset = 0,
+					S32 bytes = 0)
 {
 	if (!LLHTTPClient::hasPump())
 	{
@@ -283,7 +216,7 @@ static void request(
 	LLPumpIO::chain_t chain;
 
 	LLURLRequest *req = new LLURLRequest(method, url);
-	req->requestEncoding("");
+	req->checkRootCertificate(true);
 
     // Insert custom headers is the caller sent any
     if (headers.isMap())
@@ -308,10 +241,6 @@ static void request(
             req->addHeader(header.str().c_str());
         }
     }
-	if (!gCABundle.empty())
-	{
-		req->checkRootCertificate(true, gCABundle.c_str());
-	}
 	req->setCallback(new LLHTTPClientURLAdaptor(responder));
 
 	if (method == LLURLRequest::HTTP_POST  &&  gMessageSystem)
@@ -327,19 +256,26 @@ static void request(
 
    		chain.push_back(LLIOPipe::ptr_t(body_injector));
 	}
+
+	if (method == LLURLRequest::HTTP_GET && (offset > 0 || bytes > 0))
+	{
+		std::string range = llformat("Range: bytes=%d-%d", offset,offset+bytes-1);
+		req->addHeader(range.c_str());
+   	}
+	
 	chain.push_back(LLIOPipe::ptr_t(req));
 
 	theClientPump->addChain(chain, timeout);
 }
 
-static void request(
-	const std::string& url,
-	LLURLRequest::ERequestAction method,
-	Injector* body_injector,
-	LLHTTPClient::ResponderPtr responder,
-	const F32 timeout=HTTP_REQUEST_EXPIRY_SECS)
+
+void LLHTTPClient::getByteRange(const std::string& url,
+								S32 offset, S32 bytes,
+								ResponderPtr responder,
+								const LLSD& headers,
+								const F32 timeout)
 {
-    request(url, method, body_injector, responder, LLSD(), timeout);
+    request(url, LLURLRequest::HTTP_GET, NULL, responder, LLSD(), timeout, offset, bytes);
 }
 
 void LLHTTPClient::head(const std::string& url, ResponderPtr responder, const F32 timeout)
@@ -355,10 +291,6 @@ void LLHTTPClient::getHeaderOnly(const std::string& url, ResponderPtr responder,
 {
 	request(url, LLURLRequest::HTTP_HEAD, NULL, responder, headers, timeout);
 }
-void LLHTTPClient::get(const std::string& url, ResponderPtr responder, const F32 timeout)
-{
-	get(url, responder, LLSD(), timeout);
-}
 void LLHTTPClient::getHeaderOnly(const std::string& url, ResponderPtr responder, const F32 timeout)
 {
 	getHeaderOnly(url, responder, LLSD(), timeout);
@@ -372,11 +304,6 @@ void LLHTTPClient::get(const std::string& url, const LLSD& query, ResponderPtr r
 	get(uri.asString(), responder, headers, timeout);
 }
 
-void LLHTTPClient::get(const std::string& url, const LLSD& query, ResponderPtr responder, const F32 timeout)
-{
-	get(url, query, responder, LLSD(), timeout);
-}
-
 // A simple class for managing data returned from a curl http request.
 class LLHTTPBuffer
 {
@@ -412,6 +339,7 @@ private:
 	std::string mBuffer;
 };
 
+// *TODO: Deprecate (only used by dataserver)
 // This call is blocking! This is probably usually bad. :(
 LLSD LLHTTPClient::blockingGet(const std::string& url)
 {
@@ -505,24 +433,3 @@ bool LLHTTPClient::hasPump()
 {
 	return theClientPump != NULL;
 }
-
-void LLHTTPClient::setCABundle(const std::string& caBundle)
-{
-	gCABundle = caBundle;
-}
-
-namespace boost
-{
-	void intrusive_ptr_add_ref(LLHTTPClient::Responder* p)
-	{
-		++p->mReferenceCount;
-	}
-	
-	void intrusive_ptr_release(LLHTTPClient::Responder* p)
-	{
-		if(p && 0 == --p->mReferenceCount)
-		{
-			delete p;
-		}
-	}
-};
diff --git a/indra/llmessage/llhttpclient.h b/indra/llmessage/llhttpclient.h
index 1fbf0c36dc01ce6fcfe23126b8dfc527090cfb70..b011761f5f7d2ec8614b9e90f123a7c00a4d20b4 100644
--- a/indra/llmessage/llhttpclient.h
+++ b/indra/llmessage/llhttpclient.h
@@ -41,7 +41,7 @@
 #include <boost/intrusive_ptr.hpp>
 
 #include "llassettype.h"
-#include "llbuffer.h"
+#include "llcurl.h"
 #include "lliopipe.h"
 
 extern const F32 HTTP_REQUEST_EXPIRY_SECS;
@@ -54,57 +54,18 @@ class LLSD;
 class LLHTTPClient
 {
 public:
-	class Responder
-	{
-	public:
-		Responder();
-		virtual ~Responder();
-
-		/**
-		 * @brief return true if the status code indicates success.
-		 */
-		static bool isGoodStatus(U32 status)
-		{
-			return((200 <= status) && (status < 300));
-		}
-
-		virtual void error(U32 status, const std::string& reason);	// called with bad status codes
-		
-		virtual void result(const LLSD& content);
-		
-		// Override point for clients that may want to use this class
-		// when the response is some other format besides LLSD
-		virtual void completedRaw(
-			U32 status,
-			const std::string& reason,
-			const LLChannelDescriptors& channels,
-			const LLIOPipe::buffer_ptr_t& buffer);
-
-		virtual void completed(
-			U32 status,
-			const std::string& reason,
-			const LLSD& content);
-			/**< The default implemetnation calls
-				either:
-				* result(), or
-				* error() 
-			*/
-
-		// Override to handle parsing of the header only.  Note: this is the only place where the contents
-		// of the header can be parsed.  In the ::completed call above only the body is contained in the LLSD.
-		virtual void completedHeader(U32 status, const std::string& reason, const LLSD& content);
-
-	public: /* but not really -- don't touch this */
-		U32 mReferenceCount;
-	};
-
-	typedef boost::intrusive_ptr<Responder>	ResponderPtr;
-	
+	// class Responder moved to LLCurl
+
+	// For convenience
+	typedef LLCurl::Responder Responder;
+	typedef LLCurl::ResponderPtr ResponderPtr;
+
+	// non-blocking
 	static void head(const std::string& url, ResponderPtr, const F32 timeout=HTTP_REQUEST_EXPIRY_SECS);
-	static void get(const std::string& url, ResponderPtr, const F32 timeout=HTTP_REQUEST_EXPIRY_SECS);
-	static void get(const std::string& url, ResponderPtr, const LLSD& headers, const F32 timeout=HTTP_REQUEST_EXPIRY_SECS);
-	static void get(const std::string& url, const LLSD& query, ResponderPtr, const F32 timeout=HTTP_REQUEST_EXPIRY_SECS);
-	static void get(const std::string& url, const LLSD& query, ResponderPtr, const LLSD& headers, const F32 timeout=HTTP_REQUEST_EXPIRY_SECS);
+	static void getByteRange(const std::string& url, S32 offset, S32 bytes, ResponderPtr, const LLSD& headers=LLSD(), const F32 timeout=HTTP_REQUEST_EXPIRY_SECS);
+	static void get(const std::string& url, ResponderPtr, const LLSD& headers = LLSD(), const F32 timeout=HTTP_REQUEST_EXPIRY_SECS);
+	static void get(const std::string& url, const LLSD& query, ResponderPtr, const LLSD& headers = LLSD(), const F32 timeout=HTTP_REQUEST_EXPIRY_SECS);
+
 	static void put(const std::string& url, const LLSD& body, ResponderPtr, const F32 timeout=HTTP_REQUEST_EXPIRY_SECS);
 	static void getHeaderOnly(const std::string& url, ResponderPtr, const F32 timeout=HTTP_REQUEST_EXPIRY_SECS);
 	static void getHeaderOnly(const std::string& url, ResponderPtr, const LLSD& headers, const F32 timeout=HTTP_REQUEST_EXPIRY_SECS);
@@ -127,20 +88,6 @@ public:
 		///< must be called before any of the above calls are made
 	static bool hasPump();
 		///< for testing
-	
-	static void setCABundle(const std::string& caBundle);
-		///< use this root CA bundle when checking SSL connections
-		///< defaults to the standard system root CA bundle
-		///< @see LLURLRequest::checkRootCertificate()
 };
 
-
-
-namespace boost
-{
-	void intrusive_ptr_add_ref(LLHTTPClient::Responder* p);
-	void intrusive_ptr_release(LLHTTPClient::Responder* p);
-};
-
-
 #endif // LL_LLHTTPCLIENT_H
diff --git a/indra/llmessage/llurlrequest.cpp b/indra/llmessage/llurlrequest.cpp
index 631eea3e882c2b9dc1504a2310767d5de934e93f..f850656785a0f330eeacd3348f2a99b41721210f 100644
--- a/indra/llmessage/llurlrequest.cpp
+++ b/indra/llmessage/llurlrequest.cpp
@@ -37,6 +37,7 @@
 #include <curl/curl.h>
 #include <algorithm>
 
+#include "llcurl.h"
 #include "llioutil.h"
 #include "llmemtype.h"
 #include "llpumpio.h"
@@ -52,8 +53,7 @@ static const U32 HTTP_STATUS_PIPE_ERROR = 499;
 const std::string CONTEXT_DEST_URI_SD_LABEL("dest_uri");
 
 
-static
-size_t headerCallback(void* data, size_t size, size_t nmemb, void* user);
+static size_t headerCallback(void* data, size_t size, size_t nmemb, void* user);
 
 /**
  * class LLURLRequestDetail
@@ -63,12 +63,8 @@ class LLURLRequestDetail
 public:
 	LLURLRequestDetail();
 	~LLURLRequestDetail();
-	CURLM* mCurlMulti;
- 	CURL* mCurl;
-	struct curl_slist* mHeaders;
-	char* mURL;
-	char mCurlErrorBuf[CURL_ERROR_SIZE + 1];		/* Flawfinder: ignore */
-	bool mNeedToRemoveEasyHandle;
+	std::string mURL;
+	LLCurlEasyRequest* mCurlRequest;
 	LLBufferArray* mResponseBuffer;
 	LLChannelDescriptors mChannels;
 	U8* mLastRead;
@@ -77,11 +73,7 @@ public:
 };
 
 LLURLRequestDetail::LLURLRequestDetail() :
-	mCurlMulti(NULL),
-	mCurl(NULL),
-	mHeaders(NULL),
-	mURL(NULL),
-	mNeedToRemoveEasyHandle(false),
+	mCurlRequest(NULL),
 	mResponseBuffer(NULL),
 	mLastRead(NULL),
 	mBodyLimit(0),
@@ -89,34 +81,13 @@ LLURLRequestDetail::LLURLRequestDetail() :
 	
 {
 	LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
-	mCurlErrorBuf[0] = '\0';
+	mCurlRequest = new LLCurlEasyRequest();
 }
 
 LLURLRequestDetail::~LLURLRequestDetail()
 {
 	LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
-	if(mCurl)
-	{
-		if(mNeedToRemoveEasyHandle && mCurlMulti)
-		{
-			curl_multi_remove_handle(mCurlMulti, mCurl);
-			mNeedToRemoveEasyHandle = false;
-		}
-		curl_easy_cleanup(mCurl);
-		mCurl = NULL;
-	}
-	if(mCurlMulti)
-	{
-		curl_multi_cleanup(mCurlMulti);
-		mCurlMulti = NULL;
-	}
-	if(mHeaders)
-	{
-		curl_slist_free_all(mHeaders);
-		mHeaders = NULL;
-	}
-	delete[] mURL;
-	mURL = NULL;
+	delete mCurlRequest;
 	mResponseBuffer = NULL;
 	mLastRead = NULL;
 }
@@ -126,9 +97,6 @@ LLURLRequestDetail::~LLURLRequestDetail()
  * class LLURLRequest
  */
 
-static std::string sCAFile("");
-static std::string sCAPath("");
-
 LLURLRequest::LLURLRequest(LLURLRequest::ERequestAction action) :
 	mAction(action)
 {
@@ -155,31 +123,13 @@ LLURLRequest::~LLURLRequest()
 void LLURLRequest::setURL(const std::string& url)
 {
 	LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
-	if(mDetail->mURL)
-	{
-		// *NOTE: if any calls to set the url have been made to curl,
-		// this will probably lead to a crash.
-		delete[] mDetail->mURL;
-		mDetail->mURL = NULL;
-	}
-	if(!url.empty())
-	{
-		mDetail->mURL = new char[url.size() + 1];
-		url.copy(mDetail->mURL, url.size());
-		mDetail->mURL[url.size()] = '\0';
-	}
+	mDetail->mURL = url;
 }
 
 void LLURLRequest::addHeader(const char* header)
 {
 	LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
-	mDetail->mHeaders = curl_slist_append(mDetail->mHeaders, header);
-}
-
-void LLURLRequest::requestEncoding(const char* encoding)
-{
-	LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
-	curl_easy_setopt(mDetail->mCurl, CURLOPT_ENCODING, encoding);
+	mDetail->mCurlRequest->slist_append(header);
 }
 
 void LLURLRequest::setBodyLimit(U32 size)
@@ -188,22 +138,17 @@ void LLURLRequest::setBodyLimit(U32 size)
 	mDetail->mIsBodyLimitSet = true;
 }
 
-void LLURLRequest::checkRootCertificate(bool check, const char* caBundle)
+void LLURLRequest::checkRootCertificate(bool check)
 {
-	curl_easy_setopt(mDetail->mCurl, CURLOPT_SSL_VERIFYPEER, (check? TRUE : FALSE));
-	if (caBundle)
-	{
-		curl_easy_setopt(mDetail->mCurl, CURLOPT_CAINFO, caBundle);
-	}
+	mDetail->mCurlRequest->setopt(CURLOPT_SSL_VERIFYPEER, (check? TRUE : FALSE));
+	mDetail->mCurlRequest->setoptString(CURLOPT_ENCODING, "");
 }
 
 void LLURLRequest::setCallback(LLURLRequestComplete* callback)
 {
 	LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
 	mCompletionCallback = callback;
-
-	curl_easy_setopt(mDetail->mCurl, CURLOPT_HEADERFUNCTION, &headerCallback);
-	curl_easy_setopt(mDetail->mCurl, CURLOPT_WRITEHEADER, callback);
+	mDetail->mCurlRequest->setHeaderCallback(&headerCallback, (void*)callback);
 }
 
 // Added to mitigate the effect of libcurl looking
@@ -239,11 +184,11 @@ void LLURLRequest::useProxy(bool use_proxy)
 
     if (env_proxy && use_proxy)
     {
-        curl_easy_setopt(mDetail->mCurl, CURLOPT_PROXY, env_proxy);
+		mDetail->mCurlRequest->setoptString(CURLOPT_PROXY, env_proxy);
     }
     else
     {
-        curl_easy_setopt(mDetail->mCurl, CURLOPT_PROXY, "");
+        mDetail->mCurlRequest->setoptString(CURLOPT_PROXY, "");
     }
 }
 
@@ -309,27 +254,20 @@ LLIOPipe::EStatus LLURLRequest::process_impl(
 	case STATE_PROCESSING_RESPONSE:
 	{
 		PUMP_DEBUG;
-		const S32 MAX_CALLS = 5;
-		S32 count = MAX_CALLS;
-		CURLMcode code;
 		LLIOPipe::EStatus status = STATUS_BREAK;
-		S32 queue;
-		do
-		{
-			LLFastTimer t2(LLFastTimer::FTM_CURL);
-			code = curl_multi_perform(mDetail->mCurlMulti, &queue);			
-		}while((CURLM_CALL_MULTI_PERFORM == code) && (queue > 0) && count--);
-		CURLMsg* curl_msg;
-		do
+		mDetail->mCurlRequest->perform();
+		while(1)
 		{
-			curl_msg = curl_multi_info_read(mDetail->mCurlMulti, &queue);
-			if(curl_msg && (curl_msg->msg == CURLMSG_DONE))
+			CURLcode result;
+			bool newmsg = mDetail->mCurlRequest->getResult(&result);
+			if (!newmsg)
 			{
-				mState = STATE_HAVE_RESPONSE;
+				break;
+			}
 
-				CURLcode result = curl_msg->data.result;
-				switch(result)
-				{
+			mState = STATE_HAVE_RESPONSE;
+			switch(result)
+			{
 				case CURLE_OK:
 				case CURLE_WRITE_ERROR:
 					// NB: The error indication means that we stopped the
@@ -352,31 +290,21 @@ LLIOPipe::EStatus LLURLRequest::process_impl(
 						mCompletionCallback = NULL;
 					}
 					break;
+				case CURLE_FAILED_INIT:
 				case CURLE_COULDNT_CONNECT:
 					status = STATUS_NO_CONNECTION;
 					break;
 				default:
-					llwarns << "URLRequest Error: " << curl_msg->data.result
+					llwarns << "URLRequest Error: " << result
 							<< ", "
-#if LL_DARWIN
-							// curl_easy_strerror was added in libcurl 7.12.0.  Unfortunately, the version in the Mac OS X 10.3.9 SDK is 7.10.2...
-							// There's a problem with the custom curl headers in our build that keeps me from #ifdefing this on the libcurl version number
-							// (the correct check would be #if LIBCURL_VERSION_NUM >= 0x070c00).  We'll fix the header problem soon, but for now
-							// just punt and print the numeric error code on the Mac.
-							<< curl_msg->data.result
-#else // LL_DARWIN
-							<< curl_easy_strerror(curl_msg->data.result)
-#endif // LL_DARWIN
+							<< LLCurl::strerror(result)
 							<< ", "
-							<< (mDetail->mURL ? mDetail->mURL : "<EMPTY URL>")
+							<< (mDetail->mURL.empty() ? "<EMPTY URL>" : mDetail->mURL)
 							<< llendl;
 					status = STATUS_ERROR;
 					break;
-				}
-				curl_multi_remove_handle(mDetail->mCurlMulti, mDetail->mCurl);
-				mDetail->mNeedToRemoveEasyHandle = false;
 			}
-		}while(curl_msg && (queue > 0));
+		}
 		return status;
 	}
 	case STATE_HAVE_RESPONSE:
@@ -397,26 +325,9 @@ void LLURLRequest::initialize()
 	LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
 	mState = STATE_INITIALIZED;
 	mDetail = new LLURLRequestDetail;
-	mDetail->mCurl = curl_easy_init();
-	mDetail->mCurlMulti = curl_multi_init();
-	curl_easy_setopt(mDetail->mCurl, CURLOPT_NOSIGNAL, 1);
-	curl_easy_setopt(mDetail->mCurl, CURLOPT_WRITEFUNCTION, &downCallback);
-	curl_easy_setopt(mDetail->mCurl, CURLOPT_WRITEDATA, this);
-	curl_easy_setopt(mDetail->mCurl, CURLOPT_READFUNCTION, &upCallback);
-	curl_easy_setopt(mDetail->mCurl, CURLOPT_READDATA, this);
-	curl_easy_setopt(
-		mDetail->mCurl,
-		CURLOPT_ERRORBUFFER,
-		mDetail->mCurlErrorBuf);
-
-	if(sCAPath != std::string(""))
-	{
-		curl_easy_setopt(mDetail->mCurl, CURLOPT_CAPATH, sCAPath.c_str());
-	}
-	if(sCAFile != std::string(""))
-	{
-		curl_easy_setopt(mDetail->mCurl, CURLOPT_CAINFO, sCAFile.c_str());
-	}
+	mDetail->mCurlRequest->setopt(CURLOPT_NOSIGNAL, 1);
+	mDetail->mCurlRequest->setWriteCallback(&downCallback, (void*)this);
+	mDetail->mCurlRequest->setReadCallback(&upCallback, (void*)this);
 }
 
 bool LLURLRequest::configure()
@@ -429,13 +340,14 @@ bool LLURLRequest::configure()
 	switch(mAction)
 	{
 	case HTTP_HEAD:
-		// These are in addition to the HTTP_GET options.
-		curl_easy_setopt(mDetail->mCurl, CURLOPT_HEADER, 1);
-		curl_easy_setopt(mDetail->mCurl, CURLOPT_NOBODY, 1);
-		
+		mDetail->mCurlRequest->setopt(CURLOPT_HEADER, 1);
+		mDetail->mCurlRequest->setopt(CURLOPT_NOBODY, 1);
+		mDetail->mCurlRequest->setopt(CURLOPT_FOLLOWLOCATION, 1);
+		rv = true;
+		break;
 	case HTTP_GET:
-		curl_easy_setopt(mDetail->mCurl, CURLOPT_HTTPGET, 1);
-		curl_easy_setopt(mDetail->mCurl, CURLOPT_FOLLOWLOCATION, 1);
+		mDetail->mCurlRequest->setopt(CURLOPT_HTTPGET, 1);
+		mDetail->mCurlRequest->setopt(CURLOPT_FOLLOWLOCATION, 1);
 		rv = true;
 		break;
 
@@ -444,8 +356,8 @@ bool LLURLRequest::configure()
 		// to turning this on, and I am not too sure what it means.
 		addHeader("Expect:");
 
-		curl_easy_setopt(mDetail->mCurl, CURLOPT_UPLOAD, 1);
-		curl_easy_setopt(mDetail->mCurl, CURLOPT_INFILESIZE, bytes);
+		mDetail->mCurlRequest->setopt(CURLOPT_UPLOAD, 1);
+		mDetail->mCurlRequest->setopt(CURLOPT_INFILESIZE, bytes);
 		rv = true;
 		break;
 
@@ -459,15 +371,13 @@ bool LLURLRequest::configure()
 		addHeader("Content-Type:");
 
 		// Set the handle for an http post
-		curl_easy_setopt(mDetail->mCurl, CURLOPT_POST, 1);
-		curl_easy_setopt(mDetail->mCurl, CURLOPT_POSTFIELDS, NULL);
-		curl_easy_setopt(mDetail->mCurl, CURLOPT_POSTFIELDSIZE, bytes);
+		mDetail->mCurlRequest->setPost(NULL, bytes);
 		rv = true;
 		break;
 
 	case HTTP_DELETE:
 		// Set the handle for an http post
-		curl_easy_setopt(mDetail->mCurl, CURLOPT_CUSTOMREQUEST, "DELETE");
+		mDetail->mCurlRequest->setoptString(CURLOPT_CUSTOMREQUEST, "DELETE");
 		rv = true;
 		break;
 
@@ -477,24 +387,14 @@ bool LLURLRequest::configure()
 	}
 	if(rv)
 	{
-		if(mDetail->mHeaders)
-		{
-			curl_easy_setopt(
-				mDetail->mCurl,
-				CURLOPT_HTTPHEADER,
-				mDetail->mHeaders);
-		}
-		curl_easy_setopt(mDetail->mCurl, CURLOPT_URL, mDetail->mURL);
-		lldebugs << "URL: " << mDetail->mURL << llendl;
-		curl_multi_add_handle(mDetail->mCurlMulti, mDetail->mCurl);
-		mDetail->mNeedToRemoveEasyHandle = true;
+		mDetail->mCurlRequest->sendRequest(mDetail->mURL);
 	}
 	return rv;
 }
 
 // static
 size_t LLURLRequest::downCallback(
-	void* data,
+	char* data,
 	size_t size,
 	size_t nmemb,
 	void* user)
@@ -528,7 +428,7 @@ size_t LLURLRequest::downCallback(
 
 // static
 size_t LLURLRequest::upCallback(
-	void* data,
+	char* data,
 	size_t size,
 	size_t nmemb,
 	void* user)
@@ -548,8 +448,7 @@ size_t LLURLRequest::upCallback(
 	return bytes;
 }
 
-static
-size_t headerCallback(void* data, size_t size, size_t nmemb, void* user)
+static size_t headerCallback(void* data, size_t size, size_t nmemb, void* user)
 {
 	const char* headerLine = (const char*)data;
 	size_t headerLen = size * nmemb;
@@ -605,18 +504,6 @@ size_t headerCallback(void* data, size_t size, size_t nmemb, void* user)
 	return headerLen;
 }
 
-//static 
-void LLURLRequest::setCertificateAuthorityFile(const std::string& file_name)
-{
-	sCAFile = file_name;
-}
-
-//static 
-void LLURLRequest::setCertificateAuthorityPath(const std::string& path)
-{
-	sCAPath = path;
-}
-
 /**
  * LLContextURLExtractor
  */
diff --git a/indra/llmessage/llurlrequest.h b/indra/llmessage/llurlrequest.h
index 5bdb6a1e69c2520e07ebf506eb1f4b9e7b06edd6..b154794ff1da78cf3538434b1c36bbbedefb3767 100644
--- a/indra/llmessage/llurlrequest.h
+++ b/indra/llmessage/llurlrequest.h
@@ -129,18 +129,8 @@ public:
 	 *
 	 * Set whether request will check that remote server
 	 * certificates are signed by a known root CA when using HTTPS.
-	 * Use the supplied root certificate bundle if supplied, else use
-	 * the standard bundle as found by libcurl and openssl.
 	 */
-	void checkRootCertificate(bool check, const char* caBundle = NULL);
-
-	/** 
-	 * @brief Request a particular response encoding if available.
-	 *
-	 * This call is a shortcut for requesting a particular encoding
-	 * from the server, eg, 'gzip'. 
-	 */
-	void requestEncoding(const char* encoding);
+	void checkRootCertificate(bool check);
 
 	/**
 	 * @brief Return at most size bytes of body.
@@ -168,16 +158,6 @@ public:
 	void setCallback(LLURLRequestComplete* callback);
 	//@}
 
-	/**
-	 * @ brief Set certificate authority file used to verify HTTPS certs.
-	 */
-	static void setCertificateAuthorityFile(const std::string& file_name);
-
-	/**
-	 * @ brief Set certificate authority path used to verify HTTPS certs.
-	 */
-	static void setCertificateAuthorityPath(const std::string& path);
-
 	/* @name LLIOPipe virtual implementations
 	 */
 
@@ -234,7 +214,7 @@ private:
 	 * @brief Download callback method.
 	 */
  	static size_t downCallback(
-		void* data,
+		char* data,
 		size_t size,
 		size_t nmemb,
 		void* user);
@@ -243,7 +223,7 @@ private:
 	 * @brief Upload callback method.
 	 */
  	static size_t upCallback(
-		void* data,
+		char* data,
 		size_t size,
 		size_t nmemb,
 		void* user);
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 666fcd130171f40e6421f5afe65999c970e6c7c1..61699d21c86495eac3595c88ad2e36dd8b91aa06 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -51,6 +51,7 @@
 #include "llstartup.h"
 #include "llfocusmgr.h"
 #include "llviewerjoystick.h"
+#include "llares.h" 
 #include "llcurl.h"
 #include "llfloatersnapshot.h"
 #include "llviewerwindow.h"
@@ -1338,7 +1339,7 @@ bool LLAppViewer::mainLoop()
 	// Create IO Pump to use for HTTP Requests.
 	gServicePump = new LLPumpIO(gAPRPoolp);
 	LLHTTPClient::setPump(*gServicePump);
-	LLHTTPClient::setCABundle(gDirUtilp->getCAFile());
+	LLCurl::setCAFile(gDirUtilp->getCAFile());
 	
 	// initialize voice stuff here
 	gLocalSpeakerMgr = new LLLocalSpeakerMgr();
@@ -1398,10 +1399,14 @@ bool LLAppViewer::mainLoop()
 				{
 					LLFastTimer t3(LLFastTimer::FTM_IDLE);
 					idle();
-					LLCurl::process();
-					// this pump is necessary to make the login screen show up
-					gServicePump->pump();
-					gServicePump->callback();
+
+					{
+						LLFastTimer t4(LLFastTimer::FTM_PUMP);
+						gAres->process();
+						// this pump is necessary to make the login screen show up
+						gServicePump->pump();
+						gServicePump->callback();
+					}
 				}
 
 				if (gDoDisconnect && (LLStartUp::getStartupState() == STATE_STARTED))
@@ -1811,7 +1816,7 @@ bool LLAppViewer::cleanup()
 	end_messaging_system();
 
     // *NOTE:Mani - The following call is not thread safe. 
-    LLCurl::cleanup();
+    LLCurl::cleanupClass();
 
     // If we're exiting to launch an URL, do that here so the screen
 	// is at the right resolution before we launch IE.
diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp
index 7b27a830c44ad5017bf3904f7a714e98c5cd82dc..17a8b844720bb8b5ab3dce6bc1a592c2fb5b05eb 100644
--- a/indra/newview/llinventorymodel.cpp
+++ b/indra/newview/llinventorymodel.cpp
@@ -47,6 +47,7 @@
 #include "llviewerinventory.h"
 #include "llviewermessage.h"
 #include "llviewerwindow.h"
+#include "llviewerregion.h"
 #include "llappviewer.h"
 #include "lldbstrings.h"
 #include "llviewerstats.h"
@@ -54,6 +55,8 @@
 #include "llnotify.h"
 #include "llcallbacklist.h"
 #include "llpreview.h"
+#include "llviewercontrol.h"
+#include "llsdutil.h"
 #include <deque>
 
 //#define DIFF_INVENTORY_FILES
@@ -69,6 +72,8 @@ F32  LLInventoryModel::sMinTimeBetweenFetches = 0.3f;
 F32  LLInventoryModel::sMaxTimeBetweenFetches = 10.f;
 BOOL LLInventoryModel::sTimelyFetchPending = FALSE;
 LLFrameTimer LLInventoryModel::sFetchTimer;
+LLInventoryModel::cat_map_t LLInventoryModel::sBulkFetchMap;
+S16 LLInventoryModel::sBulkFetchCount = 0;
 
 // RN: for some reason, using std::queue in the header file confuses the compiler which things it's an xmlrpc_queue
 static std::deque<LLUUID> sFetchQueue;
@@ -1002,6 +1007,286 @@ void LLInventoryModel::fetchDescendentsOf(const LLUUID& folder_id)
 	}
 }
 
+//Initialize statics.
+LLAlertDialog* LLInventoryModel::fetchDescendentsResponder::sRetryDialog=NULL;
+LLSD LLInventoryModel::fetchDescendentsResponder::sRetrySD;
+
+bool LLInventoryModel::isBulkFetchProcessingComplete()
+{
+	return ( (sFetchQueue.empty() 
+			&& sBulkFetchMap.empty() 
+			&& sBulkFetchCount==0)  ?  TRUE : FALSE ) ;
+}
+
+//If we get back a normal response, handle it here
+void  LLInventoryModel::fetchDescendentsResponder::result(const LLSD& content)
+{	
+	if (content.has("folders"))	
+	{
+		for(LLSD::array_const_iterator folder_it = content["folders"].beginArray();
+			folder_it != content["folders"].endArray();
+			++folder_it)
+		{	
+			LLSD folder_sd = *folder_it;
+			
+
+			LLUUID agent_id = folder_sd["agent-id"];
+
+			if(agent_id != gAgent.getID())	//This should never happen.
+			{
+				llwarns << "Got a UpdateInventoryItem for the wrong agent."
+						<< llendl;
+				break;
+			}
+			LLUUID parent_id = folder_sd["folder-id"];
+			LLUUID owner_id = folder_sd["owner-id"];
+			S32    version  = (S32)folder_sd["version"].asInteger();
+			S32    descendents = (S32)folder_sd["descendents"].asInteger();
+			LLPointer<LLViewerInventoryCategory> tcategory = new LLViewerInventoryCategory(owner_id);
+			for(LLSD::array_const_iterator category_it = folder_sd["categories"].beginArray();
+				category_it != folder_sd["categories"].endArray();
+				++category_it)
+			{	
+				LLSD category = *category_it;
+				tcategory->fromLLSD(category); 
+							
+				if (sFullFetchStarted)
+				{
+					sFetchQueue.push_back(tcategory->getUUID());
+				}
+				else if ( !gInventory.isCategoryComplete(tcategory->getUUID()) )
+				{
+					gInventory.updateCategory(tcategory);
+				}
+
+			}
+			LLPointer<LLViewerInventoryItem> titem = new LLViewerInventoryItem;
+			for(LLSD::array_const_iterator item_it = folder_sd["items"].beginArray();
+				item_it != folder_sd["items"].endArray();
+				++item_it)
+			{	
+				LLSD item = *item_it;
+				titem->unpackMessage(item);
+				
+				gInventory.updateItem(titem);
+			}
+
+			// set version and descendentcount according to message.
+			LLViewerInventoryCategory* cat = gInventory.getCategory(parent_id);
+			if(cat)
+			{
+				cat->setVersion(version);
+				cat->setDescendentCount(descendents);
+			}
+
+		}
+	}
+		
+	if (content.has("bad-folders"))
+	{
+		for(LLSD::array_const_iterator folder_it = content["bad-folders"].beginArray();
+			folder_it != content["bad-folders"].endArray();
+			++folder_it)
+		{	
+			LLSD folder_sd = *folder_it;
+			
+			//These folders failed on the dataserver.  We probably don't want to retry them.
+			llinfos << "Folder " << folder_sd["folder-id"].asString() 
+					<< "Error: " << folder_sd["error"].asString() << llendl;
+		}
+	}
+
+	LLInventoryModel::incrBulkFetch(-1);
+	
+	if (isBulkFetchProcessingComplete())
+	{
+		llinfos << "Inventory fetch completed" << llendl;
+		if (sFullFetchStarted)
+		{
+			sAllFoldersFetched = TRUE;
+		}
+		stopBackgroundFetch();
+	}
+	
+	gInventory.notifyObservers();
+}
+
+//If we get back an error (not found, etc...), handle it here
+void LLInventoryModel::fetchDescendentsResponder::error(U32 status, const std::string& reason)
+{
+	llinfos << "fetchDescendentsResponder::error "
+		<< status << ": " << reason << llendl;
+						
+	LLInventoryModel::incrBulkFetch(-1);
+
+	if (status==499)		//timed out.  Let's be awesome!
+	{
+		for(LLSD::array_const_iterator folder_it = mRequestSD["folders"].beginArray();
+			folder_it != mRequestSD["folders"].endArray();
+			++folder_it)
+		{	
+			LLSD folder_sd = *folder_it;
+			sRetrySD["folders"].append(folder_sd);
+		}
+		sMinTimeBetweenFetches = 10.0f; //Add 10 seconds for every time out in this sequence.
+		
+		if (!sRetryDialog)			//The dialog isn't up.  Prompt the resident.
+		{
+			sRetryDialog = gViewerWindow->alertXml("RetryFetchInventoryDescendents", onClickRetry, this);
+		}
+	}
+	else
+	{
+		if (isBulkFetchProcessingComplete())
+		{
+			if (sFullFetchStarted)
+			{
+				sAllFoldersFetched = TRUE;
+			}
+			stopBackgroundFetch();
+		}
+	}
+	gInventory.notifyObservers();
+}
+
+void LLInventoryModel::fetchDescendentsResponder::onClickRetry(S32 option, void* userdata)
+{
+	if (option == 0)
+	{
+		std::string url = gAgent.getRegion()->getCapability("FetchInventoryDescendents");
+
+		if (!url.empty()) //Capability found.  Build up LLSD and use it.
+		{
+			LLSD body = sRetrySD;
+			LLInventoryModel::incrBulkFetch(1);
+			LLHTTPClient::post(url, body, new LLInventoryModel::fetchDescendentsResponder(body),300);
+		}
+	}
+	else
+	{
+		if (isBulkFetchProcessingComplete())
+		{
+			if (sFullFetchStarted)
+			{
+				sAllFoldersFetched = TRUE;
+			}
+			stopBackgroundFetch();
+		}
+	}
+	sRetryDialog=NULL;
+	sRetrySD.clear();
+}
+
+//static   Bundle up a bunch of requests to send all at once.
+void LLInventoryModel::bulkFetch(std::string url)
+{
+	//Background fetch is called from gIdleCallbacks in a loop until background fetch is stopped.
+	//If there are items in sFetchQueue, we want to check the time since the last bulkFetch was 
+	//sent.  If it exceeds our retry time, go ahead and fire off another batch.  
+	//Stopbackgroundfetch will be run from the Responder instead of here.  
+
+	S16 max_concurrent_fetches=8;
+	F32 new_min_time = 0.5f;			//HACK!  Clean this up when old code goes away entirely.
+	if (sMinTimeBetweenFetches <= new_min_time) sMinTimeBetweenFetches=new_min_time;  //HACK!  See above.
+	
+	if(gDisconnected 
+	|| sBulkFetchCount > max_concurrent_fetches
+	|| sFetchTimer.getElapsedTimeF32() < sMinTimeBetweenFetches)
+	{
+		return; // just bail if we are disconnected.
+	}	
+
+	//HACK.  This is inelegant.  We're shuffling a dequeue to a map to get rid of 
+	//redundant requests.  When we get rid of the old code entirely, we can change
+	//the dequeue to a map.  In the new model, there is no benefit to queue order.
+	U32 folder_count=0;
+	U32 max_batch_size=10;
+	while( !(sFetchQueue.empty() ) )
+	{
+		LLViewerInventoryCategory* cat = gInventory.getCategory(sFetchQueue.front());
+		
+		if (cat)
+		{
+			if ( !gInventory.isCategoryComplete(cat->getUUID()) )	//grab this folder.
+			{
+				sBulkFetchMap[(cat->getUUID())] = cat;
+			}
+			else if (sFullFetchStarted)
+			{	//Already have this folder but append child folders to list.
+				// add all children to queue
+				parent_cat_map_t::iterator cat_it = gInventory.mParentChildCategoryTree.find(cat->getUUID());
+				if (cat_it != gInventory.mParentChildCategoryTree.end())
+				{
+					cat_array_t* child_categories = cat_it->second;
+
+					for (S32 child_num = 0; child_num < child_categories->count(); child_num++)
+					{
+						sFetchQueue.push_back(child_categories->get(child_num)->getUUID());
+					}
+				}
+
+			}
+		}
+		sFetchQueue.pop_front();
+	}
+		
+
+	if (!sBulkFetchMap.empty())	//There's stuff to fetch.
+	{
+		U32 sort_order = gSavedSettings.getU32("InventorySortOrder") & 0x1;
+
+		LLSD body;
+		
+		cat_map_t::iterator iter=sBulkFetchMap.begin();
+		while( iter!=sBulkFetchMap.end() && (folder_count < max_batch_size) )
+		{
+			LLViewerInventoryCategory* cat = iter->second;
+			
+			if (cat && !gInventory.isCategoryComplete(cat->getUUID()) ) 	//Category exists
+			{
+				BOOL fetchItems=TRUE;
+				if ( sFullFetchStarted 
+					&& gInventory.isCategoryComplete(cat->getUUID()) )
+				{
+					fetchItems=FALSE;
+				}
+				
+				LLSD folder_sd;
+				folder_sd["folder-id"]		= cat->getUUID();
+				folder_sd["owner-id"]		= cat->getOwnerID();
+				folder_sd["sort-order"]		= (LLSD::Integer)sort_order;
+				folder_sd["fetch-folders"]	= (LLSD::Boolean)sFullFetchStarted;
+				folder_sd["fetch-items"]	= (LLSD::Boolean)fetchItems;
+				body["folders"].append(folder_sd);
+
+				folder_count++;
+			}
+			sBulkFetchMap.erase(iter);
+			iter=sBulkFetchMap.begin();
+		}
+	
+		if (iter == sBulkFetchMap.end()) sBulkFetchMap.clear();
+		
+		if (folder_count > 0)
+		{
+			sBulkFetchCount++;
+			
+			LLHTTPClient::post(url, body, new LLInventoryModel::fetchDescendentsResponder(body));
+			sFetchTimer.reset();
+		}
+		
+	}	
+	
+	if (isBulkFetchProcessingComplete())
+	{
+		if (sFullFetchStarted)
+		{
+			sAllFoldersFetched = TRUE;
+		}
+		stopBackgroundFetch();
+	}	
+}
+
 // static
 bool LLInventoryModel::isEverythingFetched()
 {
@@ -1049,6 +1334,9 @@ void LLInventoryModel::stopBackgroundFetch()
 	{
 		sBackgroundFetchActive = FALSE;
 		gIdleCallbacks.deleteFunction(&LLInventoryModel::backgroundFetch, NULL);
+		sBulkFetchCount=0;
+		sMinTimeBetweenFetches=0.0f;
+//		sFullFetchStarted=FALSE;
 	}
 }
 
@@ -1057,6 +1345,15 @@ void LLInventoryModel::backgroundFetch(void*)
 {
 	if (sBackgroundFetchActive)
 	{
+		//If we'll be using the capability, we'll be sending batches and the background thing isn't as important.
+		std::string url = gAgent.getRegion()->getCapability("FetchInventoryDescendents");   
+		if (!url.empty()) 
+		{
+			bulkFetch(url);
+			return;
+		}
+		
+		//DEPRECATED OLD CODE FOLLOWS.
 		// no more categories to fetch, stop fetch process
 		if (sFetchQueue.empty())
 		{
@@ -3063,8 +3360,8 @@ void LLInventoryFetchDescendentsObserver::fetchDescendents(
 		if(!cat) continue;
 		if(!isComplete(cat))
 		{
-			cat->fetchDescendents();
-			mIncompleteFolders.push_back(*it);
+			cat->fetchDescendents();		//blindly fetch it without seeing if anything else is fetching it.
+			mIncompleteFolders.push_back(*it);	//Add to list of things being downloaded for this observer.
 		}
 		else
 		{
diff --git a/indra/newview/llinventorymodel.h b/indra/newview/llinventorymodel.h
index b560aea8b2ec0c37fff97833dd21435d970d041d..79a35f78ea520f9f7cc38c4a35b0d4c25a54efb9 100644
--- a/indra/newview/llinventorymodel.h
+++ b/indra/newview/llinventorymodel.h
@@ -91,6 +91,7 @@ class LLViewerInventoryItem;
 class LLViewerInventoryCategory;
 class LLMessageSystem;
 class LLInventoryCollectFunctor;
+class LLAlertDialog;
 
 class LLInventoryModel
 {
@@ -105,11 +106,26 @@ public:
 	// These are used a lot...
 	typedef LLDynamicArray<LLPointer<LLViewerInventoryCategory> > cat_array_t;
 	typedef LLDynamicArray<LLPointer<LLViewerInventoryItem> > item_array_t;
-
 	// construction & destruction
 	LLInventoryModel();
 	~LLInventoryModel();
 
+	class fetchDescendentsResponder: public LLHTTPClient::Responder
+	{
+		public:
+			fetchDescendentsResponder(const LLSD& request_sd) : mRequestSD(request_sd) {};
+			void result(const LLSD& content);			
+			void error(U32 status, const std::string& reason);
+			static void onClickRetry(S32 option, void* userdata);
+			static void appendRetryList(LLSD retry_sd);
+		public:
+			typedef std::vector<LLViewerInventoryCategory*> folder_ref_t;
+		protected:
+			LLSD mRequestSD;
+			static LLSD sRetrySD;
+			static LLAlertDialog		*sRetryDialog;
+	};
+
 	//
 	// Accessors
 	//
@@ -263,6 +279,9 @@ public:
 
 	// make sure we have the descendents in the structure.
 	void fetchDescendentsOf(const LLUUID& folder_id);
+	
+	// Add categories to a list to be fetched in bulk.
+	static void bulkFetch(std::string url);
 
 	// call this method to request the inventory.
 	//void requestFromServer(const LLUUID& agent_id);
@@ -348,7 +367,7 @@ public:
 	static BOOL backgroundFetchActive();
 	static bool isEverythingFetched();
 	static void backgroundFetch(void*); // background fetch idle function
-
+	static void incrBulkFetch(S16 fetching) {  sBulkFetchCount+=fetching; if (sBulkFetchCount<0) sBulkFetchCount=0; }
 protected:
 
 	// Internal methods which add inventory and make sure that all of
@@ -395,7 +414,8 @@ protected:
 	static void processInventoryDescendents(LLMessageSystem* msg, void**);
 	static void processMoveInventoryItem(LLMessageSystem* msg, void**);
 	static void processFetchInventoryReply(LLMessageSystem* msg, void**);
-
+	static bool isBulkFetchProcessingComplete();
+	
 	bool messageUpdateCore(LLMessageSystem* msg, bool do_accounting);
 
 protected:
@@ -430,6 +450,7 @@ protected:
 	observer_list_t mObservers;
 
 	// completing the fetch once per session should be sufficient
+	static cat_map_t sBulkFetchMap;
 	static BOOL sBackgroundFetchActive;
 	static BOOL sTimelyFetchPending;
 	static BOOL sAllFoldersFetched; 
@@ -438,6 +459,7 @@ protected:
 	static LLFrameTimer sFetchTimer;
 	static F32 sMinTimeBetweenFetches;
 	static F32 sMaxTimeBetweenFetches;
+	static S16 sBulkFetchCount;
 
 	// This flag is used to handle an invalid inventory state.
 	bool mIsAgentInvUsable;
diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp
index 981605d1fa64f50c2383551d3ebcf498bc7bb875..01feff9b3c84604899cf097a210bc52ff16ceef9 100644
--- a/indra/newview/llviewerinventory.cpp
+++ b/indra/newview/llviewerinventory.cpp
@@ -48,6 +48,8 @@
 #include "llviewerregion.h"
 #include "llviewerobjectlist.h"
 #include "llpreviewgesture.h"
+#include "llviewerwindow.h"
+
 ///----------------------------------------------------------------------------
 /// Local function declarations, constants, enums, and typedefs
 ///----------------------------------------------------------------------------
@@ -212,6 +214,14 @@ void LLViewerInventoryItem::fetchFromServer(void) const
 	}
 }
 
+// virtual
+BOOL LLViewerInventoryItem::unpackMessage(LLSD item)
+{
+	BOOL rv = LLInventoryItem::fromLLSD(item);
+	mIsComplete = TRUE;
+	return rv;
+}
+
 // virtual
 BOOL LLViewerInventoryItem::unpackMessage(
 	LLMessageSystem* msg, const char* block, S32 block_num)
@@ -420,30 +430,42 @@ void LLViewerInventoryCategory::removeFromServer( void )
 bool LLViewerInventoryCategory::fetchDescendents()
 {
 	if((VERSION_UNKNOWN == mVersion)
-	   && mDescendentsRequested.hasExpired())
+	   && mDescendentsRequested.hasExpired())	//Expired check prevents multiple downloads.
 	{
 		const F32 FETCH_TIMER_EXPIRY = 10.0f;
 		mDescendentsRequested.reset();
 		mDescendentsRequested.setTimerExpirySec(FETCH_TIMER_EXPIRY);
 
-		LLMessageSystem* msg = gMessageSystem;
-		msg->newMessage("FetchInventoryDescendents");
-		msg->nextBlock("AgentData");
-		msg->addUUID("AgentID", gAgent.getID());
-		msg->addUUID("SessionID", gAgent.getSessionID());
-		msg->nextBlock("InventoryData");
-		msg->addUUID("FolderID", mUUID);
-		msg->addUUID("OwnerID", mOwnerID);
 		// bitfield
 		// 1 = by date
 		// 2 = folders by date
 		// Need to mask off anything but the first bit.
 		// This comes from LLInventoryFilter from llfolderview.h
 		U32 sort_order = gSavedSettings.getU32("InventorySortOrder") & 0x1;
-		msg->addS32("SortOrder", sort_order);
-		msg->addBOOL("FetchFolders", FALSE);
-		msg->addBOOL("FetchItems", TRUE);
-		gAgent.sendReliableMessage();
+
+		std::string url = gAgent.getRegion()->getCapability("FetchInventoryDescendents");
+   
+		if (!url.empty()) //Capability found.  Build up LLSD and use it.
+		{
+			LLInventoryModel::startBackgroundFetch(mUUID);			
+		}
+		else
+		{	//Deprecated, but if we don't have a capability, use the old system.
+			llinfos << "FetchInventoryDescendents capability not found.  Using deprecated UDP message." << llendl;
+			LLMessageSystem* msg = gMessageSystem;
+			msg->newMessage("FetchInventoryDescendents");
+			msg->nextBlock("AgentData");
+			msg->addUUID("AgentID", gAgent.getID());
+			msg->addUUID("SessionID", gAgent.getSessionID());
+			msg->nextBlock("InventoryData");
+			msg->addUUID("FolderID", mUUID);
+			msg->addUUID("OwnerID", mOwnerID);
+
+			msg->addS32("SortOrder", sort_order);
+			msg->addBOOL("FetchFolders", FALSE);
+			msg->addBOOL("FetchItems", TRUE);
+			gAgent.sendReliableMessage();
+		}
 		return true;
 	}
 	return false;
diff --git a/indra/newview/llviewerinventory.h b/indra/newview/llviewerinventory.h
index fd6928243b4b07c6ccb7ea07b3c057e3d85503df..bf49a1604f89d7d21c635f07275fb198bbc41513 100644
--- a/indra/newview/llviewerinventory.h
+++ b/indra/newview/llviewerinventory.h
@@ -99,6 +99,7 @@ public:
 
 	//virtual void packMessage(LLMessageSystem* msg) const;
 	virtual BOOL unpackMessage(LLMessageSystem* msg, const char* block, S32 block_num = 0);
+	virtual BOOL unpackMessage(LLSD item);
 	virtual BOOL importFile(FILE* fp);
 	virtual BOOL importLegacyStream(std::istream& input_stream);
 
diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp
index 3f0f5bee98082da6025fe6a6d91a4b6df68f63f4..42654e250ba87bdb7c80602be65614868b79829f 100644
--- a/indra/newview/llviewerregion.cpp
+++ b/indra/newview/llviewerregion.cpp
@@ -1378,6 +1378,7 @@ void LLViewerRegion::setSeedCapability(const std::string& url)
 	capabilityNames.append("DispatchRegionInfo");
 	capabilityNames.append("EstateChangeInfo");
 	capabilityNames.append("EventQueueGet");
+	capabilityNames.append("FetchInventoryDescendents");
 	capabilityNames.append("GroupProposalBallot");
 	capabilityNames.append("MapLayer");
 	capabilityNames.append("MapLayerGod");
diff --git a/indra/newview/llxmlrpctransaction.cpp b/indra/newview/llxmlrpctransaction.cpp
index 3df2073c6a7e3bdde715955f18dee084817d17ae..9dc92efa81f5cc166b50920ed854981d68202497 100644
--- a/indra/newview/llxmlrpctransaction.cpp
+++ b/indra/newview/llxmlrpctransaction.cpp
@@ -33,10 +33,10 @@
 
 #include "llxmlrpctransaction.h"
 
+#include "llcurl.h"
 #include "llviewercontrol.h"
 
 // Have to include these last to avoid queue redefinition!
-#include <curl/curl.h>
 #include <xmlrpc-epi/xmlrpc.h>
 
 #include "llappviewer.h"
@@ -150,51 +150,48 @@ class LLXMLRPCTransaction::Impl
 {
 public:
 	typedef LLXMLRPCTransaction::Status	Status;
-	
-	CURL*	mCurl;
-	CURLM*	mCurlMulti;
+
+	LLCurlEasyRequest* mCurlRequest;
 
 	Status		mStatus;
 	CURLcode	mCurlCode;
 	std::string	mStatusMessage;
 	std::string	mStatusURI;
+	LLCurl::TransferInfo mTransferInfo;
 	
-	char				mCurlErrorBuffer[CURL_ERROR_SIZE];		/* Flawfinder: ignore */
-
 	std::string			mURI;
 	char*				mRequestText;
 	int					mRequestTextSize;
 	
 	std::string			mProxyAddress;
-	struct curl_slist*	mHeaders;
 
 	std::string			mResponseText;
 	XMLRPC_REQUEST		mResponse;
 	
 	Impl(const std::string& uri, XMLRPC_REQUEST request, bool useGzip);
 	Impl(const std::string& uri,
-		const std::string& method, LLXMLRPCValue params, bool useGzip);
+		 const std::string& method, LLXMLRPCValue params, bool useGzip);
 	~Impl();
 	
 	bool process();
 	
 	void setStatus(Status code,
-		const std::string& message = "", const std::string& uri = "");
+				   const std::string& message = "", const std::string& uri = "");
 	void setCurlStatus(CURLcode);
 
 private:
 	void init(XMLRPC_REQUEST request, bool useGzip);
 
 	static size_t curlDownloadCallback(
-		void* data, size_t size, size_t nmemb, void* user_data);
+		char* data, size_t size, size_t nmemb, void* user_data);
 };
 
 LLXMLRPCTransaction::Impl::Impl(const std::string& uri,
 		XMLRPC_REQUEST request, bool useGzip)
-	: mCurl(0), mCurlMulti(0),
+	: mCurlRequest(0),
 	  mStatus(LLXMLRPCTransaction::StatusNotStarted),
 	  mURI(uri),
-	  mRequestText(0), mHeaders(0),
+	  mRequestText(0), 
 	  mResponse(0)
 {
 	init(request, useGzip);
@@ -203,10 +200,10 @@ LLXMLRPCTransaction::Impl::Impl(const std::string& uri,
 
 LLXMLRPCTransaction::Impl::Impl(const std::string& uri,
 		const std::string& method, LLXMLRPCValue params, bool useGzip)
-	: mCurl(0), mCurlMulti(0),
+	: mCurlRequest(0),
 	  mStatus(LLXMLRPCTransaction::StatusNotStarted),
 	  mURI(uri),
-	  mRequestText(0), mHeaders(0),
+	  mRequestText(0), 
 	  mResponse(0)
 {
 	XMLRPC_REQUEST request = XMLRPC_RequestNew();
@@ -222,55 +219,53 @@ LLXMLRPCTransaction::Impl::Impl(const std::string& uri,
 
 void LLXMLRPCTransaction::Impl::init(XMLRPC_REQUEST request, bool useGzip)
 {
-	mCurl = curl_easy_init();
-
+	if (!mCurlRequest)
+	{
+		mCurlRequest = new LLCurlEasyRequest();
+	}
+	
 	if (gSavedSettings.getBOOL("BrowserProxyEnabled"))
 	{
 		mProxyAddress = gSavedSettings.getString("BrowserProxyAddress");
 		S32 port = gSavedSettings.getS32 ( "BrowserProxyPort" );
 
 		// tell curl about the settings
-		curl_easy_setopt(mCurl, CURLOPT_PROXY, mProxyAddress.c_str());
-		curl_easy_setopt(mCurl, CURLOPT_PROXYPORT, (long) port);
-		curl_easy_setopt(mCurl, CURLOPT_PROXYTYPE, (long) CURLPROXY_HTTP);
-	};
-
-//	curl_easy_setopt(mCurl, CURLOPT_VERBOSE, 1L); // usefull for debugging
-	curl_easy_setopt(mCurl, CURLOPT_NOSIGNAL, 1L);
-	curl_easy_setopt(mCurl, CURLOPT_WRITEFUNCTION, &curlDownloadCallback);
-	curl_easy_setopt(mCurl, CURLOPT_WRITEDATA, this);
-	curl_easy_setopt(mCurl, CURLOPT_ERRORBUFFER, &mCurlErrorBuffer);
-	curl_easy_setopt(mCurl, CURLOPT_CAINFO, gDirUtilp->getCAFile().c_str());
-	curl_easy_setopt(mCurl, CURLOPT_SSL_VERIFYPEER, (long) gVerifySSLCert);
-	curl_easy_setopt(mCurl, CURLOPT_SSL_VERIFYHOST, gVerifySSLCert? 2L : 0L);
+		mCurlRequest->setoptString(CURLOPT_PROXY, mProxyAddress);
+		mCurlRequest->setopt(CURLOPT_PROXYPORT, port);
+		mCurlRequest->setopt(CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
+	}
+
+//	mCurlRequest->setopt(CURLOPT_VERBOSE, 1); // usefull for debugging
+	mCurlRequest->setopt(CURLOPT_NOSIGNAL, 1);
+	mCurlRequest->setWriteCallback(&curlDownloadCallback, (void*)this);
+	mCurlRequest->setopt(CURLOPT_SSL_VERIFYPEER, gVerifySSLCert);
+	mCurlRequest->setopt(CURLOPT_SSL_VERIFYHOST, gVerifySSLCert? 2 : 0);
 	// Be a little impatient about establishing connections.
-	curl_easy_setopt(mCurl, CURLOPT_CONNECTTIMEOUT, 40L);
+	mCurlRequest->setopt(CURLOPT_CONNECTTIMEOUT, 40L);
 
 	/* Setting the DNS cache timeout to -1 disables it completely.
 	   This might help with bug #503 */
-	curl_easy_setopt(mCurl, CURLOPT_DNS_CACHE_TIMEOUT, -1L);
+	mCurlRequest->setopt(CURLOPT_DNS_CACHE_TIMEOUT, -1);
+
+    mCurlRequest->slist_append("Content-Type: text/xml");
 
-    mHeaders = curl_slist_append(mHeaders, "Content-Type: text/xml");
-	curl_easy_setopt(mCurl, CURLOPT_URL, mURI.c_str());
-	curl_easy_setopt(mCurl, CURLOPT_HTTPHEADER, mHeaders);
 	if (useGzip)
 	{
-		curl_easy_setopt(mCurl, CURLOPT_ENCODING, "");
+		mCurlRequest->setoptString(CURLOPT_ENCODING, "");
 	}
 	
 	mRequestText = XMLRPC_REQUEST_ToXML(request, &mRequestTextSize);
 	if (mRequestText)
 	{
-		curl_easy_setopt(mCurl, CURLOPT_POSTFIELDS, mRequestText);
-		curl_easy_setopt(mCurl, CURLOPT_POSTFIELDSIZE, (long) mRequestTextSize);
+		mCurlRequest->setoptString(CURLOPT_POSTFIELDS, mRequestText);
+		mCurlRequest->setopt(CURLOPT_POSTFIELDSIZE, mRequestTextSize);
 	}
 	else
 	{
 		setStatus(StatusOtherError);
 	}
-	
-	mCurlMulti = curl_multi_init();
-	curl_multi_add_handle(mCurlMulti, mCurl);
+
+	mCurlRequest->sendRequest(mURI);
 }
 
 
@@ -281,30 +276,12 @@ LLXMLRPCTransaction::Impl::~Impl()
 		XMLRPC_RequestFree(mResponse, 1);
 	}
 	
-	if (mHeaders)
-	{
-		curl_slist_free_all(mHeaders);
-	}
-	
 	if (mRequestText)
 	{
 		XMLRPC_Free(mRequestText);
 	}
 	
-	if (mCurl)
-	{
-		if (mCurlMulti)
-		{
-			curl_multi_remove_handle(mCurlMulti, mCurl);
-		}
-		curl_easy_cleanup(mCurl);
-	}
-	
-	if (mCurlMulti)
-	{
-		curl_multi_cleanup(mCurlMulti);
-	}
-	
+	delete mCurlRequest;
 }
 
 bool LLXMLRPCTransaction::Impl::process()
@@ -333,27 +310,28 @@ bool LLXMLRPCTransaction::Impl::process()
 	
 	const F32 MAX_PROCESSING_TIME = 0.05f;
 	LLTimer timer;
-	int count;
-	
-	while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(mCurlMulti, &count))
+
+	while (mCurlRequest->perform() > 0)
 	{
 		if (timer.getElapsedTimeF32() >= MAX_PROCESSING_TIME)
 		{
 			return false;
 		}
 	}
-			 
-	while(CURLMsg* curl_msg = curl_multi_info_read(mCurlMulti, &count))
+
+	while(1)
 	{
-		if (CURLMSG_DONE == curl_msg->msg)
+		CURLcode result;
+		bool newmsg = mCurlRequest->getResult(&result, &mTransferInfo);
+		if (newmsg)
 		{
-			if (curl_msg->data.result != CURLE_OK)
+			if (result != CURLE_OK)
 			{
-				setCurlStatus(curl_msg->data.result);
+				setCurlStatus(result);
 				llwarns << "LLXMLRPCTransaction CURL error "
-					<< mCurlCode << ": " << mCurlErrorBuffer << llendl;
+						<< mCurlCode << ": " << mCurlRequest->getErrorString() << llendl;
 				llwarns << "LLXMLRPCTransaction request URI: "
-					<< mURI << llendl;
+						<< mURI << llendl;
 					
 				return true;
 			}
@@ -361,7 +339,7 @@ bool LLXMLRPCTransaction::Impl::process()
 			setStatus(LLXMLRPCTransaction::StatusComplete);
 
 			mResponse = XMLRPC_REQUEST_FromXML(
-				mResponseText.data(), mResponseText.size(), NULL);
+					mResponseText.data(), mResponseText.size(), NULL);
 
 			bool		hasError = false;
 			bool		hasFault = false;
@@ -387,15 +365,19 @@ bool LLXMLRPCTransaction::Impl::process()
 				setStatus(LLXMLRPCTransaction::StatusXMLRPCError);
 				
 				llwarns << "LLXMLRPCTransaction XMLRPC "
-					<< (hasError ? "error " : "fault ")
-					<< faultCode << ": "
-					<< faultString << llendl;
+						<< (hasError ? "error " : "fault ")
+						<< faultCode << ": "
+						<< faultString << llendl;
 				llwarns << "LLXMLRPCTransaction request URI: "
-					<< mURI << llendl;
+						<< mURI << llendl;
 			}
 			
 			return true;
 		}
+		else
+		{
+			break; // done
+		}
 	}
 	
 	return false;
@@ -504,13 +486,13 @@ void LLXMLRPCTransaction::Impl::setCurlStatus(CURLcode code)
 }
 
 size_t LLXMLRPCTransaction::Impl::curlDownloadCallback(
-		void* data, size_t size, size_t nmemb, void* user_data)
+		char* data, size_t size, size_t nmemb, void* user_data)
 {
 	Impl& impl(*(Impl*)user_data);
 	
 	size_t n = size * nmemb;
 
-	impl.mResponseText.append((const char*)data, n);
+	impl.mResponseText.append(data, n);
 	
 	if (impl.mStatus == LLXMLRPCTransaction::StatusStarted)
 	{
@@ -579,25 +561,17 @@ LLXMLRPCValue LLXMLRPCTransaction::responseValue()
 
 F64 LLXMLRPCTransaction::transferRate()
 {
-	if (!impl.mCurl  ||  impl.mStatus != StatusComplete)
+	if (impl.mStatus != StatusComplete)
 	{
 		return 0.0L;
 	}
 	
-	double size_bytes = 0.0;
-	double time_seconds = 0.0;
-	double rate_bytes_per_sec = 0.0;
-
-	curl_easy_getinfo(impl.mCurl, CURLINFO_SIZE_DOWNLOAD, &size_bytes);
-	curl_easy_getinfo(impl.mCurl, CURLINFO_TOTAL_TIME, &time_seconds);
-	curl_easy_getinfo(impl.mCurl, CURLINFO_SPEED_DOWNLOAD, &rate_bytes_per_sec);
-
-	double rate_bits_per_sec = rate_bytes_per_sec * 8.0;
+	double rate_bits_per_sec = impl.mTransferInfo.mSpeedDownload * 8.0;
 	
 	llinfos << "Buffer size:   " << impl.mResponseText.size() << " B" << llendl;
-	llinfos << "Transfer size: " << size_bytes << " B" << llendl;
-	llinfos << "Transfer time: " << time_seconds << " s" << llendl;
-	llinfos << "Transfer rate: " << rate_bits_per_sec/1000.0 << " Kb/s" << llendl;
+	llinfos << "Transfer size: " << impl.mTransferInfo.mSizeDownload << " B" << llendl;
+	llinfos << "Transfer time: " << impl.mTransferInfo.mTotalTime << " s" << llendl;
+	llinfos << "Transfer rate: " << rate_bits_per_sec / 1000.0 << " Kb/s" << llendl;
 
 	return rate_bits_per_sec;
 }