Rails: How to escape_javascript with client-side AJAX request - javascript

How do I escape a collection when I send it via to_json from the controller action directly client-side?
When I say the request is sent from the controller action and then directly to client-side (skips pre-processing) it looks like this:
AJAX request gets routed to a controller action
Controller Action directly sends the result via javascript to the requester, and on the client-side the requester's javascript would do the processing. something like: app/assets/javascripts/blogs.js
This is as opposed to the request being sent to a controller_action, then to a server-side view for pre-processing, then the results being sent to the requester. Looks like this:
AJAX request
Routed to a controller action
Sent to a view for pre-process. Something like: app/views/blogs/index.js.erb
Results get sent to the requester
Short Example:
def some_action
#blogs = Blog.all
respond_to do |format|
format.json {render json: #blogs} # currently dangerous, #blogs is not sanitized.
end
end
As an example: assume one of the #blogs records in that collection has this data input from a hacker:
#blogs.first.title
=> <script>alert('Site now hacked if this javascript runs!')</script>
When the #blogs get rendered in the browser: I want to escape the #blogs content so that the javascript will not get triggered from that hacked entry.
Longer Example:
The user makes a selection for a blogger in a select box.
An AJAX request gets sent which grabs all the blogs associated to that selected blogger.
The AJAX request then updates a second select box for blogs which will now list as options all the blogs that belong_to that selected blogger.
For the code: the controller action code above would be exactly the same. Below is the client-side javascript:
app/assets/javascripts/blogs.js
$("body").on('change', '[data-action="blogger_sel"]', function() {
var blogs_selection = $(this).closest('[data-parent-for="blogs_sel"]').find('[data-action="blogs_sel"]');
$.ajax({
url: "/blogs",
type: "GET",
data: {blogger_id: $(this).val()},
success: function (data) {
blogs_selection.children().remove();
$.each(data, function (index, item) {
blogs_selection.append('<option value=' + item.id + '>' + item.title + '</option>');
});
}
})
});
So up above: the part that I am concerned about is value.id and value.title. Those are things that could be dangerous if I do not escape them. I want to make sure it is escaped so that any dangerous input will be rendered harmless.

Below is a solution. Keep in mind that it is often times a good idea to sanitize data before it gets persisted into the database as well. Also: it is preferable to sanitize server-side before sending the response to the requester:
app/assets/javascripts/blogs.js
$("body").on('change', '[data-action="blogger_sel"]', function() {
var blog_sel = $(this).closest('[data-parent-for="blog_sel"]').find('[data-action="blog_sel"]');
$.ajax({
url: "/blogs",
type: "GET",
data: {blogger_id: $(this).val()},
success: function (data) {
blog_sel.children().remove();
$.each(data, function (index, item) {
blog_sel.append($('<option>', {
value: item.id,
text : item.title
}));
});
}
})
});
Do not append options the following way because it will execute dangerous hacks:
blogs_selection.append('<option value=' + value.id + '>' + value.title + '</option>');

You're dealing with an unsanitized HTML string like any other. You need to make it safe before inserting it on your page. All you really need to do is replace the < and > characters with < and >.
Using this controller as an example:
class SomethingController < ApplicationController
def jsontest
render json: { blogs: ["<script>alert(1);</script>"] }
end
end
jQuery can do this for you, if you don't mind the clunkiness:
$.ajax({
type: 'GET',
url: '/something/jsontest',
dataType: 'json',
success: function (data) {
console.log($('<div>').text(data['blogs'][0]).html());
}
})
> <script>alert(1);</script>
You could also look at using ActionView::Helpers::SanitizeHelper#sanitize on the controller side, which will clobber any HTML tags it finds. (Normally this is only available to views, so you could either make a view to render your JSON or include ActionView::Helpers::SanitizeHelper in your controller). Or do both!

Related

Correct jQuery syntax for doing a $.post with a header (ASP.NET MVC)

I'm working on a ASP.NET MVC project that uses some jQuery on the client side. I have a jQuery call like this, which works correctly:
$.post($('form').attr("action"), $('form').serialize(), function(data){
// Deal with the data that came back from the ASP.NET MVC controller
}
I want to do the exact same call, except I also want to send in a custom header. I am able to successfully create and send a custom header like this:
var token = $('input[name="__RequestVerificationToken"]').val();
var headers = {};
headers['__RequestVerificationToken'] = token;
$.ajax({
url: "/report_observation/" + submitType,
cache: false,
type: "POST",
data: {
'viewModel': $('form').serialize(),
},
headers: headers,
success: function (data) {
// Deal with the data that came back from the ASP.NET MVC controller
},
error: function (response) {
alert("Error: " + response);
}
});
There are two problems with this second bit of code. One is that I have to make a custom url to go to the correct controller, and I don't know of a way to simply use $('form').attr("action") to automatically go to the correct place.
The second -- and bigger -- problem is that I'm not able to pass over the form data with $('form').serialize() as I could in the one liner $.post example. Doing a #Html.Raw(Json.Encode(Model)) in my Razor cshtml file doesn't send over the model for the whole form, presumably because this code is within a partial view that doesn't know the state of the models in the other partial views that make up this form.
Anyway, I'm thinking there must be an easy way to take this code...
var token = $('input[name="__RequestVerificationToken"]').val();
var headers = {};
headers['__RequestVerificationToken'] = token;
...and incorporate the headers property into this bit of code:
$.post($('form').attr("action"), $('form').serialize(), function(data){
// Deal with the data that came back from the ASP.NET MVC controller
}
However, I can't figure out the correct syntax. Any ideas?
OK, I figured it out. The second example can indeed work if the data field looks like this:
data: $("form").serialize(),
In other words, I had to NOT assign it to the viewModel parameter.
yes, for the second problem use that dev5000 says, and, for the URL, simply get the value for the attribute action fo the form and use it:
var url = $("from").attr("action");
$.ajax({
url: url,
...
})

PHP, Javascript, Ajax and the aspect of getting a result

I have a php file with a lot of checkboxes on the left hand side. I extract the values via javscript and pass them into an array. Which works just fine. I would like to pass then this array via Ajax to PHP in order to mess around with the values and create SQL-statements out of them.
$(document).ready(function(e) {
$('#getSelectedValues').click(function() {
var chkBoxArray = [];
$('.graphselectors:checked').each(function() {
chkBoxArray.push($(this).val());
});
for (var i = 0; i < chkBoxArray.length; i++) {
console.log(i + " = " + chkBoxArray[i]);
}
$.ajax({
url: 'index.php', // (1)
type: 'POST',
dataType:'json',
data: chkBoxArray, //(2)
success: function(data){
console.log(data.length);
console.log(data);
}
});
});
});
Several questions:
(1) what file name do I need to add here? The origion or the target?
(2) I have numerous ways of this: serialization, with these brackets {}, and so on. How to get it done right?
An error that I get is the following:
Notice: Undefined index: data in graph.php
That makes me wondering a bit, because it clearly shows no data is being send.
var_dumps on $_POST and $_SERVER offer these results:
array(0) { }
array(0) { }
which is somewhat unsatisfying.
Where am I doing things wrong? The only puzzling aspect is the ajax, all other stuff is not much of an issue.
The site is supposed to work in the following way:
Page -> Checkbox(clicked) -> Button -> result (ajax) -> PHP fetches result -> SQL DB -> PHP gets DB result -> fetch result (ajax) -> jslibrary uses result for something.
1- You need to point your ajax to the script that will use the data you are sending. I would not recommend to point to index.php, since you'll need to add a if statement checking if there is data on $_POST that is exactly what your're expecting, otherwise it will return the same page that you're in (considering that you are in index.php and is making a request to index.php). A point to consider. Since it is a whole request and it's not a method call to actually return something to your page you need to echo things. That said, consider also to set header('Content-Type: application/json') then, since you're expecting dataType: 'json', echo json_encode($objectorarray);
2- Since you're sending a Javascript array to PHP, it can't interpret correctly the structure, that is why you should use JSON.stringify(chkBoxArray) before sending it. But just setting it on data attribute would send the number of checkboxes you selected as values to POST, so consider to data: {myCheckboxValues: JSON.stringify(chkBoxArray)}
In your PHP script, considering all the security measures to take, you can $foo = json_decode($_POST['myCheckboxValues']);
Well, of course you need to pass the target page as url in your ajax call. It can't guess which file should process the data.
As for the data property. You need to give your data a name.
data: {
something: "something"
}
Becomes: $_POST['something']. (value: something)
$.ajax({
url: 'target.php', // (1)
type: 'POST',
dataType:'json',
data: { data: chkBoxArray }, //(2) Now $_POST['data'] will work
success: function(data){
console.log(data.length);
console.log(data);
}
});

MVC Jquery/Controller Post Redirect

I'm struggling to achieve the following, I have a page where a user Logs a Call, the user needs to input various fields and selects from several dropdowns, I then need to post that data (either via JQuery or the controller) to another page, where the user can view the entered data and decide to commit it or not.
I've been going back and fourth for ages now, trying to figure out how to post data from my cshtml to my controller and then redirect to another page with that data persisting.
I've tried to redirect via JQuery and/or the controller and just can't seem to get one or the other working.
Code extracts below:
cshtml:
$.ajax({
url: dir + '/Submit/',
async: true,
type: 'POST',
data: JSON.stringify(callData),
contentType: 'application/json; charset=utf-8',
complete: function () { },
success: function (data) {
}
})
Controller:
[HttpPost]
public ActionResult Submit(SupportCallModel callData)
{
SupportCallModel newData = new SupportCallModel();
newData.SupportCallID = 1;
newData.CallTypeID = callData.CallTypeID;
newData.TroubleShooting = callData.TroubleShooting;
newData.EmailRequest = callData.EmailRequest;
newData.MailDate = callData.MailDate;
newData.FSEOnSite = callData.FSEOnSite;
newData.FSEEmployeeID = callData.FSEEmployeeID;
newData.CallCategory = callData.CallCategory;
newData.CallType = callData.CallType;
newData.CallItem = callData.CallItem;
newData.Summary = callData.Summary;
newData.Description = callData.Description;
newData.ExternalReference = callData.ExternalReference;
newData.CallStatusID = callData.CallStatusID;
newData.CallPriorityID = callData.CallPriorityID;
newData.CallCoEmployeeID = callData.CallCoEmployeeID;
return RedirectToAction("Verify", newData);
}
public ActionResult Verify(SupportCallModel postData)
{
return View(postData);
}
Using ajax is pointless since ajax calls stay on the same page (return RedirectToAction("Verify", newData); is ignored). You can just do a normal submit. Assuming your rendering all the required inputs for SupportCallModel in view, then it will post back. I would recommend you include
[HttpPost]
public ActionResult Submit(SupportCallModel callData)
{
if (!ModelState.IsValid)
{
return View(callData);
}
...
at the top of the method in case the model contains validation errors.
You then create a new instance of SupportCallModel based on the properties of callData which also seems pointless (why not just pass callData instead of newData?)
If SupportCallModel contains only properties which are Value types then you can use return RedirectToAction("Verify", newData); or return RedirectToAction("Verify", callData);. Internally a RouteValueDictionary is created based on the name and value of each property and postData will be correctly bound in the Verify() method. If however any of the properties are complex types or collections then binding will fail for those properties. In that case you need to persist the model so it can be retrieved in the Verify method. My recommendation would be to persist to the database (either a separate table or the existing table that includes a field indicating a pending status), but you could use Session or TempData (in conjuction with .Peek so its not lost if the user hits the refresh button).
I'm not sure exactly what the Verify GET method is rendering, but if it does not include controls for all properties then the Verify submit button will need to post back some ID value that allows you the retrieve the model again from the database or session and finally save it to the database.
It's not working because you're redirecting on the server side when you're making the call via AJAX.
Your redirects should be made on the client side since you're calling the ActionResult on a non-traditional sense (AJAX).
You can remove the return RedirectToAction("Verify", newData); from your action result since it will not do anything. You can probably just return something that specifies whether the call was valid or not.
For your data to persist on another page, you will have to save the data into a temp table in DB so you can show it when you do a redirect.
$.ajax({
url: dir + '/Submit/',
async: true,
type: 'POST',
data: JSON.stringify(callData),
contentType: 'application/json; charset=utf-8',
complete: function () {
},
success: function (data) {
if (data && data.isValid) {
// Grab the tempId that was saved temporarily for verification.
var tempId = data.tempId;
// Perform redirect
window.location = dir + '/Verify/' + tempId;
}
}
});
In your url post...
$.ajax({
url: '#Url.Action("Submit","{ControllerName}")',
async: true,
type: 'POST',
data: JSON.stringify(callData),
contentType: 'application/json; charset=utf-8',
complete: function () { },
success: function (data) {
window.location.href = '#Url.Action("Verify","{ControllerName}", Model);'
}
})
You could model this all without Ajax using standard form posts to MVC controllers reasonably simply.
Assuming your flow looks something like this:
Make the Submit POST return a view containing the data sent in the model. If that data can be verified make the view allow the data on that form to be posted to a Confirm controller action.
In this design the data is entirely transient, the data sent in the HTTP form payload on the initial post is then returned as a form within the returned HTML. This data is then in turn sent to the Verify action.
To do this in your case I think it might be as simple as calling the Submit with the Post verb as a none Ajax call and amending it so the return line looks like return View("Verify", newData);
You will also obviously need to do something else with Verify to do something in that action method.

better way to do html updates then to expose javascript in rails3

I have a following code that is part of the _form.html.erb code. Basically I have a form in which I have a observe_field function where on change, it will set fields' values without refreshing the page. Following is my html code:
<script type="text/javascript">
// When DOM loads, init the page.
$(function() {
// Executes a callback detecting changes with a frequency of 1 second
$("#id_element_placeholder").observe_field(1, function( ) {
$.ajax({
type: "GET",
dataType: "json",
url: "/students/get/" + this.value,
success: function(data){
$('#last_name').attr('value', data.student.last_name);
$('#building').attr('value', data.student.building);
$('#room').attr('value', data.student.room);
}
});
});
});
</script>
Problem here is that I'm exposing lot of my code in javascript. Is there a better way to do it without exposing code in javascript?
Here is what my controller looks like:
def get
#student = Student.find(params[:id])
respond_to do |format|
format.html
format.json { render :json => #student }
end
end
Basically I get an id in a form and from there I have to get the corresponding object and update the fields on the page.
Assuming your design requires you to make AJAX calls to query student info by id, then you need to expose a URL for the call. If you don't want to expose a JSON data structure, you could return a chunk of HTML (instead of JSON), and replace the contents of the container of all of the controls you mention above.

apply link_to_remote from a jQuery handler in Rails

I have a game with 5 boxes and when a box is clicked I want to send an ajax request to a controller that in effect is exactly like link_to_remote. I need to send a hash of informatoin to a definition in a controller but don't need anything to get sent back or even updated.
What the following jQuery/js mess does is render a function which either correct or incorrect. From those functions I want to be able to send an ajax request to some controller with a hash of information.
$('#tones a').click(function(e) {
if (playGame) {
id = this.id.replace('t', '')
if (correctCharacter[1] == id) {
correct();
} else {
incorrect();
}
addCharacters();
}
});
})
I know link_to_remote has the following syntax and this link is not really related at all:
# Generates: <a href="#" onclick="new Ajax.Updater('posts', '/blog/destroy/3', {asynchronous:true, evalScripts:true});
link_to_remote "Delete this post", :update => "posts", :url => { :action => "destroy", :id => post.id }
If you could show me how to make this link work in the latter function. I just need one ajax link or request whatever is right that goes to a controller and sends a hash of information.
I want to put it in the following block. If it is correct then it will trigger and if it is not correct then it will trigger.
if (correctCharacter[1] == id) {
correct();
} else {
incorrect();
}
Here is js source from a link_to_remote I put on a page just to see the security stuff. It's not really related to where I want to post information but it is related to my specific application.
Test
This is what I came up with. It works except it isn't posting to the correct url.
$.ajax({
type: "POST",
url: "analytics/test",
data: 'correct=' + correct_or_incorrect + '&id=' + correctCharacter[3] + "'"
});

Categories

Resources