merge latest node-fetch-cookies version

This commit is contained in:
2019-11-29 01:51:54 +01:00
10 changed files with 540 additions and 59 deletions

View File

@ -1,30 +1,46 @@
import Cookie from "./cookie.mjs";
import {promises as fs} from "fs";
import url from "url";
import Cookie from "./cookie.mjs";
import {paramError, CookieParseError} from "./errors.mjs";
export default class CookieJar {
constructor() {
constructor(file, flags = "rw", cookies) {
this.flags = flags;
this.file = file;
this.cookies = new Map();
if(typeof this.flags !== "string")
throw paramError("First", "flags", "new CookieJar()", "string");
if(this.file && typeof this.file !== "string")
throw paramError("Second", "file", "new CookieJar()", "string");
if(Array.isArray(cookies)) {
if(!cookies.every(c => c instanceof Cookie))
throw paramError("Third", "cookies", "new CookieJar()", "[Cookie]");
cookies.forEach(cookie => this.addCookie(cookie));
}
else if(cookies instanceof Cookie)
this.addCookie(cookies);
else if(cookies)
throw paramError("Third", "cookies", "new CookieJar()", ["[Cookie]", "Cookie"]);
}
addCookie(c, fromURL) {
if(typeof c === "string") {
addCookie(cookie, fromURL) {
if(typeof cookie === "string") {
try {
c = new Cookie(c, fromURL);
cookie = new Cookie(cookie, fromURL);
}
catch(error) {
if(error.name === "CookieParseError") {
console.warn("Ignored cookie: " + c);
if(error instanceof CookieParseError) {
console.warn("Ignored cookie: " + cookie);
console.warn("Reason: " + error.message);
return false;
}
else
throw error;
throw error;
}
}
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());
this.cookies.get(c.domain).set(c.name, c);
else if(!(cookie instanceof Cookie))
throw paramError("First", "cookie", "CookieJar.addCookie()", ["string", "Cookie"]);
if(!this.cookies.get(cookie.domain))
this.cookies.set(cookie.domain, new Map());
this.cookies.get(cookie.domain).set(cookie.name, cookie);
return true;
}
domains() {
@ -44,7 +60,7 @@ export default class CookieJar {
yield* this.cookiesDomain(domain);
}
*cookiesValidForRequest(requestURL) {
const namesYielded = [],
const namesYielded = new Set(),
domains = url
.parse(requestURL)
.hostname
@ -54,8 +70,8 @@ export default class CookieJar {
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);
&& !namesYielded.has(cookie.name)) {
namesYielded.add(cookie.name);
yield cookie;
}
}
@ -66,4 +82,15 @@ export default class CookieJar {
this.cookies = new Map();
validCookies.forEach(c => this.addCookie(c));
}
async load(file = this.file) {
if(typeof file !== "string")
throw new Error("No file has been specified for this cookie jar!");
JSON.parse(await fs.readFile(file)).forEach(c => this.addCookie(Cookie.fromObject(c)));
}
async save(file = this.file) {
if(typeof file !== "string")
throw new Error("No file has been specified for this cookie jar!");
// only save cookies that haven't expired
await fs.writeFile(this.file, JSON.stringify([...this.cookiesValid(false)]));
}
};

View File

@ -1,11 +1,5 @@
import url from "url";
class CookieParseError extends Error {
constructor(...args) {
super(...args);
this.name = "CookieParseError";
}
}
import {paramError, CookieParseError} from "./errors.mjs";
const validateHostname = (cookieHostname, requestHostname, subdomains) => {
cookieHostname = cookieHostname.toLowerCase();
@ -37,12 +31,17 @@ const splitN = (str, sep, n) => {
export default class Cookie {
constructor(str, requestURL) {
if(typeof str !== "string")
throw new TypeError("First parameter is not a string!");
throw paramError("First", "str", "new Cookie()", "string");
if(typeof requestURL !== "string")
throw paramError("Second", "requestURL", "new Cookie()", "string");
// check if url is valid
new url.URL(requestURL);
const splitted = str.split("; ");
[this.name, this.value] = splitN(splitted[0], "=", 1);
if(!this.name)
throw new CookieParseError("Invalid cookie name \"" + this.name + "\"");
throw new CookieParseError("Invalid cookie name \"" + this.name + "\"!");
if(this.value.startsWith("\"") && this.value.endsWith("\""))
this.value = this.value.slice(1, -1);
@ -56,7 +55,8 @@ export default class Cookie {
if(this.expiry) // max-age has precedence over expires
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")
|| (this.expiry = new Date(v)).toString() === "Invalid Date"
|| this.expiry.getTime() < 0)
throw new CookieParseError("Invalid value for Expires \"" + v + "\"!");
}
else if(k === "max-age") {
@ -93,7 +93,7 @@ export default class Cookie {
if(this.name.toLowerCase().startsWith("__secure-") && (!this.secure || parsedURL.protocol !== "https:"))
throw new CookieParseError("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 || (this.path && this.path !== "/")))
if(this.name.toLowerCase().startsWith("__host-") && (!this.secure || parsedURL.protocol !== "https:" || this.domain || this.path !== "/"))
throw new CookieParseError("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

12
src/errors.mjs Normal file
View File

@ -0,0 +1,12 @@
export class CookieParseError extends Error {
constructor(...args) {
super(...args);
this.name = "CookieParseError";
}
};
export function paramError(position, paramName, functionName, validTypes) {
validTypes = [validTypes].flat().map(t => "\"" + t + "\"");
validTypes = validTypes.slice(0, -1).join(", ") + (validTypes.length > 1 ? " or " : "") + validTypes.slice(-1);
return new TypeError(`${position} parameter "${paramName}" passed to "${functionName}" is not of type ${validTypes}!`);
};

View File

@ -1,13 +1,12 @@
import fetch from "flumm-fetch";
import _fetch from "flumm-fetch";
import CookieJar from "./cookie-jar.mjs";
import Cookie from "./cookie.mjs";
const cookieJar = new CookieJar();
export default async function cookieFetch(url, options) {
export default async function fetch(url, options) {
let cookies = "";
[...cookieJar.cookiesValidForRequest(url)]
.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(cookies) {
@ -30,3 +29,4 @@ export default async function cookieFetch(url, options) {
}
export {cookieJar, CookieJar, Cookie};