From 9f498864bbd69a08382e6f606328792f6ac6908f Mon Sep 17 00:00:00 2001 From: jkhsjdhjs Date: Wed, 14 Aug 2019 23:35:55 +0200 Subject: [PATCH] bugfixes --- README.md | 14 ++++++------ src/cookie-jar.mjs | 54 +++++++++++++++++++++++++++++----------------- src/cookie.mjs | 52 +++++++++++++++++++++++++------------------- src/index.mjs | 48 +++++++++++++---------------------------- 4 files changed, 86 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index 3fb5767..5b05b71 100644 --- a/README.md +++ b/README.md @@ -50,19 +50,19 @@ Adds a cookie to the jar. Reads a cookie jar from the disk and adds the contained cookies. #### domains() -Returns an array of the domains currently stored cookies for. +Returns an iterator over all domains currently stored cookies for. -#### *iterValidForRequest(domain, url) -Returns an iterator over all cookies valid for a request to `domain` and `url`. +#### *cookiesDomain(domain) +Returns an iterator over all cookies currently stored for `domain`. -#### *iterValid() +#### *cookiesValid() Returns an iterator over all valid (non-expired) cookies. -#### *iterAll() +#### *cookiesAll() Returns an iterator over all cookies currently stored. -#### *iter(domain) -Returns an iterator over all cookies for a specific domain. +#### *cookiesValidForRequest(url) +Returns an iterator over all cookies valid for a request to `url`. #### deleteExpired() Removes all expired cookies from the jar. diff --git a/src/cookie-jar.mjs b/src/cookie-jar.mjs index e990031..81c3e4d 100644 --- a/src/cookie-jar.mjs +++ b/src/cookie-jar.mjs @@ -1,4 +1,5 @@ import fs from "fs"; +import url from "url"; import Cookie from "./cookie"; export default class CookieJar { @@ -28,7 +29,7 @@ export default class CookieJar { addCookie(c, fromURL) { if(typeof c === "string") c = new Cookie(c, fromURL); - if(!(c instanceof Cookie)) + else if(!(c instanceof Cookie)) throw new TypeError("First parameter is neither a string nor a cookie!"); if(!this.cookies.get(c.domain)) this.cookies.set(c.domain, new Map()); @@ -38,35 +39,48 @@ export default class CookieJar { JSON.parse(fs.readFileSync(this.file)).forEach(c => this.addCookie(Cookie.fromObject(c))); } domains() { - return [...this.cookies.keys()]; + return this.cookies.keys(); } - *iterValidForRequest(domain, url) { - for(const cookie of this.iter(domain)) - if(cookie.isValidForRequest(url)) - yield cookie; - } - *iterValid() { - for(const cookie of this.iterAll()) - if(!cookie.hasExpired()) - yield cookie; - } - *iterAll() { - for(const domain of this.domains()) - yield* this.iter(domain); - } - *iter(domain) { + *cookiesDomain(domain) { for(const cookie of (this.cookies.get(domain) || []).values()) yield cookie; } + *cookiesValid() { + for(const cookie of this.cookiesAll()) + if(!cookie.hasExpired()) + yield cookie; + } + *cookiesAll() { + for(const domain of this.domains()) + yield* this.cookiesDomain(domain); + } + *cookiesValidForRequest(requestURL) { + const namesYielded = [], + domains = url + .parse(requestURL) + .hostname + .split(".") + .map((_, i, a) => a.slice(i).join(".")) + .slice(0, -1); + for(const domain of domains) { + for(const cookie of this.cookiesDomain(domain)) { + if(cookie.isValidForRequest(requestURL) + && namesYielded.every(name => name !== cookie.name)) { + namesYielded.push(cookie.name); + yield cookie; + } + } + } + } deleteExpired() { - const filteredCookies = [...this.iterValid()]; + const validCookies = [...this.cookiesValid()]; this.cookies = new Map(); - filteredCookies.forEach(c => this.addCookie(c)); + validCookies.forEach(c => this.addCookie(c)); } save() { if(typeof this.file !== "string") throw new Error("No file has been specified for this cookie jar!"); // only save cookies that haven't expired - fs.writeFileSync(this.file, JSON.stringify([...this.iterValid()])); + fs.writeFileSync(this.file, JSON.stringify([...this.cookiesValid()])); } }; diff --git a/src/cookie.mjs b/src/cookie.mjs index bc4e31b..b04b76f 100644 --- a/src/cookie.mjs +++ b/src/cookie.mjs @@ -1,4 +1,4 @@ -import urlParser from "url"; +import url from "url"; const validateHostname = (cookieHostname, requestHostname, subdomains) => { cookieHostname = cookieHostname.toLowerCase(); @@ -28,12 +28,14 @@ const splitN = (str, sep, n) => { }; export default class Cookie { - constructor(str, url) { + constructor(str, requestURL) { if(typeof str !== "string") - throw new TypeError("Input not a string"); + throw new TypeError("First parameter is not a string!"); const splitted = str.split("; "); [this.name, this.value] = splitN(splitted[0], "=", 1); + if(!this.name) + throw new Error("Invalid cookie name \"" + this.name + "\""); if(this.value.startsWith("\"") && this.value.endsWith("\"")) this.value = this.value.slice(1, -1); @@ -46,12 +48,12 @@ export default class Cookie { continue; if(!/^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), \d{2}[ -](?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[ -]\d{2,4} \d{2}:\d{2}:\d{2} GMT$/.test(v) || (this.expiry = new Date(v)) === "Invalid Date") - throw new TypeError("Invalid value for Expires \"" + v + "\"!"); + throw new Error("Invalid value for Expires \"" + v + "\"!"); } else if(k === "max-age") { const seconds = ~~+v; if(seconds.toString() !== v) - throw new TypeError("Invalid value for Max-Age \"" + v + "\"!"); + throw new Error("Invalid value for Max-Age \"" + v + "\"!"); this.expiry = new Date(); this.expiry.setSeconds(this.expiry.getSeconds() + seconds); } @@ -67,7 +69,7 @@ export default class Cookie { else if(k === "samesite") // only relevant for cross site requests, so not for us continue; else - throw new TypeError("Invalid key \"" + k + "\" specified!"); + throw new Error("Invalid key \"" + k + "\" with value \"" + v + "\" specified!"); } else { if(k === "secure") @@ -75,23 +77,32 @@ export default class Cookie { else if(k === "httponly") // only relevant for browsers continue; else - throw new TypeError("Invalid key \"" + k + "\" specified!"); + throw new Error("Invalid key \"" + k + "\" without value specified!"); } } + + const parsedURL = url.parse(requestURL); + + if(this.name.toLowerCase().startsWith("__secure-") && (!this.secure || parsedURL.protocol !== "https:")) + throw new Error("Cookie has \"__Secure-\" prefix but \"Secure\" isn't set or the cookie is not set via https!"); + if(this.name.toLowerCase().startsWith("__host-") && (!this.secure || parsedURL.protocol !== "https:" || this.domain)) + throw new Error("Cookie has \"__Host-\" prefix but \"Secure\" isn't set, the cookie is not set via https, \"Domain\" is set or \"Path\" is not equal to \"/\"!"); + + // assign defaults if(!this.domain) { - this.domain = urlParser.parse(url).hostname; + this.domain = parsedURL.hostname; this.subdomains = false; } if(!this.path) this.path = "/"; - if(this.name.toLowerCase().startsWith("__secure-") && (!this.secure || !url.toLowerCase().startsWith("https:"))) - throw new TypeError("Cookie has \"__Secure-\" prefix but \"Secure\" isn't set or the cookie is not set via https!"); - if(this.name.toLowerCase().startsWith("__host-") && (!this.secure || !url.toLowerCase().startsWith("https:") || this.domain || this.path !== "/")) - throw new TypeError("Cookie has \"__Host-\" prefix but \"Secure\" isn't set, the cookie is not set via https, \"Domain\" is set or \"Path\" is not equal to \"/\"!"); + if(!this.secure) + this.secure = false; + if(!this.expiry) + this.expiry = null; } static fromObject(obj) { let c = Object.assign(Object.create(this.prototype), obj); - if(c.expiry && typeof c.expiry === "string") + if(typeof c.expiry === "string") c.expiry = new Date(c.expiry); return c; } @@ -101,17 +112,14 @@ export default class Cookie { hasExpired() { return this.expiry && this.expiry < new Date(); } - isValidForRequest(url) { + isValidForRequest(requestURL) { if(this.hasExpired()) return false; - const parsedURL = urlParser.parse(url); - if(parsedURL.protocol !== "http:" && parsedURL.protocol !== "https:") - return false; - if(this.secure && parsedURL.protocol !== "https:") - return false; - if(!validateHostname(this.domain, parsedURL.hostname, this.subdomains)) - return false; - if(!validatePath(this.path, parsedURL.pathname)) + const parsedURL = url.parse(requestURL); + if(parsedURL.protocol !== "http:" && parsedURL.protocol !== "https:" + || this.secure && parsedURL.protocol !== "https:" + || !validateHostname(this.domain, parsedURL.hostname, this.subdomains) + || !validatePath(this.path, parsedURL.pathname)) return false; return true; } diff --git a/src/index.mjs b/src/index.mjs index fe56b98..d5c194b 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -1,43 +1,27 @@ import fetch from "node-fetch"; import CookieJar from "./cookie-jar"; import Cookie from "./cookie"; -import urlParser from "url"; async function cookieFetch(cookieJars, url, options) { let cookies = ""; - const domains = - urlParser - .parse(url) - .hostname - .split(".") - .map((_, i, a) => a.slice(i).join(".")) - .slice(0, -1); - const addValidFromJar = jar => - domains - .map(d => [...jar.iterValidForRequest(d, url)]) + const addValidFromJars = jars => + jars + .map(jar => [...jar.cookiesValidForRequest(url)]) .reduce((a, b) => [...a, ...b]) - .filter((v, i, a) => a.slice(0, i).every(c => c.name !== v.name)) //unique + .filter((v, i, a) => a.slice(0, i).every(c => c.name !== v.name)) // filter cookies with duplicate names .forEach(c => cookies += c.serialize() + "; "); if(cookieJars) { - if(Array.isArray(cookieJars) && cookieJars.every(c => c instanceof CookieJar)) { - cookieJars.forEach(jar => { - if(!jar.flags.includes("r")) - return; - addValidFromJar(jar); - }); - } + if(Array.isArray(cookieJars) && cookieJars.every(c => c instanceof CookieJar)) + addValidFromJars(cookieJars.filter(jar => jar.flags.includes("r"))); else if(cookieJars instanceof CookieJar && cookieJars.flags.includes("r")) - addValidFromJar(cookieJars); + addValidFromJars([cookieJars]); else throw new TypeError("First paramter is neither a cookie jar nor an array of cookie jars!"); } - if(cookies.length !== 0) { - if(!options) { - options = { - headers: {} - }; - } - else if(!options.headers) + if(cookies) { + if(!options) + options = {}; + if(!options.headers) options.headers = {}; options.headers.cookie = cookies.slice(0, -2); } @@ -46,13 +30,11 @@ async function cookieFetch(cookieJars, url, options) { cookies = result.headers[Object.getOwnPropertySymbols(result.headers)[0]]["set-cookie"]; if(cookies && cookieJars) { if(Array.isArray(cookieJars)) { - cookieJars.forEach(jar => { - if(!jar.flags.includes("w")) - return; - cookies.forEach(c => jar.addCookie(c, url)); - }); + cookieJars + .filter(jar => jar.flags.includes("w")) + .forEach(jar => cookies.forEach(c => jar.addCookie(c, url))); } - else if(cookieJars.flags.includes("w")) { + else if(cookieJars instanceof CookieJar && cookieJars.flags.includes("w")) { cookies.forEach(c => cookieJars.addCookie(c, url)); } }