├── .npmignore ├── .eslintignore ├── .gitignore ├── .editorconfig ├── .github └── workflows │ ├── nodeci.yml │ └── codeql.yml ├── LICENSE ├── package.json ├── README.md ├── .eslintrc ├── lib └── index.js └── test └── index.js /.npmignore: -------------------------------------------------------------------------------- 1 | **/.* 2 | *.yml 3 | *.md 4 | test 5 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.md 2 | *.log 3 | *.json 4 | node_modules 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # fs 2 | .DS_Store 3 | Thumbs.db 4 | 5 | # logs 6 | *.log 7 | 8 | # libs 9 | node_modules/* 10 | 11 | # ide 12 | .idea 13 | 14 | # dropbox 15 | Icon? 16 | .dropbox 17 | 18 | coverage.html 19 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | # Indentation override for all JS under lib directory 11 | [server/**.js, server/**.json] 12 | curly_bracket_next_line = false 13 | spaces_around_operators = true 14 | spaces_around_brackets = true 15 | indent_style = space 16 | quote_type = single 17 | indent_size = 2 18 | tab_width = 2 19 | charset = utf-8 20 | -------------------------------------------------------------------------------- /.github/workflows/nodeci.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: nodeci 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | node-version: [14.x, 16.x] 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - uses: actions/setup-node@v1 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | - run: npm i 26 | - run: npm test 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Gene Diaz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "disinfect", 3 | "version": "2.0.0", 4 | "author": "Gene Diaz (http://genediazjr.com/)", 5 | "description": "Request query, payload, and params sanitization", 6 | "keywords": [ 7 | "hapi", 8 | "plugin", 9 | "sanitizer", 10 | "query sanitizer", 11 | "payload sanitizer", 12 | "parameter sanitizer", 13 | "google caja", 14 | "caja" 15 | ], 16 | "license": "MIT", 17 | "repository": "git://github.com/genediazjr/disinfect", 18 | "bugs": "https://github.com/genediazjr/disinfect/issues", 19 | "main": "lib/index.js", 20 | "engines": { 21 | "node": ">=14.0.0" 22 | }, 23 | "scripts": { 24 | "test": "lab -c -v -L -a @hapi/code", 25 | "test-cover": "lab -c -v -L -a @hapi/code -r html -o ./coverage.html -L && open ./coverage.html" 26 | }, 27 | "dependencies": { 28 | "@hapi/hoek": "^10.0.1", 29 | "async": "^3.2.4", 30 | "joi": "^17.7.0", 31 | "sanitizer": "^0.1.3" 32 | }, 33 | "devDependencies": { 34 | "@hapi/code": "^9.0.1", 35 | "@hapi/hapi": "^20.2.2", 36 | "@hapi/lab": "^25.0.1", 37 | "coveralls": "^3.1.1" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: codeql 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '44 16 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v2 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # disinfect 2 | [![NodeCI](https://github.com/genediazjr/disinfect/actions/workflows/nodeci.yml/badge.svg)](https://github.com/genediazjr/disinfect/actions/workflows/nodeci.yml) 3 | [![CodeQL](https://github.com/genediazjr/disinfect/actions/workflows/codeql.yml/badge.svg)](https://github.com/genediazjr/disinfect/actions/workflows/codeql.yml) 4 | [![NPM Version](https://badge.fury.io/js/disinfect.svg)](https://www.npmjs.com/disinfect) 5 | [![NPM Downloads](https://img.shields.io/npm/dt/disinfect.svg?maxAge=2592000)](https://www.npmjs.com/disinfect) 6 | [![Code Climate](https://codeclimate.com/github/genediazjr/disinfect/badges/gpa.svg)](https://codeclimate.com/github/genediazjr/disinfect) 7 | [![Known Vulnerabilities](https://snyk.io/test/github/genediazjr/disinfect/badge.svg)](https://snyk.io/test/github/genediazjr/disinfect) 8 | 9 | Hapi plugin to apply Google's [Caja](https://github.com/google/caja) HTML Sanitizer on route query, payload, and params. 10 | 11 | * Capable for custom sanitization and per-route configuration. 12 | * Can also be used for input formatting using the custom sanitizer option. 13 | * Can be disabled per route. 14 | 15 | ## Usage 16 | 17 | ```js 18 | const registerPlugins = async (server) => Promise.all([ 19 | server.register({ 20 | plugin: require('disinfect'), 21 | options: { 22 | disinfectQuery: true, 23 | disinfectParams: true, 24 | disinfectPayload: true 25 | } 26 | }) 27 | ]); 28 | 29 | registerPlugins(server) 30 | .then(() => { 31 | // ... 32 | }) 33 | .catch((err) => { 34 | // ... 35 | }) 36 | 37 | ``` 38 | [Glue](https://github.com/hapijs/glue) manifest 39 | ```js 40 | register: { 41 | plugins: [ 42 | { 43 | plugin: require('disinfect'), 44 | options: { 45 | disinfectQuery: true, 46 | disinfectParams: true, 47 | disinfectPayload: true 48 | } 49 | } 50 | ] 51 | } 52 | ``` 53 | 54 | ## Options 55 | 56 | * **deleteEmpty** - remove empty query or payload keys. 57 | * **deleteWhitespace** - remove whitespace query, payload, or params keys. 58 | * **disinfectQuery** - sanitize query strings. 59 | * **disinfectParams** - sanitize url params. 60 | * **disinfectPayload** - sanitize payload. 61 | * **genericSanitizer** - custom synchronous function to do the sanitization of query, payload, and params. 62 | * **querySanitizer** - custom synchronous function to do the sanitization of query strings. 63 | * **paramsSanitizer** - custom synchronous function to do the sanitization of url params. 64 | * **payloadSanitizer** - custom synchronous function to do the sanitization of payload. 65 | 66 | `deleteEmpty` and `deleteWhitespace` defaults to `false`. 67 | 68 | `disinfectQuery`, `disinfectParams`, and `disinfectPayload` defaults to `false`. If set to true, object will be passed to `caja` first before custom sanitizers. 69 | 70 | ``` 71 | dirtyObject ->`Caja` sanitizer -> `genericSanitizer` -> `query-`, `params-`, or `payload-` sanitizer -> deleteWhitespace -> deleteEmpty -> cleanObject. 72 | ``` 73 | 74 | `genericSanitizer`, `querySanitizer`, `paramsSanitizer`, and `payloadSanitizer` should be in the following format: 75 | 76 | ```js 77 | const customSanitizer = (dirtyObj) => { 78 | // ... 79 | return cleanObj; 80 | } 81 | ``` 82 | 83 | All options can be passed on a per-[route](http://hapijs.com/api#route-options) basis. Route options overrides server options. 84 | 85 | ```js 86 | // example 87 | { 88 | path: '/', 89 | method: 'get', 90 | handler: (request, reply) => { 91 | ... 92 | }, 93 | options: { 94 | plugins: { 95 | disinfect: { 96 | disinfectQuery: true, 97 | disinfectParams: false, 98 | disinfectPayload: true 99 | } 100 | } 101 | } 102 | } 103 | ``` 104 | 105 | Disable on a route. 106 | ```js 107 | { 108 | path: '/', 109 | method: 'get', 110 | handler: (request, reply) => { 111 | ... 112 | }, 113 | options: { 114 | plugins: { 115 | disinfect: false 116 | } 117 | } 118 | } 119 | ``` 120 | 121 | ## Contributing 122 | * Include 100% test coverage 123 | * Follow the [Hapi coding conventions](http://hapijs.com/styleguide) 124 | * Submit an issue first for significant changes. 125 | 126 | ## Credits 127 | * [hapi-sanitize-payload](https://github.com/lob/hapi-sanitize-payload) - Hapi plugin to sanitize the request payload 128 | * [Caja-HTML-Sanitizer](https://github.com/theSmaw/Caja-HTML-Sanitizer) - Bundles Google Caja's HTML Sanitizer within a npm installable node.js module 129 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 8 4 | }, 5 | "env": { 6 | "node": true, 7 | "es6": true 8 | }, 9 | "rules": { 10 | "semi": 2, 11 | "yoda": 2, 12 | "curly": 2, 13 | "indent": 2, 14 | "eqeqeq": 2, 15 | "new-cap": 2, 16 | "eol-last": 2, 17 | "use-isnan": 2, 18 | "wrap-iife": 2, 19 | "camelcase": 2, 20 | "complexity": 2, 21 | "comma-style": 2, 22 | "key-spacing": 2, 23 | "dot-notation": 2, 24 | "semi-spacing": 2, 25 | "guard-for-in": 2, 26 | "valid-typeof": 2, 27 | "comma-dangle": 2, 28 | "arrow-parens": 2, 29 | "prefer-const": 2, 30 | "default-case": 2, 31 | "arrow-spacing": 2, 32 | "comma-spacing": 2, 33 | "block-spacing": 2, 34 | "global-require": 0, 35 | "spaced-comment": 2, 36 | "callback-return": 2, 37 | "keyword-spacing": 2, 38 | "space-unary-ops": 2, 39 | "space-infix-ops": 2, 40 | "space-in-parens": 2, 41 | "block-scoped-var": 2, 42 | "prefer-rest-params": 1, 43 | "space-before-blocks": 2, 44 | "handle-callback-err": 2, 45 | "max-nested-callbacks": 2, 46 | "array-bracket-spacing": 2, 47 | "array-callback-return": 2, 48 | "prefer-arrow-callback": 1, 49 | "generator-star-spacing": 2, 50 | "computed-property-spacing": 2, 51 | "no-var": 2, 52 | "no-new": 2, 53 | "no-eval": 2, 54 | "no-with": 2, 55 | "no-void": 2, 56 | "no-undef": 2, 57 | "no-proto": 2, 58 | "no-octal": 2, 59 | "no-alert": 2, 60 | "no-labels": 2, 61 | "no-caller": 2, 62 | "no-bitwise": 2, 63 | "no-console": 2, 64 | "no-debugger": 2, 65 | "no-iterator": 2, 66 | "no-continue": 2, 67 | "no-new-func": 2, 68 | "no-div-regex": 2, 69 | "no-loop-func": 2, 70 | "no-dupe-args": 2, 71 | "no-dupe-keys": 2, 72 | "no-label-var": 2, 73 | "no-multi-str": 2, 74 | "no-lonely-if": 2, 75 | "no-redeclare": 2, 76 | "no-sequences": 2, 77 | "no-obj-calls": 2, 78 | "no-ex-assign": 2, 79 | "no-new-object": 2, 80 | "no-new-symbol": 2, 81 | "no-extra-semi": 2, 82 | "no-delete-var": 2, 83 | "no-script-url": 2, 84 | "no-new-require": 2, 85 | "no-cond-assign": 2, 86 | "no-self-assign": 2, 87 | "no-lone-blocks": 2, 88 | "no-func-assign": 2, 89 | "no-else-return": 2, 90 | "no-unused-vars": 2, 91 | "no-unreachable": 2, 92 | "no-fallthrough": 2, 93 | "no-new-wrappers": 2, 94 | "no-self-compare": 2, 95 | "no-multi-spaces": 2, 96 | "no-regex-spaces": 2, 97 | "no-class-assign": 2, 98 | "no-const-assign": 2, 99 | "no-implied-eval": 2, 100 | "no-useless-call": 2, 101 | "no-octal-escape": 2, 102 | "no-throw-literal": 2, 103 | "no-sparse-arrays": 2, 104 | "no-return-assign": 2, 105 | "no-unsafe-finally": 2, 106 | "no-param-reassign": 2, 107 | "no-useless-escape": 2, 108 | "no-useless-concat": 2, 109 | "no-invalid-regexp": 2, 110 | "no-negated-in-lhs": 2, 111 | "no-duplicate-case": 2, 112 | "no-trailing-spaces": 2, 113 | "no-inline-comments": 2, 114 | "no-confusing-arrow": 2, 115 | "no-unneeded-ternary": 2, 116 | "no-floating-decimal": 2, 117 | "no-implicit-coercion": 2, 118 | "no-duplicate-imports": 2, 119 | "no-case-declarations": 2, 120 | "no-this-before-super": 2, 121 | "no-use-before-define": 2, 122 | "no-constant-condition": 2, 123 | "no-inner-declarations": 2, 124 | "no-extra-boolean-cast": 2, 125 | "no-dupe-class-members": 2, 126 | "no-unexpected-multiline": 2, 127 | "no-irregular-whitespace": 2, 128 | "no-useless-computed-key": 2, 129 | "no-empty-character-class": 2, 130 | "no-mixed-spaces-and-tabs": 2, 131 | "no-unmodified-loop-condition": 2, 132 | "no-whitespace-before-property": 2, 133 | "no-multiple-empty-lines": [ 134 | 2, 135 | { 136 | "max": 2, 137 | "maxEOF": 1 138 | } 139 | ], 140 | "space-before-function-paren": [ 141 | 2, 142 | { 143 | "anonymous": "always", 144 | "named": "never" 145 | } 146 | ], 147 | "no-underscore-dangle": [ 148 | 2, 149 | { 150 | "allow": [ 151 | "_disinfect" 152 | ] 153 | } 154 | ], 155 | "operator-linebreak": [ 156 | 2, 157 | "before" 158 | ], 159 | "arrow-body-style": [ 160 | 2, 161 | "always" 162 | ], 163 | "dot-location": [ 164 | 2, 165 | "property" 166 | ], 167 | "brace-style": [ 168 | 2, 169 | "stroustrup" 170 | ], 171 | "quote-props": [ 172 | 2, 173 | "as-needed" 174 | ], 175 | "quotes": [ 176 | 2, 177 | "single" 178 | ], 179 | "one-var": [ 180 | 2, 181 | "never" 182 | ], 183 | "strict": [ 184 | 2, 185 | "global" 186 | ] 187 | }, 188 | "globals": {} 189 | } 190 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Caja = require('sanitizer'); 4 | const Hoek = require('@hapi/hoek'); 5 | const Joi = require('joi'); 6 | const internals = {}; 7 | 8 | internals.whiteRegex = new RegExp(/^[\s\f\n\r\t\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000\ufeff\x09\x0a\x0b\x0c\x0d\x20\xa0]+$/); 9 | 10 | internals.noop = (obj) => { 11 | 12 | return obj; 13 | }; 14 | 15 | internals.recurseObj = (item, index, array) => { 16 | if (typeof array[index] === 'object') { 17 | array[index] = internals.sanitize(array[index]); 18 | } 19 | else { 20 | array[index] = Caja.sanitize(array[index]); 21 | } 22 | }; 23 | 24 | internals.sanitize = (obj) => { 25 | 26 | const keys = Object.keys(obj); 27 | for (let i = 0; i < keys.length; ++i) { 28 | if (obj[keys[i]] !== null) { 29 | if (typeof obj[keys[i]] === 'object') { 30 | if (Array.isArray(obj[keys[i]])) { 31 | obj[keys[i]].forEach(internals.recurseObj); 32 | } 33 | else { 34 | obj[keys[i]] = internals.sanitize(obj[keys[i]]); 35 | } 36 | } 37 | else if (typeof obj === 'object') { 38 | obj[keys[i]] = Caja.sanitize(obj[keys[i]]); 39 | } 40 | } 41 | } 42 | return obj; 43 | }; 44 | 45 | internals.deleteEmpty = (obj) => { 46 | 47 | const keys = Object.keys(obj); 48 | 49 | for (let i = 0; i < keys.length; ++i) { 50 | if (obj[keys[i]] === '' || obj[keys[i]] === null) { 51 | delete obj[keys[i]]; 52 | } 53 | } 54 | 55 | return obj; 56 | }; 57 | 58 | internals.deleteWhitespace = (obj) => { 59 | 60 | const keys = Object.keys(obj); 61 | 62 | for (let i = 0; i < keys.length; ++i) { 63 | if (internals.whiteRegex.test(obj[keys[i]])) { 64 | delete obj[keys[i]]; 65 | } 66 | } 67 | 68 | return obj; 69 | }; 70 | 71 | internals.disinfect = (inputObj, options, firstPass, secondPass) => { 72 | 73 | let cleansed = inputObj; 74 | if (cleansed && Object.keys(cleansed).length) { 75 | 76 | if (options[firstPass]) { 77 | cleansed = internals.sanitize(cleansed); 78 | } 79 | cleansed = options.genericSanitizer(cleansed); 80 | cleansed = options[secondPass](cleansed); 81 | if (options.deleteWhitespace) { 82 | cleansed = internals.deleteWhitespace(cleansed); 83 | } 84 | if (options.deleteEmpty) { 85 | cleansed = internals.deleteEmpty(cleansed); 86 | } 87 | } 88 | 89 | return cleansed; 90 | }; 91 | 92 | internals.schema = Joi.object().keys({ 93 | deleteEmpty: Joi.boolean().optional(), 94 | deleteWhitespace: Joi.boolean().optional(), 95 | disinfectQuery: Joi.boolean().optional(), 96 | disinfectParams: Joi.boolean().optional(), 97 | disinfectPayload: Joi.boolean().optional(), 98 | genericSanitizer: Joi.func().optional(), 99 | querySanitizer: Joi.func().optional(), 100 | paramsSanitizer: Joi.func().optional(), 101 | payloadSanitizer: Joi.func().optional() 102 | }); 103 | 104 | internals.defaults = { 105 | deleteEmpty: false, 106 | deleteWhitespace: false, 107 | disinfectQuery: false, 108 | disinfectParams: false, 109 | disinfectPayload: false, 110 | genericSanitizer: internals.noop, 111 | querySanitizer: internals.noop, 112 | paramsSanitizer: internals.noop, 113 | payloadSanitizer: internals.noop 114 | }; 115 | 116 | 117 | exports.plugin = { 118 | register: (server, options) => { 119 | 120 | const validateOptions = internals.schema.validate(options); 121 | if (validateOptions.error) { 122 | throw validateOptions.error; 123 | } 124 | 125 | const serverSettings = Hoek.applyToDefaults(internals.defaults, options); 126 | 127 | server.ext('onPostAuth', (request, reply) => { 128 | 129 | if (request.route.settings.plugins.disinfect === false) { 130 | return reply.continue; 131 | } 132 | 133 | if (request.payload || Object.keys(request.params).length || Object.keys(request.query).length) { 134 | 135 | request.route.settings.plugins._disinfect = Hoek.applyToDefaults(serverSettings, request.route.settings.plugins.disinfect || {}); 136 | 137 | request.query = internals.disinfect(request.query, request.route.settings.plugins._disinfect, 'disinfectQuery', 'querySanitizer'); 138 | request.params = internals.disinfect(request.params, request.route.settings.plugins._disinfect, 'disinfectParams', 'paramsSanitizer'); 139 | request.payload = internals.disinfect(request.payload, request.route.settings.plugins._disinfect, 'disinfectPayload', 'payloadSanitizer'); 140 | } 141 | 142 | return reply.continue; 143 | }); 144 | }, 145 | pkg: require('../package.json') 146 | }; 147 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Hapi = require('@hapi/hapi'); 4 | const Code = require('@hapi/code'); 5 | const Lab = require('@hapi/lab'); 6 | const Plugin = require('../'); 7 | 8 | const expect = Code.expect; 9 | const lab = exports.lab = Lab.script(); 10 | const beforeEach = lab.beforeEach; 11 | const describe = lab.describe; 12 | const it = lab.it; 13 | 14 | describe('registration and functionality', () => { 15 | 16 | let server; 17 | 18 | beforeEach(async () => { 19 | 20 | server = new Hapi.Server({ port: 80 }); 21 | 22 | await server.route([{ 23 | method: 'get', 24 | path: '/queryTest', 25 | handler: (request) => { 26 | return request.query; 27 | } 28 | }, { 29 | method: 'get', 30 | path: '/paramsTest/{a}/{b?}', 31 | handler: (request) => { 32 | return request.params; 33 | } 34 | }, { 35 | method: 'post', 36 | path: '/payloadTest', 37 | handler: (request) => { 38 | return request.payload; 39 | } 40 | }]); 41 | }); 42 | 43 | const register = async (options) => { 44 | return server.register({ plugin: Plugin, options }); 45 | }; 46 | 47 | it('registers without options', async () => { 48 | 49 | let err; 50 | 51 | try { 52 | await register({}); 53 | 54 | await server.route([{ 55 | method: 'get', 56 | path: '/', 57 | handler: () => { 58 | return 'Hello'; 59 | } 60 | }]); 61 | 62 | await Promise.all([ 63 | server.inject({ 64 | method: 'get', 65 | url: '/' 66 | }).then((res) => { 67 | 68 | expect(res.statusCode).to.be.equal(200); 69 | expect(res.result).to.equal('Hello'); 70 | }) 71 | ]); 72 | } 73 | 74 | catch (error) { 75 | err = error; 76 | } 77 | 78 | expect(err).to.not.exist(); 79 | }); 80 | 81 | it('registers with error if invalid options', async () => { 82 | 83 | let err; 84 | 85 | try { 86 | await register({ 87 | some: 'value' 88 | }); 89 | } 90 | 91 | catch (error) { 92 | err = error; 93 | } 94 | 95 | expect(err).to.exist(); 96 | }); 97 | 98 | it('can be disabled per route', async () => { 99 | 100 | let err; 101 | 102 | try { 103 | await register({ 104 | deleteEmpty: true 105 | }); 106 | 107 | await server.route([{ 108 | method: 'get', 109 | path: '/disabled', 110 | handler: (request) => { 111 | return request.query; 112 | }, 113 | options: { plugins: { disinfect: false } } 114 | }, { 115 | method: 'post', 116 | path: '/disabled', 117 | handler: (request) => { 118 | return request.payload; 119 | }, 120 | options: { plugins: { disinfect: false } } 121 | }]); 122 | 123 | await Promise.all([ 124 | server.inject({ 125 | method: 'get', 126 | url: '/disabled?a=&b=&c=c' 127 | }).then((res) => { 128 | expect(res.statusCode).to.be.equal(200); 129 | expect(res.result).to.equal({ a: '', b: '', c: 'c' }); 130 | }), 131 | 132 | server.inject({ 133 | method: 'post', 134 | url: '/disabled', 135 | payload: { a: '', b: '', c: 'c', d: null } 136 | }).then((res) => { 137 | 138 | expect(res.statusCode).to.be.equal(200); 139 | expect(res.result).to.equal({ a: '', b: '', c: 'c', d: null }); 140 | }) 141 | ]); 142 | } 143 | 144 | catch (error) { 145 | err = error; 146 | } 147 | 148 | expect(err).to.not.exist(); 149 | }); 150 | 151 | it('removes empties', async () => { 152 | 153 | let err; 154 | 155 | try { 156 | await register({ 157 | deleteEmpty: true 158 | }); 159 | 160 | await Promise.all([ 161 | server.inject({ 162 | method: 'get', 163 | url: '/queryTest?a=&b=&c=c' 164 | }).then((res) => { 165 | 166 | expect(res.statusCode).to.be.equal(200); 167 | expect(res.result).to.equal({ c: 'c' }); 168 | }), 169 | 170 | server.inject({ 171 | method: 'post', 172 | url: '/payloadTest', 173 | payload: { a: '', b: '', c: 'c' } 174 | }).then((res) => { 175 | 176 | expect(res.statusCode).to.be.equal(200); 177 | expect(res.result).to.equal({ c: 'c' }); 178 | }) 179 | ]); 180 | } 181 | 182 | catch (error) { 183 | err = error; 184 | } 185 | 186 | expect(err).to.not.exist(); 187 | }); 188 | 189 | it('removes empties on a per route options', async () => { 190 | 191 | let err; 192 | 193 | try { 194 | await register({ 195 | deleteEmpty: true 196 | }); 197 | 198 | await server.route([{ 199 | method: 'get', 200 | path: '/queryTestPerRoute', 201 | handler: (request) => { 202 | return request.query; 203 | }, 204 | options: { 205 | plugins: { 206 | disinfect: { 207 | deleteEmpty: true 208 | } 209 | } 210 | } 211 | }, { 212 | method: 'post', 213 | path: '/payloadTestPerRoute', 214 | handler: (request) => { 215 | return request.payload; 216 | }, 217 | options: { 218 | plugins: { 219 | disinfect: { 220 | deleteEmpty: true 221 | } 222 | } 223 | } 224 | }]); 225 | 226 | await Promise.all([ 227 | server.inject({ 228 | method: 'get', 229 | url: '/queryTestPerRoute?a=&b=&c=c' 230 | }).then((res) => { 231 | 232 | expect(res.statusCode).to.be.equal(200); 233 | expect(res.result).to.equal({ c: 'c' }); 234 | }), 235 | 236 | server.inject({ 237 | method: 'post', 238 | url: '/payloadTestPerRoute', 239 | payload: { a: '', b: '', c: 'c', d: null } 240 | }).then((res) => { 241 | 242 | expect(res.statusCode).to.be.equal(200); 243 | expect(res.result).to.equal({ c: 'c' }); 244 | }) 245 | ]); 246 | } 247 | 248 | catch (error) { 249 | err = error; 250 | } 251 | 252 | expect(err).to.not.exist(); 253 | }); 254 | 255 | it('removes whitespaces', async () => { 256 | 257 | let err; 258 | 259 | try { 260 | await register({ 261 | deleteWhitespace: true 262 | }); 263 | 264 | await Promise.all([ 265 | server.inject({ 266 | method: 'get', 267 | url: '/queryTest?a=%20%20%20&b=%20%20%20&c=c' 268 | }).then((res) => { 269 | 270 | expect(res.statusCode).to.be.equal(200); 271 | expect(res.result).to.equal({ c: 'c' }); 272 | }), 273 | 274 | server.inject({ 275 | method: 'get', 276 | url: '/paramsTest/' + encodeURIComponent(' ') + '/5' 277 | }).then((res) => { 278 | 279 | expect(res.statusCode).to.be.equal(200); 280 | expect(res.result).to.equal({ b: '5' }); 281 | }), 282 | 283 | server.inject({ 284 | method: 'post', 285 | url: '/payloadTest', 286 | payload: { a: ' ', b: ' ', c: 'c' } 287 | }).then((res) => { 288 | 289 | expect(res.statusCode).to.be.equal(200); 290 | expect(res.result).to.equal({ c: 'c' }); 291 | }) 292 | ]); 293 | } 294 | 295 | catch (error) { 296 | err = error; 297 | } 298 | 299 | expect(err).to.not.exist(); 300 | }); 301 | 302 | it('removes whitespaces on a per route options', async () => { 303 | 304 | let err; 305 | 306 | try { 307 | await register({ 308 | deleteEmpty: true 309 | }); 310 | await server.route([{ 311 | method: 'get', 312 | path: '/queryTestPerRoute', 313 | handler: (request) => { 314 | return request.query; 315 | }, 316 | options: { 317 | plugins: { 318 | disinfect: { 319 | deleteWhitespace: true 320 | } 321 | } 322 | } 323 | }, { 324 | method: 'get', 325 | path: '/paramsTestPerRoute/{a}/{b?}', 326 | handler: (request) => { 327 | return request.params; 328 | }, 329 | options: { 330 | plugins: { 331 | disinfect: { 332 | deleteWhitespace: true 333 | } 334 | } 335 | } 336 | }, { 337 | method: 'post', 338 | path: '/payloadTestPerRoute', 339 | handler: (request) => { 340 | return request.payload; 341 | }, 342 | options: { 343 | plugins: { 344 | disinfect: { 345 | deleteWhitespace: true 346 | } 347 | } 348 | } 349 | }]); 350 | 351 | await Promise.all([ 352 | server.inject({ 353 | method: 'get', 354 | url: '/queryTestPerRoute?a=%20%20%20&b=%20%20%20&c=c' 355 | }).then((res) => { 356 | 357 | expect(res.statusCode).to.be.equal(200); 358 | expect(res.result).to.equal({ c: 'c' }); 359 | }), 360 | 361 | server.inject({ 362 | method: 'get', 363 | url: '/paramsTestPerRoute/' + encodeURIComponent(' ') + '/c' 364 | }).then((res) => { 365 | 366 | expect(res.statusCode).to.be.equal(200); 367 | expect(res.result).to.equal({ b: 'c' }); 368 | }), 369 | 370 | server.inject({ 371 | method: 'post', 372 | url: '/payloadTestPerRoute', 373 | payload: { a: ' ', b: ' ', c: 'c' } 374 | }).then((res) => { 375 | 376 | expect(res.statusCode).to.be.equal(200); 377 | expect(res.result).to.equal({ c: 'c' }); 378 | }) 379 | ]); 380 | } 381 | 382 | catch (error) { 383 | err = error; 384 | } 385 | 386 | expect(err).to.not.exist(); 387 | }); 388 | 389 | it('sanitizes query', async () => { 390 | 391 | let err; 392 | 393 | try { 394 | await register({ 395 | disinfectQuery: true 396 | }); 397 | 398 | await Promise.all([ 399 | server.inject({ 400 | method: 'get', 401 | url: '/queryTest?a=' + encodeURIComponent('hello world') 402 | }).then((res) => { 403 | 404 | expect(res.statusCode).to.be.equal(200); 405 | expect(res.result).to.equal({ a: 'hello world' }); 406 | }) 407 | ]); 408 | } 409 | 410 | catch (error) { 411 | err = error; 412 | } 413 | 414 | expect(err).to.not.exist(); 415 | }); 416 | 417 | it('sanitizes query on a per route options', async () => { 418 | 419 | let err; 420 | 421 | try { 422 | await register({}); 423 | 424 | await server.route([{ 425 | method: 'get', 426 | path: '/queryTestPerRoute', 427 | handler: (request) => { 428 | return request.query; 429 | }, 430 | options: { 431 | plugins: { 432 | disinfect: { 433 | disinfectQuery: true 434 | } 435 | } 436 | } 437 | }]); 438 | 439 | await Promise.all([ 440 | server.inject({ 441 | method: 'get', 442 | url: '/queryTestPerRoute?a=' + encodeURIComponent('hello world') 443 | }).then((res) => { 444 | 445 | expect(res.statusCode).to.be.equal(200); 446 | expect(res.result).to.equal({ a: 'hello world' }); 447 | 448 | }) 449 | ]); 450 | } 451 | 452 | catch (error) { 453 | err = error; 454 | } 455 | 456 | expect(err).to.not.exist(); 457 | }); 458 | 459 | it('sanitizes params', async () => { 460 | 461 | let err; 462 | 463 | try { 464 | await register({ 465 | disinfectParams: true 466 | }); 467 | 468 | await Promise.all([ 469 | server.inject({ 470 | method: 'get', 471 | url: '/paramsTest/' + encodeURIComponent('hello world') 472 | }).then((res) => { 473 | 474 | expect(res.statusCode).to.be.equal(200); 475 | expect(res.result).to.equal({ a: 'hello world' }); 476 | }) 477 | ]); 478 | } 479 | 480 | catch (error) { 481 | err = error; 482 | } 483 | 484 | expect(err).to.not.exist(); 485 | }); 486 | 487 | it('sanitizes params on a per route options', async () => { 488 | 489 | let err; 490 | 491 | try { 492 | await register({ 493 | deleteEmpty: true 494 | }); 495 | 496 | await server.route([{ 497 | method: 'get', 498 | path: '/paramsTestPerRoute/{a}/{b?}', 499 | handler: (request) => { 500 | return request.params; 501 | }, 502 | options: { 503 | plugins: { 504 | disinfect: { 505 | disinfectParams: true 506 | } 507 | } 508 | } 509 | }]); 510 | 511 | await Promise.all([ 512 | server.inject({ 513 | method: 'get', 514 | url: '/paramsTestPerRoute/' + encodeURIComponent('hello world') 515 | }).then((res) => { 516 | 517 | expect(res.statusCode).to.be.equal(200); 518 | expect(res.result).to.equal({ a: 'hello world' }); 519 | }) 520 | ]); 521 | } 522 | 523 | catch (error) { 524 | err = error; 525 | } 526 | 527 | expect(err).to.not.exist(); 528 | }); 529 | 530 | it('sanitizes payload', async () => { 531 | 532 | let err; 533 | 534 | try { 535 | await register({ 536 | disinfectPayload: true 537 | }); 538 | 539 | await Promise.all([ 540 | server.inject({ 541 | method: 'post', 542 | url: '/payloadTest', 543 | payload: { a: 'hello world', b: null } 544 | }).then((res) => { 545 | 546 | expect(res.statusCode).to.be.equal(200); 547 | expect(res.result).to.equal({ a: 'hello world', b: null }); 548 | }) 549 | ]); 550 | } 551 | 552 | catch (error) { 553 | err = error; 554 | } 555 | 556 | expect(err).to.not.exist(); 557 | }); 558 | 559 | it('sanitizes payload and doesnt cast arrays to strings on a per route config', async () => { 560 | 561 | let err; 562 | 563 | try { 564 | await register({}); 565 | 566 | await server.route([{ 567 | method: 'post', 568 | path: '/payloadTestPerRoute', 569 | handler: (request) => { 570 | return request.payload; 571 | }, 572 | options: { 573 | plugins: { 574 | disinfect: { 575 | disinfectPayload: true 576 | } 577 | } 578 | } 579 | }]); 580 | 581 | await Promise.all([ 582 | server.inject({ 583 | method: 'post', 584 | url: '/payloadTestPerRoute', 585 | payload: { 586 | text1: 'test a', 587 | text2: 'test b', 588 | array: [ 589 | { text3: 'test c', text4: 'test d' }, 590 | { text3: 'test e', text4: 'hello world' }, 591 | 'eddie' 592 | ], 593 | array2: [ 594 | ['a', 'b', 'c'], 595 | { a: 'eddie' } 596 | ], 597 | obj: { a: 'eddie' } 598 | } 599 | }).then((res) => { 600 | 601 | expect(res.statusCode).to.be.equal(200); 602 | expect(res.result).to.equal({ 603 | text1: 'test a', 604 | text2: 'test b', 605 | array: [ 606 | { text3: 'test c', text4: 'test d' }, 607 | { text3: 'test e', text4: 'hello world' }, 608 | 'eddie' 609 | ], 610 | array2: [ 611 | ['a', 'b', 'c'], 612 | { a: 'eddie' } 613 | ], 614 | obj: { a: 'eddie' } 615 | }); 616 | }) 617 | ]); 618 | } 619 | 620 | catch (error) { 621 | err = error; 622 | } 623 | 624 | expect(err).to.not.exist(); 625 | 626 | }); 627 | 628 | it('sanitizes payload and handle when payload is json with content-type other than application/json', async () => { 629 | 630 | let err; 631 | 632 | try { 633 | await register({}); 634 | 635 | await server.route([{ 636 | method: 'post', 637 | path: '/payloadTestWithWrongHeader', 638 | handler: (request) => { 639 | return request.payload; 640 | }, 641 | options: { 642 | plugins: { 643 | disinfect: { 644 | disinfectPayload: true 645 | } 646 | } 647 | } 648 | }]); 649 | 650 | await Promise.all([ 651 | server.inject({ 652 | method: 'post', 653 | url: '/payloadTestWithWrongHeader', 654 | headers: { 'content-type': 'text/javascript' }, 655 | payload: { text1: 'test a', text2: 'test b' } 656 | }).then((res) => { 657 | expect(res.statusCode).to.be.equal(200); 658 | expect(res.result).to.equal('{"text1":"test a","text2":"test b"}'); 659 | }) 660 | ]); 661 | } 662 | 663 | catch (error) { 664 | err = error; 665 | } 666 | 667 | expect(err).to.not.exist(); 668 | 669 | }); 670 | 671 | it('sanitizes payload on a per route options', async () => { 672 | 673 | let err; 674 | 675 | try { 676 | await register({}); 677 | 678 | await server.route([{ 679 | method: 'post', 680 | path: '/payloadTestPerRoute', 681 | handler: (request) => { 682 | return request.payload; 683 | }, 684 | options: { 685 | plugins: { 686 | disinfect: { 687 | disinfectPayload: true 688 | } 689 | } 690 | } 691 | }]); 692 | 693 | await Promise.all([ 694 | server.inject({ 695 | method: 'post', 696 | url: '/payloadTestPerRoute', 697 | payload: { a: 'hello world' } 698 | }).then((res) => { 699 | 700 | expect(res.statusCode).to.be.equal(200); 701 | expect(res.result).to.equal({ a: 'hello world' }); 702 | }) 703 | ]); 704 | } 705 | 706 | catch (error) { 707 | err = error; 708 | } 709 | 710 | expect(err).to.not.exist(); 711 | }); 712 | 713 | it('accepts custom generic sanitizer', async () => { 714 | 715 | let err; 716 | 717 | try { 718 | await register({ 719 | genericSanitizer: (obj) => { 720 | 721 | const keys = Object.keys(obj); 722 | 723 | for (let i = 0; i < keys.length; ++i) { 724 | obj[keys[i]] = obj[keys[i]] + 'x'; 725 | } 726 | 727 | return obj; 728 | } 729 | }); 730 | 731 | await Promise.all([ 732 | server.inject({ 733 | method: 'get', 734 | url: '/queryTest?a=a&b=b&c=c' 735 | }).then((res) => { 736 | 737 | expect(res.statusCode).to.be.equal(200); 738 | expect(res.result).to.equal({ a: 'ax', b: 'bx', c: 'cx' }); 739 | }), 740 | 741 | server.inject({ 742 | method: 'get', 743 | url: '/paramsTest/a/b' 744 | }).then((res) => { 745 | 746 | expect(res.statusCode).to.be.equal(200); 747 | expect(res.result).to.equal({ a: 'ax', b: 'bx' }); 748 | }), 749 | 750 | server.inject({ 751 | method: 'post', 752 | url: '/payloadTest', 753 | payload: { a: 'a', b: 'b', c: 'c' } 754 | }).then((res) => { 755 | 756 | expect(res.statusCode).to.be.equal(200); 757 | expect(res.result).to.equal({ a: 'ax', b: 'bx', c: 'cx' }); 758 | }) 759 | ]); 760 | } 761 | 762 | catch (error) { 763 | err = error; 764 | } 765 | 766 | expect(err).to.not.exist(); 767 | }); 768 | 769 | it('accepts generic sanitizer on a per route options', async () => { 770 | 771 | let err; 772 | 773 | try { 774 | await register({}); 775 | 776 | await server.route([{ 777 | method: 'get', 778 | path: '/queryTestPerRoute', 779 | handler: (request) => { 780 | return request.query; 781 | }, 782 | options: { 783 | plugins: { 784 | disinfect: { 785 | genericSanitizer: (obj) => { 786 | 787 | const keys = Object.keys(obj); 788 | 789 | for (let i = 0; i < keys.length; ++i) { 790 | obj[keys[i]] = obj[keys[i]] + '1'; 791 | } 792 | 793 | return obj; 794 | } 795 | } 796 | } 797 | } 798 | }, { 799 | method: 'get', 800 | path: '/paramsTestPerRoute/{a}/{b?}', 801 | handler: (request) => { 802 | return request.params; 803 | }, 804 | options: { 805 | plugins: { 806 | disinfect: { 807 | genericSanitizer: (obj) => { 808 | 809 | const keys = Object.keys(obj); 810 | 811 | for (let i = 0; i < keys.length; ++i) { 812 | obj[keys[i]] = obj[keys[i]] + '2'; 813 | } 814 | 815 | return obj; 816 | } 817 | } 818 | } 819 | } 820 | }, { 821 | method: 'post', 822 | path: '/payloadTestPerRoute', 823 | handler: (request) => { 824 | return request.payload; 825 | }, 826 | options: { 827 | plugins: { 828 | disinfect: { 829 | genericSanitizer: (obj) => { 830 | 831 | const keys = Object.keys(obj); 832 | 833 | for (let i = 0; i < keys.length; ++i) { 834 | obj[keys[i]] = obj[keys[i]] + '3'; 835 | } 836 | 837 | return obj; 838 | } 839 | } 840 | } 841 | } 842 | }]); 843 | 844 | await Promise.all([ 845 | server.inject({ 846 | method: 'get', 847 | url: '/queryTestPerRoute?a=a&b=b&c=c' 848 | }).then((res) => { 849 | 850 | expect(res.statusCode).to.be.equal(200); 851 | expect(res.result).to.equal({ a: 'a1', b: 'b1', c: 'c1' }); 852 | }), 853 | 854 | server.inject({ 855 | method: 'get', 856 | url: '/paramsTestPerRoute/a/b' 857 | }).then((res) => { 858 | 859 | expect(res.statusCode).to.be.equal(200); 860 | expect(res.result).to.equal({ a: 'a2', b: 'b2' }); 861 | }), 862 | 863 | server.inject({ 864 | method: 'post', 865 | url: '/payloadTestPerRoute', 866 | payload: { a: 'a', b: 'b', c: 'c' } 867 | }).then((res) => { 868 | 869 | expect(res.statusCode).to.be.equal(200); 870 | expect(res.result).to.equal({ a: 'a3', b: 'b3', c: 'c3' }); 871 | }) 872 | ]); 873 | } 874 | 875 | catch (error) { 876 | err = error; 877 | } 878 | 879 | expect(err).to.not.exist(); 880 | }); 881 | 882 | it('accepts query sanitizer', async () => { 883 | 884 | let err; 885 | 886 | try { 887 | await register({ 888 | querySanitizer: (obj) => { 889 | 890 | const keys = Object.keys(obj); 891 | 892 | for (let i = 0; i < keys.length; ++i) { 893 | obj[keys[i]] = obj[keys[i]] + 'q'; 894 | } 895 | 896 | return obj; 897 | } 898 | }); 899 | 900 | await Promise.all([ 901 | server.inject({ 902 | method: 'get', 903 | url: '/queryTest?a=a&b=b&c=c' 904 | }).then((res) => { 905 | 906 | expect(res.statusCode).to.be.equal(200); 907 | expect(res.result).to.equal({ a: 'aq', b: 'bq', c: 'cq' }); 908 | }) 909 | ]); 910 | } 911 | 912 | catch (error) { 913 | err = error; 914 | } 915 | 916 | expect(err).to.not.exist(); 917 | }); 918 | 919 | it('accepts query sanitizer on a per route options', async () => { 920 | 921 | let err; 922 | 923 | try { 924 | await register({}); 925 | 926 | await server.route([{ 927 | method: 'get', 928 | path: '/queryTestPerRoute', 929 | handler: (request) => { 930 | return request.query; 931 | }, 932 | options: { 933 | plugins: { 934 | disinfect: { 935 | querySanitizer: (obj) => { 936 | 937 | const keys = Object.keys(obj); 938 | 939 | for (let i = 0; i < keys.length; ++i) { 940 | obj[keys[i]] = obj[keys[i]] + 'q1'; 941 | } 942 | 943 | return obj; 944 | } 945 | } 946 | } 947 | } 948 | }]); 949 | 950 | await Promise.all([ 951 | server.inject({ 952 | method: 'get', 953 | url: '/queryTestPerRoute?a=a&b=b&c=c' 954 | }).then((res) => { 955 | 956 | expect(res.statusCode).to.be.equal(200); 957 | expect(res.result).to.equal({ a: 'aq1', b: 'bq1', c: 'cq1' }); 958 | }) 959 | ]); 960 | } 961 | 962 | catch (error) { 963 | err = error; 964 | } 965 | 966 | expect(err).to.not.exist(); 967 | }); 968 | 969 | it('accepts params sanitizer', async () => { 970 | 971 | let err; 972 | 973 | try { 974 | await register({ 975 | paramsSanitizer: (obj) => { 976 | 977 | const keys = Object.keys(obj); 978 | 979 | for (let i = 0; i < keys.length; ++i) { 980 | obj[keys[i]] = obj[keys[i]] + 'm'; 981 | } 982 | 983 | return obj; 984 | } 985 | }); 986 | 987 | await Promise.all([ 988 | server.inject({ 989 | method: 'get', 990 | url: '/paramsTest/a/b' 991 | }).then((res) => { 992 | 993 | expect(res.statusCode).to.be.equal(200); 994 | expect(res.result).to.equal({ a: 'am', b: 'bm' }); 995 | }) 996 | ]); 997 | } 998 | 999 | catch (error) { 1000 | err = error; 1001 | } 1002 | 1003 | expect(err).to.not.exist(); 1004 | }); 1005 | 1006 | it('accepts params sanitizer on a per route options', async () => { 1007 | 1008 | let err; 1009 | 1010 | try { 1011 | await register({ 1012 | paramsSanitizer: (obj) => { 1013 | 1014 | return obj; 1015 | } 1016 | }); 1017 | 1018 | await server.route([{ 1019 | method: 'get', 1020 | path: '/paramsTestPerRoute/{a}/{b?}', 1021 | handler: (request) => { 1022 | return request.params; 1023 | }, 1024 | options: { 1025 | plugins: { 1026 | disinfect: { 1027 | paramsSanitizer: (obj) => { 1028 | 1029 | const keys = Object.keys(obj); 1030 | 1031 | for (let i = 0; i < keys.length; ++i) { 1032 | obj[keys[i]] = obj[keys[i]] + 'm1'; 1033 | } 1034 | 1035 | return obj; 1036 | } 1037 | } 1038 | } 1039 | } 1040 | }]); 1041 | 1042 | await Promise.all([ 1043 | server.inject({ 1044 | method: 'get', 1045 | url: '/paramsTestPerRoute/a/b' 1046 | }).then((res) => { 1047 | 1048 | expect(res.statusCode).to.be.equal(200); 1049 | expect(res.result).to.equal({ a: 'am1', b: 'bm1' }); 1050 | }) 1051 | ]); 1052 | } 1053 | 1054 | catch (error) { 1055 | err = error; 1056 | } 1057 | 1058 | expect(err).to.not.exist(); 1059 | }); 1060 | 1061 | it('accepts payload sanitizer', async () => { 1062 | 1063 | let err; 1064 | 1065 | try { 1066 | await register({ 1067 | payloadSanitizer: (obj) => { 1068 | 1069 | const keys = Object.keys(obj); 1070 | 1071 | for (let i = 0; i < keys.length; ++i) { 1072 | obj[keys[i]] = obj[keys[i]] + 'p'; 1073 | } 1074 | 1075 | return obj; 1076 | } 1077 | }); 1078 | 1079 | await Promise.all([ 1080 | server.inject({ 1081 | method: 'post', 1082 | url: '/payloadTest', 1083 | payload: { a: 'a', b: 'b', c: 'c' } 1084 | }).then((res) => { 1085 | 1086 | expect(res.statusCode).to.be.equal(200); 1087 | expect(res.result).to.equal({ a: 'ap', b: 'bp', c: 'cp' }); 1088 | }) 1089 | ]); 1090 | } 1091 | 1092 | catch (error) { 1093 | err = error; 1094 | } 1095 | 1096 | expect(err).to.not.exist(); 1097 | }); 1098 | 1099 | it('accepts payload sanitizer a per route options', async () => { 1100 | 1101 | let err; 1102 | 1103 | try { 1104 | await register({}); 1105 | 1106 | await server.route([{ 1107 | method: 'post', 1108 | path: '/payloadTestPerRoute', 1109 | handler: (request) => { 1110 | return request.payload; 1111 | }, 1112 | options: { 1113 | plugins: { 1114 | disinfect: { 1115 | payloadSanitizer: (obj) => { 1116 | 1117 | const keys = Object.keys(obj); 1118 | 1119 | for (let i = 0; i < keys.length; ++i) { 1120 | obj[keys[i]] = obj[keys[i]] + 'p1'; 1121 | } 1122 | 1123 | return obj; 1124 | } 1125 | } 1126 | } 1127 | } 1128 | }]); 1129 | 1130 | await Promise.all([ 1131 | server.inject({ 1132 | method: 'post', 1133 | url: '/payloadTestPerRoute', 1134 | payload: { a: 'a', b: 'b', c: 'c' } 1135 | }).then((res) => { 1136 | 1137 | expect(res.statusCode).to.be.equal(200); 1138 | expect(res.result).to.equal({ a: 'ap1', b: 'bp1', c: 'cp1' }); 1139 | }) 1140 | ]); 1141 | } 1142 | 1143 | catch (error) { 1144 | err = error; 1145 | } 1146 | 1147 | expect(err).to.not.exist(); 1148 | }); 1149 | }); 1150 | --------------------------------------------------------------------------------