UNPKG

cookiefile

Version:

Package for operating with Netscape HTTP Cookie File

361 lines (321 loc) 11.4 kB
/** * Created by horat1us on 09.10.16. */ "use strict"; module.exports = { Cookie: class Cookie { constructor({domain, httpOnly = false, crossDomain = false, path = '/', https = false, expire = 0, name, value}) { if (!(expire instanceof Date || require('isnumeric')(expire))) { throw new module.exports.CookieError(); } this.domain = domain; this.crossDomain = crossDomain; this.path = path; this.https = https; this.expire = ~~expire ? ~~expire : 0; this.value = value; this.cookieName = name; this.httpOnly = httpOnly } /** @return {String} */ get name() { return this.cookieName; } get isCrossDomain() { return this.crossDomain.toString().toUpperCase(); } get isHttps() { return this.https.toString().toUpperCase(); } /** * @return {Cookie} */ clone() { return new Cookie(this); } /** @return {String} */ toString() { let _this = this, string = this.httpOnly ? "#HttpOnly_" : ""; ['domain', 'isCrossDomain', 'path', 'isHttps', 'expire', 'name', 'value'] .forEach(prop => string += _this[prop] + '\t'); return string.trim() + '\n'; } toHeader() { return `${this.name}=${this.value}; `; } toResponseHeader() { return `Set-Cookie: ${this.name}=${this.value}; ` + [['Domain', 'domain'], ['Path', 'path']] .map(([name, prop]) => `${name}=${this[prop]}`) .join('; ') + '; ' + `expires=${new Date(this.expire).toUTCString()}; ` + [['Secure', 'https'], ['HttpOnly', 'httpOnly']] .filter(([,property]) => this[property]) .map(([name]) => ` ${name}`) .join('; '); } /** * @param {Cookie} cookie * @return {Boolean} */ is(cookie) { for (let prop in ['domain', 'crossDomain', 'path', 'https', 'expire', 'name', 'value']) { if (this[prop] !== cookie[prop]) { return false; } } return true; } }, CookieMap: class CookieMap extends Map { constructor(file = []) { if (Array.isArray(file)) { super(); file.forEach(cookie => { if (!(cookie instanceof module.exports.Cookie)) { throw new module.exports.CookieError(4); } this.set(cookie); }); this.file = false; } else if (typeof(file) === 'string') { super(); this.file = file; return this.readFile(); } else { throw new TypeError("Wrong argument supplied for CookieMap construtor"); } } /** * @param {String} nameValue a=b=c * @returns {Array} [ 'a', 'b=c' ] */ static split(nameValue) { let parts = nameValue .trim() .split('='); if (parts.length == 1) { return parts; } parts = [parts[0], (nameValue.replace(`${parts[0]}=`, ''))]; return parts; } /** * @param {String} header HTTP Header like Set-Cookie: ... * @param {Object} props * @return {CookieMap} */ header(header, props = {}) { if (!CookieMap.validateHeader(header)) { return this; } let CookieInfo = {}; header .replace('Set-Cookie: ', '') .split(';') .map(CookieMap.split) .map(parts => parts.map(part => part.trim())) .filter(part => { if (part.length === 2) { return true; } switch (part[0]) { case "Secure": CookieInfo.https = true; break; case "HttpOnly": CookieInfo.httpOnly = true; break; } return false; }) .forEach(([name, value]) => { switch (name.toLowerCase()) { case "domain": return CookieInfo.domain = value; case "path": return CookieInfo.path = value; case "expires": return CookieInfo.expire = new Date(value); case "same-Site": case "max-Age": // TODO: Integrate support for Max-Age and Same-Site directives return; default: CookieInfo.name = name; CookieInfo.value = value; } }); CookieInfo = Object.assign(props, CookieInfo); ['name', 'value', 'domain'] .forEach(prop => { if (!CookieInfo.hasOwnProperty(prop)) { throw new module.exports.CookieError(5); } }); this.set(new module.exports.Cookie(CookieInfo)); return this; } /** * @param {String} requestHeader: Cookie: a=b; d=c * @param {Object} params * @return {CookieMap} */ generate(requestHeader, params = {}) { requestHeader .replace('Cookie: ', '') .trim() .split(';') .map(cookie => cookie.trim()) .filter(cookie => cookie.length > 0) .map(CookieMap.split) .filter(cookie => cookie.length === 2) .map(([name, value]) => Object.assign({name, value}, params)) .map(params => new module.exports.Cookie(params)) .forEach(cookie => this.set(cookie)); return this; } /** * Takes sample HTTP Cookie and return true if set-cookie header given * @param {String} header HTTP Header like Set-Cookie: ... */ static validateHeader(header) { return header .trim() .substr(0, 11) === "Set-Cookie:"; } /** * @param {Cookie} cookie * @return {CookieMap} */ set(cookie) { if (!(cookie instanceof module.exports.Cookie)) { throw new TypeError(`Cookie must be type of cookie, ${typeof(cookie)} given`); } super.set(cookie.name, cookie); return this; } /** * @return {CookieMap} */ save(file = false) { if (file === false || typeof(file) !== 'string') { file = this.file; } if (file === false) { throw new module.exports.CookieError(2); } require('fs').writeFileSync(file, this.toString()); return this; } /** * @returns {CookieMap} */ clone() { let cookieMap = new CookieMap(); for ([, cookie] of this) { cookieMap.set(cookie.clone()); } return cookieMap; } toString() { let cookieContent = module.exports.CookieFile.Header; /** @var {Cookie} cookie */ for (let cookie of this.values()) { cookieContent += cookie.toString(); } return cookieContent.trim(); } /** * @return {String} HTTP User header */ toRequestHeader({http = true, secure = true} = {}) { let string = 'Cookie: '; /** * @var {String} name * @var {Cookie} cookie */ for (let [, cookie] of this) { string += cookie.toHeader(); } return string.replace(/;\s*$/, ''); } /** * @return {String} HTTP Server header */ toResponseHeader() { let headers = []; for (let [,cookie] of this) { headers.push(cookie.toResponseHeader()); } return headers; } /** * @return {CookieMap} */ readFile() { if (!require('file-exists')(this.file)) { throw new module.exports.CookieError(1); } const fs = require('fs'); let cookieFileContents = fs.readFileSync(this.file, {encoding: 'UTF-8'}) const cookies = cookieFileContents .split('\n') .map(line => line.split("\t").map((word) => word.trim())) .filter(line => line.length === 7) .map(cookieData => ({ name: cookieData[5], value: cookieData[6], domain: cookieData[0], crossDomain: cookieData[1] === 'TRUE', path: cookieData[2], https: cookieData[3] === 'TRUE', expire: ~~cookieData[4] ? ~~cookieData[4] : 0, })) .map(cookie => { if (cookie.domain.substr(0, 10) === "#HttpOnly_") { cookie.httpOnly = true; cookie.domain = cookie.domain.substr(10); } return cookie; }) .forEach(cookie => this.set( new module.exports.Cookie(cookie) )); return this; } get size() { return super.size; } }, CookieError: class CookieError extends Error { constructor(code = 0) { const message = (() => { switch (code) { case 5: return "Wrong header passed for creating cookie"; case 4: return "Cookie passed to constructor is incorrect"; case 3: return "Cookie file writing error"; case 2: return 'You can not save this object because is initialized by Map, not file'; case 1: return 'Cookie File doesn\'t exists'; default: return "Cookie expire must be instance of Date or integer"; } })(); super(message); } } , CookieFile: { Header: `# Netscape HTTP Cookie File # https://curl.haxx.se/docs/http-cookies.html # This file was generated by node-httpcookie! Edit at your own risk `, } } ;