From 33462ef54a6c481491c9716726619b5bb25c0dcf Mon Sep 17 00:00:00 2001 From: Hamish Morrison Date: Thu, 31 Jan 2013 18:53:25 +0000 Subject: [PATCH] NetworkCookie: bring SetCookie parsing in line with RFC 6265 --- headers/os/net/NetworkCookie.h | 30 +- src/kits/network/libnetapi/NetworkCookie.cpp | 329 +++++++++++-------- 2 files changed, 202 insertions(+), 157 deletions(-) diff --git a/headers/os/net/NetworkCookie.h b/headers/os/net/NetworkCookie.h index 5a6fd0139f..7db429f19c 100644 --- a/headers/os/net/NetworkCookie.h +++ b/headers/os/net/NetworkCookie.h @@ -17,7 +17,6 @@ class BNetworkCookie : public BArchivable { public: BNetworkCookie(const char* name, const char* value); - BNetworkCookie(const BNetworkCookie& other); BNetworkCookie(const BString& cookieString); BNetworkCookie(const BString& cookieString, const BUrl& url); @@ -25,7 +24,7 @@ public: BNetworkCookie(); virtual ~BNetworkCookie(); - // Parse a "SetCookie" string, or "name=value" + // Parse a "SetCookie" string BNetworkCookie& ParseCookieStringFromUrl(const BString& string, const BUrl& url); @@ -53,6 +52,7 @@ public: bool HttpOnly() const; const BString& RawCookie(bool full) const; + bool IsHostOnly() const; bool IsSessionCookie() const; bool IsValid(bool strict = false) const; bool IsValidForUrl(const BUrl& url) const; @@ -76,34 +76,36 @@ public: static BArchivable* Instantiate(BMessage* archive); // Overloaded operators - BNetworkCookie& operator=(const BNetworkCookie& other); BNetworkCookie& operator=(const char* string); bool operator==(const BNetworkCookie& other); bool operator!=(const BNetworkCookie& other); private: void _Reset(); - void _ExtractNameValuePair( - const BString& cookieString, int16* index, - bool parseField = false); - void _SetDefaultPathForUrl(const BUrl& url); + int32 _ExtractNameValuePair(const BString& string, + BString& name, BString& value, + int32 index); + int32 _ExtractAttributeValuePair( + const BString& string, BString& name, + BString& value, int32 index); + BString _DefaultPathForUrl(const BUrl& url); private: mutable BString fRawCookie; mutable bool fRawCookieValid; mutable BString fRawFullCookie; mutable bool fRawFullCookieValid; - - BString fDomain; - BDateTime fExpiration; mutable BString fExpirationString; mutable bool fExpirationStringValid; - BString fPath; - bool fSecure; - bool fHttpOnly; + BString fName; BString fValue; + BString fDomain; + BString fPath; + BDateTime fExpiration; + bool fSecure; + bool fHttpOnly; - bool fHasExpirationDate; + bool fHostOnly; bool fSessionCookie; }; diff --git a/src/kits/network/libnetapi/NetworkCookie.cpp b/src/kits/network/libnetapi/NetworkCookie.cpp index ca86851553..a06335a9e6 100644 --- a/src/kits/network/libnetapi/NetworkCookie.cpp +++ b/src/kits/network/libnetapi/NetworkCookie.cpp @@ -1,9 +1,10 @@ /* - * Copyright 2010 Haiku Inc. All rights reserved. + * Copyright 2010-2013 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 */ @@ -13,11 +14,10 @@ #include #include +#include #include #include -#define PRINT(x) printf x; - using BPrivate::BHttpTime; static const char* kArchivedCookieName = "be:cookie.name"; @@ -27,22 +27,14 @@ static const char* kArchivedCookiePath = "be:cookie.path"; static const char* kArchivedCookieExpirationDate = "be:cookie.expirationdate"; static const char* kArchivedCookieSecure = "be:cookie.secure"; static const char* kArchivedCookieHttpOnly = "be:cookie.httponly"; +static const char* kArchivedCookieHostOnly = "be:cookie.hostonly"; BNetworkCookie::BNetworkCookie(const char* name, const char* value) - : - fName(name), - fValue(value), - fSessionCookie(true) { _Reset(); -} - - -BNetworkCookie::BNetworkCookie(const BNetworkCookie& other) -{ - _Reset(); - *this = other; + fName = name; + fValue = value; } @@ -62,8 +54,6 @@ BNetworkCookie::BNetworkCookie(const BString& cookieString, BNetworkCookie::BNetworkCookie(BMessage* archive) - : - fSessionCookie(true) { _Reset(); @@ -74,6 +64,7 @@ BNetworkCookie::BNetworkCookie(BMessage* archive) archive->FindString(kArchivedCookiePath, &fPath); archive->FindBool(kArchivedCookieSecure, &fSecure); archive->FindBool(kArchivedCookieHttpOnly, &fHttpOnly); + archive->FindBool(kArchivedCookieHostOnly, &fHostOnly); int32 expiration; if (archive->FindInt32(kArchivedCookieExpirationDate, &expiration) @@ -101,19 +92,61 @@ BNetworkCookie& BNetworkCookie::ParseCookieStringFromUrl(const BString& string, const BUrl& url) { - BString cookieString(string); - int16 index = 0; - _Reset(); - // Default values from url - SetDomain(url.Host()); - _SetDefaultPathForUrl(url); + BString name; + BString value; + int32 index = 0; - _ExtractNameValuePair(cookieString, &index); + // Parse the name and value of the cookie + index = _ExtractNameValuePair(string, name, value, index); + // The set-cookie-string is not valid + if (index == -1) + return *this; - while (index < cookieString.Length()) - _ExtractNameValuePair(cookieString, &index, true); + SetName(name); + SetValue(value); + + // Parse the remaining cookie attributes + while (index < string.Length()) { + ASSERT(string[index] == ';'); + index++; + + index = _ExtractAttributeValuePair(string, name, value, index); + + if (name.ICompare("secure") == 0) + SetSecure(true); + else if (name.ICompare("httponly") == 0) + SetHttpOnly(true); + + // The following attributes require a value + if (value.IsEmpty()) + continue; + + if (name.ICompare("max-age") == 0) { + // Validate the max-age value + char* end = NULL; + long maxAge = strtol(value.String(), &end, 10); + if (*end == '\0') + SetMaxAge((int)maxAge); + } else if (name.ICompare("expires") == 0) { + BHttpTime date(value); + SetExpirationDate(date.Parse()); + } else if (name.ICompare("domain") == 0) + SetDomain(value); + else if (name.ICompare("path") == 0) + SetPath(value); + } + + // If no domain was specified, we set a host-only domain from the URL + if (!HasDomain()) { + SetDomain(url.Host()); + fHostOnly = true; + } + + // If no path was specified we compute the default path from the URL + if (!HasPath()) + SetPath(_DefaultPathForUrl(url)); return *this; } @@ -164,6 +197,7 @@ BNetworkCookie& BNetworkCookie::SetDomain(const BString& domain) { fDomain = domain; + fHostOnly = false; // We always use pre-dotted domains for tail matching if (fDomain.ByteAt(0) != '.') @@ -200,13 +234,11 @@ BNetworkCookie::SetExpirationDate(BDateTime& expireDate) fSessionCookie = true; fExpirationStringValid = false; fRawFullCookieValid = false; - fHasExpirationDate = false; } else { fExpiration = expireDate; fSessionCookie = false; fExpirationStringValid = false; fRawFullCookieValid = false; - fHasExpirationDate = true; } return *this; @@ -331,6 +363,13 @@ BNetworkCookie::RawCookie(bool full) const // #pragma mark Cookie test +bool +BNetworkCookie::IsHostOnly() const +{ + return fHostOnly; +} + + bool BNetworkCookie::IsSessionCookie() const { @@ -410,7 +449,7 @@ BNetworkCookie::HasPath() const bool BNetworkCookie::HasExpirationDate() const { - return fHasExpirationDate; + return !IsSessionCookie(); } @@ -461,7 +500,7 @@ BNetworkCookie::Archive(BMessage* into, bool deep) const return error; } - if (fHasExpirationDate) { + if (HasExpirationDate()) { error = into->AddInt32(kArchivedCookieExpirationDate, fExpiration.Time_t()); if (error != B_OK) @@ -486,6 +525,12 @@ BNetworkCookie::Archive(BMessage* into, bool deep) const return error; } + if (IsHostOnly()) { + error = into->AddBool(kArchivedCookieHostOnly, true); + if (error != B_OK) + return error; + } + return B_OK; } @@ -504,32 +549,6 @@ BNetworkCookie::Instantiate(BMessage* archive) // #pragma mark Overloaded operators -BNetworkCookie& -BNetworkCookie::operator=(const BNetworkCookie& other) -{ - // Should we prefer to discard the cache ? - fRawCookie = other.fRawCookie; - fRawCookieValid = other.fRawCookieValid; - fRawFullCookie = other.fRawFullCookie; - fRawFullCookieValid = other.fRawFullCookieValid; - fExpirationString = other.fExpirationString; - fExpirationStringValid = other.fExpirationStringValid; - - fName = other.fName; - fValue = other.fValue; - fDomain = other.fDomain; - fPath = other.fPath; - fExpiration = other.fExpiration; - fSecure = other.fSecure; - fHttpOnly = other.fHttpOnly; - - fHasExpirationDate = other.fHasExpirationDate; - fSessionCookie = other.fSessionCookie; - - return *this; -} - - BNetworkCookie& BNetworkCookie::operator=(const char* string) { @@ -555,109 +574,133 @@ BNetworkCookie::operator!=(const BNetworkCookie& other) void BNetworkCookie::_Reset() { - fDomain.Truncate(0); - fPath.Truncate(0); fName.Truncate(0); fValue.Truncate(0); - fSecure = false; - fHttpOnly = false; - fExpiration = BDateTime(); + fDomain.Truncate(0); + fPath.Truncate(0); + fExpiration = BDateTime(); + fSecure = false; + fHttpOnly = false; - fHasExpirationDate = false; - fSessionCookie = true; + fSessionCookie = true; + fHostOnly = true; - fRawCookieValid = false; - fRawFullCookieValid = false; - fExpirationStringValid = false; + fRawCookieValid = false; + fRawFullCookieValid = false; + fExpirationStringValid = false; } -void -BNetworkCookie::_ExtractNameValuePair(const BString& cookieString, - int16* index, bool parseField) +int32 +skip_whitespace_forward(const BString& string, int32 index) { - // Skip whitespaces - while (cookieString.ByteAt(*index) == ' ' - && *index < cookieString.Length()) - (*index)++; - - if (*index >= cookieString.Length()) - return; - - - // Look for a name=value pair - int16 firstSemiColon = cookieString.FindFirst(";", *index); - int16 firstEqual = cookieString.FindFirst("=", *index); - - BString name; - BString value; - - if (firstSemiColon == -1) { - if (firstEqual != -1) { - cookieString.CopyInto(name, *index, firstEqual - *index); - cookieString.CopyInto(value, firstEqual + 1, - cookieString.Length() - firstEqual - 1); - } else - cookieString.CopyInto(value, *index, - cookieString.Length() - *index); - - *index = cookieString.Length() + 1; - } else { - if (firstEqual != -1 && firstEqual < firstSemiColon) { - cookieString.CopyInto(name, *index, firstEqual - *index); - cookieString.CopyInto(value, firstEqual + 1, - firstSemiColon - firstEqual - 1); - } else - cookieString.CopyInto(value, *index, firstSemiColon - *index); - - *index = firstSemiColon + 1; - } - - // Cookie name/value pair - if (!parseField) { - SetName(name); - SetValue(value); - return; - } - - name.ToLower(); - name.Trim(); - value.Trim(); - - // Cookie max-age - if (name == "maxage") - SetMaxAge(atoi(value.String())); - // Cookie expiration date - else if (name == "expires") { - BHttpTime date(value); - SetExpirationDate(date.Parse()); - // Cookie valid domain - } else if (name == "domain") - SetDomain(value); - // Cookie valid path - else if (name == "path") - SetPath(value); - // Cookie secure flag - else if (name == "secure") - SetSecure(value.Length() == 0 || value.ToLower() == "true"); + while (index < string.Length() && (string[index] == ' ' + || string[index] == '\t')) + index++; + return index; } -void -BNetworkCookie::_SetDefaultPathForUrl(const BUrl& url) +int32 +skip_whitespace_backward(const BString& string, int32 index) +{ + while (index >= 0 && (string[index] == ' ' || string[index] == '\t')) + index--; + return index; +} + + +int32 +BNetworkCookie::_ExtractNameValuePair(const BString& cookieString, + BString& name, BString& value, int32 index) +{ + // Find our name-value-pair and the delimiter. + int32 firstEquals = cookieString.FindFirst('=', index); + int32 nameValueEnd = cookieString.FindFirst(';', index); + + // If the set-cookie-string lacks a semicolon, the name-value-pair + // is the whole string. + if (nameValueEnd == -1) + nameValueEnd = cookieString.Length(); + + // If the name-value-pair lacks an equals, the parse should fail. + if (firstEquals == -1 || firstEquals > nameValueEnd) + return -1; + + int32 first = skip_whitespace_forward(cookieString, index); + int32 last = skip_whitespace_backward(cookieString, firstEquals - 1); + + // If we lack a name, fail to parse. + if (first > last) + return -1; + + cookieString.CopyInto(name, first, last - first + 1); + + first = skip_whitespace_forward(cookieString, firstEquals + 1); + last = skip_whitespace_backward(cookieString, nameValueEnd - 1); + if (first <= last) + cookieString.CopyInto(value, first, last - first + 1); + else + value.SetTo(""); + + return nameValueEnd; +} + + +int32 +BNetworkCookie::_ExtractAttributeValuePair(const BString& cookieString, + BString& attribute, BString& value, int32 index) +{ + // Find the end of our cookie-av. + int32 cookieAVEnd = cookieString.FindFirst(';', index); + + // If the unparsed-attributes lacks a semicolon, then the cookie-av is the + // whole string. + if (cookieAVEnd == -1) + cookieAVEnd = cookieString.Length(); + + int32 attributeNameEnd = cookieString.FindFirst('=', index); + // If the cookie-av has no equals, the attribute-name is the entire + // cookie-av and the attribute-value is empty. + if (attributeNameEnd == -1 || attributeNameEnd > cookieAVEnd) + attributeNameEnd = cookieAVEnd; + + int32 first = skip_whitespace_forward(cookieString, index); + int32 last = skip_whitespace_backward(cookieString, attributeNameEnd - 1); + + if (first <= last) + cookieString.CopyInto(attribute, first, last - first + 1); + else + attribute.SetTo(""); + + if (attributeNameEnd == cookieAVEnd) { + value.SetTo(""); + return cookieAVEnd; + } + + first = skip_whitespace_forward(cookieString, attributeNameEnd + 1); + last = skip_whitespace_backward(cookieString, cookieAVEnd - 1); + if (first <= last) + cookieString.CopyInto(value, first, last - first + 1); + else + value.SetTo(""); + + return cookieAVEnd; +} + + +BString +BNetworkCookie::_DefaultPathForUrl(const BUrl& url) { const BString& path = url.Path(); - if (path.IsEmpty() || path.ByteAt(0) != '/') { - SetPath("/"); - return; - } + if (path.IsEmpty() || path.ByteAt(0) != '/') + return ""; int32 index = path.FindLast('/'); - if (index == 0) { - SetPath("/"); - return; - } + if (index == 0) + return ""; BString newPath = path; - SetPath(newPath.Truncate(index)); + newPath.Truncate(index); + return newPath; }