Skip to content

Commit

Permalink
fix(lib): Escape double quotes in TFExpressions
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielMSchmidt committed Nov 1, 2021
1 parent 28c5398 commit 2d82cd6
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 4 deletions.
25 changes: 24 additions & 1 deletion packages/cdktf/lib/terraform-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { call } from "./tfExpression";
import { IResolvable } from "./tokens/resolvable";
import { TokenMap } from "./tokens/private/token-map";
import { TokenString } from "./tokens/private/encoding";
import { rawString } from ".";

// We use branding here to ensure we internally only handle validated values
// this allows us to catch usage errors before terraform does in some cases
Expand All @@ -11,6 +12,12 @@ type TFValueValidator = (value: any) => TFValue;

type ExecutableTfFunction = (...args: any[]) => IResolvable;

function hasUnescapedDoubleQuotes(str: string) {
const ret = /\\([\s\S])|(")/.test(str);
console.log("hasUnescapedDoubleQuotes", str, ret);
return ret;
}

// Validators
function anyValue(value: any): any {
return value;
Expand All @@ -22,8 +29,15 @@ function mapValue(value: any): any {

function stringValue(value: any): any {
if (typeof value !== "string" && !Tokenization.isResolvable(value)) {
throw new Error(`${value} is not a valid number nor a token`);
throw new Error(`'${value}' is not a valid string nor a token`);
}

if (typeof value === "string" && hasUnescapedDoubleQuotes(value)) {
throw new Error(
`'${value}' can not be used as value directly since it has unescaped double quotes in it. To safely use the value please use Fn.rawString on your string.`
);
}

return value;
}

Expand Down Expand Up @@ -1249,4 +1263,13 @@ export class Fn {
public static try(expression: any[]) {
return asAny(terraformFunction("try", listOf(anyValue))(...expression));
}

/**
* Use this function to wrap a string and escape it properly for the use in Terraform
* This is only needed in certain scenarios (e.g., if you have unescaped double quotes in the string)
* @param {String} str
*/
public static rawString(str: string): string {
return asString(rawString(str));
}
}
27 changes: 24 additions & 3 deletions packages/cdktf/lib/tfExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ class TFExpression extends Intrinsic implements IResolvable {
* Escape string removes characters from the string that are not allowed in Terraform or JSON
* It must only be used on non-token values
*/
private escapeString(str: string) {
return str
protected escapeString(str: string) {
return str // Escape double quotes
.replace(/\n/g, "\\n") // escape newlines
.replace(/\${/g, "$$${"); // escape ${ to $${
}
Expand All @@ -45,7 +45,9 @@ class TFExpression extends Intrinsic implements IResolvable {

// String literal
if (numberOfTokens === 0) {
return resolvedArg.startsWith('"') && resolvedArg.endsWith('"')
return resolvedArg !== `"` &&
resolvedArg.startsWith('"') &&
resolvedArg.endsWith('"')
? this.escapeString(resolvedArg)
: `"${this.escapeString(resolvedArg)}"`;
}
Expand Down Expand Up @@ -77,6 +79,25 @@ class TFExpression extends Intrinsic implements IResolvable {
}
}

// A string that represents an input value to be escaped
class RawString extends TFExpression {
constructor(private readonly str: string) {
super(str);
}

public resolve() {
return `"${this.escapeString(this.str).replace(/\"/g, '\\"')}"`; // eslint-disable-line no-useless-escape
}

public toString() {
return this.str;
}
}

export function rawString(str: string): IResolvable {
return new RawString(str);
}

class Reference extends TFExpression {
constructor(private identifier: string) {
super(identifier);
Expand Down
31 changes: 31 additions & 0 deletions packages/cdktf/test/functions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,3 +407,34 @@ test("undefined and null", () => {
}"
`);
});

test("throws error on unescaped double quote string inputs", () => {
expect(() => {
const app = Testing.app();
const stack = new TerraformStack(app, "test");
new TerraformOutput(stack, "test-output", {
value: Fn.md5(`"`),
});
Testing.synth(stack);
}).toThrowErrorMatchingInlineSnapshot(
`"'\\"' can not be used as value directly since it has unescaped double quotes in it. To safely use the value please use Fn.rawString on your string."`
);
});

test("throws no error when wrapping unescaped double quotes in Fn.rawString", () => {
const app = Testing.app();
const stack = new TerraformStack(app, "test");
new TerraformOutput(stack, "test-output", {
value: Fn.md5(Fn.rawString(`"`)),
});

expect(Testing.synth(stack)).toMatchInlineSnapshot(`
"{
\\"output\\": {
\\"test-output\\": {
\\"value\\": \\"\${md5(\\\\\\"\\\\\\\\\\\\\\"\\\\\\")}\\"
}
}
}"
`);
});
7 changes: 7 additions & 0 deletions packages/cdktf/test/tfExpression.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
call,
subOperation,
Expression,
rawString,
} from "../lib/tfExpression";
import { resolve } from "../lib/_tokens";
const resolveExpression = (expr: Expression) => resolve(null as any, expr);
Expand Down Expand Up @@ -163,3 +164,9 @@ test("functions don't escape terraform references that have been tokenized", ()
)
).toMatchInlineSnapshot(`"\${length(docker_container.foo.bar)}"`);
});

test("functions escape string markers", () => {
expect(
resolveExpression(call("length", [rawString(`"`)]))
).toMatchInlineSnapshot(`"\${length(\\"\\\\\\"\\")}"`);
});

0 comments on commit 2d82cd6

Please sign in to comment.