mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-08-26 13:24:48 +02:00
fix(template-function-json): escape control characters in json.escape (#607)
This commit is contained in:
@@ -8,7 +8,8 @@
|
|||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "yaakcli build",
|
"build": "yaakcli build",
|
||||||
"dev": "yaakcli dev"
|
"dev": "yaakcli dev",
|
||||||
|
"test": "vp test --run tests"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jsonpath-plus": "^10.3.0"
|
"jsonpath-plus": "^10.3.0"
|
||||||
|
|||||||
@@ -85,7 +85,11 @@ export const plugin: PluginDefinition = {
|
|||||||
],
|
],
|
||||||
async onRender(_ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
async onRender(_ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
||||||
const input = String(args.values.input ?? "");
|
const input = String(args.values.input ?? "");
|
||||||
return input.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
// JSON.stringify produces a spec-correct string literal: it escapes
|
||||||
|
// the backslash and quote this used to handle, and also the control
|
||||||
|
// characters it did not. Slicing off the surrounding quotes leaves
|
||||||
|
// the escaped inner text this function is meant to emit.
|
||||||
|
return JSON.stringify(input).slice(1, -1);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import type { Context } from "@yaakapp/api";
|
||||||
|
import { describe, expect, it } from "vite-plus/test";
|
||||||
|
import { plugin } from "../src";
|
||||||
|
|
||||||
|
const LF = String.fromCharCode(10);
|
||||||
|
const TAB = String.fromCharCode(9);
|
||||||
|
const CR = String.fromCharCode(13);
|
||||||
|
|
||||||
|
describe("json.escape", () => {
|
||||||
|
const escapeFunction = plugin.templateFunctions?.find((f) => f.name === "json.escape");
|
||||||
|
|
||||||
|
const escape = async (input: string) =>
|
||||||
|
await escapeFunction!.onRender({} as Context, { values: { input } } as never);
|
||||||
|
|
||||||
|
// The point of the function is that the result can be dropped between two
|
||||||
|
// quotes in a JSON document, so that is what these assert.
|
||||||
|
const embeds = (escaped: string | null) => {
|
||||||
|
JSON.parse(`{"k":"${escaped}"}`);
|
||||||
|
return JSON.parse(`{"k":"${escaped}"}`).k;
|
||||||
|
};
|
||||||
|
|
||||||
|
it("should exist", () => {
|
||||||
|
expect(escapeFunction).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("escapes a quote", async () => {
|
||||||
|
const input = `say "hi"`;
|
||||||
|
expect(embeds(await escape(input))).toBe(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("escapes a backslash", async () => {
|
||||||
|
const input = `a${String.fromCharCode(92)}b`;
|
||||||
|
expect(embeds(await escape(input))).toBe(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("escapes a newline", async () => {
|
||||||
|
const input = `line1${LF}line2`;
|
||||||
|
expect(embeds(await escape(input))).toBe(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("escapes a tab and a carriage return", async () => {
|
||||||
|
const input = `a${TAB}b${CR}c`;
|
||||||
|
expect(embeds(await escape(input))).toBe(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("round-trips a pretty-printed JSON document", async () => {
|
||||||
|
const input = JSON.stringify({ name: `he said "hi"`, items: [1, 2] }, null, 2);
|
||||||
|
expect(embeds(await escape(input))).toBe(input);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user