diff --git a/README.md b/README.md index 01e309b5..d72f088a 100644 --- a/README.md +++ b/README.md @@ -630,6 +630,25 @@ integer-like values, such as: - `'2e4'` - _note this will be converted to `2`, not `20000`_ - `1.5` - _note this will be converted to `1`_ + +#### Raw string +By default the library escape all string. With format "raw" the string isn't escaped. This has a very dangerous potential security issue. Raw format would not escape a double quote char which makes it very easy to inject something into the data. You can use it only if you 200% sure in your data. +The advantage is a massive performance improvement + +Example: +```javascript +const stringify = fastJson({ + title: 'Example Schema', + type: 'object', + properties: { + 'code': { + type: 'string', + format 'raw' + } + } +}) +``` + ##### Benchmarks For reference, here goes some benchmarks for comparison over the three diff --git a/benchmark/bench.js b/benchmark/bench.js index 12726184..72eca5b4 100644 --- a/benchmark/bench.js +++ b/benchmark/bench.js @@ -47,6 +47,14 @@ const benchmarks = [ }, input: 'hello world' }, + { + name: 'short string raw', + schema: { + type: 'string', + format: 'raw' + }, + input: 'hello world' + }, { name: 'short string with double quote', schema: { @@ -61,6 +69,14 @@ const benchmarks = [ }, input: longSimpleString }, + { + name: 'long string without double quotes raw', + schema: { + type: 'string', + format: 'raw' + }, + input: longSimpleString + }, { name: 'long string', schema: { @@ -68,6 +84,14 @@ const benchmarks = [ }, input: longString }, + { + name: 'long string raw', + schema: { + type: 'string', + format: 'raw' + }, + input: longString + }, { name: 'number', schema: { diff --git a/index.js b/index.js index 6a8a7351..6a88801b 100644 --- a/index.js +++ b/index.js @@ -284,7 +284,7 @@ function buildExtraObjectPropertiesSerializer (context, location, addComma) { code += ` if (/${propertyKey.replace(/\\*\//g, '\\/')}/.test(key)) { ${addComma} - json += serializer.asString(key) + ':' + json += serializer.asString(key,null) + ':' ${buildValue(context, propertyLocation, 'value')} continue } @@ -299,13 +299,13 @@ function buildExtraObjectPropertiesSerializer (context, location, addComma) { if (additionalPropertiesSchema === true) { code += ` ${addComma} - json += serializer.asString(key) + ':' + JSON.stringify(value) + json += serializer.asString(key,null) + ':' + JSON.stringify(value) ` } else { const propertyLocation = location.getPropertyLocation('additionalProperties') code += ` ${addComma} - json += serializer.asString(key) + ':' + json += serializer.asString(key,null) + ':' ${buildValue(context, propertyLocation, 'value')} ` } @@ -726,7 +726,8 @@ function buildSingleTypeSerializer (context, location, input) { } else if (schema.format === 'time') { return `json += serializer.asTime(${input})` } else { - return `json += serializer.asString(${input})` + const format = schema?.format ? `"${schema.format}"` : 'null' + return `json += serializer.asString(${input},${format})` } } case 'integer': diff --git a/lib/serializer.js b/lib/serializer.js index 922be357..38fddd79 100644 --- a/lib/serializer.js +++ b/lib/serializer.js @@ -98,7 +98,7 @@ module.exports = class Serializer { throw new Error(`The value "${date}" cannot be converted to a time.`) } - asString (str) { + asString (str, format) { if (typeof str !== 'string') { if (str === null) { return '""' @@ -113,7 +113,9 @@ module.exports = class Serializer { } } - if (str.length < 42) { + if (format === 'raw') { + return '"' + str + '"' + } else if (str.length < 42) { return this.asStringSmall(str) } else if (STR_ESCAPE.test(str) === false) { return '"' + str + '"' diff --git a/test/basic.test.js b/test/basic.test.js index bd47ac7d..488a2a38 100644 --- a/test/basic.test.js +++ b/test/basic.test.js @@ -18,6 +18,12 @@ function buildTest (schema, toStringify) { }) } +buildTest({ + title: 'string', + type: 'string', + format: 'raw' +}, 'hello world') + buildTest({ title: 'basic', type: 'object', diff --git a/test/string.test.js b/test/string.test.js index 5a36ec9d..ce9e08b8 100644 --- a/test/string.test.js +++ b/test/string.test.js @@ -4,6 +4,22 @@ const t = require('tap') const test = t.test const build = require('..') +test('serialize short string raw', (t) => { + t.plan(2) + + const schema = { + type: 'string', + format: 'raw' + } + + const input = 'abcd' + const stringify = build(schema) + const output = stringify(input) + + t.equal(output, '"abcd"') + t.equal(JSON.parse(output), input) +}) + test('serialize short string', (t) => { t.plan(2)