/* * 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; } } // 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: = ( ";" SP = )*? // NOTE: Just name/value pairs. let pairs = header.split(/; /); for (let pair of pairs) { let match = pair.match(/^(?[^\s=]+)[ \t]*=[ \t]*(?.*)$/); 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: = ( ";" SP )*? // 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(/^(?[^\s=]+)[ \t]*=[ \t]*(?[^;]*)/); 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(/^(?[^\s=]+)(?:=(?.*))?$/); 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", };