diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 62562b7..0000000 --- a/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -coverage -node_modules diff --git a/.eslintrc.yml b/.eslintrc.yml deleted file mode 100644 index 44febf6..0000000 --- a/.eslintrc.yml +++ /dev/null @@ -1,11 +0,0 @@ -extends: - - plugin:markdown/recommended -plugins: - - markdown -overrides: - - files: '**/*.md' - processor: 'markdown/markdown' -rules: - eol-last: error - indent: ["error", 2, { "SwitchCase": 1 }] - no-trailing-spaces: error diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9da9752..92c5aba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,253 +1,27 @@ name: ci on: -- pull_request -- push + - pull_request + - push jobs: test: - runs-on: ubuntu-20.04 + name: Node.js ${{ matrix.node-version }} + runs-on: ubuntu-latest strategy: matrix: - name: - - Node.js 0.6 - - Node.js 0.8 - - Node.js 0.10 - - Node.js 0.12 - - io.js 1.x - - io.js 2.x - - io.js 3.x - - Node.js 4.x - - Node.js 5.x - - Node.js 6.x - - Node.js 7.x - - Node.js 8.x - - Node.js 9.x - - Node.js 10.x - - Node.js 11.x - - Node.js 12.x - - Node.js 13.x - - Node.js 14.x - - Node.js 15.x - - Node.js 16.x - - Node.js 17.x - - Node.js 18.x - - Node.js 19.x - - Node.js 20.x - - Node.js 21.x - - include: - - name: Node.js 0.6 - node-version: "0.6" - npm-i: mocha@1.21.5 - npm-rm: beautify-benchmark benchmark nyc top-sites - - - name: Node.js 0.8 - node-version: "0.8" - npm-i: mocha@2.5.3 - npm-rm: beautify-benchmark benchmark nyc top-sites - - - name: Node.js 0.10 - node-version: "0.10" - npm-i: mocha@3.5.3 nyc@10.3.2 - npm-rm: beautify-benchmark benchmark top-sites - - - name: Node.js 0.12 - node-version: "0.12" - npm-i: mocha@3.5.3 nyc@10.3.2 - npm-rm: beautify-benchmark benchmark top-sites - - - name: io.js 1.x - node-version: "1.8" - npm-i: mocha@3.5.3 nyc@10.3.2 - npm-rm: beautify-benchmark benchmark top-sites - - - name: io.js 2.x - node-version: "2.5" - npm-i: mocha@3.5.3 nyc@10.3.2 - npm-rm: beautify-benchmark benchmark top-sites - - - name: io.js 3.x - node-version: "3.3" - npm-i: mocha@3.5.3 nyc@10.3.2 - npm-rm: beautify-benchmark benchmark top-sites - - - name: Node.js 4.x - node-version: "4.9" - npm-i: mocha@5.2.0 nyc@11.9.0 - npm-rm: beautify-benchmark benchmark top-sites - - - name: Node.js 5.x - node-version: "5.12" - npm-i: mocha@5.2.0 nyc@11.9.0 - npm-rm: beautify-benchmark benchmark top-sites - - - name: Node.js 6.x - node-version: "6.17" - npm-i: mocha@6.2.2 nyc@14.1.1 - npm-rm: beautify-benchmark benchmark top-sites - - - name: Node.js 7.x - node-version: "7.10" - npm-i: mocha@6.2.2 nyc@14.1.1 - npm-rm: beautify-benchmark benchmark top-sites - - - name: Node.js 8.x - node-version: "8.17" - npm-i: mocha@7.1.2 nyc@14.1.1 - npm-rm: beautify-benchmark benchmark top-sites - - - name: Node.js 9.x - node-version: "9.11" - npm-i: mocha@7.1.2 nyc@14.1.1 - npm-rm: beautify-benchmark benchmark top-sites - - - name: Node.js 10.x - node-version: "10.24" - npm-i: mocha@8.4.0 - npm-rm: beautify-benchmark benchmark top-sites - - - name: Node.js 11.x - node-version: "11.15" - npm-i: mocha@8.4.0 - npm-rm: beautify-benchmark benchmark top-sites - - - name: Node.js 12.x - node-version: "12.22" - npm-i: mocha@9.2.2 - npm-rm: beautify-benchmark benchmark top-sites - - - name: Node.js 13.x - node-version: "13.14" - npm-i: mocha@9.2.2 - npm-rm: beautify-benchmark benchmark top-sites - - - name: Node.js 14.x - node-version: "14.21" - npm-rm: beautify-benchmark benchmark top-sites - - - name: Node.js 15.x - node-version: "15.14" - npm-rm: beautify-benchmark benchmark top-sites - - - name: Node.js 16.x - node-version: "16.20" - npm-rm: beautify-benchmark benchmark top-sites - - - name: Node.js 17.x - node-version: "17.9" - npm-rm: beautify-benchmark benchmark top-sites - - - name: Node.js 18.x - node-version: "18.18" - npm-rm: beautify-benchmark benchmark top-sites - - - name: Node.js 19.x - node-version: "19.9" - npm-rm: beautify-benchmark benchmark top-sites - - - name: Node.js 20.x - node-version: "20.9" - npm-rm: beautify-benchmark benchmark top-sites - - - name: Node.js 21.x - node-version: "21.1" - - steps: - - uses: actions/checkout@v3 - - - name: Install Node.js ${{ matrix.node-version }} - shell: bash -eo pipefail -l {0} - run: | - if [[ "${{ matrix.node-version }}" == 0.6* ]]; then - sudo sh -c 'echo "deb http://us.archive.ubuntu.com/ubuntu/ bionic universe" >> /etc/apt/sources.list' - sudo sh -c 'echo "deb http://security.ubuntu.com/ubuntu bionic-security main" >> /etc/apt/sources.list' - sudo apt-get update - sudo apt-get install g++-4.8 gcc-4.8 libssl1.0-dev python2 python-is-python2 - export CC=/usr/bin/gcc-4.8 - export CXX=/usr/bin/g++-4.8 - fi - nvm install --default ${{ matrix.node-version }} - if [[ "${{ matrix.node-version }}" == 0.* && "$(cut -d. -f2 <<< "${{ matrix.node-version }}")" -lt 10 ]]; then - nvm install --alias=npm 0.10 - nvm use ${{ matrix.node-version }} - if [[ "$(npm -v)" == 1.1.* ]]; then - nvm exec npm npm install -g npm@1.1 - ln -fs "$(which npm)" "$(dirname "$(nvm which npm)")/npm" - else - sed -i '1s;^.*$;'"$(printf '#!%q' "$(nvm which npm)")"';' "$(readlink -f "$(which npm)")" - fi - npm config set strict-ssl false - fi - dirname "$(nvm which ${{ matrix.node-version }})" >> "$GITHUB_PATH" - - - name: Configure npm - run: | - if [[ "$(npm config get package-lock)" == "true" ]]; then - npm config set package-lock false - else - npm config set shrinkwrap false - fi - - - name: Remove npm module(s) ${{ matrix.npm-rm }} - run: npm rm --silent --save-dev ${{ matrix.npm-rm }} - if: matrix.npm-rm != '' - - - name: Install npm module(s) ${{ matrix.npm-i }} - run: npm install --save-dev ${{ matrix.npm-i }} - if: matrix.npm-i != '' - - - name: Setup Node.js version-specific dependencies - shell: bash - run: | - # eslint for linting - # - remove on Node.js < 12 - if [[ "$(cut -d. -f1 <<< "${{ matrix.node-version }}")" -lt 12 ]]; then - node -pe 'Object.keys(require("./package").devDependencies).join("\n")' | \ - grep -E '^eslint(-|$)' | \ - sort -r | \ - xargs -n1 npm rm --silent --save-dev - fi - - - name: Install Node.js dependencies - run: npm install - - - name: List environment - id: list_env - shell: bash - run: | - echo "node@$(node -v)" - echo "npm@$(npm -v)" - npm -s ls ||: - (npm -s ls --depth=0 ||:) | awk -F'[ @]' 'NR>1 && $2 { print $2 "=" $3 }' >> "$GITHUB_OUTPUT" - - - name: Run tests - shell: bash - run: | - if npm -ps ls nyc | grep -q nyc; then - npm run test-ci - else - npm test - fi - - - name: Lint code - if: steps.list_env.outputs.eslint != '' - run: npm run lint - - - name: Collect code coverage - uses: coverallsapp/github-action@master - if: steps.list_env.outputs.nyc != '' - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - flag-name: run-${{ matrix.test_number }} - parallel: true - - coverage: - needs: test - runs-on: ubuntu-latest + node-version: + - "16" + - "18" + - "20" + - "*" steps: - - name: Upload code coverage - uses: coverallsapp/github-action@master - with: - github-token: ${{ secrets.github_token }} - parallel-finished: true + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + - run: npm ci + - run: npm test + - uses: codecov/codecov-action@v4 + with: + name: Node.js ${{ matrix.node-version }} diff --git a/.gitignore b/.gitignore index f15b98e..e9c5519 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ coverage/ node_modules/ npm-debug.log package-lock.json +dist/ +*.tsbuildinfo diff --git a/README.md b/README.md index 71fdac1..cfbc5e6 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ $ npm install cookie ## API ```js -var cookie = require('cookie'); +var cookie = require("cookie"); ``` ### cookie.parse(str, options) @@ -31,7 +31,7 @@ The `str` argument is the string representing a `Cookie` header value and `optio optional object containing additional parsing options. ```js -var cookies = cookie.parse('foo=bar; equation=E%3Dmc%5E2'); +var cookies = cookie.parse("foo=bar; equation=E%3Dmc%5E2"); // { foo: 'bar', equation: 'E=mc^2' } ``` @@ -58,7 +58,7 @@ name for the cookie, the `value` argument is the value to set the cookie to, and argument is an optional object containing additional serialization options. ```js -var setCookie = cookie.serialize('foo', 'bar'); +var setCookie = cookie.serialize("foo", "bar"); // foo=bar ``` @@ -127,9 +127,9 @@ is considered the ["default path"][rfc-6265-5.1.4]. Specifies the `string` to be the value for the [`Priority` `Set-Cookie` attribute][rfc-west-cookie-priority-00-4.1]. - - `'low'` will set the `Priority` attribute to `Low`. - - `'medium'` will set the `Priority` attribute to `Medium`, the default priority when not set. - - `'high'` will set the `Priority` attribute to `High`. +- `'low'` will set the `Priority` attribute to `Low`. +- `'medium'` will set the `Priority` attribute to `Medium`, the default priority when not set. +- `'high'` will set the `Priority` attribute to `High`. More information about the different priority levels can be found in [the specification][rfc-west-cookie-priority-00-4.1]. @@ -141,11 +141,11 @@ This also means many clients may ignore this attribute until they understand it. Specifies the `boolean` or `string` to be the value for the [`SameSite` `Set-Cookie` attribute][rfc-6265bis-09-5.4.7]. - - `true` will set the `SameSite` attribute to `Strict` for strict same site enforcement. - - `false` will not set the `SameSite` attribute. - - `'lax'` will set the `SameSite` attribute to `Lax` for lax same site enforcement. - - `'none'` will set the `SameSite` attribute to `None` for an explicit cross-site cookie. - - `'strict'` will set the `SameSite` attribute to `Strict` for strict same site enforcement. +- `true` will set the `SameSite` attribute to `Strict` for strict same site enforcement. +- `false` will not set the `SameSite` attribute. +- `'lax'` will set the `SameSite` attribute to `Lax` for lax same site enforcement. +- `'none'` will set the `SameSite` attribute to `None` for an explicit cross-site cookie. +- `'strict'` will set the `SameSite` attribute to `Strict` for strict same site enforcement. More information about the different enforcement levels can be found in [the specification][rfc-6265bis-09-5.4.7]. @@ -167,10 +167,10 @@ The following example uses this module in conjunction with the Node.js core HTTP to prompt a user for their name and display it back on future visits. ```js -var cookie = require('cookie'); -var escapeHtml = require('escape-html'); -var http = require('http'); -var url = require('url'); +var cookie = require("cookie"); +var escapeHtml = require("escape-html"); +var http = require("http"); +var url = require("url"); function onRequest(req, res) { // Parse the query string @@ -178,35 +178,40 @@ function onRequest(req, res) { if (query && query.name) { // Set a new cookie with the name - res.setHeader('Set-Cookie', cookie.serialize('name', String(query.name), { - httpOnly: true, - maxAge: 60 * 60 * 24 * 7 // 1 week - })); + res.setHeader( + "Set-Cookie", + cookie.serialize("name", String(query.name), { + httpOnly: true, + maxAge: 60 * 60 * 24 * 7, // 1 week + }), + ); // Redirect back after setting cookie res.statusCode = 302; - res.setHeader('Location', req.headers.referer || '/'); + res.setHeader("Location", req.headers.referer || "/"); res.end(); return; } // Parse the cookies on the request - var cookies = cookie.parse(req.headers.cookie || ''); + var cookies = cookie.parse(req.headers.cookie || ""); // Get the visitor name set in the cookie var name = cookies.name; - res.setHeader('Content-Type', 'text/html; charset=UTF-8'); + res.setHeader("Content-Type", "text/html; charset=UTF-8"); if (name) { - res.write('

Welcome back, ' + escapeHtml(name) + '!

'); + res.write("

Welcome back, " + escapeHtml(name) + "!

"); } else { - res.write('

Hello, new visitor!

'); + res.write("

Hello, new visitor!

"); } res.write('
'); - res.write(' '); - res.end('
'); + res.write( + ' ', + ); + res.end(""); } http.createServer(onRequest).listen(3000); diff --git a/benchmark/index.js b/benchmark/index.js deleted file mode 100644 index 8a39f3a..0000000 --- a/benchmark/index.js +++ /dev/null @@ -1,34 +0,0 @@ -var fs = require('fs') -var path = require('path') -var spawn = require('child_process').spawn - -var exe = process.argv[0] -var cwd = process.cwd() - -for (var dep in process.versions) { - console.log(' %s@%s', dep, process.versions[dep]) -} - -console.log('') - -runScripts(fs.readdirSync(__dirname)) - -function runScripts (fileNames) { - var fileName = fileNames.shift() - - if (!fileName) return - if (!/\.js$/i.test(fileName)) return runScripts(fileNames) - if (fileName.toLowerCase() === 'index.js') return runScripts(fileNames) - - var fullPath = path.join(__dirname, fileName) - - console.log('> %s %s', exe, path.relative(cwd, fullPath)) - - var proc = spawn(exe, [fullPath], { - 'stdio': 'inherit' - }) - - proc.on('exit', function () { - runScripts(fileNames) - }) -} diff --git a/benchmark/parse-top.js b/benchmark/parse-top.js deleted file mode 100644 index 4cbfd00..0000000 --- a/benchmark/parse-top.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Module dependencies. - */ - -var benchmark = require('benchmark') -var benchmarks = require('beautify-benchmark') -var top = require('./parse-top.json') - -/** - * Globals for benchmark.js - */ - -global.cookie = require('..') - -var suite = new benchmark.Suite() - -Object.keys(top).forEach(function (domain) { - suite.add({ - name: 'parse ' + domain, - minSamples: 100, - fn: 'var val = cookie.parse(' + JSON.stringify(top[domain]) + ')' - }) -}) - -suite.on('start', function () { - process.stdout.write(' cookie.parse - top sites\n\n') -}) - -suite.on('cycle', function (event) { - benchmarks.add(event.target) -}) - -suite.on('complete', function () { - benchmarks.log() -}) - -suite.run({ async: false }) diff --git a/benchmark/parse.js b/benchmark/parse.js deleted file mode 100644 index 567f4b6..0000000 --- a/benchmark/parse.js +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Module dependencies. - */ - -var benchmark = require('benchmark') -var benchmarks = require('beautify-benchmark') - -/** - * Globals for benchmark.js - */ - -global.cookie = require('..') - -var suite = new benchmark.Suite() - -suite.add({ - name: 'simple', - minSamples: 100, - fn: 'var val = cookie.parse("foo=bar")' -}) - -suite.add({ - name: 'decode', - minSamples: 100, - fn: 'var val = cookie.parse("foo=hello%20there!")' -}) - -suite.add({ - name: 'unquote', - minSamples: 100, - fn: 'var val = cookie.parse("foo=\\"foo bar\\"")' -}) - -suite.add({ - name: 'duplicates', - minSamples: 100, - fn: 'var val = cookie.parse(' + JSON.stringify(gencookies(2) + '; ' + gencookies(2)) + ')' -}) - -suite.add({ - name: '10 cookies', - minSamples: 100, - fn: 'var val = cookie.parse(' + JSON.stringify(gencookies(10)) + ')' -}) - -suite.add({ - name: '100 cookies', - minSamples: 100, - fn: 'var val = cookie.parse(' + JSON.stringify(gencookies(100)) + ')' -}) - -suite.on('start', function () { - process.stdout.write(' cookie.parse - generic\n\n') -}) - -suite.on('cycle', function (event) { - benchmarks.add(event.target) -}) - -suite.on('complete', function onComplete () { - benchmarks.log() -}) - -suite.run({ async: false }) - -function gencookies (num) { - var str = '' - - for (var i = 0; i < num; i++) { - str += '; foo' + i + '=bar' - } - - return str.slice(2) -} diff --git a/package.json b/package.json index f498ea7..ed648a8 100644 --- a/package.json +++ b/package.json @@ -1,44 +1,42 @@ { "name": "cookie", - "description": "HTTP server cookie parsing and serialization", "version": "0.7.1", - "author": "Roman Shtylman ", - "contributors": [ - "Douglas Christopher Wilson " - ], - "license": "MIT", + "description": "HTTP server cookie parsing and serialization", "keywords": [ "cookie", "cookies" ], "repository": "jshttp/cookie", - "devDependencies": { - "beautify-benchmark": "0.2.4", - "benchmark": "2.1.4", - "eslint": "8.53.0", - "eslint-plugin-markdown": "3.0.1", - "mocha": "10.2.0", - "nyc": "15.1.0", - "safe-buffer": "5.2.1", - "top-sites": "1.1.194" - }, + "license": "MIT", + "author": "Roman Shtylman ", + "contributors": [ + "Douglas Christopher Wilson " + ], + "main": "dist/index.js", + "types": "dist/index.d.ts", "files": [ - "HISTORY.md", - "LICENSE", - "README.md", - "SECURITY.md", - "index.js" + "dist/" ], - "main": "index.js", + "scripts": { + "bench": "vitest bench", + "build": "ts-scripts build", + "format": "ts-scripts format", + "prepare": "ts-scripts install", + "prepublishOnly": "npm run build", + "specs": "ts-scripts specs", + "test": "ts-scripts test" + }, + "devDependencies": { + "@borderless/ts-scripts": "^0.15.0", + "@vitest/coverage-v8": "^2.1.2", + "top-sites": "1.1.194", + "typescript": "^5.6.2", + "vitest": "^2.1.2" + }, "engines": { - "node": ">= 0.6" + "node": ">=16" }, - "scripts": { - "bench": "node benchmark/index.js", - "lint": "eslint .", - "test": "mocha --reporter spec --bail --check-leaks test/", - "test-ci": "nyc --reporter=lcov --reporter=text npm test", - "test-cov": "nyc --reporter=html --reporter=text npm test", - "update-bench": "node scripts/update-benchmark.js" + "ts-scripts": { + "project": "tsconfig.build.json" } } diff --git a/benchmark/parse-top.json b/scripts/parse-top.json similarity index 100% rename from benchmark/parse-top.json rename to scripts/parse-top.json diff --git a/scripts/update-benchmark.js b/scripts/update-benchmark.js index 23cdcfe..ebe032d 100644 --- a/scripts/update-benchmark.js +++ b/scripts/update-benchmark.js @@ -1,69 +1,90 @@ -'use strict' +"use strict"; -var fs = require('fs') -var http = require('http') -var https = require('https') -var path = require('path') -var topSites = require('top-sites') -var url = require('url') +var fs = require("fs"); +var http = require("http"); +var https = require("https"); +var path = require("path"); +var topSites = require("top-sites"); +var url = require("url"); -var BENCH_COOKIES_FILE = path.join(__dirname, '..', 'benchmark', 'parse-top.json') +var BENCH_COOKIES_FILE = path.join(__dirname, "parse-top.json"); getAllCookies(topSites.slice(0, 20), function (err, cookies) { - if (err) throw err - var str = '{\n' + - Object.keys(cookies).sort().map(function (key) { - return ' ' + JSON.stringify(key) + ': ' + JSON.stringify(cookies[key]) - }).join(',\n') + - '\n}\n' - fs.writeFileSync(BENCH_COOKIES_FILE, str) -}) + if (err) throw err; + var str = + "{\n" + + Object.keys(cookies) + .sort() + .map(function (key) { + return " " + JSON.stringify(key) + ": " + JSON.stringify(cookies[key]); + }) + .join(",\n") + + "\n}\n"; + fs.writeFileSync(BENCH_COOKIES_FILE, str); +}); -function get (href, callback) { - var protocol = url.parse(href, false, true).protocol - var proto = protocol === 'https:' ? https : http +function get(href, callback) { + var protocol = url.parse(href, false, true).protocol; + var proto = protocol === "https:" ? https : http; - proto.get(href) - .on('error', callback) - .on('response', function (res) { - if (res.headers.location && res.statusCode >= 300 && res.statusCode < 400) { - get(url.resolve(href, res.headers.location), callback) + proto + .get(href) + .on("error", callback) + .on("response", function (res) { + if ( + res.headers.location && + res.statusCode >= 300 && + res.statusCode < 400 + ) { + get(url.resolve(href, res.headers.location), callback); } else { - callback(null, res) + callback(null, res); } - }) + }); } -function getAllCookies (sites, callback) { - var all = Object.create(null) - var wait = sites.length +function getAllCookies(sites, callback) { + var all = Object.create(null); + var wait = sites.length; sites.forEach(function (site) { getCookies(site, function (err, cookies) { if (!err && cookies.length) { - all[site.rootDomain] = cookies.map(obfuscate).join('; ') + all[site.rootDomain] = cookies.map(obfuscate).join("; "); } if (!--wait) { - callback(null, all) + callback(null, all); } - }) - }) + }); + }); } -function getCookies (site, callback) { - var href = url.format({ hostname: site.rootDomain, protocol: 'http' }) +function getCookies(site, callback) { + var href = url.format({ hostname: site.rootDomain, protocol: "http" }); get(href, function (err, res) { - if (err) return callback(err) - var cookies = (res.headers['set-cookie'] || []).map(function (c) { return c.split(';')[0] }) - callback(null, cookies) - }) + if (err) return callback(err); + var cookies = (res.headers["set-cookie"] || []).map(function (c) { + return c.split(";")[0]; + }); + callback(null, cookies); + }); } -function obfuscate (str) { +function obfuscate(str) { return str - .replace(/%[0-9a-f]{2}/gi, function () { return '%__' }) - .replace(/[a-z]/g, function () { return 'l' }) - .replace(/[A-Z]/g, function () { return 'U' }) - .replace(/[0-9]/g, function () { return '0' }) - .replace(/%__/g, function () { return '%22' }) + .replace(/%[0-9a-f]{2}/gi, function () { + return "%__"; + }) + .replace(/[a-z]/g, function () { + return "l"; + }) + .replace(/[A-Z]/g, function () { + return "U"; + }) + .replace(/[0-9]/g, function () { + return "0"; + }) + .replace(/%__/g, function () { + return "%22"; + }); } diff --git a/index.js b/src/index.ts similarity index 50% rename from index.js rename to src/index.ts index 51a58cb..d887761 100644 --- a/index.js +++ b/src/index.ts @@ -1,27 +1,3 @@ -/*! - * cookie - * Copyright(c) 2012-2014 Roman Shtylman - * Copyright(c) 2015 Douglas Christopher Wilson - * MIT Licensed - */ - -'use strict'; - -/** - * Module exports. - * @public - */ - -exports.parse = parse; -exports.serialize = serialize; - -/** - * Module variables. - * @private - */ - -var __toString = Object.prototype.toString - /** * RegExp to match cookie-name in RFC 6265 sec 4.1.1 * This refers out to the obsoleted definition of token in RFC 2616 sec 2.2 @@ -33,8 +9,7 @@ var __toString = Object.prototype.toString * "*" / "+" / "-" / "." / "^" / "_" / * "`" / "|" / "~" / DIGIT / ALPHA */ - -var cookieNameRegExp = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/; +const cookieNameRegExp = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/; /** * RegExp to match cookie-value in RFC 6265 sec 4.1.1 @@ -45,8 +20,8 @@ var cookieNameRegExp = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/; * ; whitespace DQUOTE, comma, semicolon, * ; and backslash */ - -var cookieValueRegExp = /^("?)[\u0021\u0023-\u002B\u002D-\u003A\u003C-\u005B\u005D-\u007E]*\1$/; +const cookieValueRegExp = + /^("?)[\u0021\u0023-\u002B\u002D-\u003A\u003C-\u005B\u005D-\u007E]*\1$/; /** * RegExp to match domain-value in RFC 6265 sec 4.1.1 @@ -71,8 +46,8 @@ var cookieValueRegExp = /^("?)[\u0021\u0023-\u002B\u002D-\u003A\u003C-\u005B\u00 * character is not permitted, but a trailing %x2E ("."), if present, will * cause the user agent to ignore the attribute.) */ - -var domainValueRegExp = /^([.]?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)([.][a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/i; +const domainValueRegExp = + /^([.]?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)([.][a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/i; /** * RegExp to match path-value in RFC 6265 sec 4.1.1 @@ -81,90 +56,105 @@ var domainValueRegExp = /^([.]?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)([.][a-z0-9]([ * CHAR = %x01-7F * ; defined in RFC 5234 appendix B.1 */ +const pathValueRegExp = /^[\u0020-\u003A\u003D-\u007E]*$/; -var pathValueRegExp = /^[\u0020-\u003A\u003D-\u007E]*$/; +/** + * Parse options. + */ +export interface ParseOptions { + decode?: (str: string) => string; +} /** * Parse a cookie header. * * Parse the given cookie header string into an object * The object has the various cookies as keys(names) => values - * - * @param {string} str - * @param {object} [opt] - * @return {object} - * @public */ - -function parse(str, opt) { - if (typeof str !== 'string') { - throw new TypeError('argument str must be a string'); - } - - var obj = {}; - var len = str.length; +export function parse( + str: string, + options?: ParseOptions, +): Record { + const obj: Record = {}; + const len = str.length; // RFC 6265 sec 4.1.1, RFC 2616 2.2 defines a cookie name consists of one char minimum, plus '='. if (len < 2) return obj; - var dec = (opt && opt.decode) || decode; - var index = 0; - var eqIdx = 0; - var endIdx = 0; + const dec = options?.decode || decode; + let index = 0; do { - eqIdx = str.indexOf('=', index); + const eqIdx = str.indexOf("=", index); if (eqIdx === -1) break; // No more cookie pairs. - endIdx = str.indexOf(';', index); + const colonIdx = str.indexOf(";", index); + const endIdx = colonIdx === -1 ? len : colonIdx; - if (endIdx === -1) { - endIdx = len; - } else if (eqIdx > endIdx) { + if (eqIdx > endIdx) { // backtrack on prior semicolon - index = str.lastIndexOf(';', eqIdx - 1) + 1; + index = str.lastIndexOf(";", eqIdx - 1) + 1; continue; } - var keyStartIdx = startIndex(str, index, eqIdx); - var keyEndIdx = endIndex(str, eqIdx, keyStartIdx); - var key = str.slice(keyStartIdx, keyEndIdx); + const keyStartIdx = startIndex(str, index, eqIdx); + const keyEndIdx = endIndex(str, eqIdx, keyStartIdx); + const key = str.slice(keyStartIdx, keyEndIdx); // only assign once if (!obj.hasOwnProperty(key)) { - var valStartIdx = startIndex(str, eqIdx + 1, endIdx); - var valEndIdx = endIndex(str, endIdx, valStartIdx); + let valStartIdx = startIndex(str, eqIdx + 1, endIdx); + let valEndIdx = endIndex(str, endIdx, valStartIdx); - if (str.charCodeAt(valStartIdx) === 0x22 /* " */ && str.charCodeAt(valEndIdx - 1) === 0x22 /* " */) { + if ( + str.charCodeAt(valStartIdx) === 0x22 /* " */ && + str.charCodeAt(valEndIdx - 1) === 0x22 /* " */ + ) { valStartIdx++; valEndIdx--; } - var val = str.slice(valStartIdx, valEndIdx); + const val = str.slice(valStartIdx, valEndIdx); obj[key] = tryDecode(val, dec); } - index = endIdx + 1 + index = endIdx + 1; } while (index < len); return obj; } -function startIndex(str, index, max) { +function startIndex(str: string, index: number, max: number) { do { - var code = str.charCodeAt(index); + const code = str.charCodeAt(index); if (code !== 0x20 /* */ && code !== 0x09 /* \t */) return index; } while (++index < max); return max; } -function endIndex(str, index, min) { +function endIndex(str: string, index: number, min: number) { while (index > min) { - var code = str.charCodeAt(--index); + const code = str.charCodeAt(--index); if (code !== 0x20 /* */ && code !== 0x09 /* \t */) return index + 1; } return min; } +/** + * Serialize options. + */ +export interface SerializeOptions { + encode?: (str: string) => string; + maxAge?: number; + domain?: string; + path?: string; + expires?: Date; + httpOnly?: boolean; + secure?: boolean; + partitioned?: boolean; + priority?: "low" | "medium" | "high"; + sameSite?: boolean | "lax" | "strict" | "none"; +} + /** * Serialize data into a cookie header. * @@ -173,120 +163,108 @@ function endIndex(str, index, min) { * * serialize('foo', 'bar', { httpOnly: true }) * => "foo=bar; httpOnly" - * - * @param {string} name - * @param {string} val - * @param {object} [opt] - * @return {string} - * @public */ - -function serialize(name, val, opt) { - var enc = (opt && opt.encode) || encodeURIComponent; - - if (typeof enc !== 'function') { - throw new TypeError('option encode is invalid'); +export function serialize( + name: string, + val: string, + opt?: SerializeOptions, +): string { + const enc = opt?.encode || encodeURIComponent; + + if (typeof enc !== "function") { + throw new TypeError("option encode is invalid"); } if (!cookieNameRegExp.test(name)) { - throw new TypeError('argument name is invalid'); + throw new TypeError("argument name is invalid"); } - var value = enc(val); + const value = enc(val); if (!cookieValueRegExp.test(value)) { - throw new TypeError('argument val is invalid'); + throw new TypeError("argument val is invalid"); } - var str = name + '=' + value; + let str = name + "=" + value; if (!opt) return str; - if (null != opt.maxAge) { - var maxAge = Math.floor(opt.maxAge); - - if (!isFinite(maxAge)) { - throw new TypeError('option maxAge is invalid') + if (opt.maxAge !== undefined) { + if (!Number.isInteger(opt.maxAge)) { + throw new TypeError("option maxAge is invalid"); } - str += '; Max-Age=' + maxAge; + str += "; Max-Age=" + opt.maxAge; } if (opt.domain) { if (!domainValueRegExp.test(opt.domain)) { - throw new TypeError('option domain is invalid'); + throw new TypeError("option domain is invalid"); } - str += '; Domain=' + opt.domain; + str += "; Domain=" + opt.domain; } if (opt.path) { if (!pathValueRegExp.test(opt.path)) { - throw new TypeError('option path is invalid'); + throw new TypeError("option path is invalid"); } - str += '; Path=' + opt.path; + str += "; Path=" + opt.path; } if (opt.expires) { - var expires = opt.expires - - if (!isDate(expires) || isNaN(expires.valueOf())) { - throw new TypeError('option expires is invalid'); + if ( + typeof opt.expires.toUTCString !== "function" || + !Number.isFinite(opt.expires.valueOf()) + ) { + throw new TypeError("option expires is invalid"); } - str += '; Expires=' + expires.toUTCString() + str += "; Expires=" + opt.expires.toUTCString(); } if (opt.httpOnly) { - str += '; HttpOnly'; + str += "; HttpOnly"; } if (opt.secure) { - str += '; Secure'; + str += "; Secure"; } if (opt.partitioned) { - str += '; Partitioned' + str += "; Partitioned"; } if (opt.priority) { - var priority = typeof opt.priority === 'string' - ? opt.priority.toLowerCase() : opt.priority; - - switch (priority) { - case 'low': - str += '; Priority=Low' - break - case 'medium': - str += '; Priority=Medium' - break - case 'high': - str += '; Priority=High' - break + switch (opt.priority) { + case "low": + str += "; Priority=Low"; + break; + case "medium": + str += "; Priority=Medium"; + break; + case "high": + str += "; Priority=High"; + break; default: - throw new TypeError('option priority is invalid') + throw new TypeError("option priority is invalid"); } } if (opt.sameSite) { - var sameSite = typeof opt.sameSite === 'string' - ? opt.sameSite.toLowerCase() : opt.sameSite; - - switch (sameSite) { + switch (opt.sameSite) { case true: - str += '; SameSite=Strict'; + case "strict": + str += "; SameSite=Strict"; break; - case 'lax': - str += '; SameSite=Lax'; + case "lax": + str += "; SameSite=Lax"; break; - case 'strict': - str += '; SameSite=Strict'; - break; - case 'none': - str += '; SameSite=None'; + case "none": + str += "; SameSite=None"; break; default: - throw new TypeError('option sameSite is invalid'); + throw new TypeError("option sameSite is invalid"); } } @@ -295,37 +273,15 @@ function serialize(name, val, opt) { /** * URL-decode string value. Optimized to skip native call when no %. - * - * @param {string} str - * @returns {string} */ - -function decode (str) { - return str.indexOf('%') !== -1 - ? decodeURIComponent(str) - : str -} - -/** - * Determine if value is a Date. - * - * @param {*} val - * @private - */ - -function isDate (val) { - return __toString.call(val) === '[object Date]'; +function decode(str: string): string { + return str.indexOf("%") !== -1 ? decodeURIComponent(str) : str; } /** * Try decoding a string using a decoding function. - * - * @param {string} str - * @param {function} decode - * @private */ - -function tryDecode(str, decode) { +function tryDecode(str: string, decode: (str: string) => string): string { try { return decode(str); } catch (e) { diff --git a/src/parse.bench.ts b/src/parse.bench.ts new file mode 100644 index 0000000..574f470 --- /dev/null +++ b/src/parse.bench.ts @@ -0,0 +1,47 @@ +import { describe, bench } from "vitest"; +import * as cookie from "./index.js"; +import top from "../scripts/parse-top.json"; + +describe("parse", () => { + bench("simple", () => { + cookie.parse("foo=bar"); + }); + + bench("decode", () => { + cookie.parse("foo=hello%20there!"); + }); + + bench("unquote", () => { + cookie.parse('foo="foo bar"'); + }); + + bench("duplicates", () => { + cookie.parse(genCookies(2) + "; " + genCookies(2)); + }); + + bench("10 cookies", () => { + cookie.parse(genCookies(10)); + }); + + bench("100 cookies", () => { + cookie.parse(genCookies(100)); + }); +}); + +describe("parse top-sites", () => { + Object.entries(top).forEach(function ([domain, value]) { + bench("parse " + domain, () => { + cookie.parse(value); + }); + }); +}); + +function genCookies(num: number) { + let str = ""; + + for (let i = 0; i < num; i++) { + str += "; foo" + i + "=bar"; + } + + return str.slice(2); +} diff --git a/src/parse.spec.ts b/src/parse.spec.ts new file mode 100644 index 0000000..224aa3d --- /dev/null +++ b/src/parse.spec.ts @@ -0,0 +1,96 @@ +import { describe, it, expect } from "vitest"; +import * as cookie from "./index.js"; + +describe("cookie.parse(str)", function () { + it("should parse cookie string to object", function () { + expect(cookie.parse("foo=bar")).toEqual({ foo: "bar" }); + expect(cookie.parse("foo=123")).toEqual({ foo: "123" }); + }); + + it("should ignore OWS", function () { + expect(cookie.parse("FOO = bar; baz = raz")).toEqual({ + FOO: "bar", + baz: "raz", + }); + }); + + it("should parse cookie with empty value", function () { + expect(cookie.parse("foo=; bar=")).toEqual({ foo: "", bar: "" }); + }); + + it("should parse cookie with minimum length", function () { + expect(cookie.parse("f=")).toEqual({ f: "" }); + expect(cookie.parse("f=;b=")).toEqual({ f: "", b: "" }); + }); + + it("should URL-decode values", function () { + expect(cookie.parse('foo="bar=123456789&name=Magic+Mouse"')).toEqual({ + foo: "bar=123456789&name=Magic+Mouse", + }); + + expect(cookie.parse("email=%20%22%2c%3b%2f")).toEqual({ email: ' ",;/' }); + }); + + it("should parse quoted values", function () { + expect(cookie.parse('foo="bar"')).toEqual({ foo: "bar" }); + expect(cookie.parse('foo=" a b c "')).toEqual({ foo: " a b c " }); + }); + + it("should trim whitespace around key and value", function () { + expect(cookie.parse(' foo = "bar" ')).toEqual({ foo: "bar" }); + expect(cookie.parse(" foo = bar ; fizz = buzz ")).toEqual({ + foo: "bar", + fizz: "buzz", + }); + expect(cookie.parse(' foo = " a b c " ')).toEqual({ foo: " a b c " }); + expect(cookie.parse(" = bar ")).toEqual({ "": "bar" }); + expect(cookie.parse(" foo = ")).toEqual({ foo: "" }); + expect(cookie.parse(" = ")).toEqual({ "": "" }); + expect(cookie.parse("\tfoo\t=\tbar\t")).toEqual({ foo: "bar" }); + }); + + it("should return original value on escape error", function () { + expect(cookie.parse("foo=%1;bar=bar")).toEqual({ foo: "%1", bar: "bar" }); + }); + + it("should ignore cookies without value", function () { + expect(cookie.parse("foo=bar;fizz ; buzz")).toEqual({ foo: "bar" }); + expect(cookie.parse(" fizz; foo= bar")).toEqual({ foo: "bar" }); + }); + + it("should ignore duplicate cookies", function () { + expect(cookie.parse("foo=%1;bar=bar;foo=boo")).toEqual({ + foo: "%1", + bar: "bar", + }); + expect(cookie.parse("foo=false;bar=bar;foo=true")).toEqual({ + foo: "false", + bar: "bar", + }); + expect(cookie.parse("foo=;bar=bar;foo=boo")).toEqual({ + foo: "", + bar: "bar", + }); + }); + + it("should parse native properties", function () { + expect(cookie.parse("toString=foo;valueOf=bar")).toEqual({ + toString: "foo", + valueOf: "bar", + }); + }); +}); + +describe("cookie.parse(str, options)", function () { + describe('with "decode" option', function () { + it("should specify alternative value decoder", function () { + expect( + cookie.parse('foo="YmFy"', { + decode: function (v) { + return Buffer.from(v, "base64").toString(); + }, + }), + ).toEqual({ foo: "bar" }); + }); + }); +}); diff --git a/src/serialize.spec.ts b/src/serialize.spec.ts new file mode 100644 index 0000000..e9a6ba8 --- /dev/null +++ b/src/serialize.spec.ts @@ -0,0 +1,338 @@ +import { describe, it, expect } from "vitest"; +import * as cookie from "./index.js"; + +describe("cookie.serialize(name, value)", function () { + it("should serialize name and value", function () { + expect(cookie.serialize("foo", "bar")).toEqual("foo=bar"); + }); + + it("should URL-encode value", function () { + expect(cookie.serialize("foo", "bar +baz")).toEqual("foo=bar%20%2Bbaz"); + }); + + it("should serialize empty value", function () { + expect(cookie.serialize("foo", "")).toEqual("foo="); + }); + + it("should serialize valid name", function () { + var validNames = [ + "foo", + "foo!bar", + "foo#bar", + "foo$bar", + "foo'bar", + "foo*bar", + "foo+bar", + "foo-bar", + "foo.bar", + "foo^bar", + "foo_bar", + "foo`bar", + "foo|bar", + "foo~bar", + "foo7bar", + ]; + + validNames.forEach(function (name) { + expect(cookie.serialize(name, "baz")).toEqual(name + "=baz"); + }); + }); + + it("should throw for invalid name", function () { + var invalidNames = [ + "foo\n", + "foo\u280a", + "foo/foo", + "foo,foo", + "foo;foo", + "foo@foo", + "foo[foo]", + "foo?foo", + "foo:foo", + "foo{foo}", + "foo foo", + "foo\tfoo", + 'foo"foo', + "foo', + ]; + + invalidPaths.forEach(function (path) { + expect( + cookie.serialize.bind(cookie, "foo", "bar", { path: path }), + ).toThrow(/option path is invalid/); + }); + }); + }); + + describe('with "priority" option', function () { + it("should throw on invalid priority", function () { + expect(function () { + cookie.serialize("foo", "bar", { priority: "foo" as any }); + }).toThrow(/option priority is invalid/); + }); + + it("should throw on non-string", function () { + expect(function () { + cookie.serialize("foo", "bar", { priority: 42 as any }); + }).toThrow(/option priority is invalid/); + }); + + it("should set priority low", function () { + expect(cookie.serialize("foo", "bar", { priority: "low" })).toEqual( + "foo=bar; Priority=Low", + ); + }); + + it("should set priority medium", function () { + expect(cookie.serialize("foo", "bar", { priority: "medium" })).toEqual( + "foo=bar; Priority=Medium", + ); + }); + + it("should set priority high", function () { + expect(cookie.serialize("foo", "bar", { priority: "high" })).toEqual( + "foo=bar; Priority=High", + ); + }); + }); + + describe('with "sameSite" option', function () { + it("should throw on invalid sameSite", function () { + expect(() => { + cookie.serialize("foo", "bar", { sameSite: "foo" as any }); + }).toThrow(/option sameSite is invalid/); + }); + + it("should set sameSite strict", function () { + expect(cookie.serialize("foo", "bar", { sameSite: "strict" })).toEqual( + "foo=bar; SameSite=Strict", + ); + }); + + it("should set sameSite lax", function () { + expect(cookie.serialize("foo", "bar", { sameSite: "lax" })).toEqual( + "foo=bar; SameSite=Lax", + ); + }); + + it("should set sameSite none", function () { + expect(cookie.serialize("foo", "bar", { sameSite: "none" })).toEqual( + "foo=bar; SameSite=None", + ); + }); + + it("should set sameSite strict when true", function () { + expect(cookie.serialize("foo", "bar", { sameSite: true })).toEqual( + "foo=bar; SameSite=Strict", + ); + }); + + it("should not set sameSite when false", function () { + expect(cookie.serialize("foo", "bar", { sameSite: false })).toEqual( + "foo=bar", + ); + }); + }); + + describe('with "secure" option', function () { + it("should include secure flag when true", function () { + expect(cookie.serialize("foo", "bar", { secure: true })).toEqual( + "foo=bar; Secure", + ); + }); + + it("should not include secure flag when false", function () { + expect(cookie.serialize("foo", "bar", { secure: false })).toEqual( + "foo=bar", + ); + }); + }); +}); diff --git a/test/parse.js b/test/parse.js deleted file mode 100644 index b5f65ed..0000000 --- a/test/parse.js +++ /dev/null @@ -1,85 +0,0 @@ - -var assert = require('assert'); -var Buffer = require('safe-buffer').Buffer - -var cookie = require('..'); - -describe('cookie.parse(str)', function () { - it('should throw with no arguments', function () { - assert.throws(cookie.parse.bind(), /argument str must be a string/) - }) - - it('should throw when not a string', function () { - assert.throws(cookie.parse.bind(null, 42), /argument str must be a string/) - }) - - it('should parse cookie string to object', function () { - assert.deepEqual(cookie.parse('foo=bar'), { foo: 'bar' }) - assert.deepEqual(cookie.parse('foo=123'), { foo: '123' }) - }) - - it('should ignore OWS', function () { - assert.deepEqual(cookie.parse('FOO = bar; baz = raz'), - { FOO: 'bar', baz: 'raz' }) - }) - - it('should parse cookie with empty value', function () { - assert.deepEqual(cookie.parse('foo=; bar='), { foo: '', bar: '' }) - }) - - it('should parse cookie with minimum length', function () { - assert.deepEqual(cookie.parse('f='), { f: '' }) - assert.deepEqual(cookie.parse('f=;b='), { f: '', b: '' }) - }) - - it('should URL-decode values', function () { - assert.deepEqual(cookie.parse('foo="bar=123456789&name=Magic+Mouse"'), - { foo: 'bar=123456789&name=Magic+Mouse' }) - - assert.deepEqual(cookie.parse('email=%20%22%2c%3b%2f'), { email: ' ",;/' }) - }) - - it('should parse quoted values', function () { - assert.deepEqual(cookie.parse('foo="bar"'), { foo: 'bar' }) - assert.deepEqual(cookie.parse('foo=" a b c "'), { foo: ' a b c ' }) - }) - - it('should trim whitespace around key and value', function () { - assert.deepEqual(cookie.parse(' foo = "bar" '), { foo: 'bar' }) - assert.deepEqual(cookie.parse(' foo = bar ; fizz = buzz '), { foo: 'bar', fizz: 'buzz' }) - assert.deepEqual(cookie.parse(' foo = " a b c " '), { foo: ' a b c ' }) - assert.deepEqual(cookie.parse(' = bar '), { '': 'bar' }) - assert.deepEqual(cookie.parse(' foo = '), { foo: '' }) - assert.deepEqual(cookie.parse(' = '), { '': '' }) - assert.deepEqual(cookie.parse('\tfoo\t=\tbar\t'), { foo: 'bar' }) - }) - - it('should return original value on escape error', function () { - assert.deepEqual(cookie.parse('foo=%1;bar=bar'), { foo: '%1', bar: 'bar' }) - }) - - it('should ignore cookies without value', function () { - assert.deepEqual(cookie.parse('foo=bar;fizz ; buzz'), { foo: 'bar' }) - assert.deepEqual(cookie.parse(' fizz; foo= bar'), { foo: 'bar' }) - }) - - it('should ignore duplicate cookies', function () { - assert.deepEqual(cookie.parse('foo=%1;bar=bar;foo=boo'), { foo: '%1', bar: 'bar' }) - assert.deepEqual(cookie.parse('foo=false;bar=bar;foo=true'), { foo: 'false', bar: 'bar' }) - assert.deepEqual(cookie.parse('foo=;bar=bar;foo=boo'), { foo: '', bar: 'bar' }) - }) - - it('should parse native properties', function () { - assert.deepEqual(cookie.parse('toString=foo;valueOf=bar'), { toString: 'foo', valueOf: 'bar' }) - }) -}) - -describe('cookie.parse(str, options)', function () { - describe('with "decode" option', function () { - it('should specify alternative value decoder', function () { - assert.deepEqual(cookie.parse('foo="YmFy"', { - decode: function (v) { return Buffer.from(v, 'base64').toString() } - }), { foo: 'bar' }) - }) - }) -}) diff --git a/test/serialize.js b/test/serialize.js deleted file mode 100644 index 1ee3235..0000000 --- a/test/serialize.js +++ /dev/null @@ -1,319 +0,0 @@ - -var assert = require('assert'); -var Buffer = require('safe-buffer').Buffer - -var cookie = require('..'); - -describe('cookie.serialize(name, value)', function () { - it('should serialize name and value', function () { - assert.equal(cookie.serialize('foo', 'bar'), 'foo=bar') - }) - - it('should URL-encode value', function () { - assert.equal(cookie.serialize('foo', 'bar +baz'), 'foo=bar%20%2Bbaz') - }) - - it('should serialize empty value', function () { - assert.equal(cookie.serialize('foo', ''), 'foo=') - }) - - it('should serialize valid name', function () { - var validNames = [ - 'foo', - 'foo!bar', - 'foo#bar', - 'foo$bar', - "foo'bar", - 'foo*bar', - 'foo+bar', - 'foo-bar', - 'foo.bar', - 'foo^bar', - 'foo_bar', - 'foo`bar', - 'foo|bar', - 'foo~bar', - 'foo7bar', - ]; - - validNames.forEach(function (name) { - assert.equal(cookie.serialize(name, 'baz'), name + '=baz'); - }); - }); - - it('should throw for invalid name', function () { - var invalidNames = [ - 'foo\n', - 'foo\u280a', - 'foo/foo', - 'foo,foo', - 'foo;foo', - 'foo@foo', - 'foo[foo]', - 'foo?foo', - 'foo:foo', - 'foo{foo}', - 'foo foo', - 'foo\tfoo', - 'foo"foo', - 'foo' - ]; - - invalidPaths.forEach(function (path) { - assert.throws( - cookie.serialize.bind(cookie, 'foo', 'bar', { path: path }), - /option path is invalid/, - 'Expected an error for invalid path: ' + path - ); - }); - }); - }); - - describe('with "priority" option', function () { - it('should throw on invalid priority', function () { - assert.throws(function () { - cookie.serialize('foo', 'bar', { priority: 'foo' }) - }, /option priority is invalid/) - }) - - it('should throw on non-string', function () { - assert.throws(function () { - cookie.serialize('foo', 'bar', { priority: 42 }) - }, /option priority is invalid/) - }) - - it('should set priority low', function () { - assert.equal(cookie.serialize('foo', 'bar', { priority: 'Low' }), 'foo=bar; Priority=Low') - assert.equal(cookie.serialize('foo', 'bar', { priority: 'loW' }), 'foo=bar; Priority=Low') - }) - - it('should set priority medium', function () { - assert.equal(cookie.serialize('foo', 'bar', { priority: 'Medium' }), 'foo=bar; Priority=Medium') - assert.equal(cookie.serialize('foo', 'bar', { priority: 'medium' }), 'foo=bar; Priority=Medium') - }) - - it('should set priority high', function () { - assert.equal(cookie.serialize('foo', 'bar', { priority: 'High' }), 'foo=bar; Priority=High') - assert.equal(cookie.serialize('foo', 'bar', { priority: 'HIGH' }), 'foo=bar; Priority=High') - }) - }) - - describe('with "sameSite" option', function () { - it('should throw on invalid sameSite', function () { - assert.throws(function () { - cookie.serialize('foo', 'bar', { sameSite: 'foo' }) - }, /option sameSite is invalid/) - }) - - it('should set sameSite strict', function () { - assert.equal(cookie.serialize('foo', 'bar', { sameSite: 'Strict' }), 'foo=bar; SameSite=Strict') - assert.equal(cookie.serialize('foo', 'bar', { sameSite: 'strict' }), 'foo=bar; SameSite=Strict') - }) - - it('should set sameSite lax', function () { - assert.equal(cookie.serialize('foo', 'bar', { sameSite: 'Lax' }), 'foo=bar; SameSite=Lax') - assert.equal(cookie.serialize('foo', 'bar', { sameSite: 'lax' }), 'foo=bar; SameSite=Lax') - }) - - it('should set sameSite none', function () { - assert.equal(cookie.serialize('foo', 'bar', { sameSite: 'None' }), 'foo=bar; SameSite=None') - assert.equal(cookie.serialize('foo', 'bar', { sameSite: 'none' }), 'foo=bar; SameSite=None') - }) - - it('should set sameSite strict when true', function () { - assert.equal(cookie.serialize('foo', 'bar', { sameSite: true }), 'foo=bar; SameSite=Strict') - }) - - it('should not set sameSite when false', function () { - assert.equal(cookie.serialize('foo', 'bar', { sameSite: false }), 'foo=bar') - }) - }) - - describe('with "secure" option', function () { - it('should include secure flag when true', function () { - assert.equal(cookie.serialize('foo', 'bar', { secure: true }), 'foo=bar; Secure') - }) - - it('should not include secure flag when false', function () { - assert.equal(cookie.serialize('foo', 'bar', { secure: false }), 'foo=bar') - }) - }) -}) diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..4b7a898 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["src/**/*.spec.ts", "src/**/*.bench.ts"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..ef26e83 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@borderless/ts-scripts/configs/tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "resolveJsonModule": true + }, + "include": ["src/**/*"] +}