* Copyright 2010-2014 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Christophe Huriaux, c.huriaux@gmail.com
* Hamish Morrison, hamishm53@gmail.com
*/
#include <new>
#include <stdio.h>
#include <HashMap.h>
#include <HashString.h>
#include <Message.h>
#include <NetworkCookieJar.h>
#include "NetworkCookieJarPrivate.h"
using namespace BPrivate::Network;
#ifdef TRACE_COOKIE
# define TRACE(x...) printf(x)
#else
# define TRACE(x...) ;
#endif
const char* kArchivedCookieMessageName = "be:cookie";
BNetworkCookieJar::BNetworkCookieJar()
:
fCookieHashMap(new(std::nothrow) PrivateHashMap())
{
}
BNetworkCookieJar::BNetworkCookieJar(const BNetworkCookieJar& other)
:
fCookieHashMap(new(std::nothrow) PrivateHashMap())
{
*this = other;
}
BNetworkCookieJar::BNetworkCookieJar(const BNetworkCookieList& otherList)
:
fCookieHashMap(new(std::nothrow) PrivateHashMap())
{
AddCookies(otherList);
}
BNetworkCookieJar::BNetworkCookieJar(BMessage* archive)
:
fCookieHashMap(new(std::nothrow) PrivateHashMap())
{
BMessage extractedCookie;
for (int32 i = 0; archive->FindMessage(kArchivedCookieMessageName, i,
&extractedCookie) == B_OK; i++) {
BNetworkCookie* heapCookie
= new(std::nothrow) BNetworkCookie(&extractedCookie);
if (heapCookie == NULL)
break;
if (AddCookie(heapCookie) != B_OK) {
delete heapCookie;
continue;
}
}
}
BNetworkCookieJar::~BNetworkCookieJar()
{
for (Iterator it = GetIterator(); it.Next() != NULL;)
delete it.Remove();
fCookieHashMap->Lock();
PrivateHashMap::Iterator it = fCookieHashMap->GetIterator();
while (it.HasNext()) {
BNetworkCookieList* list = it.Next().value;
list->LockForWriting();
delete list;
}
delete fCookieHashMap;
}
status_t
BNetworkCookieJar::AddCookie(const BNetworkCookie& cookie)
{
BNetworkCookie* heapCookie = new(std::nothrow) BNetworkCookie(cookie);
if (heapCookie == NULL)
return B_NO_MEMORY;
status_t result = AddCookie(heapCookie);
if (result != B_OK)
delete heapCookie;
return result;
}
status_t
BNetworkCookieJar::AddCookie(const BString& cookie, const BUrl& referrer)
{
BNetworkCookie* heapCookie = new(std::nothrow) BNetworkCookie(cookie,
referrer);
if (heapCookie == NULL)
return B_NO_MEMORY;
status_t result = AddCookie(heapCookie);
if (result != B_OK)
delete heapCookie;
return result;
}
status_t
BNetworkCookieJar::AddCookie(BNetworkCookie* cookie)
{
if (fCookieHashMap == NULL)
return B_NO_MEMORY;
if (cookie == NULL || !cookie->IsValid())
return B_BAD_VALUE;
HashString key(cookie->Domain());
if (!fCookieHashMap->Lock())
return B_ERROR;
BNetworkCookieList* list = fCookieHashMap->Get(key);
if (list == NULL) {
list = new(std::nothrow) BNetworkCookieList();
if (list == NULL) {
fCookieHashMap->Unlock();
return B_NO_MEMORY;
}
if (fCookieHashMap->Put(key, list) != B_OK) {
fCookieHashMap->Unlock();
delete list;
return B_NO_MEMORY;
}
}
if (list->LockForWriting() != B_OK) {
fCookieHashMap->Unlock();
return B_ERROR;
}
fCookieHashMap->Unlock();
for (int32 i = 0; i < list->CountItems(); i++) {
const BNetworkCookie* c = list->ItemAt(i);
if (c->Name() == cookie->Name() && c->Path() == cookie->Path()) {
list->RemoveItemAt(i);
break;
}
}
if (cookie->ShouldDeleteNow()) {
TRACE("Remove cookie: %s\n", cookie->RawCookie(true).String());
delete cookie;
} else {
const BString& raw = cookie->RawCookie(true);
(void)raw;
TRACE("Add cookie: %s\n", raw.String());
int32 i;
for (i = 0; i < list->CountItems(); i++) {
const BNetworkCookie* current = list->ItemAt(i);
if (current->Path().Length() < cookie->Path().Length())
break;
}
list->AddItem(cookie, i);
}
list->Unlock();
return B_OK;
}
status_t
BNetworkCookieJar::AddCookies(const BNetworkCookieList& cookies)
{
for (int32 i = 0; i < cookies.CountItems(); i++) {
const BNetworkCookie* cookiePtr = cookies.ItemAt(i);
status_t result = AddCookie(*cookiePtr);
if (result != B_OK)
return result;
}
return B_OK;
}
uint32
BNetworkCookieJar::DeleteOutdatedCookies()
{
int32 deleteCount = 0;
const BNetworkCookie* cookiePtr;
for (Iterator it = GetIterator(); (cookiePtr = it.Next()) != NULL;) {
if (cookiePtr->ShouldDeleteNow()) {
delete it.Remove();
deleteCount++;
}
}
return deleteCount;
}
uint32
BNetworkCookieJar::PurgeForExit()
{
int32 deleteCount = 0;
const BNetworkCookie* cookiePtr;
for (Iterator it = GetIterator(); (cookiePtr = it.Next()) != NULL;) {
if (cookiePtr->ShouldDeleteAtExit()) {
delete it.Remove();
deleteCount++;
}
}
return deleteCount;
}
status_t
BNetworkCookieJar::Archive(BMessage* into, bool deep) const
{
status_t error = BArchivable::Archive(into, deep);
if (error == B_OK) {
const BNetworkCookie* cookiePtr;
for (Iterator it = GetIterator(); (cookiePtr = it.Next()) != NULL;) {
BMessage subArchive;
error = cookiePtr->Archive(&subArchive, deep);
if (error != B_OK)
return error;
error = into->AddMessage(kArchivedCookieMessageName, &subArchive);
if (error != B_OK)
return error;
}
}
return error;
}
BArchivable*
BNetworkCookieJar::Instantiate(BMessage* archive)
{
if (archive->HasMessage(kArchivedCookieMessageName))
return new(std::nothrow) BNetworkCookieJar(archive);
return NULL;
}
bool
BNetworkCookieJar::IsFixedSize() const
{
return false;
}
type_code
BNetworkCookieJar::TypeCode() const
{
return B_ANY_TYPE;
}
ssize_t
BNetworkCookieJar::FlattenedSize() const
{
_DoFlatten();
return fFlattened.Length() + 1;
}
status_t
BNetworkCookieJar::Flatten(void* buffer, ssize_t size) const
{
if (FlattenedSize() > size)
return B_ERROR;
fFlattened.CopyInto(reinterpret_cast<char*>(buffer), 0,
fFlattened.Length());
reinterpret_cast<char*>(buffer)[fFlattened.Length()] = 0;
return B_OK;
}
bool
BNetworkCookieJar::AllowsTypeCode(type_code) const
{
return false;
}
status_t
BNetworkCookieJar::Unflatten(type_code, const void* buffer, ssize_t size)
{
BString flattenedCookies;
flattenedCookies.SetTo(reinterpret_cast<const char*>(buffer), size);
while (flattenedCookies.Length() > 0) {
BNetworkCookie tempCookie;
BString tempCookieLine;
int32 endOfLine = flattenedCookies.FindFirst('\n', 0);
if (endOfLine == -1)
tempCookieLine = flattenedCookies;
else {
flattenedCookies.MoveInto(tempCookieLine, 0, endOfLine);
flattenedCookies.Remove(0, 1);
}
if (tempCookieLine.Length() != 0 && tempCookieLine[0] != '#') {
for (int32 field = 0; field < 7; field++) {
BString tempString;
int32 endOfField = tempCookieLine.FindFirst('\t', 0);
if (endOfField == -1)
tempString = tempCookieLine;
else {
tempCookieLine.MoveInto(tempString, 0, endOfField);
tempCookieLine.Remove(0, 1);
}
switch (field) {
case 0:
tempCookie.SetDomain(tempString);
break;
case 1:
break;
case 2:
tempCookie.SetPath(tempString);
break;
case 3:
tempCookie.SetSecure(tempString == "TRUE");
break;
case 4:
tempCookie.SetExpirationDate(atoi(tempString));
break;
case 5:
tempCookie.SetName(tempString);
break;
case 6:
tempCookie.SetValue(tempString);
break;
}
}
AddCookie(tempCookie);
}
}
return B_OK;
}
BNetworkCookieJar&
BNetworkCookieJar::operator=(const BNetworkCookieJar& other)
{
if (&other == this)
return *this;
for (Iterator it = GetIterator(); it.Next() != NULL;)
delete it.Remove();
BArchivable::operator=(other);
BFlattenable::operator=(other);
fFlattened = other.fFlattened;
delete fCookieHashMap;
fCookieHashMap = new(std::nothrow) PrivateHashMap();
for (Iterator it = other.GetIterator(); it.HasNext();) {
const BNetworkCookie* cookie = it.Next();
AddCookie(*cookie);
}
return *this;
}
BNetworkCookieJar::Iterator
BNetworkCookieJar::GetIterator() const
{
return BNetworkCookieJar::Iterator(this);
}
BNetworkCookieJar::UrlIterator
BNetworkCookieJar::GetUrlIterator(const BUrl& url) const
{
if (!url.HasPath()) {
BUrl copy(url);
copy.SetPath("/");
return BNetworkCookieJar::UrlIterator(this, copy);
}
return BNetworkCookieJar::UrlIterator(this, url);
}
void
BNetworkCookieJar::_DoFlatten() const
{
fFlattened.Truncate(0);
const BNetworkCookie* cookiePtr;
for (Iterator it = GetIterator(); (cookiePtr = it.Next()) != NULL;) {
fFlattened << cookiePtr->Domain() << '\t' << "TRUE" << '\t'
<< cookiePtr->Path() << '\t'
<< (cookiePtr->Secure()?"TRUE":"FALSE") << '\t'
<< (int32)cookiePtr->ExpirationDate() << '\t'
<< cookiePtr->Name() << '\t' << cookiePtr->Value() << '\n';
}
}
BNetworkCookieJar::Iterator::Iterator(const Iterator& other)
:
fCookieJar(other.fCookieJar),
fIterator(NULL),
fLastList(NULL),
fList(NULL),
fElement(NULL),
fLastElement(NULL),
fIndex(0)
{
fIterator = new(std::nothrow) PrivateIterator(
fCookieJar->fCookieHashMap->GetIterator());
_FindNext();
}
BNetworkCookieJar::Iterator::Iterator(const BNetworkCookieJar* cookieJar)
:
fCookieJar(const_cast<BNetworkCookieJar*>(cookieJar)),
fIterator(NULL),
fLastList(NULL),
fList(NULL),
fElement(NULL),
fLastElement(NULL),
fIndex(0)
{
fIterator = new(std::nothrow) PrivateIterator(
fCookieJar->fCookieHashMap->GetIterator());
_FindNext();
}
BNetworkCookieJar::Iterator::~Iterator()
{
if (fList != NULL)
fList->Unlock();
if (fLastList != NULL)
fLastList->Unlock();
delete fIterator;
}
BNetworkCookieJar::Iterator&
BNetworkCookieJar::Iterator::operator=(const Iterator& other)
{
if (this == &other)
return *this;
delete fIterator;
if (fList != NULL)
fList->Unlock();
fCookieJar = other.fCookieJar;
fIterator = NULL;
fLastList = NULL;
fList = NULL;
fElement = NULL;
fLastElement = NULL;
fIndex = 0;
fIterator = new(std::nothrow) PrivateIterator(
fCookieJar->fCookieHashMap->GetIterator());
_FindNext();
return *this;
}
bool
BNetworkCookieJar::Iterator::HasNext() const
{
return fElement;
}
const BNetworkCookie*
BNetworkCookieJar::Iterator::Next()
{
if (!fElement)
return NULL;
const BNetworkCookie* result = fElement;
_FindNext();
return result;
}
const BNetworkCookie*
BNetworkCookieJar::Iterator::NextDomain()
{
if (!fElement)
return NULL;
const BNetworkCookie* result = fElement;
if (!fIterator->fCookieMapIterator.HasNext()) {
fElement = NULL;
return result;
}
if (fList != NULL)
fList->Unlock();
if (fCookieJar->fCookieHashMap->Lock()) {
fList = fIterator->fCookieMapIterator.Next().value;
fList->LockForReading();
while (fList->CountItems() == 0
&& fIterator->fCookieMapIterator.HasNext()) {
fList->Unlock();
fList = fIterator->fCookieMapIterator.Next().value;
fList->LockForReading();
}
fCookieJar->fCookieHashMap->Unlock();
}
fIndex = 0;
fElement = fList->ItemAt(fIndex);
return result;
}
const BNetworkCookie*
BNetworkCookieJar::Iterator::Remove()
{
if (!fLastElement)
return NULL;
const BNetworkCookie* result = fLastElement;
if (fIndex == 0) {
if (fLastList && fCookieJar->fCookieHashMap->Lock()) {
fLastList->Unlock();
if (fLastList->LockForWriting() == B_OK) {
fLastList->RemoveItemAt(fLastList->CountItems() - 1);
fLastList->Unlock();
fLastList->LockForReading();
}
fCookieJar->fCookieHashMap->Unlock();
}
} else {
fIndex--;
if (fCookieJar->fCookieHashMap->Lock()) {
fList->Unlock();
if (fList->LockForWriting() == B_OK) {
fList->RemoveItemAt(fIndex);
fList->Unlock();
}
fList->LockForReading();
fCookieJar->fCookieHashMap->Unlock();
}
}
fLastElement = NULL;
return result;
}
void
BNetworkCookieJar::Iterator::_FindNext()
{
fLastElement = fElement;
fIndex++;
if (fList && fIndex < fList->CountItems()) {
fElement = fList->ItemAt(fIndex);
return;
}
if (fIterator == NULL || !fIterator->fCookieMapIterator.HasNext()) {
fElement = NULL;
return;
}
if (fLastList != NULL) {
fLastList->Unlock();
}
fLastList = fList;
if (fCookieJar->fCookieHashMap->Lock()) {
fList = (fIterator->fCookieMapIterator.Next().value);
fList->LockForReading();
while (fList->CountItems() == 0
&& fIterator->fCookieMapIterator.HasNext()) {
fList->Unlock();
fList = fIterator->fCookieMapIterator.Next().value;
fList->LockForReading();
}
fCookieJar->fCookieHashMap->Unlock();
}
fIndex = 0;
fElement = fList->ItemAt(fIndex);
}
BNetworkCookieJar::UrlIterator::UrlIterator(const UrlIterator& other)
:
fCookieJar(other.fCookieJar),
fIterator(NULL),
fList(NULL),
fLastList(NULL),
fElement(NULL),
fLastElement(NULL),
fIndex(0),
fLastIndex(0),
fUrl(other.fUrl)
{
_Initialize();
}
BNetworkCookieJar::UrlIterator::UrlIterator(const BNetworkCookieJar* cookieJar,
const BUrl& url)
:
fCookieJar(const_cast<BNetworkCookieJar*>(cookieJar)),
fIterator(NULL),
fList(NULL),
fLastList(NULL),
fElement(NULL),
fLastElement(NULL),
fIndex(0),
fLastIndex(0),
fUrl(url)
{
_Initialize();
}
BNetworkCookieJar::UrlIterator::~UrlIterator()
{
if (fList != NULL)
fList->Unlock();
if (fLastList != NULL)
fLastList->Unlock();
delete fIterator;
}
bool
BNetworkCookieJar::UrlIterator::HasNext() const
{
return fElement;
}
const BNetworkCookie*
BNetworkCookieJar::UrlIterator::Next()
{
if (!fElement)
return NULL;
const BNetworkCookie* result = fElement;
_FindNext();
return result;
}
const BNetworkCookie*
BNetworkCookieJar::UrlIterator::Remove()
{
if (!fLastElement)
return NULL;
const BNetworkCookie* result = fLastElement;
if (fCookieJar->fCookieHashMap->Lock()) {
fLastList->Unlock();
if (fLastList->LockForWriting() == B_OK) {
fLastList->RemoveItemAt(fLastIndex);
if (fLastList->CountItems() == 0) {
fCookieJar->fCookieHashMap->Remove(fIterator->fCookieMapIterator);
delete fLastList;
fLastList = NULL;
} else {
fLastList->Unlock();
fLastList->LockForReading();
}
}
fCookieJar->fCookieHashMap->Unlock();
}
fLastElement = NULL;
return result;
}
BNetworkCookieJar::UrlIterator&
BNetworkCookieJar::UrlIterator::operator=(
const BNetworkCookieJar::UrlIterator& other)
{
if (this == &other)
return *this;
if (fList)
fList->Unlock();
delete fIterator;
fCookieJar = other.fCookieJar;
fIterator = NULL;
fList = NULL;
fLastList = NULL;
fElement = NULL;
fLastElement = NULL;
fIndex = 0;
fLastIndex = 0;
fUrl = other.fUrl;
_Initialize();
return *this;
}
void
BNetworkCookieJar::UrlIterator::_Initialize()
{
BString domain = fUrl.Host();
if (!domain.Length()) {
if (fUrl.Protocol() == "file")
domain = "localhost";
else
return;
}
fIterator = new(std::nothrow) PrivateIterator(
fCookieJar->fCookieHashMap->GetIterator());
if (fIterator != NULL) {
domain.Prepend(".");
fIterator->fKey.SetTo(domain, domain.Length());
_FindNext();
}
}
bool
BNetworkCookieJar::UrlIterator::_SuperDomain()
{
BString domain(fIterator->fKey.GetString());
int32 firstDot = domain.FindFirst('.');
if (firstDot < 0)
return false;
const char* nextDot = domain.String() + firstDot;
fIterator->fKey.SetTo(nextDot + 1);
return true;
}
void
BNetworkCookieJar::UrlIterator::_FindNext()
{
fLastIndex = fIndex;
fLastElement = fElement;
if (fLastList != NULL)
fLastList->Unlock();
fLastList = fList;
if (fCookieJar->fCookieHashMap->Lock()) {
if (fLastList)
fLastList->LockForReading();
while (!_FindPath()) {
if (!_SuperDomain()) {
fElement = NULL;
fCookieJar->fCookieHashMap->Unlock();
return;
}
_FindDomain();
}
fCookieJar->fCookieHashMap->Unlock();
}
}
void
BNetworkCookieJar::UrlIterator::_FindDomain()
{
if (fList != NULL)
fList->Unlock();
if (fCookieJar->fCookieHashMap->Lock()) {
fList = fCookieJar->fCookieHashMap->Get(fIterator->fKey);
if (fList == NULL)
fElement = NULL;
else {
fList->LockForReading();
}
fCookieJar->fCookieHashMap->Unlock();
}
fIndex = -1;
}
bool
BNetworkCookieJar::UrlIterator::_FindPath()
{
fIndex++;
while (fList && fIndex < fList->CountItems()) {
fElement = fList->ItemAt(fIndex);
if (fElement->IsValidForPath(fUrl.Path()))
return true;
fIndex++;
}
return false;
}
BNetworkCookieList::BNetworkCookieList()
{
pthread_rwlock_init(&fLock, NULL);
}
BNetworkCookieList::~BNetworkCookieList()
{
pthread_rwlock_destroy(&fLock);
}
status_t
BNetworkCookieList::LockForReading()
{
return pthread_rwlock_rdlock(&fLock);
}
status_t
BNetworkCookieList::LockForWriting()
{
return pthread_rwlock_wrlock(&fLock);
}
status_t
BNetworkCookieList::Unlock()
{
return pthread_rwlock_unlock(&fLock);
}