blob: 84c1a102244c29fb03fd954d119f0f74daa00d3d [file] [log] [blame]
/*
* Copyright (C) 2017 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
WI.Cookie = class Cookie
{
constructor(type, name, value, raw, expires, maxAge, path, domain, secure, httpOnly)
{
console.assert(Object.values(WI.Cookie.Type).includes(type));
console.assert(typeof name === "string");
console.assert(typeof value === "string");
console.assert(!raw || typeof raw === "string");
console.assert(!expires || expires instanceof Date);
console.assert(!maxAge || typeof maxAge === "number");
console.assert(!path || typeof path === "string");
console.assert(!domain || typeof domain === "string");
console.assert(!secure || typeof secure === "boolean");
console.assert(!httpOnly || typeof httpOnly === "boolean");
this.type = type;
this.name = name || "";
this.value = value || "";
if (this.type === WI.Cookie.Type.Response) {
this.rawHeader = raw || "";
this.expires = expires || null;
this.maxAge = maxAge || null;
this.path = path || null;
this.domain = domain || null;
this.secure = secure || false;
this.httpOnly = httpOnly || false;
}
}
// Public
expirationDate(requestSentDate)
{
if (this.maxAge) {
let startDate = requestSentDate || new Date;
return new Date(startDate.getTime() + (this.maxAge * 1000));
}
return this.expires;
}
// Static
// RFC 6265 defines the HTTP Cookie and Set-Cookie header fields:
// https://www.ietf.org/rfc/rfc6265.txt
static parseCookieRequestHeader(header)
{
if (!header)
return [];
header = header.trim();
if (!header)
return [];
let cookies = [];
// Cookie: <name> = <value> ( ";" SP <name> = <value> )*?
// NOTE: Just name/value pairs.
let pairs = header.split(/; /);
for (let pair of pairs) {
let match = pair.match(/^(?<name>[^\s=]+)[ \t]*=[ \t]*(?<value>.*)$/);
if (!match) {
WI.reportInternalError("Failed to parse Cookie pair", {header, pair});
continue;
}
let {name, value} = match.groups;
cookies.push(new WI.Cookie(WI.Cookie.Type.Request, name, value));
}
return cookies;
}
static parseSetCookieResponseHeader(header)
{
if (!header)
return null;
// Set-Cookie: <name> = <value> ( ";" SP <attr-maybe-pair> )*?
// NOTE: Some attributes can have pairs (e.g. "Path=/"), some are only a
// single word (e.g. "Secure").
// Parse name/value.
let nameValueMatch = header.match(/^(?<name>[^\s=]+)[ \t]*=[ \t]*(?<value>[^;]*)/);
if (!nameValueMatch) {
WI.reportInternalError("Failed to parse Set-Cookie header", {header});
return null;
}
let {name, value} = nameValueMatch.groups;
let expires = null;
let maxAge = null;
let path = null;
let domain = null;
let secure = false;
let httpOnly = false;
// Parse Attributes
let remaining = header.substr(nameValueMatch[0].length);
let attributes = remaining.split(/; ?/);
for (let attribute of attributes) {
if (!attribute)
continue;
let match = attribute.match(/^(?<name>[^\s=]+)(?:=(?<value>.*))?$/);
if (!match) {
console.error("Failed to parse Set-Cookie attribute:", attribute);
continue;
}
let attributeName = match.groups.name;
let attributeValue = match.groups.value;
switch (attributeName.toLowerCase()) {
case "expires":
console.assert(attributeValue);
expires = new Date(attributeValue);
if (isNaN(expires.getTime())) {
console.warn("Invalid Expires date:", attributeValue);
expires = null;
}
break;
case "max-age":
console.assert(attributeValue);
maxAge = parseInt(attributeValue, 10);
if (isNaN(maxAge) || !/^\d+$/.test(attributeValue)) {
console.warn("Invalid MaxAge value:", attributeValue);
maxAge = null;
}
break;
case "path":
console.assert(attributeValue);
path = attributeValue;
break;
case "domain":
console.assert(attributeValue);
domain = attributeValue;
break;
case "secure":
console.assert(!attributeValue);
secure = true;
break;
case "httponly":
console.assert(!attributeValue);
httpOnly = true;
break;
default:
console.warn("Unknown Cookie attribute:", attribute);
break;
}
}
return new WI.Cookie(WI.Cookie.Type.Response, name, value, header, expires, maxAge, path, domain, secure, httpOnly);
}
}
WI.Cookie.Type = {
Request: "request",
Response: "response",
};