dotCMS / dotCMS/core

$json.generate() output breaks JSON.parse() when embedded in a JS single-quoted string literal (control character in string literal)

Open
#37,305 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

OKR : Customer Support Team : Maintenance
Dominant language
Java
Stars
970
Forks
486
Avg merge
3d 33m
Merged PRs (30d)
170

Description

Problem Statement

$json.generate() (JSONTool) produces syntactically valid, correctly-escaped JSON — but the common VTL pattern used to consume it client-side, JSON.parse('$!json.generate($specs)'), breaks whenever any value in the generated JSON contains a character the JSON serializer escapes with a backslash sequence (\n, \r, \t, \", \\), or contains a raw single-quote character. Because the JSON text is embedded directly inside a JS single-quoted string literal, the browser's own JavaScript parser decodes those same escape sequences (JS also treats \n, \r, \t, \", \\ as string-literal escapes) before JSON.parse ever runs — turning valid JSON escape sequences into raw, literal control-character bytes. JSON.parse then rejects the string with Uncaught SyntaxError: Bad control character in string literal in JSON, since the JSON grammar forbids literal (unescaped) control characters inside string literals.

This is not specific to any one field type — any content field whose value can contain a newline, tab, or embedded quote (a Textarea, WYSIWYG field, multi-line Custom Field, etc.) will trigger it. It affects any VTL author using dotCMS's own $json Velocity tool for its documented purpose (producing JSON for front-end JS consumption), because dotCMS currently provides no safe way to embed $json.generate() output inside a <script> block for JSON.parse().

Identified via a support investigation into a VTL widget rendering a list pulled via $dotcontent.pull(). See linked Freshdesk ticket for the original report.

Steps to Reproduce
  1. In a dotCMS instance, identify or create any Content Type with at least one field whose stored value can contain a newline, tab, or embedded quote. In practice this is nearly unavoidable — even a content type's own built-in field metadata (e.g. a hidden "URL Title" custom field storing a multi-line VTL template) is enough to trigger this; no special field type or customer-authored content is required.
  2. Add a VTL page/widget containing:
    #set($pulledContent = $dotcontent.pull("+contentType:<YourContentType>",10,"modDate desc"))
    var pulledContentVariable = JSON.parse('$!json.generate($pulledContent)');
    
  3. Load the page and open the browser console.

Expected: pulledContent is populated with the parsed JSON; no console error.
Actual: Uncaught SyntaxError: Bad control character in string literal in JSON at position N thrown by JSON.parse.

Confirmed via: Reproduced independently on two unrelated content types in two separate environments — the originally-reported content type, and a second, unrelated content type in an internal test environment — confirming this is not tied to a specific content type or field configuration. In both cases, extracting the exact byte at the reported failure position (via raw.charCodeAt(pos) on the JS string, before calling JSON.parse) showed a literal, unescaped control byte (code 13 / \r) sitting inside what dotCMS's server-side JSON serializer (com.dotmarketing.util.json.JSONObject.quote(), JSONObject.java:1159) had correctly escaped as a 2-character \r sequence server-side — confirming the corruption happens client-side during JS string-literal parsing, not in dotCMS's JSON generation.

Acceptance Criteria
  • Add a new method to JSONTool (e.g. generateEscaped(Object o)) that returns the same JSON produced by generate(Object), additionally escaped so it is safe to embed directly inside a single-quoted JS string literal for JSON.parse() — specifically: double every backslash so JSON's own escape sequences survive JS string-literal decoding intact, and escape any raw single-quote character so it can't terminate the JS string delimiter early.
  • JSON.parse('$!json.generateEscaped($specs)') correctly parses regardless of embedded newlines, tabs, quotes, or backslashes anywhere in the underlying data (verified with a repro case containing at least one such character).
  • Existing $json.generate() behavior/output is unchanged (this is an additive method, not a behavior change).
  • Unit test covering generateEscaped() with a value containing \r\n, \", \\, and '.
  • Brief javadoc on the new method explaining when to use it vs. generate().
dotCMS Version

26.08.03-01

Severity

Medium - Some functionality impacted

Links

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Locate JSONTool and its existing generate(Object) implementation, then compare its output with the escaping behavior noted in JSONObject.java:1159. Add the documented generateEscaped() behavior and a unit test using carriage returns, newlines, quotes, backslashes, and a single quote. Confirm the new output parses through JSON.parse while generate() remains unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
backend
Issue type
Feature
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.