Skip to content

Commit 8ed855b

Browse files
efebarlasepoberezkin
andcommittedNov 13, 2021
option regExp to specify RegExp engine (e.g. re2) #1684
commit 1835f3517ffb750ea4c75ce3ee8d9c262374e8f4 Author: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat Nov 13 18:04:08 2021 +0000 simplify regExp option commit e7f1eb9 Merge: 98f04d3 f68ef8f Author: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat Nov 13 17:20:15 2021 +0000 Merge branch 'master' into master commit 98f04d3 Author: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat Nov 13 17:20:04 2021 +0000 Update docs/options.md commit 0ff99ed Merge: d9ea90c 8fccddb Author: Efe Barlas <43009963+efebarlas@users.noreply.github.com> Date: Wed Nov 10 00:15:33 2021 -0500 Merge branch 'master' into master commit d9ea90c Author: efebarlas <ebarlas@purdue.edu> Date: Wed Nov 10 00:09:17 2021 -0500 prettier:write to pass CI Signed-off-by: efebarlas <ebarlas@purdue.edu> commit b29cd91 Merge: f50eb43 20089ed Author: efebarlas <ebarlas@purdue.edu> Date: Tue Nov 9 21:54:45 2021 -0500 Merge branch 'master' of github.com:efebarlas/ajv Tests added for code.regExp option commit f50eb43 Author: efebarlas <ebarlas@purdue.edu> Date: Tue Nov 9 21:54:28 2021 -0500 Tests added Signed-off-by: efebarlas <ebarlas@purdue.edu> commit 20089ed Author: Efe Barlas <43009963+efebarlas@users.noreply.github.com> Date: Tue Nov 9 21:53:34 2021 -0500 Update options.md commit fd3e290 Merge: 41dd4bc 6ef0c66 Author: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sun Sep 12 19:07:28 2021 +0100 Merge branch 'master' into master commit 41dd4bc Merge: 698f411 a9f38cd Author: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sun Sep 12 11:35:20 2021 +0100 Merge branch 'master' into master commit 698f411 Author: Efe Barlas <ebarlas@purdue.edu> Date: Thu Aug 12 14:55:17 2021 -0400 dev-dependency to node-re2 added commit a0720f8 Author: Efe Barlas <ebarlas@purdue.edu> Date: Thu Aug 12 14:43:39 2021 -0400 re2 runtime lib + regExp code option added commit 1470c23 Author: Efe Barlas <ebarlas@purdue.edu> Date: Fri Jul 9 14:14:45 2021 -0400 variable name changes Signed-off-by: Efe Barlas <ebarlas@purdue.edu> commit 8f7ca34 Author: Efe Barlas <ebarlas@purdue.edu> Date: Fri Jul 9 13:22:38 2021 -0400 minor changes Signed-off-by: Efe Barlas <ebarlas@purdue.edu> commit 9791cce Author: Efe Barlas <ebarlas@purdue.edu> Date: Fri Jul 9 13:20:47 2021 -0400 remove comments Signed-off-by: Efe Barlas <ebarlas@purdue.edu> commit b07542d Author: Efe Barlas <ebarlas@purdue.edu> Date: Fri Jul 9 11:28:29 2021 -0400 added: RE2 Option with fallback Signed-off-by: Efe Barlas <ebarlas@purdue.edu> Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
1 parent f68ef8f commit 8ed855b

File tree

8 files changed

+76
-4
lines changed

8 files changed

+76
-4
lines changed
 

‎docs/options.md

+7
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ const defaultOptions = {
6969
source: false,
7070
process: undefined, // (code: string) => string
7171
optimize: true,
72+
regExp: RegExp
7273
},
7374
}
7475
```
@@ -361,6 +362,12 @@ type CodeOptions = {
361362
// Code snippet created with `_` tagged template literal that contains all format definitions,
362363
// it can be the code of actual definitions or `require` call:
363364
// _`require("./my-formats")`
365+
regExp: RegExpEngine
366+
// Pass non-standard RegExp engine to mitigate ReDoS, e.g. node-re2.
367+
// During validation of a schema, code.regExp will be
368+
// used to match strings against regular expressions.
369+
// The supplied function must support the interface:
370+
// regExp(regex, unicodeFlag).test(string) => boolean
364371
}
365372

366373
type Source = {

‎lib/core.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import type {
4848
ErrorObject,
4949
Format,
5050
AddedFormat,
51+
RegExpEngine,
5152
} from "./types"
5253
import type {JSONSchemaType} from "./types/json-schema"
5354
import type {JTDSchemaType, SomeJTDSchemaType, JTDDataType} from "./types/jtd-schema"
@@ -62,6 +63,9 @@ import {eachItem} from "./compile/util"
6263

6364
import * as $dataRefSchema from "./refs/data.json"
6465

66+
const defaultRegExp: RegExpEngine = (str, flags) => new RegExp(str, flags)
67+
defaultRegExp.code = "new RegExp"
68+
6569
const META_IGNORE_OPTIONS: (keyof Options)[] = ["removeAdditional", "useDefaults", "coerceTypes"]
6670
const EXT_SCOPE_NAMES = new Set([
6771
"validate",
@@ -141,9 +145,11 @@ export interface CodeOptions {
141145
formats?: Code // code to require (or construct) map of available formats - for standalone code
142146
source?: boolean
143147
process?: (code: string, schema?: SchemaEnv) => string
148+
regExp?: RegExpEngine
144149
}
145150

146151
interface InstanceCodeOptions extends CodeOptions {
152+
regExp: RegExpEngine
147153
optimize: number
148154
}
149155

@@ -231,13 +237,14 @@ function requiredOptions(o: Options): RequiredInstanceOptions {
231237
const s = o.strict
232238
const _optz = o.code?.optimize
233239
const optimize = _optz === true || _optz === undefined ? 1 : _optz || 0
240+
const regExp = o.code?.regExp ?? defaultRegExp
234241
return {
235242
strictSchema: o.strictSchema ?? s ?? true,
236243
strictNumbers: o.strictNumbers ?? s ?? true,
237244
strictTypes: o.strictTypes ?? s ?? "log",
238245
strictTuples: o.strictTuples ?? s ?? "log",
239246
strictRequired: o.strictRequired ?? s ?? false,
240-
code: o.code ? {...o.code, optimize} : {optimize},
247+
code: o.code ? {...o.code, optimize, regExp} : {optimize, regExp},
241248
loopRequired: o.loopRequired ?? MAX_EXPRESSION,
242249
loopEnum: o.loopEnum ?? MAX_EXPRESSION,
243250
meta: o.meta ?? true,
@@ -279,6 +286,7 @@ export default class Ajv {
279286
constructor(opts: Options = {}) {
280287
opts = this.opts = {...opts, ...requiredOptions(opts)}
281288
const {es5, lines} = this.opts.code
289+
282290
this.scope = new ValueScope({scope: {}, prefixes: EXT_SCOPE_NAMES, es5, lines})
283291
this.logger = getLogger(opts.logger)
284292
const formatOpt = opts.validateFormats

‎lib/runtime/re2.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import * as re2 from "re2"
2+
3+
type Re2 = typeof re2 & {code: string}
4+
;(re2 as Re2).code = 'require("ajv/dist/runtime/re2").default'
5+
6+
export default re2 as Re2

‎lib/types/index.ts

+9
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,12 @@ export type AddedFormat =
222222
| AsyncFormatDefinition<number>
223223

224224
export type Format = AddedFormat | string
225+
226+
export interface RegExpEngine {
227+
(pattern: string, u: string): RegExpLike
228+
code: string
229+
}
230+
231+
export interface RegExpLike {
232+
test: (s: string) => boolean
233+
}

‎lib/vocabularies/code.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type {KeywordCxt} from "../compile/validate"
44
import {CodeGen, _, and, or, not, nil, strConcat, getProperty, Code, Name} from "../compile/codegen"
55
import {alwaysValidSchema, Type} from "../compile/util"
66
import N from "../compile/names"
7-
7+
import {useFunc} from "../compile/util"
88
export function checkReportMissingProp(cxt: KeywordCxt, prop: string): void {
99
const {gen, data, it} = cxt
1010
gen.if(noPropertyInData(gen, data, prop, it.opts.ownProperties), () => {
@@ -90,12 +90,16 @@ export function callValidateCode(
9090
return context !== nil ? _`${func}.call(${context}, ${args})` : _`${func}(${args})`
9191
}
9292

93+
const newRegExp = _`new RegExp`
94+
9395
export function usePattern({gen, it: {opts}}: KeywordCxt, pattern: string): Name {
9496
const u = opts.unicodeRegExp ? "u" : ""
97+
const {regExp} = opts.code
98+
9599
return gen.scopeValue("pattern", {
96100
key: pattern,
97-
ref: new RegExp(pattern, u),
98-
code: _`new RegExp(${pattern}, ${u})`,
101+
ref: regExp(pattern, u),
102+
code: _`${regExp.code === "new RegExp" ? newRegExp : useFunc(gen, regExp)}(${pattern}, ${u})`,
99103
})
100104
}
101105

‎package.json

+1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
"node-fetch": "^3.0.0",
9898
"nyc": "^15.0.0",
9999
"prettier": "^2.3.1",
100+
"re2": "^1.16.0",
100101
"rollup": "^2.44.0",
101102
"rollup-plugin-terser": "^7.0.2",
102103
"ts-node": "^10.0.0",

‎spec/issues/1683_re2_engine.spec.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import getAjvAllInstances from "../ajv_all_instances"
2+
import {withStandalone} from "../ajv_standalone"
3+
import {_} from "../../dist/compile/codegen/code"
4+
import jsonSchemaTest = require("json-schema-test")
5+
import options from "../ajv_options"
6+
import {afterError, afterEach} from "../after_test"
7+
import chai from "../chai"
8+
import re2 from "../../dist/runtime/re2"
9+
import re2tests from "./re2"
10+
11+
const instances = getAjvAllInstances(options, {
12+
$data: true,
13+
formats: {allowedUnknown: true},
14+
strictTypes: false,
15+
strictTuples: false,
16+
})
17+
18+
instances.forEach((ajv) => {
19+
ajv.opts.code.source = true
20+
ajv.opts.code.formats = _`{allowedUnknown: true}`
21+
ajv.opts.code.regExp = re2
22+
})
23+
24+
jsonSchemaTest(withStandalone(instances), {
25+
description: "Test with re2 RegExp engine with " + instances.length + " ajv instances",
26+
suites: {"regular expressions": re2tests},
27+
assert: chai.assert,
28+
afterError,
29+
afterEach,
30+
cwd: __dirname,
31+
hideFolder: "extras/",
32+
timeout: 90000,
33+
})

‎spec/issues/re2.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export default [
2+
{name: "$data/format", test: require("../extras/$data/format.json")},
3+
{name: "$data/pattern", test: require("../extras/$data/pattern.json")},
4+
]

0 commit comments

Comments
 (0)
Please sign in to comment.