Skip to content
This repository has been archived by the owner on Jul 12, 2023. It is now read-only.

Commit

Permalink
Update README in docs
Browse files Browse the repository at this point in the history
  • Loading branch information
bentranter committed Jan 2, 2018
1 parent 4fd3aac commit 036cad6
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 9 deletions.
4 changes: 2 additions & 2 deletions docs/Turbolinks.html
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,12 @@ <h2>Overview</h2>
<h2>Defined in:</h2>


<a href="https://github.com/bentranter/turbolinks/blob/2bc88aafe4d014867512e00a5333b22b286c14e2/src/turbolinks/version.cr#L1" target="_blank">turbolinks/version.cr</a>
<a href="https://github.com/bentranter/turbolinks/blob/4fd3aacaed4f40306b61565b12806a6800658237/src/turbolinks/version.cr#L1" target="_blank">turbolinks/version.cr</a>

<br/>


<a href="https://github.com/bentranter/turbolinks/blob/2bc88aafe4d014867512e00a5333b22b286c14e2/src/turbolinks.cr#L7" target="_blank">turbolinks.cr</a>
<a href="https://github.com/bentranter/turbolinks/blob/4fd3aacaed4f40306b61565b12806a6800658237/src/turbolinks.cr#L7" target="_blank">turbolinks.cr</a>

<br/>

Expand Down
4 changes: 2 additions & 2 deletions docs/Turbolinks/Handler.html
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ <h2>Included Modules</h2>
<h2>Defined in:</h2>


<a href="https://github.com/bentranter/turbolinks/blob/2bc88aafe4d014867512e00a5333b22b286c14e2/src/turbolinks.cr#L22" target="_blank">turbolinks.cr</a>
<a href="https://github.com/bentranter/turbolinks/blob/4fd3aacaed4f40306b61565b12806a6800658237/src/turbolinks.cr#L22" target="_blank">turbolinks.cr</a>

<br/>

Expand Down Expand Up @@ -176,7 +176,7 @@ <h2>Instance Method Detail</h2>
<br/>
<div>

[<a href="https://github.com/bentranter/turbolinks/blob/2bc88aafe4d014867512e00a5333b22b286c14e2/src/turbolinks.cr#L30" target="_blank">View source</a>]
[<a href="https://github.com/bentranter/turbolinks/blob/4fd3aacaed4f40306b61565b12806a6800658237/src/turbolinks.cr#L30" target="_blank">View source</a>]

</div>
</div>
Expand Down
109 changes: 105 additions & 4 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,6 @@ <h2>Installation</h2>
turbolinks:
github: bentranter/turbolinks</code></pre>

<h2>Warning</h2>

<p>The <code>Location</code> header that is provided on a redirect after a form submit is used to set the value of <code>location</code> in <code>Turbolinks.visit(location)</code>. This value is current <strong>not escaped</strong> and opens you up to JS injection -- please escape this value manually, or wait until a fix is pushed to this repo.</p>

<h2>Usage</h2>

<p>Turbolinks extends <code>HTTP::Handler</code>, so it can be used as HTTP middleware. You can use it with the standard library like so:</p>
Expand All @@ -83,6 +79,111 @@ <h2>Usage</h2>

<span class="t">Kemal</span>.run</code></pre>

<h2>A Note About Security</h2>

<p>A common pattern if you're coming from the Rails world (and using something like <a href="https://github.com/rails/rails/tree/master/actionview/app/assets/javascripts" target="_blank">rails-ujs</a>) a common approach is to handle a <code>POST</code> request, and then redirect to another route. Turbolinks handles this case by intercepting the redirection after the <code>POST</code> request executes, and then responding with a JavaScript snippet to execute <code>Turbolinks.visit("#{location}")</code> for the location to redirect to.</p>

<p>While this is typically safe, if your handler allows the user to input this <code>location</code>, you open yourself up to JavaScript injection. Lets look at two examples where a user is leaving a comment, submitted through a form <code>post</code> request.</p>

<pre><code class='language-crystal'><span class="k">require</span> <span class="s">&quot;kemal&quot;</span>
<span class="k">require</span> <span class="s">&quot;turbolinks&quot;</span>

add_handler <span class="t">Turbolinks</span><span class="t">::</span><span class="t">Handler</span>.<span class="k">new</span>

<span class="c"># Unsafe approach!</span>
post <span class="s">&quot;/unsafe-comment&quot;</span> <span class="k">do</span> <span class="o">|</span>env<span class="o">|</span>
title <span class="o">=</span> env.params.body[<span class="s">&quot;title&quot;</span>]
comment <span class="o">=</span> env.params.body[<span class="s">&quot;comment&quot;</span>]

<span class="c"># Imagine `new_comment` does something to handle a new commment, for</span>
<span class="c"># example&#39;s sake.</span>
new_comment(title, comment)

<span class="c"># This is dangerous because the value of `title` here could be anything --</span>
<span class="c"># including malicious JavaScript. Since the frontend will excute JavaScript</span>
<span class="c"># containing the value of this redirect here, any malicious JavaScript will</span>
<span class="c"># execute.</span>
env.redirect <span class="s">&quot;/comments/</span><span class="i">#{</span></span>title<span class="s"><span class="i">}</span>&quot;</span>
<span class="k">end</span>

<span class="c"># Safe approach.</span>
post <span class="s">&quot;/safe-comment&quot;</span> <span class="k">do</span> <span class="o">|</span>env<span class="o">|</span>
title <span class="o">=</span> env.params.body[<span class="s">&quot;title&quot;</span>]
comment <span class="o">=</span> env.params.body[<span class="s">&quot;comment&quot;</span>]

<span class="c"># Imagine `new_comment` does something to handle a new commment, for</span>
<span class="c"># example&#39;s sake.</span>
comment_id <span class="o">=</span> new_comment(title, comment)

<span class="c"># This is safe because there&#39;s no way for the user submitted `title` or</span>
<span class="c"># `comment` to be available in the returned JavaScript -- you generate an</span>
<span class="c"># ID, and redirect to that route.</span>
env.redirect <span class="s">&quot;/comments/</span><span class="i">#{</span></span>comment_id<span class="s"><span class="i">}</span>&quot;</span>
<span class="k">end</span>

<span class="t">Kemal</span>.run</code></pre>

<p>If you <em>must</em> do something like the unsafe approach, you'll need to sanitize the input yourself.</p>

<h2>A Note For Non <code>rails-ujs</code> Users</h2>

<p>In order for form submissions <em>not</em> to trigger a full page reload, you'll need to ensure that those submissions are submitted as AJAX requests. While a library like <a href="https://github.com/rails/rails/tree/master/actionview/app/assets/javascripts" target="_blank">Rails-UJS</a> or <a href="https://github.com/rails/jquery-ujs" target="_blank">JQuery-UJS</a> would handle this for you, it's straightforward to implement this yourself. The following JavaScript snippet adds bare-minimum support for AJAX form submissions that work with Turbolinks, but I encourage to look at what <code>rails-ujs</code> does as well.</p>

<pre><code class='language-js'>(function() {
"use strict";
/*
* For Google Analytics, you need this:
* &lt;body>
* &lt;script>_gaq.push(['_trackPageview']);&lt;/script>
* ...the rest of the body here...
* &lt;/body>
* See https://coderwall.com/p/ypzfdw/faster-page-loads-with-turbolinks for
* more info.
*/

/*
* By default, Turbolinks submits forms normally. While this may feel
* frustrating as a consumer of the library, it makes sense:
* - No specialized logic on the backend
* - Cache is purged since the page refreshed.
*/
document.addEventListener("DOMContentLoaded", function() {
/**
* submit sends an HTTP request via XHR.
* @param {*Object} formEl - the form element to submit via XHR.
*/
function submit(formEl) {
var xhr = new XMLHttpRequest();
xhr.open(formEl.method, formEl.action, true);

/* See
* https://github.com/rails/rails/blob/master/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee
* for a more in-depth usage.
*/
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
var script = document.createElement("script");
script.innerText = xhr.responseText;
document.head.appendChild(script).parentNode.removeChild(script);
}
}
/* Set relevant headers that some backends check for */
xhr.setRequestHeader("Turbolinks-Referrer", window.location.href);
xhr.setRequestHeader("X-Requested-With", "xhr");
xhr.send(new FormData(formEl));
return false;
}

/* Intercept **any** submit event to submit via XHR instead. */
document.addEventListener("submit", function(e) {
e.preventDefault();
if (e.srcElement) {
submit(e.srcElement);
}
});
});
})();</code></pre>

<h2>Development</h2>

<p>Turbolinks follows the typical Crystal project structure, so cloning the repo and making changes is all you need to do. However, you're encouraged to run this backend alongside the Turbolinks frontend to make sure it works as expected, especially when compared to the Rails backend. The Turbolinks frontend is available at <a href="https://github.com/turbolinks/turbolinks" target="_blank">github.com/turbolinks/turbolinks</a>, and the Rails gem is available at <a href="https://github.com/turbolinks/turbolinks-rails" target="_blank">github.com/turbolinks/turbolinks-rails</a>.</p>
Expand Down
Loading

0 comments on commit 036cad6

Please sign in to comment.