604 lines
17 KiB
C++
604 lines
17 KiB
C++
/**
|
|
* @file llwebsharing.cpp
|
|
* @author Aimee
|
|
* @brief Web Snapshot Sharing
|
|
*
|
|
* $LicenseInfo:firstyear=2010&license=viewerlgpl$
|
|
* Second Life Viewer Source Code
|
|
* Copyright (C) 2010, Linden Research, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation;
|
|
* version 2.1 of the License only.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
|
* $/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;
|
|
}
|
|
|