diff options
Diffstat (limited to 'indra/newview/llwebsharing.cpp')
-rw-r--r-- | indra/newview/llwebsharing.cpp | 609 |
1 files changed, 609 insertions, 0 deletions
diff --git a/indra/newview/llwebsharing.cpp b/indra/newview/llwebsharing.cpp new file mode 100644 index 0000000000..2b9e5cc8cb --- /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; +} + |