Add AIHTTPReceivedHeaders
This fixes the problem that existed with received headers:
The server sends some headers ("set-cookie") more than once
in the same reply, which cannot be stored in std::map.
The old code just ignored the additional cookies, while
curlthreading3 (since the introduction of AIHTTPHeaders)
caused an assertion.
AIHTTPReceivedHeaders is written around a std::multimap
and allows to retrieve multiple headers with the same key.
Also, it is case insensitive so that if a server sends
"Content-Type" it will still find it (the viewer looks for
"content-type").
This commit is contained in:
@@ -458,7 +458,7 @@ AIHTTPTimeoutPolicy const& Responder::getHTTPTimeoutPolicy(void) const
|
||||
|
||||
// Called with HTML header.
|
||||
// virtual
|
||||
void Responder::completedHeaders(U32, std::string const&, AIHTTPHeaders const&)
|
||||
void Responder::completedHeaders(U32, std::string const&, AIHTTPReceivedHeaders const&)
|
||||
{
|
||||
// This should not be called unless a derived class implemented it.
|
||||
llerrs << "Unexpected call to completedHeaders()." << llendl;
|
||||
|
||||
@@ -212,7 +212,7 @@ class Responder : public AICurlResponderBufferEvents {
|
||||
std::string mURL;
|
||||
|
||||
// Headers received from the server.
|
||||
AIHTTPHeaders mReceivedHeaders;
|
||||
AIHTTPReceivedHeaders mReceivedHeaders;
|
||||
|
||||
public:
|
||||
// Called to set the URL of the current request for this Responder,
|
||||
@@ -245,7 +245,7 @@ class Responder : public AICurlResponderBufferEvents {
|
||||
|
||||
// Derived classes can override this to get the HTML headers that were received, when the message is completed.
|
||||
// The default does nothing.
|
||||
virtual void completedHeaders(U32 status, std::string const& reason, AIHTTPHeaders const& headers);
|
||||
virtual void completedHeaders(U32 status, std::string const& reason, AIHTTPReceivedHeaders const& headers);
|
||||
|
||||
public:
|
||||
// Derived classes that implement completedHeaders() should return true here.
|
||||
|
||||
@@ -35,10 +35,6 @@
|
||||
#include "debug_libcurl.h"
|
||||
#endif
|
||||
|
||||
AIHTTPHeaders::AIHTTPHeaders(void)
|
||||
{
|
||||
}
|
||||
|
||||
AIHTTPHeaders::AIHTTPHeaders(std::string const& key, std::string const& value) : mContainer(new Container)
|
||||
{
|
||||
addHeader(key, value);
|
||||
@@ -105,3 +101,53 @@ std::ostream& operator<<(std::ostream& os, AIHTTPHeaders const& headers)
|
||||
return os;
|
||||
}
|
||||
|
||||
void AIHTTPReceivedHeaders::addHeader(std::string const& key, std::string const& value)
|
||||
{
|
||||
if (!mContainer)
|
||||
{
|
||||
mContainer = new Container;
|
||||
}
|
||||
mContainer->mKeyValuePairs.insert(container_t::value_type(key, value));
|
||||
}
|
||||
|
||||
bool AIHTTPReceivedHeaders::hasHeader(std::string const& key) const
|
||||
{
|
||||
return !mContainer ? false : (mContainer->mKeyValuePairs.find(key) != mContainer->mKeyValuePairs.end());
|
||||
}
|
||||
|
||||
bool AIHTTPReceivedHeaders::getFirstValue(std::string const& key, std::string& value_out) const
|
||||
{
|
||||
AIHTTPReceivedHeaders::container_t::const_iterator iter;
|
||||
if (!mContainer || (iter = mContainer->mKeyValuePairs.find(key)) == mContainer->mKeyValuePairs.end())
|
||||
return false;
|
||||
value_out = iter->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AIHTTPReceivedHeaders::getValues(std::string const& key, range_type& value_out) const
|
||||
{
|
||||
if (!mContainer)
|
||||
return false;
|
||||
value_out = mContainer->mKeyValuePairs.equal_range(key);
|
||||
return value_out.first != value_out.second;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, AIHTTPReceivedHeaders const& headers)
|
||||
{
|
||||
os << '{';
|
||||
if (headers.mContainer)
|
||||
{
|
||||
bool first = true;
|
||||
AIHTTPReceivedHeaders::container_t::const_iterator const end = headers.mContainer->mKeyValuePairs.end();
|
||||
for (AIHTTPReceivedHeaders::container_t::const_iterator iter = headers.mContainer->mKeyValuePairs.begin(); iter != end; ++iter)
|
||||
{
|
||||
if (!first)
|
||||
os << ", ";
|
||||
os << '"' << iter->first << ": " << iter->second << '"';
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
os << '}';
|
||||
return os;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
*
|
||||
* 15/08/2012
|
||||
* Initial version, written by Aleric Inglewood @ SL
|
||||
* 21/10/2012
|
||||
* Added AIHTTPReceivedHeaders
|
||||
*/
|
||||
|
||||
#ifndef AIHTTPHEADERS_H
|
||||
@@ -34,6 +36,7 @@
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <iosfwd>
|
||||
#include <algorithm>
|
||||
#include "llpointer.h"
|
||||
#include "llthread.h" // LLThreadSafeRefCount
|
||||
|
||||
@@ -49,7 +52,7 @@ class AIHTTPHeaders {
|
||||
};
|
||||
|
||||
// Construct an empty container.
|
||||
AIHTTPHeaders(void);
|
||||
AIHTTPHeaders(void) { }
|
||||
|
||||
// Construct a container with a single header.
|
||||
AIHTTPHeaders(std::string const& key, std::string const& value);
|
||||
@@ -86,4 +89,70 @@ class AIHTTPHeaders {
|
||||
LLPointer<Container> mContainer;
|
||||
};
|
||||
|
||||
// Functor that returns true if c1 is less than c2, ignoring bit 5.
|
||||
// The effect is that characters in the range a-z equivalent ordering with A-Z.
|
||||
// This function assumes UTF-8 or ASCII encoding!
|
||||
//
|
||||
// Note that other characters aren't important in the case of HTTP header keys;
|
||||
// however if one considers all printable ASCII characters, then this functor
|
||||
// also compares "@[\]^" equal to "`{|}~" (any other is either not printable or
|
||||
// would be equal to a not printable character).
|
||||
struct AIHTTPReceivedHeadersCharCompare {
|
||||
bool operator()(std::string::value_type c1, std::string::value_type c2) const
|
||||
{
|
||||
static std::string::value_type const bit5 = 0x20;
|
||||
return (c1 | bit5) < (c2 | bit5);
|
||||
}
|
||||
};
|
||||
|
||||
// Functor to lexiographically compare two HTTP header keys using the above predicate.
|
||||
// This means that for example "Content-Type" and "content-type" will have equivalent ordering.
|
||||
struct AIHTTPReceivedHeadersCompare {
|
||||
bool operator()(std::string const& h1, std::string const& h2) const
|
||||
{
|
||||
static AIHTTPReceivedHeadersCharCompare const predicate;
|
||||
return std::lexicographical_compare(h1.begin(), h1.end(), h2.begin(), h2.end(), predicate);
|
||||
}
|
||||
};
|
||||
|
||||
class AIHTTPReceivedHeaders {
|
||||
private:
|
||||
typedef std::multimap<std::string, std::string, AIHTTPReceivedHeadersCompare> container_t;
|
||||
|
||||
public:
|
||||
typedef container_t::const_iterator iterator_type;
|
||||
typedef std::pair<iterator_type, iterator_type> range_type;
|
||||
|
||||
// Construct an empty container.
|
||||
AIHTTPReceivedHeaders(void) { }
|
||||
|
||||
// Clear all headers.
|
||||
void clear(void) { if (mContainer) mContainer->mKeyValuePairs.clear(); }
|
||||
|
||||
// Add a header.
|
||||
void addHeader(std::string const& key, std::string const& value);
|
||||
|
||||
// Return true if there are no headers associated with this object.
|
||||
bool empty(void) const { return !mContainer || mContainer->mKeyValuePairs.empty(); }
|
||||
|
||||
// Return true if the header exists.
|
||||
bool hasHeader(std::string const& key) const;
|
||||
|
||||
// Return true if key exists and fill value_out with the value. Return false otherwise.
|
||||
bool getFirstValue(std::string const& key, std::string& value_out) const;
|
||||
|
||||
// Return true if key exists and fill value_out with all values. Return false otherwise.
|
||||
bool getValues(std::string const& key, range_type& value_out) const;
|
||||
|
||||
// For debug purposes.
|
||||
friend std::ostream& operator<<(std::ostream& os, AIHTTPReceivedHeaders const& headers);
|
||||
|
||||
private:
|
||||
struct Container : public LLThreadSafeRefCount {
|
||||
container_t mKeyValuePairs;
|
||||
};
|
||||
|
||||
LLPointer<Container> mContainer;
|
||||
};
|
||||
|
||||
#endif // AIHTTPHEADERS_H
|
||||
|
||||
@@ -129,7 +129,7 @@ namespace LLAvatarNameCache
|
||||
// Erase expired names from cache
|
||||
void eraseUnrefreshed();
|
||||
|
||||
bool expirationFromCacheControl(AIHTTPHeaders const& headers, F64* expires);
|
||||
bool expirationFromCacheControl(AIHTTPReceivedHeaders const& headers, F64* expires);
|
||||
}
|
||||
|
||||
/* Sample response:
|
||||
@@ -182,7 +182,7 @@ private:
|
||||
std::vector<LLUUID> mAgentIDs;
|
||||
|
||||
// Need the headers to look up Expires: and Retry-After:
|
||||
AIHTTPHeaders mHeaders;
|
||||
AIHTTPReceivedHeaders mHeaders;
|
||||
|
||||
public:
|
||||
virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return avatarNameResponder_timeout; }
|
||||
@@ -191,7 +191,7 @@ public:
|
||||
: mAgentIDs(agent_ids)
|
||||
{ }
|
||||
|
||||
/*virtual*/ void completedHeaders(U32 status, std::string const& reason, AIHTTPHeaders const& headers)
|
||||
/*virtual*/ void completedHeaders(U32 status, std::string const& reason, AIHTTPReceivedHeaders const& headers)
|
||||
{
|
||||
mHeaders = headers;
|
||||
}
|
||||
@@ -791,42 +791,33 @@ void LLAvatarNameCache::insert(const LLUUID& agent_id, const LLAvatarName& av_na
|
||||
sCache[agent_id] = av_name;
|
||||
}
|
||||
|
||||
F64 LLAvatarNameCache::nameExpirationFromHeaders(AIHTTPHeaders const& headers)
|
||||
F64 LLAvatarNameCache::nameExpirationFromHeaders(AIHTTPReceivedHeaders const& headers)
|
||||
{
|
||||
F64 expires = 0.0;
|
||||
if (expirationFromCacheControl(headers, &expires))
|
||||
{
|
||||
return expires;
|
||||
}
|
||||
else
|
||||
{
|
||||
// With no expiration info, default to an hour
|
||||
const F64 DEFAULT_EXPIRES = 60.0 * 60.0;
|
||||
F64 now = LLFrameTimer::getTotalSeconds();
|
||||
return now + DEFAULT_EXPIRES;
|
||||
}
|
||||
F64 expires;
|
||||
expirationFromCacheControl(headers, &expires);
|
||||
return expires;
|
||||
}
|
||||
|
||||
bool LLAvatarNameCache::expirationFromCacheControl(AIHTTPHeaders const& headers, F64* expires)
|
||||
bool LLAvatarNameCache::expirationFromCacheControl(AIHTTPReceivedHeaders const& headers, F64* expires)
|
||||
{
|
||||
bool fromCacheControl = false;
|
||||
S32 max_age = 3600; // With no expiration info, default to an hour.
|
||||
F64 now = LLFrameTimer::getTotalSeconds();
|
||||
// Allow the header to override the default
|
||||
std::string cache_control;
|
||||
if (headers.getValue("cache-control", cache_control))
|
||||
if (headers.getFirstValue("cache-control", cache_control))
|
||||
{
|
||||
S32 max_age = 0;
|
||||
if (max_age_from_cache_control(cache_control, &max_age))
|
||||
{
|
||||
*expires = now + (F64)max_age;
|
||||
fromCacheControl = true;
|
||||
}
|
||||
}
|
||||
*expires = now + (F64)max_age;
|
||||
LL_DEBUGS("AvNameCache")
|
||||
<< ( fromCacheControl ? "expires based on cache control " : "default expiration " )
|
||||
<< "in " << *expires - now << " seconds"
|
||||
<< LL_ENDL;
|
||||
|
||||
|
||||
return fromCacheControl;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
#include <boost/signals2.hpp>
|
||||
|
||||
class LLUUID;
|
||||
class AIHTTPHeaders;
|
||||
class AIHTTPReceivedHeaders;
|
||||
|
||||
namespace LLAvatarNameCache
|
||||
{
|
||||
@@ -98,7 +98,7 @@ namespace LLAvatarNameCache
|
||||
|
||||
// Compute name expiration time from HTTP Cache-Control header,
|
||||
// or return default value, in seconds from epoch.
|
||||
F64 nameExpirationFromHeaders(AIHTTPHeaders const& headers);
|
||||
F64 nameExpirationFromHeaders(AIHTTPReceivedHeaders const& headers);
|
||||
|
||||
void addUseDisplayNamesCallback(const use_display_name_signal_t::slot_type& cb);
|
||||
}
|
||||
|
||||
@@ -64,10 +64,10 @@ public:
|
||||
|
||||
virtual bool needsHeaders(void) const { return true; }
|
||||
|
||||
virtual void completedHeaders(U32 status, std::string const& reason, AIHTTPHeaders const& headers)
|
||||
virtual void completedHeaders(U32 status, std::string const& reason, AIHTTPReceivedHeaders const& headers)
|
||||
{
|
||||
std::string media_type;
|
||||
bool content_type_found = headers.getValue("content-type", media_type);
|
||||
bool content_type_found = headers.getFirstValue("content-type", media_type);
|
||||
llassert_always(content_type_found);
|
||||
std::string::size_type idx1 = media_type.find_first_of(";");
|
||||
std::string mime_type = media_type.substr(0, idx1);
|
||||
|
||||
@@ -184,7 +184,7 @@ class LLDisplayNameUpdate : public LLHTTPNode
|
||||
// *TODO: get actual headers out of ResponsePtr
|
||||
//LLSD headers = response->mHeaders;
|
||||
av_name.mExpires =
|
||||
LLAvatarNameCache::nameExpirationFromHeaders(AIHTTPHeaders());
|
||||
LLAvatarNameCache::nameExpirationFromHeaders(AIHTTPReceivedHeaders());
|
||||
|
||||
LLAvatarNameCache::insert(agent_id, av_name);
|
||||
|
||||
|
||||
@@ -82,10 +82,10 @@ public:
|
||||
|
||||
virtual bool needsHeaders(void) const { return true; }
|
||||
|
||||
virtual void completedHeaders(U32 status, std::string const& reason, AIHTTPHeaders const& headers)
|
||||
virtual void completedHeaders(U32 status, std::string const& reason, AIHTTPReceivedHeaders const& headers)
|
||||
{
|
||||
std::string media_type;
|
||||
bool content_type_found = headers.getValue("content-type", media_type);
|
||||
bool content_type_found = headers.getFirstValue("content-type", media_type);
|
||||
llassert_always(content_type_found);
|
||||
std::string::size_type idx1 = media_type.find_first_of(";");
|
||||
std::string mime_type = media_type.substr(0, idx1);
|
||||
@@ -125,14 +125,15 @@ public:
|
||||
|
||||
/* virtual */ bool needsHeaders(void) const { return true; }
|
||||
|
||||
/* virtual */ void completedHeaders(U32 status, std::string const& reason, AIHTTPHeaders const& headers)
|
||||
/* virtual */ void completedHeaders(U32 status, std::string const& reason, AIHTTPReceivedHeaders const& headers)
|
||||
{
|
||||
LL_DEBUGS("MediaAuth") << "status = " << status << ", reason = " << reason << LL_ENDL;
|
||||
LL_DEBUGS("MediaAuth") << headers << LL_ENDL;
|
||||
std::string cookie;
|
||||
if (headers.getValue("set-cookie", cookie))
|
||||
AIHTTPReceivedHeaders::range_type cookies;
|
||||
if (headers.getValues("set-cookie", cookies))
|
||||
{
|
||||
LLViewerMedia::openIDCookieResponse(cookie);
|
||||
for (AIHTTPReceivedHeaders::iterator_type cookie = cookies.first; cookie != cookies.second; ++cookie)
|
||||
LLViewerMedia::openIDCookieResponse(cookie->second);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,15 +165,16 @@ public:
|
||||
|
||||
/* virtual */ bool needsHeaders(void) const { return true; }
|
||||
|
||||
/* virtual */ void completedHeaders(U32 status, std::string const& reason, AIHTTPHeaders const& headers)
|
||||
/* virtual */ void completedHeaders(U32 status, std::string const& reason, AIHTTPReceivedHeaders const& headers)
|
||||
{
|
||||
LL_INFOS("MediaAuth") << "status = " << status << ", reason = " << reason << LL_ENDL;
|
||||
LL_INFOS("MediaAuth") << headers << LL_ENDL;
|
||||
|
||||
std::string cookie;
|
||||
if (headers.getValue("set-cookie", cookie))
|
||||
AIHTTPReceivedHeaders::range_type cookies;
|
||||
if (headers.getValues("set-cookie", cookies))
|
||||
{
|
||||
LLViewerMedia::getCookieStore()->setCookiesFromHost(cookie, mHost);
|
||||
for (AIHTTPReceivedHeaders::iterator_type cookie = cookies.first; cookie != cookies.second; ++cookie)
|
||||
LLViewerMedia::getCookieStore()->setCookiesFromHost(cookie->second, mHost);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user