Long story short, I'm trying to get the output from JsonConvert.SerializeObject to be sanitized without having to modify the contents of the saved data.
I'm working on an app that has the following markup in the view:
<textarea data-bind="value: aboutMe"></textarea>
If I save the following text, I run into problems:
<script type="text/javascript">alert("hey")</script>
The error I get in FF:
The relevant part of the offending rendered text:
$(document).ready(ko.applyBindings(new
MyProfileVm({"profileUsername":"admin","username":"Admin","aboutMe":"alert(\"hey\")","title":"Here's a
short self-bio!
:)","thumbnail":"https://i.imgur.com/H1HYxU9.jpg","locationZip":"22182","locationName":"Vienna,
VA"
And finally - at the bottom of my view:
<script type="text/javascript">
$(document).ready(ko.applyBindings(new MyProfileVm(#Html.Raw(JsonConvert.SerializeObject(Model, new JsonSerializerSettings() { ContractResolver = new CamelCasePropertyNamesContractResolver() })))));
</script>
Here, I'm passing the model that I get from the MVC controller into the js ViewModel for knockout to map into observable data. The Raw encoding seems to be the problem, but I'm not sure how to go about handling it.
To be clear, I'm getting data from the server, and outputting it to the client, which is mucking up the JSON/KO combo.
The problems is that you cannot have a closing </script> tag inside a JavaScript string literal because the browser interprets it as then end of the script block. See also: Script tag in JavaScript string
There is no builtin function in Asp.Net what could handle it on the server side you before outputting your generated script you need to replace the </script> to something else:
<script type="text/javascript">
$(document).ready(ko.applyBindings(new MyProfileVm(#Html.Raw(
JsonConvert.SerializeObject(Model,
new JsonSerializerSettings() {
ContractResolver = new CamelCasePropertyNamesContractResolver()
}).Replace("</script>", "</scripttag>")
))));
</script>
Of course if you will need this in multiple place you can move this logic into a helper/extension method, like:
public static class JavaScriptExtensions
{
public static string SerializeAndEscapeScriptTags(this object model)
{
return JsonConvert.SerializeObject(model,
new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
}).Replace("</script>", "</scripttag>");
}
}
And use it with:
#using YourExtensionMethodsNamespace
<script type="text/javascript">
$(document).ready(ko.applyBindings(new MyProfileVm(#Html.Raw(
Model.SerializeAndEscapeScriptTags()))));
</script>
And on the JavaScript side in your Knockout viewmodel you need to replace back the </script> tag before the usage:
var MyProfileVm = function(data) {
//...
this.aboutMe = ko.observable(
// you need `"</scr"+ "ipt>"` because of the above mentioned problem.
data.aboutMe.replace(/<\/scripttag>/g, "</scr"+ "ipt>"));
}
Of course you can also create a helper function for this, like:
function fixScriptTags(data) {
for(var prop in data) {
if (typeof(data[prop]) == "string") {
data[prop] = data[prop].replace(/<\/scripttag>/g, "</scr"+ "ipt>");
}
//todo check for complex property values and call fixScriptTags recursively
}
return data;
}
And use it with:
ko.applyBindings(new ViewModel(fixScriptTags(data)));
Demo JSFiddle.
I've had a similar problem, it came from using knockout.js to get input from a <textarea> just like you did. Everything was fine on the "create" part, but once I put the data back into an action via #Html.Raw(...), it turned out to contain linefeed and carriage-return characters that broke the json string.
So I added something like this:
// Regex to replace all unescaped (single) backslashes in a string
private static Regex _regex = new Regex(#"(?<!\\)\\(?!\\)", RegexOptions.Compiled);
(I know it doesn't handle "\\\", but that doesn't appear from knockout)
Then I build my anonymous classes and do this:
var coJson = JsonHelper.Serialize(co);
var coJsonEsc = _regex.Replace(coJson, #"\\")
Maybe this can help you. I found it by breaking in the razor view and looking at the strings.
This problem also appears with unesacped tabs (\t) and possibly other escape sequences.
Related
ViewBag value is completely ignored while running ASP.NET MVC4 web page.
Here is the source for above image.
Even though I am checking if the ViewBag.SearchResultsJson is null or empty, ViewBag still isn't written in the output.
<script>
#{
HtmlString jsonText = new HtmlString("");
if (!string.IsNullOrWhiteSpace(ViewBag.SearchResultsJson))
{
jsonText = Html.Raw(ViewBag.SearchResultsJson);
}
}
$(document).ready(function() {
var json = #jsonText;
app.value('searchResultsJson', json);
})
</script>
What am I missing here?
According your code, an empty jsonText is a valid scenario so I won't focus on why this variable is empty.
The reason for your error is you're not wrapping #jsonText with quotes to render a valid string on Javascript side.
You should change
var json = #jsonText;
by
var json = '#jsonText';
I’ve gotten stuck on an “Object reference not set to an instance of an object” error message.
We’re trying to use a PagePart field that is attached to the Page type to dynamically link a CSS file in the HEAD of a layout file. See below code.
<!-- DYNAMIC CSS-->
var contentItem = Model.ContentItem;
var pagePart = (PagePart)contentItem.PagePart;
if (!String.IsNullOrWhiteSpace(pagePart.FestivalProgramName))
{
<link ref="#Url.Content("/Themes/MyTheme/Styles/festival-programs/" + pagePart.FestivalProgramName + ".css")" rel="stylesheet" type="text/css">
}
This is in a file called:
Layout.cshtml
Something is wrong about this (obviously) since pagePart is “null” when I Attach to Debugger and look. I get that the Layout file doesn’t know that it’s associated with a “Page” Content Type but this layout is only used with Pages. Anyway, this is very similar to code that works elsewhere in our Orchard site. Any help or advice is hugely appreciated!
Thanks, T
In the Layout, the Model is the Layout object. It has nothing to do whatsoever with whatever content is going to get rendered into the Content zone.
I think what you are trying to do should be done by overriding the Page template (Content-Page.Detail.cshtml). (Note the Detail part, you probably don't want to import every css when displaying multiple pages in summary)
In there you can do:
#{
var contentItem = Model.ContentItem; // The Page content item
var pagePart = contentItem.Page; // Note that casting to PagePart won't work, because it does not exist
if (!String.IsNullOrWhiteSpace(pagePart.FestivalProgramName.Value))
{
// "Orchard's" way to include styles
Style.Include("festival-programs/" + pagePart.FestivalProgramName.Value + ".css");
}
}
EDIT:
What you probably should do (I assume not every page is a festival page) is create a new Content Type: FestivalPage. Then attach the following parts to this content type (same as the Page content type):
Common
Publish later
Title
Autoroute
Body (Orchard 1.8.1 and lower)
Layout (Orchard 1.9.x)
Tags
Localization
Menu
And your field:
FestivalProgramName
Then create an alternate Content-FestivalPage.Detail.cshtml with the following content:
#using Orchard.Utility.Extensions;
#{
if (Model.Title != null) {
Layout.Title = Model.Title;
}
Model.Classes.Add("content-item");
var contentTypeClassName = ((string)Model.ContentItem.ContentType).HtmlClassify();
Model.Classes.Add(contentTypeClassName);
var tag = Tag(Model, "article");
var contentItem = Model.ContentItem; // The FestivalPage content item
var pagePart = contentItem.FestivalPage; // The FestivalPage part with the FestivalProgramName field
if (!String.IsNullOrWhiteSpace(pagePart.FestivalProgramName.Value))
{
// "Orchard's" way to include styles
Style.Include("festival-programs/" + pagePart.FestivalProgramName.Value + ".css");
}
}
// -- Default Orchard content --
#tag.StartElement
<header>
#Display(Model.Header)
#if (Model.Meta != null) {
<div class="metadata">
#Display(Model.Meta)
</div>
}
</header>
#Display(Model.Content)
#if(Model.Footer != null) {
<footer>
#Display(Model.Footer)
</footer>
}
#tag.EndElement
// ----
This way you won't get in the way with the normal pages of the application.
Thank you so much for responding. Your solution works but not in my case since I use the Layout Selector module and can't override at the Page.Detail level since that would imply one layout - or at least from my perspective it seems that way. I found another option though that does the trick.
We already take advantage of the Handlers class to insert META stuff into the HEAD of the page and thanks to your feedback and this thread Adding an element to page <head> in Orchard CMS, it dawned on me to use the same Handler to insert the CSS link.
PagePartHandler.cs.
using System;
using MyModuleName.Models;
using Orchard.ContentManagement.Handlers;
using Orchard.Core.Title.Models;
using Orchard.Data;
using Orchard.UI.Resources;
using Orchard.Utility.Extensions;
namespace MyModuleName.Handlers {
public class PagePartHandler : ContentHandler {
private readonly IResourceManager _resourceManager;
public PagePartHandler(
IRepository<PagePartRecord> repository,
IResourceManager resourceManager) {
_resourceManager = resourceManager;
Filters.Add(StorageFilter.For(repository));
OnGetDisplayShape<PagePart>(RegisterFestivalProgramStyle);
}
private void RegisterFestivalProgramStyle(BuildDisplayContext context, PagePart part) {
if (context.DisplayType != "Detail")
return;
if (String.IsNullOrWhiteSpace(part.FestivalProgramName))
return;
_resourceManager.RegisterLink(new LinkEntry
{
Rel = "stylesheet",
Type = "text/css",
Href = "/Themes/Bootstrap/Styles/festival-programs/" + part.FestivalProgramName + ".css"
});
}
}
}
This uses the tradition link style, not ResourceManifest.cs, but WORKS!
I want to add unknown number of images based on how many is there for specific user; i have used .Append function for div,labels,inputs and its work fine but when i want to append with source its not working !
so how can i do this ?
i have tried this:
` string sc = "$('<img/>', {src=\"~/Content/themes/img2.jpg\" alt=\"\",class:'table-wrapper'}).appendTo(finalDalata);";
return JavaScript(sc)`
finalDalata is my Div name
so whats iam doing wrong ?
Your appendTo code is invalid, it needs a selector, and your image attributes are not formatted properly, so it should be:
string sc = "$('<img/>', { src:'~/Content/themes/img2.jpg', alt:'', class:'table-wrapper'}).appendTo('#finalDalata');"
However, controller should return data and not control (exploits and injections comes to mind), so a better way would be to create an AppendImage function on the View side in javascript:
function AppendImage(attributes)
{
$('<img/>', attributes).appendTo('#finalDalata');
}
Note: you should probably sanitize the attributes before to be sure.
Then return a list of attributes only:
var images = new List<dynamic>();
// Do this for each images
images.Add(new { src="~/Content/themes/img2.jpg", alt="", class="table-wrapper"});
return Json(images);
And in the ajax success you can:
success: function (data) {
data.foreach( function (item) {
AppendImage(item);
});
}
Solved by TagBuilder
TagBuilder tag = new TagBuilder("img");
tag.Attributes.Add("id", "myImage");
tag.Attributes.Add("src", "/imgs/" + UserName + ".jpg");
tag.Attributes.Add("alt", "my image");
tag.Attributes.Add("class", "imgsClass");
then convert tag to string and pass it back to view
thnx
I am trying to create the name of a function dynamically:
I set the name in razor code:
# { name="bob"; }
Then I try to create the javascript function, but the syntax is wrong.
function #name#:_onActivate() {
.....
}
How can I use #name in the name of the function?
I know im late to the party, but i had the same question and came up with this..
Use the #() Razor syntax:
function #(Model.Name)_Init()
{
.....some code...
}
Works like a charm
(http://haacked.com/archive/2011/01/06/razor-syntax-quick-reference.aspx/)
This was found to work in writing Javascript function names dynamically
#{
#:function
#name<text>_onActivate() {
....
}
</text>
}
There is also another option:
#{
var functionStart = "function " + name + "_onActivate()";
}
#functionStart
{
...
}
This method doesn't make so much mess in Visual Studio in some cases.
This worked well for me:
#{
var someFuncName = "someFuncName";
var someFuncName2 = "someFuncName" + Model.type;
var someDynamicFunc = Model.funcName;
var someFuncNameComplete = "functionHelper()";
}
<script type="text/javascript">
function #someFuncName#{<text>()</text>}{
...
}
function #someFuncName2#{<text>(data)</text>}{
...
}
function #someDynamicFunc#{<text>()</text>}{
...
}
function #someFuncNameComplete{
...
}
</script>
If your JavaScript is in the view (rather than its own js file - though I'm not saying I think this is advisable), you can try wrapping your JS code in <text> tags, so that the parser switches back to HTML mode (vs C# mode).
Another alternative may be not naming the function dynamically but instead passing in the name as a parameter - but whether that would work may depend on your design/implementation.
First you can set the function name in a string with below structure:
#{
string name = "bob";
string funcTxt = name + "_onActivate()";
}
Then you can use this string into your function declaration:
<script>
function #funcTxt {
...
}
</script>
I have a view from which I call another view to render some json inside a script tag in my html:
public ActionResult App()
{
return View();
}
public JsonResult SomeJsonData()
{
// ... here goes the code that generates the model
return Json(model, JsonRequestBehavior.AllowGet);
}
inside my App.cshtml file I have something like this:
<script type='text/javascript'>
var myJsonData = #Html.Action("SomeJsonData", "MyController");
</script>
The problem is that sometimes when I reload the page in the browser (using Chrome 20 right now) it shows all the markup, and if I go to the Network tab in the developer tools I can see that the Content-Type of the page request was of type "application/json". If I just reload the page then it loads correctly (the content type is "text/html" as it should be).
Any idea on why does this happen? or what am I doing wrong?
When you return a JsonResult you are modifying the response Content-Type to application/json. So you first invoke the App controller action which returns a View and obviously sets the Content-Type to text/html and inside the returned view you call the SomeJsonData action which craps on the previous content type and modifies it to application/json. Of course the last one wins and that's what the user agent sees at the end of the day: application/json.
So, here's how to proceed:
public ActionResult App()
{
// ... here goes the code that generates the model
var model = ...
return View(model);
}
and in your strongly typed view:
#model MyViewModel
<script type="text/javascript">
var myJsonData = #Html.Raw(Json.Encode(Model));
</script>
Actually I just found another related question
calling #Html.Action for JsonResult changes my response type in parent template
I couldn't find anything before I posted.
The approach I'm gonna take is just changing the content type when returning the json data:
public JsonResult SomeJsonData(bool returnAsHtml = false)
{
// ... here goes the code that generates the model
return returnAsHtml ?Json(model, "text/html", JsonRequestBehavior.AllowGet) : Json(model, JsonRequestBehavior.AllowGet);
}
and on App.cshtml
<script type='text/javascript'>
var myJsonData = #Html.Action("SomeJsonData", "MyController", new {returnAsHtml = true});
</script>
And I'm adding also a flag to allow calling the actionmethod from other places that are expecting an application/json response.
Like other have said, the action that is returning JSON is changing the content-type of the response.
I was able to work around this by using a HtmlHelper to place the JSON into the page. I am avoiding using the controller and action; my HtmlHelper is calling a static method. This solution probably won't work in all cases and is kind of a hack, but you can avoid having to put the data into a bunch of view models this way.
namespace System.Web.Mvc.Html
{
public static class JsonDataProviderHelper
{
public static MvcHtmlString JsonDataProvider(this HtmlHelper helper, JsonDataType jsonDataType)
{
switch (jsonDataType)
{
case JsonDataType.YARDSALE_MINI_CALENDAR:
var yardsales = SalesEventCrud.GetByMonthForJavascript(null, MvcApplication.CurrentPortalId);
return new MvcHtmlString(JsonConvert.SerializeObject(SalesEventCrud.GetByMonthForJavascript(null, MvcApplication.CurrentPortalId)));
default:
return new MvcHtmlString("");
}
}
public enum JsonDataType
{
YARDSALE_MINI_CALENDAR
}
}
}
I am using json.net for serialization. I have the switch statement because I plan to use this helper to return JSON for other situations also.
And in the page:
<script type="text/javascript">
jQuery(document).ready(function () {
setupSmallSidebarCalendar(#Html.JsonDataProvider(JsonDataProviderHelper.JsonDataType.YARDSALE_MINI_CALENDAR));
});
</script>
In the page, the data is written in straight JSON, so no other parsing / massaging of the data is needed.
#Daran's found the cause - the ContentType gets overwritten with whatever was done last. His solution should work, however if you still like the Action method pattern, you should be able to do something like the following and simply change the ContentType before you return. It's still JSON data, but the content type plays nice with HTML views.
[HttpPost]
public JsonResult GetJson(int someId, int foobar)
{
JsonResult result = CreateResult(someId, foobar);
result.ContentType = "text/html";
return result;
}
The downside is that this is, well, a bit weird and/or unexpected behavior. I doubt this will work if you called the endpoint via AJAX, for example. But it looks like it would almost work and that would probably cause some confusion if it's in a shared codebase. For that reason, if you do this, it may be worth naming the endpoint in such a way that it isn't misused. You could even go so far as to create a new type of Result that is designed to work this way rather than use JsonResult.