From 4d90b802949144fefb101474f4880ddedbc3afc8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 11 Feb 2023 11:39:14 +0000 Subject: [PATCH 01/10] chore(deps): update all non-major dependencies --- package.json | 8 +-- pnpm-lock.yaml | 150 ++++++++++++++++++++++++------------------------- 2 files changed, 79 insertions(+), 79 deletions(-) diff --git a/package.json b/package.json index 9d5c38d8a..33e55449b 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "dependencies": { "cookie-es": "^0.5.0", "destr": "^1.2.2", - "iron-webcrypto": "^0.4.0", + "iron-webcrypto": "^0.5.0", "radix3": "^1.0.0", "ufo": "^1.0.1", "uncrypto": "^0.1.2" @@ -45,11 +45,11 @@ "autocannon": "^7.10.0", "changelogen": "^0.4.1", "connect": "^3.7.0", - "eslint": "^8.33.0", + "eslint": "^8.34.0", "eslint-config-unjs": "^0.1.0", "express": "^4.18.2", "get-port": "^6.1.2", - "jiti": "^1.16.2", + "jiti": "^1.17.0", "listhen": "^1.0.2", "node-fetch-native": "^1.0.1", "prettier": "^2.8.4", @@ -58,5 +58,5 @@ "unbuild": "^1.1.1", "vitest": "^0.28.4" }, - "packageManager": "pnpm@7.26.3" + "packageManager": "pnpm@7.27.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0f73ce5b5..4b8d6652b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,12 +11,12 @@ specifiers: connect: ^3.7.0 cookie-es: ^0.5.0 destr: ^1.2.2 - eslint: ^8.33.0 + eslint: ^8.34.0 eslint-config-unjs: ^0.1.0 express: ^4.18.2 get-port: ^6.1.2 - iron-webcrypto: ^0.4.0 - jiti: ^1.16.2 + iron-webcrypto: ^0.5.0 + jiti: ^1.17.0 listhen: ^1.0.2 node-fetch-native: ^1.0.1 prettier: ^2.8.4 @@ -31,7 +31,7 @@ specifiers: dependencies: cookie-es: 0.5.0 destr: 1.2.2 - iron-webcrypto: 0.4.0 + iron-webcrypto: 0.5.0 radix3: 1.0.0 ufo: 1.0.1 uncrypto: 0.1.2 @@ -45,11 +45,11 @@ devDependencies: autocannon: 7.10.0 changelogen: 0.4.1 connect: 3.7.0 - eslint: 8.33.0 - eslint-config-unjs: 0.1.0_4vsywjlpuriuw3tl5oq6zy5a64 + eslint: 8.34.0 + eslint-config-unjs: 0.1.0_7kw3g6rralp5ps6mg3uyzz6azm express: 4.18.2 get-port: 6.1.2 - jiti: 1.16.2 + jiti: 1.17.0 listhen: 1.0.2 node-fetch-native: 1.0.1 prettier: 2.8.4 @@ -827,7 +827,7 @@ packages: '@types/superagent': 4.1.15 dev: true - /@typescript-eslint/eslint-plugin/5.36.1_7fwakgfwbcaoaxenppfi5lwfpu: + /@typescript-eslint/eslint-plugin/5.36.1_nb3n477fc4lbvxx7gfppl6s3em: resolution: {integrity: sha512-iC40UK8q1tMepSDwiLbTbMXKDxzNy+4TfPWgIL661Ym0sD42vRcQU93IsZIrmi+x292DBr60UI/gSwfdVYexCA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -838,12 +838,12 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/parser': 5.36.1_4vsywjlpuriuw3tl5oq6zy5a64 + '@typescript-eslint/parser': 5.36.1_7kw3g6rralp5ps6mg3uyzz6azm '@typescript-eslint/scope-manager': 5.36.1 - '@typescript-eslint/type-utils': 5.36.1_4vsywjlpuriuw3tl5oq6zy5a64 - '@typescript-eslint/utils': 5.36.1_4vsywjlpuriuw3tl5oq6zy5a64 + '@typescript-eslint/type-utils': 5.36.1_7kw3g6rralp5ps6mg3uyzz6azm + '@typescript-eslint/utils': 5.36.1_7kw3g6rralp5ps6mg3uyzz6azm debug: 4.3.4 - eslint: 8.33.0 + eslint: 8.34.0 functional-red-black-tree: 1.0.1 ignore: 5.2.0 regexpp: 3.2.0 @@ -854,7 +854,7 @@ packages: - supports-color dev: true - /@typescript-eslint/parser/5.36.1_4vsywjlpuriuw3tl5oq6zy5a64: + /@typescript-eslint/parser/5.36.1_7kw3g6rralp5ps6mg3uyzz6azm: resolution: {integrity: sha512-/IsgNGOkBi7CuDfUbwt1eOqUXF9WGVBW9dwEe1pi+L32XrTsZIgmDFIi2RxjzsvB/8i+MIf5JIoTEH8LOZ368A==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -868,7 +868,7 @@ packages: '@typescript-eslint/types': 5.36.1 '@typescript-eslint/typescript-estree': 5.36.1_typescript@4.9.5 debug: 4.3.4 - eslint: 8.33.0 + eslint: 8.34.0 typescript: 4.9.5 transitivePeerDependencies: - supports-color @@ -882,7 +882,7 @@ packages: '@typescript-eslint/visitor-keys': 5.36.1 dev: true - /@typescript-eslint/type-utils/5.36.1_4vsywjlpuriuw3tl5oq6zy5a64: + /@typescript-eslint/type-utils/5.36.1_7kw3g6rralp5ps6mg3uyzz6azm: resolution: {integrity: sha512-xfZhfmoQT6m3lmlqDvDzv9TiCYdw22cdj06xY0obSznBsT///GK5IEZQdGliXpAOaRL34o8phEvXzEo/VJx13Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -893,9 +893,9 @@ packages: optional: true dependencies: '@typescript-eslint/typescript-estree': 5.36.1_typescript@4.9.5 - '@typescript-eslint/utils': 5.36.1_4vsywjlpuriuw3tl5oq6zy5a64 + '@typescript-eslint/utils': 5.36.1_7kw3g6rralp5ps6mg3uyzz6azm debug: 4.3.4 - eslint: 8.33.0 + eslint: 8.34.0 tsutils: 3.21.0_typescript@4.9.5 typescript: 4.9.5 transitivePeerDependencies: @@ -928,7 +928,7 @@ packages: - supports-color dev: true - /@typescript-eslint/utils/5.36.1_4vsywjlpuriuw3tl5oq6zy5a64: + /@typescript-eslint/utils/5.36.1_7kw3g6rralp5ps6mg3uyzz6azm: resolution: {integrity: sha512-lNj4FtTiXm5c+u0pUehozaUWhh7UYKnwryku0nxJlYUEWetyG92uw2pr+2Iy4M/u0ONMKzfrx7AsGBTCzORmIg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -938,9 +938,9 @@ packages: '@typescript-eslint/scope-manager': 5.36.1 '@typescript-eslint/types': 5.36.1 '@typescript-eslint/typescript-estree': 5.36.1_typescript@4.9.5 - eslint: 8.33.0 + eslint: 8.34.0 eslint-scope: 5.1.1 - eslint-utils: 3.0.0_eslint@8.33.0 + eslint-utils: 3.0.0_eslint@8.34.0 transitivePeerDependencies: - supports-color - typescript @@ -1474,7 +1474,7 @@ packages: defu: 6.1.2 dotenv: 16.0.3 giget: 1.0.0 - jiti: 1.16.2 + jiti: 1.17.0 mlly: 1.1.0 pathe: 1.1.0 pkg-types: 1.0.1 @@ -2542,16 +2542,16 @@ packages: engines: {node: '>=10'} dev: true - /eslint-config-prettier/8.5.0_eslint@8.33.0: + /eslint-config-prettier/8.5.0_eslint@8.34.0: resolution: {integrity: sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==} hasBin: true peerDependencies: eslint: '>=7.0.0' dependencies: - eslint: 8.33.0 + eslint: 8.34.0 dev: true - /eslint-config-standard/17.0.0_o5gsboltjsyjtwetr6dr4qrvvi: + /eslint-config-standard/17.0.0_dz3gtop7htx36q4jimj264d6gi: resolution: {integrity: sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==} peerDependencies: eslint: ^8.0.1 @@ -2559,29 +2559,29 @@ packages: eslint-plugin-n: ^15.0.0 eslint-plugin-promise: ^6.0.0 dependencies: - eslint: 8.33.0 - eslint-plugin-import: 2.26.0_ygwnpix7owifdhwvmep23sjfcy - eslint-plugin-n: 15.2.5_eslint@8.33.0 - eslint-plugin-promise: 6.0.1_eslint@8.33.0 + eslint: 8.34.0 + eslint-plugin-import: 2.26.0_h44c6pqxjfvbac4l7hvm4enx6q + eslint-plugin-n: 15.2.5_eslint@8.34.0 + eslint-plugin-promise: 6.0.1_eslint@8.34.0 dev: true - /eslint-config-unjs/0.1.0_4vsywjlpuriuw3tl5oq6zy5a64: + /eslint-config-unjs/0.1.0_7kw3g6rralp5ps6mg3uyzz6azm: resolution: {integrity: sha512-P51/jQg3RoLKqDTR6/x5637iOBYIEka/Ec6TwaNKiLhSOeYBKRVPsg/FdbV8MBExC9q4j/wRoTYBKj7sEVNUgQ==} peerDependencies: eslint: '*' typescript: '*' dependencies: - '@typescript-eslint/eslint-plugin': 5.36.1_7fwakgfwbcaoaxenppfi5lwfpu - '@typescript-eslint/parser': 5.36.1_4vsywjlpuriuw3tl5oq6zy5a64 - eslint: 8.33.0 - eslint-config-prettier: 8.5.0_eslint@8.33.0 - eslint-config-standard: 17.0.0_o5gsboltjsyjtwetr6dr4qrvvi - eslint-import-resolver-typescript: 3.5.0_ga4sep7loct7ajt7pxcme7xdqu - eslint-plugin-import: 2.26.0_ygwnpix7owifdhwvmep23sjfcy - eslint-plugin-n: 15.2.5_eslint@8.33.0 - eslint-plugin-node: 11.1.0_eslint@8.33.0 - eslint-plugin-promise: 6.0.1_eslint@8.33.0 - eslint-plugin-unicorn: 43.0.2_eslint@8.33.0 + '@typescript-eslint/eslint-plugin': 5.36.1_nb3n477fc4lbvxx7gfppl6s3em + '@typescript-eslint/parser': 5.36.1_7kw3g6rralp5ps6mg3uyzz6azm + eslint: 8.34.0 + eslint-config-prettier: 8.5.0_eslint@8.34.0 + eslint-config-standard: 17.0.0_dz3gtop7htx36q4jimj264d6gi + eslint-import-resolver-typescript: 3.5.0_w7dy265x2bmlgtc6kmsfumkjde + eslint-plugin-import: 2.26.0_h44c6pqxjfvbac4l7hvm4enx6q + eslint-plugin-n: 15.2.5_eslint@8.34.0 + eslint-plugin-node: 11.1.0_eslint@8.34.0 + eslint-plugin-promise: 6.0.1_eslint@8.34.0 + eslint-plugin-unicorn: 43.0.2_eslint@8.34.0 typescript: 4.9.5 transitivePeerDependencies: - eslint-import-resolver-webpack @@ -2597,7 +2597,7 @@ packages: - supports-color dev: true - /eslint-import-resolver-typescript/3.5.0_ga4sep7loct7ajt7pxcme7xdqu: + /eslint-import-resolver-typescript/3.5.0_w7dy265x2bmlgtc6kmsfumkjde: resolution: {integrity: sha512-DEfpfuk+O/T5e9HBZOxocmwMuUGkvQQd5WRiMJF9kKNT9amByqOyGlWoAZAQiv0SZSy4GMtG1clmnvQA/RzA0A==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} peerDependencies: @@ -2606,8 +2606,8 @@ packages: dependencies: debug: 4.3.4 enhanced-resolve: 5.10.0 - eslint: 8.33.0 - eslint-plugin-import: 2.26.0_ygwnpix7owifdhwvmep23sjfcy + eslint: 8.34.0 + eslint-plugin-import: 2.26.0_h44c6pqxjfvbac4l7hvm4enx6q get-tsconfig: 4.2.0 globby: 13.1.3 is-core-module: 2.10.0 @@ -2635,38 +2635,38 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 5.36.1_4vsywjlpuriuw3tl5oq6zy5a64 + '@typescript-eslint/parser': 5.36.1_7kw3g6rralp5ps6mg3uyzz6azm debug: 3.2.7 eslint-import-resolver-node: 0.3.6 - eslint-import-resolver-typescript: 3.5.0_ga4sep7loct7ajt7pxcme7xdqu + eslint-import-resolver-typescript: 3.5.0_w7dy265x2bmlgtc6kmsfumkjde find-up: 2.1.0 transitivePeerDependencies: - supports-color dev: true - /eslint-plugin-es/3.0.1_eslint@8.33.0: + /eslint-plugin-es/3.0.1_eslint@8.34.0: resolution: {integrity: sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==} engines: {node: '>=8.10.0'} peerDependencies: eslint: '>=4.19.1' dependencies: - eslint: 8.33.0 + eslint: 8.34.0 eslint-utils: 2.1.0 regexpp: 3.2.0 dev: true - /eslint-plugin-es/4.1.0_eslint@8.33.0: + /eslint-plugin-es/4.1.0_eslint@8.34.0: resolution: {integrity: sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==} engines: {node: '>=8.10.0'} peerDependencies: eslint: '>=4.19.1' dependencies: - eslint: 8.33.0 + eslint: 8.34.0 eslint-utils: 2.1.0 regexpp: 3.2.0 dev: true - /eslint-plugin-import/2.26.0_ygwnpix7owifdhwvmep23sjfcy: + /eslint-plugin-import/2.26.0_h44c6pqxjfvbac4l7hvm4enx6q: resolution: {integrity: sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==} engines: {node: '>=4'} peerDependencies: @@ -2676,12 +2676,12 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 5.36.1_4vsywjlpuriuw3tl5oq6zy5a64 + '@typescript-eslint/parser': 5.36.1_7kw3g6rralp5ps6mg3uyzz6azm array-includes: 3.1.4 array.prototype.flat: 1.2.5 debug: 2.6.9 doctrine: 2.1.0 - eslint: 8.33.0 + eslint: 8.34.0 eslint-import-resolver-node: 0.3.6 eslint-module-utils: 2.7.3_557a6ieby6o22fltd5pqi5vjdq has: 1.0.3 @@ -2697,16 +2697,16 @@ packages: - supports-color dev: true - /eslint-plugin-n/15.2.5_eslint@8.33.0: + /eslint-plugin-n/15.2.5_eslint@8.34.0: resolution: {integrity: sha512-8+BYsqiyZfpu6NXmdLOXVUfk8IocpCjpd8nMRRH0A9ulrcemhb2VI9RSJMEy5udx++A/YcVPD11zT8hpFq368g==} engines: {node: '>=12.22.0'} peerDependencies: eslint: '>=7.0.0' dependencies: builtins: 5.0.1 - eslint: 8.33.0 - eslint-plugin-es: 4.1.0_eslint@8.33.0 - eslint-utils: 3.0.0_eslint@8.33.0 + eslint: 8.34.0 + eslint-plugin-es: 4.1.0_eslint@8.34.0 + eslint-utils: 3.0.0_eslint@8.34.0 ignore: 5.2.0 is-core-module: 2.10.0 minimatch: 3.1.2 @@ -2714,14 +2714,14 @@ packages: semver: 7.3.8 dev: true - /eslint-plugin-node/11.1.0_eslint@8.33.0: + /eslint-plugin-node/11.1.0_eslint@8.34.0: resolution: {integrity: sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==} engines: {node: '>=8.10.0'} peerDependencies: eslint: '>=5.16.0' dependencies: - eslint: 8.33.0 - eslint-plugin-es: 3.0.1_eslint@8.33.0 + eslint: 8.34.0 + eslint-plugin-es: 3.0.1_eslint@8.34.0 eslint-utils: 2.1.0 ignore: 5.2.0 minimatch: 3.1.2 @@ -2729,16 +2729,16 @@ packages: semver: 6.3.0 dev: true - /eslint-plugin-promise/6.0.1_eslint@8.33.0: + /eslint-plugin-promise/6.0.1_eslint@8.34.0: resolution: {integrity: sha512-uM4Tgo5u3UWQiroOyDEsYcVMOo7re3zmno0IZmB5auxoaQNIceAbXEkSt8RNrKtaYehARHG06pYK6K1JhtP0Zw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 dependencies: - eslint: 8.33.0 + eslint: 8.34.0 dev: true - /eslint-plugin-unicorn/43.0.2_eslint@8.33.0: + /eslint-plugin-unicorn/43.0.2_eslint@8.34.0: resolution: {integrity: sha512-DtqZ5mf/GMlfWoz1abIjq5jZfaFuHzGBZYIeuJfEoKKGWRHr2JiJR+ea+BF7Wx2N1PPRoT/2fwgiK1NnmNE3Hg==} engines: {node: '>=14.18'} peerDependencies: @@ -2747,8 +2747,8 @@ packages: '@babel/helper-validator-identifier': 7.19.1 ci-info: 3.3.2 clean-regexp: 1.0.0 - eslint: 8.33.0 - eslint-utils: 3.0.0_eslint@8.33.0 + eslint: 8.34.0 + eslint-utils: 3.0.0_eslint@8.34.0 esquery: 1.4.0 indent-string: 4.0.0 is-builtin-module: 3.2.0 @@ -2784,13 +2784,13 @@ packages: eslint-visitor-keys: 1.3.0 dev: true - /eslint-utils/3.0.0_eslint@8.33.0: + /eslint-utils/3.0.0_eslint@8.34.0: resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} peerDependencies: eslint: '>=5' dependencies: - eslint: 8.33.0 + eslint: 8.34.0 eslint-visitor-keys: 2.1.0 dev: true @@ -2809,8 +2809,8 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint/8.33.0: - resolution: {integrity: sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA==} + /eslint/8.34.0: + resolution: {integrity: sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: @@ -2825,7 +2825,7 @@ packages: doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.1.1 - eslint-utils: 3.0.0_eslint@8.33.0 + eslint-utils: 3.0.0_eslint@8.34.0 eslint-visitor-keys: 3.3.0 espree: 9.4.0 esquery: 1.4.0 @@ -3617,8 +3617,8 @@ packages: engines: {node: '>= 0.10'} dev: true - /iron-webcrypto/0.4.0: - resolution: {integrity: sha512-5OG53gJ4dBTq4y3IJqK7MEG9CPZRsYn9EP9J4jjgH4TcP/ywdsSMAmqj9VTSzdXu0/xfUrqjGHU7WLUme2+k5Q==} + /iron-webcrypto/0.5.0: + resolution: {integrity: sha512-9m0tDUIo+GPwDYi1CNlAW3ToIFTS9y88lf41KsEwbBsL4PKNjhrNDGoA0WlB6WWaJ6pgp+FOP1+6ls0YftivyA==} dev: false /is-arguments/1.1.1: @@ -3854,8 +3854,8 @@ packages: istanbul-lib-report: 3.0.0 dev: true - /jiti/1.16.2: - resolution: {integrity: sha512-OKBOVWmU3FxDt/UH4zSwiKPuc1nihFZiOD722FuJlngvLz2glX1v2/TJIgoA4+mrpnXxHV6dSAoCvPcYQtoG5A==} + /jiti/1.17.0: + resolution: {integrity: sha512-CByzPgFqYoB9odEeef7GNmQ3S5THIBOtzRYoSCya2Sv27AuQxy2jgoFjQ6VTF53xsq1MXRm+YWNvOoDHUAteOw==} hasBin: true dev: true @@ -4231,7 +4231,7 @@ packages: esbuild: 0.16.17 fs-extra: 11.1.0 globby: 13.1.3 - jiti: 1.16.2 + jiti: 1.17.0 mri: 1.2.0 pathe: 1.1.0 typescript: 4.9.5 @@ -5684,7 +5684,7 @@ packages: esbuild: 0.16.17 globby: 13.1.3 hookable: 5.4.2 - jiti: 1.16.2 + jiti: 1.17.0 magic-string: 0.27.0 mkdirp: 1.0.4 mkdist: 1.1.0_typescript@4.9.5 From 8873d874cdbd360d57094c3fb4b6bcb23a25a957 Mon Sep 17 00:00:00 2001 From: Divyansh Singh <40380293+brc-dd@users.noreply.github.com> Date: Tue, 14 Feb 2023 20:36:04 +0530 Subject: [PATCH 02/10] docs: update build status badge url (#331) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fd9ef49d6..244ba6806 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![npm downloads](https://img.shields.io/npm/dm/h3.svg?style=flat-square)](https://npmjs.com/package/h3) [![version](https://img.shields.io/npm/v/h3/latest.svg?style=flat-square)](https://npmjs.com/package/h3) [![bundlephobia](https://img.shields.io/bundlephobia/min/h3/latest.svg?style=flat-square)](https://bundlephobia.com/result?p=h3) -[![build status](https://img.shields.io/github/workflow/status/unjs/h3/ci/main?style=flat-square)](https://github.com/unjs/h3/actions) +[![build status](https://img.shields.io/github/actions/workflow/status/unjs/h3/ci.yml?branch=main&style=flat-square)](https://github.com/unjs/h3/actions) [![coverage](https://img.shields.io/codecov/c/gh/unjs/h3/main?style=flat-square)](https://codecov.io/gh/unjs/h3) [![jsDocs.io](https://img.shields.io/badge/jsDocs.io-reference-blue?style=flat-square)](https://www.jsdocs.io/package/h3) From 0708ca40541974994acdbb1f139564f9a994c5fd Mon Sep 17 00:00:00 2001 From: Nozomu Ikuta <16436160+NozomuIkuta@users.noreply.github.com> Date: Thu, 16 Feb 2023 20:56:11 +0900 Subject: [PATCH 03/10] chore: improve `lint` npm script (#329) --- .gitignore | 1 + package.json | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index cdd8de7f8..5236dadb3 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ dist coverage .profile .idea +.eslintcache diff --git a/package.json b/package.json index 33e55449b..65a0511f2 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "scripts": { "build": "unbuild", "dev": "vitest", - "lint": "eslint --ext ts,mjs,cjs . && prettier -c src test playground", + "lint": "eslint --cache --ext .ts,.js,.mjs,.cjs . && prettier -c src test playground", + "lint:fix": "eslint --cache --ext .ts,.js,.mjs,.cjs . --fix && prettier -c src test playground -w", "play": "jiti ./playground/index.ts", "profile": "0x -o -D .profile -P 'autocannon -c 100 -p 10 -d 40 http://localhost:$PORT' ./playground/server.cjs", "release": "pnpm test && pnpm build && changelogen --release && pnpm publish && git push --follow-tags", From 8544914903f314ee18aa38e53e9bec44d6f5d299 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Feb 2023 12:56:23 +0100 Subject: [PATCH 04/10] chore(deps): update all non-major dependencies to ^0.28.5 (#333) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 4 ++-- pnpm-lock.yaml | 54 +++++++++++++++++++++++++------------------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/package.json b/package.json index 65a0511f2..0ffbadfda 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "@types/express": "^4.17.17", "@types/node": "^18.13.0", "@types/supertest": "^2.0.12", - "@vitest/coverage-c8": "^0.28.4", + "@vitest/coverage-c8": "^0.28.5", "autocannon": "^7.10.0", "changelogen": "^0.4.1", "connect": "^3.7.0", @@ -57,7 +57,7 @@ "supertest": "^6.3.3", "typescript": "^4.9.5", "unbuild": "^1.1.1", - "vitest": "^0.28.4" + "vitest": "^0.28.5" }, "packageManager": "pnpm@7.27.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4b8d6652b..80e76b9b8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,7 +5,7 @@ specifiers: '@types/express': ^4.17.17 '@types/node': ^18.13.0 '@types/supertest': ^2.0.12 - '@vitest/coverage-c8': ^0.28.4 + '@vitest/coverage-c8': ^0.28.5 autocannon: ^7.10.0 changelogen: ^0.4.1 connect: ^3.7.0 @@ -26,7 +26,7 @@ specifiers: ufo: ^1.0.1 unbuild: ^1.1.1 uncrypto: ^0.1.2 - vitest: ^0.28.4 + vitest: ^0.28.5 dependencies: cookie-es: 0.5.0 @@ -41,7 +41,7 @@ devDependencies: '@types/express': 4.17.17 '@types/node': 18.13.0 '@types/supertest': 2.0.12 - '@vitest/coverage-c8': 0.28.4 + '@vitest/coverage-c8': 0.28.5 autocannon: 7.10.0 changelogen: 0.4.1 connect: 3.7.0 @@ -56,7 +56,7 @@ devDependencies: supertest: 6.3.3 typescript: 4.9.5 unbuild: 1.1.1 - vitest: 0.28.4 + vitest: 0.28.5 packages: @@ -954,13 +954,13 @@ packages: eslint-visitor-keys: 3.3.0 dev: true - /@vitest/coverage-c8/0.28.4: - resolution: {integrity: sha512-btelLBxaWhHnywXRQxDlrvPhGdnuIaD3XulsxcZRIcnpLPbFu39dNTT0IYu2QWP2ZZrV0AmNtdLIfD4c77zMAg==} + /@vitest/coverage-c8/0.28.5: + resolution: {integrity: sha512-zCNyurjudoG0BAqAgknvlBhkV2V9ZwyYLWOAGtHSDhL/St49MJT+V2p1G0yPaoqBbKOTATVnP5H2p1XL15H75g==} dependencies: c8: 7.12.0 picocolors: 1.0.0 std-env: 3.3.1 - vitest: 0.28.4 + vitest: 0.28.5 transitivePeerDependencies: - '@edge-runtime/vm' - '@vitest/browser' @@ -974,30 +974,30 @@ packages: - terser dev: true - /@vitest/expect/0.28.4: - resolution: {integrity: sha512-JqK0NZ4brjvOSL8hXAnIsfi+jxDF7rH/ZWCGCt0FAqRnVFc1hXsfwXksQvEnKqD84avRt3gmeXoK4tNbmkoVsQ==} + /@vitest/expect/0.28.5: + resolution: {integrity: sha512-gqTZwoUTwepwGIatnw4UKpQfnoyV0Z9Czn9+Lo2/jLIt4/AXLTn+oVZxlQ7Ng8bzcNkR+3DqLJ08kNr8jRmdNQ==} dependencies: - '@vitest/spy': 0.28.4 - '@vitest/utils': 0.28.4 + '@vitest/spy': 0.28.5 + '@vitest/utils': 0.28.5 chai: 4.3.7 dev: true - /@vitest/runner/0.28.4: - resolution: {integrity: sha512-Q8UV6GjDvBSTfUoq0QXVCNpNOUrWu4P2qvRq7ssJWzn0+S0ojbVOxEjMt+8a32X6SdkhF8ak+2nkppsqV0JyNQ==} + /@vitest/runner/0.28.5: + resolution: {integrity: sha512-NKkHtLB+FGjpp5KmneQjTcPLWPTDfB7ie+MmF1PnUBf/tGe2OjGxWyB62ySYZ25EYp9krR5Bw0YPLS/VWh1QiA==} dependencies: - '@vitest/utils': 0.28.4 + '@vitest/utils': 0.28.5 p-limit: 4.0.0 pathe: 1.1.0 dev: true - /@vitest/spy/0.28.4: - resolution: {integrity: sha512-8WuhfXLlvCXpNXEGJW6Gc+IKWI32435fQJLh43u70HnZ1otJOa2Cmg2Wy2Aym47ZnNCP4NolF+8cUPwd0MigKQ==} + /@vitest/spy/0.28.5: + resolution: {integrity: sha512-7if6rsHQr9zbmvxN7h+gGh2L9eIIErgf8nSKYDlg07HHimCxp4H6I/X/DPXktVPPLQfiZ1Cw2cbDIx9fSqDjGw==} dependencies: tinyspy: 1.0.2 dev: true - /@vitest/utils/0.28.4: - resolution: {integrity: sha512-l2QztOLdc2LkR+w/lP52RGh8hW+Ul4KESmCAgVE8q737I7e7bQoAfkARKpkPJ4JQtGpwW4deqlj1732VZD7TFw==} + /@vitest/utils/0.28.5: + resolution: {integrity: sha512-UyZdYwdULlOa4LTUSwZ+Paz7nBHGTT72jKwdFSV4IjHF1xsokp+CabMdhjvVhYwkLfO88ylJT46YMilnkSARZA==} dependencies: cli-truncate: 3.1.0 diff: 5.1.0 @@ -5827,8 +5827,8 @@ packages: engines: {node: '>= 0.8'} dev: true - /vite-node/0.28.4: - resolution: {integrity: sha512-KM0Q0uSG/xHHKOJvVHc5xDBabgt0l70y7/lWTR7Q0pR5/MrYxadT+y32cJOE65FfjGmJgxpVEEY+69btJgcXOQ==} + /vite-node/0.28.5: + resolution: {integrity: sha512-LmXb9saMGlrMZbXTvOveJKwMTBTNUH66c8rJnQ0ZPNX+myPEol64+szRzXtV5ORb0Hb/91yq+/D3oERoyAt6LA==} engines: {node: '>=v14.16.0'} hasBin: true dependencies: @@ -5875,8 +5875,8 @@ packages: fsevents: 2.3.2 dev: true - /vitest/0.28.4: - resolution: {integrity: sha512-sfWIy0AdlbyGRhunm+TLQEJrFH9XuRPdApfubsyLcDbCRrUX717BRQKInTgzEfyl2Ipi1HWoHB84Nqtcwxogcg==} + /vitest/0.28.5: + resolution: {integrity: sha512-pyCQ+wcAOX7mKMcBNkzDwEHRGqQvHUl0XnoHR+3Pb1hytAHISgSxv9h0gUiSiYtISXUU3rMrKiKzFYDrI6ZIHA==} engines: {node: '>=v14.16.0'} hasBin: true peerDependencies: @@ -5900,10 +5900,10 @@ packages: '@types/chai': 4.3.4 '@types/chai-subset': 1.3.3 '@types/node': 18.13.0 - '@vitest/expect': 0.28.4 - '@vitest/runner': 0.28.4 - '@vitest/spy': 0.28.4 - '@vitest/utils': 0.28.4 + '@vitest/expect': 0.28.5 + '@vitest/runner': 0.28.5 + '@vitest/spy': 0.28.5 + '@vitest/utils': 0.28.5 acorn: 8.8.1 acorn-walk: 8.2.0 cac: 6.7.14 @@ -5919,7 +5919,7 @@ packages: tinypool: 0.3.1 tinyspy: 1.0.2 vite: 3.1.8 - vite-node: 0.28.4 + vite-node: 0.28.5 why-is-node-running: 2.2.2 transitivePeerDependencies: - less From 45e6dd4f877b707ee1c8d6ac3fa32716f6977678 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Thu, 16 Feb 2023 14:32:00 +0100 Subject: [PATCH 05/10] types: export `MultiPartData` (resolves #332) --- src/utils/{multipart.ts => _multipart.ts} | 0 src/utils/body.ts | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) rename src/utils/{multipart.ts => _multipart.ts} (100%) diff --git a/src/utils/multipart.ts b/src/utils/_multipart.ts similarity index 100% rename from src/utils/multipart.ts rename to src/utils/_multipart.ts diff --git a/src/utils/body.ts b/src/utils/body.ts index e88824ef4..1f2af8b01 100644 --- a/src/utils/body.ts +++ b/src/utils/body.ts @@ -1,9 +1,11 @@ import destr from "destr"; import type { Encoding, HTTPMethod } from "../types"; import type { H3Event } from "../event"; -import { parse as parseMultipartData } from "./multipart"; +import { parse as parseMultipartData } from "./_multipart"; import { assertMethod, getRequestHeader } from "./request"; +export type { MultiPartData } from "./_multipart"; + const RawBodySymbol = Symbol.for("h3RawBody"); const ParsedBodySymbol = Symbol.for("h3ParsedBody"); From e5bfd2f2af7493201788a42d1795eb811a5184bb Mon Sep 17 00:00:00 2001 From: Martin Meixger Date: Thu, 16 Feb 2023 15:00:11 +0100 Subject: [PATCH 06/10] fix(proxy): separate multiple cookie headers (#319) * separate multiple cookie headers * refactor to utils/internal * update proxy test * reneme internal * fix headers testing --------- Co-authored-by: Pooya Parsa --- src/utils/body.ts | 4 +- src/utils/internal/cookies.ts | 78 +++++++++++++++++++ .../{_multipart.ts => internal/multipart.ts} | 0 src/utils/proxy.ts | 5 ++ test/proxy.test.ts | 47 ++++++++++- 5 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 src/utils/internal/cookies.ts rename src/utils/{_multipart.ts => internal/multipart.ts} (100%) diff --git a/src/utils/body.ts b/src/utils/body.ts index 1f2af8b01..db2603a55 100644 --- a/src/utils/body.ts +++ b/src/utils/body.ts @@ -1,10 +1,10 @@ import destr from "destr"; import type { Encoding, HTTPMethod } from "../types"; import type { H3Event } from "../event"; -import { parse as parseMultipartData } from "./_multipart"; +import { parse as parseMultipartData } from "./internal/multipart"; import { assertMethod, getRequestHeader } from "./request"; -export type { MultiPartData } from "./_multipart"; +export type { MultiPartData } from "./internal/multipart"; const RawBodySymbol = Symbol.for("h3RawBody"); const ParsedBodySymbol = Symbol.for("h3ParsedBody"); diff --git a/src/utils/internal/cookies.ts b/src/utils/internal/cookies.ts new file mode 100644 index 000000000..d96162a7a --- /dev/null +++ b/src/utils/internal/cookies.ts @@ -0,0 +1,78 @@ +/** + * Set-Cookie header field-values are sometimes comma joined in one string. This splits them without choking on commas + * that are within a single set-cookie field-value, such as in the Expires portion. + * This is uncommon, but explicitly allowed - see https://tools.ietf.org/html/rfc2616#section-4.2 + * Node.js does this for every header *except* set-cookie - see https://github.com/nodejs/node/blob/d5e363b77ebaf1caf67cd7528224b651c86815c1/lib/_http_incoming.js#L128 + * Based on: https://github.com/google/j2objc/commit/16820fdbc8f76ca0c33472810ce0cb03d20efe25 + * Credits to: https://github.com/tomball for original and https://github.com/chrusart for JavaScript implementation + * @source https://github.com/nfriedly/set-cookie-parser/blob/3eab8b7d5d12c8ed87832532861c1a35520cf5b3/lib/set-cookie.js#L144 + */ +export default function splitCookiesString(cookiesString: string) { + if (typeof cookiesString !== "string") { + return []; + } + + const cookiesStrings: string[] = []; + let pos = 0; + let start; + let ch; + let lastComma: number; + let nextStart; + let cookiesSeparatorFound; + + function skipWhitespace() { + while (pos < cookiesString.length && /\s/.test(cookiesString.charAt(pos))) { + pos += 1; + } + return pos < cookiesString.length; + } + + function notSpecialChar() { + ch = cookiesString.charAt(pos); + + return ch !== "=" && ch !== ";" && ch !== ","; + } + + while (pos < cookiesString.length) { + start = pos; + cookiesSeparatorFound = false; + + while (skipWhitespace()) { + ch = cookiesString.charAt(pos); + if (ch === ",") { + // ',' is a cookie separator if we have later first '=', not ';' or ',' + lastComma = pos; + pos += 1; + + skipWhitespace(); + nextStart = pos; + + while (pos < cookiesString.length && notSpecialChar()) { + pos += 1; + } + + // currently special character + if (pos < cookiesString.length && cookiesString.charAt(pos) === "=") { + // we found cookies separator + cookiesSeparatorFound = true; + // pos is inside the next cookie, so back up and return it. + pos = nextStart; + cookiesStrings.push(cookiesString.slice(start, lastComma)); + start = pos; + } else { + // in param ',' or param separator ';', + // we continue from that comma + pos = lastComma + 1; + } + } else { + pos += 1; + } + } + + if (!cookiesSeparatorFound || pos >= cookiesString.length) { + cookiesStrings.push(cookiesString.slice(start, cookiesString.length)); + } + } + + return cookiesStrings; +} diff --git a/src/utils/_multipart.ts b/src/utils/internal/multipart.ts similarity index 100% rename from src/utils/_multipart.ts rename to src/utils/internal/multipart.ts diff --git a/src/utils/proxy.ts b/src/utils/proxy.ts index 0092ab7d3..0069edb0d 100644 --- a/src/utils/proxy.ts +++ b/src/utils/proxy.ts @@ -2,6 +2,7 @@ import type { H3Event } from "../event"; import type { RequestHeaders } from "../types"; import { getMethod, getRequestHeaders } from "./request"; import { readRawBody } from "./body"; +import splitCookiesString from "./internal/cookies"; export interface ProxyOptions { headers?: RequestHeaders | HeadersInit; @@ -73,6 +74,10 @@ export async function sendProxy( if (key === "content-length") { continue; } + if (key === "set-cookie") { + event.node.res.setHeader("set-cookie", splitCookiesString(value)); + continue; + } event.node.res.setHeader(key, value); } diff --git a/test/proxy.test.ts b/test/proxy.test.ts index 469aa9ae0..721175852 100644 --- a/test/proxy.test.ts +++ b/test/proxy.test.ts @@ -10,6 +10,7 @@ import { getHeaders, getMethod, readRawBody, + setCookie, } from "../src"; import { sendProxy, proxyRequest } from "../src/utils/proxy"; @@ -82,11 +83,51 @@ describe("", () => { const result = await fetch(url + "/", { method: "POST", body: "hello", - }).then((r) => r.text()); + headers: { + "content-type": "text/custom", + "x-custom": "hello", + }, + }).then((r) => r.json()); - expect(result).toMatchInlineSnapshot( - '"{\\"method\\":\\"POST\\",\\"headers\\":{\\"accept\\":\\"*/*\\",\\"accept-encoding\\":\\"gzip, deflate, br\\",\\"connection\\":\\"close\\",\\"content-length\\":\\"5\\",\\"content-type\\":\\"text/plain;charset=UTF-8\\",\\"user-agent\\":\\"node-fetch\\"},\\"body\\":\\"hello\\"}"' + const { headers, ...data } = result; + expect(headers["content-type"]).toEqual("text/custom"); + expect(headers["x-custom"]).toEqual("hello"); + expect(data).toMatchInlineSnapshot(` + { + "body": "hello", + "method": "POST", + } + `); + }); + }); + + describe("multipleCookies", () => { + it("can split multiple cookies", async () => { + app.use( + "/setcookies", + eventHandler((event) => { + setCookie(event, "user", "alice", { + expires: new Date("Thu, 01 Jun 2023 10:00:00 GMT"), + httpOnly: true, + }); + setCookie(event, "role", "guest"); + return {}; + }) + ); + + app.use( + "/", + eventHandler((event) => { + return sendProxy(event, url + "/setcookies", { fetch }); + }) ); + + const result = await request.get("/"); + const cookies = result.header["set-cookie"]; + expect(cookies).toEqual([ + "user=alice; Path=/; Expires=Thu, 01 Jun 2023 10:00:00 GMT; HttpOnly", + "role=guest; Path=/", + ]); }); }); }); From 080bf53d69b8193c4e22c873522d658dbda16656 Mon Sep 17 00:00:00 2001 From: Nozomu Ikuta <16436160+NozomuIkuta@users.noreply.github.com> Date: Thu, 16 Feb 2023 23:45:20 +0900 Subject: [PATCH 07/10] feat: add cors utils (#322) * feat: add CORS support * docs: modify explanation about h3-cors * chore: integrate h3-cors code to core * chore: rename functions * chore: modify exports * chore: use `sendNoContent` for preflight request * fix: fix format * chore: remove redundant wrapper * docs: add CORS utility functions * chore: add prefix to types * avoid destructure * improve control flow of `handleCors` returning a boolean * refactor: lowercase headers * update tests * refactor: rename to isCorsOriginAllowed * simplify to appendCorsHeaders --------- Co-authored-by: Pooya Parsa --- README.md | 8 +- package.json | 1 + pnpm-lock.yaml | 9 +- src/utils/cors/handler.ts | 20 ++ src/utils/cors/index.ts | 9 + src/utils/cors/types.ts | 77 ++++++ src/utils/cors/utils.ts | 195 +++++++++++++++ src/utils/index.ts | 1 + test/cors.test.ts | 485 ++++++++++++++++++++++++++++++++++++++ 9 files changed, 796 insertions(+), 9 deletions(-) create mode 100644 src/utils/cors/handler.ts create mode 100644 src/utils/cors/index.ts create mode 100644 src/utils/cors/types.ts create mode 100644 src/utils/cors/utils.ts create mode 100644 test/cors.test.ts diff --git a/README.md b/README.md index 244ba6806..3f78992a1 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,11 @@ H3 has a concept of composable utilities that accept `event` (from `eventHandler - `clearSession(event, config)` - `sealSession(event, config)` - `unsealSession(event, config, sealed)` +- `handleCors(options)` (see [h3-cors](https://github.com/NozomuIkuta/h3-cors) for more detail about options) +- `isPreflightRequest(event)` +- `isCorsOriginAllowed(event)` +- `appendCorsHeaders(event, options)` (see [h3-cors](https://github.com/NozomuIkuta/h3-cors) for more detail about options) +- `appendCorsPreflightHeaders(event, options)` (see [h3-cors](https://github.com/NozomuIkuta/h3-cors) for more detail about options) 👉 You can learn more about usage in [JSDocs Documentation](https://www.jsdocs.io/package/h3#package-functions). @@ -172,9 +177,6 @@ Please check their READMEs for more details. PRs are welcome to add your packages. -- [h3-cors](https://github.com/NozomuIkuta/h3-cors) - - `defineCorsEventHandler(options)` - - `isPreflight(event)` - [h3-typebox](https://github.com/kevinmarrec/h3-typebox) - `validateBody(event, schema)` - `validateQuery(event, schema)` diff --git a/package.json b/package.json index 0ffbadfda..0836c2c9e 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ }, "dependencies": { "cookie-es": "^0.5.0", + "defu": "^6.1.2", "destr": "^1.2.2", "iron-webcrypto": "^0.5.0", "radix3": "^1.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 80e76b9b8..75e3eb018 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,6 +10,7 @@ specifiers: changelogen: ^0.4.1 connect: ^3.7.0 cookie-es: ^0.5.0 + defu: ^6.1.2 destr: ^1.2.2 eslint: ^8.34.0 eslint-config-unjs: ^0.1.0 @@ -30,6 +31,7 @@ specifiers: dependencies: cookie-es: 0.5.0 + defu: 6.1.2 destr: 1.2.2 iron-webcrypto: 0.5.0 radix3: 1.0.0 @@ -2068,13 +2070,8 @@ packages: resolution: {integrity: sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==} dev: true - /defu/6.1.1: - resolution: {integrity: sha512-aA964RUCsBt0FGoNIlA3uFgo2hO+WWC0fiC6DBps/0SFzkKcYoM/3CzVLIa5xSsrFjdioMdYgAIbwo80qp2MoA==} - dev: true - /defu/6.1.2: resolution: {integrity: sha512-+uO4+qr7msjNNWKYPHqN/3+Dx3NFkmIzayk2L1MyZQlvgZb/J1A0fo410dpKrN2SnqFjt8n4JL8fDJE0wIgjFQ==} - dev: true /delayed-stream/1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} @@ -5680,7 +5677,7 @@ packages: '@rollup/pluginutils': 5.0.2_rollup@3.10.0 chalk: 5.2.0 consola: 2.15.3 - defu: 6.1.1 + defu: 6.1.2 esbuild: 0.16.17 globby: 13.1.3 hookable: 5.4.2 diff --git a/src/utils/cors/handler.ts b/src/utils/cors/handler.ts new file mode 100644 index 000000000..82d182168 --- /dev/null +++ b/src/utils/cors/handler.ts @@ -0,0 +1,20 @@ +import { H3Event } from "../../event"; +import { sendNoContent } from "../response"; +import { + resolveCorsOptions, + appendCorsPreflightHeaders, + appendCorsHeaders, + isPreflightRequest, +} from "./utils"; +import type { H3CorsOptions } from "./types"; + +export function handleCors(event: H3Event, options: H3CorsOptions): boolean { + const _options = resolveCorsOptions(options); + if (isPreflightRequest(event)) { + appendCorsPreflightHeaders(event, options); + sendNoContent(event, _options.preflight.statusCode); + return true; + } + appendCorsHeaders(event, options); + return false; +} diff --git a/src/utils/cors/index.ts b/src/utils/cors/index.ts new file mode 100644 index 000000000..400578ea7 --- /dev/null +++ b/src/utils/cors/index.ts @@ -0,0 +1,9 @@ +export { handleCors } from "./handler"; +export { + isPreflightRequest, + isCorsOriginAllowed, + appendCorsHeaders, + appendCorsPreflightHeaders, +} from "./utils"; + +export type { H3CorsOptions } from "./types"; diff --git a/src/utils/cors/types.ts b/src/utils/cors/types.ts new file mode 100644 index 000000000..46c5e45b5 --- /dev/null +++ b/src/utils/cors/types.ts @@ -0,0 +1,77 @@ +import { HTTPMethod } from "../../types"; + +export interface H3CorsOptions { + origin?: "*" | "null" | (string | RegExp)[] | ((origin: string) => boolean); + methods?: "*" | HTTPMethod[]; + allowHeaders?: "*" | string[]; + exposeHeaders?: "*" | string[]; + credentials?: boolean; + maxAge?: string | false; + preflight?: { + statusCode?: number; + }; +} + +// TODO: Define `ResolvedCorsOptions` as "deep required nonnullable" type of `CorsOptions` +export interface H3ResolvedCorsOptions { + origin: "*" | "null" | (string | RegExp)[] | ((origin: string) => boolean); + methods: "*" | HTTPMethod[]; + allowHeaders: "*" | string[]; + exposeHeaders: "*" | string[]; + credentials: boolean; + maxAge: string | false; + preflight: { + statusCode: number; + }; +} + +export type H3EmptyHeader = Record; + +export type H3AccessControlAllowOriginHeader = + | { + "access-control-allow-origin": "*"; + } + | { + "access-control-allow-origin": "null" | string; + vary: "origin"; + } + | H3EmptyHeader; + +export type H3AccessControlAllowMethodsHeader = + | { + "access-control-allow-methods": "*" | string; + } + | H3EmptyHeader; + +export type H3AccessControlAllowCredentialsHeader = + | { + "access-control-allow-credentials": "true"; + } + | H3EmptyHeader; + +export type H3AccessControlAllowHeadersHeader = + | { + "access-control-allow-headers": "*" | string; + vary: "access-control-request-headers"; + } + | H3EmptyHeader; + +export type H3AccessControlExposeHeadersHeader = + | { + "access-control-expose-headers": "*" | string; + } + | H3EmptyHeader; + +export type H3AccessControlMaxAgeHeader = + | { + "access-control-max-age": string; + } + | H3EmptyHeader; + +export type H3CorsHeaders = + | H3AccessControlAllowOriginHeader + | H3AccessControlAllowMethodsHeader + | H3AccessControlAllowCredentialsHeader + | H3AccessControlAllowHeadersHeader + | H3AccessControlExposeHeadersHeader + | H3AccessControlMaxAgeHeader; diff --git a/src/utils/cors/utils.ts b/src/utils/cors/utils.ts new file mode 100644 index 000000000..a83872f1c --- /dev/null +++ b/src/utils/cors/utils.ts @@ -0,0 +1,195 @@ +import { defu } from "defu"; +import { appendHeaders } from "../response"; +import { getMethod, getRequestHeaders, getRequestHeader } from "../request"; +import type { H3Event } from "../../event"; +import type { + H3CorsOptions, + H3ResolvedCorsOptions, + H3AccessControlAllowOriginHeader, + H3AccessControlAllowMethodsHeader, + H3AccessControlAllowCredentialsHeader, + H3AccessControlAllowHeadersHeader, + H3AccessControlExposeHeadersHeader, + H3AccessControlMaxAgeHeader, +} from "./types"; + +export function resolveCorsOptions( + options: H3CorsOptions = {} +): H3ResolvedCorsOptions { + const defaultOptions: H3ResolvedCorsOptions = { + origin: "*", + methods: "*", + allowHeaders: "*", + exposeHeaders: "*", + credentials: false, + maxAge: false, + preflight: { + statusCode: 204, + }, + }; + + return defu(options, defaultOptions); +} + +export function isPreflightRequest(event: H3Event): boolean { + const method = getMethod(event); + const origin = getRequestHeader(event, "origin"); + const accessControlRequestMethod = getRequestHeader( + event, + "access-control-request-method" + ); + + return method === "OPTIONS" && !!origin && !!accessControlRequestMethod; +} + +export function isCorsOriginAllowed( + origin: ReturnType["origin"], + options: H3CorsOptions +): boolean { + const { origin: originOption } = options; + + if ( + !origin || + !originOption || + originOption === "*" || + originOption === "null" + ) { + return true; + } + + if (Array.isArray(originOption)) { + return originOption.some((_origin) => { + if (_origin instanceof RegExp) { + return _origin.test(origin); + } + + return origin === _origin; + }); + } + + return originOption(origin); +} + +export function createOriginHeaders( + event: H3Event, + options: H3CorsOptions +): H3AccessControlAllowOriginHeader { + const { origin: originOption } = options; + const origin = getRequestHeader(event, "origin"); + + if (!origin || !originOption || originOption === "*") { + return { "access-control-allow-origin": "*" }; + } + + if (typeof originOption === "string") { + return { "access-control-allow-origin": originOption, vary: "origin" }; + } + + return isCorsOriginAllowed(origin, options) + ? { "access-control-allow-origin": origin, vary: "origin" } + : {}; +} + +export function createMethodsHeaders( + options: H3CorsOptions +): H3AccessControlAllowMethodsHeader { + const { methods } = options; + + if (!methods) { + return {}; + } + + if (methods === "*") { + return { "access-control-allow-methods": "*" }; + } + + return methods.length > 0 + ? { "access-control-allow-methods": methods.join(",") } + : {}; +} + +export function createCredentialsHeaders( + options: H3CorsOptions +): H3AccessControlAllowCredentialsHeader { + const { credentials } = options; + + if (credentials) { + return { "access-control-allow-credentials": "true" }; + } + + return {}; +} + +export function createAllowHeaderHeaders( + event: H3Event, + options: H3CorsOptions +): H3AccessControlAllowHeadersHeader { + const { allowHeaders } = options; + + if (!allowHeaders || allowHeaders === "*" || allowHeaders.length === 0) { + const header = getRequestHeader(event, "access-control-request-headers"); + + return header + ? { + "access-control-allow-headers": header, + vary: "access-control-request-headers", + } + : {}; + } + + return { + "access-control-allow-headers": allowHeaders.join(","), + vary: "access-control-request-headers", + }; +} + +export function createExposeHeaders( + options: H3CorsOptions +): H3AccessControlExposeHeadersHeader { + const { exposeHeaders } = options; + + if (!exposeHeaders) { + return {}; + } + + if (exposeHeaders === "*") { + return { "access-control-expose-headers": exposeHeaders }; + } + + return { "access-control-expose-headers": exposeHeaders.join(",") }; +} + +export function createMaxAgeHeader( + options: H3CorsOptions +): H3AccessControlMaxAgeHeader { + const { maxAge } = options; + + if (maxAge) { + return { "access-control-max-age": maxAge }; + } + + return {}; +} + +// TODO: Implemente e2e tests to improve code coverage +/* c8 ignore start */ +export function appendCorsPreflightHeaders( + event: H3Event, + options: H3CorsOptions +) { + appendHeaders(event, createOriginHeaders(event, options)); + appendHeaders(event, createCredentialsHeaders(options)); + appendHeaders(event, createExposeHeaders(options)); + appendHeaders(event, createMethodsHeaders(options)); + appendHeaders(event, createAllowHeaderHeaders(event, options)); +} +/* c8 ignore end */ + +// TODO: Implemente e2e tests to improve code coverage +/* c8 ignore start */ +export function appendCorsHeaders(event: H3Event, options: H3CorsOptions) { + appendHeaders(event, createOriginHeaders(event, options)); + appendHeaders(event, createCredentialsHeaders(options)); + appendHeaders(event, createExposeHeaders(options)); +} +/* c8 ignore end */ diff --git a/src/utils/index.ts b/src/utils/index.ts index 01d1735ae..5e8b84a30 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -7,3 +7,4 @@ export * from "./proxy"; export * from "./request"; export * from "./response"; export * from "./session"; +export * from "./cors"; diff --git a/test/cors.test.ts b/test/cors.test.ts new file mode 100644 index 000000000..d913a9605 --- /dev/null +++ b/test/cors.test.ts @@ -0,0 +1,485 @@ +import { expect, it, describe } from "vitest"; +import { + resolveCorsOptions, + isPreflightRequest, + isCorsOriginAllowed, + createOriginHeaders, + createMethodsHeaders, + createCredentialsHeaders, + createAllowHeaderHeaders, + createExposeHeaders, + createMaxAgeHeader, +} from "../src/utils/cors/utils"; +import type { H3Event } from "../src"; +import type { H3CorsOptions } from "../src/utils/cors"; + +describe("resolveCorsOptions", () => { + it("can merge default options and user options", () => { + const options1 = resolveCorsOptions(); + const options2 = resolveCorsOptions({ + origin: ["https://example.com:3000"], + methods: ["GET", "POST"], + allowHeaders: ["CUSTOM-HEADER"], + exposeHeaders: ["EXPOSED-HEADER"], + maxAge: "12345", + preflight: { + statusCode: 404, + }, + }); + + expect(options1).toEqual({ + origin: "*", + methods: "*", + allowHeaders: "*", + exposeHeaders: "*", + credentials: false, + maxAge: false, + preflight: { + statusCode: 204, + }, + }); + expect(options2).toEqual({ + origin: ["https://example.com:3000"], + methods: ["GET", "POST"], + allowHeaders: ["CUSTOM-HEADER"], + exposeHeaders: ["EXPOSED-HEADER"], + credentials: false, + maxAge: "12345", + preflight: { + statusCode: 404, + }, + }); + }); +}); + +describe("isPreflightRequest", () => { + it("can detect preflight request", () => { + const eventMock = { + node: { + req: { + method: "OPTIONS", + headers: { + origin: "http://example.com", + "access-control-request-method": "GET", + }, + }, + }, + } as H3Event; + + expect(isPreflightRequest(eventMock)).toEqual(true); + }); + + it("can detect request of non-OPTIONS method)", () => { + const eventMock = { + node: { + req: { + method: "GET", + headers: { + origin: "http://example.com", + "access-control-request-method": "GET", + }, + }, + }, + } as H3Event; + + expect(isPreflightRequest(eventMock)).toEqual(false); + }); + + it("can detect request without origin header", () => { + const eventMock = { + node: { + req: { + method: "OPTIONS", + headers: { + "access-control-request-method": "GET", + }, + }, + }, + } as H3Event; + + expect(isPreflightRequest(eventMock)).toEqual(false); + }); + + it("can detect request without AccessControlRequestMethod header", () => { + const eventMock = { + node: { + req: { + method: "OPTIONS", + headers: { + origin: "http://example.com", + }, + }, + }, + } as H3Event; + + expect(isPreflightRequest(eventMock)).toEqual(false); + }); +}); + +describe("isCorsOriginAllowed", () => { + it("returns `true` if `origin` header is not defined", () => { + const origin = undefined; + const options: H3CorsOptions = {}; + + expect(isCorsOriginAllowed(origin, options)).toEqual(true); + }); + + it("returns `true` if `origin` option is not defined", () => { + const origin = "https://example.com"; + const options: H3CorsOptions = {}; + + expect(isCorsOriginAllowed(origin, options)).toEqual(true); + }); + + it('returns `true` if `origin` option is `"*"`', () => { + const origin = "https://example.com"; + const options: H3CorsOptions = { + origin: "*", + }; + + expect(isCorsOriginAllowed(origin, options)).toEqual(true); + }); + + it('returns `true` if `origin` option is `"null"`', () => { + const origin = "https://example.com"; + const options: H3CorsOptions = { + origin: "null", + }; + + expect(isCorsOriginAllowed(origin, options)).toEqual(true); + }); + + it("can detect allowed origin (string)", () => { + const origin = "https://example.com"; + const options: H3CorsOptions = { + origin: ["https://example.com"], + }; + + expect(isCorsOriginAllowed(origin, options)).toEqual(true); + }); + + it("can detect allowed origin (regular expression)", () => { + const origin = "https://example.com"; + const options: H3CorsOptions = { + origin: [/example/], + }; + + expect(isCorsOriginAllowed(origin, options)).toEqual(true); + }); + + it("can detect allowed origin (function)", () => { + const origin = "https://example.com"; + const options: H3CorsOptions = { + origin: (_origin: string) => { + expect(_origin).toEqual(origin); + return true; + }, + }; + + expect(isCorsOriginAllowed(origin, options)).toEqual(true); + }); + + it("can detect allowed origin (falsy)", () => { + const origin = "https://example.com"; + const options: H3CorsOptions = { + origin: ["https://example2.com"], + }; + + expect(isCorsOriginAllowed(origin, options)).toEqual(false); + }); +}); + +describe("createOriginHeaders", () => { + it('returns an object whose `access-control-allow-origin` is `"*"` if `origin` option is not defined, or `"*"`', () => { + const eventMock = { + node: { + req: { + method: "OPTIONS", + headers: { + origin: "http://example.com", + }, + }, + }, + } as H3Event; + const options1: H3CorsOptions = {}; + const options2: H3CorsOptions = { + origin: "*", + }; + + expect(createOriginHeaders(eventMock, options1)).toEqual({ + "access-control-allow-origin": "*", + }); + expect(createOriginHeaders(eventMock, options2)).toEqual({ + "access-control-allow-origin": "*", + }); + }); + + it('returns an object whose `access-control-allow-origin` is `"*"` if `origin` header is not defined', () => { + const eventMock = { + node: { + req: { + method: "OPTIONS", + headers: {}, + }, + }, + } as H3Event; + const options: H3CorsOptions = {}; + + expect(createOriginHeaders(eventMock, options)).toEqual({ + "access-control-allow-origin": "*", + }); + }); + + it('returns an object with `access-control-allow-origin` and `vary` keys if `origin` option is `"null"`', () => { + const eventMock = { + node: { + req: { + method: "OPTIONS", + headers: { + origin: "http://example.com", + }, + }, + }, + } as H3Event; + const options: H3CorsOptions = { + origin: "null", + }; + + expect(createOriginHeaders(eventMock, options)).toEqual({ + "access-control-allow-origin": "null", + vary: "origin", + }); + }); + + it("returns an object with `access-control-allow-origin` and `vary` keys if `origin` option and `origin` header matches", () => { + const eventMock = { + node: { + req: { + method: "OPTIONS", + headers: { + origin: "http://example.com", + }, + }, + }, + } as H3Event; + const options1: H3CorsOptions = { + origin: ["http://example.com"], + }; + const options2: H3CorsOptions = { + origin: [/example.com/], + }; + + expect(createOriginHeaders(eventMock, options1)).toEqual({ + "access-control-allow-origin": "http://example.com", + vary: "origin", + }); + expect(createOriginHeaders(eventMock, options2)).toEqual({ + "access-control-allow-origin": "http://example.com", + vary: "origin", + }); + }); + + it("returns an empty object if `origin` option is one that is not allowed", () => { + const eventMock = { + node: { + req: { + method: "OPTIONS", + headers: { + origin: "http://example.com", + }, + }, + }, + } as H3Event; + const options1: H3CorsOptions = { + origin: ["http://example2.com"], + }; + const options2: H3CorsOptions = { + origin: () => false, + }; + + expect(createOriginHeaders(eventMock, options1)).toEqual({}); + expect(createOriginHeaders(eventMock, options2)).toEqual({}); + }); +}); + +describe("createMethodsHeaders", () => { + it("returns an empty object if `methods` option is not defined or an empty array", () => { + const options1: H3CorsOptions = {}; + const options2: H3CorsOptions = { + methods: [], + }; + + expect(createMethodsHeaders(options1)).toEqual({}); + expect(createMethodsHeaders(options2)).toEqual({}); + }); + + it('returns an object whose `access-control-allow-methods` is `"*"` if `methods` option is `"*"`', () => { + const options1: H3CorsOptions = { + methods: "*", + }; + + expect(createMethodsHeaders(options1)).toEqual({ + "access-control-allow-methods": "*", + }); + }); + + it("returns an object whose `access-control-allow-methods` is set as `methods` option", () => { + const options: H3CorsOptions = { + methods: ["GET", "POST"], + }; + + expect(createMethodsHeaders(options)).toEqual({ + "access-control-allow-methods": "GET,POST", + }); + }); +}); + +describe("createCredentialsHeaders", () => { + it("returns an empty object if `credentials` option is not defined", () => { + const options: H3CorsOptions = {}; + + expect(createCredentialsHeaders(options)).toEqual({}); + }); + + it('returns an object whose `access-control-allow-credentials` is `"true"` if `credentials` option is true', () => { + const options: H3CorsOptions = { + credentials: true, + }; + + expect(createCredentialsHeaders(options)).toEqual({ + "access-control-allow-credentials": "true", + }); + }); +}); + +describe("createAllowHeaderHeaders", () => { + it('returns an object with `access-control-allow-headers` and `vary` keys according to `access-control-request-headers` header if `allowHeaders` option is not defined, `"*"`, or an empty array', () => { + const eventMock = { + node: { + req: { + method: "OPTIONS", + headers: { + "access-control-request-headers": "CUSTOM-HEADER", + }, + }, + }, + } as H3Event; + const options1: H3CorsOptions = {}; + const options2: H3CorsOptions = { + allowHeaders: "*", + }; + const options3: H3CorsOptions = { + allowHeaders: [], + }; + + expect(createAllowHeaderHeaders(eventMock, options1)).toEqual({ + "access-control-allow-headers": "CUSTOM-HEADER", + vary: "access-control-request-headers", + }); + expect(createAllowHeaderHeaders(eventMock, options2)).toEqual({ + "access-control-allow-headers": "CUSTOM-HEADER", + vary: "access-control-request-headers", + }); + expect(createAllowHeaderHeaders(eventMock, options3)).toEqual({ + "access-control-allow-headers": "CUSTOM-HEADER", + vary: "access-control-request-headers", + }); + }); + + it("returns an object with `access-control-allow-headers` and `vary` keys according to `allowHeaders` option if `access-control-request-headers` header is not defined", () => { + const eventMock = { + node: { + req: { + method: "OPTIONS", + headers: {}, + }, + }, + } as H3Event; + const options: H3CorsOptions = { + allowHeaders: ["CUSTOM-HEADER"], + }; + + expect(createAllowHeaderHeaders(eventMock, options)).toEqual({ + "access-control-allow-headers": "CUSTOM-HEADER", + vary: "access-control-request-headers", + }); + }); + + it('returns an empty object if `allowHeaders` option is not defined, `"*"`, or an empty array, and `access-control-request-headers` is not defined', () => { + const eventMock = { + node: { + req: { + method: "OPTIONS", + headers: {}, + }, + }, + } as H3Event; + const options1: H3CorsOptions = {}; + const options2: H3CorsOptions = { + allowHeaders: "*", + }; + const options3: H3CorsOptions = { + allowHeaders: [], + }; + + expect(createAllowHeaderHeaders(eventMock, options1)).toEqual({}); + expect(createAllowHeaderHeaders(eventMock, options2)).toEqual({}); + expect(createAllowHeaderHeaders(eventMock, options3)).toEqual({}); + }); +}); + +describe("createExposeHeaders", () => { + it("returns an object if `exposeHeaders` option is not defined", () => { + const options: H3CorsOptions = {}; + + expect(createExposeHeaders(options)).toEqual({}); + }); + + it("returns an object with `access-control-expose-headers` key according to `exposeHeaders` option", () => { + const options1: H3CorsOptions = { + exposeHeaders: "*", + }; + const options2: H3CorsOptions = { + exposeHeaders: ["EXPOSED-HEADER-1", "EXPOSED-HEADER-2"], + }; + + expect(createExposeHeaders(options1)).toEqual({ + "access-control-expose-headers": "*", + }); + expect(createExposeHeaders(options2)).toEqual({ + "access-control-expose-headers": "EXPOSED-HEADER-1,EXPOSED-HEADER-2", + }); + }); +}); + +describe("createMaxAgeHeader", () => { + it("returns an object if `maxAge` option is not defined, false, or an empty string", () => { + const options1: H3CorsOptions = {}; + const options2: H3CorsOptions = { + maxAge: false, + }; + const options3: H3CorsOptions = { + maxAge: "", + }; + + expect(createMaxAgeHeader(options1)).toEqual({}); + expect(createMaxAgeHeader(options2)).toEqual({}); + expect(createMaxAgeHeader(options3)).toEqual({}); + }); + + it("returns an object with `access-control-max-age` key according to `exposeHeaders` option", () => { + const options1: H3CorsOptions = { + maxAge: "12345", + }; + const options2: H3CorsOptions = { + maxAge: "0", + }; + + expect(createMaxAgeHeader(options1)).toEqual({ + "access-control-max-age": "12345", + }); + expect(createMaxAgeHeader(options2)).toEqual({ + "access-control-max-age": "0", + }); + }); +}); From 3495dbe2fc7df4a3381cc5ac9039c6647d63a79e Mon Sep 17 00:00:00 2001 From: Enkot Date: Thu, 16 Feb 2023 17:01:37 +0200 Subject: [PATCH 08/10] feat(proxy): support `cookieDomainRewrite` and `cookiePathRewrite` (#313) * feat: add cookieDomainRewrite and cookiePathRewrite to proxy * refactor: simplify --------- Co-authored-by: Pooya Parsa --- README.md | 17 ++- src/utils/internal/cookies.ts | 2 +- src/utils/proxy.ts | 44 ++++++- test/proxy.test.ts | 211 ++++++++++++++++++++++++++++++++++ 4 files changed, 270 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3f78992a1..a6a36d2e6 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,19 @@ app.use(eventHandler(() => '

Hello world!

')) app.use('/1', eventHandler(() => '

Hello world!

')) .use('/2', eventHandler(() => '

Goodbye!

')) +// We can proxy requests and rewrite cookie's domain and path +app.use('/api', eventHandler((event) => proxyRequest('https://example.com', { + // f.e. keep one domain unchanged, rewrite one domain and remove other domains + cookieDomainRewrite: { + "example.com": "example.com", + "example.com": "somecompany.co.uk", + "*": "", + }, + cookiePathRewrite: { + "/": "/api" + }, +})) + // Legacy middleware with 3rd argument are automatically promisified app.use(fromNodeMiddleware((req, res, next) => { req.setHeader('x-foo', 'bar'); next() })) @@ -146,8 +159,8 @@ H3 has a concept of composable utilities that accept `event` (from `eventHandler - `isMethod(event, expected, allowHead?)` - `assertMethod(event, expected, allowHead?)` - `createError({ statusCode, statusMessage, data? })` -- `sendProxy(event, { target, headers?, fetchOptions?, fetch?, sendStream? })` -- `proxyRequest(event, { target, headers?, fetchOptions?, fetch?, sendStream? })` +- `sendProxy(event, { target, ...options })` +- `proxyRequest(event, { target, ...options })` - `fetchWithEvent(event, req, init, { fetch? }?)` - `getProxyRequestHeaders(event)` - `sendNoContent(event, code = 204)` diff --git a/src/utils/internal/cookies.ts b/src/utils/internal/cookies.ts index d96162a7a..8e8b7ef58 100644 --- a/src/utils/internal/cookies.ts +++ b/src/utils/internal/cookies.ts @@ -7,7 +7,7 @@ * Credits to: https://github.com/tomball for original and https://github.com/chrusart for JavaScript implementation * @source https://github.com/nfriedly/set-cookie-parser/blob/3eab8b7d5d12c8ed87832532861c1a35520cf5b3/lib/set-cookie.js#L144 */ -export default function splitCookiesString(cookiesString: string) { +export default function splitCookiesString(cookiesString: string): string[] { if (typeof cookiesString !== "string") { return []; } diff --git a/src/utils/proxy.ts b/src/utils/proxy.ts index 0069edb0d..46757d32e 100644 --- a/src/utils/proxy.ts +++ b/src/utils/proxy.ts @@ -9,6 +9,8 @@ export interface ProxyOptions { fetchOptions?: RequestInit; fetch?: typeof fetch; sendStream?: boolean; + cookieDomainRewrite?: string | Record; + cookiePathRewrite?: string | Record; } const PayloadMethods = new Set(["PATCH", "POST", "PUT", "DELETE"]); @@ -75,9 +77,27 @@ export async function sendProxy( continue; } if (key === "set-cookie") { - event.node.res.setHeader("set-cookie", splitCookiesString(value)); + const cookies = splitCookiesString(value).map((cookie) => { + if (opts.cookieDomainRewrite) { + cookie = rewriteCookieProperty( + cookie, + opts.cookieDomainRewrite, + "domain" + ); + } + if (opts.cookiePathRewrite) { + cookie = rewriteCookieProperty( + cookie, + opts.cookiePathRewrite, + "path" + ); + } + return cookie; + }); + event.node.res.setHeader("set-cookie", cookies); continue; } + event.node.res.setHeader(key, value); } @@ -140,3 +160,25 @@ function _getFetch(_fetch?: typeof fetch) { "fetch is not available. Try importing `node-fetch-native/polyfill` for Node.js." ); } + +function rewriteCookieProperty( + header: string, + map: string | Record, + property: string +) { + const _map = typeof map === "string" ? { "*": map } : map; + return header.replace( + new RegExp(`(;\\s*${property}=)([^;]+)`, "gi"), + (match, prefix, previousValue) => { + let newValue; + if (previousValue in _map) { + newValue = _map[previousValue]; + } else if ("*" in _map) { + newValue = _map["*"]; + } else { + return match; + } + return newValue ? prefix + newValue : ""; + } + ); +} diff --git a/test/proxy.test.ts b/test/proxy.test.ts index 721175852..3a5c458c4 100644 --- a/test/proxy.test.ts +++ b/test/proxy.test.ts @@ -9,6 +9,7 @@ import { eventHandler, getHeaders, getMethod, + setHeader, readRawBody, setCookie, } from "../src"; @@ -130,4 +131,214 @@ describe("", () => { ]); }); }); + + describe("cookieDomainRewrite", () => { + beforeEach(() => { + app.use( + "/debug", + eventHandler((event) => { + setHeader( + event, + "set-cookie", + "foo=219ffwef9w0f; Domain=somecompany.co.uk; Path=/; Expires=Wed, 30 Aug 2022 00:00:00 GMT" + ); + return {}; + }) + ); + }); + + it("can rewrite cookie domain by string", async () => { + app.use( + "/", + eventHandler((event) => { + return proxyRequest(event, url + "/debug", { + fetch, + cookieDomainRewrite: "new.domain", + }); + }) + ); + + const result = await fetch(url + "/"); + + expect(result.headers.get("set-cookie")).toEqual( + "foo=219ffwef9w0f; Domain=new.domain; Path=/; Expires=Wed, 30 Aug 2022 00:00:00 GMT" + ); + }); + + it("can rewrite cookie domain by mapper object", async () => { + app.use( + "/", + eventHandler((event) => { + return proxyRequest(event, url + "/debug", { + fetch, + cookieDomainRewrite: { + "somecompany.co.uk": "new.domain", + }, + }); + }) + ); + + const result = await fetch(url + "/"); + + expect(result.headers.get("set-cookie")).toEqual( + "foo=219ffwef9w0f; Domain=new.domain; Path=/; Expires=Wed, 30 Aug 2022 00:00:00 GMT" + ); + }); + + it("can rewrite domains of multiple cookies", async () => { + app.use( + "/multiple/debug", + eventHandler((event) => { + setHeader(event, "set-cookie", [ + "foo=219ffwef9w0f; Domain=somecompany.co.uk; Path=/; Expires=Wed, 30 Aug 2022 00:00:00 GMT", + "bar=38afes7a8; Domain=somecompany.co.uk; Path=/; Expires=Wed, 30 Aug 2022 00:00:00 GMT", + ]); + return {}; + }) + ); + + app.use( + "/", + eventHandler((event) => { + return proxyRequest(event, url + "/multiple/debug", { + fetch, + cookieDomainRewrite: { + "somecompany.co.uk": "new.domain", + }, + }); + }) + ); + + const result = await fetch(url + "/"); + + expect(result.headers.get("set-cookie")).toEqual( + "foo=219ffwef9w0f; Domain=new.domain; Path=/; Expires=Wed, 30 Aug 2022 00:00:00 GMT, bar=38afes7a8; Domain=new.domain; Path=/; Expires=Wed, 30 Aug 2022 00:00:00 GMT" + ); + }); + + it("can remove cookie domain", async () => { + app.use( + "/", + eventHandler((event) => { + return proxyRequest(event, url + "/debug", { + fetch, + cookieDomainRewrite: { + "somecompany.co.uk": "", + }, + }); + }) + ); + + const result = await fetch(url + "/"); + + expect(result.headers.get("set-cookie")).toEqual( + "foo=219ffwef9w0f; Path=/; Expires=Wed, 30 Aug 2022 00:00:00 GMT" + ); + }); + }); + + describe("cookiePathRewrite", () => { + beforeEach(() => { + app.use( + "/debug", + eventHandler((event) => { + setHeader( + event, + "set-cookie", + "foo=219ffwef9w0f; Domain=somecompany.co.uk; Path=/; Expires=Wed, 30 Aug 2022 00:00:00 GMT" + ); + return {}; + }) + ); + }); + + it("can rewrite cookie path by string", async () => { + app.use( + "/", + eventHandler((event) => { + return proxyRequest(event, url + "/debug", { + fetch, + cookiePathRewrite: "/api", + }); + }) + ); + + const result = await fetch(url + "/"); + + expect(result.headers.get("set-cookie")).toEqual( + "foo=219ffwef9w0f; Domain=somecompany.co.uk; Path=/api; Expires=Wed, 30 Aug 2022 00:00:00 GMT" + ); + }); + + it("can rewrite cookie path by mapper object", async () => { + app.use( + "/", + eventHandler((event) => { + return proxyRequest(event, url + "/debug", { + fetch, + cookiePathRewrite: { + "/": "/api", + }, + }); + }) + ); + + const result = await fetch(url + "/"); + + expect(result.headers.get("set-cookie")).toEqual( + "foo=219ffwef9w0f; Domain=somecompany.co.uk; Path=/api; Expires=Wed, 30 Aug 2022 00:00:00 GMT" + ); + }); + + it("can rewrite paths of multiple cookies", async () => { + app.use( + "/multiple/debug", + eventHandler((event) => { + setHeader(event, "set-cookie", [ + "foo=219ffwef9w0f; Domain=somecompany.co.uk; Path=/; Expires=Wed, 30 Aug 2022 00:00:00 GMT", + "bar=38afes7a8; Domain=somecompany.co.uk; Path=/; Expires=Wed, 30 Aug 2022 00:00:00 GMT", + ]); + return {}; + }) + ); + + app.use( + "/", + eventHandler((event) => { + return proxyRequest(event, url + "/multiple/debug", { + fetch, + cookiePathRewrite: { + "/": "/api", + }, + }); + }) + ); + + const result = await fetch(url + "/"); + + expect(result.headers.get("set-cookie")).toEqual( + "foo=219ffwef9w0f; Domain=somecompany.co.uk; Path=/api; Expires=Wed, 30 Aug 2022 00:00:00 GMT, bar=38afes7a8; Domain=somecompany.co.uk; Path=/api; Expires=Wed, 30 Aug 2022 00:00:00 GMT" + ); + }); + + it("can remove cookie path", async () => { + app.use( + "/", + eventHandler((event) => { + return proxyRequest(event, url + "/debug", { + fetch, + cookiePathRewrite: { + "/": "", + }, + }); + }) + ); + + const result = await fetch(url + "/"); + + expect(result.headers.get("set-cookie")).toEqual( + "foo=219ffwef9w0f; Domain=somecompany.co.uk; Expires=Wed, 30 Aug 2022 00:00:00 GMT" + ); + }); + }); }); From bdca8591cdeb04e6391af36cee2a0cee0ea92be8 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Fri, 17 Feb 2023 00:49:59 +0100 Subject: [PATCH 09/10] chore: update ufo dependency --- package.json | 4 ++-- pnpm-lock.yaml | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 0836c2c9e..373a71aa6 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "destr": "^1.2.2", "iron-webcrypto": "^0.5.0", "radix3": "^1.0.0", - "ufo": "^1.0.1", + "ufo": "^1.1.0", "uncrypto": "^0.1.2" }, "devDependencies": { @@ -53,7 +53,7 @@ "get-port": "^6.1.2", "jiti": "^1.17.0", "listhen": "^1.0.2", - "node-fetch-native": "^1.0.1", + "node-fetch-native": "^1.0.2", "prettier": "^2.8.4", "supertest": "^6.3.3", "typescript": "^4.9.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 75e3eb018..c704fc721 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,12 +19,12 @@ specifiers: iron-webcrypto: ^0.5.0 jiti: ^1.17.0 listhen: ^1.0.2 - node-fetch-native: ^1.0.1 + node-fetch-native: ^1.0.2 prettier: ^2.8.4 radix3: ^1.0.0 supertest: ^6.3.3 typescript: ^4.9.5 - ufo: ^1.0.1 + ufo: ^1.1.0 unbuild: ^1.1.1 uncrypto: ^0.1.2 vitest: ^0.28.5 @@ -35,7 +35,7 @@ dependencies: destr: 1.2.2 iron-webcrypto: 0.5.0 radix3: 1.0.0 - ufo: 1.0.1 + ufo: 1.1.0 uncrypto: 0.1.2 devDependencies: @@ -53,7 +53,7 @@ devDependencies: get-port: 6.1.2 jiti: 1.17.0 listhen: 1.0.2 - node-fetch-native: 1.0.1 + node-fetch-native: 1.0.2 prettier: 2.8.4 supertest: 6.3.3 typescript: 4.9.5 @@ -1591,7 +1591,7 @@ packages: convert-gitmoji: 0.1.3 execa: 6.1.0 mri: 1.2.0 - node-fetch-native: 1.0.1 + node-fetch-native: 1.0.2 pkg-types: 1.0.1 scule: 1.0.0 semver: 7.3.8 @@ -3254,7 +3254,7 @@ packages: defu: 6.1.2 https-proxy-agent: 5.0.1 mri: 1.2.0 - node-fetch-native: 1.0.1 + node-fetch-native: 1.0.2 pathe: 1.1.0 tar: 6.1.13 transitivePeerDependencies: @@ -3962,7 +3962,7 @@ packages: http-shutdown: 1.2.2 ip-regex: 5.0.0 node-forge: 1.3.1 - ufo: 1.0.1 + ufo: 1.1.0 dev: true /local-pkg/0.4.2: @@ -4240,7 +4240,7 @@ packages: acorn: 8.8.1 pathe: 1.1.0 pkg-types: 1.0.1 - ufo: 1.0.1 + ufo: 1.1.0 dev: true /module-deps/6.2.3: @@ -4343,8 +4343,8 @@ packages: lower-case: 1.1.4 dev: true - /node-fetch-native/1.0.1: - resolution: {integrity: sha512-VzW+TAk2wE4X9maiKMlT+GsPU4OMmR1U9CrHSmd3DFLn2IcZ9VJ6M6BBugGfYUnPCLSYxXdZy17M0BEJyhUTwg==} + /node-fetch-native/1.0.2: + resolution: {integrity: sha512-KIkvH1jl6b3O7es/0ShyCgWLcfXxlBrLBbP3rOr23WArC66IMcU4DeZEeYEOwnopYhawLTn7/y+YtmASe8DFVQ==} dev: true /node-forge/1.3.1: @@ -5648,8 +5648,8 @@ packages: hasBin: true dev: true - /ufo/1.0.1: - resolution: {integrity: sha512-boAm74ubXHY7KJQZLlXrtMz52qFvpsbOxDcZOnw/Wf+LS4Mmyu7JxmzD4tDLtUQtmZECypJ0FrCz4QIe6dvKRA==} + /ufo/1.1.0: + resolution: {integrity: sha512-LQc2s/ZDMaCN3QLpa+uzHUOQ7SdV0qgv3VBXOolQGXTaaZpIur6PwUclF5nN2hNkiTRcUugXd1zFOW3FLJ135Q==} /umd/3.0.3: resolution: {integrity: sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==} From abe7ddbdd7219d4f9dc322c6f2e735d1c6796f57 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Fri, 17 Feb 2023 00:50:53 +0100 Subject: [PATCH 10/10] chore(release): v1.5.0 --- CHANGELOG.md | 35 +++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79d35f5e2..45d7e61bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,41 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## v1.5.0 + +[compare changes](https://github.com/unjs/h3/compare/v1.4.0...v1.5.0) + + +### 🚀 Enhancements + + - Add cors utils ([#322](https://github.com/unjs/h3/pull/322)) + - **proxy:** Support `cookieDomainRewrite` and `cookiePathRewrite` ([#313](https://github.com/unjs/h3/pull/313)) + +### 🩹 Fixes + + - **proxy:** Separate multiple cookie headers ([#319](https://github.com/unjs/h3/pull/319)) + +### 📖 Documentation + + - Update build status badge url ([#331](https://github.com/unjs/h3/pull/331)) + +### 🌊 Types + + - Export `MultiPartData` ([#332](https://github.com/unjs/h3/pull/332)) + +### 🏡 Chore + + - Improve `lint` npm script ([#329](https://github.com/unjs/h3/pull/329)) + - Update ufo dependency ([bdca859](https://github.com/unjs/h3/commit/bdca859)) + +### ❤️ Contributors + +- Pooya Parsa +- Enkot +- Nozomu Ikuta +- Martin Meixger +- Divyansh Singh + ## v1.4.0 [compare changes](https://github.com/unjs/h3/compare/v1.3.0...v1.4.0) diff --git a/package.json b/package.json index 373a71aa6..2e091175e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "h3", - "version": "1.4.0", + "version": "1.5.0", "description": "Tiny JavaScript Server", "repository": "unjs/h3", "license": "MIT",