PageRenderTime 39ms CodeModel.GetById 13ms app.highlight 21ms RepoModel.GetById 1ms app.codeStats 0ms

/indra/newview/llwebprofile.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 305 lines | 190 code | 47 blank | 68 comment | 12 complexity | 5bd233ff2749fecd69b746aa347b32ea MD5 | raw file
  1/** 
  2 * @file llwebprofile.cpp
  3 * @brief Web profile access.
  4 *
  5 * $LicenseInfo:firstyear=2011&license=viewerlgpl$
  6 * Second Life Viewer Source Code
  7 * Copyright (C) 2011, Linden Research, Inc.
  8 * 
  9 * This library is free software; you can redistribute it and/or
 10 * modify it under the terms of the GNU Lesser General Public
 11 * License as published by the Free Software Foundation;
 12 * version 2.1 of the License only.
 13 * 
 14 * This library is distributed in the hope that it will be useful,
 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 17 * Lesser General Public License for more details.
 18 * 
 19 * You should have received a copy of the GNU Lesser General Public
 20 * License along with this library; if not, write to the Free Software
 21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 22 * 
 23 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 24 * $/LicenseInfo$
 25 */
 26
 27#include "llviewerprecompiledheaders.h"
 28
 29#include "llwebprofile.h"
 30
 31// libs
 32#include "llbufferstream.h"
 33#include "llhttpclient.h"
 34#include "llimagepng.h"
 35#include "llplugincookiestore.h"
 36
 37// newview
 38#include "llpanelprofile.h" // for getProfileURL(). FIXME: move the method to LLAvatarActions
 39#include "llviewermedia.h" // FIXME: don't use LLViewerMedia internals
 40
 41// third-party
 42#include "reader.h" // JSON
 43
 44/*
 45 * Workflow:
 46 * 1. LLViewerMedia::setOpenIDCookie()
 47 *    -> GET https://my-demo.secondlife.com/ via LLViewerMediaWebProfileResponder
 48 *    -> LLWebProfile::setAuthCookie()
 49 * 2. LLWebProfile::uploadImage()
 50 *    -> GET "https://my-demo.secondlife.com/snapshots/s3_upload_config" via ConfigResponder
 51 * 3. LLWebProfile::post()
 52 *    -> POST <config_url> via PostImageResponder
 53 *    -> redirect
 54 *    -> GET <redirect_url> via PostImageRedirectResponder
 55 */
 56
 57///////////////////////////////////////////////////////////////////////////////
 58// LLWebProfileResponders::ConfigResponder
 59
 60class LLWebProfileResponders::ConfigResponder : public LLHTTPClient::Responder
 61{
 62	LOG_CLASS(LLWebProfileResponders::ConfigResponder);
 63
 64public:
 65	ConfigResponder(LLPointer<LLImageFormatted> imagep)
 66	:	mImagep(imagep)
 67	{
 68	}
 69
 70	/*virtual*/ void completedRaw(
 71		U32 status,
 72		const std::string& reason,
 73		const LLChannelDescriptors& channels,
 74		const LLIOPipe::buffer_ptr_t& buffer)
 75	{
 76		LLBufferStream istr(channels, buffer.get());
 77		std::stringstream strstrm;
 78		strstrm << istr.rdbuf();
 79		const std::string body = strstrm.str();
 80
 81		if (status != 200)
 82		{
 83			llwarns << "Failed to get upload config (" << status << ")" << llendl;
 84			LLWebProfile::reportImageUploadStatus(false);
 85			return;
 86		}
 87
 88		Json::Value root;
 89		Json::Reader reader;
 90		if (!reader.parse(body, root))
 91		{
 92			llwarns << "Failed to parse upload config: " << reader.getFormatedErrorMessages() << llendl;
 93			LLWebProfile::reportImageUploadStatus(false);
 94			return;
 95		}
 96
 97		// *TODO: 404 = not supported by the grid
 98		// *TODO: increase timeout or handle 499 Expired
 99
100		// Convert config to LLSD.
101		const Json::Value data = root["data"];
102		const std::string upload_url = root["url"].asString();
103		LLSD config;
104		config["acl"]						= data["acl"].asString();
105		config["AWSAccessKeyId"]			= data["AWSAccessKeyId"].asString();
106		config["Content-Type"]				= data["Content-Type"].asString();
107		config["key"]						= data["key"].asString();
108		config["policy"]					= data["policy"].asString();
109		config["success_action_redirect"]	= data["success_action_redirect"].asString();
110		config["signature"]					= data["signature"].asString();
111		config["add_loc"]					= data.get("add_loc", "0").asString();
112		config["caption"]					= data.get("caption", "").asString();
113
114		// Do the actual image upload using the configuration.
115		LL_DEBUGS("Snapshots") << "Got upload config, POSTing image to " << upload_url << ", config=[" << config << "]" << llendl;
116		LLWebProfile::post(mImagep, config, upload_url);
117	}
118
119private:
120	LLPointer<LLImageFormatted> mImagep;
121};
122
123///////////////////////////////////////////////////////////////////////////////
124// LLWebProfilePostImageRedirectResponder
125class LLWebProfileResponders::PostImageRedirectResponder : public LLHTTPClient::Responder
126{
127	LOG_CLASS(LLWebProfileResponders::PostImageRedirectResponder);
128
129public:
130	/*virtual*/ void completedRaw(
131		U32 status,
132		const std::string& reason,
133		const LLChannelDescriptors& channels,
134		const LLIOPipe::buffer_ptr_t& buffer)
135	{
136		if (status != 200)
137		{
138			llwarns << "Failed to upload image: " << status << " " << reason << llendl;
139			LLWebProfile::reportImageUploadStatus(false);
140			return;
141		}
142
143		LLBufferStream istr(channels, buffer.get());
144		std::stringstream strstrm;
145		strstrm << istr.rdbuf();
146		const std::string body = strstrm.str();
147		llinfos << "Image uploaded." << llendl;
148		LL_DEBUGS("Snapshots") << "Uploading image succeeded. Response: [" << body << "]" << llendl;
149		LLWebProfile::reportImageUploadStatus(true);
150	}
151
152private:
153	LLPointer<LLImageFormatted> mImagep;
154};
155
156
157///////////////////////////////////////////////////////////////////////////////
158// LLWebProfileResponders::PostImageResponder
159class LLWebProfileResponders::PostImageResponder : public LLHTTPClient::Responder
160{
161	LOG_CLASS(LLWebProfileResponders::PostImageResponder);
162
163public:
164	/*virtual*/ void completedHeader(U32 status, const std::string& reason, const LLSD& content)
165	{
166		// Viewer seems to fail to follow a 303 redirect on POST request
167		// (URLRequest Error: 65, Send failed since rewinding of the data stream failed).
168		// Handle it manually.
169		if (status == 303)
170		{
171			LLSD headers = LLViewerMedia::getHeaders();
172			headers["Cookie"] = LLWebProfile::getAuthCookie();
173			const std::string& redir_url = content["location"];
174			LL_DEBUGS("Snapshots") << "Got redirection URL: " << redir_url << llendl;
175			LLHTTPClient::get(redir_url, new LLWebProfileResponders::PostImageRedirectResponder, headers);
176		}
177		else
178		{
179			llwarns << "Unexpected POST status: " << status << " " << reason << llendl;
180			LL_DEBUGS("Snapshots") << "headers: [" << content << "]" << llendl;
181			LLWebProfile::reportImageUploadStatus(false);
182		}
183	}
184
185	// Override just to suppress warnings.
186	/*virtual*/ void completedRaw(U32 status, const std::string& reason,
187							  const LLChannelDescriptors& channels,
188							  const LLIOPipe::buffer_ptr_t& buffer)
189	{
190	}
191};
192
193///////////////////////////////////////////////////////////////////////////////
194// LLWebProfile
195
196std::string LLWebProfile::sAuthCookie;
197LLWebProfile::status_callback_t LLWebProfile::mStatusCallback;
198
199// static
200void LLWebProfile::uploadImage(LLPointer<LLImageFormatted> image, const std::string& caption, bool add_location)
201{
202	// Get upload configuration data.
203	std::string config_url(getProfileURL(LLStringUtil::null) + "snapshots/s3_upload_config");
204	config_url += "?caption=" + LLURI::escape(caption);
205	config_url += "&add_loc=" + std::string(add_location ? "1" : "0");
206
207	LL_DEBUGS("Snapshots") << "Requesting " << config_url << llendl;
208	LLSD headers = LLViewerMedia::getHeaders();
209	headers["Cookie"] = getAuthCookie();
210	LLHTTPClient::get(config_url, new LLWebProfileResponders::ConfigResponder(image), headers);
211}
212
213// static
214void LLWebProfile::setAuthCookie(const std::string& cookie)
215{
216	LL_DEBUGS("Snapshots") << "Setting auth cookie: " << cookie << llendl;
217	sAuthCookie = cookie;
218}
219
220// static
221void LLWebProfile::post(LLPointer<LLImageFormatted> image, const LLSD& config, const std::string& url)
222{
223	if (dynamic_cast<LLImagePNG*>(image.get()) == 0)
224	{
225		llwarns << "Image to upload is not a PNG" << llendl;
226		llassert(dynamic_cast<LLImagePNG*>(image.get()) != 0);
227		return;
228	}
229
230	const std::string boundary = "----------------------------0123abcdefab";
231
232	LLSD headers = LLViewerMedia::getHeaders();
233	headers["Cookie"] = getAuthCookie();
234	headers["Content-Type"] = "multipart/form-data; boundary=" + boundary;
235
236	std::ostringstream body;
237
238	// *NOTE: The order seems to matter.
239	body	<< "--" << boundary << "\r\n"
240			<< "Content-Disposition: form-data; name=\"key\"\r\n\r\n"
241			<< config["key"].asString() << "\r\n";
242
243	body	<< "--" << boundary << "\r\n"
244			<< "Content-Disposition: form-data; name=\"AWSAccessKeyId\"\r\n\r\n"
245			<< config["AWSAccessKeyId"].asString() << "\r\n";
246
247	body	<< "--" << boundary << "\r\n"
248			<< "Content-Disposition: form-data; name=\"acl\"\r\n\r\n"
249			<< config["acl"].asString() << "\r\n";
250
251	body	<< "--" << boundary << "\r\n"
252			<< "Content-Disposition: form-data; name=\"Content-Type\"\r\n\r\n"
253			<< config["Content-Type"].asString() << "\r\n";
254
255	body	<< "--" << boundary << "\r\n"
256			<< "Content-Disposition: form-data; name=\"policy\"\r\n\r\n"
257			<< config["policy"].asString() << "\r\n";
258
259	body	<< "--" << boundary << "\r\n"
260			<< "Content-Disposition: form-data; name=\"signature\"\r\n\r\n"
261			<< config["signature"].asString() << "\r\n";
262
263	body	<< "--" << boundary << "\r\n"
264			<< "Content-Disposition: form-data; name=\"success_action_redirect\"\r\n\r\n"
265			<< config["success_action_redirect"].asString() << "\r\n";
266
267	body	<< "--" << boundary << "\r\n"
268			<< "Content-Disposition: form-data; name=\"file\"; filename=\"snapshot.png\"\r\n"
269			<< "Content-Type: image/png\r\n\r\n";
270
271	// Insert the image data.
272	// *FIX: Treating this as a string will probably screw it up ...
273	U8* image_data = image->getData();
274	for (S32 i = 0; i < image->getDataSize(); ++i)
275	{
276		body << image_data[i];
277	}
278
279	body <<	"\r\n--" << boundary << "--\r\n";
280
281	// postRaw() takes ownership of the buffer and releases it later.
282	size_t size = body.str().size();
283	U8 *data = new U8[size];
284	memcpy(data, body.str().data(), size);
285
286	// Send request, successful upload will trigger posting metadata.
287	LLHTTPClient::postRaw(url, data, size, new LLWebProfileResponders::PostImageResponder(), headers);
288}
289
290// static
291void LLWebProfile::reportImageUploadStatus(bool ok)
292{
293	if (mStatusCallback)
294	{
295		mStatusCallback(ok);
296	}
297}
298
299// static
300std::string LLWebProfile::getAuthCookie()
301{
302	// This is needed to test image uploads on Linux viewer built with OpenSSL 1.0.0 (0.9.8 works fine).
303	const char* debug_cookie = getenv("LL_SNAPSHOT_COOKIE");
304	return debug_cookie ? debug_cookie : sAuthCookie;
305}