/indra/llplugin/llplugincookiestore.cpp

https://bitbucket.org/lindenlab/viewer-beta/ · C++ · 664 lines · 553 code · 44 blank · 67 comment · 68 complexity · 2d69056fb42b91afe0f51f100b3f02a2 MD5 · raw file

  1. /**
  2. * @file llplugincookiestore.cpp
  3. * @brief LLPluginCookieStore provides central storage for http cookies used by plugins
  4. *
  5. * @cond
  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. * @endcond
  27. */
  28. #include "linden_common.h"
  29. #include "indra_constants.h"
  30. #include "llplugincookiestore.h"
  31. #include <iostream>
  32. // for curl_getdate() (apparently parsing RFC 1123 dates is hard)
  33. #include <curl/curl.h>
  34. LLPluginCookieStore::LLPluginCookieStore():
  35. mHasChangedCookies(false)
  36. {
  37. }
  38. LLPluginCookieStore::~LLPluginCookieStore()
  39. {
  40. clearCookies();
  41. }
  42. LLPluginCookieStore::Cookie::Cookie(const std::string &s, std::string::size_type cookie_start, std::string::size_type cookie_end):
  43. mCookie(s, cookie_start, cookie_end - cookie_start),
  44. mNameStart(0), mNameEnd(0),
  45. mValueStart(0), mValueEnd(0),
  46. mDomainStart(0), mDomainEnd(0),
  47. mPathStart(0), mPathEnd(0),
  48. mDead(false), mChanged(true)
  49. {
  50. }
  51. LLPluginCookieStore::Cookie *LLPluginCookieStore::Cookie::createFromString(const std::string &s, std::string::size_type cookie_start, std::string::size_type cookie_end, const std::string &host)
  52. {
  53. Cookie *result = new Cookie(s, cookie_start, cookie_end);
  54. if(!result->parse(host))
  55. {
  56. delete result;
  57. result = NULL;
  58. }
  59. return result;
  60. }
  61. std::string LLPluginCookieStore::Cookie::getKey() const
  62. {
  63. std::string result;
  64. if(mDomainEnd > mDomainStart)
  65. {
  66. result += mCookie.substr(mDomainStart, mDomainEnd - mDomainStart);
  67. }
  68. result += ';';
  69. if(mPathEnd > mPathStart)
  70. {
  71. result += mCookie.substr(mPathStart, mPathEnd - mPathStart);
  72. }
  73. result += ';';
  74. result += mCookie.substr(mNameStart, mNameEnd - mNameStart);
  75. return result;
  76. }
  77. bool LLPluginCookieStore::Cookie::parse(const std::string &host)
  78. {
  79. bool first_field = true;
  80. std::string::size_type cookie_end = mCookie.size();
  81. std::string::size_type field_start = 0;
  82. LL_DEBUGS("CookieStoreParse") << "parsing cookie: " << mCookie << LL_ENDL;
  83. while(field_start < cookie_end)
  84. {
  85. // Finding the start of the next field requires honoring special quoting rules
  86. // see the definition of 'quoted-string' in rfc2616 for details
  87. std::string::size_type next_field_start = findFieldEnd(field_start);
  88. // The end of this field should not include the terminating ';' or any trailing whitespace
  89. std::string::size_type field_end = mCookie.find_last_not_of("; ", next_field_start);
  90. if(field_end == std::string::npos || field_end < field_start)
  91. {
  92. // This field was empty or all whitespace. Set end = start so it shows as empty.
  93. field_end = field_start;
  94. }
  95. else if (field_end < next_field_start)
  96. {
  97. // we actually want the index of the char _after_ what 'last not of' found
  98. ++field_end;
  99. }
  100. // find the start of the actual name (skip separator and possible whitespace)
  101. std::string::size_type name_start = mCookie.find_first_not_of("; ", field_start);
  102. if(name_start == std::string::npos || name_start > next_field_start)
  103. {
  104. // Again, nothing but whitespace.
  105. name_start = field_start;
  106. }
  107. // the name and value are separated by the first equals sign
  108. std::string::size_type name_value_sep = mCookie.find_first_of("=", name_start);
  109. if(name_value_sep == std::string::npos || name_value_sep > field_end)
  110. {
  111. // No separator found, so this is a field without an =
  112. name_value_sep = field_end;
  113. }
  114. // the name end is before the name-value separator
  115. std::string::size_type name_end = mCookie.find_last_not_of("= ", name_value_sep);
  116. if(name_end == std::string::npos || name_end < name_start)
  117. {
  118. // I'm not sure how we'd hit this case... it seems like it would have to be an empty name.
  119. name_end = name_start;
  120. }
  121. else if (name_end < name_value_sep)
  122. {
  123. // we actually want the index of the char _after_ what 'last not of' found
  124. ++name_end;
  125. }
  126. // Value is between the name-value sep and the end of the field.
  127. std::string::size_type value_start = mCookie.find_first_not_of("= ", name_value_sep);
  128. if(value_start == std::string::npos || value_start > field_end)
  129. {
  130. // All whitespace or empty value
  131. value_start = field_end;
  132. }
  133. std::string::size_type value_end = mCookie.find_last_not_of("; ", field_end);
  134. if(value_end == std::string::npos || value_end < value_start)
  135. {
  136. // All whitespace or empty value
  137. value_end = value_start;
  138. }
  139. else if (value_end < field_end)
  140. {
  141. // we actually want the index of the char _after_ what 'last not of' found
  142. ++value_end;
  143. }
  144. LL_DEBUGS("CookieStoreParse")
  145. << " field name: \"" << mCookie.substr(name_start, name_end - name_start)
  146. << "\", value: \"" << mCookie.substr(value_start, value_end - value_start) << "\""
  147. << LL_ENDL;
  148. // See whether this field is one we know
  149. if(first_field)
  150. {
  151. // The first field is the name=value pair
  152. mNameStart = name_start;
  153. mNameEnd = name_end;
  154. mValueStart = value_start;
  155. mValueEnd = value_end;
  156. first_field = false;
  157. }
  158. else
  159. {
  160. // Subsequent fields must come from the set in rfc2109
  161. if(matchName(name_start, name_end, "expires"))
  162. {
  163. std::string date_string(mCookie, value_start, value_end - value_start);
  164. // If the cookie contains an "expires" field, it MUST contain a parsable date.
  165. // HACK: LLDate apparently can't PARSE an rfc1123-format date, even though it can GENERATE one.
  166. // The curl function curl_getdate can do this, but I'm hesitant to unilaterally introduce a curl dependency in LLDate.
  167. #if 1
  168. time_t date = curl_getdate(date_string.c_str(), NULL );
  169. mDate.secondsSinceEpoch((F64)date);
  170. LL_DEBUGS("CookieStoreParse") << " expire date parsed to: " << mDate.asRFC1123() << LL_ENDL;
  171. #else
  172. // This doesn't work (rfc1123-format dates cause it to fail)
  173. if(!mDate.fromString(date_string))
  174. {
  175. // Date failed to parse.
  176. LL_WARNS("CookieStoreParse") << "failed to parse cookie's expire date: " << date << LL_ENDL;
  177. return false;
  178. }
  179. #endif
  180. }
  181. else if(matchName(name_start, name_end, "domain"))
  182. {
  183. mDomainStart = value_start;
  184. mDomainEnd = value_end;
  185. }
  186. else if(matchName(name_start, name_end, "path"))
  187. {
  188. mPathStart = value_start;
  189. mPathEnd = value_end;
  190. }
  191. else if(matchName(name_start, name_end, "max-age"))
  192. {
  193. // TODO: how should we handle this?
  194. }
  195. else if(matchName(name_start, name_end, "secure"))
  196. {
  197. // We don't care about the value of this field (yet)
  198. }
  199. else if(matchName(name_start, name_end, "version"))
  200. {
  201. // We don't care about the value of this field (yet)
  202. }
  203. else if(matchName(name_start, name_end, "comment"))
  204. {
  205. // We don't care about the value of this field (yet)
  206. }
  207. else if(matchName(name_start, name_end, "httponly"))
  208. {
  209. // We don't care about the value of this field (yet)
  210. }
  211. else
  212. {
  213. // An unknown field is a parse failure
  214. LL_WARNS("CookieStoreParse") << "unexpected field name: " << mCookie.substr(name_start, name_end - name_start) << LL_ENDL;
  215. return false;
  216. }
  217. }
  218. // move on to the next field, skipping this field's separator and any leading whitespace
  219. field_start = mCookie.find_first_not_of("; ", next_field_start);
  220. }
  221. // The cookie MUST have a name
  222. if(mNameEnd <= mNameStart)
  223. return false;
  224. // If the cookie doesn't have a domain, add the current host as the domain.
  225. if(mDomainEnd <= mDomainStart)
  226. {
  227. if(host.empty())
  228. {
  229. // no domain and no current host -- this is a parse failure.
  230. return false;
  231. }
  232. // Figure out whether this cookie ended with a ";" or not...
  233. std::string::size_type last_char = mCookie.find_last_not_of(" ");
  234. if((last_char != std::string::npos) && (mCookie[last_char] != ';'))
  235. {
  236. mCookie += ";";
  237. }
  238. mCookie += " domain=";
  239. mDomainStart = mCookie.size();
  240. mCookie += host;
  241. mDomainEnd = mCookie.size();
  242. LL_DEBUGS("CookieStoreParse") << "added domain (" << mDomainStart << " to " << mDomainEnd << "), new cookie is: " << mCookie << LL_ENDL;
  243. }
  244. // If the cookie doesn't have a path, add "/".
  245. if(mPathEnd <= mPathStart)
  246. {
  247. // Figure out whether this cookie ended with a ";" or not...
  248. std::string::size_type last_char = mCookie.find_last_not_of(" ");
  249. if((last_char != std::string::npos) && (mCookie[last_char] != ';'))
  250. {
  251. mCookie += ";";
  252. }
  253. mCookie += " path=";
  254. mPathStart = mCookie.size();
  255. mCookie += "/";
  256. mPathEnd = mCookie.size();
  257. LL_DEBUGS("CookieStoreParse") << "added path (" << mPathStart << " to " << mPathEnd << "), new cookie is: " << mCookie << LL_ENDL;
  258. }
  259. return true;
  260. }
  261. std::string::size_type LLPluginCookieStore::Cookie::findFieldEnd(std::string::size_type start, std::string::size_type end)
  262. {
  263. std::string::size_type result = start;
  264. if(end == std::string::npos)
  265. end = mCookie.size();
  266. bool in_quotes = false;
  267. for(; (result < end); result++)
  268. {
  269. switch(mCookie[result])
  270. {
  271. case '\\':
  272. if(in_quotes)
  273. result++; // The next character is backslash-quoted. Skip over it.
  274. break;
  275. case '"':
  276. in_quotes = !in_quotes;
  277. break;
  278. case ';':
  279. if(!in_quotes)
  280. return result;
  281. break;
  282. }
  283. }
  284. // If we got here, no ';' was found.
  285. return end;
  286. }
  287. bool LLPluginCookieStore::Cookie::matchName(std::string::size_type start, std::string::size_type end, const char *name)
  288. {
  289. // NOTE: this assumes 'name' is already in lowercase. The code which uses it should be able to arrange this...
  290. while((start < end) && (*name != '\0'))
  291. {
  292. if(tolower(mCookie[start]) != *name)
  293. return false;
  294. start++;
  295. name++;
  296. }
  297. // iff both strings hit the end at the same time, they're equal.
  298. return ((start == end) && (*name == '\0'));
  299. }
  300. std::string LLPluginCookieStore::getAllCookies()
  301. {
  302. std::stringstream result;
  303. writeAllCookies(result);
  304. return result.str();
  305. }
  306. void LLPluginCookieStore::writeAllCookies(std::ostream& s)
  307. {
  308. cookie_map_t::iterator iter;
  309. for(iter = mCookies.begin(); iter != mCookies.end(); iter++)
  310. {
  311. // Don't return expired cookies
  312. if(!iter->second->isDead())
  313. {
  314. s << (iter->second->getCookie()) << "\n";
  315. }
  316. }
  317. }
  318. std::string LLPluginCookieStore::getPersistentCookies()
  319. {
  320. std::stringstream result;
  321. writePersistentCookies(result);
  322. return result.str();
  323. }
  324. void LLPluginCookieStore::writePersistentCookies(std::ostream& s)
  325. {
  326. cookie_map_t::iterator iter;
  327. for(iter = mCookies.begin(); iter != mCookies.end(); iter++)
  328. {
  329. // Don't return expired cookies or session cookies
  330. if(!iter->second->isDead() && !iter->second->isSessionCookie())
  331. {
  332. s << iter->second->getCookie() << "\n";
  333. }
  334. }
  335. }
  336. std::string LLPluginCookieStore::getChangedCookies(bool clear_changed)
  337. {
  338. std::stringstream result;
  339. writeChangedCookies(result, clear_changed);
  340. return result.str();
  341. }
  342. void LLPluginCookieStore::writeChangedCookies(std::ostream& s, bool clear_changed)
  343. {
  344. if(mHasChangedCookies)
  345. {
  346. lldebugs << "returning changed cookies: " << llendl;
  347. cookie_map_t::iterator iter;
  348. for(iter = mCookies.begin(); iter != mCookies.end(); )
  349. {
  350. cookie_map_t::iterator next = iter;
  351. next++;
  352. // Only return cookies marked as "changed"
  353. if(iter->second->isChanged())
  354. {
  355. s << iter->second->getCookie() << "\n";
  356. lldebugs << " " << iter->second->getCookie() << llendl;
  357. // If requested, clear the changed mark
  358. if(clear_changed)
  359. {
  360. if(iter->second->isDead())
  361. {
  362. // If this cookie was previously marked dead, it needs to be removed entirely.
  363. delete iter->second;
  364. mCookies.erase(iter);
  365. }
  366. else
  367. {
  368. // Not dead, just mark as not changed.
  369. iter->second->setChanged(false);
  370. }
  371. }
  372. }
  373. iter = next;
  374. }
  375. }
  376. if(clear_changed)
  377. mHasChangedCookies = false;
  378. }
  379. void LLPluginCookieStore::setAllCookies(const std::string &cookies, bool mark_changed)
  380. {
  381. clearCookies();
  382. setCookies(cookies, mark_changed);
  383. }
  384. void LLPluginCookieStore::readAllCookies(std::istream& s, bool mark_changed)
  385. {
  386. clearCookies();
  387. readCookies(s, mark_changed);
  388. }
  389. void LLPluginCookieStore::setCookies(const std::string &cookies, bool mark_changed)
  390. {
  391. std::string::size_type start = 0;
  392. while(start != std::string::npos)
  393. {
  394. std::string::size_type end = cookies.find_first_of("\r\n", start);
  395. if(end > start)
  396. {
  397. // The line is non-empty. Try to create a cookie from it.
  398. setOneCookie(cookies, start, end, mark_changed);
  399. }
  400. start = cookies.find_first_not_of("\r\n ", end);
  401. }
  402. }
  403. void LLPluginCookieStore::setCookiesFromHost(const std::string &cookies, const std::string &host, bool mark_changed)
  404. {
  405. std::string::size_type start = 0;
  406. while(start != std::string::npos)
  407. {
  408. std::string::size_type end = cookies.find_first_of("\r\n", start);
  409. if(end > start)
  410. {
  411. // The line is non-empty. Try to create a cookie from it.
  412. setOneCookie(cookies, start, end, mark_changed, host);
  413. }
  414. start = cookies.find_first_not_of("\r\n ", end);
  415. }
  416. }
  417. void LLPluginCookieStore::readCookies(std::istream& s, bool mark_changed)
  418. {
  419. std::string line;
  420. while(s.good() && !s.eof())
  421. {
  422. std::getline(s, line);
  423. if(!line.empty())
  424. {
  425. // Try to create a cookie from this line.
  426. setOneCookie(line, 0, std::string::npos, mark_changed);
  427. }
  428. }
  429. }
  430. std::string LLPluginCookieStore::quoteString(const std::string &s)
  431. {
  432. std::stringstream result;
  433. result << '"';
  434. for(std::string::size_type i = 0; i < s.size(); ++i)
  435. {
  436. char c = s[i];
  437. switch(c)
  438. {
  439. // All these separators need to be quoted in HTTP headers, according to section 2.2 of rfc 2616:
  440. case '(': case ')': case '<': case '>': case '@':
  441. case ',': case ';': case ':': case '\\': case '"':
  442. case '/': case '[': case ']': case '?': case '=':
  443. case '{': case '}': case ' ': case '\t':
  444. result << '\\';
  445. break;
  446. }
  447. result << c;
  448. }
  449. result << '"';
  450. return result.str();
  451. }
  452. std::string LLPluginCookieStore::unquoteString(const std::string &s)
  453. {
  454. std::stringstream result;
  455. bool in_quotes = false;
  456. for(std::string::size_type i = 0; i < s.size(); ++i)
  457. {
  458. char c = s[i];
  459. switch(c)
  460. {
  461. case '\\':
  462. if(in_quotes)
  463. {
  464. // The next character is backslash-quoted. Pass it through untouched.
  465. ++i;
  466. if(i < s.size())
  467. {
  468. result << s[i];
  469. }
  470. continue;
  471. }
  472. break;
  473. case '"':
  474. in_quotes = !in_quotes;
  475. continue;
  476. break;
  477. }
  478. result << c;
  479. }
  480. return result.str();
  481. }
  482. // The flow for deleting a cookie is non-obvious enough that I should call it out here...
  483. // Deleting a cookie is done by setting a cookie with the same name, path, and domain, but with an expire timestamp in the past.
  484. // (This is exactly how a web server tells a browser to delete a cookie.)
  485. // When deleting with mark_changed set to true, this replaces the existing cookie in the list with an entry that's marked both dead and changed.
  486. // Some time later when writeChangedCookies() is called with clear_changed set to true, the dead cookie is deleted from the list after being returned, so that the
  487. // delete operation (in the form of the expired cookie) is passed along.
  488. void LLPluginCookieStore::setOneCookie(const std::string &s, std::string::size_type cookie_start, std::string::size_type cookie_end, bool mark_changed, const std::string &host)
  489. {
  490. Cookie *cookie = Cookie::createFromString(s, cookie_start, cookie_end, host);
  491. if(cookie)
  492. {
  493. LL_DEBUGS("CookieStoreUpdate") << "setting cookie: " << cookie->getCookie() << LL_ENDL;
  494. // Create a key for this cookie
  495. std::string key = cookie->getKey();
  496. // Check to see whether this cookie should have expired
  497. if(!cookie->isSessionCookie() && (cookie->getDate() < LLDate::now()))
  498. {
  499. // This cookie has expired.
  500. if(mark_changed)
  501. {
  502. // If we're marking cookies as changed, we should keep it anyway since we'll need to send it out with deltas.
  503. cookie->setDead(true);
  504. LL_DEBUGS("CookieStoreUpdate") << " marking dead" << LL_ENDL;
  505. }
  506. else
  507. {
  508. // If we're not marking cookies as changed, we don't need to keep this cookie at all.
  509. // If the cookie was already in the list, delete it.
  510. removeCookie(key);
  511. delete cookie;
  512. cookie = NULL;
  513. LL_DEBUGS("CookieStoreUpdate") << " removing" << LL_ENDL;
  514. }
  515. }
  516. if(cookie)
  517. {
  518. // If it already exists in the map, replace it.
  519. cookie_map_t::iterator iter = mCookies.find(key);
  520. if(iter != mCookies.end())
  521. {
  522. if(iter->second->getCookie() == cookie->getCookie())
  523. {
  524. // The new cookie is identical to the old -- don't mark as changed.
  525. // Just leave the old one in the map.
  526. delete cookie;
  527. cookie = NULL;
  528. LL_DEBUGS("CookieStoreUpdate") << " unchanged" << LL_ENDL;
  529. }
  530. else
  531. {
  532. // A matching cookie was already in the map. Replace it.
  533. delete iter->second;
  534. iter->second = cookie;
  535. cookie->setChanged(mark_changed);
  536. if(mark_changed)
  537. mHasChangedCookies = true;
  538. LL_DEBUGS("CookieStoreUpdate") << " replacing" << LL_ENDL;
  539. }
  540. }
  541. else
  542. {
  543. // The cookie wasn't in the map. Insert it.
  544. mCookies.insert(std::make_pair(key, cookie));
  545. cookie->setChanged(mark_changed);
  546. if(mark_changed)
  547. mHasChangedCookies = true;
  548. LL_DEBUGS("CookieStoreUpdate") << " adding" << LL_ENDL;
  549. }
  550. }
  551. }
  552. else
  553. {
  554. LL_WARNS("CookieStoreUpdate") << "failed to parse cookie: " << s.substr(cookie_start, cookie_end - cookie_start) << LL_ENDL;
  555. }
  556. }
  557. void LLPluginCookieStore::clearCookies()
  558. {
  559. while(!mCookies.empty())
  560. {
  561. cookie_map_t::iterator iter = mCookies.begin();
  562. delete iter->second;
  563. mCookies.erase(iter);
  564. }
  565. }
  566. void LLPluginCookieStore::removeCookie(const std::string &key)
  567. {
  568. cookie_map_t::iterator iter = mCookies.find(key);
  569. if(iter != mCookies.end())
  570. {
  571. delete iter->second;
  572. mCookies.erase(iter);
  573. }
  574. }