summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--indra/llplugin/CMakeLists.txt2
-rw-r--r--indra/llplugin/llpluginclassmedia.cpp14
-rw-r--r--indra/llplugin/llpluginclassmedia.h1
-rw-r--r--indra/llplugin/llpluginclassmediaowner.h2
-rw-r--r--indra/llplugin/llplugincookiestore.cpp600
-rw-r--r--indra/llplugin/llplugincookiestore.h122
-rw-r--r--indra/media_plugins/webkit/media_plugin_webkit.cpp17
-rw-r--r--indra/newview/llappviewer.cpp2
-rw-r--r--indra/newview/llstartup.cpp3
-rw-r--r--indra/newview/llviewermedia.cpp150
-rw-r--r--indra/newview/llviewermedia.h11
-rw-r--r--install.xml8
12 files changed, 927 insertions, 5 deletions
diff --git a/indra/llplugin/CMakeLists.txt b/indra/llplugin/CMakeLists.txt
index 6706775d4f..def9fcbeae 100644
--- a/indra/llplugin/CMakeLists.txt
+++ b/indra/llplugin/CMakeLists.txt
@@ -23,6 +23,7 @@ include_directories(
set(llplugin_SOURCE_FILES
llpluginclassmedia.cpp
+ llplugincookiestore.cpp
llplugininstance.cpp
llpluginmessage.cpp
llpluginmessagepipe.cpp
@@ -36,6 +37,7 @@ set(llplugin_HEADER_FILES
llpluginclassmedia.h
llpluginclassmediaowner.h
+ llplugincookiestore.h
llplugininstance.h
llpluginmessage.h
llpluginmessageclasses.h
diff --git a/indra/llplugin/llpluginclassmedia.cpp b/indra/llplugin/llpluginclassmedia.cpp
index bf0e19473e..e09b511a6e 100644
--- a/indra/llplugin/llpluginclassmedia.cpp
+++ b/indra/llplugin/llpluginclassmedia.cpp
@@ -993,6 +993,13 @@ void LLPluginClassMedia::receivePluginMessage(const LLPluginMessage &message)
mClickTargetType = TARGET_NONE;
mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CLICK_LINK_NOFOLLOW);
}
+ else if(message_name == "cookie_set")
+ {
+ if(mOwner)
+ {
+ mOwner->handleCookieSet(this, message.getValue("cookie"));
+ }
+ }
else
{
LL_WARNS("Plugin") << "Unknown " << message_name << " class message: " << message_name << LL_ENDL;
@@ -1076,6 +1083,13 @@ void LLPluginClassMedia::clear_cookies()
sendMessage(message);
}
+void LLPluginClassMedia::set_cookies(const std::string &cookies)
+{
+ LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "set_cookies");
+ message.setValue("cookies", cookies);
+ sendMessage(message);
+}
+
void LLPluginClassMedia::enable_cookies(bool enable)
{
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "enable_cookies");
diff --git a/indra/llplugin/llpluginclassmedia.h b/indra/llplugin/llpluginclassmedia.h
index 79356beb68..8c7b00f45b 100644
--- a/indra/llplugin/llpluginclassmedia.h
+++ b/indra/llplugin/llpluginclassmedia.h
@@ -189,6 +189,7 @@ public:
void focus(bool focused);
void clear_cache();
void clear_cookies();
+ void set_cookies(const std::string &cookies);
void enable_cookies(bool enable);
void proxy_setup(bool enable, const std::string &host = LLStringUtil::null, int port = 0);
void browse_stop();
diff --git a/indra/llplugin/llpluginclassmediaowner.h b/indra/llplugin/llpluginclassmediaowner.h
index 6d369cd51a..5669b81fd1 100644
--- a/indra/llplugin/llpluginclassmediaowner.h
+++ b/indra/llplugin/llpluginclassmediaowner.h
@@ -39,6 +39,7 @@
#include <queue>
class LLPluginClassMedia;
+class LLPluginCookieStore;
class LLPluginClassMediaOwner
{
@@ -78,6 +79,7 @@ public:
virtual ~LLPluginClassMediaOwner() {};
virtual void handleMediaEvent(LLPluginClassMedia* /*self*/, EMediaEvent /*event*/) {};
+ virtual void handleCookieSet(LLPluginClassMedia* /*self*/, const std::string &/*cookie*/) {};
};
#endif // LL_LLPLUGINCLASSMEDIAOWNER_H
diff --git a/indra/llplugin/llplugincookiestore.cpp b/indra/llplugin/llplugincookiestore.cpp
new file mode 100644
index 0000000000..1964b8d789
--- /dev/null
+++ b/indra/llplugin/llplugincookiestore.cpp
@@ -0,0 +1,600 @@
+/**
+ * @file llplugincookiestore.cpp
+ * @brief LLPluginCookieStore provides central storage for http cookies used by plugins
+ *
+ * @cond
+ * $LicenseInfo:firstyear=2010&license=viewergpl$
+ *
+ * Copyright (c) 2010, Linden Research, Inc.
+ *
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab. Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlife.com/developers/opensource/gplv2
+ *
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at http://secondlife.com/developers/opensource/flossexception
+ *
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ *
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ * @endcond
+ */
+
+#include "linden_common.h"
+#include "indra_constants.h"
+
+#include "llplugincookiestore.h"
+#include <iostream>
+
+// for curl_getdate() (apparently parsing RFC 1123 dates is hard)
+#include <curl/curl.h>
+
+LLPluginCookieStore::LLPluginCookieStore():
+ mHasChangedCookies(false)
+{
+}
+
+
+LLPluginCookieStore::~LLPluginCookieStore()
+{
+ clearCookies();
+}
+
+
+LLPluginCookieStore::Cookie::Cookie(const std::string &s, std::string::size_type cookie_start, std::string::size_type cookie_end):
+ mCookie(s, cookie_start, cookie_end),
+ mNameStart(0), mNameEnd(0),
+ mValueStart(0), mValueEnd(0),
+ mDomainStart(0), mDomainEnd(0),
+ mPathStart(0), mPathEnd(0),
+ mDead(false), mChanged(true)
+{
+}
+
+LLPluginCookieStore::Cookie *LLPluginCookieStore::Cookie::createFromString(const std::string &s, std::string::size_type cookie_start, std::string::size_type cookie_end)
+{
+ Cookie *result = new Cookie(s, cookie_start, cookie_end);
+
+ if(!result->parse())
+ {
+ delete result;
+ result = NULL;
+ }
+
+ return result;
+}
+
+std::string LLPluginCookieStore::Cookie::getKey() const
+{
+ std::string result;
+ if(mDomainEnd > mDomainStart)
+ {
+ result += mCookie.substr(mDomainStart, mDomainEnd - mDomainStart);
+ }
+ result += ';';
+ if(mPathEnd > mPathStart)
+ {
+ result += mCookie.substr(mPathStart, mPathEnd - mPathStart);
+ }
+ result += ';';
+ result += mCookie.substr(mNameStart, mNameEnd - mNameStart);
+ return result;
+}
+
+bool LLPluginCookieStore::Cookie::parse()
+{
+ bool first_field = true;
+
+ std::string::size_type cookie_end = mCookie.size();
+ std::string::size_type field_start = 0;
+
+ lldebugs << "parsing cookie: " << mCookie << llendl;
+ while(field_start < cookie_end)
+ {
+ // Finding the start of the next field requires honoring special quoting rules
+ // see the definition of 'quoted-string' in rfc2616 for details
+ std::string::size_type next_field_start = findFieldEnd(field_start);
+
+ // The end of this field should not include the terminating ';' or any trailing whitespace
+ std::string::size_type field_end = mCookie.find_last_not_of("; ", next_field_start);
+ if(field_end == std::string::npos || field_end < field_start)
+ {
+ // This field was empty or all whitespace. Set end = start so it shows as empty.
+ field_end = field_start;
+ }
+ else if (field_end < next_field_start)
+ {
+ // we actually want the index of the char _after_ what 'last not of' found
+ ++field_end;
+ }
+
+ // find the start of the actual name (skip separator and possible whitespace)
+ std::string::size_type name_start = mCookie.find_first_not_of("; ", field_start);
+ if(name_start == std::string::npos || name_start > next_field_start)
+ {
+ // Again, nothing but whitespace.
+ name_start = field_start;
+ }
+
+ // the name and value are separated by the first equals sign
+ std::string::size_type name_value_sep = mCookie.find_first_of("=", name_start);
+ if(name_value_sep == std::string::npos || name_value_sep > field_end)
+ {
+ // No separator found, so this is a field without an =
+ name_value_sep = field_end;
+ }
+
+ // the name end is before the name-value separator
+ std::string::size_type name_end = mCookie.find_last_not_of("= ", name_value_sep);
+ if(name_end == std::string::npos || name_end < name_start)
+ {
+ // I'm not sure how we'd hit this case... it seems like it would have to be an empty name.
+ name_end = name_start;
+ }
+ else if (name_end < name_value_sep)
+ {
+ // we actually want the index of the char _after_ what 'last not of' found
+ ++name_end;
+ }
+
+ // Value is between the name-value sep and the end of the field.
+ std::string::size_type value_start = mCookie.find_first_not_of("= ", name_value_sep);
+ if(value_start == std::string::npos || value_start > field_end)
+ {
+ // All whitespace or empty value
+ value_start = field_end;
+ }
+ std::string::size_type value_end = mCookie.find_last_not_of("; ", field_end);
+ if(value_end == std::string::npos || value_end < value_start)
+ {
+ // All whitespace or empty value
+ value_end = value_start;
+ }
+ else if (value_end < field_end)
+ {
+ // we actually want the index of the char _after_ what 'last not of' found
+ ++value_end;
+ }
+
+ lldebugs
+ << " field name: \"" << mCookie.substr(name_start, name_end - name_start)
+ << "\", value: \"" << mCookie.substr(value_start, value_end - value_start) << "\""
+ << llendl;
+
+ // See whether this field is one we know
+ if(first_field)
+ {
+ // The first field is the name=value pair
+ mNameStart = name_start;
+ mNameEnd = name_end;
+ mValueStart = value_start;
+ mValueEnd = value_end;
+ first_field = false;
+ }
+ else
+ {
+ // Subsequent fields must come from the set in rfc2109
+ if(matchName(name_start, name_end, "expires"))
+ {
+ std::string date_string(mCookie, value_start, value_end - value_start);
+ // If the cookie contains an "expires" field, it MUST contain a parsable date.
+
+ // HACK: LLDate apparently can't PARSE an rfc1123-format date, even though it can GENERATE one.
+ // The curl function curl_getdate can do this, but I'm hesitant to unilaterally introduce a curl dependency in LLDate.
+#if 1
+ time_t date = curl_getdate(date_string.c_str(), NULL );
+ mDate.secondsSinceEpoch((F64)date);
+ lldebugs << " expire date parsed to: " << mDate.asRFC1123() << llendl;
+#else
+ // This doesn't work (rfc1123-format dates cause it to fail)
+ if(!mDate.fromString(date_string))
+ {
+ // Date failed to parse.
+ llwarns << "failed to parse cookie's expire date: " << date << llendl;
+ return false;
+ }
+#endif
+ }
+ else if(matchName(name_start, name_end, "domain"))
+ {
+ mDomainStart = value_start;
+ mDomainEnd = value_end;
+ }
+ else if(matchName(name_start, name_end, "path"))
+ {
+ mPathStart = value_start;
+ mPathEnd = value_end;
+ }
+ else if(matchName(name_start, name_end, "max-age"))
+ {
+ // TODO: how should we handle this?
+ }
+ else if(matchName(name_start, name_end, "secure"))
+ {
+ // We don't care about the value of this field (yet)
+ }
+ else if(matchName(name_start, name_end, "version"))
+ {
+ // We don't care about the value of this field (yet)
+ }
+ else if(matchName(name_start, name_end, "comment"))
+ {
+ // We don't care about the value of this field (yet)
+ }
+ else
+ {
+ // An unknown field is a parse failure
+ return false;
+ }
+
+ }
+
+
+ // move on to the next field, skipping this field's separator and any leading whitespace
+ field_start = mCookie.find_first_not_of("; ", next_field_start);
+ }
+
+ // The cookie MUST have a name
+ if(mNameEnd <= mNameStart)
+ return false;
+
+ return true;
+}
+
+std::string::size_type LLPluginCookieStore::Cookie::findFieldEnd(std::string::size_type start, std::string::size_type end)
+{
+ std::string::size_type result = start;
+
+ if(end == std::string::npos)
+ end = mCookie.size();
+
+ bool in_quotes = false;
+ for(; (result < end); result++)
+ {
+ switch(mCookie[result])
+ {
+ case '\\':
+ if(in_quotes)
+ result++; // The next character is backslash-quoted. Skip over it.
+ break;
+ case '"':
+ in_quotes = !in_quotes;
+ break;
+ case ';':
+ if(!in_quotes)
+ return result;
+ break;
+ }
+ }
+
+ // If we got here, no ';' was found.
+ return end;
+}
+
+bool LLPluginCookieStore::Cookie::matchName(std::string::size_type start, std::string::size_type end, const char *name)
+{
+ // NOTE: this assumes 'name' is already in lowercase. The code which uses it should be able to arrange this...
+
+ while((start < end) && (*name != '\0'))
+ {
+ if(tolower(mCookie[start]) != *name)
+ return false;
+
+ start++;
+ name++;
+ }
+
+ // iff both strings hit the end at the same time, they're equal.
+ return ((start == end) && (*name == '\0'));
+}
+
+std::string LLPluginCookieStore::getAllCookies()
+{
+ std::stringstream result;
+ writeAllCookies(result);
+ return result.str();
+}
+
+void LLPluginCookieStore::writeAllCookies(std::ostream& s)
+{
+ cookie_map_t::iterator iter;
+ for(iter = mCookies.begin(); iter != mCookies.end(); iter++)
+ {
+ // Don't return expired cookies
+ if(!iter->second->isDead())
+ {
+ s << (iter->second->getCookie()) << "\n";
+ }
+ }
+
+}
+
+std::string LLPluginCookieStore::getPersistentCookies()
+{
+ std::stringstream result;
+ writePersistentCookies(result);
+ return result.str();
+}
+
+void LLPluginCookieStore::writePersistentCookies(std::ostream& s)
+{
+ cookie_map_t::iterator iter;
+ for(iter = mCookies.begin(); iter != mCookies.end(); iter++)
+ {
+ // Don't return expired cookies or session cookies
+ if(!iter->second->isDead() && !iter->second->isSessionCookie())
+ {
+ s << iter->second->getCookie() << "\n";
+ }
+ }
+}
+
+std::string LLPluginCookieStore::getChangedCookies(bool clear_changed)
+{
+ std::stringstream result;
+ writeChangedCookies(result, clear_changed);
+
+ return result.str();
+}
+
+void LLPluginCookieStore::writeChangedCookies(std::ostream& s, bool clear_changed)
+{
+ if(mHasChangedCookies)
+ {
+ lldebugs << "returning changed cookies: " << llendl;
+ cookie_map_t::iterator iter;
+ for(iter = mCookies.begin(); iter != mCookies.end(); )
+ {
+ cookie_map_t::iterator next = iter;
+ next++;
+
+ // Only return cookies marked as "changed"
+ if(iter->second->isChanged())
+ {
+ s << iter->second->getCookie() << "\n";
+
+ lldebugs << " " << iter->second->getCookie() << llendl;
+
+ // If requested, clear the changed mark
+ if(clear_changed)
+ {
+ if(iter->second->isDead())
+ {
+ // If this cookie was previously marked dead, it needs to be removed entirely.
+ delete iter->second;
+ mCookies.erase(iter);
+ }
+ else
+ {
+ // Not dead, just mark as not changed.
+ iter->second->setChanged(false);
+ }
+ }
+ }
+
+ iter = next;
+ }
+ }
+
+ if(clear_changed)
+ mHasChangedCookies = false;
+}
+
+void LLPluginCookieStore::setAllCookies(const std::string &cookies, bool mark_changed)
+{
+ clearCookies();
+ setCookies(cookies, mark_changed);
+}
+
+void LLPluginCookieStore::readAllCookies(std::istream& s, bool mark_changed)
+{
+ clearCookies();
+ readCookies(s, mark_changed);
+}
+
+void LLPluginCookieStore::setCookies(const std::string &cookies, bool mark_changed)
+{
+ std::string::size_type start = 0;
+
+ while(start != std::string::npos)
+ {
+ std::string::size_type end = cookies.find('\n', start);
+ if(end > start)
+ {
+ // The line is non-empty. Try to create a cookie from it.
+ setOneCookie(cookies, start, end, mark_changed);
+ }
+ start = cookies.find_first_not_of("\n ", end);
+ }
+}
+
+void LLPluginCookieStore::readCookies(std::istream& s, bool mark_changed)
+{
+ std::string line;
+ while(s.good() && !s.eof())
+ {
+ std::getline(s, line);
+ if(!line.empty())
+ {
+ // Try to create a cookie from this line.
+ setOneCookie(line, 0, std::string::npos, mark_changed);
+ }
+ }
+}
+
+std::string LLPluginCookieStore::quoteString(const std::string &s)
+{
+ std::stringstream result;
+
+ result << '"';
+
+ for(std::string::size_type i = 0; i < s.size(); ++i)
+ {
+ char c = s[i];
+ switch(c)
+ {
+ // All these separators need to be quoted in HTTP headers, according to section 2.2 of rfc 2616:
+ case '(': case ')': case '<': case '>': case '@':
+ case ',': case ';': case ':': case '\\': case '"':
+ case '/': case '[': case ']': case '?': case '=':
+ case '{': case '}': case ' ': case '\t':
+ result << '\\';
+ break;
+ }
+
+ result << c;
+ }
+
+ result << '"';
+
+ return result.str();
+}
+
+std::string LLPluginCookieStore::unquoteString(const std::string &s)
+{
+ std::stringstream result;
+
+ bool in_quotes = false;
+
+ for(std::string::size_type i = 0; i < s.size(); ++i)
+ {
+ char c = s[i];
+ switch(c)
+ {
+ case '\\':
+ if(in_quotes)
+ {
+ // The next character is backslash-quoted. Pass it through untouched.
+ ++i;
+ if(i < s.size())
+ {
+ result << s[i];
+ }
+ continue;
+ }
+ break;
+ case '"':
+ in_quotes = !in_quotes;
+ continue;
+ break;
+ }
+
+ result << c;
+ }
+
+ return result.str();
+}
+
+// The flow for deleting a cookie is non-obvious enough that I should call it out here...
+// Deleting a cookie is done by setting a cookie with the same name, path, and domain, but with an expire timestamp in the past.
+// (This is exactly how a web server tells a browser to delete a cookie.)
+// 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.
+// 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
+// delete operation (in the form of the expired cookie) is passed along.
+void LLPluginCookieStore::setOneCookie(const std::string &s, std::string::size_type cookie_start, std::string::size_type cookie_end, bool mark_changed)
+{
+ Cookie *cookie = Cookie::createFromString(s, cookie_start, cookie_end);
+ if(cookie)
+ {
+ lldebugs << "setting cookie: " << cookie->getCookie() << llendl;
+
+ // Create a key for this cookie
+ std::string key = cookie->getKey();
+
+ // Check to see whether this cookie should have expired
+ if(!cookie->isSessionCookie() && (cookie->getDate() < LLDate::now()))
+ {
+ // This cookie has expired.
+ if(mark_changed)
+ {
+ // If we're marking cookies as changed, we should keep it anyway since we'll need to send it out with deltas.
+ cookie->setDead(true);
+ lldebugs << " marking dead" << llendl;
+ }
+ else
+ {
+ // If we're not marking cookies as changed, we don't need to keep this cookie at all.
+ // If the cookie was already in the list, delete it.
+ removeCookie(key);
+
+ delete cookie;
+ cookie = NULL;
+
+ lldebugs << " removing" << llendl;
+ }
+ }
+
+ if(cookie)
+ {
+ // If it already exists in the map, replace it.
+ cookie_map_t::iterator iter = mCookies.find(key);
+ if(iter != mCookies.end())
+ {
+ if(iter->second->getCookie() == cookie->getCookie())
+ {
+ // The new cookie is identical to the old -- don't mark as changed.
+ // Just leave the old one in the map.
+ delete cookie;
+ cookie = NULL;
+
+ lldebugs << " unchanged" << llendl;
+ }
+ else
+ {
+ // A matching cookie was already in the map. Replace it.
+ delete iter->second;
+ iter->second = cookie;
+
+ cookie->setChanged(mark_changed);
+ if(mark_changed)
+ mHasChangedCookies = true;
+
+ lldebugs << " replacing" << llendl;
+ }
+ }
+ else
+ {
+ // The cookie wasn't in the map. Insert it.
+ mCookies.insert(std::make_pair(key, cookie));
+
+ cookie->setChanged(mark_changed);
+ if(mark_changed)
+ mHasChangedCookies = true;
+
+ lldebugs << " adding" << llendl;
+ }
+ }
+ }
+}
+
+void LLPluginCookieStore::clearCookies()
+{
+ while(!mCookies.empty())
+ {
+ cookie_map_t::iterator iter = mCookies.begin();
+ delete iter->second;
+ mCookies.erase(iter);
+ }
+}
+
+void LLPluginCookieStore::removeCookie(const std::string &key)
+{
+ cookie_map_t::iterator iter = mCookies.find(key);
+ if(iter != mCookies.end())
+ {
+ delete iter->second;
+ mCookies.erase(iter);
+ }
+}
+
diff --git a/indra/llplugin/llplugincookiestore.h b/indra/llplugin/llplugincookiestore.h
new file mode 100644
index 0000000000..5250f008b6
--- /dev/null
+++ b/indra/llplugin/llplugincookiestore.h
@@ -0,0 +1,122 @@
+/**
+ * @file llplugincookiestore.h
+ * @brief LLPluginCookieStore provides central storage for http cookies used by plugins
+ *
+ * @cond
+ * $LicenseInfo:firstyear=2010&license=viewergpl$
+ *
+ * Copyright (c) 2010, Linden Research, Inc.
+ *
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab. Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlife.com/developers/opensource/gplv2
+ *
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at http://secondlife.com/developers/opensource/flossexception
+ *
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ *
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ * @endcond
+ */
+
+#ifndef LL_LLPLUGINCOOKIESTORE_H
+#define LL_LLPLUGINCOOKIESTORE_H
+
+#include "lldate.h"
+#include <map>
+#include <string>
+#include <iostream>
+
+class LLPluginCookieStore
+{
+ LOG_CLASS(LLPluginCookieStore);
+public:
+ LLPluginCookieStore();
+ ~LLPluginCookieStore();
+
+ // gets all cookies currently in storage -- use when initializing a plugin
+ std::string getAllCookies();
+ void writeAllCookies(std::ostream& s);
+
+ // gets only persistent cookies (i.e. not session cookies) -- use when writing cookies to a file
+ std::string getPersistentCookies();
+ void writePersistentCookies(std::ostream& s);
+
+ // gets cookies which are marked as "changed" -- use when sending periodic updates to plugins
+ std::string getChangedCookies(bool clear_changed = true);
+ void writeChangedCookies(std::ostream& s, bool clear_changed = true);
+
+ // (re)initializes internal data structures and bulk-sets cookies -- use when reading cookies from a file
+ void setAllCookies(const std::string &cookies, bool mark_changed = false);
+ void readAllCookies(std::istream& s, bool mark_changed = false);
+
+ // sets one or more cookies (without reinitializing anything) -- use when receiving cookies from a plugin
+ void setCookies(const std::string &cookies, bool mark_changed = true);
+ void readCookies(std::istream& s, bool mark_changed = true);
+
+ // quote or unquote a string as per the definition of 'quoted-string' in rfc2616
+ static std::string quoteString(const std::string &s);
+ static std::string unquoteString(const std::string &s);
+
+private:
+
+ void setOneCookie(const std::string &s, std::string::size_type cookie_start, std::string::size_type cookie_end, bool mark_changed);
+
+ class Cookie
+ {
+ public:
+ static Cookie *createFromString(const std::string &s, std::string::size_type cookie_start = 0, std::string::size_type cookie_end = std::string::npos);
+
+ // Construct a string from the cookie that uniquely represents it, to be used as a key in a std::map.
+ std::string getKey() const;
+
+ const std::string &getCookie() const { return mCookie; };
+ bool isSessionCookie() const { return mDate.isNull(); };
+
+ bool isDead() const { return mDead; };
+ void setDead(bool dead) { mDead = dead; };
+
+ bool isChanged() const { return mChanged; };
+ void setChanged(bool changed) { mChanged = changed; };
+
+ const LLDate &getDate() const { return mDate; };
+
+ private:
+ Cookie(const std::string &s, std::string::size_type cookie_start = 0, std::string::size_type cookie_end = std::string::npos);
+ bool parse();
+ std::string::size_type findFieldEnd(std::string::size_type start = 0, std::string::size_type end = std::string::npos);
+ bool matchName(std::string::size_type start, std::string::size_type end, const char *name);
+
+ std::string mCookie; // The full cookie, in RFC 2109 string format
+ LLDate mDate; // The expiration date of the cookie. For session cookies, this will be a null date (mDate.isNull() is true).
+ // Start/end indices of various parts of the cookie string. Stored as indices into the string to save space and time.
+ std::string::size_type mNameStart, mNameEnd;
+ std::string::size_type mValueStart, mValueEnd;
+ std::string::size_type mDomainStart, mDomainEnd;
+ std::string::size_type mPathStart, mPathEnd;
+ bool mDead;
+ bool mChanged;
+ };
+
+ typedef std::map<std::string, Cookie*> cookie_map_t;
+
+ cookie_map_t mCookies;
+ bool mHasChangedCookies;
+
+ void clearCookies();
+ void removeCookie(const std::string &key);
+};
+
+#endif // LL_LLPLUGINCOOKIESTORE_H
diff --git a/indra/media_plugins/webkit/media_plugin_webkit.cpp b/indra/media_plugins/webkit/media_plugin_webkit.cpp
index 0462fce236..85d6b2f5ff 100644
--- a/indra/media_plugins/webkit/media_plugin_webkit.cpp
+++ b/indra/media_plugins/webkit/media_plugin_webkit.cpp
@@ -507,6 +507,19 @@ private:
sendMessage(message);
}
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // virtual
+ void onCookieChanged(const EventType& event)
+ {
+ LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "cookie_set");
+ message.setValue("cookie", event.getStringValue());
+ // These could be passed through as well, but aren't really needed.
+// message.setValue("uri", event.getEventUri());
+// message.setValueBoolean("dead", (event.getIntValue() != 0))
+ sendMessage(message);
+ }
+
LLQtWebKit::EKeyboardModifier decodeModifiers(std::string &modifiers)
{
int result = 0;
@@ -1051,6 +1064,10 @@ void MediaPluginWebKit::receiveMessage(const char *message_string)
mJavascriptEnabled = message_in.getValueBoolean("enable");
//LLQtWebKit::getInstance()->enableJavascript( mJavascriptEnabled );
}
+ else if(message_name == "set_cookies")
+ {
+ LLQtWebKit::getInstance()->setCookies(message_in.getValue("cookies"));
+ }
else if(message_name == "proxy_setup")
{
bool val = message_in.getValueBoolean("enable");
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index d30d7fd26d..a3d0b8d8d9 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -1526,6 +1526,8 @@ bool LLAppViewer::cleanup()
LLLocationHistory::getInstance()->save();
LLAvatarIconIDCache::getInstance()->save();
+
+ LLViewerMedia::saveCookieFile();
llinfos << "Shutting down Threads" << llendflush;
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index edd03dc836..59d118abe2 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -938,6 +938,9 @@ bool idle_startup()
// Load Avatars icons cache
LLAvatarIconIDCache::getInstance()->load();
+
+ // Load media plugin cookies
+ LLViewerMedia::loadCookieFile();
//-------------------------------------------------
// Handle startup progress screen
diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp
index 2fcd3f1114..8863bf187d 100644
--- a/indra/newview/llviewermedia.cpp
+++ b/indra/newview/llviewermedia.cpp
@@ -45,6 +45,7 @@
#include "llviewertexturelist.h"
#include "llvovolume.h"
#include "llpluginclassmedia.h"
+#include "llplugincookiestore.h"
#include "llviewerwindow.h"
#include "llfocusmgr.h"
#include "llcallbacklist.h"
@@ -256,6 +257,7 @@ public:
LLViewerMediaImpl *mMediaImpl;
bool mInitialized;
};
+LLPluginCookieStore *LLViewerMedia::sCookieStore = NULL;
static LLViewerMedia::impl_list sViewerMediaImplList;
static LLViewerMedia::impl_id_map sViewerMediaTextureIDMap;
static LLTimer sMediaCreateTimer;
@@ -264,6 +266,8 @@ static F32 sGlobalVolume = 1.0f;
static F64 sLowestLoadableImplInterest = 0.0f;
static bool sAnyMediaShowing = false;
static boost::signals2::connection sTeleportFinishConnection;
+static std::string sUpdatedCookies;
+static const char *PLUGIN_COOKIE_FILE_NAME = "plugin_cookies.txt";
//////////////////////////////////////////////////////////////////////////////////////////
static void add_media_impl(LLViewerMediaImpl* media)
@@ -399,7 +403,6 @@ viewer_media_t LLViewerMedia::updateMediaImpl(LLMediaEntry* media_entry, const s
media_impl->setHomeURL(media_entry->getHomeURL());
media_impl->mMediaAutoPlay = media_entry->getAutoPlay();
media_impl->mMediaEntryURL = media_entry->getCurrentURL();
-
if(media_impl->isAutoPlayable())
{
needs_navigate = true;
@@ -698,6 +701,13 @@ static bool proximity_comparitor(const LLViewerMediaImpl* i1, const LLViewerMedi
void LLViewerMedia::updateMedia(void *dummy_arg)
{
sAnyMediaShowing = false;
+ sUpdatedCookies = getCookieStore()->getChangedCookies();
+ if(!sUpdatedCookies.empty())
+ {
+ lldebugs << "updated cookies will be sent to all loaded plugins: " << llendl;
+ lldebugs << sUpdatedCookies << llendl;
+ }
+
impl_list::iterator iter = sViewerMediaImplList.begin();
impl_list::iterator end = sViewerMediaImplList.end();
@@ -1095,6 +1105,116 @@ void LLViewerMedia::setProxyConfig(bool enable, const std::string &host, int por
/////////////////////////////////////////////////////////////////////////////////////////
// static
+/////////////////////////////////////////////////////////////////////////////////////////
+// static
+LLPluginCookieStore *LLViewerMedia::getCookieStore()
+{
+ if(sCookieStore == NULL)
+ {
+ sCookieStore = new LLPluginCookieStore;
+ }
+
+ return sCookieStore;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// static
+void LLViewerMedia::loadCookieFile()
+{
+ // build filename for each user
+ std::string resolved_filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, PLUGIN_COOKIE_FILE_NAME);
+
+ if (resolved_filename.empty())
+ {
+ llinfos << "can't get path to plugin cookie file - probably not logged in yet." << llendl;
+ return;
+ }
+
+ // open the file for reading
+ llifstream file(resolved_filename);
+ if (!file.is_open())
+ {
+ llwarns << "can't load plugin cookies from file \"" << PLUGIN_COOKIE_FILE_NAME << "\"" << llendl;
+ return;
+ }
+
+ getCookieStore()->readAllCookies(file, true);
+
+ file.close();
+
+ // TODO: send the clear_cookies message to all loaded plugins
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// static
+void LLViewerMedia::saveCookieFile()
+{
+ // build filename for each user
+ std::string resolved_filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, PLUGIN_COOKIE_FILE_NAME);
+
+ if (resolved_filename.empty())
+ {
+ llinfos << "can't get path to plugin cookie file - probably not logged in yet." << llendl;
+ return;
+ }
+
+ // open a file for writing
+ llofstream file (resolved_filename);
+ if (!file.is_open())
+ {
+ llwarns << "can't open plugin cookie file \"" << PLUGIN_COOKIE_FILE_NAME << "\" for writing" << llendl;
+ return;
+ }
+
+ getCookieStore()->writePersistentCookies(file);
+
+ file.close();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// static
+void LLViewerMedia::addCookie(const std::string &name, const std::string &value, const std::string &domain, const LLDate &expires, const std::string &path, bool secure)
+{
+ std::stringstream cookie;
+
+ cookie << name << "=" << LLPluginCookieStore::quoteString(value);
+
+ if(expires.notNull())
+ {
+ cookie << "; expires=" << expires.asRFC1123();
+ }
+
+ cookie << "; domain=" << domain;
+
+ cookie << "; path=" << path;
+
+ if(secure)
+ {
+ cookie << "; secure";
+ }
+
+ getCookieStore()->setCookies(cookie.str());
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// static
+void LLViewerMedia::addSessionCookie(const std::string &name, const std::string &value, const std::string &domain, const std::string &path, bool secure)
+{
+ // A session cookie just has a NULL date.
+ addCookie(name, value, domain, LLDate(), path, secure);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// static
+void LLViewerMedia::removeCookie(const std::string &name, const std::string &domain, const std::string &path )
+{
+ // To remove a cookie, add one with the same name, domain, and path that expires in the past.
+
+ addCookie(name, "", domain, LLDate(LLDate::now().secondsSinceEpoch() - 1.0), path);
+}
+
+
bool LLViewerMedia::hasInWorldMedia()
{
if (sInWorldMediaDisabled) return false;
@@ -1455,6 +1575,17 @@ bool LLViewerMediaImpl::initializePlugin(const std::string& media_type)
media_source->clear_cache();
}
+ // TODO: Only send cookies to plugins that need them
+ // Ideally, the plugin should tell us whether it handles cookies or not -- either via the init response or through a separate message.
+ // Due to the ordering of messages, it's possible we wouldn't get that information back in time to send cookies before sending a navigate message,
+ // which could cause odd race conditions.
+ std::string all_cookies = LLViewerMedia::getCookieStore()->getAllCookies();
+ lldebugs << "setting cookies: " << all_cookies << llendl;
+ if(!all_cookies.empty())
+ {
+ media_source->set_cookies(all_cookies);
+ }
+
mMediaSource = media_source;
updateVolume();
@@ -2152,6 +2283,16 @@ void LLViewerMediaImpl::update()
}
}
}
+ else
+ {
+ // If we didn't just create the impl, it may need to get cookie updates.
+ if(!sUpdatedCookies.empty())
+ {
+ // TODO: Only send cookies to plugins that need them
+ mMediaSource->set_cookies(sUpdatedCookies);
+ }
+ }
+
if(mMediaSource == NULL)
{
@@ -2614,6 +2755,13 @@ void LLViewerMediaImpl::handleMediaEvent(LLPluginClassMedia* plugin, LLPluginCla
////////////////////////////////////////////////////////////////////////////////
// virtual
+void LLViewerMediaImpl::handleCookieSet(LLPluginClassMedia* self, const std::string &cookie)
+{
+ LLViewerMedia::getCookieStore()->setCookies(cookie);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// virtual
void
LLViewerMediaImpl::cut()
{
diff --git a/indra/newview/llviewermedia.h b/indra/newview/llviewermedia.h
index f9870fb3b9..10dacf9532 100644
--- a/indra/newview/llviewermedia.h
+++ b/indra/newview/llviewermedia.h
@@ -50,6 +50,7 @@ class LLViewerMediaTexture;
class LLMediaEntry;
class LLVOVolume;
class LLMimeDiscoveryResponder;
+class LLPluginCookieStore;
typedef LLPointer<LLViewerMediaImpl> viewer_media_t;
///////////////////////////////////////////////////////////////////////////////
@@ -145,8 +146,17 @@ public:
// Set the proxy config for all loaded plugins
static void setProxyConfig(bool enable, const std::string &host, int port);
+ static LLPluginCookieStore *getCookieStore();
+ static void loadCookieFile();
+ static void saveCookieFile();
+ static void addCookie(const std::string &name, const std::string &value, const std::string &domain, const LLDate &expires, const std::string &path = std::string("/"), bool secure = false );
+ static void addSessionCookie(const std::string &name, const std::string &value, const std::string &domain, const std::string &path = std::string("/"), bool secure = false );
+ static void removeCookie(const std::string &name, const std::string &domain, const std::string &path = std::string("/") );
+
private:
static void onTeleportFinished();
+
+ static LLPluginCookieStore *sCookieStore;
};
// Implementation functions not exported into header file
@@ -294,6 +304,7 @@ public:
// Inherited from LLPluginClassMediaOwner
/*virtual*/ void handleMediaEvent(LLPluginClassMedia* plugin, LLPluginClassMediaOwner::EMediaEvent);
+ /*virtual*/ void handleCookieSet(LLPluginClassMedia* self, const std::string &cookie);
// LLEditMenuHandler overrides
/*virtual*/ void cut();
diff --git a/install.xml b/install.xml
index 0f4ab373f5..25c9fdd045 100644
--- a/install.xml
+++ b/install.xml
@@ -948,9 +948,9 @@ anguage Infrstructure (CLI) international standard</string>
<key>darwin</key>
<map>
<key>md5sum</key>
- <string>ada82d41467cdb7f8e25a442f58b6963</string>
+ <string>171bd85ebb81d319e1f15fab8092f8cd</string>
<key>url</key>
- <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/llqtwebkit-4.6-darwin-20100318.tar.bz2</uri>
+ <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/llqtwebkit-4.6-darwin-20100326.tar.bz2</uri>
</map>
<key>linux</key>
<map>
@@ -962,9 +962,9 @@ anguage Infrstructure (CLI) international standard</string>
<key>windows</key>
<map>
<key>md5sum</key>
- <string>93b49cfea365fe082eda3e466a9beec3</string>
+ <string>04d86bb2eeed4f928d155cb5598ca6b5</string>
<key>url</key>
- <uri>http://viewer-source-downloads.s3.amazonaws.com/install_pkgs/llqtwebkit-windows-qt4.6-20100318.tar.bz2</uri>
+ <uri>http://viewer-source-downloads.s3.amazonaws.com/install_pkgs/llqtwebkit-windows-qt4.6-20100326.tar.bz2</uri>
</map>
</map>
</map>