phoenix-firestorm/indra/newview/exoflickr.cpp

304 lines
9.3 KiB
C++
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

/**
* @file exoflickr
* @brief Lightweight wrapper around the Flickr API for signing and such
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Copyright (C) 2012 Katharine Berry
*
* 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.
* $/LicenseInfo$
*/
#include "llviewerprecompiledheaders.h"
#include "llbufferstream.h"
#include "lluri.h"
#include "llxmltree.h"
#include "llsdserialize.h"
#include "llviewercontrol.h"
#include "llbase64.h"
#include "llcorehttputil.h"
#include "llsdutil.h"
// third-party
#if LL_USESYSTEMLIBS
#include "jsoncpp/reader.h" // JSON
#else
#include "reader.h" // JSON
#endif
#include <openssl/hmac.h>
#include <openssl/evp.h>
#include "exoflickr.h"
void exoFlickrUploadResponse( LLSD const &aData, exoFlickr::response_callback_t aCallback )
{
LLSD header = aData[ LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS ][ LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_HEADERS];
LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD( aData[ LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS ] );
LL_INFOS() << "Status " << status.getType() << " aData " << ll_pretty_print_sd( aData ) << LL_ENDL;
if( !aData.has( LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_RAW ) )
{
LL_WARNS() << "No content data included in response" << LL_ENDL;
aCallback(false, LLSD());
return;
}
const LLSD::Binary &rawData = aData[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_RAW].asBinary();
std::string result;
result.assign( rawData.begin(), rawData.end() );
LLSD output;
bool success;
LLXmlTree tree;
if(!tree.parseString(result))
{
LL_WARNS("FlickrAPI") << "Couldn't parse flickr response(" << status.getType() << "): " << result << LL_ENDL;
aCallback(false, LLSD());
return;
}
LLXmlTreeNode* root = tree.getRoot();
if(!root->hasName("rsp"))
{
LL_WARNS("FlickrAPI") << "Bad root node: " << root->getName() << LL_ENDL;
aCallback(false, LLSD());
return;
}
std::string stat;
root->getAttributeString("stat", stat);
output["stat"] = stat;
if(stat == "ok")
{
success = true;
LLXmlTreeNode* photoid_node = root->getChildByName("photoid");
if(photoid_node)
{
output["photoid"] = photoid_node->getContents();
}
}
else
{
success = false;
LLXmlTreeNode* err_node = root->getChildByName("err");
if(err_node)
{
S32 code;
std::string msg;
err_node->getAttributeS32("code", code);
err_node->getAttributeString("msg", msg);
output["code"] = code;
output["msg"] = msg;
}
}
aCallback(success, output);
}
static void JsonToLLSD(const Json::Value &root, LLSD &output)
{
if(root.isObject())
{
Json::Value::Members keys = root.getMemberNames();
for(Json::Value::Members::const_iterator itr = keys.begin(); itr != keys.end(); ++itr)
{
LLSD elem;
JsonToLLSD(root[*itr], elem);
output[*itr] = elem;
}
}
else if(root.isArray())
{
for(Json::Value::const_iterator itr = root.begin(); itr != root.end(); ++itr)
{
LLSD elem;
JsonToLLSD(*itr, elem);
output.append(elem);
}
}
else
{
switch(root.type())
{
case Json::intValue:
output = root.asInt();
break;
case Json::realValue:
case Json::uintValue:
output = root.asDouble();
break;
case Json::stringValue:
output = root.asString();
break;
case Json::booleanValue:
output = root.asBool();
break;
case Json::nullValue:
output = LLSD();
break;
default:
break;
}
}
}
void exoFlickrResponse( LLSD const &aData, exoFlickr::response_callback_t aCallback )
{
LLSD header = aData[ LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS ][ LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_HEADERS];
LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD( aData[ LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS ] );
const LLSD::Binary &rawData = aData[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_RAW].asBinary();
std::string result;
result.assign( rawData.begin(), rawData.end() );
Json::Value root;
Json::Reader reader;
bool success = reader.parse(result, root);
if(!success)
{
aCallback(false, LLSD());
return;
}
else
{
LL_INFOS("FlickrAPI") << "Got response string: " << result << LL_ENDL;
LLSD response;
JsonToLLSD(root, response);
aCallback( (status.getType() >= 200 && status.getType() < 300 ), response);
}
}
//static
void exoFlickr::request(const std::string& method, const LLSD& args, response_callback_t callback)
{
LLSD params(args);
params["format"] = "json";
params["method"] = method;
params["nojsoncallback"] = 1;
signRequest(params, "GET", "https://api.flickr.com/services/rest/");
std::string url = LLURI::buildHTTP( "https://api.flickr.com/services/rest/", LLSD::emptyArray(), params ).asString();
FSCoreHttpUtil::callbackHttpGetRaw( url, boost::bind( exoFlickrResponse, _1, callback ) );
}
void exoFlickr::signRequest(LLSD& params, std::string method, std::string url)
{
// Oauth junk
params["oauth_consumer_key"] = EXO_FLICKR_API_KEY;
std::string oauth_token = gSavedPerAccountSettings.getString("ExodusFlickrToken");
if(oauth_token.length())
{
params["oauth_token"] = oauth_token;
}
params["oauth_signature_method"] = "HMAC-SHA1";
params["oauth_timestamp"] = (LLSD::Integer)time(NULL);
params["oauth_nonce"] = ll_rand();
params["oauth_version"] = "1.0";
params["oauth_signature"] = getSignatureForCall(params, url, method); // This must be the last one set.
}
//static
void exoFlickr::uploadPhoto(const LLSD& args, LLImageFormatted *image, response_callback_t callback)
{
LLSD params(args);
signRequest(params, "POST", "https://up.flickr.com/services/upload/");
// It would be nice if there was an easy way to do multipart form data. Oh well.
const std::string boundary = "------------abcdefgh012345";
std::ostringstream post_stream;
post_stream << "--" << boundary;
// Add all the parameters from LLSD to the query.
for(LLSD::map_const_iterator itr = params.beginMap(); itr != params.endMap(); ++itr)
{
post_stream << "\r\nContent-Disposition: form-data; name=\"" << itr->first << "\"";
post_stream << "\r\n\r\n" << itr->second.asString();
post_stream << "\r\n" << "--" << boundary;
}
// Headers for the photo
post_stream << "\r\nContent-Disposition: form-data; name=\"photo\"; filename=\"snapshot." << image->getExtension() << "\"";
post_stream << "\r\nContent-Type: ";
// Apparently LLImageFormatted doesn't know what mimetype it has.
if(image->getExtension() == "jpg")
{
post_stream << "image/jpeg";
}
else if(image->getExtension() == "png")
{
post_stream << "image/png";
}
else // This will (probably) only happen if someone decides to put the BMP entry back in the format selection floater.
{ // I wonder if Flickr would do the right thing.
post_stream << "application/x-wtf";
LL_WARNS("FlickrAPI") << "Uploading unknown image type." << LL_ENDL;
}
post_stream << "\r\n\r\n";
// Now we build the postdata array, including the photo in the middle of it.
std::string post_str = post_stream.str();
std::string post_tail = "\r\n--" + boundary + "--";
std::string post_data = post_str;
post_data.append( reinterpret_cast< char const* >( image->getData() ), image->getDataSize() );
post_data.append( post_tail.c_str(), post_tail.size() );
// We have a post body! Now we can go about building the actual request...
// <FS:TS> Patch from Exodus:
// The default timeout (one minute) isn't enough for a large picture.
// 10 minutes is arbitrary, but should be long enough.
LLCore::HttpHeaders::ptr_t pHeader( new LLCore::HttpHeaders() );
pHeader->append( "Content-Type", "multipart/form-data; boundary=" + boundary );
FSCoreHttpUtil::callbackHttpPostRaw( "https://up.flickr.com/services/upload/", post_data, boost::bind( exoFlickrUploadResponse, _1, callback ), boost::bind( exoFlickrUploadResponse, _1, callback ), pHeader );
// </FS:TS>
// The HTTP client takes ownership of our post_data array,
// and will delete it when it's done.
}
//static
std::string exoFlickr::getSignatureForCall(const LLSD& parameters, std::string url, std::string method)
{
std::vector<std::string> keys;
for(LLSD::map_const_iterator itr = parameters.beginMap(); itr != parameters.endMap(); ++itr)
{
keys.push_back(itr->first);
}
std::sort(keys.begin(), keys.end());
std::ostringstream q;
q << LLURI::escape(method);
q << "&" << LLURI::escape(url) << "&";
for(std::vector<std::string>::const_iterator itr = keys.begin(); itr != keys.end(); ++itr)
{
if(itr != keys.begin())
{
q << "%26";
}
q << LLURI::escape(*itr);
q << "%3D" << LLURI::escape(LLURI::escape(parameters[*itr]));
}
unsigned char data[EVP_MAX_MD_SIZE];
unsigned int length;
std::string key = std::string(EXO_FLICKR_API_SECRET) + "&" + gSavedPerAccountSettings.getString("ExodusFlickrTokenSecret");
std::string to_hash = q.str();
HMAC(EVP_sha1(), (void*)key.c_str(), key.length(), (unsigned char*)to_hash.c_str(), to_hash.length(), data, &length);
std::string signature = LLBase64::encode((U8*)data, length);
return signature;
}