Skip to content

Commit c6a6daa

Browse files
authored
Merge branch 'master' into dependabot/npm_and_yarn/mocha-8.0.1
2 parents 854dbef + d6aabb8 commit c6a6daa

20 files changed

+99
-100
lines changed

.travis.yml

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ before_script:
33
- git submodule update --init
44
- npm install -g codeclimate-test-reporter
55
node_js:
6-
- 8
76
- 10
87
- 12
98
- 14

README.md

+16-1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-04.json'));
8282
- [Getting started](#getting-started)
8383
- [Frequently Asked Questions](https://github.com/ajv-validator/ajv/blob/master/FAQ.md)
8484
- [Using in browser](#using-in-browser)
85+
- [Ajv and Content Security Policies (CSP)](#ajv-and-content-security-policies-csp)
8586
- [Command line interface](#command-line-interface)
8687
- Validation
8788
- [Keywords](#validation-keywords)
@@ -238,6 +239,16 @@ Ajv is tested with these browsers:
238239
__Please note__: some frameworks, e.g. Dojo, may redefine global require in such way that is not compatible with CommonJS module format. In such case Ajv bundle has to be loaded before the framework and then you can use global Ajv (see issue [#234](https://github.com/ajv-validator/ajv/issues/234)).
239240

240241

242+
### Ajv and Content Security Policies (CSP)
243+
244+
If you're using Ajv to compile a schema (the typical use) in a browser document that is loaded with a Content Security Policy (CSP), that policy will require a `script-src` directive that includes the value `'unsafe-eval'`.
245+
:warning: NOTE, however, that `unsafe-eval` is NOT recommended in a secure CSP[[1]](https://developer.chrome.com/extensions/contentSecurityPolicy#relaxing-eval), as it has the potential to open the document to cross-site scripting (XSS) attacks.
246+
247+
In order to make use of Ajv without easing your CSP, you can [pre-compile a schema using the CLI](https://github.com/ajv-validator/ajv-cli#compile-schemas). This will transpile the schema JSON into a JavaScript file that exports a `validate` function that works simlarly to a schema compiled at runtime.
248+
249+
Note that pre-compilation of schemas is performed using [ajv-pack](https://github.com/ajv-validator/ajv-pack) and there are [some limitations to the schema features it can compile](https://github.com/ajv-validator/ajv-pack#limitations). A successfully pre-compiled schema is equivalent to the same schema compiled at runtime.
250+
251+
241252
## Command line interface
242253

243254
CLI is available as a separate npm package [ajv-cli](https://github.com/ajv-validator/ajv-cli). It supports:
@@ -321,7 +332,7 @@ You can add additional formats and replace any of the formats above using [addFo
321332

322333
The option `unknownFormats` allows changing the default behaviour when an unknown format is encountered. In this case Ajv can either fail schema compilation (default) or ignore it (default in versions before 5.0.0). You also can whitelist specific format(s) to be ignored. See [Options](#options) for details.
323334

324-
You can find regular expressions used for format validation and the sources that were used in [formats.js](https://github.com/ajv-validatorv/ajv/blob/master/lib/compile/formats.js).
335+
You can find regular expressions used for format validation and the sources that were used in [formats.js](https://github.com/ajv-validator/ajv/blob/master/lib/compile/formats.js).
325336

326337

327338
## <a name="ref"></a>Combining schemas with $ref
@@ -722,6 +733,10 @@ isSchemaSecure(schema2); // true
722733
__Please note__: following all these recommendation is not a guarantee that validation of untrusted data is safe - it can still lead to some undesirable results.
723734

724735

736+
##### Content Security Policies (CSP)
737+
See [Ajv and Content Security Policies (CSP)](#ajv-and-content-security-policies-csp)
738+
739+
725740
## ReDoS attack
726741

727742
Certain regular expressions can lead to the exponential evaluation time even with relatively short strings.

lib/compile/util.js

+1-39
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ module.exports = {
1313
ucs2length: require('./ucs2length'),
1414
varOccurences: varOccurences,
1515
varReplace: varReplace,
16-
cleanUpCode: cleanUpCode,
17-
finalCleanUpCode: finalCleanUpCode,
1816
schemaHasRules: schemaHasRules,
1917
schemaHasRulesExcept: schemaHasRulesExcept,
2018
schemaUnknownRules: schemaUnknownRules,
@@ -139,42 +137,6 @@ function varReplace(str, dataVar, expr) {
139137
}
140138

141139

142-
var EMPTY_ELSE = /else\s*{\s*}/g
143-
, EMPTY_IF_NO_ELSE = /if\s*\([^)]+\)\s*\{\s*\}(?!\s*else)/g
144-
, EMPTY_IF_WITH_ELSE = /if\s*\(([^)]+)\)\s*\{\s*\}\s*else(?!\s*if)/g;
145-
function cleanUpCode(out) {
146-
return out.replace(EMPTY_ELSE, '')
147-
.replace(EMPTY_IF_NO_ELSE, '')
148-
.replace(EMPTY_IF_WITH_ELSE, 'if (!($1))');
149-
}
150-
151-
152-
var ERRORS_REGEXP = /[^v.]errors/g
153-
, REMOVE_ERRORS = /var errors = 0;|var vErrors = null;|validate.errors = vErrors;/g
154-
, REMOVE_ERRORS_ASYNC = /var errors = 0;|var vErrors = null;/g
155-
, RETURN_VALID = 'return errors === 0;'
156-
, RETURN_TRUE = 'validate.errors = null; return true;'
157-
, RETURN_ASYNC = /if \(errors === 0\) return data;\s*else throw new ValidationError\(vErrors\);/
158-
, RETURN_DATA_ASYNC = 'return data;'
159-
, ROOTDATA_REGEXP = /[^A-Za-z_$]rootData[^A-Za-z0-9_$]/g
160-
, REMOVE_ROOTDATA = /if \(rootData === undefined\) rootData = data;/;
161-
162-
function finalCleanUpCode(out, async) {
163-
var matches = out.match(ERRORS_REGEXP);
164-
if (matches && matches.length == 2) {
165-
out = async
166-
? out.replace(REMOVE_ERRORS_ASYNC, '')
167-
.replace(RETURN_ASYNC, RETURN_DATA_ASYNC)
168-
: out.replace(REMOVE_ERRORS, '')
169-
.replace(RETURN_VALID, RETURN_TRUE);
170-
}
171-
172-
matches = out.match(ROOTDATA_REGEXP);
173-
if (!matches || matches.length !== 3) return out;
174-
return out.replace(REMOVE_ROOTDATA, '');
175-
}
176-
177-
178140
function schemaHasRules(schema, rules) {
179141
if (typeof schema == 'boolean') return !schema;
180142
for (var key in schema) if (rules[key]) return true;
@@ -253,7 +215,7 @@ function getData($data, lvl, paths) {
253215

254216
function joinPaths (a, b) {
255217
if (a == '""') return b;
256-
return (a + ' + ' + b).replace(/' \+ '/g, '');
218+
return (a + ' + ' + b).replace(/([^\\])' \+ '/g, '$1');
257219
}
258220

259221

lib/dot/_limit.jst

+9
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@
1717
, $op = $isMax ? '<' : '>'
1818
, $notOp = $isMax ? '>' : '<'
1919
, $errorKeyword = undefined;
20+
21+
if (!($isData || typeof $schema == 'number' || $schema === undefined)) {
22+
throw new Error($keyword + ' must be number');
23+
}
24+
if (!($isDataExcl || $schemaExcl === undefined
25+
|| typeof $schemaExcl == 'number'
26+
|| typeof $schemaExcl == 'boolean')) {
27+
throw new Error($exclusiveKeyword + ' must be number or boolean');
28+
}
2029
}}
2130

2231
{{? $isDataExcl }}

lib/dot/_limitItems.jst

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
{{# def.setupKeyword }}
44
{{# def.$data }}
55

6+
{{# def.numberKeyword }}
7+
68
{{ var $op = $keyword == 'maxItems' ? '>' : '<'; }}
79
if ({{# def.$dataNotType:'number' }} {{=$data}}.length {{=$op}} {{=$schemaValue}}) {
810
{{ var $errorKeyword = $keyword; }}

lib/dot/_limitLength.jst

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
{{# def.setupKeyword }}
44
{{# def.$data }}
55

6+
{{# def.numberKeyword }}
7+
68
{{ var $op = $keyword == 'maxLength' ? '>' : '<'; }}
79
if ({{# def.$dataNotType:'number' }} {{# def.strLength }} {{=$op}} {{=$schemaValue}}) {
810
{{ var $errorKeyword = $keyword; }}

lib/dot/_limitProperties.jst

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
{{# def.setupKeyword }}
44
{{# def.$data }}
55

6+
{{# def.numberKeyword }}
7+
68
{{ var $op = $keyword == 'maxProperties' ? '>' : '<'; }}
79
if ({{# def.$dataNotType:'number' }} Object.keys({{=$data}}).length {{=$op}} {{=$schemaValue}}) {
810
{{ var $errorKeyword = $keyword; }}

lib/dot/allOf.jst

-2
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,3 @@
3030
{{= $closingBraces.slice(0,-1) }}
3131
{{?}}
3232
{{?}}
33-
34-
{{# def.cleanUp }}

lib/dot/anyOf.jst

-2
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@
3939
} else {
4040
{{# def.resetErrors }}
4141
{{? it.opts.allErrors }} } {{?}}
42-
43-
{{# def.cleanUp }}
4442
{{??}}
4543
{{? $breakOnError }}
4644
if (true) {

lib/dot/contains.jst

-2
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,3 @@ var {{=$valid}};
5353
{{# def.resetErrors }}
5454
{{?}}
5555
{{? it.opts.allErrors }} } {{?}}
56-
57-
{{# def.cleanUp }}

lib/dot/definitions.def

+7-6
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,6 @@
112112
#}}
113113

114114

115-
{{## def.cleanUp: {{ out = it.util.cleanUpCode(out); }} #}}
116-
117-
118-
{{## def.finalCleanUp: {{ out = it.util.finalCleanUpCode(out, $async); }} #}}
119-
120-
121115
{{## def.$data:
122116
{{
123117
var $isData = it.opts.$data && $schema && $schema.$data
@@ -144,6 +138,13 @@
144138
#}}
145139

146140

141+
{{## def.numberKeyword:
142+
{{? !($isData || typeof $schema == 'number') }}
143+
{{ throw new Error($keyword + ' must be number'); }}
144+
{{?}}
145+
#}}
146+
147+
147148
{{## def.beginDefOut:
148149
{{
149150
var $$outStack = $$outStack || [];

lib/dot/dependencies.jst

+1-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
, $ownProperties = it.opts.ownProperties;
2020

2121
for ($property in $schema) {
22+
if ($property == '__proto__') continue;
2223
var $sch = $schema[$property];
2324
var $deps = Array.isArray($sch) ? $propertyDeps : $schemaDeps;
2425
$deps[$property] = $sch;
@@ -76,5 +77,3 @@ var missing{{=$lvl}};
7677
{{= $closingBraces }}
7778
if ({{=$errs}} == errors) {
7879
{{?}}
79-
80-
{{# def.cleanUp }}

lib/dot/if.jst

-2
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,6 @@
6565
{{# def.extraError:'if' }}
6666
}
6767
{{? $breakOnError }} else { {{?}}
68-
69-
{{# def.cleanUp }}
7068
{{??}}
7169
{{? $breakOnError }}
7270
if (true) {

lib/dot/items.jst

-2
Original file line numberDiff line numberDiff line change
@@ -96,5 +96,3 @@ var {{=$valid}};
9696
{{= $closingBraces }}
9797
if ({{=$errs}} == errors) {
9898
{{?}}
99-
100-
{{# def.cleanUp }}

lib/dot/multipleOf.jst

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
{{# def.setupKeyword }}
44
{{# def.$data }}
55

6+
{{# def.numberKeyword }}
7+
68
var division{{=$lvl}};
79
if ({{?$isData}}
810
{{=$schemaValue}} !== undefined && (

lib/dot/properties.jst

+6-5
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@
2828
, $nextData = 'data' + $dataNxt
2929
, $dataProperties = 'dataProperties' + $lvl;
3030

31-
var $schemaKeys = Object.keys($schema || {})
31+
var $schemaKeys = Object.keys($schema || {}).filter(notProto)
3232
, $pProperties = it.schema.patternProperties || {}
33-
, $pPropertyKeys = Object.keys($pProperties)
33+
, $pPropertyKeys = Object.keys($pProperties).filter(notProto)
3434
, $aProperties = it.schema.additionalProperties
3535
, $someProperties = $schemaKeys.length || $pPropertyKeys.length
3636
, $noAdditional = $aProperties === false
@@ -42,8 +42,11 @@
4242
, $currentBaseId = it.baseId;
4343

4444
var $required = it.schema.required;
45-
if ($required && !(it.opts.$data && $required.$data) && $required.length < it.opts.loopRequired)
45+
if ($required && !(it.opts.$data && $required.$data) && $required.length < it.opts.loopRequired) {
4646
var $requiredHash = it.util.toHash($required);
47+
}
48+
49+
function notProto(p) { return p !== '__proto__'; }
4750
}}
4851

4952

@@ -240,5 +243,3 @@ var {{=$nextValid}} = true;
240243
{{= $closingBraces }}
241244
if ({{=$errs}} == errors) {
242245
{{?}}
243-
244-
{{# def.cleanUp }}

lib/dot/propertyNames.jst

-2
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,3 @@ var {{=$errs}} = errors;
5050
{{= $closingBraces }}
5151
if ({{=$errs}} == errors) {
5252
{{?}}
53-
54-
{{# def.cleanUp }}

lib/dot/validate.jst

-6
Original file line numberDiff line numberDiff line change
@@ -254,12 +254,6 @@
254254
var {{=$valid}} = errors === errs_{{=$lvl}};
255255
{{?}}
256256

257-
{{# def.cleanUp }}
258-
259-
{{? $top }}
260-
{{# def.finalCleanUp }}
261-
{{?}}
262-
263257
{{
264258
function $shouldUseGroup($rulesGroup) {
265259
var rules = $rulesGroup.rules;

spec/ajv.spec.js

+51
Original file line numberDiff line numberDiff line change
@@ -512,5 +512,56 @@ describe('Ajv', function () {
512512
});
513513
});
514514
});
515+
516+
describe('sub-schema validation outside of definitions during compilation', function() {
517+
it('maximum', function() {
518+
passValidationThrowCompile({
519+
$ref: '#/foo',
520+
foo: {maximum: 'bar'}
521+
});
522+
});
523+
524+
it('exclusiveMaximum', function() {
525+
passValidationThrowCompile({
526+
$ref: '#/foo',
527+
foo: {exclusiveMaximum: 'bar'}
528+
});
529+
});
530+
531+
it('maxItems', function() {
532+
passValidationThrowCompile({
533+
$ref: '#/foo',
534+
foo: {maxItems: 'bar'}
535+
});
536+
});
537+
538+
it('maxLength', function() {
539+
passValidationThrowCompile({
540+
$ref: '#/foo',
541+
foo: {maxLength: 'bar'}
542+
});
543+
});
544+
545+
it('maxProperties', function() {
546+
passValidationThrowCompile({
547+
$ref: '#/foo',
548+
foo: {maxProperties: 'bar'}
549+
});
550+
});
551+
552+
it('multipleOf', function() {
553+
passValidationThrowCompile({
554+
$ref: '#/foo',
555+
foo: {maxProperties: 'bar'}
556+
});
557+
});
558+
559+
function passValidationThrowCompile(schema) {
560+
ajv.validateSchema(schema) .should.equal(true);
561+
should.throw(function() {
562+
ajv.compile(schema);
563+
});
564+
}
565+
});
515566
});
516567
});

spec/issues/388_code_clean-up.spec.js

-28
This file was deleted.

0 commit comments

Comments
 (0)