replace fetch.mjs with flumm-fetch-cookies
This commit is contained in:
parent
e8d24689bb
commit
771b3af362
15
src/inc/fetch/cookie-jar.mjs
Normal file
15
src/inc/fetch/cookie-jar.mjs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import Cookie from "./cookie";
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
forEach(callback) {
|
||||||
|
this.cookies.forEach(callback);
|
||||||
|
}
|
||||||
|
};
|
109
src/inc/fetch/cookie.mjs
Normal file
109
src/inc/fetch/cookie.mjs
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
import urlParser from "url";
|
||||||
|
|
||||||
|
const validateHostname = (cookieHostname, requestHostname, subdomains) => {
|
||||||
|
cookieHostname = cookieHostname.toLowerCase();
|
||||||
|
requestHostname = requestHostname.toLowerCase();
|
||||||
|
if(requestHostname === cookieHostname || (subdomains && requestHostname.endsWith("." + cookieHostname)))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const validatePath = (cookiePath, requestPath) => {
|
||||||
|
cookiePath = cookiePath.toLowerCase();
|
||||||
|
requestPath = requestPath.toLowerCase();
|
||||||
|
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);
|
||||||
|
|
||||||
|
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{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 + "\"!");
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
let c = Object.assign(Object.create(this.prototype), obj);
|
||||||
|
if(c.expiry && typeof c.expiry === "string")
|
||||||
|
c.expiry = new Date(c.expiry);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
serialize() {
|
||||||
|
return this.name + "=" + this.value;
|
||||||
|
}
|
||||||
|
hasExpired() {
|
||||||
|
return this.expiry && this.expiry < new Date();
|
||||||
|
}
|
||||||
|
isValidForRequest(url) {
|
||||||
|
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))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
|
@ -20,10 +20,10 @@ export default (a, options = {}, link = url.parse(a), body = "") => new Promise(
|
||||||
if(options.method === "POST") {
|
if(options.method === "POST") {
|
||||||
body = querystring.stringify(options.body);
|
body = querystring.stringify(options.body);
|
||||||
delete options.body;
|
delete options.body;
|
||||||
options.headers = {
|
options.headers = {...options.headers, ...{
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
"Content-Length": Buffer.byteLength(body)
|
"Content-Length": Buffer.byteLength(body)
|
||||||
};
|
}};
|
||||||
}
|
}
|
||||||
(link.protocol === "https:"?https:http).request(options, res => resolve({
|
(link.protocol === "https:"?https:http).request(options, res => resolve({
|
||||||
body: res,
|
body: res,
|
28
src/inc/fetch/index.mjs
Normal file
28
src/inc/fetch/index.mjs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
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)
|
||||||
|
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));
|
||||||
|
return result;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user