PageRenderTime 62ms CodeModel.GetById 19ms app.highlight 38ms RepoModel.GetById 1ms app.codeStats 0ms

/indra/newview/llwebsharing.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 603 lines | 411 code | 102 blank | 90 comment | 35 complexity | eeba6e2046032ae1ffe2b8ef0b9cd509 MD5 | raw file
  1/** 
  2 * @file llwebsharing.cpp
  3 * @author Aimee
  4 * @brief Web Snapshot Sharing
  5 *
  6 * $LicenseInfo:firstyear=2010&license=viewerlgpl$
  7 * Second Life Viewer Source Code
  8 * Copyright (C) 2010, Linden Research, Inc.
  9 * 
 10 * This library is free software; you can redistribute it and/or
 11 * modify it under the terms of the GNU Lesser General Public
 12 * License as published by the Free Software Foundation;
 13 * version 2.1 of the License only.
 14 * 
 15 * This library is distributed in the hope that it will be useful,
 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 18 * Lesser General Public License for more details.
 19 * 
 20 * You should have received a copy of the GNU Lesser General Public
 21 * License along with this library; if not, write to the Free Software
 22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 23 * 
 24 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 25 * $/LicenseInfo$
 26 */
 27
 28#include "llviewerprecompiledheaders.h"
 29
 30#include "llwebsharing.h"
 31
 32#include "llagentui.h"
 33#include "llbufferstream.h"
 34#include "llhttpclient.h"
 35#include "llhttpstatuscodes.h"
 36#include "llsdserialize.h"
 37#include "llsdutil.h"
 38#include "llurl.h"
 39#include "llviewercontrol.h"
 40
 41#include <boost/regex.hpp>
 42#include <boost/algorithm/string/replace.hpp>
 43
 44
 45
 46///////////////////////////////////////////////////////////////////////////////
 47//
 48class LLWebSharingConfigResponder : public LLHTTPClient::Responder
 49{
 50	LOG_CLASS(LLWebSharingConfigResponder);
 51public:
 52	/// Overrides the default LLSD parsing behaviour, to allow parsing a JSON response.
 53	virtual void completedRaw(U32 status, const std::string& reason,
 54							  const LLChannelDescriptors& channels,
 55							  const LLIOPipe::buffer_ptr_t& buffer)
 56	{
 57		LLSD content;
 58		LLBufferStream istr(channels, buffer.get());
 59		LLPointer<LLSDParser> parser = new LLSDNotationParser();
 60
 61		if (parser->parse(istr, content, LLSDSerialize::SIZE_UNLIMITED) == LLSDParser::PARSE_FAILURE)
 62		{
 63			LL_WARNS("WebSharing") << "Failed to deserialize LLSD from JSON response. " << " [" << status << "]: " << reason << LL_ENDL;
 64		}
 65		else
 66		{
 67			completed(status, reason, content);
 68		}
 69	}
 70
 71	virtual void error(U32 status, const std::string& reason)
 72	{
 73		LL_WARNS("WebSharing") << "Error [" << status << "]: " << reason << LL_ENDL;
 74	}
 75
 76	virtual void result(const LLSD& content)
 77	{
 78		LLWebSharing::instance().receiveConfig(content);
 79	}
 80};
 81
 82
 83
 84///////////////////////////////////////////////////////////////////////////////
 85//
 86class LLWebSharingOpenIDAuthResponder : public LLHTTPClient::Responder
 87{
 88	LOG_CLASS(LLWebSharingOpenIDAuthResponder);
 89public:
 90	/* virtual */ void completedHeader(U32 status, const std::string& reason, const LLSD& content)
 91	{
 92		completed(status, reason, content);
 93	}
 94
 95	/* virtual */ void completedRaw(U32 status, const std::string& reason,
 96									const LLChannelDescriptors& channels,
 97									const LLIOPipe::buffer_ptr_t& buffer)
 98	{
 99		/// Left empty to override the default LLSD parsing behaviour.
100	}
101
102	virtual void error(U32 status, const std::string& reason)
103	{
104		if (HTTP_UNAUTHORIZED == status)
105		{
106			LL_WARNS("WebSharing") << "AU account not authenticated." << LL_ENDL;
107			// *TODO: No account found on AU, so start the account creation process here.
108		}
109		else
110		{
111			LL_WARNS("WebSharing") << "Error [" << status << "]: " << reason << LL_ENDL;
112			LLWebSharing::instance().retryOpenIDAuth();
113		}
114
115	}
116
117	virtual void result(const LLSD& content)
118	{
119		if (content.has("set-cookie"))
120		{
121			// OpenID request succeeded and returned a session cookie.
122			LLWebSharing::instance().receiveSessionCookie(content["set-cookie"].asString());
123		}
124	}
125};
126
127
128
129///////////////////////////////////////////////////////////////////////////////
130//
131class LLWebSharingSecurityTokenResponder : public LLHTTPClient::Responder
132{
133	LOG_CLASS(LLWebSharingSecurityTokenResponder);
134public:
135	/// Overrides the default LLSD parsing behaviour, to allow parsing a JSON response.
136	virtual void completedRaw(U32 status, const std::string& reason,
137							  const LLChannelDescriptors& channels,
138							  const LLIOPipe::buffer_ptr_t& buffer)
139	{
140		LLSD content;
141		LLBufferStream istr(channels, buffer.get());
142		LLPointer<LLSDParser> parser = new LLSDNotationParser();
143
144		if (parser->parse(istr, content, LLSDSerialize::SIZE_UNLIMITED) == LLSDParser::PARSE_FAILURE)
145		{
146			LL_WARNS("WebSharing") << "Failed to deserialize LLSD from JSON response. " << " [" << status << "]: " << reason << LL_ENDL;
147			LLWebSharing::instance().retryOpenIDAuth();
148		}
149		else
150		{
151			completed(status, reason, content);
152		}
153	}
154
155	virtual void error(U32 status, const std::string& reason)
156	{
157		LL_WARNS("WebSharing") << "Error [" << status << "]: " << reason << LL_ENDL;
158		LLWebSharing::instance().retryOpenIDAuth();
159	}
160
161	virtual void result(const LLSD& content)
162	{
163		if (content[0].has("st") && content[0].has("expires"))
164		{
165			const std::string& token   = content[0]["st"].asString();
166			const std::string& expires = content[0]["expires"].asString();
167			if (LLWebSharing::instance().receiveSecurityToken(token, expires))
168			{
169				// Sucessfully received a valid security token.
170				return;
171			}
172		}
173		else
174		{
175			LL_WARNS("WebSharing") << "No security token received." << LL_ENDL;
176		}
177
178		LLWebSharing::instance().retryOpenIDAuth();
179	}
180};
181
182
183
184///////////////////////////////////////////////////////////////////////////////
185//
186class LLWebSharingUploadResponder : public LLHTTPClient::Responder
187{
188	LOG_CLASS(LLWebSharingUploadResponder);
189public:
190	/// Overrides the default LLSD parsing behaviour, to allow parsing a JSON response.
191	virtual void completedRaw(U32 status, const std::string& reason,
192							  const LLChannelDescriptors& channels,
193							  const LLIOPipe::buffer_ptr_t& buffer)
194	{
195/*
196		 // Dump the body, for debugging.
197
198		 LLBufferStream istr1(channels, buffer.get());
199		 std::ostringstream ostr;
200		 std::string body;
201
202		 while (istr1.good())
203		 {
204			char buf[1024];
205			istr1.read(buf, sizeof(buf));
206			body.append(buf, istr1.gcount());
207		 }
208		 LL_DEBUGS("WebSharing") << body << LL_ENDL;
209*/
210		LLSD content;
211		LLBufferStream istr(channels, buffer.get());
212		LLPointer<LLSDParser> parser = new LLSDNotationParser();
213
214		if (parser->parse(istr, content, LLSDSerialize::SIZE_UNLIMITED) == LLSDParser::PARSE_FAILURE)
215		{
216			LL_WARNS("WebSharing") << "Failed to deserialize LLSD from JSON response. " << " [" << status << "]: " << reason << LL_ENDL;
217		}
218		else
219		{
220			completed(status, reason, content);
221		}
222	}
223
224	virtual void error(U32 status, const std::string& reason)
225	{
226		LL_WARNS("WebSharing") << "Error [" << status << "]: " << reason << LL_ENDL;
227	}
228
229	virtual void result(const LLSD& content)
230	{
231		if (content[0].has("result") && content[0].has("id") &&
232			content[0]["id"].asString() == "newMediaItem")
233		{
234			// *TODO: Upload successful, continue from here to post metadata and create AU activity.
235		}
236		else
237		{
238			LL_WARNS("WebSharing") << "Error [" << content[0]["code"].asString()
239								   << "]: " << content[0]["message"].asString() << LL_ENDL;
240		}
241	}
242};
243
244
245
246///////////////////////////////////////////////////////////////////////////////
247//
248LLWebSharing::LLWebSharing()
249:	mConfig(),
250	mSecurityToken(LLSD::emptyMap()),
251	mEnabled(false),
252	mRetries(0),
253	mImage(NULL),
254	mMetadata(LLSD::emptyMap())
255{
256}
257
258void LLWebSharing::init()
259{
260	if (!mEnabled)
261	{
262		sendConfigRequest();
263	}
264}
265
266bool LLWebSharing::shareSnapshot(LLImageJPEG* snapshot, LLSD& metadata)
267{
268	LL_INFOS("WebSharing") << metadata << LL_ENDL;
269
270	if (mImage)
271	{
272		// *TODO: Handle this possibility properly, queue them up?
273		LL_WARNS("WebSharing") << "Snapshot upload already in progress." << LL_ENDL;
274		return false;
275	}
276
277	mImage = snapshot;
278	mMetadata = metadata;
279
280	// *TODO: Check whether we have a valid security token already and re-use it.
281	sendOpenIDAuthRequest();
282	return true;
283}
284
285bool LLWebSharing::setOpenIDCookie(const std::string& cookie)
286{
287	LL_DEBUGS("WebSharing") << "Setting OpenID cookie " << cookie << LL_ENDL;
288	mOpenIDCookie = cookie;
289	return validateConfig();
290}
291
292bool LLWebSharing::receiveConfig(const LLSD& config)
293{
294	LL_DEBUGS("WebSharing") << "Received config data: " << config << LL_ENDL;
295	mConfig = config;
296	return validateConfig();
297}
298
299bool LLWebSharing::receiveSessionCookie(const std::string& cookie)
300{
301	LL_DEBUGS("WebSharing") << "Received AU session cookie: " << cookie << LL_ENDL;
302	mSessionCookie = cookie;
303
304	// Fetch a security token using the new session cookie.
305	LLWebSharing::instance().sendSecurityTokenRequest();
306
307	return (!mSessionCookie.empty());
308}
309
310bool LLWebSharing::receiveSecurityToken(const std::string& token, const std::string& expires)
311{
312	mSecurityToken["st"] = token;
313	mSecurityToken["expires"] = LLDate(expires);
314
315	if (!securityTokenIsValid(mSecurityToken))
316	{
317		LL_WARNS("WebSharing") << "Invalid security token received: \"" << token << "\" Expires: " << expires << LL_ENDL;
318		return false;
319	}
320
321	LL_DEBUGS("WebSharing") << "Received security token: \"" << token << "\" Expires: " << expires << LL_ENDL;
322	mRetries = 0;
323
324	// Continue the upload process now that we have a security token.
325	sendUploadRequest();
326
327	return true;
328}
329
330void LLWebSharing::sendConfigRequest()
331{
332	std::string config_url = gSavedSettings.getString("SnapshotConfigURL");
333	LL_DEBUGS("WebSharing") << "Requesting Snapshot Sharing config data from: " << config_url << LL_ENDL;
334
335	LLSD headers = LLSD::emptyMap();
336	headers["Accept"] = "application/json";
337
338	LLHTTPClient::get(config_url, new LLWebSharingConfigResponder(), headers);
339}
340
341void LLWebSharing::sendOpenIDAuthRequest()
342{
343	std::string auth_url = mConfig["openIdAuthUrl"];
344	LL_DEBUGS("WebSharing") << "Starting OpenID Auth: " << auth_url << LL_ENDL;
345
346	LLSD headers = LLSD::emptyMap();
347	headers["Cookie"] = mOpenIDCookie;
348	headers["Accept"] = "*/*";
349
350	// Send request, successful login will trigger fetching a security token.
351	LLHTTPClient::get(auth_url, new LLWebSharingOpenIDAuthResponder(), headers);
352}
353
354bool LLWebSharing::retryOpenIDAuth()
355{
356	if (mRetries++ >= MAX_AUTH_RETRIES)
357	{
358		LL_WARNS("WebSharing") << "Exceeded maximum number of authorization attempts, aborting." << LL_ENDL;
359		mRetries = 0;
360		return false;
361	}
362
363	LL_WARNS("WebSharing") << "Authorization failed, retrying (" << mRetries << "/" << MAX_AUTH_RETRIES << ")" << LL_ENDL;
364	sendOpenIDAuthRequest();
365	return true;
366}
367
368void LLWebSharing::sendSecurityTokenRequest()
369{
370	std::string token_url = mConfig["securityTokenUrl"];
371	LL_DEBUGS("WebSharing") << "Fetching security token from: " << token_url << LL_ENDL;
372
373	LLSD headers = LLSD::emptyMap();
374	headers["Cookie"] = mSessionCookie;
375
376	headers["Accept"] = "application/json";
377	headers["Content-Type"] = "application/json";
378
379	std::ostringstream body;
380	body << "{ \"gadgets\": [{ \"url\":\""
381		 << mConfig["gadgetSpecUrl"].asString()
382		 << "\" }] }";
383
384	// postRaw() takes ownership of the buffer and releases it later.
385	size_t size = body.str().size();
386	U8 *data = new U8[size];
387	memcpy(data, body.str().data(), size);
388
389	// Send request, receiving a valid token will trigger snapshot upload.
390	LLHTTPClient::postRaw(token_url, data, size, new LLWebSharingSecurityTokenResponder(), headers);
391}
392
393void LLWebSharing::sendUploadRequest()
394{
395	LLUriTemplate upload_template(mConfig["openSocialRpcUrlTemplate"].asString());
396	std::string upload_url(upload_template.buildURI(mSecurityToken));
397
398	LL_DEBUGS("WebSharing") << "Posting upload to: " << upload_url << LL_ENDL;
399
400	static const std::string BOUNDARY("------------abcdef012345xyZ");
401
402	LLSD headers = LLSD::emptyMap();
403	headers["Cookie"] = mSessionCookie;
404
405	headers["Accept"] = "application/json";
406	headers["Content-Type"] = "multipart/form-data; boundary=" + BOUNDARY;
407
408	std::ostringstream body;
409	body << "--" << BOUNDARY << "\r\n"
410		 << "Content-Disposition: form-data; name=\"request\"\r\n\r\n"
411		 << "[{"
412		 <<	  "\"method\":\"mediaItems.create\","
413		 <<	  "\"params\": {"
414		 <<	    "\"userId\":[\"@me\"],"
415		 <<	    "\"groupId\":\"@self\","
416		 <<	    "\"mediaItem\": {"
417		 <<	      "\"mimeType\":\"image/jpeg\","
418		 <<	      "\"type\":\"image\","
419		 <<       "\"url\":\"@field:image1\""
420		 <<	    "}"
421		 <<	  "},"
422		 <<	  "\"id\":\"newMediaItem\""
423		 <<	"}]"
424		 <<	"--" << BOUNDARY << "\r\n"
425		 <<	"Content-Disposition: form-data; name=\"image1\"\r\n\r\n";
426
427	// Insert the image data.
428	// *FIX: Treating this as a string will probably screw it up ...
429	U8* image_data = mImage->getData();
430	for (S32 i = 0; i < mImage->getDataSize(); ++i)
431	{
432		body << image_data[i];
433	}
434
435	body <<	"\r\n--" << BOUNDARY << "--\r\n";
436
437	// postRaw() takes ownership of the buffer and releases it later.
438	size_t size = body.str().size();
439	U8 *data = new U8[size];
440	memcpy(data, body.str().data(), size);
441
442	// Send request, successful upload will trigger posting metadata.
443	LLHTTPClient::postRaw(upload_url, data, size, new LLWebSharingUploadResponder(), headers);
444}
445
446bool LLWebSharing::validateConfig()
447{
448	// Check the OpenID Cookie has been set.
449	if (mOpenIDCookie.empty())
450	{
451		mEnabled = false;
452		return mEnabled;
453	}
454
455	if (!mConfig.isMap())
456	{
457		mEnabled = false;
458		return mEnabled;
459	}
460
461	// Template to match the received config against.
462	LLSD required(LLSD::emptyMap());
463	required["gadgetSpecUrl"] = "";
464	required["loginTokenUrl"] = "";
465	required["openIdAuthUrl"] = "";
466	required["photoPageUrlTemplate"] = "";
467	required["openSocialRpcUrlTemplate"] = "";
468	required["securityTokenUrl"] = "";
469	required["tokenBasedLoginUrlTemplate"] = "";
470	required["viewerIdUrl"] = "";
471
472	std::string mismatch(llsd_matches(required, mConfig));
473	if (!mismatch.empty())
474	{
475		LL_WARNS("WebSharing") << "Malformed config data response: " << mismatch << LL_ENDL;
476		mEnabled = false;
477		return mEnabled;
478	}
479
480	mEnabled = true;
481	return mEnabled;
482}
483
484// static
485bool LLWebSharing::securityTokenIsValid(LLSD& token)
486{
487	return (token.has("st") &&
488			token.has("expires") &&
489			(token["st"].asString() != "") &&
490			(token["expires"].asDate() > LLDate::now()));
491}
492
493
494
495///////////////////////////////////////////////////////////////////////////////
496//
497LLUriTemplate::LLUriTemplate(const std::string& uri_template)
498	:
499	mTemplate(uri_template)
500{
501}
502
503std::string LLUriTemplate::buildURI(const LLSD& vars)
504{
505	// *TODO: Separate parsing the template from building the URI.
506	// Parsing only needs to happen on construction/assignnment.
507
508	static const std::string VAR_NAME_REGEX("[[:alpha:]][[:alnum:]\\._-]*");
509	// Capture var name with and without surrounding {}
510	static const std::string VAR_REGEX("\\{(" + VAR_NAME_REGEX + ")\\}");
511	// Capture delimiter and comma separated list of var names.
512	static const std::string JOIN_REGEX("\\{-join\\|(&)\\|(" + VAR_NAME_REGEX + "(?:," + VAR_NAME_REGEX + ")*)\\}");
513
514	std::string uri = mTemplate;
515	boost::smatch results;
516
517	// Validate and expand join operators : {-join|&|var1,var2,...}
518
519	boost::regex join_regex(JOIN_REGEX);
520
521	while (boost::regex_search(uri, results, join_regex))
522	{
523		// Extract the list of var names from the results.
524		std::string delim = results[1].str();
525		std::string var_list = results[2].str();
526
527		// Expand the list of vars into a query string with their values
528		std::string query = expandJoin(delim, var_list, vars);
529
530		// Substitute the query string into the template.
531		uri = boost::regex_replace(uri, join_regex, query, boost::format_first_only);
532	}
533
534	// Expand vars : {var1}
535
536	boost::regex var_regex(VAR_REGEX);
537
538	std::set<std::string> var_names;
539	std::string::const_iterator start = uri.begin();
540	std::string::const_iterator end = uri.end();
541
542	// Extract the var names used.
543	while (boost::regex_search(start, end, results, var_regex))
544	{
545		var_names.insert(results[1].str());
546		start = results[0].second;
547	}
548
549	// Replace each var with its value.
550	for (std::set<std::string>::const_iterator it = var_names.begin(); it != var_names.end(); ++it)
551	{
552		std::string var = *it;
553		if (vars.has(var))
554		{
555			boost::replace_all(uri, "{" + var + "}", vars[var].asString());
556		}
557	}
558
559	return uri;
560}
561
562std::string LLUriTemplate::expandJoin(const std::string& delim, const std::string& var_list, const LLSD& vars)
563{
564	std::ostringstream query;
565
566	typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
567	boost::char_separator<char> sep(",");
568	tokenizer var_names(var_list, sep);
569	tokenizer::const_iterator it = var_names.begin();
570
571	// First var does not need a delimiter
572	if (it != var_names.end())
573	{
574		const std::string& name = *it;
575		if (vars.has(name))
576		{
577			// URL encode the value before appending the name=value pair.
578			query << name << "=" << escapeURL(vars[name].asString());
579		}
580	}
581
582	for (++it; it != var_names.end(); ++it)
583	{
584		const std::string& name = *it;
585		if (vars.has(name))
586		{
587			// URL encode the value before appending the name=value pair.
588			query << delim << name << "=" << escapeURL(vars[name].asString());
589		}
590	}
591
592	return query.str();
593}
594
595// static
596std::string LLUriTemplate::escapeURL(const std::string& unescaped)
597{
598	char* escaped = curl_escape(unescaped.c_str(), unescaped.size());
599	std::string result = escaped;
600	curl_free(escaped);
601	return result;
602}
603