add eslint + prettier

reformat code with prettier
This commit is contained in:
jkhsjdhjs 2020-06-17 15:04:32 +02:00
parent 120ce5c6c6
commit c4b04ed3dc
Signed by: jkhsjdhjs
GPG Key ID: BAC6ADBAB7D576CC
13 changed files with 1889 additions and 445 deletions

15
.eslintrc.json Normal file
View File

@ -0,0 +1,15 @@
{
"env": {
"es2020": true,
"node": true
},
"extends": ["eslint:recommended", "plugin:prettier/recommended"],
"plugins": ["prettier"],
"parserOptions": {
"ecmaVersion": 11,
"sourceType": "module"
},
"rules": {
"prettier/prettier": "error"
}
}

6
.prettierrc.json Normal file
View File

@ -0,0 +1,6 @@
{
"tabWidth": 4,
"trailingComma": "none",
"bracketSpacing": false,
"arrowParens": "avoid"
}

View File

@ -4,7 +4,7 @@ node_js:
- lts/* - lts/*
- 11.14.0 - 11.14.0
script: script:
- node --experimental-modules test/index.mjs - npm test
deploy: deploy:
provider: npm provider: npm

View File

@ -5,9 +5,10 @@ It supports reading/writing from/to a JSON cookie jar and keeps cookies in memor
### For upgrading from 1.2.x or below to 1.3.x or above, please read the [breaking API changes](#130-breaking-api-changes). ### For upgrading from 1.2.x or below to 1.3.x or above, please read the [breaking API changes](#130-breaking-api-changes).
## Usage Examples ## Usage Examples
### with file... ### with file...
```javascript ```javascript
import {fetch, CookieJar} from "node-fetch-cookies"; import {fetch, CookieJar} from "node-fetch-cookies";
@ -27,6 +28,7 @@ import {fetch, CookieJar} from "node-fetch-cookies";
``` ```
### ...or without ### ...or without
```javascript ```javascript
import {fetch, CookieJar} from "node-fetch-cookies"; import {fetch, CookieJar} from "node-fetch-cookies";
@ -40,31 +42,37 @@ import {fetch, CookieJar} from "node-fetch-cookies";
}); });
// do some requests you require login for // do some requests you require login for
response = await fetch(cookieJar, "https://example.com/api/admin/drop-all-databases"); response = await fetch(
cookieJar,
"https://example.com/api/admin/drop-all-databases"
);
// and optionally log out again // and optionally log out again
response = await fetch(cookieJar, "https://example.com/api/logout"); response = await fetch(cookieJar, "https://example.com/api/logout");
})(); })();
``` ```
## Documentation ## Documentation
### async fetch(cookieJars, url[, options]) ### async fetch(cookieJars, url[, options])
- `cookieJars` A [CookieJar](#class-cookiejar) instance, an array of CookieJar instances or null, if you don't want to send or store cookies. - `cookieJars` A [CookieJar](#class-cookiejar) instance, an array of CookieJar instances or null, if you don't want to send or store cookies.
- `url` and `options` as in https://github.com/bitinn/node-fetch#fetchurl-options - `url` and `options` as in https://github.com/bitinn/node-fetch#fetchurl-options
Returns a Promise resolving to a [Response](https://github.com/bitinn/node-fetch#class-response) instance on success. Returns a Promise resolving to a [Response](https://github.com/bitinn/node-fetch#class-response) instance on success.
### Class: CookieJar ### Class: CookieJar
A class that stores cookies. A class that stores cookies.
#### Properties #### Properties
- `flags` The read/write flags as specified below. - `flags` The read/write flags as specified below.
- `file` The path of the cookie jar on the disk. - `file` The path of the cookie jar on the disk.
- `cookies` A [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) mapping hostnames to maps, which map cookie names to the respective [Cookie](#class-cookie) instance. - `cookies` A [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) mapping hostnames to maps, which map cookie names to the respective [Cookie](#class-cookie) instance.
#### new CookieJar([file, flags = `rw`, cookies, cookieIgnoreCallback]) #### new CookieJar([file, flags = `rw`, cookies, cookieIgnoreCallback])
- `file` An optional string containing a relative or absolute path to the file on the disk to use. - `file` An optional string containing a relative or absolute path to the file on the disk to use.
- `flags` An optional string specifying whether cookies should be read and/or written from/to the jar when passing it as parameter to [fetch](#fetchcookiejar-url-options). Default: `rw` - `flags` An optional string specifying whether cookies should be read and/or written from/to the jar when passing it as parameter to [fetch](#fetchcookiejar-url-options). Default: `rw`
- `r`: only read from this jar - `r`: only read from this jar
@ -76,7 +84,9 @@ A class that stores cookies.
- `reason` A string containing the reason why the cookie has been ignored - `reason` A string containing the reason why the cookie has been ignored
#### addCookie(cookie[, fromURL]) #### addCookie(cookie[, fromURL])
Adds a cookie to the jar. Adds a cookie to the jar.
- `cookie` A [Cookie](#class-cookie) instance to add to the cookie jar. - `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. Alternatively this can also be a string, for example a serialized cookie received from a website.
In this case `fromURL` must be specified. In this case `fromURL` must be specified.
@ -86,37 +96,51 @@ Returns `true` if the cookie has been added successfully. Returns `false` otherw
If the parser throws a [CookieParseError](#class-cookieparseerror) it will be caught and a warning will be printed to console. If the parser throws a [CookieParseError](#class-cookieparseerror) it will be caught and a warning will be printed to console.
#### domains() #### domains()
Returns an iterator over all domains currently stored cookies for. Returns an iterator over all domains currently stored cookies for.
#### *cookiesDomain(domain) #### \*cookiesDomain(domain)
Returns an iterator over all cookies currently stored for `domain`. Returns an iterator over all cookies currently stored for `domain`.
#### *cookiesValid(withSession) #### \*cookiesValid(withSession)
Returns an iterator over all valid (non-expired) cookies. Returns an iterator over all valid (non-expired) cookies.
- `withSession`: A boolean. Iterator will include session cookies if set to `true`. - `withSession`: A boolean. Iterator will include session cookies if set to `true`.
#### *cookiesAll() #### \*cookiesAll()
Returns an iterator over all cookies currently stored. Returns an iterator over all cookies currently stored.
#### *cookiesValidForRequest(requestURL) #### \*cookiesValidForRequest(requestURL)
Returns an iterator over all cookies valid for a request to `url`. Returns an iterator over all cookies valid for a request to `url`.
#### deleteExpired(sessionEnded) #### deleteExpired(sessionEnded)
Removes all expired cookies from the jar. Removes all expired cookies from the jar.
- `sessionEnded`: A boolean. Also removes session cookies if set to `true`. - `sessionEnded`: A boolean. Also removes session cookies if set to `true`.
#### async load([file = this.file]) #### async load([file = this.file])
Reads cookies from `file` on the disk and adds the contained cookies. Reads cookies from `file` on the disk and adds the contained cookies.
- `file`: Path to the file where the cookies should be saved. Default: `this.file`, the file that has been passed to the constructor. - `file`: Path to the file where the cookies should be saved. Default: `this.file`, the file that has been passed to the constructor.
#### async save([file = this.file]) #### async save([file = this.file])
Saves the cookie jar to `file` on the disk. Only non-expired non-session cookies are saved. Saves the cookie jar to `file` on the disk. Only non-expired non-session cookies are saved.
- `file`: Path to the file where the cookies should be saved. Default: `this.file`, the file that has been passed to the constructor. - `file`: Path to the file where the cookies should be saved. Default: `this.file`, the file that has been passed to the constructor.
### Class: Cookie ### Class: Cookie
An abstract representation of a cookie. An abstract representation of a cookie.
#### Properties #### Properties
- `name` The identifier of the cookie. - `name` The identifier of the cookie.
- `value` The value of the cookie. - `value` The value of the cookie.
- `expiry` A [Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) object of the cookies expiry date or `null`, if the cookie expires with the session. - `expiry` A [Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) object of the cookies expiry date or `null`, if the cookie expires with the session.
@ -126,30 +150,38 @@ An abstract representation of a cookie.
- `subdomains` A boolean value specifying whether the cookie should be used for requests to subdomains of `domain` or not. - `subdomains` A boolean value specifying whether the cookie should be used for requests to subdomains of `domain` or not.
#### new Cookie(str, requestURL) #### new Cookie(str, requestURL)
Creates a cookie instance from the string representation of a cookie as send by a webserver. Creates a cookie instance from the string representation of a cookie as send by a webserver.
- `str` The string representation of a cookie. - `str` The string representation of a cookie.
- `url` The url the cookie has been received from. - `url` The url the cookie has been received from.
Will throw a `CookieParseError` if `str` couldn't be parsed. Will throw a `CookieParseError` if `str` couldn't be parsed.
#### static fromObject(obj) #### static fromObject(obj)
Creates a cookie instance from an already existing object with the same properties. Creates a cookie instance from an already existing object with the same properties.
#### serialize() #### serialize()
Serializes the cookie, transforming it to `name=value` so it can be used in requests. Serializes the cookie, transforming it to `name=value` so it can be used in requests.
#### hasExpired(sessionEnded) #### hasExpired(sessionEnded)
Returns whether the cookie has expired or not. Returns whether the cookie has expired or not.
- `sessionEnded`: A boolean that specifies whether the current session has ended, meaning if set to `true`, the function will return `true` for session cookies. - `sessionEnded`: A boolean that specifies whether the current session has ended, meaning if set to `true`, the function will return `true` for session cookies.
#### isValidForRequest(requestURL) #### isValidForRequest(requestURL)
Returns whether the cookie is valid for a request to `url`. Returns whether the cookie is valid for a request to `url`.
### Class: CookieParseError ### Class: CookieParseError
The Error that is thrown when the cookie parser located in the constructor of the [Cookie](#class-cookie) class is unable to parse the input. The Error that is thrown when the cookie parser located in the constructor of the [Cookie](#class-cookie) class is unable to parse the input.
## 1.3.0 Breaking API Changes ## 1.3.0 Breaking API Changes
- `new CookieJar(flags, file, cookies)` has been changed to `new CookieJar(file, flags = "rw", cookies)`. - `new CookieJar(flags, file, cookies)` has been changed to `new CookieJar(file, flags = "rw", cookies)`.
`new CookieJar("rw")` can now be written as `new CookieJar()`, `new CookieJar("rw", "jar.json")` can now be written as `new CookieJar("jar.json")`. `new CookieJar("rw")` can now be written as `new CookieJar()`, `new CookieJar("rw", "jar.json")` can now be written as `new CookieJar("jar.json")`.
This change has been introduced to simplify the usage of this library, since `rw` is used for `flags` in most cases anyways. This change has been introduced to simplify the usage of this library, since `rw` is used for `flags` in most cases anyways.
@ -159,6 +191,6 @@ The default value for `file` is the file passed to the constructor.
- `new CookieJar()` now doesn't load cookies from the specified file anymore. To do so, call `await CookieJar.load()` after creating the CookieJar. - `new CookieJar()` now doesn't load cookies from the specified file anymore. To do so, call `await CookieJar.load()` after creating the CookieJar.
**NOTE: `CookieJar.load()` will throw an error if the cookie jar doesn't exist or doesn't contain valid JSON!** **NOTE: `CookieJar.load()` will throw an error if the cookie jar doesn't exist or doesn't contain valid JSON!**
## License ## License
This project is licensed under the MIT license, see [LICENSE](LICENSE). This project is licensed under the MIT license, see [LICENSE](LICENSE).

1156
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@
"src/" "src/"
], ],
"scripts": { "scripts": {
"test": "node test/index.mjs" "test": "npx eslint --ext mjs . && npx prettier --check package.json package-lock.json .travis.yml .prettierrc.json .eslintrc.json README.md && node --experimental-modules test/index.mjs"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -30,5 +30,11 @@
"homepage": "https://github.com/jkhsjdhjs/node-fetch-cookies#readme", "homepage": "https://github.com/jkhsjdhjs/node-fetch-cookies#readme",
"dependencies": { "dependencies": {
"node-fetch": "^2.6.0" "node-fetch": "^2.6.0"
},
"devDependencies": {
"eslint": "^7.2.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-prettier": "^3.1.4",
"prettier": "2.0.5"
} }
} }

View File

@ -13,15 +13,26 @@ export default class CookieJar {
throw paramError("First", "flags", "new CookieJar()", "string"); throw paramError("First", "flags", "new CookieJar()", "string");
if (Array.isArray(cookies)) { if (Array.isArray(cookies)) {
if (!cookies.every(c => c instanceof Cookie)) if (!cookies.every(c => c instanceof Cookie))
throw paramError("Third", "cookies", "new CookieJar()", "[Cookie]"); throw paramError(
"Third",
"cookies",
"new CookieJar()",
"[Cookie]"
);
cookies.forEach(cookie => this.addCookie(cookie)); cookies.forEach(cookie => this.addCookie(cookie));
} } else if (cookies instanceof Cookie) this.addCookie(cookies);
else if(cookies instanceof Cookie)
this.addCookie(cookies);
else if (cookies) else if (cookies)
throw paramError("Third", "cookies", "new CookieJar()", ["[Cookie]", "Cookie"]); throw paramError("Third", "cookies", "new CookieJar()", [
"[Cookie]",
"Cookie"
]);
if (cookieIgnoreCallback && typeof cookieIgnoreCallback !== "function") if (cookieIgnoreCallback && typeof cookieIgnoreCallback !== "function")
throw paramError("Fourth", "cookieIgnoreCallback", "new CookieJar()", "function"); throw paramError(
"Fourth",
"cookieIgnoreCallback",
"new CookieJar()",
"function"
);
this.file = file; this.file = file;
this.flags = flags; this.flags = flags;
this.cookieIgnoreCallback = cookieIgnoreCallback; this.cookieIgnoreCallback = cookieIgnoreCallback;
@ -30,8 +41,7 @@ export default class CookieJar {
if (typeof cookie === "string") { if (typeof cookie === "string") {
try { try {
cookie = new Cookie(cookie, fromURL); cookie = new Cookie(cookie, fromURL);
} } catch (error) {
catch(error) {
if (error instanceof CookieParseError) { if (error instanceof CookieParseError) {
if (this.cookieIgnoreCallback) if (this.cookieIgnoreCallback)
this.cookieIgnoreCallback(cookie, error.message); this.cookieIgnoreCallback(cookie, error.message);
@ -39,9 +49,11 @@ export default class CookieJar {
} }
throw error; throw error;
} }
} } else if (!(cookie instanceof Cookie))
else if(!(cookie instanceof Cookie)) throw paramError("First", "cookie", "CookieJar.addCookie()", [
throw paramError("First", "cookie", "CookieJar.addCookie()", ["string", "Cookie"]); "string",
"Cookie"
]);
if (!this.cookies.get(cookie.domain)) if (!this.cookies.get(cookie.domain))
this.cookies.set(cookie.domain, new Map()); this.cookies.set(cookie.domain, new Map());
this.cookies.get(cookie.domain).set(cookie.name, cookie); this.cookies.get(cookie.domain).set(cookie.name, cookie);
@ -56,25 +68,24 @@ export default class CookieJar {
} }
*cookiesValid(withSession) { *cookiesValid(withSession) {
for (const cookie of this.cookiesAll()) for (const cookie of this.cookiesAll())
if(!cookie.hasExpired(!withSession)) if (!cookie.hasExpired(!withSession)) yield cookie;
yield cookie;
} }
*cookiesAll() { *cookiesAll() {
for(const domain of this.domains()) for (const domain of this.domains()) yield* this.cookiesDomain(domain);
yield* this.cookiesDomain(domain);
} }
*cookiesValidForRequest(requestURL) { *cookiesValidForRequest(requestURL) {
const namesYielded = new Set(), const namesYielded = new Set(),
domains = url domains = url
.parse(requestURL) .parse(requestURL)
.hostname .hostname.split(".")
.split(".")
.map((_, i, a) => a.slice(i).join(".")) .map((_, i, a) => a.slice(i).join("."))
.slice(0, -1); .slice(0, -1);
for (const domain of domains) { for (const domain of domains) {
for (const cookie of this.cookiesDomain(domain)) { for (const cookie of this.cookiesDomain(domain)) {
if(cookie.isValidForRequest(requestURL) if (
&& !namesYielded.has(cookie.name)) { cookie.isValidForRequest(requestURL) &&
!namesYielded.has(cookie.name)
) {
namesYielded.add(cookie.name); namesYielded.add(cookie.name);
yield cookie; yield cookie;
} }
@ -89,12 +100,17 @@ export default class CookieJar {
async load(file = this.file) { async load(file = this.file) {
if (typeof file !== "string") if (typeof 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!");
JSON.parse(await fs.readFile(file)).forEach(c => this.addCookie(Cookie.fromObject(c))); JSON.parse(await fs.readFile(file)).forEach(c =>
this.addCookie(Cookie.fromObject(c))
);
} }
async save(file = this.file) { async save(file = this.file) {
if (typeof file !== "string") if (typeof 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
await fs.writeFile(this.file, JSON.stringify([...this.cookiesValid(false)])); await fs.writeFile(
this.file,
JSON.stringify([...this.cookiesValid(false)])
);
}
} }
};

View File

@ -4,7 +4,10 @@ import {paramError, CookieParseError} from "./errors.mjs";
const validateHostname = (cookieHostname, requestHostname, subdomains) => { const validateHostname = (cookieHostname, requestHostname, subdomains) => {
cookieHostname = cookieHostname.toLowerCase(); cookieHostname = cookieHostname.toLowerCase();
requestHostname = requestHostname.toLowerCase(); requestHostname = requestHostname.toLowerCase();
if(requestHostname === cookieHostname || (subdomains && requestHostname.endsWith("." + cookieHostname))) if (
requestHostname === cookieHostname ||
(subdomains && requestHostname.endsWith("." + cookieHostname))
)
return true; return true;
return false; return false;
}; };
@ -12,10 +15,8 @@ const validateHostname = (cookieHostname, requestHostname, subdomains) => {
const validatePath = (cookiePath, requestPath) => { const validatePath = (cookiePath, requestPath) => {
cookiePath = decodeURIComponent(cookiePath).toLowerCase(); cookiePath = decodeURIComponent(cookiePath).toLowerCase();
requestPath = decodeURIComponent(requestPath).toLowerCase(); requestPath = decodeURIComponent(requestPath).toLowerCase();
if(cookiePath.endsWith("/")) if (cookiePath.endsWith("/")) cookiePath = cookiePath.slice(0, -1);
cookiePath = cookiePath.slice(0, -1); if (requestPath.endsWith("/")) requestPath = requestPath.slice(0, -1);
if(requestPath.endsWith("/"))
requestPath = requestPath.slice(0, -1);
return (requestPath + "/").startsWith(cookiePath + "/"); return (requestPath + "/").startsWith(cookiePath + "/");
}; };
@ -41,8 +42,10 @@ export default class Cookie {
const splitted = str.split("; "); const splitted = str.split("; ");
[this.name, this.value] = splitN(splitted[0], "=", 1); [this.name, this.value] = splitN(splitted[0], "=", 1);
if (!this.name) if (!this.name)
throw new CookieParseError("Invalid cookie name \"" + this.name + "\"!"); throw new CookieParseError(
if(this.value.startsWith("\"") && this.value.endsWith("\"")) 'Invalid cookie name "' + this.name + '"!'
);
if (this.value.startsWith('"') && this.value.endsWith('"'))
this.value = this.value.slice(1, -1); this.value = this.value.slice(1, -1);
const parsedURL = url.parse(requestURL); const parsedURL = url.parse(requestURL);
@ -52,83 +55,120 @@ export default class Cookie {
k = k.toLowerCase(); k = k.toLowerCase();
if (v) { if (v) {
if (k === "expires") { if (k === "expires") {
if(this.expiry) // max-age has precedence over expires if (this.expiry)
// max-age has precedence over expires
continue; 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) if (
|| (this.expiry = new Date(v)).toString() === "Invalid Date" !/^(?: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(
|| this.expiry.getTime() < 0) v
throw new CookieParseError("Invalid value for Expires \"" + v + "\"!"); ) ||
} (this.expiry = new Date(v)).toString() ===
else if(k === "max-age") { "Invalid Date" ||
this.expiry.getTime() < 0
)
throw new CookieParseError(
'Invalid value for Expires "' + v + '"!'
);
} else if (k === "max-age") {
const seconds = ~~+v; const seconds = ~~+v;
if (seconds.toString() !== v) if (seconds.toString() !== v)
throw new CookieParseError("Invalid value for Max-Age \"" + v + "\"!"); throw new CookieParseError(
'Invalid value for Max-Age "' + v + '"!'
);
this.expiry = new Date(); this.expiry = new Date();
this.expiry.setSeconds(this.expiry.getSeconds() + seconds); this.expiry.setSeconds(this.expiry.getSeconds() + seconds);
} } else if (k === "domain") {
else if(k === "domain") { if (v.startsWith(".")) v = v.substring(1);
if(v.startsWith("."))
v = v.substring(1);
if (!validateHostname(parsedURL.hostname, v, true)) if (!validateHostname(parsedURL.hostname, v, true))
throw new CookieParseError("Invalid value for Domain \"" + v + "\": cookie was received from \"" + parsedURL.hostname + "\"!"); throw new CookieParseError(
'Invalid value for Domain "' +
v +
'": cookie was received from "' +
parsedURL.hostname +
'"!'
);
this.domain = v; this.domain = v;
this.subdomains = true; this.subdomains = true;
} } else if (k === "path") this.path = v;
else if(k === "path") else if (k === "samesite")
this.path = v; // only relevant for cross site requests, so not for us
else if(k === "samesite") // only relevant for cross site requests, so not for us
continue; continue;
else else
throw new CookieParseError("Invalid key \"" + k + "\" with value \"" + v + "\" specified!"); throw new CookieParseError(
} 'Invalid key "' +
else { k +
if(k === "secure") '" with value "' +
this.secure = true; v +
else if(k === "httponly") // only relevant for browsers '" specified!'
);
} else {
if (k === "secure") this.secure = true;
else if (k === "httponly")
// only relevant for browsers
continue; continue;
else else
throw new CookieParseError("Invalid key \"" + k + "\" specified!"); throw new CookieParseError(
'Invalid key "' + k + '" specified!'
);
} }
} }
if(this.name.toLowerCase().startsWith("__secure-") && (!this.secure || parsedURL.protocol !== "https:")) if (
throw new CookieParseError("Cookie has \"__Secure-\" prefix but \"Secure\" isn't set or the cookie is not set via https!"); this.name.toLowerCase().startsWith("__secure-") &&
if(this.name.toLowerCase().startsWith("__host-") && (!this.secure || parsedURL.protocol !== "https:" || this.domain || this.path !== "/")) (!this.secure || parsedURL.protocol !== "https:")
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 \"/\"!"); )
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 !== "/")
)
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 // assign defaults
if (!this.domain) { if (!this.domain) {
this.domain = parsedURL.hostname; this.domain = parsedURL.hostname;
this.subdomains = false; this.subdomains = false;
} }
if(!this.path) if (!this.path) this.path = "/";
this.path = "/"; if (!this.secure) this.secure = false;
if(!this.secure) if (!this.expiry) this.expiry = null;
this.secure = false;
if(!this.expiry)
this.expiry = null;
} }
static fromObject(obj) { static fromObject(obj) {
let c = Object.assign(Object.create(this.prototype), obj); let c = Object.assign(Object.create(this.prototype), obj);
if(typeof c.expiry === "string") if (typeof c.expiry === "string") c.expiry = new Date(c.expiry);
c.expiry = new Date(c.expiry);
return c; return c;
} }
serialize() { serialize() {
return this.name + "=" + this.value; return this.name + "=" + this.value;
} }
hasExpired(sessionEnded) { hasExpired(sessionEnded) {
return sessionEnded && this.expiry === null || this.expiry && this.expiry < new Date(); return (
(sessionEnded && this.expiry === null) ||
(this.expiry && this.expiry < new Date())
);
} }
isValidForRequest(requestURL) { isValidForRequest(requestURL) {
if(this.hasExpired(false)) if (this.hasExpired(false)) return false;
return false;
const parsedURL = url.parse(requestURL); const parsedURL = url.parse(requestURL);
if(parsedURL.protocol !== "http:" && parsedURL.protocol !== "https:" if (
|| this.secure && parsedURL.protocol !== "https:" (parsedURL.protocol !== "http:" &&
|| !validateHostname(this.domain, parsedURL.hostname, this.subdomains) parsedURL.protocol !== "https:") ||
|| !validatePath(this.path, parsedURL.pathname)) (this.secure && parsedURL.protocol !== "https:") ||
!validateHostname(
this.domain,
parsedURL.hostname,
this.subdomains
) ||
!validatePath(this.path, parsedURL.pathname)
)
return false; return false;
return true; return true;
} }
}; }

View File

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

@ -6,56 +6,64 @@ import {paramError, CookieParseError} from "./errors.mjs";
const redirectStatus = new Set([301, 302, 303, 307, 308]); const redirectStatus = new Set([301, 302, 303, 307, 308]);
async function fetch(cookieJars, url, options) { async function fetch(cookieJars, url, options) {
let cookies = ""; let cookies = "";
const addValidFromJars = jars => { const addValidFromJars = jars => {
// since multiple cookie jars can be passed, filter duplicates by using a set of cookie names // since multiple cookie jars can be passed, filter duplicates by using a set of cookie names
const set = new Set(); const set = new Set();
jars.flatMap(jar => [...jar.cookiesValidForRequest(url)]) jars.flatMap(jar => [...jar.cookiesValidForRequest(url)]).forEach(
.forEach(cookie => { cookie => {
if(set.has(cookie.name)) if (set.has(cookie.name)) return;
return;
set.add(cookie.name); set.add(cookie.name);
cookies += cookie.serialize() + "; "; cookies += cookie.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)
)
addValidFromJars(cookieJars.filter(jar => jar.flags.includes("r"))); addValidFromJars(cookieJars.filter(jar => jar.flags.includes("r")));
else if (cookieJars instanceof CookieJar) else if (cookieJars instanceof CookieJar)
if(cookieJars.flags.includes("r")) if (cookieJars.flags.includes("r")) addValidFromJars([cookieJars]);
addValidFromJars([cookieJars]);
else else
throw paramError("First", "cookieJars", "fetch", ["CookieJar", "[CookieJar]"]); throw paramError("First", "cookieJars", "fetch", [
"CookieJar",
"[CookieJar]"
]);
} }
if (cookies) { if (cookies) {
if(!options) if (!options) options = {};
options = {}; if (!options.headers) options.headers = {};
if(!options.headers)
options.headers = {};
options.headers.cookie = cookies.slice(0, -2); options.headers.cookie = cookies.slice(0, -2);
} }
const wantFollow = !options || !options.redirect || options.redirect === 'follow'; const wantFollow =
!options || !options.redirect || options.redirect === "follow";
if (wantFollow) { if (wantFollow) {
if (!options) options = {}; if (!options) options = {};
options.redirect = 'manual'; options.redirect = "manual";
} }
const result = await _fetch(url, options); const result = await _fetch(url, options);
// I cannot use headers.get() here because it joins the cookies to a string // I cannot use headers.get() here because it joins the cookies to a string
cookies = result.headers[Object.getOwnPropertySymbols(result.headers)[0]]["set-cookie"]; cookies =
result.headers[Object.getOwnPropertySymbols(result.headers)[0]][
"set-cookie"
];
if (cookies && cookieJars) { if (cookies && cookieJars) {
if (Array.isArray(cookieJars)) { if (Array.isArray(cookieJars)) {
cookieJars cookieJars
.filter(jar => jar.flags.includes("w")) .filter(jar => jar.flags.includes("w"))
.forEach(jar => cookies.forEach(c => jar.addCookie(c, url))); .forEach(jar => cookies.forEach(c => jar.addCookie(c, url)));
} } else if (
else if(cookieJars instanceof CookieJar && cookieJars.flags.includes("w")) cookieJars instanceof CookieJar &&
cookieJars.flags.includes("w")
)
cookies.forEach(c => cookieJars.addCookie(c, url)); cookies.forEach(c => cookieJars.addCookie(c, url));
} }
if (wantFollow && redirectStatus.has(result.status)) { if (wantFollow && redirectStatus.has(result.status)) {
const location = result.headers.get('Location'); const location = result.headers.get("Location");
options.redirect = 'follow'; options.redirect = "follow";
return fetch(cookieJars, location, options); return fetch(cookieJars, location, options);
} }
return result; return result;

View File

@ -4,196 +4,319 @@ import {CookieParseError} from "../src/errors.mjs";
export default Test => [ export default Test => [
new Test("new Cookie() / cookie parser", () => { new Test("new Cookie() / cookie parser", () => {
const inputs = [ const inputs = [
[ // type error [
// type error
123, 123,
"" ""
], ],
[ // type error [
// type error
"id=a3fWa", "id=a3fWa",
123 123
], ],
[ // type error invalid url [
// type error invalid url
"id=a3fWa", "id=a3fWa",
"" ""
], ],
[ // type error invalid url [
// type error invalid url
"id=a3fWa", "id=a3fWa",
"github.com" "github.com"
], ],
// TODO: fix this test case, parser shouldn't allow it. it is currently ignored // TODO: fix this test case, parser shouldn't allow it. it is currently ignored
[ // type error invalid url [
// type error invalid url
"id=a3fWa", "id=a3fWa",
"https:abc/abs" "https:abc/abs"
], ],
[ // cookie parse error invalid cookie name [
// cookie parse error invalid cookie name
"", "",
"https://github.com" "https://github.com"
], ],
[ // cookie parse error invalid value for expires [
// cookie parse error invalid value for expires
"id=a3fWa; Expires=Wed, 21 Oct 2015 07:28: GMT; Secure; HttpOnly", "id=a3fWa; Expires=Wed, 21 Oct 2015 07:28: GMT; Secure; HttpOnly",
"https://github.com" "https://github.com"
], ],
[ // cookie parse error invalid value for expires [
// cookie parse error invalid value for expires
"id=a3fWa; Expires=Wed, 21 Onv 2015 07:28:00 GMT; Secure; HttpOnly", "id=a3fWa; Expires=Wed, 21 Onv 2015 07:28:00 GMT; Secure; HttpOnly",
"https://github.com" "https://github.com"
], ],
[ // cookie parse error invalid value for expires [
// cookie parse error invalid value for expires
"id=a3fWa; Expires=Wed, 21 Oct 20151 07:28:00 GMT; Secure; HttpOnly", "id=a3fWa; Expires=Wed, 21 Oct 20151 07:28:00 GMT; Secure; HttpOnly",
"https://github.com" "https://github.com"
], ],
[ // cookie parse error invalid value for expires [
// cookie parse error invalid value for expires
"id=a3fWa; Expires=Wed, 32 Oct 2015 07:28:00 GMT; Secure; HttpOnly", "id=a3fWa; Expires=Wed, 32 Oct 2015 07:28:00 GMT; Secure; HttpOnly",
"https://github.com" "https://github.com"
], ],
[ // cookie parse error invalid value for expires [
// cookie parse error invalid value for expires
"id=a3fWa; Expires=Wed, 21 Oct 2015 25:28:00 GMT; Secure; HttpOnly", "id=a3fWa; Expires=Wed, 21 Oct 2015 25:28:00 GMT; Secure; HttpOnly",
"https://github.com" "https://github.com"
], ],
[ // cookie parse error invalid value for expires [
// cookie parse error invalid value for expires
"id=a3fWa; Expires=Wed, 21 Oct 2015 07:61:00 GMT; Secure; HttpOnly", "id=a3fWa; Expires=Wed, 21 Oct 2015 07:61:00 GMT; Secure; HttpOnly",
"https://github.com" "https://github.com"
], ],
[ // cookie parse error invalid value for expires [
// cookie parse error invalid value for expires
"id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 UTC; Secure; HttpOnly", "id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 UTC; Secure; HttpOnly",
"https://github.com" "https://github.com"
], ],
[ // cookie parse error invalid value for expires [
// cookie parse error invalid value for expires
"id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT+2; Secure; HttpOnly", "id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT+2; Secure; HttpOnly",
"https://github.com" "https://github.com"
], ],
[ // cookie parse error invalid value for expires [
// cookie parse error invalid value for expires
"id=a3fWa; Expires=San, 21 Onv 2015 07:28:00 GMT; Secure; HttpOnly", "id=a3fWa; Expires=San, 21 Onv 2015 07:28:00 GMT; Secure; HttpOnly",
"https://github.com" "https://github.com"
], ],
[ // cookie parse error invalid value for expires [
// cookie parse error invalid value for expires
"id=a3fWa; Expires=Wed, 31 Dec 1969 07:28:00 GMT; Secure; HttpOnly", "id=a3fWa; Expires=Wed, 31 Dec 1969 07:28:00 GMT; Secure; HttpOnly",
"https://github.com" "https://github.com"
], ],
[ // cookie parse error invalid value for expires [
// cookie parse error invalid value for expires
"id=a3fWa; Max-Age=121252a; Secure; HttpOnly", "id=a3fWa; Max-Age=121252a; Secure; HttpOnly",
"https://github.com" "https://github.com"
], ],
[ // cookie parse error invalid key secur [
// cookie parse error invalid key secur
"id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secur; HttpOnly", "id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secur; HttpOnly",
"https://github.com" "https://github.com"
], ],
[ // cookie parse error invalid key HttpOly with value 2 [
// cookie parse error invalid key HttpOly with value 2
"id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOly=2", "id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOly=2",
"https://github.com" "https://github.com"
], ],
[ // cookie parse error not set via https [
// cookie parse error not set via https
"__Secure-id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Secure; HttpOnly", "__Secure-id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Secure; HttpOnly",
"http://github.com" "http://github.com"
], ],
[ // cookie parse error secure not set [
// cookie parse error secure not set
"__Secure-id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; HttpOnly", "__Secure-id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; HttpOnly",
"https://github.com" "https://github.com"
], ],
[ // cookie parse error secure not set [
// cookie parse error secure not set
"__Host-id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; HttpOnly; Path=/", "__Host-id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; HttpOnly; Path=/",
"https://github.com" "https://github.com"
], ],
[ // cookie parse error not set via https [
// cookie parse error not set via https
"__Host-id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Secure; HttpOnly; Path=/", "__Host-id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Secure; HttpOnly; Path=/",
"http://github.com" "http://github.com"
], ],
[ // cookie parse error domain is set [
// cookie parse error domain is set
"__Host-id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Secure; HttpOnly; Domain=github.com; Path=/", "__Host-id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Secure; HttpOnly; Domain=github.com; Path=/",
"https://github.com" "https://github.com"
], ],
[ // cookie parse error path is not set [
// cookie parse error path is not set
"__Host-id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Secure; HttpOnly", "__Host-id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Secure; HttpOnly",
"https://github.com" "https://github.com"
], ],
[ // cookie parse error path is not equal to / [
// cookie parse error path is not equal to /
"__Host-id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Secure; HttpOnly; Path=/lel/", "__Host-id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Secure; HttpOnly; Path=/lel/",
"https://github.com" "https://github.com"
], ],
[ // cookie parse error domain is not a subdomain [
// cookie parse error domain is not a subdomain
"id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Domain=github.com", "id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Domain=github.com",
"https://gist.github.com" "https://gist.github.com"
], ],
[ // cookie parse error domain is not a subdomain [
// cookie parse error domain is not a subdomain
"id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Domain=npmjs.com", "id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Domain=npmjs.com",
"https://gist.github.com" "https://gist.github.com"
], ],
[ // success [
// success
"__Secure-id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Secure; HttpOnly", "__Secure-id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Secure; HttpOnly",
"https://github.com" "https://github.com"
], ],
[ // success [
// success
"__Host-id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Secure; HttpOnly; Path=/", "__Host-id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Secure; HttpOnly; Path=/",
"https://github.com" "https://github.com"
], ],
[ // success [
// success
"__Host-id=a3fWa; Expires=Wed, 21 Nov 2099 20:28:33 GMT; Secure; HttpOnly; Path=/", "__Host-id=a3fWa; Expires=Wed, 21 Nov 2099 20:28:33 GMT; Secure; HttpOnly; Path=/",
"https://github.com" "https://github.com"
], ],
[ // success [
// success
"id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Secure; HttpOnly; Path=/lul/; Domain=.usercontent.github.com", "id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Secure; HttpOnly; Path=/lul/; Domain=.usercontent.github.com",
"https://github.com" "https://github.com"
], ],
[ // success [
// success
"id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Secure; HttpOnly; SameSite=Strict; Path=/lul/; Domain=usercontent.github.com", "id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Secure; HttpOnly; SameSite=Strict; Path=/lul/; Domain=usercontent.github.com",
"https://github.com" "https://github.com"
], ],
[ // success max-age takes precendence over expires [
// success max-age takes precendence over expires
"id=a3fWa; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=1000", "id=a3fWa; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=1000",
"https://github.com" "https://github.com"
], ],
[ // success max-age takes precendence over expires [
"id=\"a3fWa\"; Max-Age=1000; Expires=Thu, 01 Jan 1970 00:00:00 GMT", // success max-age takes precendence over expires
'id="a3fWa"; Max-Age=1000; Expires=Thu, 01 Jan 1970 00:00:00 GMT',
"https://github.com" "https://github.com"
], ]
]; ];
const catchErrorTest = (input, catchFnc) => { const catchErrorTest = (input, catchFnc) => {
try { try {
new Cookie(...input); new Cookie(...input);
return false; return false;
} } catch (error) {
catch(error) {
return catchFnc(error); return catchFnc(error);
} }
}; };
const catchErrorTypeMessageTest = (input, type, message) => catchErrorTest(input, e => e instanceof type && e.message === message); const catchErrorTypeMessageTest = (input, type, message) =>
catchErrorTest(
input,
e => e instanceof type && e.message === message
);
const compareCookieProps = (input, expiryFnc, properties) => { const compareCookieProps = (input, expiryFnc, properties) => {
const cookie = new Cookie(...input); const cookie = new Cookie(...input);
return Object.entries(properties).every(([prop, value]) => cookie[prop] === value) return (
&& expiryFnc(cookie.expiry); Object.entries(properties).every(
([prop, value]) => cookie[prop] === value
) && expiryFnc(cookie.expiry)
);
}; };
return inputs.slice(0, 3).every(input => catchErrorTest(input, e => e instanceof TypeError)) return (
inputs
.slice(0, 3)
.every(input =>
catchErrorTest(input, e => e instanceof TypeError)
) &&
// cookies[4] is the test case that is ignored for now // cookies[4] is the test case that is ignored for now
&& catchErrorTypeMessageTest(inputs[5], CookieParseError, "Invalid cookie name \"\"!") catchErrorTypeMessageTest(
&& catchErrorTypeMessageTest(inputs[6], CookieParseError, "Invalid value for Expires \"Wed, 21 Oct 2015 07:28: GMT\"!") inputs[5],
&& catchErrorTypeMessageTest(inputs[7], CookieParseError, "Invalid value for Expires \"Wed, 21 Onv 2015 07:28:00 GMT\"!") CookieParseError,
&& catchErrorTypeMessageTest(inputs[8], CookieParseError, "Invalid value for Expires \"Wed, 21 Oct 20151 07:28:00 GMT\"!") 'Invalid cookie name ""!'
&& catchErrorTypeMessageTest(inputs[9], CookieParseError, "Invalid value for Expires \"Wed, 32 Oct 2015 07:28:00 GMT\"!") ) &&
&& catchErrorTypeMessageTest(inputs[10], CookieParseError, "Invalid value for Expires \"Wed, 21 Oct 2015 25:28:00 GMT\"!") catchErrorTypeMessageTest(
&& catchErrorTypeMessageTest(inputs[11], CookieParseError, "Invalid value for Expires \"Wed, 21 Oct 2015 07:61:00 GMT\"!") inputs[6],
&& catchErrorTypeMessageTest(inputs[12], CookieParseError, "Invalid value for Expires \"Wed, 21 Oct 2015 07:28:00 UTC\"!") CookieParseError,
&& catchErrorTypeMessageTest(inputs[13], CookieParseError, "Invalid value for Expires \"Wed, 21 Oct 2015 07:28:00 GMT+2\"!") 'Invalid value for Expires "Wed, 21 Oct 2015 07:28: GMT"!'
&& catchErrorTypeMessageTest(inputs[14], CookieParseError, "Invalid value for Expires \"San, 21 Onv 2015 07:28:00 GMT\"!") ) &&
&& catchErrorTypeMessageTest(inputs[15], CookieParseError, "Invalid value for Expires \"Wed, 31 Dec 1969 07:28:00 GMT\"!") catchErrorTypeMessageTest(
&& catchErrorTypeMessageTest(inputs[16], CookieParseError, "Invalid value for Max-Age \"121252a\"!") inputs[7],
&& catchErrorTypeMessageTest(inputs[17], CookieParseError, "Invalid key \"secur\" specified!") CookieParseError,
&& catchErrorTypeMessageTest(inputs[18], CookieParseError, "Invalid key \"httpoly\" with value \"2\" specified!") 'Invalid value for Expires "Wed, 21 Onv 2015 07:28:00 GMT"!'
) &&
&& inputs.slice(19, 21).every(input => catchErrorTypeMessageTest(input, CookieParseError, "Cookie has \"__Secure-\" prefix but \"Secure\" isn't set or the cookie is not set via https!")) catchErrorTypeMessageTest(
inputs[8],
&& inputs.slice(21, 26).every(input => catchErrorTypeMessageTest(input, 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 \"/\"!")) CookieParseError,
'Invalid value for Expires "Wed, 21 Oct 20151 07:28:00 GMT"!'
&& catchErrorTypeMessageTest(inputs[26], CookieParseError, "Invalid value for Domain \"github.com\": cookie was received from \"gist.github.com\"!") ) &&
&& catchErrorTypeMessageTest(inputs[27], CookieParseError, "Invalid value for Domain \"npmjs.com\": cookie was received from \"gist.github.com\"!") catchErrorTypeMessageTest(
inputs[9],
&& compareCookieProps( CookieParseError,
'Invalid value for Expires "Wed, 32 Oct 2015 07:28:00 GMT"!'
) &&
catchErrorTypeMessageTest(
inputs[10],
CookieParseError,
'Invalid value for Expires "Wed, 21 Oct 2015 25:28:00 GMT"!'
) &&
catchErrorTypeMessageTest(
inputs[11],
CookieParseError,
'Invalid value for Expires "Wed, 21 Oct 2015 07:61:00 GMT"!'
) &&
catchErrorTypeMessageTest(
inputs[12],
CookieParseError,
'Invalid value for Expires "Wed, 21 Oct 2015 07:28:00 UTC"!'
) &&
catchErrorTypeMessageTest(
inputs[13],
CookieParseError,
'Invalid value for Expires "Wed, 21 Oct 2015 07:28:00 GMT+2"!'
) &&
catchErrorTypeMessageTest(
inputs[14],
CookieParseError,
'Invalid value for Expires "San, 21 Onv 2015 07:28:00 GMT"!'
) &&
catchErrorTypeMessageTest(
inputs[15],
CookieParseError,
'Invalid value for Expires "Wed, 31 Dec 1969 07:28:00 GMT"!'
) &&
catchErrorTypeMessageTest(
inputs[16],
CookieParseError,
'Invalid value for Max-Age "121252a"!'
) &&
catchErrorTypeMessageTest(
inputs[17],
CookieParseError,
'Invalid key "secur" specified!'
) &&
catchErrorTypeMessageTest(
inputs[18],
CookieParseError,
'Invalid key "httpoly" with value "2" specified!'
) &&
inputs
.slice(19, 21)
.every(input =>
catchErrorTypeMessageTest(
input,
CookieParseError,
'Cookie has "__Secure-" prefix but "Secure" isn\'t set or the cookie is not set via https!'
)
) &&
inputs
.slice(21, 26)
.every(input =>
catchErrorTypeMessageTest(
input,
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 "/"!'
)
) &&
catchErrorTypeMessageTest(
inputs[26],
CookieParseError,
'Invalid value for Domain "github.com": cookie was received from "gist.github.com"!'
) &&
catchErrorTypeMessageTest(
inputs[27],
CookieParseError,
'Invalid value for Domain "npmjs.com": cookie was received from "gist.github.com"!'
) &&
compareCookieProps(
inputs[28], inputs[28],
exp => exp.getTime() === new Date("Wed, 21 Nov 2015 07:28:00 GMT").getTime(), exp =>
exp.getTime() ===
new Date("Wed, 21 Nov 2015 07:28:00 GMT").getTime(),
{ {
name: "__Secure-id", name: "__Secure-id",
value: "a3fWa", value: "a3fWa",
@ -202,11 +325,12 @@ export default Test => [
subdomains: false, subdomains: false,
path: "/" path: "/"
} }
) ) &&
compareCookieProps(
&& compareCookieProps(
inputs[29], inputs[29],
exp => exp.getTime() === new Date("Wed, 21 Nov 2015 07:28:00 GMT").getTime(), exp =>
exp.getTime() ===
new Date("Wed, 21 Nov 2015 07:28:00 GMT").getTime(),
{ {
name: "__Host-id", name: "__Host-id",
value: "a3fWa", value: "a3fWa",
@ -215,11 +339,12 @@ export default Test => [
subdomains: false, subdomains: false,
path: "/" path: "/"
} }
) ) &&
compareCookieProps(
&& compareCookieProps(
inputs[30], inputs[30],
exp => exp.getTime() === new Date("Wed, 21 Nov 2099 20:28:33 GMT").getTime(), exp =>
exp.getTime() ===
new Date("Wed, 21 Nov 2099 20:28:33 GMT").getTime(),
{ {
name: "__Host-id", name: "__Host-id",
value: "a3fWa", value: "a3fWa",
@ -228,11 +353,12 @@ export default Test => [
subdomains: false, subdomains: false,
path: "/" path: "/"
} }
) ) &&
compareCookieProps(
&& compareCookieProps(
inputs[31], inputs[31],
exp => exp.getTime() === new Date("Wed, 21 Nov 2015 07:28:00 GMT").getTime(), exp =>
exp.getTime() ===
new Date("Wed, 21 Nov 2015 07:28:00 GMT").getTime(),
{ {
name: "id", name: "id",
value: "a3fWa", value: "a3fWa",
@ -241,11 +367,12 @@ export default Test => [
subdomains: true, subdomains: true,
path: "/lul/" path: "/lul/"
} }
) ) &&
compareCookieProps(
&& compareCookieProps(
inputs[32], inputs[32],
exp => exp.getTime() === new Date("Wed, 21 Nov 2015 07:28:00 GMT").getTime(), exp =>
exp.getTime() ===
new Date("Wed, 21 Nov 2015 07:28:00 GMT").getTime(),
{ {
name: "id", name: "id",
value: "a3fWa", value: "a3fWa",
@ -254,11 +381,26 @@ export default Test => [
subdomains: true, subdomains: true,
path: "/lul/" path: "/lul/"
} }
) ) &&
compareCookieProps(
&& compareCookieProps(
inputs[33], inputs[33],
exp => exp.getTime() > new Date("Thu, 01 Jan 1970 00:00:00 GMT").getTime(), exp =>
exp.getTime() >
new Date("Thu, 01 Jan 1970 00:00:00 GMT").getTime(),
{
name: "id",
value: "a3fWa",
secure: false,
domain: "github.com",
subdomains: false,
path: "/"
}
) &&
compareCookieProps(
inputs[34],
exp =>
exp.getTime() >
new Date("Thu, 01 Jan 1970 00:00:00 GMT").getTime(),
{ {
name: "id", name: "id",
value: "a3fWa", value: "a3fWa",
@ -268,18 +410,6 @@ export default Test => [
path: "/" path: "/"
} }
) )
&& compareCookieProps(
inputs[34],
exp => exp.getTime() > new Date("Thu, 01 Jan 1970 00:00:00 GMT").getTime(),
{
name: "id",
value: "a3fWa",
secure: false,
domain: "github.com",
subdomains: false,
path: "/"
}
); );
}), }),
new Test("static Cookie.fromObject()", () => { new Test("static Cookie.fromObject()", () => {
@ -307,24 +437,34 @@ export default Test => [
const cookies = inputs.map(i => Cookie.fromObject(i)); const cookies = inputs.map(i => Cookie.fromObject(i));
return cookies[0].name === "testname" return (
&& cookies[0].value === "somevalue" cookies[0].name === "testname" &&
&& cookies[0].domain === "github.com" cookies[0].value === "somevalue" &&
&& cookies[0].path === "/" cookies[0].domain === "github.com" &&
&& cookies[0].expiry.getTime() === new Date(inputs[0].expiry).getTime() cookies[0].path === "/" &&
&& cookies[0].secure === false cookies[0].expiry.getTime() ===
&& cookies[0].subdomains === false new Date(inputs[0].expiry).getTime() &&
cookies[0].secure === false &&
&& cookies[1].name === "testname" cookies[0].subdomains === false &&
&& cookies[1].value === "lul" cookies[1].name === "testname" &&
&& cookies[1].domain === "somedomain.tld" cookies[1].value === "lul" &&
&& cookies[1].path === "/lel/" cookies[1].domain === "somedomain.tld" &&
&& cookies[1].expiry.getTime() === date.getTime() cookies[1].path === "/lel/" &&
&& cookies[1].secure === true cookies[1].expiry.getTime() === date.getTime() &&
&& cookies[1].subdomains === true; cookies[1].secure === true &&
cookies[1].subdomains === true
);
}), }),
new Test("Cookie.serialize()", () => { new Test("Cookie.serialize()", () => {
return new Cookie("abc=def; Expires=Wed, 20 Oct 2018 08:08:08 GMT; Path=/", "https://somedomain.tld").serialize() === "abc=def" return (
&& new Cookie("jkhsd231=ajkshdgi", "https://somedomain.tld").serialize() === "jkhsd231=ajkshdgi"; new Cookie(
"abc=def; Expires=Wed, 20 Oct 2018 08:08:08 GMT; Path=/",
"https://somedomain.tld"
).serialize() === "abc=def" &&
new Cookie(
"jkhsd231=ajkshdgi",
"https://somedomain.tld"
).serialize() === "jkhsd231=ajkshdgi"
);
}) })
]; ];

View File

@ -8,20 +8,32 @@ export default Test => [
const validTypes = ["lol", "lel", "lul", "lal"]; const validTypes = ["lol", "lel", "lul", "lal"];
const errors = [ const errors = [
paramError(position, paramName, functionName, validTypes[0]), paramError(position, paramName, functionName, validTypes[0]),
paramError(position, paramName, functionName, validTypes.slice(0, 2)), paramError(
position,
paramName,
functionName,
validTypes.slice(0, 2)
),
paramError(position, paramName, functionName, validTypes) paramError(position, paramName, functionName, validTypes)
]; ];
return errors.every(e => e instanceof TypeError) return (
&& errors.every(e => e.name === "TypeError") errors.every(e => e instanceof TypeError) &&
&& errors[0].message === "something parameter \"some_param\" passed to \"some_func\" is not of type \"lol\"!" errors.every(e => e.name === "TypeError") &&
&& errors[1].message === "something parameter \"some_param\" passed to \"some_func\" is not of type \"lol\" or \"lel\"!" errors[0].message ===
&& errors[2].message === "something parameter \"some_param\" passed to \"some_func\" is not of type \"lol\", \"lel\", \"lul\" or \"lal\"!"; 'something parameter "some_param" passed to "some_func" is not of type "lol"!' &&
errors[1].message ===
'something parameter "some_param" passed to "some_func" is not of type "lol" or "lel"!' &&
errors[2].message ===
'something parameter "some_param" passed to "some_func" is not of type "lol", "lel", "lul" or "lal"!'
);
}), }),
new Test("class CookieParseError", () => { new Test("class CookieParseError", () => {
const message = "this is a test error"; const message = "this is a test error";
const error = new CookieParseError(message); const error = new CookieParseError(message);
return error instanceof CookieParseError return (
&& error.name === "CookieParseError" error instanceof CookieParseError &&
&& error.message === message; error.name === "CookieParseError" &&
error.message === message
);
}) })
]; ];

View File

@ -3,43 +3,51 @@ import errors from "./errors.mjs";
class Test { class Test {
constructor(name, fnc) { constructor(name, fnc) {
this.name = name this.name = name;
this.fnc = fnc this.fnc = fnc;
} }
async runTest() { async runTest() {
return this.fnc(); return this.fnc();
} }
} }
const tests = [ const tests = [cookie, errors].flatMap(t => t(Test));
cookie,
errors
].flatMap(t => t(Test));
(async () => { (async () => {
console.log("running tests..."); console.log("running tests...");
const testResults = await Promise.all(tests.map(async t => { const testResults = await Promise.all(
tests.map(async t => {
try { try {
t.result = await t.runTest(); t.result = await t.runTest();
if (t.result !== !!t.result) { if (t.result !== !!t.result) {
t.result = false; t.result = false;
console.error("test did not return a boolean: " + t.name); console.error("test did not return a boolean: " + t.name);
} }
} } catch (error) {
catch(error) { console.error(
console.error("uncaught error in test: " + t.name + "\n", error); "uncaught error in test: " + t.name + "\n",
error
);
t.result = false; t.result = false;
} }
return t; return t;
})); })
);
testResults.forEach(t => { testResults.forEach(t => {
if(t.result) if (t.result) console.info("✔ " + t.name);
console.info("✔ " + t.name); else console.warn("✘ " + t.name);
else
console.warn("✘ " + t.name);
}); });
const succeededTests = testResults.map(t => t.result).reduce((a, b) => a + b); const succeededTests = testResults
.map(t => t.result)
.reduce((a, b) => a + b);
const success = succeededTests === testResults.length; const success = succeededTests === testResults.length;
(success ? console.info : console.warn)((success ? "✔" : "✘") + " " + succeededTests + "/" + testResults.length + " tests successful"); (success ? console.info : console.warn)(
(success ? "✔" : "✘") +
" " +
succeededTests +
"/" +
testResults.length +
" tests successful"
);
!success && process.exit(1); !success && process.exit(1);
})(); })();