Reputation: 105
I have a multipage website that uses React as a glorified widget toolkit (different bundles on different pages). However, some widget state persists inside of a login session When the user reopens the page, the components should reload their initial state. I currently do this by:
This seemed wasteful as the backend already know both what the page is, and the initial state of the widgets. So I thought about rendering the initial state, as a plain object inside the served page and have the object fetch it upon mounting.
My initial try rendered the inital state as:
[some other HTML ... ]
<script>
var WIDGET_INITIAL_STATE = {
startDate: '...',
filters: ['filter1', 'filter2', ... ]
}
</script>
And then using this global in the component setup. Of course, this is dangerously unsafe as filters
is controlled by a text input, so if one were to enter </script> [ arbitrary HTML ] <script>
, the above would become:
<script>
var WIDGET_INITIAL_STATE = {
startDate: '...',
filters['<script>
[arbitrary HTML]
</script>', 'filter2']
}
</script>
which is catastrophically bad.
However, if I do my due diligence and sanitize it server-side, there's no easy way of rendering it in React, as passing a string of the form <script>
would render that literally, as per this page.
The only solution that I see to getting this to work this way is to escape the offending characters as unicode escapes.
Note: I am aware this is highly unorthodox, but I want to see any better takes, if nothing else than a thought exercise.
Upvotes: 1
Views: 448
Reputation: 1073998
When outputting the contents of your filters
array to the script
element, do two things:
Ensure that they're properly-formed string literals. An easy way to do that is to pass the filter string into JSON.stringify
.
Ensure that the sequence </script>
never occurs literally, which is fairly easily done with a regular expression. (This is only necessary because you're outputting these strings in the body of a script
tag; if they were going in an external .js
file that was loaded via src
, this wouldn't matter.)
So for instance, for each filter string
stringToOutputToFiltersArray = JSON.stringify(filterString.replace(/<\s*\/\s*script\s*>/gm, "<\\/script>"))
// "server side" code
var filters = [
'filter1',
'<script> [arbitrary HTML] </sc' + 'ript>',
'filter3'
];
var inlineScript =
"<script>\n" +
"var WIDGET_INITIAL_STATE = {\n" +
" startDate: '...',\n" +
" filters: [" + filters.map(filter =>
JSON.stringify(filter.replace(/<\s*\/\s*script\s*>/gm, "<\\/script>"))
) + "]\n" +
"};\n" +
"</sc" + "ript>";
document.write(inlineScript);
console.log("filters:", filters);
console.log("inlineScript:", inlineScript);
console.log("WIDGET_INITIAL_STATE.filters[1] = ", WIDGET_INITIAL_STATE.filters[1]);
.as-console-wrapper {
max-height: 100% !important;
}
Upvotes: 1