merge latest node-fetch-cookies version
This commit is contained in:
		
							
								
								
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "node-fetch-cookies",
 | 
			
		||||
  "version": "1.0.6",
 | 
			
		||||
  "name": "flumm-fetch-cookies",
 | 
			
		||||
  "version": "1.1.0",
 | 
			
		||||
  "lockfileVersion": 1
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "flumm-fetch-cookies",
 | 
			
		||||
  "version": "1.0.6",
 | 
			
		||||
  "version": "1.1.0",
 | 
			
		||||
  "description": "flumm-fetch wrapper that adds support for cookie-jars",
 | 
			
		||||
  "main": "src/index.mjs",
 | 
			
		||||
  "engines": {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,69 @@
 | 
			
		||||
import Cookie from "./cookie";
 | 
			
		||||
import url from "url";
 | 
			
		||||
 | 
			
		||||
export default class CookieJar {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        this.cookies = new Map();
 | 
			
		||||
    }
 | 
			
		||||
    addCookie(c, fromURL) {
 | 
			
		||||
        if(typeof c === "string")
 | 
			
		||||
            c = new Cookie(c, fromURL);
 | 
			
		||||
        this.cookies.set(c.name, c);
 | 
			
		||||
        if(typeof c === "string") {
 | 
			
		||||
            try {
 | 
			
		||||
                c = new Cookie(c, fromURL);
 | 
			
		||||
            }
 | 
			
		||||
            catch(error) {
 | 
			
		||||
                if(error.name === "CookieParseError") {
 | 
			
		||||
                    console.warn("Ignored cookie: " + c);
 | 
			
		||||
                    console.warn("Reason: " + error.message);
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                    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);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    forEach(callback) {
 | 
			
		||||
        this.cookies.forEach(callback);
 | 
			
		||||
    domains() {
 | 
			
		||||
        return this.cookies.keys();
 | 
			
		||||
    }
 | 
			
		||||
    *cookiesDomain(domain) {
 | 
			
		||||
        for(const cookie of (this.cookies.get(domain) || []).values())
 | 
			
		||||
            yield cookie;
 | 
			
		||||
    }
 | 
			
		||||
    *cookiesValid(withSession) {
 | 
			
		||||
        for(const cookie of this.cookiesAll())
 | 
			
		||||
            if(!cookie.hasExpired(!withSession))
 | 
			
		||||
                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(sessionEnded) {
 | 
			
		||||
        const validCookies = [...this.cookiesValid(!sessionEnded)];
 | 
			
		||||
        this.cookies = new Map();
 | 
			
		||||
        validCookies.forEach(c => this.addCookie(c));
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,11 @@
 | 
			
		||||
import urlParser from "url";
 | 
			
		||||
import url from "url";
 | 
			
		||||
 | 
			
		||||
class CookieParseError extends Error {
 | 
			
		||||
    constructor(...args) {
 | 
			
		||||
        super(...args);
 | 
			
		||||
        this.name = "CookieParseError";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const validateHostname = (cookieHostname, requestHostname, subdomains) => {
 | 
			
		||||
    cookieHostname = cookieHostname.toLowerCase();
 | 
			
		||||
@@ -9,8 +16,8 @@ const validateHostname = (cookieHostname, requestHostname, subdomains) => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const validatePath = (cookiePath, requestPath) => {
 | 
			
		||||
    cookiePath = cookiePath.toLowerCase();
 | 
			
		||||
    requestPath = requestPath.toLowerCase();
 | 
			
		||||
    cookiePath = decodeURIComponent(cookiePath).toLowerCase();
 | 
			
		||||
    requestPath = decodeURIComponent(requestPath).toLowerCase();
 | 
			
		||||
    if(cookiePath.endsWith("/"))
 | 
			
		||||
        cookiePath = cookiePath.slice(0, -1);
 | 
			
		||||
    if(requestPath.endsWith("/"))
 | 
			
		||||
@@ -18,18 +25,31 @@ const validatePath = (cookiePath, requestPath) => {
 | 
			
		||||
    return (requestPath + "/").startsWith(cookiePath + "/");
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const splitN = (str, sep, n) => {
 | 
			
		||||
    const splitted = str.split(sep);
 | 
			
		||||
    if(n < splitted.length - 1) {
 | 
			
		||||
        splitted[n] = splitted.slice(n).join(sep);
 | 
			
		||||
        splitted.splice(n + 1);
 | 
			
		||||
    }
 | 
			
		||||
    return splitted;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
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] = splitted[0].split("=");
 | 
			
		||||
        [this.name, this.value] = splitN(splitted[0], "=", 1);
 | 
			
		||||
        if(!this.name)
 | 
			
		||||
            throw new CookieParseError("Invalid cookie name \"" + this.name + "\"");
 | 
			
		||||
        if(this.value.startsWith("\"") && this.value.endsWith("\""))
 | 
			
		||||
            this.value = this.value.slice(1, -1);
 | 
			
		||||
 | 
			
		||||
        const parsedURL = url.parse(requestURL);
 | 
			
		||||
 | 
			
		||||
        for(let i = 1; i < splitted.length; i++) {
 | 
			
		||||
            let [k, v] = splitted[i].split("=");
 | 
			
		||||
            let [k, v] = splitN(splitted[i], "=", 1);
 | 
			
		||||
            k = k.toLowerCase();
 | 
			
		||||
            if(v) {
 | 
			
		||||
                if(k === "expires") {
 | 
			
		||||
@@ -37,28 +57,29 @@ 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 CookieParseError("Invalid value for Expires \"" + v + "\"!");
 | 
			
		||||
                }
 | 
			
		||||
                else if(k === "max-age") {
 | 
			
		||||
                    const seconds = parseInt(v);
 | 
			
		||||
                    const seconds = ~~+v;
 | 
			
		||||
                    if(seconds.toString() !== v)
 | 
			
		||||
                        throw new TypeError("Invalid value for Max-Age \"" + v + "\"!");
 | 
			
		||||
                        throw new CookieParseError("Invalid value for Max-Age \"" + v + "\"!");
 | 
			
		||||
                    this.expiry = new Date();
 | 
			
		||||
                    this.expiry.setSeconds(this.expiry.getSeconds() + seconds);
 | 
			
		||||
                }
 | 
			
		||||
                else if(k === "domain") {
 | 
			
		||||
                    if(v.startsWith("."))
 | 
			
		||||
                        v = v.substring(1);
 | 
			
		||||
                    if(!validateHostname(parsedURL.hostname, v, true))
 | 
			
		||||
                        throw new CookieParseError("Invalid value for Domain \"" + v + "\": cookie was received from \"" + parsedURL.hostname + "\"!");
 | 
			
		||||
                    this.domain = v;
 | 
			
		||||
                    this.subdomains = true;
 | 
			
		||||
                }
 | 
			
		||||
                else if(k === "path") {
 | 
			
		||||
                else if(k === "path")
 | 
			
		||||
                    this.path = v;
 | 
			
		||||
                }
 | 
			
		||||
                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 CookieParseError("Invalid key \"" + k + "\" with value \"" + v + "\" specified!");
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                if(k === "secure")
 | 
			
		||||
@@ -66,43 +87,41 @@ export default class Cookie {
 | 
			
		||||
                else if(k === "httponly") // only relevant for browsers
 | 
			
		||||
                    continue;
 | 
			
		||||
                else
 | 
			
		||||
                    throw new TypeError("Invalid key \"" + k + "\" specified!");
 | 
			
		||||
                    throw new CookieParseError("Invalid key \"" + k + "\" specified!");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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 !== "/")))
 | 
			
		||||
            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
 | 
			
		||||
        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 \"/\"!");
 | 
			
		||||
    }
 | 
			
		||||
    static fromObject(obj) {
 | 
			
		||||
        let c = Object.assign(Object.create(this.prototype), obj);
 | 
			
		||||
        if(c.expiry && typeof c.expiry === "string")
 | 
			
		||||
            c.expiry = new Date(c.expiry);
 | 
			
		||||
        return c;
 | 
			
		||||
        if(!this.secure)
 | 
			
		||||
            this.secure = false;
 | 
			
		||||
        if(!this.expiry)
 | 
			
		||||
            this.expiry = null;
 | 
			
		||||
    }
 | 
			
		||||
    serialize() {
 | 
			
		||||
        return this.name + "=" + this.value;
 | 
			
		||||
    }
 | 
			
		||||
    hasExpired() {
 | 
			
		||||
        return this.expiry && this.expiry < new Date();
 | 
			
		||||
    hasExpired(sessionEnded) {
 | 
			
		||||
        return sessionEnded && this.expiry === null || this.expiry < new Date();
 | 
			
		||||
    }
 | 
			
		||||
    isValidForRequest(url) {
 | 
			
		||||
        if(this.hasExpired())
 | 
			
		||||
    isValidForRequest(requestURL) {
 | 
			
		||||
        if(this.hasExpired(false))
 | 
			
		||||
            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;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,28 +1,29 @@
 | 
			
		||||
import fetch from "./fetch";
 | 
			
		||||
import CookieJar from "./cookie-jar";
 | 
			
		||||
import Cookie from "./cookie";
 | 
			
		||||
 | 
			
		||||
const cookieJar = new CookieJar();
 | 
			
		||||
 | 
			
		||||
export default async function cookieFetch(url, options) {
 | 
			
		||||
    let cookies = "";
 | 
			
		||||
    cookieJar.forEach(c => {
 | 
			
		||||
        if(c.isValidForRequest(url))
 | 
			
		||||
            cookies += c.serialize() + "; ";
 | 
			
		||||
    });
 | 
			
		||||
    if(cookies.length !== 0) {
 | 
			
		||||
        if(!options) {
 | 
			
		||||
            options = {
 | 
			
		||||
                headers: {}
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
        else if(!options.headers)
 | 
			
		||||
    [...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) {
 | 
			
		||||
        if(!options)
 | 
			
		||||
            options = {};
 | 
			
		||||
        if(!options.headers)
 | 
			
		||||
            options.headers = {};
 | 
			
		||||
        options.headers.cookie = cookies.slice(0, -2);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const result = await fetch(url, options);
 | 
			
		||||
    cookies = result.headers["set-cookie"];
 | 
			
		||||
    if(cookies)
 | 
			
		||||
        cookies.forEach(c => cookieJar.addCookie(c, url));
 | 
			
		||||
 | 
			
		||||
    cookies = result.headers["set-cookie"] || [];
 | 
			
		||||
    cookies.forEach(c => cookieJar.addCookie(c, url));
 | 
			
		||||
 | 
			
		||||
    // delete expired cookies after each request
 | 
			
		||||
    cookieJar.deleteExpired(false);
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user