initial commit
This commit is contained in:
commit
d92f8d3564
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
node_modules
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019 jkhsjdhjs
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
13
package-lock.json
generated
Normal file
13
package-lock.json
generated
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"name": "node-fetch-cookies",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"requires": true,
|
||||||
|
"dependencies": {
|
||||||
|
"node-fetch": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
package.json
Normal file
31
package.json
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"name": "node-fetch-cookies",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "node-fetch wrapper that adds support for cookie-jars",
|
||||||
|
"main": "src/index.mjs",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/jkhsjdhjs/node-fetch-cookies.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"cookie",
|
||||||
|
"cookie-jar",
|
||||||
|
"node-fetch",
|
||||||
|
"fetch"
|
||||||
|
],
|
||||||
|
"author": "jkhsjdhjs",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/jkhsjdhjs/node-fetch-cookies/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/jkhsjdhjs/node-fetch-cookies#readme",
|
||||||
|
"dependencies": {
|
||||||
|
"node-fetch": "^2.3.0"
|
||||||
|
}
|
||||||
|
}
|
43
src/cookie-jar.mjs
Normal file
43
src/cookie-jar.mjs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import fs from "fs";
|
||||||
|
import Cookie from "./cookie";
|
||||||
|
|
||||||
|
export default class CookieJar {
|
||||||
|
constructor(flags, file, cookies) {
|
||||||
|
this.cookies = new Map();
|
||||||
|
this.file = file;
|
||||||
|
this.flags = flags;
|
||||||
|
if(typeof this.flags !== "string")
|
||||||
|
throw new TypeError("First parameter is not a string!");
|
||||||
|
if(typeof this.file !== "string")
|
||||||
|
throw new TypeError("Second parameter is not a string!");
|
||||||
|
if(Array.isArray(cookies)) {
|
||||||
|
if(!cookies.every(c => c instanceof Cookie))
|
||||||
|
throw new TypeError("Third parameter is not an array of cookies!");
|
||||||
|
else
|
||||||
|
cookies.forEach(cookie => this.cookies.set(cookie.name, cookie));
|
||||||
|
}
|
||||||
|
else if(cookies instanceof Cookie)
|
||||||
|
this.cookies.set(cookies.name, cookies);
|
||||||
|
else
|
||||||
|
throw new TypeError("Third parameter is neither an array nor a cookie!");
|
||||||
|
if(this.cookies.size === 0 && this.file.length !== 0 && fs.existsSync(this.file))
|
||||||
|
this.cookies = new Map(JSON.parse(fs.readFileSync(this.file)));
|
||||||
|
}
|
||||||
|
addCookie(c) {
|
||||||
|
if(typeof c === "string")
|
||||||
|
c = new Cookie(c);
|
||||||
|
this.cookies.set(c.name, c);
|
||||||
|
}
|
||||||
|
forEach(callback) {
|
||||||
|
this.cookies.forEach(callback);
|
||||||
|
}
|
||||||
|
save() {
|
||||||
|
// only save cookies that haven't expired
|
||||||
|
let cookiesToSave = new Map();
|
||||||
|
this.forEach(cookie => {
|
||||||
|
if(cookie.expiry && cookie.expiry > new Date())
|
||||||
|
cookiesToSave.set(cookie.name, cookie);
|
||||||
|
});
|
||||||
|
fs.writeFileSync(this.file, JSON.stringify([...cookiesToSave]));
|
||||||
|
}
|
||||||
|
};
|
100
src/cookie.mjs
Normal file
100
src/cookie.mjs
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
import urlParser from "url";
|
||||||
|
|
||||||
|
const validateHostname = (cookieHostname, requestHostname, subdomains) => {
|
||||||
|
if(requestHostname === cookieHostname || (subdomains && requestHostname.endsWith("." + cookieHostname)))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const validatePath = (cookiePath, requestPath) => {
|
||||||
|
if(cookiePath.endsWith("/"))
|
||||||
|
cookiePath = cookiePath.slice(0, -1);
|
||||||
|
if(requestPath.endsWith("/"))
|
||||||
|
requestPath = requestPath.slice(0, -1);
|
||||||
|
return (requestPath + "/").startsWith(cookiePath + "/");
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class Cookie {
|
||||||
|
constructor(str, url) {
|
||||||
|
if(typeof str !== "string")
|
||||||
|
throw new TypeError("Input not a string");
|
||||||
|
|
||||||
|
const splitted = str.split("; ");
|
||||||
|
[this.name, this.value] = splitted[0].split("=");
|
||||||
|
if(this.value.startsWith("\"") && this.value.endsWith("\""))
|
||||||
|
this.value = this.value.slice(1, -1);
|
||||||
|
this.value = this.value;
|
||||||
|
|
||||||
|
for(let i = 1; i < splitted.length; i++) {
|
||||||
|
let [k, v] = splitted[i].split("=");
|
||||||
|
k = k.toLowerCase();
|
||||||
|
if(v) {
|
||||||
|
if(k === "expires") {
|
||||||
|
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{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 + "\"!");
|
||||||
|
}
|
||||||
|
else if(k === "max-age") {
|
||||||
|
const seconds = parseInt(v);
|
||||||
|
if(seconds.toString() !== v)
|
||||||
|
throw new TypeError("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);
|
||||||
|
this.domain = v;
|
||||||
|
this.subdomains = true;
|
||||||
|
}
|
||||||
|
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!");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if(k === "secure")
|
||||||
|
this.secure = true;
|
||||||
|
else if(k === "httponly") // only relevant for browsers
|
||||||
|
continue;
|
||||||
|
else
|
||||||
|
throw new TypeError("Invalid key \"" + k + "\" specified!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!this.domain) {
|
||||||
|
this.domain = urlParser.parse(url).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) {
|
||||||
|
return Object.assign(Object.create(this.prototype), obj);
|
||||||
|
}
|
||||||
|
serialize() {
|
||||||
|
return this.name + "=" + this.value;
|
||||||
|
}
|
||||||
|
isValidForRequest(url) {
|
||||||
|
if(this.expiry && this.expiry > new Date())
|
||||||
|
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))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
40
src/index.mjs
Normal file
40
src/index.mjs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import fetch from "node-fetch";
|
||||||
|
import CookieJar from "./cookie-jar";
|
||||||
|
import Cookie from "./cookie";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
fetch: async (url, options, cookieJars) => {
|
||||||
|
let cookies = "";
|
||||||
|
if(Array.isArray(cookieJars) && cookieJars.every(c => c instanceof CookieJar)) {
|
||||||
|
cookieJars.forEach(jar => {
|
||||||
|
if(!jar.flags.includes("r"))
|
||||||
|
return;
|
||||||
|
jar.forEach(c => c.isValidForRequest(url) && (cookies += c.serialize() + "; "));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if(cookieJars instanceof CookieJar && cookieJars.flags.includes("r")) {
|
||||||
|
cookieJars.forEach(c => c.isValidForRequest(url) && (cookies += c.serialize() + "; "));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw new TypeError("Third paramter is neither a cookie jar nor an array of cookie jars!");
|
||||||
|
if(cookies.length !== 0)
|
||||||
|
options.headers["Cookie"] = cookies.slice(0, -2);
|
||||||
|
const result = await fetch(url, options);
|
||||||
|
// i cannot use headers.get() here because it joins the cookies to a string
|
||||||
|
cookies = result.headers[Object.getOwnPropertySymbols(result.headers)[0]]["set-cookie"];
|
||||||
|
if(cookies) {
|
||||||
|
if(Array.isArray(cookieJars)) {
|
||||||
|
cookieJars.forEach(jar => {
|
||||||
|
if(!jar.flags.includes("w"))
|
||||||
|
return;
|
||||||
|
cookies.forEach(c => jar.addCookie(c));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if(cookieJars.flags.includes("w")) {
|
||||||
|
cookies.forEach(c => cookieJars.addCookie(c));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
CookieJar: CookieJar,
|
||||||
|
Cookie: Cookie
|
||||||
|
};
|
5
src/test.mjs
Normal file
5
src/test.mjs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import cookieFetch from "./index.mjs";
|
||||||
|
import fetch from "node-fetch";
|
||||||
|
|
||||||
|
//console.log(new cookieFetch());
|
||||||
|
fetch("https://google.de").then(x => console.log();
|
Loading…
Reference in New Issue
Block a user