├── .eslintrc ├── .travis.yml ├── .gitignore ├── Gruntfile.js ├── package.json ├── LICENSE ├── README.md ├── lib └── biskviit.js └── test └── biskviit-test.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "nodemailer" 3 | } 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - 6 5 | - 8 6 | notifications: 7 | email: 8 | - andris@kreata.ee 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | .DS_Store 30 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | // Project configuration. 5 | grunt.initConfig({ 6 | eslint: { 7 | all: ['lib/*.js', 'test/*.js', 'Gruntfile.js'] 8 | }, 9 | 10 | mochaTest: { 11 | all: { 12 | options: { 13 | reporter: 'spec' 14 | }, 15 | src: ['test/*-test.js'] 16 | } 17 | } 18 | }); 19 | 20 | // Load the plugin(s) 21 | grunt.loadNpmTasks('grunt-eslint'); 22 | grunt.loadNpmTasks('grunt-mocha-test'); 23 | 24 | // Tasks 25 | grunt.registerTask('default', ['eslint', 'mochaTest']); 26 | }; 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "biskviit", 3 | "version": "2.0.0", 4 | "description": "Yet another module for http cookie handling", 5 | "main": "lib/biskviit.js", 6 | "scripts": { 7 | "test": "grunt" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/andris9/biskviit.git" 12 | }, 13 | "keywords": ["HTTP", "cookie", "cookies"], 14 | "author": "Andris Reinman", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/andris9/biskviit/issues" 18 | }, 19 | "homepage": "https://github.com/andris9/biskviit#readme", 20 | "devDependencies": { 21 | "chai": "^4.1.1", 22 | "eslint-config-nodemailer": "^1.2.0", 23 | "grunt": "^1.0.1", 24 | "grunt-cli": "^1.2.0", 25 | "grunt-eslint": "^20.0.0", 26 | "grunt-mocha-test": "^0.13.2", 27 | "mocha": "^3.5.0" 28 | }, 29 | "dependencies": { 30 | "psl": "^1.1.19" 31 | }, 32 | "engines": { 33 | "node": ">=1.0.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2017 Andris Reinman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # biskviit 2 | 3 | Yet another node module for handling http cookies. This module parses `Set-Cookie` header, stores the data to memory and returns valid value for `Cookie` header based on the stored cookie data. 4 | 5 | > **NB** Requires Node.js v6+ 6 | 7 | ## Usage 8 | 9 | Install from npm 10 | 11 | npm install biskviit --save 12 | 13 | Require as `Biskviit` 14 | 15 | ```javascript 16 | const Biskviit = require('biskviit'); 17 | ``` 18 | 19 | Create a cookie managing *biskviit* instance 20 | 21 | ```javascript 22 | const biskviit = new Biskviit(options); 23 | ``` 24 | 25 | Where 26 | 27 | * **options** is an optional options object with the following properties: 28 | * **sessionTimeout** is the amount in seconds for default session length, used for cookies without an expire argument 29 | 30 | **Example** 31 | 32 | ```javascript 33 | const Biskviit = require('biskviit'); 34 | const biskviit = new Biskviit({ 35 | sessionTimeout: 5 * 60 // expire cookies after 5 minutes 36 | }); 37 | ``` 38 | 39 | ### set 40 | 41 | To add new cookies to the storage use `set` 42 | 43 | ```javascript 44 | biskviit.set(cookieString, url) 45 | ``` 46 | 47 | Where 48 | 49 | * **cookieString** is the value from the `Set-Cookie:` header 50 | * **url** is the currently open URL that sent the cookie header 51 | 52 | **Example** 53 | 54 | ```javascript 55 | biskviit.set('theme=light', 'http://example.com/'); 56 | biskviit.set('sessionToken=abc123; Expires=Wed, 09 Jun 2021 10:18:14 GMT', 'http://example.com/'); 57 | ``` 58 | 59 | ### get 60 | 61 | To list all available cookies for a specified URL use `get` 62 | 63 | ```javascript 64 | const cookiesString = biskviit.get(url); 65 | ``` 66 | 67 | Where 68 | 69 | * **url** is the URL the cookies are required for 70 | 71 | **Example** 72 | 73 | ```javascript 74 | const cookiesString = biskviit.get('http://example.com/'); 75 | // theme=light; sessionToken=abc123 76 | ``` 77 | 78 | ### list 79 | 80 | If you need to filter cookies as objects, use `list` 81 | 82 | ```javascript 83 | const cookiesString = biskviit.list(url); 84 | ``` 85 | 86 | Where 87 | 88 | * **url** is the URL the cookies are required for 89 | 90 | **Example** 91 | 92 | ```javascript 93 | const cookiesString = biskviit.list('http://example.com/'); 94 | // [{key: 'theme', value: 'light', expires: ...}, {key: 'sessionToken', value: 'abc123', expires: ...}] 95 | ``` 96 | 97 | ## License 98 | 99 | **MIT** 100 | -------------------------------------------------------------------------------- /lib/biskviit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const urllib = require('url'); 4 | const psl = require('psl'); 5 | 6 | const SESSION_TIMEOUT = 1800; // 30 min 7 | 8 | /** 9 | * Creates a biskviit cookie jar for managing cookie values in memory 10 | * 11 | * @constructor 12 | * @param {Object} [options] Optional options object 13 | */ 14 | class Biskviit { 15 | constructor(options) { 16 | this.options = options || {}; 17 | this.cookies = []; 18 | } 19 | 20 | /** 21 | * Stores a cookie string to the cookie storage 22 | * 23 | * @param {String} cookieStr Value from the 'Set-Cookie:' header 24 | * @param {String} url Current URL 25 | */ 26 | set(cookieStr, url) { 27 | let urlparts = urllib.parse(url || ''); 28 | let cookie = this.parse(cookieStr); 29 | 30 | if (cookie.domain) { 31 | let domain = cookie.domain.replace(/^\./, ''); 32 | 33 | // do not allow generic TLDs, except unlisted 34 | if (psl.parse(domain).listed && !psl.isValid(domain)) { 35 | cookie.domain = urlparts.hostname; 36 | } 37 | 38 | // do not allow cross origin cookies 39 | if ( 40 | // can't be valid if the requested domain is shorter than current hostname 41 | urlparts.hostname.length < domain.length || 42 | // prefix domains with dot to be sure that partial matches are not used 43 | ('.' + urlparts.hostname).substr(-domain.length + 1) !== '.' + domain 44 | ) { 45 | cookie.domain = urlparts.hostname; 46 | } 47 | } else { 48 | cookie.domain = urlparts.hostname; 49 | } 50 | 51 | if (!cookie.path) { 52 | cookie.path = this.getPath(urlparts.pathname); 53 | } 54 | 55 | // if no expire date, then use sessionTimeout value 56 | if (!cookie.expires) { 57 | cookie.expires = new Date(Date.now() + (Number(this.options.sessionTimeout || SESSION_TIMEOUT) || SESSION_TIMEOUT) * 1000); 58 | } 59 | 60 | return this.add(cookie); 61 | } 62 | 63 | /** 64 | * Returns cookie string for the 'Cookie:' header. 65 | * 66 | * @param {String} url URL to check for 67 | * @returns {String} Cookie header or empty string if no matches were found 68 | */ 69 | get(url) { 70 | return this.list(url).map(cookie => cookie.name + '=' + cookie.value).join('; '); 71 | } 72 | 73 | /** 74 | * Lists all valied cookie objects for the specified URL 75 | * 76 | * @param {String} url URL to check for 77 | * @returns {Array} An array of cookie objects 78 | */ 79 | list(url) { 80 | let result = []; 81 | 82 | for (let i = this.cookies.length - 1; i >= 0; i--) { 83 | let cookie = this.cookies[i]; 84 | 85 | if (this.isExpired(cookie)) { 86 | this.cookies.splice(i, i); 87 | continue; 88 | } 89 | 90 | if (this.match(cookie, url)) { 91 | result.unshift(cookie); 92 | } 93 | } 94 | 95 | return result; 96 | } 97 | 98 | /** 99 | * Parses cookie string from the 'Set-Cookie:' header 100 | * 101 | * @param {String} cookieStr String from the 'Set-Cookie:' header 102 | * @returns {Object} Cookie object 103 | */ 104 | parse(cookieStr) { 105 | let cookie = {}; 106 | 107 | (cookieStr || '').toString().split(';').forEach(cookiePart => { 108 | let valueParts = cookiePart.split('='); 109 | let key = valueParts.shift().trim().toLowerCase(); 110 | let value = valueParts.join('=').trim(); 111 | 112 | if (!key) { 113 | // skip empty parts 114 | return; 115 | } 116 | 117 | switch (key) { 118 | case 'expires': 119 | value = new Date(value); 120 | // ignore date if can not parse it 121 | if (value.toString() !== 'Invalid Date') { 122 | cookie.expires = value; 123 | } 124 | break; 125 | 126 | case 'path': 127 | cookie.path = value; 128 | break; 129 | 130 | case 'domain': { 131 | let domain = value.toLowerCase(); 132 | if (domain.length && domain.charAt(0) !== '.') { 133 | domain = '.' + domain; // ensure preceeding dot for user set domains 134 | } 135 | cookie.domain = domain; 136 | break; 137 | } 138 | case 'max-age': 139 | cookie.expires = new Date(Date.now() + (Number(value) || 0) * 1000); 140 | break; 141 | 142 | case 'secure': 143 | cookie.secure = true; 144 | break; 145 | 146 | case 'httponly': 147 | cookie.httponly = true; 148 | break; 149 | 150 | default: 151 | if (!cookie.name) { 152 | cookie.name = key; 153 | cookie.value = value; 154 | } 155 | } 156 | }); 157 | 158 | return cookie; 159 | } 160 | 161 | /** 162 | * Checks if a cookie object is valid for a specified URL 163 | * 164 | * @param {Object} cookie Cookie object 165 | * @param {String} url URL to check for 166 | * @returns {Boolean} true if cookie is valid for specifiec URL 167 | */ 168 | match(cookie, url) { 169 | let urlparts = urllib.parse(url || ''); 170 | 171 | // check if hostname matches 172 | // .foo.com also matches subdomains, foo.com does not 173 | if ( 174 | urlparts.hostname !== cookie.domain && 175 | (cookie.domain.charAt(0) !== '.' || ('.' + urlparts.hostname).substr(-cookie.domain.length) !== cookie.domain) 176 | ) { 177 | return false; 178 | } 179 | 180 | // check if path matches 181 | let path = this.getPath(urlparts.pathname); 182 | if (path.substr(0, cookie.path.length) !== cookie.path) { 183 | return false; 184 | } 185 | 186 | // check secure argument 187 | if (cookie.secure && urlparts.protocol !== 'https:') { 188 | return false; 189 | } 190 | 191 | return true; 192 | } 193 | 194 | /** 195 | * Adds (or updates/removes if needed) a cookie object to the cookie storage 196 | * 197 | * @param {Object} cookie Cookie value to be stored 198 | */ 199 | add(cookie) { 200 | // nothing to do here 201 | if (!cookie || !cookie.name) { 202 | return false; 203 | } 204 | 205 | // overwrite if has same params 206 | for (let i = 0, len = this.cookies.length; i < len; i++) { 207 | if (this.compare(this.cookies[i], cookie)) { 208 | // check if the cookie needs to be removed instead 209 | if (this.isExpired(cookie)) { 210 | this.cookies.splice(i, 1); // remove expired/unset cookie 211 | return false; 212 | } 213 | 214 | this.cookies[i] = cookie; 215 | return true; 216 | } 217 | } 218 | 219 | // add as new if not already expired 220 | if (!this.isExpired(cookie)) { 221 | this.cookies.push(cookie); 222 | } 223 | 224 | return true; 225 | } 226 | 227 | /** 228 | * Checks if two cookie objects are the same 229 | * 230 | * @param {Object} a Cookie to check against 231 | * @param {Object} b Cookie to check against 232 | * @returns {Boolean} True, if the cookies are the same 233 | */ 234 | compare(a, b) { 235 | return a.name === b.name && a.path === b.path && a.domain === b.domain && a.secure === b.secure && a.httponly === a.httponly; 236 | } 237 | 238 | /** 239 | * Checks if a cookie is expired 240 | * 241 | * @param {Object} cookie Cookie object to check against 242 | * @returns {Boolean} True, if the cookie is expired 243 | */ 244 | isExpired(cookie) { 245 | return (cookie.expires && cookie.expires < new Date()) || !cookie.value; 246 | } 247 | 248 | /** 249 | * Returns normalized cookie path for an URL path argument 250 | * 251 | * @param {String} pathname 252 | * @returns {String} Normalized path 253 | */ 254 | getPath(pathname) { 255 | let path = (pathname || '/').split('/'); 256 | path.pop(); // remove filename part 257 | path = path.join('/').trim(); 258 | 259 | // ensure path prefix / 260 | if (path.charAt(0) !== '/') { 261 | path = '/' + path; 262 | } 263 | 264 | // ensure path suffix / 265 | if (path.substr(-1) !== '/') { 266 | path += '/'; 267 | } 268 | 269 | return path; 270 | } 271 | } 272 | 273 | module.exports = Biskviit; 274 | -------------------------------------------------------------------------------- /test/biskviit-test.js: -------------------------------------------------------------------------------- 1 | /* eslint no-unused-expressions:0 */ 2 | /* globals afterEach, beforeEach, describe, it */ 3 | 4 | 'use strict'; 5 | 6 | const chai = require('chai'); 7 | const expect = chai.expect; 8 | 9 | //var http = require('http'); 10 | const Biskviit = require('../lib/biskviit'); 11 | 12 | chai.config.includeStack = true; 13 | 14 | describe('Biskviit Unit Tests', () => { 15 | let biskviit; 16 | 17 | beforeEach(() => { 18 | biskviit = new Biskviit(); 19 | }); 20 | 21 | describe('#getPath', () => { 22 | it('should return root path', () => { 23 | expect(biskviit.getPath('/')).to.equal('/'); 24 | expect(biskviit.getPath('')).to.equal('/'); 25 | expect(biskviit.getPath('/index.php')).to.equal('/'); 26 | }); 27 | 28 | it('should return without file', () => { 29 | expect(biskviit.getPath('/path/to/file')).to.equal('/path/to/'); 30 | }); 31 | }); 32 | 33 | describe('#isExpired', () => { 34 | it('should match expired cookie', () => { 35 | expect( 36 | biskviit.isExpired({ 37 | name: 'a', 38 | value: 'b', 39 | expires: new Date(Date.now() + 10000) 40 | }) 41 | ).to.be.false; 42 | 43 | expect( 44 | biskviit.isExpired({ 45 | name: 'a', 46 | value: '', 47 | expires: new Date(Date.now() + 10000) 48 | }) 49 | ).to.be.true; 50 | 51 | expect( 52 | biskviit.isExpired({ 53 | name: 'a', 54 | value: 'b', 55 | expires: new Date(Date.now() - 10000) 56 | }) 57 | ).to.be.true; 58 | }); 59 | }); 60 | 61 | describe('#compare', () => { 62 | it('should match similar cookies', () => { 63 | expect( 64 | biskviit.compare( 65 | { 66 | name: 'zzz', 67 | path: '/', 68 | domain: 'example.com', 69 | secure: false, 70 | httponly: false 71 | }, 72 | { 73 | name: 'zzz', 74 | path: '/', 75 | domain: 'example.com', 76 | secure: false, 77 | httponly: false 78 | } 79 | ) 80 | ).to.be.true; 81 | 82 | expect( 83 | biskviit.compare( 84 | { 85 | name: 'zzz', 86 | path: '/', 87 | domain: 'example.com', 88 | secure: false, 89 | httponly: false 90 | }, 91 | { 92 | name: 'yyy', 93 | path: '/', 94 | domain: 'example.com', 95 | secure: false, 96 | httponly: false 97 | } 98 | ) 99 | ).to.be.false; 100 | 101 | expect( 102 | biskviit.compare( 103 | { 104 | name: 'zzz', 105 | path: '/', 106 | domain: 'example.com', 107 | secure: false, 108 | httponly: false 109 | }, 110 | { 111 | name: 'zzz', 112 | path: '/amp', 113 | domain: 'example.com', 114 | secure: false, 115 | httponly: false 116 | } 117 | ) 118 | ).to.be.false; 119 | 120 | expect( 121 | biskviit.compare( 122 | { 123 | name: 'zzz', 124 | path: '/', 125 | domain: 'example.com', 126 | secure: false, 127 | httponly: false 128 | }, 129 | { 130 | name: 'zzz', 131 | path: '/', 132 | domain: 'examples.com', 133 | secure: false, 134 | httponly: false 135 | } 136 | ) 137 | ).to.be.false; 138 | 139 | expect( 140 | biskviit.compare( 141 | { 142 | name: 'zzz', 143 | path: '/', 144 | domain: 'example.com', 145 | secure: false, 146 | httponly: false 147 | }, 148 | { 149 | name: 'zzz', 150 | path: '/', 151 | domain: 'example.com', 152 | secure: true, 153 | httponly: false 154 | } 155 | ) 156 | ).to.be.false; 157 | }); 158 | }); 159 | 160 | describe('#add', () => { 161 | it('should append new cookie', () => { 162 | expect(biskviit.cookies.length).to.equal(0); 163 | biskviit.add({ 164 | name: 'zzz', 165 | value: 'abc', 166 | path: '/', 167 | expires: new Date(Date.now() + 10000), 168 | domain: 'example.com', 169 | secure: false, 170 | httponly: false 171 | }); 172 | expect(biskviit.cookies.length).to.equal(1); 173 | expect(biskviit.cookies[0].name).to.equal('zzz'); 174 | expect(biskviit.cookies[0].value).to.equal('abc'); 175 | }); 176 | 177 | it('should update existing cookie', () => { 178 | expect(biskviit.cookies.length).to.equal(0); 179 | biskviit.add({ 180 | name: 'zzz', 181 | value: 'abc', 182 | path: '/', 183 | expires: new Date(Date.now() + 10000), 184 | domain: 'example.com', 185 | secure: false, 186 | httponly: false 187 | }); 188 | biskviit.add({ 189 | name: 'zzz', 190 | value: 'def', 191 | path: '/', 192 | expires: new Date(Date.now() + 10000), 193 | domain: 'example.com', 194 | secure: false, 195 | httponly: false 196 | }); 197 | expect(biskviit.cookies.length).to.equal(1); 198 | expect(biskviit.cookies[0].name).to.equal('zzz'); 199 | expect(biskviit.cookies[0].value).to.equal('def'); 200 | }); 201 | }); 202 | 203 | describe('#match', () => { 204 | it('should check if a cookie matches particular domain and path', () => { 205 | let cookie = { 206 | name: 'zzz', 207 | value: 'abc', 208 | path: '/def/', 209 | expires: new Date(Date.now() + 10000), 210 | domain: 'example.com', 211 | secure: false, 212 | httponly: false 213 | }; 214 | expect(biskviit.match(cookie, 'http://example.com/def/')).to.be.true; 215 | expect(biskviit.match(cookie, 'http://example.com/bef/')).to.be.false; 216 | }); 217 | 218 | it('should check if a cookie matches particular domain and path', () => { 219 | let cookie = { 220 | name: 'zzz', 221 | value: 'abc', 222 | path: '/def', 223 | expires: new Date(Date.now() + 10000), 224 | domain: 'example.com', 225 | secure: false, 226 | httponly: false 227 | }; 228 | expect(biskviit.match(cookie, 'http://example.com/def/')).to.be.true; 229 | expect(biskviit.match(cookie, 'http://example.com/bef/')).to.be.false; 230 | }); 231 | 232 | it('should check if a cookie is secure', () => { 233 | let cookie = { 234 | name: 'zzz', 235 | value: 'abc', 236 | path: '/def/', 237 | expires: new Date(Date.now() + 10000), 238 | domain: 'example.com', 239 | secure: true, 240 | httponly: false 241 | }; 242 | expect(biskviit.match(cookie, 'https://example.com/def/')).to.be.true; 243 | expect(biskviit.match(cookie, 'http://example.com/def/')).to.be.false; 244 | }); 245 | }); 246 | 247 | describe('#parse', () => { 248 | it('should parse Set-Cookie value', () => { 249 | expect(biskviit.parse('theme=plain')).to.deep.equal({ 250 | name: 'theme', 251 | value: 'plain' 252 | }); 253 | 254 | expect(biskviit.parse('SSID=Ap4P….GTEq; Domain=foo.com; Path=/; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly')).to.deep.equal({ 255 | name: 'ssid', 256 | value: 'Ap4P….GTEq', 257 | domain: '.foo.com', 258 | path: '/', 259 | httponly: true, 260 | secure: true, 261 | expires: new Date('Wed, 13 Jan 2021 22:23:01 GMT') 262 | }); 263 | }); 264 | 265 | it('should ignore invalid expire header', () => { 266 | expect(biskviit.parse('theme=plain; Expires=Wed, 13 Jan 2021 22:23:01 GMT')).to.deep.equal({ 267 | name: 'theme', 268 | value: 'plain', 269 | expires: new Date('Wed, 13 Jan 2021 22:23:01 GMT') 270 | }); 271 | 272 | expect(biskviit.parse('theme=plain; Expires=ZZZZZZZZ GMT')).to.deep.equal({ 273 | name: 'theme', 274 | value: 'plain' 275 | }); 276 | }); 277 | }); 278 | 279 | describe('Listing', () => { 280 | beforeEach(() => { 281 | biskviit.cookies = [ 282 | { 283 | name: 'ssid1', 284 | value: 'Ap4P….GTEq1', 285 | domain: '.foo.com', 286 | path: '/', 287 | httponly: true, 288 | secure: true, 289 | expires: new Date('Wed, 13 Jan 2021 22:23:01 GMT') 290 | }, 291 | { 292 | name: 'ssid2', 293 | value: 'Ap4P….GTEq2', 294 | domain: '.foo.com', 295 | path: '/', 296 | httponly: true, 297 | secure: true, 298 | expires: new Date('Wed, 13 Jan 1900 22:23:01 GMT') 299 | }, 300 | { 301 | name: 'ssid3', 302 | value: 'Ap4P….GTEq3', 303 | domain: 'foo.com', 304 | path: '/', 305 | httponly: true, 306 | secure: true, 307 | expires: new Date('Wed, 13 Jan 2021 22:23:01 GMT') 308 | }, 309 | { 310 | name: 'ssid4', 311 | value: 'Ap4P….GTEq4', 312 | domain: 'www.foo.com', 313 | path: '/', 314 | httponly: true, 315 | secure: true, 316 | expires: new Date('Wed, 13 Jan 2021 22:23:01 GMT') 317 | }, 318 | { 319 | name: 'ssid5', 320 | value: 'Ap4P….GTEq5', 321 | domain: 'broo.com', 322 | path: '/', 323 | httponly: true, 324 | secure: true, 325 | expires: new Date('Wed, 13 Jan 2021 22:23:01 GMT') 326 | } 327 | ]; 328 | }); 329 | 330 | describe('#list', () => { 331 | it('should return matching cookies for an URL', () => { 332 | expect(biskviit.list('https://www.foo.com')).to.deep.equal([ 333 | { 334 | name: 'ssid1', 335 | value: 'Ap4P….GTEq1', 336 | domain: '.foo.com', 337 | path: '/', 338 | httponly: true, 339 | secure: true, 340 | expires: new Date('Wed, 13 Jan 2021 22:23:01 GMT') 341 | }, 342 | { 343 | name: 'ssid4', 344 | value: 'Ap4P….GTEq4', 345 | domain: 'www.foo.com', 346 | path: '/', 347 | httponly: true, 348 | secure: true, 349 | expires: new Date('Wed, 13 Jan 2021 22:23:01 GMT') 350 | } 351 | ]); 352 | }); 353 | }); 354 | 355 | describe('#get', () => { 356 | it('should return matching cookies for an URL', () => { 357 | expect(biskviit.get('https://www.foo.com')).to.equal('ssid1=Ap4P….GTEq1; ssid4=Ap4P….GTEq4'); 358 | }); 359 | }); 360 | }); 361 | 362 | describe('#set', () => { 363 | it('should set cookie', () => { 364 | // short 365 | biskviit.set('theme=plain', 'https://foo.com/'); 366 | // long 367 | biskviit.set('SSID=Ap4P….GTEq; Domain=foo.com; Path=/test; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly', 'https://foo.com/'); 368 | // subdomains 369 | biskviit.set('SSID=Ap4P….GTEq; Domain=.foo.com; Path=/; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly', 'https://www.foo.com/'); 370 | // invalid cors 371 | biskviit.set('invalid_1=cors; domain=example.com', 'https://foo.com/'); 372 | biskviit.set('invalid_2=cors; domain=www.foo.com', 'https://foo.com/'); 373 | // invalid date 374 | biskviit.set('invalid_3=date; Expires=zzzz', 'https://foo.com/'); 375 | // invalid tld 376 | biskviit.set('invalid_4=cors; domain=.co.uk', 'https://foo.co.uk/'); 377 | // should not be added 378 | biskviit.set('expired_1=date; Expires=1999-01-01 01:01:01 GMT', 'https://foo.com/'); 379 | 380 | expect( 381 | biskviit.cookies.map(cookie => { 382 | delete cookie.expires; 383 | return cookie; 384 | }) 385 | ).to.deep.equal([ 386 | { 387 | name: 'theme', 388 | value: 'plain', 389 | domain: 'foo.com', 390 | path: '/' 391 | }, 392 | { 393 | name: 'ssid', 394 | value: 'Ap4P….GTEq', 395 | domain: 'foo.com', 396 | path: '/test', 397 | secure: true, 398 | httponly: true 399 | }, 400 | { 401 | name: 'ssid', 402 | value: 'Ap4P….GTEq', 403 | domain: 'www.foo.com', 404 | path: '/', 405 | secure: true, 406 | httponly: true 407 | }, 408 | { 409 | name: 'invalid_1', 410 | value: 'cors', 411 | domain: 'foo.com', 412 | path: '/' 413 | }, 414 | { 415 | name: 'invalid_2', 416 | value: 'cors', 417 | domain: 'foo.com', 418 | path: '/' 419 | }, 420 | { 421 | name: 'invalid_3', 422 | value: 'date', 423 | domain: 'foo.com', 424 | path: '/' 425 | }, 426 | { 427 | name: 'invalid_4', 428 | value: 'cors', 429 | domain: 'foo.co.uk', 430 | path: '/' 431 | } 432 | ]); 433 | }); 434 | }); 435 | }); 436 | --------------------------------------------------------------------------------