From 6fcc0994dff2bd88256ff8306c8df670df627ef6 Mon Sep 17 00:00:00 2001
From: Aimee Linden <aimee@lindenlab.com>
Date: Mon, 28 Jun 2010 16:30:08 +0100
Subject: [PATCH] EXT-7498 WIP Snapshot Sharing

Reviewed by Tofu.
---
 indra/llmessage/llhttpclient.cpp              |  11 +-
 indra/llmessage/llurlrequest.cpp              |   5 +
 indra/llmessage/llurlrequest.h                |   5 +
 indra/newview/CMakeLists.txt                  |   2 +
 indra/newview/app_settings/settings.xml       |  22 +
 indra/newview/llfloatersnapshot.cpp           | 182 ++++--
 indra/newview/llstartup.cpp                   |   7 +
 indra/newview/llviewermedia.cpp               |   4 +
 indra/newview/llwebsharing.cpp                | 609 ++++++++++++++++++
 indra/newview/llwebsharing.h                  | 230 +++++++
 .../skins/default/xui/en/floater_snapshot.xml |  34 +-
 11 files changed, 1057 insertions(+), 54 deletions(-)
 create mode 100644 indra/newview/llwebsharing.cpp
 create mode 100644 indra/newview/llwebsharing.h

diff --git a/indra/llmessage/llhttpclient.cpp b/indra/llmessage/llhttpclient.cpp
index e8dc2071144..5d9448b42c6 100644
--- a/indra/llmessage/llhttpclient.cpp
+++ b/indra/llmessage/llhttpclient.cpp
@@ -240,9 +240,14 @@ static void request(
 	lldebugs << LLURLRequest::actionAsVerb(method) << " " << url << " "
 		<< headers << llendl;
 
-    // Insert custom headers is the caller sent any
-    if (headers.isMap())
-    {
+	// Insert custom headers if the caller sent any
+	if (headers.isMap())
+	{
+		if (headers.has("Cookie"))
+		{
+			req->allowCookies();
+		}
+
         LLSD::map_const_iterator iter = headers.beginMap();
         LLSD::map_const_iterator end  = headers.endMap();
 
diff --git a/indra/llmessage/llurlrequest.cpp b/indra/llmessage/llurlrequest.cpp
index 1e76d108289..295f69e902a 100644
--- a/indra/llmessage/llurlrequest.cpp
+++ b/indra/llmessage/llurlrequest.cpp
@@ -251,6 +251,11 @@ void LLURLRequest::useProxy(const std::string &proxy)
     mDetail->mCurlRequest->setoptString(CURLOPT_PROXY, proxy);
 }
 
+void LLURLRequest::allowCookies()
+{
+	mDetail->mCurlRequest->setoptString(CURLOPT_COOKIEFILE, "");
+}
+
 // virtual
 LLIOPipe::EStatus LLURLRequest::handleError(
 	LLIOPipe::EStatus status,
diff --git a/indra/llmessage/llurlrequest.h b/indra/llmessage/llurlrequest.h
index 69fd22e5928..378cc563743 100644
--- a/indra/llmessage/llurlrequest.h
+++ b/indra/llmessage/llurlrequest.h
@@ -189,6 +189,11 @@ class LLURLRequest : public LLIOPipe
      */
 	void useProxy(const std::string& proxy);
 
+	/**
+	 * @brief Turn on cookie handling for this request with CURLOPT_COOKIEFILE.
+	 */
+	void allowCookies();
+
 public:
 	/** 
 	 * @brief Give this pipe a chance to handle a generated error
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index ce42cb60381..e7475114b26 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -552,6 +552,7 @@ set(viewer_SOURCE_FILES
     llwearablelist.cpp
     llwearabletype.cpp
     llweb.cpp
+    llwebsharing.cpp
     llwind.cpp
     llwlanimator.cpp
     llwldaycycle.cpp
@@ -1076,6 +1077,7 @@ set(viewer_HEADER_FILES
     llwearablelist.h
     llwearabletype.h
     llweb.h
+    llwebsharing.h
     llwind.h
     llwlanimator.h
     llwldaycycle.h
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 50eee73c4c8..dd93f1bfa6b 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -9483,6 +9483,28 @@
       <key>Value</key>
       <integer>75</integer>
     </map>
+    <key>SnapshotSharingEnabled</key>
+    <map>
+      <key>Comment</key>
+      <string>Enable uploading of snapshots to a web service.</string>
+      <key>Persist</key>
+      <integer>1</integer>
+      <key>Type</key>
+      <string>Boolean</string>
+      <key>Value</key>
+      <integer>0</integer>
+    </map>
+    <key>SnapshotConfigURL</key>
+    <map>
+      <key>Comment</key>
+      <string>URL to fetch Snapshot Sharing configuration data from.</string>
+      <key>Persist</key>
+      <integer>1</integer>
+      <key>Type</key>
+      <string>String</string>
+      <key>Value</key>
+      <string>http://photos.apps.staging.avatarsunited.com/viewer_config</string>
+    </map>
     <key>SnapshotTextureLastResolution</key>
     <map>
       <key>Comment</key>
diff --git a/indra/newview/llfloatersnapshot.cpp b/indra/newview/llfloatersnapshot.cpp
index f3baa482a0a..43ea6143b10 100644
--- a/indra/newview/llfloatersnapshot.cpp
+++ b/indra/newview/llfloatersnapshot.cpp
@@ -46,6 +46,7 @@
 #include "llbutton.h"
 #include "llcombobox.h"
 #include "lleconomy.h"
+#include "lllandmarkactions.h"
 #include "llsliderctrl.h"
 #include "llspinctrl.h"
 #include "llviewercontrol.h"
@@ -59,6 +60,7 @@
 #include "llradiogroup.h"
 #include "lltoolfocus.h"
 #include "lltoolmgr.h"
+#include "llwebsharing.h"
 #include "llworld.h"
 #include "llagentui.h"
 
@@ -86,8 +88,8 @@
 ///----------------------------------------------------------------------------
 /// Local function declarations, constants, enums, and typedefs
 ///----------------------------------------------------------------------------
-S32 LLFloaterSnapshot::sUIWinHeightLong = 526 ;
-S32 LLFloaterSnapshot::sUIWinHeightShort = LLFloaterSnapshot::sUIWinHeightLong - 230 ;
+S32 LLFloaterSnapshot::sUIWinHeightLong = 530 ;
+S32 LLFloaterSnapshot::sUIWinHeightShort = LLFloaterSnapshot::sUIWinHeightLong - 240 ;
 S32 LLFloaterSnapshot::sUIWinWidth = 215 ;
 
 LLSnapshotFloaterView* gSnapshotFloaterView = NULL;
@@ -115,7 +117,8 @@ class LLSnapshotLivePreview : public LLView
 	{
 		SNAPSHOT_POSTCARD,
 		SNAPSHOT_TEXTURE,
-		SNAPSHOT_LOCAL
+		SNAPSHOT_LOCAL,
+		SNAPSHOT_WEB
 	};
 
 
@@ -161,6 +164,7 @@ class LLSnapshotLivePreview : public LLView
 	void setSnapshotQuality(S32 quality);
 	void setSnapshotBufferType(LLViewerWindow::ESnapshotType type) { mSnapshotBufferType = type; }
 	void updateSnapshot(BOOL new_snapshot, BOOL new_thumbnail = FALSE, F32 delay = 0.f);
+	void saveWeb();
 	LLFloaterPostcard* savePostcard();
 	void saveTexture();
 	BOOL saveLocal();
@@ -173,6 +177,9 @@ class LLSnapshotLivePreview : public LLView
 	// Returns TRUE when snapshot generated, FALSE otherwise.
 	static BOOL onIdle( void* snapshot_preview );
 
+	// callback for region name resolve
+	void regionNameCallback(LLImageJPEG* snapshot, LLSD& metadata, const std::string& name, S32 x, S32 y, S32 z);
+
 private:
 	LLColor4					mColor;
 	LLPointer<LLViewerTexture>	mViewerImage[2]; //used to represent the scene when the frame is frozen.
@@ -826,9 +833,19 @@ BOOL LLSnapshotLivePreview::onIdle( void* snapshot_preview )
 			// delete any existing image
 			previewp->mFormattedImage = NULL;
 			// now create the new one of the appropriate format.
-			// note: postcards hardcoded to use jpeg always.
-			LLFloaterSnapshot::ESnapshotFormat format = previewp->getSnapshotType() == SNAPSHOT_POSTCARD
-				? LLFloaterSnapshot::SNAPSHOT_FORMAT_JPEG : previewp->getSnapshotFormat();
+			// note: postcards and web hardcoded to use jpeg always.
+			LLFloaterSnapshot::ESnapshotFormat format;
+
+			if (previewp->getSnapshotType() == SNAPSHOT_POSTCARD ||
+				previewp->getSnapshotType() == SNAPSHOT_WEB)
+			{
+				format = LLFloaterSnapshot::SNAPSHOT_FORMAT_JPEG;
+			}
+			else
+			{
+				format = previewp->getSnapshotFormat();
+			}
+
 			switch(format)
 			{
 			case LLFloaterSnapshot::SNAPSHOT_FORMAT_PNG:
@@ -1021,6 +1038,33 @@ BOOL LLSnapshotLivePreview::saveLocal()
 	return success;
 }
 
+void LLSnapshotLivePreview::saveWeb()
+{
+	// *FIX: Will break if the window closes because of CloseSnapshotOnKeep!
+	// Needs to pass on ownership of the image.
+	LLImageJPEG* jpg = dynamic_cast<LLImageJPEG*>(mFormattedImage.get());
+	if(!jpg)
+	{
+		llwarns << "Formatted image not a JPEG" << llendl;
+		return;
+	}
+
+	LLSD metadata;
+	metadata["description"] = getChild<LLLineEditor>("description")->getText();
+
+	LLLandmarkActions::getRegionNameAndCoordsFromPosGlobal(gAgentCamera.getCameraPositionGlobal(),
+		boost::bind(&LLSnapshotLivePreview::regionNameCallback, this, jpg, metadata, _1, _2, _3, _4));
+
+	gViewerWindow->playSnapshotAnimAndSound();
+}
+
+void LLSnapshotLivePreview::regionNameCallback(LLImageJPEG* snapshot, LLSD& metadata, const std::string& name, S32 x, S32 y, S32 z)
+{
+	metadata["slurl"] = LLSLURL(name, LLVector3d(x, y, z)).getSLURLString();
+
+	LLWebSharing::instance().shareSnapshot(snapshot, metadata);
+}
+
 ///----------------------------------------------------------------------------
 /// Class LLFloaterSnapshot::Impl
 ///----------------------------------------------------------------------------
@@ -1071,6 +1115,7 @@ class LLFloaterSnapshot::Impl
 
 private:
 	static LLSnapshotLivePreview::ESnapshotType getTypeIndex(LLFloaterSnapshot* floater);
+	static LLSD getTypeName(LLSnapshotLivePreview::ESnapshotType index);
 	static ESnapshotFormat getFormatIndex(LLFloaterSnapshot* floater);
 	static LLViewerWindow::ESnapshotType getLayerType(LLFloaterSnapshot* floater);
 	static void comboSetCustom(LLFloaterSnapshot *floater, const std::string& comboname);
@@ -1097,16 +1142,50 @@ LLSnapshotLivePreview::ESnapshotType LLFloaterSnapshot::Impl::getTypeIndex(LLFlo
 {
 	LLSnapshotLivePreview::ESnapshotType index = LLSnapshotLivePreview::SNAPSHOT_POSTCARD;
 	LLSD value = floater->childGetValue("snapshot_type_radio");
+
 	const std::string id = value.asString();
 	if (id == "postcard")
+	{
 		index = LLSnapshotLivePreview::SNAPSHOT_POSTCARD;
+	}
 	else if (id == "texture")
+	{
 		index = LLSnapshotLivePreview::SNAPSHOT_TEXTURE;
+	}
 	else if (id == "local")
+	{
 		index = LLSnapshotLivePreview::SNAPSHOT_LOCAL;
+	}
+	else if (id == "share_to_web")
+	{
+		index = LLSnapshotLivePreview::SNAPSHOT_WEB;
+	}
+
 	return index;
 }
 
+// static
+LLSD LLFloaterSnapshot::Impl::getTypeName(LLSnapshotLivePreview::ESnapshotType index)
+{
+	std::string id;
+	switch (index)
+	{
+		case LLSnapshotLivePreview::SNAPSHOT_WEB:
+			id = "share_to_web";
+			break;
+		case LLSnapshotLivePreview::SNAPSHOT_POSTCARD:
+			id = "postcard";
+			break;
+		case LLSnapshotLivePreview::SNAPSHOT_TEXTURE:
+			id = "texture";
+			break;
+		case LLSnapshotLivePreview::SNAPSHOT_LOCAL:
+		default:
+			id = "local";
+			break;
+	}
+	return LLSD(id);
+}
 
 // static
 LLFloaterSnapshot::ESnapshotFormat LLFloaterSnapshot::Impl::getFormatIndex(LLFloaterSnapshot* floater)
@@ -1243,11 +1322,14 @@ void LLFloaterSnapshot::Impl::updateLayout(LLFloaterSnapshot* floaterp)
 void LLFloaterSnapshot::Impl::updateControls(LLFloaterSnapshot* floater)
 {
 	LLRadioGroup* snapshot_type_radio = floater->getChild<LLRadioGroup>("snapshot_type_radio");
-	snapshot_type_radio->setSelectedIndex(gSavedSettings.getS32("LastSnapshotType"));
-	LLSnapshotLivePreview::ESnapshotType shot_type = getTypeIndex(floater);
-	ESnapshotFormat shot_format = (ESnapshotFormat)gSavedSettings.getS32("SnapshotFormat"); //getFormatIndex(floater);	LLViewerWindow::ESnapshotType layer_type = getLayerType(floater);
+	LLSnapshotLivePreview::ESnapshotType shot_type = (LLSnapshotLivePreview::ESnapshotType)gSavedSettings.getS32("LastSnapshotType");
+	snapshot_type_radio->setSelectedByValue(getTypeName(shot_type), true);
+
+	ESnapshotFormat shot_format = (ESnapshotFormat)gSavedSettings.getS32("SnapshotFormat");
 	LLViewerWindow::ESnapshotType layer_type = getLayerType(floater);
 
+	floater->childSetVisible("share_to_web", gSavedSettings.getBOOL("SnapshotSharingEnabled"));
+
 	floater->childSetVisible("postcard_size_combo", FALSE);
 	floater->childSetVisible("texture_size_combo", FALSE);
 	floater->childSetVisible("local_size_combo", FALSE);
@@ -1257,17 +1339,19 @@ void LLFloaterSnapshot::Impl::updateControls(LLFloaterSnapshot* floater)
 	floater->getChild<LLComboBox>("local_size_combo")->selectNthItem(gSavedSettings.getS32("SnapshotLocalLastResolution"));
 	floater->getChild<LLComboBox>("local_format_combo")->selectNthItem(gSavedSettings.getS32("SnapshotFormat"));
 
+	// *TODO: Separate settings for Web images from postcards
+	floater->childSetVisible("send_btn",			shot_type == LLSnapshotLivePreview::SNAPSHOT_POSTCARD ||
+													shot_type == LLSnapshotLivePreview::SNAPSHOT_WEB);
 	floater->childSetVisible("upload_btn",			shot_type == LLSnapshotLivePreview::SNAPSHOT_TEXTURE);
-	floater->childSetVisible("send_btn",			shot_type == LLSnapshotLivePreview::SNAPSHOT_POSTCARD);
 	floater->childSetVisible("save_btn",			shot_type == LLSnapshotLivePreview::SNAPSHOT_LOCAL);
 	floater->childSetEnabled("keep_aspect_check",	shot_type != LLSnapshotLivePreview::SNAPSHOT_TEXTURE && !floater->impl.mAspectRatioCheckOff);
 	floater->childSetEnabled("layer_types",			shot_type == LLSnapshotLivePreview::SNAPSHOT_LOCAL);
 
 	BOOL is_advance = gSavedSettings.getBOOL("AdvanceSnapshot");
 	BOOL is_local = shot_type == LLSnapshotLivePreview::SNAPSHOT_LOCAL;
-	BOOL show_slider = 
-		shot_type == LLSnapshotLivePreview::SNAPSHOT_POSTCARD
-		|| (is_local && shot_format == LLFloaterSnapshot::SNAPSHOT_FORMAT_JPEG);
+	BOOL show_slider = (shot_type == LLSnapshotLivePreview::SNAPSHOT_POSTCARD ||
+						shot_type == LLSnapshotLivePreview::SNAPSHOT_WEB ||
+					   (is_local && shot_format == LLFloaterSnapshot::SNAPSHOT_FORMAT_JPEG));
 
 	floater->childSetVisible("more_btn", !is_advance); // the only item hidden in advanced mode
 	floater->childSetVisible("less_btn",				is_advance);
@@ -1290,7 +1374,10 @@ void LLFloaterSnapshot::Impl::updateControls(LLFloaterSnapshot* floater)
 	BOOL got_bytes = previewp && previewp->getDataSize() > 0;
 	BOOL got_snap = previewp && previewp->getSnapshotUpToDate();
 
-	floater->childSetEnabled("send_btn",   shot_type == LLSnapshotLivePreview::SNAPSHOT_POSTCARD && got_snap && previewp->getDataSize() <= MAX_POSTCARD_DATASIZE);
+	// *TODO: Separate maximum size for Web images from postcards
+	floater->childSetEnabled("send_btn",   (shot_type == LLSnapshotLivePreview::SNAPSHOT_POSTCARD ||
+											shot_type == LLSnapshotLivePreview::SNAPSHOT_WEB) &&
+											got_snap && previewp->getDataSize() <= MAX_POSTCARD_DATASIZE);
 	floater->childSetEnabled("upload_btn", shot_type == LLSnapshotLivePreview::SNAPSHOT_TEXTURE  && got_snap);
 	floater->childSetEnabled("save_btn",   shot_type == LLSnapshotLivePreview::SNAPSHOT_LOCAL    && got_snap);
 
@@ -1311,6 +1398,8 @@ void LLFloaterSnapshot::Impl::updateControls(LLFloaterSnapshot* floater)
 
 	switch(shot_type)
 	{
+	  // *TODO: Separate settings for Web images from postcards
+	  case LLSnapshotLivePreview::SNAPSHOT_WEB:
 	  case LLSnapshotLivePreview::SNAPSHOT_POSTCARD:
 		layer_type = LLViewerWindow::SNAPSHOT_TYPE_COLOR;
 		floater->childSetValue("layer_types", "colors");
@@ -1402,28 +1491,39 @@ void LLFloaterSnapshot::Impl::onClickKeep(void* data)
 {
 	LLFloaterSnapshot *view = (LLFloaterSnapshot *)data;
 	LLSnapshotLivePreview* previewp = getPreviewView(view);
-	
+
 	if (previewp)
 	{
-		if (previewp->getSnapshotType() == LLSnapshotLivePreview::SNAPSHOT_POSTCARD)
+		switch (previewp->getSnapshotType())
 		{
-			LLFloaterPostcard* floater = previewp->savePostcard();
-			// if still in snapshot mode, put postcard floater in snapshot floaterview
-			// and link it to snapshot floater
-			if (floater && !gSavedSettings.getBOOL("CloseSnapshotOnKeep"))
+		  case LLSnapshotLivePreview::SNAPSHOT_WEB:
+			previewp->saveWeb();
+			break;
+
+		  case LLSnapshotLivePreview::SNAPSHOT_POSTCARD:
 			{
-				gFloaterView->removeChild(floater);
-				gSnapshotFloaterView->addChild(floater);
-				view->addDependentFloater(floater, FALSE);
+				LLFloaterPostcard* floater = previewp->savePostcard();
+				// if still in snapshot mode, put postcard floater in snapshot floaterview
+				// and link it to snapshot floater
+				if (floater && !gSavedSettings.getBOOL("CloseSnapshotOnKeep"))
+				{
+					gFloaterView->removeChild(floater);
+					gSnapshotFloaterView->addChild(floater);
+					view->addDependentFloater(floater, FALSE);
+				}
 			}
-		}
-		else if (previewp->getSnapshotType() == LLSnapshotLivePreview::SNAPSHOT_TEXTURE)
-		{
+			break;
+
+		  case LLSnapshotLivePreview::SNAPSHOT_TEXTURE:
 			previewp->saveTexture();
-		}
-		else
-		{
+			break;
+
+		  case LLSnapshotLivePreview::SNAPSHOT_LOCAL:
 			previewp->saveLocal();
+			break;
+
+		  default:
+			break;
 		}
 
 		if (gSavedSettings.getBOOL("CloseSnapshotOnKeep"))
@@ -1648,18 +1748,22 @@ static std::string lastSnapshotWidthName()
 {
 	switch(gSavedSettings.getS32("LastSnapshotType"))
 	{
-	case LLSnapshotLivePreview::SNAPSHOT_POSTCARD: return "LastSnapshotToEmailWidth";
-	case LLSnapshotLivePreview::SNAPSHOT_TEXTURE:  return "LastSnapshotToInventoryWidth";
-	default:                                       return "LastSnapshotToDiskWidth";
+	  // *TODO: Separate settings for Web snapshots and postcards
+	  case LLSnapshotLivePreview::SNAPSHOT_WEB:		 return "LastSnapshotToEmailWidth";
+	  case LLSnapshotLivePreview::SNAPSHOT_POSTCARD: return "LastSnapshotToEmailWidth";
+	  case LLSnapshotLivePreview::SNAPSHOT_TEXTURE:  return "LastSnapshotToInventoryWidth";
+	  default:                                       return "LastSnapshotToDiskWidth";
 	}
 }
 static std::string lastSnapshotHeightName()
 {
 	switch(gSavedSettings.getS32("LastSnapshotType"))
 	{
-	case LLSnapshotLivePreview::SNAPSHOT_POSTCARD: return "LastSnapshotToEmailHeight";
-	case LLSnapshotLivePreview::SNAPSHOT_TEXTURE:  return "LastSnapshotToInventoryHeight";
-	default:                                       return "LastSnapshotToDiskHeight";
+	  // *TODO: Separate settings for Web snapshots and postcards
+	  case LLSnapshotLivePreview::SNAPSHOT_WEB:	     return "LastSnapshotToEmailHeight";
+	  case LLSnapshotLivePreview::SNAPSHOT_POSTCARD: return "LastSnapshotToEmailHeight";
+	  case LLSnapshotLivePreview::SNAPSHOT_TEXTURE:  return "LastSnapshotToInventoryHeight";
+	  default:                                       return "LastSnapshotToDiskHeight";
 	}
 }
 
@@ -1773,7 +1877,7 @@ void LLFloaterSnapshot::Impl::onCommitSnapshotType(LLUICtrl* ctrl, void* data)
 //static 
 void LLFloaterSnapshot::Impl::onCommitSnapshotFormat(LLUICtrl* ctrl, void* data)
 {
-	LLFloaterSnapshot *view = (LLFloaterSnapshot *)data;		
+	LLFloaterSnapshot *view = (LLFloaterSnapshot *)data;
 	if (view)
 	{
 		gSavedSettings.setS32("SnapshotFormat", getFormatIndex(view));
@@ -1994,6 +2098,12 @@ LLFloaterSnapshot::~LLFloaterSnapshot()
 
 BOOL LLFloaterSnapshot::postBuild()
 {
+	// Kick start Web Sharing, to fetch its config data if it needs to.
+	if (gSavedSettings.getBOOL("SnapshotSharingEnabled"))
+	{
+		LLWebSharing::instance().init();
+	}
+
 	childSetCommitCallback("snapshot_type_radio", Impl::onCommitSnapshotType, this);
 	childSetCommitCallback("local_format_combo", Impl::onCommitSnapshotFormat, this);
 	
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index 96088fed9cc..e9efc49e277 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -3148,6 +3148,13 @@ bool process_login_success_response()
 		}
 	}
 
+	// Set the location of the snapshot sharing config endpoint
+	std::string snapshot_config_url = response["snapshot_config_url"];
+	if(!snapshot_config_url.empty())
+	{
+		gSavedSettings.setString("SnapshotConfigURL", snapshot_config_url);
+	}
+
 	// Start the process of fetching the OpenID session cookie for this user login
 	std::string openid_url = response["openid_url"];
 	if(!openid_url.empty())
diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp
index 34e30b3ccd1..178d928f57e 100644
--- a/indra/newview/llviewermedia.cpp
+++ b/indra/newview/llviewermedia.cpp
@@ -56,6 +56,7 @@
 #include "llvoavatar.h"
 #include "llvoavatarself.h"
 #include "llviewerregion.h"
+#include "llwebsharing.h"	// For LLWebSharing::setOpenIDCookie(), *TODO: find a better way to do this!
 
 #include "llevent.h"		// LLSimpleListener
 #include "llnotificationsutil.h"
@@ -1318,6 +1319,9 @@ void LLViewerMedia::setOpenIDCookie()
 		}
 		
 		getCookieStore()->setCookiesFromHost(sOpenIDCookie, authority.substr(host_start, host_end - host_start));
+
+		// *HACK: Doing this here is nasty, find a better way.
+		LLWebSharing::instance().setOpenIDCookie(sOpenIDCookie);
 	}
 }
 
diff --git a/indra/newview/llwebsharing.cpp b/indra/newview/llwebsharing.cpp
new file mode 100644
index 00000000000..2b9e5cc8cb2
--- /dev/null
+++ b/indra/newview/llwebsharing.cpp
@@ -0,0 +1,609 @@
+/** 
+ * @file llwebsharing.cpp
+ * @author Aimee
+ * @brief Web Snapshot Sharing
+ *
+ * $LicenseInfo:firstyear=2010&license=viewergpl$
+ * 
+ * Copyright (c) 2010, Linden Research, Inc.
+ * 
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab.  Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ * 
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at
+ * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ * 
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ * 
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "llwebsharing.h"
+
+#include "llagentui.h"
+#include "llbufferstream.h"
+#include "llhttpclient.h"
+#include "llhttpstatuscodes.h"
+#include "llsdserialize.h"
+#include "llsdutil.h"
+#include "llurl.h"
+#include "llviewercontrol.h"
+
+#include <boost/regex.hpp>
+#include <boost/algorithm/string/replace.hpp>
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+class LLWebSharingConfigResponder : public LLHTTPClient::Responder
+{
+	LOG_CLASS(LLWebSharingConfigResponder);
+public:
+	/// Overrides the default LLSD parsing behaviour, to allow parsing a JSON response.
+	virtual void completedRaw(U32 status, const std::string& reason,
+							  const LLChannelDescriptors& channels,
+							  const LLIOPipe::buffer_ptr_t& buffer)
+	{
+		LLSD content;
+		LLBufferStream istr(channels, buffer.get());
+		LLPointer<LLSDParser> parser = new LLSDNotationParser();
+
+		if (parser->parse(istr, content, LLSDSerialize::SIZE_UNLIMITED) == LLSDParser::PARSE_FAILURE)
+		{
+			LL_WARNS("WebSharing") << "Failed to deserialize LLSD from JSON response. " << " [" << status << "]: " << reason << LL_ENDL;
+		}
+		else
+		{
+			completed(status, reason, content);
+		}
+	}
+
+	virtual void error(U32 status, const std::string& reason)
+	{
+		LL_WARNS("WebSharing") << "Error [" << status << "]: " << reason << LL_ENDL;
+	}
+
+	virtual void result(const LLSD& content)
+	{
+		LLWebSharing::instance().receiveConfig(content);
+	}
+};
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+class LLWebSharingOpenIDAuthResponder : public LLHTTPClient::Responder
+{
+	LOG_CLASS(LLWebSharingOpenIDAuthResponder);
+public:
+	/* virtual */ void completedHeader(U32 status, const std::string& reason, const LLSD& content)
+	{
+		completed(status, reason, content);
+	}
+
+	/* virtual */ void completedRaw(U32 status, const std::string& reason,
+									const LLChannelDescriptors& channels,
+									const LLIOPipe::buffer_ptr_t& buffer)
+	{
+		/// Left empty to override the default LLSD parsing behaviour.
+	}
+
+	virtual void error(U32 status, const std::string& reason)
+	{
+		if (HTTP_UNAUTHORIZED == status)
+		{
+			LL_WARNS("WebSharing") << "AU account not authenticated." << LL_ENDL;
+			// *TODO: No account found on AU, so start the account creation process here.
+		}
+		else
+		{
+			LL_WARNS("WebSharing") << "Error [" << status << "]: " << reason << LL_ENDL;
+			LLWebSharing::instance().retryOpenIDAuth();
+		}
+
+	}
+
+	virtual void result(const LLSD& content)
+	{
+		if (content.has("set-cookie"))
+		{
+			// OpenID request succeeded and returned a session cookie.
+			LLWebSharing::instance().receiveSessionCookie(content["set-cookie"].asString());
+		}
+	}
+};
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+class LLWebSharingSecurityTokenResponder : public LLHTTPClient::Responder
+{
+	LOG_CLASS(LLWebSharingSecurityTokenResponder);
+public:
+	/// Overrides the default LLSD parsing behaviour, to allow parsing a JSON response.
+	virtual void completedRaw(U32 status, const std::string& reason,
+							  const LLChannelDescriptors& channels,
+							  const LLIOPipe::buffer_ptr_t& buffer)
+	{
+		LLSD content;
+		LLBufferStream istr(channels, buffer.get());
+		LLPointer<LLSDParser> parser = new LLSDNotationParser();
+
+		if (parser->parse(istr, content, LLSDSerialize::SIZE_UNLIMITED) == LLSDParser::PARSE_FAILURE)
+		{
+			LL_WARNS("WebSharing") << "Failed to deserialize LLSD from JSON response. " << " [" << status << "]: " << reason << LL_ENDL;
+			LLWebSharing::instance().retryOpenIDAuth();
+		}
+		else
+		{
+			completed(status, reason, content);
+		}
+	}
+
+	virtual void error(U32 status, const std::string& reason)
+	{
+		LL_WARNS("WebSharing") << "Error [" << status << "]: " << reason << LL_ENDL;
+		LLWebSharing::instance().retryOpenIDAuth();
+	}
+
+	virtual void result(const LLSD& content)
+	{
+		if (content[0].has("st") && content[0].has("expires"))
+		{
+			const std::string& token   = content[0]["st"].asString();
+			const std::string& expires = content[0]["expires"].asString();
+			if (LLWebSharing::instance().receiveSecurityToken(token, expires))
+			{
+				// Sucessfully received a valid security token.
+				return;
+			}
+		}
+		else
+		{
+			LL_WARNS("WebSharing") << "No security token received." << LL_ENDL;
+		}
+
+		LLWebSharing::instance().retryOpenIDAuth();
+	}
+};
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+class LLWebSharingUploadResponder : public LLHTTPClient::Responder
+{
+	LOG_CLASS(LLWebSharingUploadResponder);
+public:
+	/// Overrides the default LLSD parsing behaviour, to allow parsing a JSON response.
+	virtual void completedRaw(U32 status, const std::string& reason,
+							  const LLChannelDescriptors& channels,
+							  const LLIOPipe::buffer_ptr_t& buffer)
+	{
+/*
+		 // Dump the body, for debugging.
+
+		 LLBufferStream istr1(channels, buffer.get());
+		 std::ostringstream ostr;
+		 std::string body;
+
+		 while (istr1.good())
+		 {
+			char buf[1024];
+			istr1.read(buf, sizeof(buf));
+			body.append(buf, istr1.gcount());
+		 }
+		 LL_DEBUGS("WebSharing") << body << LL_ENDL;
+*/
+		LLSD content;
+		LLBufferStream istr(channels, buffer.get());
+		LLPointer<LLSDParser> parser = new LLSDNotationParser();
+
+		if (parser->parse(istr, content, LLSDSerialize::SIZE_UNLIMITED) == LLSDParser::PARSE_FAILURE)
+		{
+			LL_WARNS("WebSharing") << "Failed to deserialize LLSD from JSON response. " << " [" << status << "]: " << reason << LL_ENDL;
+		}
+		else
+		{
+			completed(status, reason, content);
+		}
+	}
+
+	virtual void error(U32 status, const std::string& reason)
+	{
+		LL_WARNS("WebSharing") << "Error [" << status << "]: " << reason << LL_ENDL;
+	}
+
+	virtual void result(const LLSD& content)
+	{
+		if (content[0].has("result") && content[0].has("id") &&
+			content[0]["id"].asString() == "newMediaItem")
+		{
+			// *TODO: Upload successful, continue from here to post metadata and create AU activity.
+		}
+		else
+		{
+			LL_WARNS("WebSharing") << "Error [" << content[0]["code"].asString()
+								   << "]: " << content[0]["message"].asString() << LL_ENDL;
+		}
+	}
+};
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+LLWebSharing::LLWebSharing()
+:	mConfig(),
+	mSecurityToken(LLSD::emptyMap()),
+	mEnabled(false),
+	mRetries(0),
+	mImage(NULL),
+	mMetadata(LLSD::emptyMap())
+{
+}
+
+void LLWebSharing::init()
+{
+	if (!mEnabled)
+	{
+		sendConfigRequest();
+	}
+}
+
+bool LLWebSharing::shareSnapshot(LLImageJPEG* snapshot, LLSD& metadata)
+{
+	LL_INFOS("WebSharing") << metadata << LL_ENDL;
+
+	if (mImage)
+	{
+		// *TODO: Handle this possibility properly, queue them up?
+		LL_WARNS("WebSharing") << "Snapshot upload already in progress." << LL_ENDL;
+		return false;
+	}
+
+	mImage = snapshot;
+	mMetadata = metadata;
+
+	// *TODO: Check whether we have a valid security token already and re-use it.
+	sendOpenIDAuthRequest();
+	return true;
+}
+
+bool LLWebSharing::setOpenIDCookie(const std::string& cookie)
+{
+	LL_DEBUGS("WebSharing") << "Setting OpenID cookie " << cookie << LL_ENDL;
+	mOpenIDCookie = cookie;
+	return validateConfig();
+}
+
+bool LLWebSharing::receiveConfig(const LLSD& config)
+{
+	LL_DEBUGS("WebSharing") << "Received config data: " << config << LL_ENDL;
+	mConfig = config;
+	return validateConfig();
+}
+
+bool LLWebSharing::receiveSessionCookie(const std::string& cookie)
+{
+	LL_DEBUGS("WebSharing") << "Received AU session cookie: " << cookie << LL_ENDL;
+	mSessionCookie = cookie;
+
+	// Fetch a security token using the new session cookie.
+	LLWebSharing::instance().sendSecurityTokenRequest();
+
+	return (!mSessionCookie.empty());
+}
+
+bool LLWebSharing::receiveSecurityToken(const std::string& token, const std::string& expires)
+{
+	mSecurityToken["st"] = token;
+	mSecurityToken["expires"] = LLDate(expires);
+
+	if (!securityTokenIsValid(mSecurityToken))
+	{
+		LL_WARNS("WebSharing") << "Invalid security token received: \"" << token << "\" Expires: " << expires << LL_ENDL;
+		return false;
+	}
+
+	LL_DEBUGS("WebSharing") << "Received security token: \"" << token << "\" Expires: " << expires << LL_ENDL;
+	mRetries = 0;
+
+	// Continue the upload process now that we have a security token.
+	sendUploadRequest();
+
+	return true;
+}
+
+void LLWebSharing::sendConfigRequest()
+{
+	std::string config_url = gSavedSettings.getString("SnapshotConfigURL");
+	LL_DEBUGS("WebSharing") << "Requesting Snapshot Sharing config data from: " << config_url << LL_ENDL;
+
+	LLSD headers = LLSD::emptyMap();
+	headers["Accept"] = "application/json";
+
+	LLHTTPClient::get(config_url, new LLWebSharingConfigResponder(), headers);
+}
+
+void LLWebSharing::sendOpenIDAuthRequest()
+{
+	std::string auth_url = mConfig["openIdAuthUrl"];
+	LL_DEBUGS("WebSharing") << "Starting OpenID Auth: " << auth_url << LL_ENDL;
+
+	LLSD headers = LLSD::emptyMap();
+	headers["Cookie"] = mOpenIDCookie;
+	headers["Accept"] = "*/*";
+
+	// Send request, successful login will trigger fetching a security token.
+	LLHTTPClient::get(auth_url, new LLWebSharingOpenIDAuthResponder(), headers);
+}
+
+bool LLWebSharing::retryOpenIDAuth()
+{
+	if (mRetries++ >= MAX_AUTH_RETRIES)
+	{
+		LL_WARNS("WebSharing") << "Exceeded maximum number of authorization attempts, aborting." << LL_ENDL;
+		mRetries = 0;
+		return false;
+	}
+
+	LL_WARNS("WebSharing") << "Authorization failed, retrying (" << mRetries << "/" << MAX_AUTH_RETRIES << ")" << LL_ENDL;
+	sendOpenIDAuthRequest();
+	return true;
+}
+
+void LLWebSharing::sendSecurityTokenRequest()
+{
+	std::string token_url = mConfig["securityTokenUrl"];
+	LL_DEBUGS("WebSharing") << "Fetching security token from: " << token_url << LL_ENDL;
+
+	LLSD headers = LLSD::emptyMap();
+	headers["Cookie"] = mSessionCookie;
+
+	headers["Accept"] = "application/json";
+	headers["Content-Type"] = "application/json";
+
+	std::ostringstream body;
+	body << "{ \"gadgets\": [{ \"url\":\""
+		 << mConfig["gadgetSpecUrl"].asString()
+		 << "\" }] }";
+
+	// postRaw() takes ownership of the buffer and releases it later.
+	size_t size = body.str().size();
+	U8 *data = new U8[size];
+	memcpy(data, body.str().data(), size);
+
+	// Send request, receiving a valid token will trigger snapshot upload.
+	LLHTTPClient::postRaw(token_url, data, size, new LLWebSharingSecurityTokenResponder(), headers);
+}
+
+void LLWebSharing::sendUploadRequest()
+{
+	LLUriTemplate upload_template(mConfig["openSocialRpcUrlTemplate"].asString());
+	std::string upload_url(upload_template.buildURI(mSecurityToken));
+
+	LL_DEBUGS("WebSharing") << "Posting upload to: " << upload_url << LL_ENDL;
+
+	static const std::string BOUNDARY("------------abcdef012345xyZ");
+
+	LLSD headers = LLSD::emptyMap();
+	headers["Cookie"] = mSessionCookie;
+
+	headers["Accept"] = "application/json";
+	headers["Content-Type"] = "multipart/form-data; boundary=" + BOUNDARY;
+
+	std::ostringstream body;
+	body << "--" << BOUNDARY << "\r\n"
+		 << "Content-Disposition: form-data; name=\"request\"\r\n\r\n"
+		 << "[{"
+		 <<	  "\"method\":\"mediaItems.create\","
+		 <<	  "\"params\": {"
+		 <<	    "\"userId\":[\"@me\"],"
+		 <<	    "\"groupId\":\"@self\","
+		 <<	    "\"mediaItem\": {"
+		 <<	      "\"mimeType\":\"image/jpeg\","
+		 <<	      "\"type\":\"image\","
+		 <<       "\"url\":\"@field:image1\""
+		 <<	    "}"
+		 <<	  "},"
+		 <<	  "\"id\":\"newMediaItem\""
+		 <<	"}]"
+		 <<	"--" << BOUNDARY << "\r\n"
+		 <<	"Content-Disposition: form-data; name=\"image1\"\r\n\r\n";
+
+	// Insert the image data.
+	// *FIX: Treating this as a string will probably screw it up ...
+	U8* image_data = mImage->getData();
+	for (S32 i = 0; i < mImage->getDataSize(); ++i)
+	{
+		body << image_data[i];
+	}
+
+	body <<	"\r\n--" << BOUNDARY << "--\r\n";
+
+	// postRaw() takes ownership of the buffer and releases it later.
+	size_t size = body.str().size();
+	U8 *data = new U8[size];
+	memcpy(data, body.str().data(), size);
+
+	// Send request, successful upload will trigger posting metadata.
+	LLHTTPClient::postRaw(upload_url, data, size, new LLWebSharingUploadResponder(), headers);
+}
+
+bool LLWebSharing::validateConfig()
+{
+	// Check the OpenID Cookie has been set.
+	if (mOpenIDCookie.empty())
+	{
+		mEnabled = false;
+		return mEnabled;
+	}
+
+	if (!mConfig.isMap())
+	{
+		mEnabled = false;
+		return mEnabled;
+	}
+
+	// Template to match the received config against.
+	LLSD required(LLSD::emptyMap());
+	required["gadgetSpecUrl"] = "";
+	required["loginTokenUrl"] = "";
+	required["openIdAuthUrl"] = "";
+	required["photoPageUrlTemplate"] = "";
+	required["openSocialRpcUrlTemplate"] = "";
+	required["securityTokenUrl"] = "";
+	required["tokenBasedLoginUrlTemplate"] = "";
+	required["viewerIdUrl"] = "";
+
+	std::string mismatch(llsd_matches(required, mConfig));
+	if (!mismatch.empty())
+	{
+		LL_WARNS("WebSharing") << "Malformed config data response: " << mismatch << LL_ENDL;
+		mEnabled = false;
+		return mEnabled;
+	}
+
+	mEnabled = true;
+	return mEnabled;
+}
+
+// static
+bool LLWebSharing::securityTokenIsValid(LLSD& token)
+{
+	return (token.has("st") &&
+			token.has("expires") &&
+			(token["st"].asString() != "") &&
+			(token["expires"].asDate() > LLDate::now()));
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+LLUriTemplate::LLUriTemplate(const std::string& uri_template)
+	:
+	mTemplate(uri_template)
+{
+}
+
+std::string LLUriTemplate::buildURI(const LLSD& vars)
+{
+	// *TODO: Separate parsing the template from building the URI.
+	// Parsing only needs to happen on construction/assignnment.
+
+	static const std::string VAR_NAME_REGEX("[[:alpha:]][[:alnum:]\\._-]*");
+	// Capture var name with and without surrounding {}
+	static const std::string VAR_REGEX("\\{(" + VAR_NAME_REGEX + ")\\}");
+	// Capture delimiter and comma separated list of var names.
+	static const std::string JOIN_REGEX("\\{-join\\|(&)\\|(" + VAR_NAME_REGEX + "(?:," + VAR_NAME_REGEX + ")*)\\}");
+
+	std::string uri = mTemplate;
+	boost::smatch results;
+
+	// Validate and expand join operators : {-join|&|var1,var2,...}
+
+	boost::regex join_regex(JOIN_REGEX);
+
+	while (boost::regex_search(uri, results, join_regex))
+	{
+		// Extract the list of var names from the results.
+		std::string delim = results[1].str();
+		std::string var_list = results[2].str();
+
+		// Expand the list of vars into a query string with their values
+		std::string query = expandJoin(delim, var_list, vars);
+
+		// Substitute the query string into the template.
+		uri = boost::regex_replace(uri, join_regex, query, boost::format_first_only);
+	}
+
+	// Expand vars : {var1}
+
+	boost::regex var_regex(VAR_REGEX);
+
+	std::set<std::string> var_names;
+	std::string::const_iterator start = uri.begin();
+	std::string::const_iterator end = uri.end();
+
+	// Extract the var names used.
+	while (boost::regex_search(start, end, results, var_regex))
+	{
+		var_names.insert(results[1].str());
+		start = results[0].second;
+	}
+
+	// Replace each var with its value.
+	for (std::set<std::string>::const_iterator it = var_names.begin(); it != var_names.end(); ++it)
+	{
+		std::string var = *it;
+		if (vars.has(var))
+		{
+			boost::replace_all(uri, "{" + var + "}", vars[var].asString());
+		}
+	}
+
+	return uri;
+}
+
+std::string LLUriTemplate::expandJoin(const std::string& delim, const std::string& var_list, const LLSD& vars)
+{
+	std::ostringstream query;
+
+	typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
+	boost::char_separator<char> sep(",");
+	tokenizer var_names(var_list, sep);
+	tokenizer::const_iterator it = var_names.begin();
+
+	// First var does not need a delimiter
+	if (it != var_names.end())
+	{
+		const std::string& name = *it;
+		if (vars.has(name))
+		{
+			// URL encode the value before appending the name=value pair.
+			query << name << "=" << escapeURL(vars[name].asString());
+		}
+	}
+
+	for (++it; it != var_names.end(); ++it)
+	{
+		const std::string& name = *it;
+		if (vars.has(name))
+		{
+			// URL encode the value before appending the name=value pair.
+			query << delim << name << "=" << escapeURL(vars[name].asString());
+		}
+	}
+
+	return query.str();
+}
+
+// static
+std::string LLUriTemplate::escapeURL(const std::string& unescaped)
+{
+	char* escaped = curl_escape(unescaped.c_str(), unescaped.size());
+	std::string result = escaped;
+	curl_free(escaped);
+	return result;
+}
+
diff --git a/indra/newview/llwebsharing.h b/indra/newview/llwebsharing.h
new file mode 100644
index 00000000000..70046ff1d83
--- /dev/null
+++ b/indra/newview/llwebsharing.h
@@ -0,0 +1,230 @@
+/** 
+ * @file llwebsharing.h
+ * @author Aimee
+ * @brief Web Snapshot Sharing
+ *
+ * $LicenseInfo:firstyear=2010&license=viewergpl$
+ * 
+ * Copyright (c) 2010, Linden Research, Inc.
+ * 
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab.  Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ * 
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at
+ * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ * 
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ * 
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLWEBSHARING_H
+#define LL_LLWEBSHARING_H
+
+#include "llimagejpeg.h"
+#include "llsingleton.h"
+
+
+
+/**
+ * @class LLWebSharing
+ *
+ * Manages authentication to, and interaction with, a web service allowing the
+ * upload of snapshot images taken within the viewer, using OpenID and the
+ * OpenSocial APIs.
+ * http://www.opensocial.org/Technical-Resources/opensocial-spec-v09/RPC-Protocol.html
+ */
+class LLWebSharing : public LLSingleton<LLWebSharing>
+{
+	LOG_CLASS(LLWebSharing);
+public:
+	/*
+	 * Performs initial setup, by requesting config data from the web service if
+	 * it has not already been received.
+	 */
+	void init();
+
+	/*
+	 * @return true if both the OpenID cookie and config data have been received.
+	 */
+	bool enabled() const { return mEnabled; };
+
+	/*
+	 * Sets the OpenID cookie to use for login to the web service.
+	 *
+	 * @param cookie a string containing the OpenID cookie.
+	 *
+	 * @return true if both the OpenID cookie and config data have been received.
+	 */
+	bool setOpenIDCookie(const std::string& cookie);
+
+	/*
+	 * Receive config data used to connect to the web service.
+	 *
+	 * @param config an LLSD map of URL templates for the web service end-points.
+	 *
+	 * @return true if both the OpenID cookie and config data have been received.
+	 *
+	 * @see sendConfigRequest()
+	 */
+	bool receiveConfig(const LLSD& config);
+
+	/*
+	 * Receive the session cookie from the web service, which is the result of
+	 * the OpenID login process.
+	 *
+	 * @see sendOpenIDAuthRequest()
+	 */
+	bool receiveSessionCookie(const std::string& cookie);
+
+	/*
+	 * Receive a security token for the upload service.
+	 *
+	 * @see sendSecurityTokenRequest()
+	 */
+	bool receiveSecurityToken(const std::string& token, const std::string& expires);
+
+	/*
+	 * Restarts the authentication process if the maximum number of retries has
+	 * not been exceeded.
+	 *
+	 * @return true if retrying, false if LLWebSharing::MAX_AUTH_RETRIES has been exceeded.
+	 */
+	bool retryOpenIDAuth();
+
+	/*
+	 * Post a snapshot to the upload service.
+	 *
+	 * @return true if accepted for upload, false if already uploading another image.
+	 */
+	bool shareSnapshot(LLImageJPEG* snapshot, LLSD& metadata);
+
+private:
+	static const S32 MAX_AUTH_RETRIES = 4;
+
+	friend class LLSingleton<LLWebSharing>;
+
+	LLWebSharing();
+	~LLWebSharing() {};
+
+	/*
+	 * Request a map of URLs and URL templates to the web service end-points.
+	 *
+	 * @see receiveConfig()
+	 */
+	void sendConfigRequest();
+
+	/*
+	 * Initiate the OpenID login process.
+	 *
+	 * @see receiveSessionCookie()
+	 */
+	void sendOpenIDAuthRequest();
+
+	/*
+	 * Request a security token for the upload service.
+	 *
+	 * @see receiveSecurityToken()
+	 */
+	void sendSecurityTokenRequest();
+
+	/*
+	 * Request a security token for the upload service.
+	 *
+	 * @see receiveSecurityToken()
+	 */
+	void sendUploadRequest();
+
+	/*
+	 * Checks all necessary config information has been received, and sets mEnabled.
+	 *
+	 * @return true if both the OpenID cookie and config data have been received.
+	 */
+	bool validateConfig();
+
+	/*
+	 * Checks the security token is present and has not expired.
+	 *
+	 * @param token an LLSD map containing the token string and the time it expires.
+	 *
+	 * @return true if the token is not empty and has not expired.
+	 */
+	static bool securityTokenIsValid(LLSD& token);
+
+	std::string mOpenIDCookie;
+	std::string mSessionCookie;
+	LLSD mSecurityToken;
+
+	LLSD mConfig;
+	bool mEnabled;
+
+	LLPointer<LLImageJPEG> mImage;
+	LLSD mMetadata;
+
+	S32 mRetries;
+};
+
+/**
+ * @class LLUriTemplate
+ *
+ * @brief Builds complete URIs, given URI template and a map of keys and values
+ *        to use for substition.
+ *        Note: This is only a partial implementation of a draft standard required
+ *        by the web API used by LLWebSharing.
+ *        See: http://tools.ietf.org/html/draft-gregorio-uritemplate-03
+ *
+ * @see LLWebSharing
+ */
+class LLUriTemplate
+{
+	LOG_CLASS(LLUriTemplate);
+public:
+	LLUriTemplate(const std::string& uri_template);
+	~LLUriTemplate() {};
+
+	/*
+	 * Builds a complete URI from the template.
+	 *
+	 * @param vars an LLSD map of keys and values for substitution.
+	 *
+	 * @return a string containing the complete URI.
+	 */
+	std::string buildURI(const LLSD& vars);
+
+private:
+	/*
+	 * Builds a URL query string.
+	 *
+	 * @param delim    a string containing the separator to use between name=value pairs.
+	 * @param var_list a string containing a comma separated list of variable names.
+	 * @param vars     an LLSD map of keys and values for substitution.
+	 *
+	 * @return a URL query string.
+	 */
+	std::string expandJoin(const std::string& delim, const std::string& var_list, const LLSD& vars);
+
+	/*
+	 * URL escape the given string.
+	 * LLWeb::escapeURL() only does a partial escape, so this uses curl_escape() instead.
+	 */
+	static std::string escapeURL(const std::string& unescaped);
+
+	std::string mTemplate;
+};
+
+
+
+#endif // LL_LLWEBSHARING_H
diff --git a/indra/newview/skins/default/xui/en/floater_snapshot.xml b/indra/newview/skins/default/xui/en/floater_snapshot.xml
index 452b2ac664f..857932e51a1 100644
--- a/indra/newview/skins/default/xui/en/floater_snapshot.xml
+++ b/indra/newview/skins/default/xui/en/floater_snapshot.xml
@@ -4,48 +4,52 @@
  can_minimize="false"
  can_close="true"
  follows="left|top"
- height="516"
+ height="520"
  layout="topleft"
  name="Snapshot"
  help_topic="snapshot"
  save_rect="true"
  save_visibility="true"
  title="SNAPSHOT PREVIEW"
- width="215">   
-    <floater.string
-     name="share_to_web_url" translate="false">
-http://pdp36.lindenlab.com:12777/
-    </floater.string>
+ width="215">
     <floater.string
      name="unknown">
         unknown
     </floater.string>
     <radio_group
-     height="58"
+     height="70"
      label="Snapshot type"
      layout="topleft"
      left="10"
      name="snapshot_type_radio"
-     top="25"
+     top="20"
      width="205">
+<!--
         <radio_item
-         bottom="19"
+         height="16"
+         label="Share to Web"
+         layout="topleft"
+         name="share_to_web"
+         top_pad="0" />
+-->
+         <radio_item
          height="16"
          label="Email"
          layout="topleft"
-         name="postcard" />
+         name="postcard"
+         top_pad="2" />
         <radio_item
-         bottom="38"
          height="16"
          label="My inventory (L$[AMOUNT])"
          layout="topleft"
-         name="texture" />
+         name="texture"
+         top_pad="2" />
         <radio_item
-         bottom="57"
          height="16"
          label="Save to my computer"
          layout="topleft"
-         name="local" />
+         name="local"
+         top_pad="2" />
     </radio_group>
   <ui_ctrl 
     height="90"
@@ -67,7 +71,7 @@ http://pdp36.lindenlab.com:12777/
      left_delta="0"
      halign="right"
      name="file_size_label"
-     top_pad="10"
+     top_pad="8"
      width="195">
         [SIZE] KB
     </text>
-- 
GitLab