Nette - {link!} macro can't handle already existing GET params in URL - macros

I Use Nette 2 in my project and I also use .latte template system with AJAX.
Now I have jquery function (in template) that should generate GET request on the same destination but it should add some GET parameters after it.
This destination is initially rendered using one GET parameter, then some actions are made and during some of them AJAX loads some information from the same destination (just adds a couple of GET parameters).
Now I generate AJAX URL using .latte {link!} macro (exclamation mark stands for signal). This is now able to generate new URL with GET params appended to original one. But append is badly parsed, because there is &amp%3B in the URL instead of just &.
I have this code in my template:
{block content}
<h1 n:block=title>New Rule</h1>
{snippet rulesForm}
{form newRuleForm}
{$form}
<script type="text/javascript">
{foreach $form['rule']->containers as $i => $rule}
{include #jsCallback, id => $i, input => $rule['table']->name, link => tableChange}
{include #jsCallback, id => $i, input => $rule['column']->name, link => tableChange}
{/foreach}
</script>
{/form}
{/snippet}
{/block}
{define #jsCallback}
$('#{$control["newRuleForm"]['rule'][$id][$input]->htmlId}').on('change', function(){
alert('{$link}');
$.nette.ajax({
type: 'GET',
url: '{link {$link}!}',
data: {
'value': $(this).val(),
}
});
});
{/define}
How can I fix this problem so I can generate link which appends GET parameters correctly?
Thanks.

Best approach is to avoid generating inline Javascript. You can mark all those form controls by CSS class (eg. special-input), then you don't have to generate Javascript code in Latte iteration.
{snippet rulesForm}
<div data-link-table="{link tableChange!}"></div>
...
{/snippet}
$('.special-input').on('change', function () {
$.nette.ajax({
type: 'GET',
url: $('[data-link-table]').data('linkTable'),
data: {
value: $(this).val()
}
});
});

Maybe noescape modifier?
$.nette.ajax({
type: 'GET',
url: '{link {$link|noescape}!}',
data: {
'value': $(this).val(),
}
});

Related

Where to implement XSS prevention in symfony-based REST API and Vue.js front-end

I'm building an application that requires html tags to be allowed for user comments in Vue.js.
I wan't to allow users to input a certain selection of HTML tags(p, i, ul, li) and escape/sanitize other like script or div.
Right now I see three ways of dealing with this issue:
On rendering the content with Vue.js
Before sending the response in Symfony(I'm using JMS Serializer)
Upon receiving request to the API
Personally I think that we could save the data to database with tags like script or div, and just sanitize them before sending a response.
Basically my question is where should I implement the prevention and should I allow tags like script into my database?
If you're using v-html to render the comments, then there's always the possibility of XSS. Strict HTML sanitization can mitigate the risk, but you never know.
The only surefire way to prevent XSS is to never use v-html or innerHTML. This means you'll have to parse the HTML (using DOMParser) and render the comments manually.
For something like this it will be easier if you write the render function manually so you have full control over how the comment content will be rendered – only render the HTML tags you choose. Whitelist instead of blacklist.
Don't render user-defined HTML attributes.
HTML sanitization won't be necessary on the server because the HTML will never be rendered as-is in the browser, but you can still sanitize it if you want to trim the fat beforehand.
Here's a basic example:
Vue.component('comment-content', {
functional: true,
props: {
html: {},
allowedElements: {
default: () => ['p', 'i', 'b', 'ul', 'li'],
},
},
render(h, ctx) {
const { html, allowedElements } = ctx.props;
const renderNode = node => {
switch (node.nodeType) {
case Node.TEXT_NODE: return renderTextNode(node);
case Node.ELEMENT_NODE: return renderElementNode(node);
}
};
const renderTextNode = node => {
return node.nodeValue;
};
const renderElementNode = node => {
const tag = node.tagName.toLowerCase();
if (allowedElements.includes(tag)) {
const children = [...node.childNodes].map(node => renderNode(node));
return h(tag, children);
}
};
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
return [...doc.body.childNodes].map(node => renderNode(node));
},
});
new Vue({
el: '#app',
data: {
html: `
<p>Paragraph</p>
<ul>
<li>One <script>alert('Hacked')<\/script></li>
<li onmouseover="alert('Hacked')">Two</li>
<li style="color: red">Three <b>bold</b> <i>italic</i></li>
<li>Four <img src="javascript:alert('Hacked')"></li>
</ul>
<section>This element isn't allowed</section>
<p>Last paragraph</p>
`,
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<comment-content :html="html"></comment-content>
</div>

Retrieving params with sinatra on form submit. Params is undefined

I am having trouble accessing params in Sinatra after submitting a form. This is my form:
function submitForm(e) {
e.preventDefault();
$.ajax({
type: 'POST',
url: '/mix_addresses',
//grab the inputs from address_section
//data: $('.add_address_section .add_address_field').map(function() { return $(this).val() }),
data: [1,2,3],
dataType: "json",
success: function(data) {
debugger;
}
});
}
And this is my endpoint:
require 'sinatra'
require 'jobcoin_client'
get '/' do
erb :add_coins
end
post '/mix_addresses' do
puts params
end
I'm getting to the endpoint, but the params are blank. Shouldn't it be [1,2,3]? Instead, it's:
{"undefined"=>""}
Anyone see what I'm doing wrong?
Several issues here :)
Sinatra configuration
Main problem is coming from the fact that Sinatra doesn't deal with JSON payloads by default. If you want to send a JSON payload, then the easiest solution will be :
To add rack-contrib to your Gemfile,
Then, require it in your sinatra app: require rack/contrib
And load the middleware that deals with this issue: use Rack::PostBodyContentTypeParser
And you should be good to go!
Source: several SO post reference this issue, here, here or here for instance.
jQuery ajax call
Also, note that there might be some issues with your request :
You'll need to use a key: value format for your JSON payload: { values: [1,2,3] }
You'll need to stringify your JSON payload before sending it: data: JSON.stringify( ... )
You'll need to set the request content type to application/json (dataType is related to the data returned by the server, and doesn't say anything about your request format, see jQuery ajax documentation for more details).
Eventually, you should end up with something like this on the client side:
function submitForm(e) {
e.preventDefault();
$.ajax({
type: 'POST',
url: '/mix_addresses',
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify({ values: [1,2,3] }),
success: function(data) {
debugger;
}
});
}

LavaCharts - Exporting Chart as an Image - 'getImageCallBack is not defined'

So I am using Kevin Hill's excellent PHP wrapper for Google Charts, 'LavaCharts' and am following his guide on how to access the image URI of each (I need to output the charts in an image format so that I can export the page as a PDF).
https://github.com/kevinkhill/lavacharts/wiki/Getting-a-chart-as-an-image-3.0.x
I am getting the following error: 'getImageCallBack is not defined'
I am registering the 'getImageCallBack' event on each chart from the Controller:
\Lava::PieChart('TotalCallsReceived', $totalCallsReceived, [
'events' => ['ready' => 'getImageCallBack'],
'title' => 'Total Calls Received & Transferred',
'is3D' => false,
]);
And then in the head of my page I have the following. (I will figure out what to do with the URI once I've solved this error. For now logging it is fine)
<script type="text/javascript">
function getImageCallback(event, chart) {
console.log(chart.getImageURI());
// This will return in the form of "data:image/png;base64,iVBORw0KGgoAAAAUA..."
}
</script>
Has anybody else overcome this problem?
the function names are different and need to match case...
the "B" is capitalized here...
'events' => ['ready' => 'getImageCallBack'],
and not here...
function getImageCallback(event, chart) {

Twitter Typeahead.js with Yahoo Finance in AJAX

I am trying to couple the new version of Typeahead.js and using it with JSON that needs to be pulled from AJAX and not from a JSON file like they have in their examples. I just can't get it to work, I don't want to cache the JSON result or anything, I want to pull it live from Yahoo.
My HTML input is <input type="text" id="symbol" name="symbol" autofocus autocomplete="off" placeholder="Symbol" onkeyup="onSymbolChange(this.value)" />
My AJAX/PHP file has this to retrieve data (this part work, I tested it with Firebug)
header('Content-type:text/html; charset=UTF-8;');
$action = (isset($_GET['action'])) ? $_GET['action'] : null;
$symbol = (isset($_GET['symbol'])) ? $_GET['symbol'] : null;
switch($action) {
case 'autocjson':
getYahooSymbolAutoComplete($symbol);
break;
}
function getYahooSymbolAutoCompleteJson($symbolChar) {
$data = #file_get_contents("http://d.yimg.com/aq/autoc?callback=YAHOO.util.ScriptNodeDataSource.callbacks&query=$symbolChar");
// parse yahoo data into a list of symbols
$result = [];
$json = json_decode(substr($data, strlen('YAHOO.util.ScriptNodeDataSource.callbacks('), -1));
foreach ($json->ResultSet->Result as $stock) {
$result[] = '('.$stock->symbol.') '.$stock->name;
}
echo json_encode(['symbols' => $result]);
}
The JS file (this is where I'm struggling)
function onSymbolChange(symbolChar) {
$.ajax({
url: 'yahoo_autocomplete_ajax.php',
type: 'GET',
dataType: 'json',
data: {
action: 'autocjson',
symbol: symbolChar
},
success: function(response) {
$('#symbol').typeahead({
name: 'symbol',
remote: response.symbols
});
}
});
}
I don't think that I'm suppose to attach a typeahead inside an AJAX success response, but I don't see much examples with AJAX (except for a previous version of typeahead)... I see the JSON response with Firebug after typing a character but the input doesn't react so good. Any guidance would really be appreciated, I'm working on a proof of concept at this point... It's also worth to know that I'm using AJAX because I am in HTTPS and using a direct http to Yahoo API is giving all kind of problems with Chrome and new Firefox for insecure page.
UPDATE
To make it to work, thanks to Hieu Nguyen, I had to modify the AJAX JSON response from this echo json_encode(['symbols' => $result]); to instead this echo json_encode($result); and modify the JS file to use the code as suggested here:
$('#symbol').typeahead({
name: 'symbol',
remote: 'yahoo_autocomplete_ajax.php?action=autocjson&symbol=%QUERY'
});
I have to do it in reverse, i.e: hook the ajax call inside typeahead remote handler. You can try:
$('#symbol').typeahead({
name: 'symbol',
remote: '/yahoo_autocomplete_ajax.php?action=autocjson&symbol=%QUERY'
});
You don't have to create onSymbolChange() function since typeahead will take care of that already.
You can also filter and debug the response from backend by using:
$('#symbol').typeahead({
name: 'symbol',
remote: {
url: '/yahoo_autocomplete_ajax.php?action=autocjson&symbol=%QUERY',
filter: function(resp) {
var dataset = [];
console.log(resp); // debug the response here
// do some filtering if needed with the response
return dataset;
}
}
});
Hope it helps!

JSON object parsing error using jQuery Form Plugin

Environment: JQuery Form Plugin, jQuery 1.7.1, Zend Framework 1.11.11.
Cannot figure out why jQuery won't parse my json object if I specify an url other than a php file.
The form is as follows:
<form id="imageform" enctype="multipart/form-data">
Upload your image <input type="file" name="photoimg" id="photoimg" />
<input type="submit" id ="button" value="Send" />
</form>
The javascript triggering the ajax request is:
<script type="text/javascript" >
$(document).ready(function() {
var options = {
type: "POST",
url: "<?php $this->baseURL();?>/contact/upload",
dataType: 'json',
success: function(result) {
console.log(result);
},
error: function(ob,errStr) {
console.log(ob);
alert('There was an error processing your request. Please try again. '+errStr);
}
};
$("#imageform").ajaxForm(options);
});
</script>
The code in my zend controller is:
class ContactController extends BaseController {
public function init() {
/* Initialize action controller here */
}
public function indexAction() {
}
public function uploadAction() {
if (isset($_POST) and $_SERVER['REQUEST_METHOD'] == "POST") {
$image = $_FILES['photoimg']['tmp_name'];
$im = new imagick($image);
$im->pingImage($image);
$im->readImage($image);
$im->thumbnailImage(75, null);
$im->writeImage('userImages/test/test_thumb.jpg');
$im->destroy();
echo json_encode(array("status" => "success", "message" => "posted successfully"));
}
else
echo json_encode(array("status" => "fail", "message" => "not posted successfully"));
}
}
When I create an upload.php file with the above code, and modify the url from the ajax request to
url: "upload.php",
i don't run into that parsing error, and the json object is properly returned. Any help to figure out what I'm doing wrong would be greatly appreciated! Thanks.
You need either to disable layouts, or using an action helper such as ContextSwitch or AjaxContext (even better).
First option:
$this->_helper->viewRenderer->setNoRender(true);
$this->_helper->layout->disableLayout();
And for the second option, using AjaxContext, you should add in your _init() method:
$ajaxContext = $this->_helper->getHelper('AjaxContext');
$ajaxContext->addActionContext('upload', 'json')
->initContext();
This will disable automatically disable layouts and send a json header response.
So, instead of your two json_encode lines, you should write:
$this->status = "success";
$this->message = "posted successfully";
and
$this->status = "fail";
$this->message = "not posted successfully";
In order to set what to send back to the client, you simply have to assign whatever content you want into view variables, and these variables will be automatically convert to json (through Zend_Json).
Also, in order to tell your controller which action should be triggered, you need to add /format/json at the end of your URL in your jQuery script as follow:
url: "<?php $this->baseURL();?>/contact/upload/format/json",
More information about AjaxContext in the manual.
Is the Content-type header being properly set as "application/json" when returning your JSON?