Search code examples
phplaravelroutesescapingline-breaks

Laravel 10 partially unescaping back JSON strings with \n \r escape sequences inside


In my Laravel 10 test project I have an issue with JSON files that are read from storage folder and served as simple text output into the browser.

If there is a string field with \r and/or \n escape sequences, some of them are printed as real line-breaks / new-lines in the browser.

I created a reproducible example with HTML code inside the string, but the same issue is also with normal text, provided that there are \n \r escape sequences.

Please focus on the fact that the JSON files are usually rendered in a browser without breaking the page.

Indeed in my case the rendering is not the problem, instead the issue is that the PHP function echo or the return statement are altering the output, printing real \n \r characters instead of escape sequences.

Let's say that I have a text JSON file with this object as content:

{"html":"<!DOCTYPE html>\n<html><head><meta charset=\"UTF-8\"><\/head><body>\n<p style=\"vertical-align:middle;font-size:2em;\" >\ntest\n<\/p><\/body><\/html>"}

The HTMl is from a text editor, so it can contain \n and/or \r escape sequences.

If I put the file in the public folder and ask the browser to retrieve its content by its url I get this output:

{"html":"<!DOCTYPE html>\n<html><head><meta charset=\"UTF-8\"><\/head><body>\n<p style=\"vertical-align:middle;font-size:2em;\" >\ntest\n<\/p><\/body><\/html>"}

that is correct.

If I put the file in a suitable folder of the storage and apply this route in the web.php file:

Route::get('/demo/{filename}', function ($filename) {
  $filePath =Storage::path("public/demo/".$filename);
             
        \Log::Info('file '.$filePath);
       
    if (file_exists($filePath)) {
        $json = file_get_contents($filePath); 
        \Log::Info($json);

        return $json; //the same if the echo function is used

    } else {
     return view('welcome'); }
 });

and then ask the browser to retrieve the file, I get this output:

{"html":"\n<\/head>\n

\ntest\n<\/p><\/body><\/html>"}

You can see that it's three lines, a line is interposed, and parts are missing. The last line is also bigger in font size, although it cannot be seen here.

(in the sample code you can use the echo function instead of the return statement with same results)

A different output in the log file:

[2024-03-26 12:25:29] production.INFO: {"html":"<!DOCTYPE html>
<html><head><meta charset=\"UTF-8\"><\/head><body>
<p style=\"vertical-align:middle;font-size:2em;\" >
test
<\/p><\/body><\/html>"}  

As you can see, new-lines are there too. The main difference is that it's not rendered as HTML as it happens in the browser, but the output is the same.

Is the issue my code's fault or a Laravel's bug? Can be solved?


Solution

  • You are returning the raw unparsed JSON as a regular response, which'll be interpreted as HTML, and any tags within it will be interpreted.

    You can manually tell the browser you're returning JSON:

    return response($json)->header('Content-Type', 'application/json');
    

    or parse the JSON and return a JSON response:

    $json = json_decode($json);
    return response()->json($json);