Addomg a fix for #4370 - allow queries to contain newlines, they will always be escaped.
This commit is contained in:
parent
f625828b58
commit
9298071cc2
|
@ -52,7 +52,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if query?.parameters?.length > 0}
|
{#if query?.parameters?.length > 0}
|
||||||
<div>
|
<div class="params">
|
||||||
<BindingBuilder
|
<BindingBuilder
|
||||||
bind:customParams={parameters.queryParams}
|
bind:customParams={parameters.queryParams}
|
||||||
queryBindings={query.parameters}
|
queryBindings={query.parameters}
|
||||||
|
@ -70,3 +70,11 @@
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.params {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-l);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -230,7 +230,6 @@ describe("/queries", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("variables", () => {
|
describe("variables", () => {
|
||||||
|
|
||||||
async function preview(datasource, fields) {
|
async function preview(datasource, fields) {
|
||||||
return config.previewQuery(request, config, datasource, fields)
|
return config.previewQuery(request, config, datasource, fields)
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,10 +161,16 @@ class QueryRunner {
|
||||||
const responses = await Promise.all(dynamics)
|
const responses = await Promise.all(dynamics)
|
||||||
for (let i = 0; i < foundVars.length; i++) {
|
for (let i = 0; i < foundVars.length; i++) {
|
||||||
const variable = foundVars[i]
|
const variable = foundVars[i]
|
||||||
parameters[variable.name] = processStringSync(variable.value, {
|
parameters[variable.name] = processStringSync(
|
||||||
|
variable.value,
|
||||||
|
{
|
||||||
data: responses[i].rows,
|
data: responses[i].rows,
|
||||||
info: responses[i].extra,
|
info: responses[i].extra,
|
||||||
})
|
},
|
||||||
|
{
|
||||||
|
escapeNewlines: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
// make sure its known that this uses dynamic variables in case it fails
|
// make sure its known that this uses dynamic variables in case it fails
|
||||||
this.hasDynamicVariables = true
|
this.hasDynamicVariables = true
|
||||||
}
|
}
|
||||||
|
@ -188,6 +194,7 @@ class QueryRunner {
|
||||||
enrichedQuery[key] = processStringSync(fields[key], parameters, {
|
enrichedQuery[key] = processStringSync(fields[key], parameters, {
|
||||||
noEscaping: true,
|
noEscaping: true,
|
||||||
noHelpers: true,
|
noHelpers: true,
|
||||||
|
escapeNewlines: true,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
enrichedQuery[key] = fields[key]
|
enrichedQuery[key] = fields[key]
|
||||||
|
|
|
@ -21,7 +21,7 @@ const HELPERS = [
|
||||||
// javascript helper
|
// javascript helper
|
||||||
new Helper(HelperFunctionNames.JS, processJS, false),
|
new Helper(HelperFunctionNames.JS, processJS, false),
|
||||||
// this help is applied to all statements
|
// this help is applied to all statements
|
||||||
new Helper(HelperFunctionNames.ALL, value => {
|
new Helper(HelperFunctionNames.ALL, (value, { __opts }) => {
|
||||||
if (
|
if (
|
||||||
value != null &&
|
value != null &&
|
||||||
typeof value === "object" &&
|
typeof value === "object" &&
|
||||||
|
@ -36,7 +36,11 @@ const HELPERS = [
|
||||||
if (value && value.string) {
|
if (value && value.string) {
|
||||||
value = value.string
|
value = value.string
|
||||||
}
|
}
|
||||||
let text = new SafeString(value.replace(/&/g, "&"))
|
let text = value
|
||||||
|
if (__opts && __opts.escapeNewlines) {
|
||||||
|
text = value.replace(/\n/g, "\\n")
|
||||||
|
}
|
||||||
|
text = new SafeString(text.replace(/&/g, "&"))
|
||||||
if (text == null || typeof text !== "string") {
|
if (text == null || typeof text !== "string") {
|
||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
@ -62,10 +66,14 @@ module.exports.HelperNames = () => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.registerAll = handlebars => {
|
module.exports.registerMinimum = handlebars => {
|
||||||
for (let helper of HELPERS) {
|
for (let helper of HELPERS) {
|
||||||
helper.register(handlebars)
|
helper.register(handlebars)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.registerAll = handlebars => {
|
||||||
|
module.exports.registerMinimum(handlebars)
|
||||||
// register imported helpers
|
// register imported helpers
|
||||||
externalHandlebars.registerAll(handlebars)
|
externalHandlebars.registerAll(handlebars)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const handlebars = require("handlebars")
|
const handlebars = require("handlebars")
|
||||||
const { registerAll } = require("./helpers/index")
|
const { registerAll, registerMinimum } = require("./helpers/index")
|
||||||
const processors = require("./processors")
|
const processors = require("./processors")
|
||||||
const { atob, btoa } = require("./utilities")
|
const { atob, btoa } = require("./utilities")
|
||||||
const manifest = require("../manifest.json")
|
const manifest = require("../manifest.json")
|
||||||
|
@ -8,6 +8,7 @@ const { FIND_HBS_REGEX, FIND_DOUBLE_HBS_REGEX } = require("./utilities")
|
||||||
const hbsInstance = handlebars.create()
|
const hbsInstance = handlebars.create()
|
||||||
registerAll(hbsInstance)
|
registerAll(hbsInstance)
|
||||||
const hbsInstanceNoHelpers = handlebars.create()
|
const hbsInstanceNoHelpers = handlebars.create()
|
||||||
|
registerMinimum(hbsInstanceNoHelpers)
|
||||||
const defaultOpts = { noHelpers: false, noEscaping: false }
|
const defaultOpts = { noHelpers: false, noEscaping: false }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -105,9 +106,7 @@ module.exports.processStringSync = (string, context, opts) => {
|
||||||
throw "Cannot process non-string types."
|
throw "Cannot process non-string types."
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// finalising adds a helper, can't do this with no helpers
|
string = processors.preprocess(string, opts)
|
||||||
const shouldFinalise = !opts.noHelpers
|
|
||||||
string = processors.preprocess(string, shouldFinalise)
|
|
||||||
// this does not throw an error when template can't be fulfilled, have to try correct beforehand
|
// this does not throw an error when template can't be fulfilled, have to try correct beforehand
|
||||||
const instance = opts.noHelpers ? hbsInstanceNoHelpers : hbsInstance
|
const instance = opts.noHelpers ? hbsInstanceNoHelpers : hbsInstance
|
||||||
const templateString =
|
const templateString =
|
||||||
|
@ -119,8 +118,10 @@ module.exports.processStringSync = (string, context, opts) => {
|
||||||
return processors.postprocess(
|
return processors.postprocess(
|
||||||
template({
|
template({
|
||||||
now: new Date(now).toISOString(),
|
now: new Date(now).toISOString(),
|
||||||
|
__opts: opts,
|
||||||
...context,
|
...context,
|
||||||
})
|
}),
|
||||||
|
{ escapeNewlines: opts ? opts.escapeNewlines : false }
|
||||||
)
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return input
|
return input
|
||||||
|
|
|
@ -2,7 +2,7 @@ const { FIND_HBS_REGEX } = require("../utilities")
|
||||||
const preprocessor = require("./preprocessor")
|
const preprocessor = require("./preprocessor")
|
||||||
const postprocessor = require("./postprocessor")
|
const postprocessor = require("./postprocessor")
|
||||||
|
|
||||||
function process(output, processors) {
|
function process(output, processors, opts) {
|
||||||
for (let processor of processors) {
|
for (let processor of processors) {
|
||||||
// if a literal statement has occurred stop
|
// if a literal statement has occurred stop
|
||||||
if (typeof output !== "string") {
|
if (typeof output !== "string") {
|
||||||
|
@ -15,24 +15,18 @@ function process(output, processors) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for (let match of matches) {
|
for (let match of matches) {
|
||||||
output = processor.process(output, match)
|
output = processor.process(output, match, opts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.preprocess = (string, finalise = true) => {
|
module.exports.preprocess = (string, opts) => {
|
||||||
let processors = preprocessor.processors
|
let processors = preprocessor.processors
|
||||||
// the pre-processor finalisation stops handlebars from ever throwing an error
|
return process(string, processors, opts)
|
||||||
// might want to pre-process for other benefits but still want to see errors
|
|
||||||
if (!finalise) {
|
|
||||||
processors = processors.filter(
|
|
||||||
processor => processor.name !== preprocessor.PreprocessorNames.FINALISE
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return process(string, processors)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.postprocess = string => {
|
module.exports.postprocess = string => {
|
||||||
return process(string, postprocessor.processors)
|
let processors = postprocessor.processors
|
||||||
|
return process(string, processors)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@ class Postprocessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports.PostProcessorNames = PostProcessorNames
|
||||||
|
|
||||||
module.exports.processors = [
|
module.exports.processors = [
|
||||||
new Postprocessor(PostProcessorNames.CONVERT_LITERALS, statement => {
|
new Postprocessor(PostProcessorNames.CONVERT_LITERALS, statement => {
|
||||||
if (typeof statement !== "string" || !statement.includes(LITERAL_MARKER)) {
|
if (typeof statement !== "string" || !statement.includes(LITERAL_MARKER)) {
|
||||||
|
|
|
@ -16,8 +16,8 @@ class Preprocessor {
|
||||||
this.fn = fn
|
this.fn = fn
|
||||||
}
|
}
|
||||||
|
|
||||||
process(fullString, statement) {
|
process(fullString, statement, opts) {
|
||||||
const output = this.fn(statement)
|
const output = this.fn(statement, opts)
|
||||||
const idx = fullString.indexOf(statement)
|
const idx = fullString.indexOf(statement)
|
||||||
return swapStrings(fullString, idx, statement.length, output)
|
return swapStrings(fullString, idx, statement.length, output)
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,8 @@ module.exports.processors = [
|
||||||
return statement
|
return statement
|
||||||
}),
|
}),
|
||||||
|
|
||||||
new Preprocessor(PreprocessorNames.FINALISE, statement => {
|
new Preprocessor(PreprocessorNames.FINALISE, (statement, opts) => {
|
||||||
|
const noHelpers = opts && opts.noHelpers
|
||||||
let insideStatement = statement.slice(2, statement.length - 2)
|
let insideStatement = statement.slice(2, statement.length - 2)
|
||||||
if (insideStatement.charAt(0) === " ") {
|
if (insideStatement.charAt(0) === " ") {
|
||||||
insideStatement = insideStatement.slice(1)
|
insideStatement = insideStatement.slice(1)
|
||||||
|
@ -63,7 +64,10 @@ module.exports.processors = [
|
||||||
return statement
|
return statement
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (HelperNames().some(option => option.includes(possibleHelper))) {
|
if (
|
||||||
|
!noHelpers &&
|
||||||
|
HelperNames().some(option => option.includes(possibleHelper))
|
||||||
|
) {
|
||||||
insideStatement = `(${insideStatement})`
|
insideStatement = `(${insideStatement})`
|
||||||
}
|
}
|
||||||
return `{{ all ${insideStatement} }}`
|
return `{{ all ${insideStatement} }}`
|
||||||
|
|
|
@ -59,3 +59,33 @@ describe("attempt some complex problems", () => {
|
||||||
expect(output).toBe("nulltest")
|
expect(output).toBe("nulltest")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("check behaviour with newlines", () => {
|
||||||
|
const context = {
|
||||||
|
binding: `Hello
|
||||||
|
there`
|
||||||
|
}
|
||||||
|
it("should escape new line to \\n with double brace", async () => {
|
||||||
|
const hbs = JSON.stringify({
|
||||||
|
body: "{{ binding }}"
|
||||||
|
})
|
||||||
|
const output = await processString(hbs, context, { escapeNewlines: true })
|
||||||
|
expect(JSON.parse(output).body).toBe(context.binding)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should work the same with triple brace", async () => {
|
||||||
|
const hbs = JSON.stringify({
|
||||||
|
body: "{{{ binding }}}"
|
||||||
|
})
|
||||||
|
const output = await processString(hbs, context, { escapeNewlines: true })
|
||||||
|
expect(JSON.parse(output).body).toBe(context.binding)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should still work with helpers disabled", async () => {
|
||||||
|
const hbs = JSON.stringify({
|
||||||
|
body: "{{ binding }}"
|
||||||
|
})
|
||||||
|
const output = await processString(hbs, context, { escapeNewlines: true, noHelpers: true })
|
||||||
|
expect(JSON.parse(output).body).toBe(context.binding)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
Loading…
Reference in New Issue