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:
Aleric Inglewood
2012-10-21 17:45:46 +02:00
parent f7626699da
commit 937a60c8f9
9 changed files with 152 additions and 44 deletions

View File

@@ -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;

View File

@@ -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.

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);
}
}