Webprofile converted to coroutine.

Added JSON->LLSD converter
Added corohandler for JSON data
master
Rider Linden 2015-05-20 17:37:27 -07:00
parent a4741cecb2
commit c437a9c4ec
10 changed files with 448 additions and 198 deletions

View File

@ -8,6 +8,7 @@ include(LLCommon)
include(Linking)
include(Boost)
include(LLSharedLibs)
include(JsonCpp)
include(GoogleBreakpad)
include(GooglePerfTools)
include(Copy3rdPartyLibs)
@ -17,6 +18,7 @@ include(URIPARSER)
include_directories(
${EXPAT_INCLUDE_DIRS}
${LLCOMMON_INCLUDE_DIRS}
${JSONCPP_INCLUDE_DIR}
${ZLIB_INCLUDE_DIRS}
${BREAKPAD_INCLUDE_DIRECTORIES}
${URIPARSER_INCLUDE_DIRS}
@ -85,6 +87,7 @@ set(llcommon_SOURCE_FILES
llrefcount.cpp
llrun.cpp
llsd.cpp
llsdjson.cpp
llsdparam.cpp
llsdserialize.cpp
llsdserialize_xml.cpp
@ -193,6 +196,7 @@ set(llcommon_HEADER_FILES
llrefcount.h
llsafehandle.h
llsd.h
llsdjson.h
llsdparam.h
llsdserialize.h
llsdserialize_xml.h
@ -260,6 +264,7 @@ target_link_libraries(
${APRUTIL_LIBRARIES}
${APR_LIBRARIES}
${EXPAT_LIBRARIES}
${JSONCPP_LIBRARIES}
${ZLIB_LIBRARIES}
${WINDOWS_LIBRARIES}
${BOOST_PROGRAM_OPTIONS_LIBRARY}

View File

@ -0,0 +1,78 @@
/**
* @file llsdjson.cpp
* @brief LLSD flexible data system
*
* $LicenseInfo:firstyear=2015&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2015, 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$
*/
// Must turn on conditional declarations in header file so definitions end up
// with proper linkage.
#define LLSD_DEBUG_INFO
#include "linden_common.h"
#include "llsdjson.h"
#include "llerror.h"
#include "../llmath/llmath.h"
//=========================================================================
LLSD LlsdFromJson(const Json::Value &val)
{
LLSD result;
switch (val.type())
{
default:
case Json::nullValue:
break;
case Json::intValue:
result = LLSD(static_cast<LLSD::Integer>(val.asInt()));
break;
case Json::uintValue:
result = LLSD(static_cast<LLSD::Integer>(val.asUInt()));
break;
case Json::realValue:
result = LLSD(static_cast<LLSD::Real>(val.asDouble()));
break;
case Json::stringValue:
result = LLSD(static_cast<LLSD::String>(val.asString()));
break;
case Json::booleanValue:
result = LLSD(static_cast<LLSD::Boolean>(val.asBool()));
break;
case Json::arrayValue:
result = LLSD::emptyArray();
for (Json::ValueConstIterator it = val.begin(); it != val.end(); ++it)
{
result.append(LlsdFromJson((*it)));
}
break;
case Json::objectValue:
result = LLSD::emptyMap();
for (Json::ValueConstIterator it = val.begin(); it != val.end(); ++it)
{
result[it.memberName()] = LlsdFromJson((*it));
}
break;
}
return result;
}

59
indra/llcommon/llsdjson.h Normal file
View File

@ -0,0 +1,59 @@
/**
* @file llsdjson.cpp
* @brief LLSD flexible data system
*
* $LicenseInfo:firstyear=2015&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2015, 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$
*/
#ifndef LL_LLSDJSON_H
#define LL_LLSDJSON_H
#include <map>
#include <string>
#include <vector>
#include "stdtypes.h"
#include "llsd.h"
#include "value.h"
/// Convert a parsed JSON structure into LLSD maintaining member names and
/// array indexes.
/// JSON/JavaScript types are converted as follows:
///
/// JSON Type | LLSD Type
/// --------------+--------------
/// null | undefined
/// integer | LLSD::Integer
/// unsigned | LLSD::Integer
/// real/numeric | LLSD::Real
/// string | LLSD::String
/// boolean | LLSD::Boolean
/// array | LLSD::Array
/// object | LLSD::Map
///
/// For maps and arrays child entries will be converted and added to the structure.
/// Order is preserved for an array but not for objects.
LLSD LlsdFromJson(const Json::Value &val);
#endif // LL_LLSDJSON_H

View File

@ -118,6 +118,24 @@ const std::string * HttpHeaders::find(const std::string &name) const
return NULL;
}
void HttpHeaders::remove(const char *name)
{
remove(std::string(name));
}
void HttpHeaders::remove(const std::string &name)
{
iterator iend(end());
for (iterator iter(begin()); iend != iter; ++iter)
{
if ((*iter).first == name)
{
mHeaders.erase(iter);
return;
}
}
}
// Standard Iterators
HttpHeaders::iterator HttpHeaders::begin()

View File

@ -146,13 +146,17 @@ public:
// a pointer to a std::string in the container.
// Pointer is valid only for the lifetime of
// the container or until container is modifed.
const std::string * find(const std::string &name) const;
const std::string * find(const char * name) const
{
return find(std::string(name));
}
// Remove the header from the list if found.
//
void remove(const std::string &name);
void remove(const char *name);
// Count of headers currently in the list.
size_type size() const
{

View File

@ -14,6 +14,7 @@ include(LLAddBuildTest)
include(Python)
include(Tut)
include(Python)
include(JsonCpp)
include_directories (${CMAKE_CURRENT_SOURCE_DIR})
@ -23,6 +24,7 @@ include_directories(
${LLMATH_INCLUDE_DIRS}
${LLMESSAGE_INCLUDE_DIRS}
${LLVFS_INCLUDE_DIRS}
${JSONCPP_INCLUDE_DIR}
)
set(llmessage_SOURCE_FILES
@ -229,6 +231,7 @@ target_link_libraries(
${LLVFS_LIBRARIES}
${LLMATH_LIBRARIES}
${CARES_LIBRARIES}
${JSONCPP_LIBRARIES}
${OPENSSL_LIBRARIES}
${CRYPTO_LIBRARIES}
${XMLRPCEPI_LIBRARIES}
@ -254,6 +257,7 @@ if (LL_TESTS)
${LLCOMMON_LIBRARIES}
${LLMESSAGE_LIBRARIES}
${LLCOREHTTP_LIBRARIES}
${JSONCPP_LIBRARIES}
${BOOST_CONTEXT_LIBRARY}
${BOOST_COROUTINE_LIBRARY}
${GOOGLEMOCK_LIBRARIES}

View File

@ -32,7 +32,10 @@
#include <iterator>
#include "llcorehttputil.h"
#include "llhttpconstants.h"
#include "llsd.h"
#include "llsdjson.h"
#include "llsdserialize.h"
#include "reader.h"
using namespace LLCore;
@ -252,6 +255,23 @@ void HttpCoroHandler::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRespons
}
buildStatusEntry(response, status, result);
#if 0
// commenting out, but keeping since this can be useful for debugging
if (!status)
{
LLSD &httpStatus = result[HttpCoroutineAdapter::HTTP_RESULTS];
LLCore::BufferArray *body = response->getBody();
LLCore::BufferArrayStream bas(body);
LLSD::Binary bodyData;
bodyData.reserve(response->getBodySize());
bas >> std::noskipws;
bodyData.assign(std::istream_iterator<U8>(bas), std::istream_iterator<U8>());
httpStatus["error_body"] = bodyData;
}
#endif
mReplyPump.post(result);
}
@ -437,6 +457,58 @@ LLSD HttpCoroRawHandler::handleSuccess(LLCore::HttpResponse * response, LLCore::
return result;
}
//========================================================================
/// The HttpCoroJSONHandler is a specialization of the LLCore::HttpHandler for
/// interacting with coroutines.
///
/// In addition to the normal "http_results" the returned LLSD will contain
/// JSON entries will be converted into an LLSD map. All results are considered
/// strings
///
class HttpCoroJSONHandler : public HttpCoroHandler
{
public:
HttpCoroJSONHandler(LLEventStream &reply);
virtual LLSD handleSuccess(LLCore::HttpResponse * response, LLCore::HttpStatus &status);
};
//-------------------------------------------------------------------------
HttpCoroJSONHandler::HttpCoroJSONHandler(LLEventStream &reply) :
HttpCoroHandler(reply)
{
}
LLSD HttpCoroJSONHandler::handleSuccess(LLCore::HttpResponse * response, LLCore::HttpStatus &status)
{
LLSD result = LLSD::emptyMap();
BufferArray * body(response->getBody());
if (!body || !body->size())
{
return result;
}
LLCore::BufferArrayStream bas(body);
Json::Value jsonRoot;
try
{
bas >> jsonRoot;
}
catch (std::runtime_error e)
{ // deserialization failed. Record the reason and pass back an empty map for markup.
status = LLCore::HttpStatus(499, std::string(e.what()));
return result;
}
// Convert the JSON structure to LLSD
result = LlsdFromJson(jsonRoot);
return result;
}
//========================================================================
HttpRequestPumper::HttpRequestPumper(const LLCore::HttpRequest::ptr_t &request) :
mHttpRequest(request)
@ -614,6 +686,16 @@ LLSD HttpCoroutineAdapter::getRawAndYield(LLCoros::self & self, LLCore::HttpRequ
return getAndYield_(self, request, url, options, headers, httpHandler);
}
LLSD HttpCoroutineAdapter::getJsonAndYield(LLCoros::self & self, LLCore::HttpRequest::ptr_t request,
const std::string & url, LLCore::HttpOptions::ptr_t options, LLCore::HttpHeaders::ptr_t headers)
{
LLEventStream replyPump(mAdapterName + "Reply", true);
HttpCoroHandler::ptr_t httpHandler = HttpCoroHandler::ptr_t(new HttpCoroJSONHandler(replyPump));
return getAndYield_(self, request, url, options, headers, httpHandler);
}
LLSD HttpCoroutineAdapter::getAndYield_(LLCoros::self & self, LLCore::HttpRequest::ptr_t &request,
const std::string & url,
LLCore::HttpOptions::ptr_t &options, LLCore::HttpHeaders::ptr_t &headers,

View File

@ -373,6 +373,18 @@ public:
headers);
}
LLSD getJsonAndYield(LLCoros::self & self, LLCore::HttpRequest::ptr_t request,
const std::string & url,
LLCore::HttpOptions::ptr_t options = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions(), false),
LLCore::HttpHeaders::ptr_t headers = LLCore::HttpHeaders::ptr_t(new LLCore::HttpHeaders(), false));
LLSD getJsonndYield(LLCoros::self & self, LLCore::HttpRequest::ptr_t &request,
const std::string & url, LLCore::HttpHeaders::ptr_t &headers)
{
return getJsonAndYield(self, request, url,
LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions(), false),
headers);
}
/// Execute a DELETE transaction on the supplied URL and yield execution of
/// the coroutine until a result is available.

View File

@ -34,10 +34,14 @@
#include "llimagepng.h"
#include "llplugincookiestore.h"
#include "llsdserialize.h"
// newview
#include "llpanelprofile.h" // for getProfileURL(). FIXME: move the method to LLAvatarActions
#include "llviewermedia.h" // FIXME: don't use LLViewerMedia internals
#include "llcorehttputil.h"
// third-party
#include "reader.h" // JSON
@ -54,139 +58,6 @@
* -> GET <redirect_url> via PostImageRedirectResponder
*/
///////////////////////////////////////////////////////////////////////////////
// LLWebProfileResponders::ConfigResponder
class LLWebProfileResponders::ConfigResponder : public LLHTTPClient::Responder
{
LOG_CLASS(LLWebProfileResponders::ConfigResponder);
public:
ConfigResponder(LLPointer<LLImageFormatted> imagep)
: mImagep(imagep)
{
}
// *TODO: Check for 'application/json' content type, and parse json at the base class.
/*virtual*/ void completedRaw(
const LLChannelDescriptors& channels,
const LLIOPipe::buffer_ptr_t& buffer)
{
LLBufferStream istr(channels, buffer.get());
std::stringstream strstrm;
strstrm << istr.rdbuf();
const std::string body = strstrm.str();
if (getStatus() != HTTP_OK)
{
LL_WARNS() << "Failed to get upload config " << dumpResponse() << LL_ENDL;
LLWebProfile::reportImageUploadStatus(false);
return;
}
Json::Value root;
Json::Reader reader;
if (!reader.parse(body, root))
{
LL_WARNS() << "Failed to parse upload config: " << reader.getFormatedErrorMessages() << LL_ENDL;
LLWebProfile::reportImageUploadStatus(false);
return;
}
// *TODO: 404 = not supported by the grid
// *TODO: increase timeout or handle 499 Expired
// Convert config to LLSD.
const Json::Value data = root["data"];
const std::string upload_url = root["url"].asString();
LLSD config;
config["acl"] = data["acl"].asString();
config["AWSAccessKeyId"] = data["AWSAccessKeyId"].asString();
config["Content-Type"] = data["Content-Type"].asString();
config["key"] = data["key"].asString();
config["policy"] = data["policy"].asString();
config["success_action_redirect"] = data["success_action_redirect"].asString();
config["signature"] = data["signature"].asString();
config["add_loc"] = data.get("add_loc", "0").asString();
config["caption"] = data.get("caption", "").asString();
// Do the actual image upload using the configuration.
LL_DEBUGS("Snapshots") << "Got upload config, POSTing image to " << upload_url << ", config=[" << config << "]" << LL_ENDL;
LLWebProfile::post(mImagep, config, upload_url);
}
private:
LLPointer<LLImageFormatted> mImagep;
};
///////////////////////////////////////////////////////////////////////////////
// LLWebProfilePostImageRedirectResponder
class LLWebProfileResponders::PostImageRedirectResponder : public LLHTTPClient::Responder
{
LOG_CLASS(LLWebProfileResponders::PostImageRedirectResponder);
public:
/*virtual*/ void completedRaw(
const LLChannelDescriptors& channels,
const LLIOPipe::buffer_ptr_t& buffer)
{
if (getStatus() != HTTP_OK)
{
LL_WARNS() << "Failed to upload image " << dumpResponse() << LL_ENDL;
LLWebProfile::reportImageUploadStatus(false);
return;
}
LLBufferStream istr(channels, buffer.get());
std::stringstream strstrm;
strstrm << istr.rdbuf();
const std::string body = strstrm.str();
LL_INFOS() << "Image uploaded." << LL_ENDL;
LL_DEBUGS("Snapshots") << "Uploading image succeeded. Response: [" << body << "]" << LL_ENDL;
LLWebProfile::reportImageUploadStatus(true);
}
};
///////////////////////////////////////////////////////////////////////////////
// LLWebProfileResponders::PostImageResponder
class LLWebProfileResponders::PostImageResponder : public LLHTTPClient::Responder
{
LOG_CLASS(LLWebProfileResponders::PostImageResponder);
public:
/*virtual*/ void completedRaw(const LLChannelDescriptors& channels,
const LLIOPipe::buffer_ptr_t& buffer)
{
// Viewer seems to fail to follow a 303 redirect on POST request
// (URLRequest Error: 65, Send failed since rewinding of the data stream failed).
// Handle it manually.
if (getStatus() == HTTP_SEE_OTHER)
{
LLSD headers = LLViewerMedia::getHeaders();
headers[HTTP_OUT_HEADER_COOKIE] = LLWebProfile::getAuthCookie();
const std::string& redir_url = getResponseHeader(HTTP_IN_HEADER_LOCATION);
if (redir_url.empty())
{
LL_WARNS() << "Received empty redirection URL " << dumpResponse() << LL_ENDL;
LL_DEBUGS("Snapshots") << "[headers:" << getResponseHeaders() << "]" << LL_ENDL;
LLWebProfile::reportImageUploadStatus(false);
}
else
{
LL_DEBUGS("Snapshots") << "Got redirection URL: " << redir_url << LL_ENDL;
LLHTTPClient::get(redir_url, new LLWebProfileResponders::PostImageRedirectResponder, headers);
}
}
else
{
LL_WARNS() << "Unexpected POST response " << dumpResponse() << LL_ENDL;
LL_DEBUGS("Snapshots") << "[headers:" << getResponseHeaders() << "]" << LL_ENDL;
LLWebProfile::reportImageUploadStatus(false);
}
}
};
///////////////////////////////////////////////////////////////////////////////
// LLWebProfile
@ -196,15 +67,9 @@ LLWebProfile::status_callback_t LLWebProfile::mStatusCallback;
// static
void LLWebProfile::uploadImage(LLPointer<LLImageFormatted> image, const std::string& caption, bool add_location)
{
// Get upload configuration data.
std::string config_url(getProfileURL(LLStringUtil::null) + "snapshots/s3_upload_config");
config_url += "?caption=" + LLURI::escape(caption);
config_url += "&add_loc=" + std::string(add_location ? "1" : "0");
LLCoros::instance().launch("LLWebProfile::uploadImageCoro",
boost::bind(&LLWebProfile::uploadImageCoro, _1, image, caption, add_location));
LL_DEBUGS("Snapshots") << "Requesting " << config_url << LL_ENDL;
LLSD headers = LLViewerMedia::getHeaders();
headers[HTTP_OUT_HEADER_COOKIE] = getAuthCookie();
LLHTTPClient::get(config_url, new LLWebProfileResponders::ConfigResponder(image), headers);
}
// static
@ -214,74 +79,193 @@ void LLWebProfile::setAuthCookie(const std::string& cookie)
sAuthCookie = cookie;
}
// static
void LLWebProfile::post(LLPointer<LLImageFormatted> image, const LLSD& config, const std::string& url)
/*static*/
LLCore::HttpHeaders::ptr_t LLWebProfile::buildDefaultHeaders()
{
if (dynamic_cast<LLImagePNG*>(image.get()) == 0)
{
LL_WARNS() << "Image to upload is not a PNG" << LL_ENDL;
llassert(dynamic_cast<LLImagePNG*>(image.get()) != 0);
return;
}
LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders);
LLSD headers = LLViewerMedia::getHeaders();
const std::string boundary = "----------------------------0123abcdefab";
for (LLSD::map_iterator it = headers.beginMap(); it != headers.endMap(); ++it)
{
httpHeaders->append((*it).first, (*it).second.asStringRef());
}
LLSD headers = LLViewerMedia::getHeaders();
headers[HTTP_OUT_HEADER_COOKIE] = getAuthCookie();
headers[HTTP_OUT_HEADER_CONTENT_TYPE] = "multipart/form-data; boundary=" + boundary;
return httpHeaders;
}
std::ostringstream body;
// *NOTE: The order seems to matter.
body << "--" << boundary << "\r\n"
<< "Content-Disposition: form-data; name=\"key\"\r\n\r\n"
<< config["key"].asString() << "\r\n";
/*static*/
void LLWebProfile::uploadImageCoro(LLCoros::self& self, LLPointer<LLImageFormatted> image, std::string caption, bool addLocation)
{
LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("genericPostCoro", httpPolicy));
LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions);
LLCore::HttpHeaders::ptr_t httpHeaders;
body << "--" << boundary << "\r\n"
<< "Content-Disposition: form-data; name=\"AWSAccessKeyId\"\r\n\r\n"
<< config["AWSAccessKeyId"].asString() << "\r\n";
if (dynamic_cast<LLImagePNG*>(image.get()) == 0)
{
LL_WARNS() << "Image to upload is not a PNG" << LL_ENDL;
llassert(dynamic_cast<LLImagePNG*>(image.get()) != 0);
return;
}
body << "--" << boundary << "\r\n"
<< "Content-Disposition: form-data; name=\"acl\"\r\n\r\n"
<< config["acl"].asString() << "\r\n";
httpOpts->setWantHeaders(true);
body << "--" << boundary << "\r\n"
<< "Content-Disposition: form-data; name=\"Content-Type\"\r\n\r\n"
<< config["Content-Type"].asString() << "\r\n";
// Get upload configuration data.
std::string configUrl(getProfileURL(std::string()) + "snapshots/s3_upload_config");
configUrl += "?caption=" + LLURI::escape(caption);
configUrl += "&add_loc=" + std::string(addLocation ? "1" : "0");
body << "--" << boundary << "\r\n"
<< "Content-Disposition: form-data; name=\"policy\"\r\n\r\n"
<< config["policy"].asString() << "\r\n";
LL_DEBUGS("Snapshots") << "Requesting " << configUrl << LL_ENDL;
body << "--" << boundary << "\r\n"
<< "Content-Disposition: form-data; name=\"signature\"\r\n\r\n"
<< config["signature"].asString() << "\r\n";
httpHeaders = buildDefaultHeaders();
httpHeaders->append(HTTP_OUT_HEADER_COOKIE, getAuthCookie());
body << "--" << boundary << "\r\n"
<< "Content-Disposition: form-data; name=\"success_action_redirect\"\r\n\r\n"
<< config["success_action_redirect"].asString() << "\r\n";
LLSD result = httpAdapter->getJsonAndYield(self, httpRequest, configUrl, httpOpts, httpHeaders);
body << "--" << boundary << "\r\n"
<< "Content-Disposition: form-data; name=\"file\"; filename=\"snapshot.png\"\r\n"
<< "Content-Type: image/png\r\n\r\n";
LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
// Insert the image data.
// *FIX: Treating this as a string will probably screw it up ...
U8* image_data = image->getData();
for (S32 i = 0; i < image->getDataSize(); ++i)
{
body << image_data[i];
}
if (!status)
{
std::ostringstream ostm;
LLSDSerialize::toPrettyXML(httpResults, ostm);
LL_WARNS("Snapshots") << "Failed to get image upload config" << LL_ENDL;
LL_WARNS("Snapshots") << ostm.str() << LL_ENDL;
LLWebProfile::reportImageUploadStatus(false);
return;
}
body << "\r\n--" << boundary << "--\r\n";
// Ready to build our image post body.
// 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);
const LLSD &data = result["data"];
const std::string &uploadUrl = result["url"].asStringRef();
const std::string boundary = "----------------------------0123abcdefab";
// Send request, successful upload will trigger posting metadata.
LLHTTPClient::postRaw(url, data, size, new LLWebProfileResponders::PostImageResponder(), headers);
// a new set of headers.
httpHeaders = buildDefaultHeaders();
httpHeaders->append(HTTP_OUT_HEADER_COOKIE, getAuthCookie());
httpHeaders->remove(HTTP_OUT_HEADER_CONTENT_TYPE);
httpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, "multipart/form-data; boundary=" + boundary);
LLCore::BufferArray::ptr_t body = LLWebProfile::buildPostData(data, image, boundary);
result = httpAdapter->postAndYield(self, httpRequest, uploadUrl, body, httpOpts, httpHeaders);
{
std::ostringstream ostm;
LLSDSerialize::toPrettyXML(result, ostm);
LL_WARNS("Snapshots") << ostm.str() << LL_ENDL;
}
body.reset();
httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
if (!status && (status != LLCore::HttpStatus(HTTP_SEE_OTHER)))
{
LL_WARNS("Snapshots") << "Failed to upload image data." << LL_ENDL;
LLWebProfile::reportImageUploadStatus(false);
return;
}
LLSD resultHeaders = httpResults[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_HEADERS];
httpHeaders = buildDefaultHeaders();
httpHeaders->append(HTTP_OUT_HEADER_COOKIE, getAuthCookie());
const std::string& redirUrl = resultHeaders[HTTP_IN_HEADER_LOCATION].asStringRef();
if (redirUrl.empty())
{
LL_WARNS("Snapshots") << "Received empty redirection URL in post image." << LL_ENDL;
LLWebProfile::reportImageUploadStatus(false);
}
LL_DEBUGS("Snapshots") << "Got redirection URL: " << redirUrl << LL_ENDL;
result = httpAdapter->getRawAndYield(self, httpRequest, redirUrl, httpOpts, httpHeaders);
httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
if (status != LLCore::HttpStatus(HTTP_OK))
{
LL_WARNS("Snapshots") << "Failed to upload image." << LL_ENDL;
LLWebProfile::reportImageUploadStatus(false);
return;
}
LLSD raw = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_RAW];
// const LLSD::Binary &rawBin = raw.asBinary();
// std::istringstream rawresult(rawBin.begin(), rawBin.end());
// LLBufferStream istr(channels, buffer.get());
// std::stringstream strstrm;
// strstrm << istr.rdbuf();
// const std::string body = strstrm.str();
LL_INFOS("Snapshots") << "Image uploaded." << LL_ENDL;
LL_DEBUGS("Snapshots") << "Uploading image succeeded. Response: [" << raw.asString() << "]" << LL_ENDL;
LLWebProfile::reportImageUploadStatus(true);
}
/*static*/
LLCore::BufferArray::ptr_t LLWebProfile::buildPostData(const LLSD &data, LLPointer<LLImageFormatted> &image, const std::string &boundary)
{
LLCore::BufferArray::ptr_t body(new LLCore::BufferArray);
LLCore::BufferArrayStream bas(body.get());
// std::ostringstream body;
// *NOTE: The order seems to matter.
bas << "--" << boundary << "\r\n"
<< "Content-Disposition: form-data; name=\"key\"\r\n\r\n"
<< data["key"].asString() << "\r\n";
bas << "--" << boundary << "\r\n"
<< "Content-Disposition: form-data; name=\"AWSAccessKeyId\"\r\n\r\n"
<< data["AWSAccessKeyId"].asString() << "\r\n";
bas << "--" << boundary << "\r\n"
<< "Content-Disposition: form-data; name=\"acl\"\r\n\r\n"
<< data["acl"].asString() << "\r\n";
bas << "--" << boundary << "\r\n"
<< "Content-Disposition: form-data; name=\"Content-Type\"\r\n\r\n"
<< data["Content-Type"].asString() << "\r\n";
bas << "--" << boundary << "\r\n"
<< "Content-Disposition: form-data; name=\"policy\"\r\n\r\n"
<< data["policy"].asString() << "\r\n";
bas << "--" << boundary << "\r\n"
<< "Content-Disposition: form-data; name=\"signature\"\r\n\r\n"
<< data["signature"].asString() << "\r\n";
bas << "--" << boundary << "\r\n"
<< "Content-Disposition: form-data; name=\"success_action_redirect\"\r\n\r\n"
<< data["success_action_redirect"].asString() << "\r\n";
bas << "--" << boundary << "\r\n"
<< "Content-Disposition: form-data; name=\"file\"; filename=\"snapshot.png\"\r\n"
<< "Content-Type: image/png\r\n\r\n";
// Insert the image data.
//char *datap = (char *)(image->getData());
//bas.write(datap, image->getDataSize());
U8* image_data = image->getData();
for (S32 i = 0; i < image->getDataSize(); ++i)
{
bas << image_data[i];
}
bas << "\r\n--" << boundary << "--\r\n";
return body;
}
// static

View File

@ -28,6 +28,10 @@
#define LL_LLWEBPROFILE_H
#include "llimage.h"
#include "lleventcoro.h"
#include "llcoros.h"
#include "httpheaders.h"
#include "bufferarray.h"
namespace LLWebProfileResponders
{
@ -54,11 +58,11 @@ public:
static void setImageUploadResultCallback(status_callback_t cb) { mStatusCallback = cb; }
private:
friend class LLWebProfileResponders::ConfigResponder;
friend class LLWebProfileResponders::PostImageResponder;
friend class LLWebProfileResponders::PostImageRedirectResponder;
static LLCore::HttpHeaders::ptr_t buildDefaultHeaders();
static void uploadImageCoro(LLCoros::self& self, LLPointer<LLImageFormatted> image, std::string caption, bool add_location);
static LLCore::BufferArray::ptr_t buildPostData(const LLSD &data, LLPointer<LLImageFormatted> &image, const std::string &boundary);
static void post(LLPointer<LLImageFormatted> image, const LLSD& config, const std::string& url);
static void reportImageUploadStatus(bool ok);
static std::string getAuthCookie();