generators are awesome

rewrite internal cookie storage
fix incorrect splitting of key value pairs when parsing serialized cookies
minor version bump
This commit is contained in:
jkhsjdhjs 2019-08-13 22:19:25 +02:00
parent 4a84e2ad7c
commit 14a874f208
Signed by: jkhsjdhjs
GPG Key ID: BAC6ADBAB7D576CC
6 changed files with 91 additions and 31 deletions

View File

@ -43,11 +43,29 @@ A class that stores cookies.
#### addCookie(cookie[, url]) #### addCookie(cookie[, url])
Adds a cookie to the jar. Adds a cookie to the jar.
- `cookie` A [Cookie](#class-cookie) instance to add to the cookie jar. Alternatively this can also be a string, for example the string received from a website. In this case `url` should be specified. - `cookie` A [Cookie](#class-cookie) instance to add to the cookie jar. Alternatively this can also be a string, for example a serialized cookie received from a website. In this case `url` should be specified.
- `url` The url a cookie has been received from. - `url` The url a cookie has been received from.
#### forEach(callback) #### addFromFile(file)
Just a wrapper for `CookieJar.cookies.forEach(callback)`. Reads a cookie jar from the disk and adds the contained cookies.
#### domains()
Returns an array of the domains currently stored cookies for.
#### *iterValidForRequest(domain, url)
Returns an iterator over all cookies valid for a request to `domain` and `url`.
#### *iterValid()
Returns an iterator over all valid (non-expired) cookies.
#### *iterAll()
Returns an iterator over all cookies currently stored.
#### *iter(domain)
Returns an iterator over all cookies for a specific domain.
#### deleteExpired()
Removes all expired cookies from the jar.
#### save() #### save()
Saves the cookie jar to disk. Only non-expired cookies are saved. Saves the cookie jar to disk. Only non-expired cookies are saved.

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "node-fetch-cookies", "name": "node-fetch-cookies",
"version": "1.0.6", "version": "1.1.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "node-fetch-cookies", "name": "node-fetch-cookies",
"version": "1.0.6", "version": "1.1.0",
"description": "node-fetch wrapper that adds support for cookie-jars", "description": "node-fetch wrapper that adds support for cookie-jars",
"main": "src/index.mjs", "main": "src/index.mjs",
"engines": { "engines": {

View File

@ -10,36 +10,63 @@ export default class CookieJar {
throw new TypeError("First parameter is not a string!"); throw new TypeError("First parameter is not a string!");
if(this.file && typeof this.file !== "string") if(this.file && typeof this.file !== "string")
throw new TypeError("Second parameter is not a string!"); throw new TypeError("Second parameter is not a string!");
if(this.file && fs.existsSync(this.file))
this.addFromFile(this.file);
else
this.cookies = new Map();
if(Array.isArray(cookies)) { if(Array.isArray(cookies)) {
if(!cookies.every(c => c instanceof Cookie)) if(!cookies.every(c => c instanceof Cookie))
throw new TypeError("Third parameter is not an array of cookies!"); throw new TypeError("Third parameter is not an array of cookies!");
else else
cookies.forEach(cookie => this.cookies.set(cookie.name, cookie)); cookies.forEach(cookie => this.addCookie(cookie));
} }
else if(cookies instanceof Cookie) else if(cookies instanceof Cookie)
this.cookies.set(cookies.name, cookies); this.addCookie(cookies);
else if(cookies) else if(cookies)
throw new TypeError("Third parameter is neither an array nor a cookie!"); throw new TypeError("Third parameter is neither an array nor a cookie!");
if(this.file && this.cookies.size === 0 && this.file.length !== 0 && fs.existsSync(this.file))
this.cookies = new Map(JSON.parse(fs.readFileSync(this.file)).map(([k, v]) => [k, Cookie.fromObject(v)]));
} }
addCookie(c, fromURL) { addCookie(c, fromURL) {
if(typeof c === "string") if(typeof c === "string")
c = new Cookie(c, fromURL); c = new Cookie(c, fromURL);
this.cookies.set(c.name, c); 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);
} }
forEach(callback) { addFromFile(file) {
this.cookies.forEach(callback); JSON.parse(fs.readFileSync(this.file)).forEach(c => this.addCookie(Cookie.fromObject(c)));
}
domains() {
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) {
for(const cookie of (this.cookies.get(domain) || []).values())
yield cookie;
}
deleteExpired() {
const filteredCookies = [...this.iterValid()];
this.cookies = new Map();
filteredCookies.forEach(c => this.addCookie(c));
} }
save() { save() {
if(typeof this.file !== "string") if(typeof this.file !== "string")
throw new Error("No file has been specified for this cookie jar!"); throw new Error("No file has been specified for this cookie jar!");
// only save cookies that haven't expired // only save cookies that haven't expired
let cookiesToSave = new Map(); fs.writeFileSync(this.file, JSON.stringify([...this.iterValid()]));
this.forEach(cookie => {
if(!cookie.hasExpired())
cookiesToSave.set(cookie.name, cookie);
});
fs.writeFileSync(this.file, JSON.stringify([...cookiesToSave]));
} }
}; };

View File

@ -18,18 +18,27 @@ const validatePath = (cookiePath, requestPath) => {
return (requestPath + "/").startsWith(cookiePath + "/"); 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 { export default class Cookie {
constructor(str, url) { constructor(str, url) {
if(typeof str !== "string") if(typeof str !== "string")
throw new TypeError("Input not a string"); throw new TypeError("Input not a string");
const splitted = str.split("; "); const splitted = str.split("; ");
[this.name, this.value] = splitted[0].split("="); [this.name, this.value] = splitN(splitted[0], "=", 1);
if(this.value.startsWith("\"") && this.value.endsWith("\"")) if(this.value.startsWith("\"") && this.value.endsWith("\""))
this.value = this.value.slice(1, -1); this.value = this.value.slice(1, -1);
for(let i = 1; i < splitted.length; i++) { for(let i = 1; i < splitted.length; i++) {
let [k, v] = splitted[i].split("="); let [k, v] = splitN(splitted[i], "=", 1);
k = k.toLowerCase(); k = k.toLowerCase();
if(v) { if(v) {
if(k === "expires") { if(k === "expires") {
@ -40,7 +49,7 @@ export default class Cookie {
throw new TypeError("Invalid value for Expires \"" + v + "\"!"); throw new TypeError("Invalid value for Expires \"" + v + "\"!");
} }
else if(k === "max-age") { else if(k === "max-age") {
const seconds = parseInt(v); const seconds = ~~+v;
if(seconds.toString() !== v) if(seconds.toString() !== v)
throw new TypeError("Invalid value for Max-Age \"" + v + "\"!"); throw new TypeError("Invalid value for Max-Age \"" + v + "\"!");
this.expiry = new Date(); this.expiry = new Date();

View File

@ -1,26 +1,32 @@
import fetch from "node-fetch"; import fetch from "node-fetch";
import CookieJar from "./cookie-jar"; import CookieJar from "./cookie-jar";
import Cookie from "./cookie"; import Cookie from "./cookie";
import urlParser from "url";
async function cookieFetch(cookieJars, url, options) { async function cookieFetch(cookieJars, url, options) {
let cookies = ""; 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)])
.reduce((a, b) => [...a, ...b])
.forEach(c => cookies += c.serialize() + "; ");
if(cookieJars) { if(cookieJars) {
if(Array.isArray(cookieJars) && cookieJars.every(c => c instanceof CookieJar)) { if(Array.isArray(cookieJars) && cookieJars.every(c => c instanceof CookieJar)) {
cookieJars.forEach(jar => { cookieJars.forEach(jar => {
if(!jar.flags.includes("r")) if(!jar.flags.includes("r"))
return; return;
jar.forEach(c => { addValidFromJar(jar);
if(c.isValidForRequest(url))
cookies += c.serialize() + "; ";
});
});
}
else if(cookieJars instanceof CookieJar && cookieJars.flags.includes("r")) {
cookieJars.forEach(c => {
if(c.isValidForRequest(url))
cookies += c.serialize() + "; ";
}); });
} }
else if(cookieJars instanceof CookieJar && cookieJars.flags.includes("r"))
addValidFromJar(cookieJars);
else else
throw new TypeError("First paramter is neither a cookie jar nor an array of cookie jars!"); throw new TypeError("First paramter is neither a cookie jar nor an array of cookie jars!");
} }