PageRenderTime 56ms CodeModel.GetById 6ms 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
Possible License(s): LGPL-2.1
  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. #include "llviewerprecompiledheaders.h"
  28. #include "llwebsharing.h"
  29. #include "llagentui.h"
  30. #include "llbufferstream.h"
  31. #include "llhttpclient.h"
  32. #include "llhttpstatuscodes.h"
  33. #include "llsdserialize.h"
  34. #include "llsdutil.h"
  35. #include "llurl.h"
  36. #include "llviewercontrol.h"
  37. #include <boost/regex.hpp>
  38. #include <boost/algorithm/string/replace.hpp>
  39. ///////////////////////////////////////////////////////////////////////////////
  40. //
  41. class LLWebSharingConfigResponder : public LLHTTPClient::Responder
  42. {
  43. LOG_CLASS(LLWebSharingConfigResponder);
  44. public:
  45. /// Overrides the default LLSD parsing behaviour, to allow parsing a JSON response.
  46. virtual void completedRaw(U32 status, const std::string& reason,
  47. const LLChannelDescriptors& channels,
  48. const LLIOPipe::buffer_ptr_t& buffer)
  49. {
  50. LLSD content;
  51. LLBufferStream istr(channels, buffer.get());
  52. LLPointer<LLSDParser> parser = new LLSDNotationParser();
  53. if (parser->parse(istr, content, LLSDSerialize::SIZE_UNLIMITED) == LLSDParser::PARSE_FAILURE)
  54. {
  55. LL_WARNS("WebSharing") << "Failed to deserialize LLSD from JSON response. " << " [" << status << "]: " << reason << LL_ENDL;
  56. }
  57. else
  58. {
  59. completed(status, reason, content);
  60. }
  61. }
  62. virtual void error(U32 status, const std::string& reason)
  63. {
  64. LL_WARNS("WebSharing") << "Error [" << status << "]: " << reason << LL_ENDL;
  65. }
  66. virtual void result(const LLSD& content)
  67. {
  68. LLWebSharing::instance().receiveConfig(content);
  69. }
  70. };
  71. ///////////////////////////////////////////////////////////////////////////////
  72. //
  73. class LLWebSharingOpenIDAuthResponder : public LLHTTPClient::Responder
  74. {
  75. LOG_CLASS(LLWebSharingOpenIDAuthResponder);
  76. public:
  77. /* virtual */ void completedHeader(U32 status, const std::string& reason, const LLSD& content)
  78. {
  79. completed(status, reason, content);
  80. }
  81. /* virtual */ void completedRaw(U32 status, const std::string& reason,
  82. const LLChannelDescriptors& channels,
  83. const LLIOPipe::buffer_ptr_t& buffer)
  84. {
  85. /// Left empty to override the default LLSD parsing behaviour.
  86. }
  87. virtual void error(U32 status, const std::string& reason)
  88. {
  89. if (HTTP_UNAUTHORIZED == status)
  90. {
  91. LL_WARNS("WebSharing") << "AU account not authenticated." << LL_ENDL;
  92. // *TODO: No account found on AU, so start the account creation process here.
  93. }
  94. else
  95. {
  96. LL_WARNS("WebSharing") << "Error [" << status << "]: " << reason << LL_ENDL;
  97. LLWebSharing::instance().retryOpenIDAuth();
  98. }
  99. }
  100. virtual void result(const LLSD& content)
  101. {
  102. if (content.has("set-cookie"))
  103. {
  104. // OpenID request succeeded and returned a session cookie.
  105. LLWebSharing::instance().receiveSessionCookie(content["set-cookie"].asString());
  106. }
  107. }
  108. };
  109. ///////////////////////////////////////////////////////////////////////////////
  110. //
  111. class LLWebSharingSecurityTokenResponder : public LLHTTPClient::Responder
  112. {
  113. LOG_CLASS(LLWebSharingSecurityTokenResponder);
  114. public:
  115. /// Overrides the default LLSD parsing behaviour, to allow parsing a JSON response.
  116. virtual void completedRaw(U32 status, const std::string& reason,
  117. const LLChannelDescriptors& channels,
  118. const LLIOPipe::buffer_ptr_t& buffer)
  119. {
  120. LLSD content;
  121. LLBufferStream istr(channels, buffer.get());
  122. LLPointer<LLSDParser> parser = new LLSDNotationParser();
  123. if (parser->parse(istr, content, LLSDSerialize::SIZE_UNLIMITED) == LLSDParser::PARSE_FAILURE)
  124. {
  125. LL_WARNS("WebSharing") << "Failed to deserialize LLSD from JSON response. " << " [" << status << "]: " << reason << LL_ENDL;
  126. LLWebSharing::instance().retryOpenIDAuth();
  127. }
  128. else
  129. {
  130. completed(status, reason, content);
  131. }
  132. }
  133. virtual void error(U32 status, const std::string& reason)
  134. {
  135. LL_WARNS("WebSharing") << "Error [" << status << "]: " << reason << LL_ENDL;
  136. LLWebSharing::instance().retryOpenIDAuth();
  137. }
  138. virtual void result(const LLSD& content)
  139. {
  140. if (content[0].has("st") && content[0].has("expires"))
  141. {
  142. const std::string& token = content[0]["st"].asString();
  143. const std::string& expires = content[0]["expires"].asString();
  144. if (LLWebSharing::instance().receiveSecurityToken(token, expires))
  145. {
  146. // Sucessfully received a valid security token.
  147. return;
  148. }
  149. }
  150. else
  151. {
  152. LL_WARNS("WebSharing") << "No security token received." << LL_ENDL;
  153. }
  154. LLWebSharing::instance().retryOpenIDAuth();
  155. }
  156. };
  157. ///////////////////////////////////////////////////////////////////////////////
  158. //
  159. class LLWebSharingUploadResponder : public LLHTTPClient::Responder
  160. {
  161. LOG_CLASS(LLWebSharingUploadResponder);
  162. public:
  163. /// Overrides the default LLSD parsing behaviour, to allow parsing a JSON response.
  164. virtual void completedRaw(U32 status, const std::string& reason,
  165. const LLChannelDescriptors& channels,
  166. const LLIOPipe::buffer_ptr_t& buffer)
  167. {
  168. /*
  169. // Dump the body, for debugging.
  170. LLBufferStream istr1(channels, buffer.get());
  171. std::ostringstream ostr;
  172. std::string body;
  173. while (istr1.good())
  174. {
  175. char buf[1024];
  176. istr1.read(buf, sizeof(buf));
  177. body.append(buf, istr1.gcount());
  178. }
  179. LL_DEBUGS("WebSharing") << body << LL_ENDL;
  180. */
  181. LLSD content;
  182. LLBufferStream istr(channels, buffer.get());
  183. LLPointer<LLSDParser> parser = new LLSDNotationParser();
  184. if (parser->parse(istr, content, LLSDSerialize::SIZE_UNLIMITED) == LLSDParser::PARSE_FAILURE)
  185. {
  186. LL_WARNS("WebSharing") << "Failed to deserialize LLSD from JSON response. " << " [" << status << "]: " << reason << LL_ENDL;
  187. }
  188. else
  189. {
  190. completed(status, reason, content);
  191. }
  192. }
  193. virtual void error(U32 status, const std::string& reason)
  194. {
  195. LL_WARNS("WebSharing") << "Error [" << status << "]: " << reason << LL_ENDL;
  196. }
  197. virtual void result(const LLSD& content)
  198. {
  199. if (content[0].has("result") && content[0].has("id") &&
  200. content[0]["id"].asString() == "newMediaItem")
  201. {
  202. // *TODO: Upload successful, continue from here to post metadata and create AU activity.
  203. }
  204. else
  205. {
  206. LL_WARNS("WebSharing") << "Error [" << content[0]["code"].asString()
  207. << "]: " << content[0]["message"].asString() << LL_ENDL;
  208. }
  209. }
  210. };
  211. ///////////////////////////////////////////////////////////////////////////////
  212. //
  213. LLWebSharing::LLWebSharing()
  214. : mConfig(),
  215. mSecurityToken(LLSD::emptyMap()),
  216. mEnabled(false),
  217. mRetries(0),
  218. mImage(NULL),
  219. mMetadata(LLSD::emptyMap())
  220. {
  221. }
  222. void LLWebSharing::init()
  223. {
  224. if (!mEnabled)
  225. {
  226. sendConfigRequest();
  227. }
  228. }
  229. bool LLWebSharing::shareSnapshot(LLImageJPEG* snapshot, LLSD& metadata)
  230. {
  231. LL_INFOS("WebSharing") << metadata << LL_ENDL;
  232. if (mImage)
  233. {
  234. // *TODO: Handle this possibility properly, queue them up?
  235. LL_WARNS("WebSharing") << "Snapshot upload already in progress." << LL_ENDL;
  236. return false;
  237. }
  238. mImage = snapshot;
  239. mMetadata = metadata;
  240. // *TODO: Check whether we have a valid security token already and re-use it.
  241. sendOpenIDAuthRequest();
  242. return true;
  243. }
  244. bool LLWebSharing::setOpenIDCookie(const std::string& cookie)
  245. {
  246. LL_DEBUGS("WebSharing") << "Setting OpenID cookie " << cookie << LL_ENDL;
  247. mOpenIDCookie = cookie;
  248. return validateConfig();
  249. }
  250. bool LLWebSharing::receiveConfig(const LLSD& config)
  251. {
  252. LL_DEBUGS("WebSharing") << "Received config data: " << config << LL_ENDL;
  253. mConfig = config;
  254. return validateConfig();
  255. }
  256. bool LLWebSharing::receiveSessionCookie(const std::string& cookie)
  257. {
  258. LL_DEBUGS("WebSharing") << "Received AU session cookie: " << cookie << LL_ENDL;
  259. mSessionCookie = cookie;
  260. // Fetch a security token using the new session cookie.
  261. LLWebSharing::instance().sendSecurityTokenRequest();
  262. return (!mSessionCookie.empty());
  263. }
  264. bool LLWebSharing::receiveSecurityToken(const std::string& token, const std::string& expires)
  265. {
  266. mSecurityToken["st"] = token;
  267. mSecurityToken["expires"] = LLDate(expires);
  268. if (!securityTokenIsValid(mSecurityToken))
  269. {
  270. LL_WARNS("WebSharing") << "Invalid security token received: \"" << token << "\" Expires: " << expires << LL_ENDL;
  271. return false;
  272. }
  273. LL_DEBUGS("WebSharing") << "Received security token: \"" << token << "\" Expires: " << expires << LL_ENDL;
  274. mRetries = 0;
  275. // Continue the upload process now that we have a security token.
  276. sendUploadRequest();
  277. return true;
  278. }
  279. void LLWebSharing::sendConfigRequest()
  280. {
  281. std::string config_url = gSavedSettings.getString("SnapshotConfigURL");
  282. LL_DEBUGS("WebSharing") << "Requesting Snapshot Sharing config data from: " << config_url << LL_ENDL;
  283. LLSD headers = LLSD::emptyMap();
  284. headers["Accept"] = "application/json";
  285. LLHTTPClient::get(config_url, new LLWebSharingConfigResponder(), headers);
  286. }
  287. void LLWebSharing::sendOpenIDAuthRequest()
  288. {
  289. std::string auth_url = mConfig["openIdAuthUrl"];
  290. LL_DEBUGS("WebSharing") << "Starting OpenID Auth: " << auth_url << LL_ENDL;
  291. LLSD headers = LLSD::emptyMap();
  292. headers["Cookie"] = mOpenIDCookie;
  293. headers["Accept"] = "*/*";
  294. // Send request, successful login will trigger fetching a security token.
  295. LLHTTPClient::get(auth_url, new LLWebSharingOpenIDAuthResponder(), headers);
  296. }
  297. bool LLWebSharing::retryOpenIDAuth()
  298. {
  299. if (mRetries++ >= MAX_AUTH_RETRIES)
  300. {
  301. LL_WARNS("WebSharing") << "Exceeded maximum number of authorization attempts, aborting." << LL_ENDL;
  302. mRetries = 0;
  303. return false;
  304. }
  305. LL_WARNS("WebSharing") << "Authorization failed, retrying (" << mRetries << "/" << MAX_AUTH_RETRIES << ")" << LL_ENDL;
  306. sendOpenIDAuthRequest();
  307. return true;
  308. }
  309. void LLWebSharing::sendSecurityTokenRequest()
  310. {
  311. std::string token_url = mConfig["securityTokenUrl"];
  312. LL_DEBUGS("WebSharing") << "Fetching security token from: " << token_url << LL_ENDL;
  313. LLSD headers = LLSD::emptyMap();
  314. headers["Cookie"] = mSessionCookie;
  315. headers["Accept"] = "application/json";
  316. headers["Content-Type"] = "application/json";
  317. std::ostringstream body;
  318. body << "{ \"gadgets\": [{ \"url\":\""
  319. << mConfig["gadgetSpecUrl"].asString()
  320. << "\" }] }";
  321. // postRaw() takes ownership of the buffer and releases it later.
  322. size_t size = body.str().size();
  323. U8 *data = new U8[size];
  324. memcpy(data, body.str().data(), size);
  325. // Send request, receiving a valid token will trigger snapshot upload.
  326. LLHTTPClient::postRaw(token_url, data, size, new LLWebSharingSecurityTokenResponder(), headers);
  327. }
  328. void LLWebSharing::sendUploadRequest()
  329. {
  330. LLUriTemplate upload_template(mConfig["openSocialRpcUrlTemplate"].asString());
  331. std::string upload_url(upload_template.buildURI(mSecurityToken));
  332. LL_DEBUGS("WebSharing") << "Posting upload to: " << upload_url << LL_ENDL;
  333. static const std::string BOUNDARY("------------abcdef012345xyZ");
  334. LLSD headers = LLSD::emptyMap();
  335. headers["Cookie"] = mSessionCookie;
  336. headers["Accept"] = "application/json";
  337. headers["Content-Type"] = "multipart/form-data; boundary=" + BOUNDARY;
  338. std::ostringstream body;
  339. body << "--" << BOUNDARY << "\r\n"
  340. << "Content-Disposition: form-data; name=\"request\"\r\n\r\n"
  341. << "[{"
  342. << "\"method\":\"mediaItems.create\","
  343. << "\"params\": {"
  344. << "\"userId\":[\"@me\"],"
  345. << "\"groupId\":\"@self\","
  346. << "\"mediaItem\": {"
  347. << "\"mimeType\":\"image/jpeg\","
  348. << "\"type\":\"image\","
  349. << "\"url\":\"@field:image1\""
  350. << "}"
  351. << "},"
  352. << "\"id\":\"newMediaItem\""
  353. << "}]"
  354. << "--" << BOUNDARY << "\r\n"
  355. << "Content-Disposition: form-data; name=\"image1\"\r\n\r\n";
  356. // Insert the image data.
  357. // *FIX: Treating this as a string will probably screw it up ...
  358. U8* image_data = mImage->getData();
  359. for (S32 i = 0; i < mImage->getDataSize(); ++i)
  360. {
  361. body << image_data[i];
  362. }
  363. body << "\r\n--" << BOUNDARY << "--\r\n";
  364. // postRaw() takes ownership of the buffer and releases it later.
  365. size_t size = body.str().size();
  366. U8 *data = new U8[size];
  367. memcpy(data, body.str().data(), size);
  368. // Send request, successful upload will trigger posting metadata.
  369. LLHTTPClient::postRaw(upload_url, data, size, new LLWebSharingUploadResponder(), headers);
  370. }
  371. bool LLWebSharing::validateConfig()
  372. {
  373. // Check the OpenID Cookie has been set.
  374. if (mOpenIDCookie.empty())
  375. {
  376. mEnabled = false;
  377. return mEnabled;
  378. }
  379. if (!mConfig.isMap())
  380. {
  381. mEnabled = false;
  382. return mEnabled;
  383. }
  384. // Template to match the received config against.
  385. LLSD required(LLSD::emptyMap());
  386. required["gadgetSpecUrl"] = "";
  387. required["loginTokenUrl"] = "";
  388. required["openIdAuthUrl"] = "";
  389. required["photoPageUrlTemplate"] = "";
  390. required["openSocialRpcUrlTemplate"] = "";
  391. required["securityTokenUrl"] = "";
  392. required["tokenBasedLoginUrlTemplate"] = "";
  393. required["viewerIdUrl"] = "";
  394. std::string mismatch(llsd_matches(required, mConfig));
  395. if (!mismatch.empty())
  396. {
  397. LL_WARNS("WebSharing") << "Malformed config data response: " << mismatch << LL_ENDL;
  398. mEnabled = false;
  399. return mEnabled;
  400. }
  401. mEnabled = true;
  402. return mEnabled;
  403. }
  404. // static
  405. bool LLWebSharing::securityTokenIsValid(LLSD& token)
  406. {
  407. return (token.has("st") &&
  408. token.has("expires") &&
  409. (token["st"].asString() != "") &&
  410. (token["expires"].asDate() > LLDate::now()));
  411. }
  412. ///////////////////////////////////////////////////////////////////////////////
  413. //
  414. LLUriTemplate::LLUriTemplate(const std::string& uri_template)
  415. :
  416. mTemplate(uri_template)
  417. {
  418. }
  419. std::string LLUriTemplate::buildURI(const LLSD& vars)
  420. {
  421. // *TODO: Separate parsing the template from building the URI.
  422. // Parsing only needs to happen on construction/assignnment.
  423. static const std::string VAR_NAME_REGEX("[[:alpha:]][[:alnum:]\\._-]*");
  424. // Capture var name with and without surrounding {}
  425. static const std::string VAR_REGEX("\\{(" + VAR_NAME_REGEX + ")\\}");
  426. // Capture delimiter and comma separated list of var names.
  427. static const std::string JOIN_REGEX("\\{-join\\|(&)\\|(" + VAR_NAME_REGEX + "(?:," + VAR_NAME_REGEX + ")*)\\}");
  428. std::string uri = mTemplate;
  429. boost::smatch results;
  430. // Validate and expand join operators : {-join|&|var1,var2,...}
  431. boost::regex join_regex(JOIN_REGEX);
  432. while (boost::regex_search(uri, results, join_regex))
  433. {
  434. // Extract the list of var names from the results.
  435. std::string delim = results[1].str();
  436. std::string var_list = results[2].str();
  437. // Expand the list of vars into a query string with their values
  438. std::string query = expandJoin(delim, var_list, vars);
  439. // Substitute the query string into the template.
  440. uri = boost::regex_replace(uri, join_regex, query, boost::format_first_only);
  441. }
  442. // Expand vars : {var1}
  443. boost::regex var_regex(VAR_REGEX);
  444. std::set<std::string> var_names;
  445. std::string::const_iterator start = uri.begin();
  446. std::string::const_iterator end = uri.end();
  447. // Extract the var names used.
  448. while (boost::regex_search(start, end, results, var_regex))
  449. {
  450. var_names.insert(results[1].str());
  451. start = results[0].second;
  452. }
  453. // Replace each var with its value.
  454. for (std::set<std::string>::const_iterator it = var_names.begin(); it != var_names.end(); ++it)
  455. {
  456. std::string var = *it;
  457. if (vars.has(var))
  458. {
  459. boost::replace_all(uri, "{" + var + "}", vars[var].asString());
  460. }
  461. }
  462. return uri;
  463. }
  464. std::string LLUriTemplate::expandJoin(const std::string& delim, const std::string& var_list, const LLSD& vars)
  465. {
  466. std::ostringstream query;
  467. typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
  468. boost::char_separator<char> sep(",");
  469. tokenizer var_names(var_list, sep);
  470. tokenizer::const_iterator it = var_names.begin();
  471. // First var does not need a delimiter
  472. if (it != var_names.end())
  473. {
  474. const std::string& name = *it;
  475. if (vars.has(name))
  476. {
  477. // URL encode the value before appending the name=value pair.
  478. query << name << "=" << escapeURL(vars[name].asString());
  479. }
  480. }
  481. for (++it; it != var_names.end(); ++it)
  482. {
  483. const std::string& name = *it;
  484. if (vars.has(name))
  485. {
  486. // URL encode the value before appending the name=value pair.
  487. query << delim << name << "=" << escapeURL(vars[name].asString());
  488. }
  489. }
  490. return query.str();
  491. }
  492. // static
  493. std::string LLUriTemplate::escapeURL(const std::string& unescaped)
  494. {
  495. char* escaped = curl_escape(unescaped.c_str(), unescaped.size());
  496. std::string result = escaped;
  497. curl_free(escaped);
  498. return result;
  499. }