Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
carlalexander committed Dec 24, 2013
0 parents commit 67720bf
Show file tree
Hide file tree
Showing 16 changed files with 1,595 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
@@ -0,0 +1,3 @@
vendor/
composer.lock
phpunit.xml
9 changes: 9 additions & 0 deletions .travis.yml
@@ -0,0 +1,9 @@
language: php

php:
- 5.3.3
- 5.3
- 5.4
- 5.5

before_script: composer install --dev --prefer-source
105 changes: 105 additions & 0 deletions EventListener/CrossDomainListener.php
@@ -0,0 +1,105 @@
<?php

/*
* This file is part of the Helthe Turbolinks package.
*
* (c) Carl Alexander <carlalexander@helthe.co>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Helthe\Component\Turbolinks\EventListener;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;

/**
* Event listener that blocks cross domain redirects.
*
* @author Carl Alexander <carlalexander@helthe.co>
*/
class CrossDomainListener implements EventSubscriberInterface
{
/**
* Header to verify on the request.
*
* @var string
*/
const REQUEST_HEADER = 'X-XHR-Referer';

/**
* Header to verify on the response.
*
* @var string
*/
const RESPONSE_HEADER = 'Location';

/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
KernelEvents::RESPONSE => array('onKernelResponse', -128),
);
}

/**
* Filters the Response.
*
* @param FilterResponseEvent $event
*/
public function onKernelResponse(FilterResponseEvent $event)
{
if (!$event->isMasterRequest()) {
return;
}

$request = $event->getRequest();
$response = $event->getResponse();

if (!$request->headers->has(self::REQUEST_HEADER) || !$response->headers->has(self::RESPONSE_HEADER)) {
return;
}

if (!$this->hasSameOrigin($request, $response)) {
$response->setStatusCode(Response::HTTP_FORBIDDEN);
}
}

/**
* Parse the given url into an origin array with the scheme, host and port.
*
* @param string $url
*
* @return array
*/
private function getUrlOrigin($url)
{
return array(
parse_url($url, PHP_URL_SCHEME),
parse_url($url, PHP_URL_HOST),
parse_url($url, PHP_URL_PORT),
);
}

/**
* Checks if the request and the response have the same origin.
*
* @param Request $request
* @param Response $response
*
* @return Boolean
*/
private function hasSameOrigin(Request $request, Response $response)
{
$requestOrigin = $this->getUrlOrigin($request->headers->get(self::REQUEST_HEADER));
$responseOrigin = $this->getUrlOrigin($response->headers->get(self::RESPONSE_HEADER));

return $requestOrigin == $responseOrigin;
}
}
60 changes: 60 additions & 0 deletions EventListener/RedirectListener.php
@@ -0,0 +1,60 @@
<?php

/*
* This file is part of the Helthe Turbolinks package.
*
* (c) Carl Alexander <carlalexander@helthe.co>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Helthe\Component\Turbolinks\EventListener;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;

/**
* Event listener that adds the necessary redirect header to a RedirectResponse.
*
* @author Carl Alexander <carlalexander@helthe.co>
*/
class RedirectListener implements EventSubscriberInterface
{
/**
* Header inserted in the response.
*
* @var string
*/
const RESPONSE_HEADER = 'X-XHR-Redirected-To';

/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
KernelEvents::RESPONSE => 'onKernelResponse',
);
}

/**
* Filters the Response.
*
* @param FilterResponseEvent $event
*/
public function onKernelResponse(FilterResponseEvent $event)
{
if (!$event->isMasterRequest()) {
return;
}

$response = $event->getResponse();

if ($response->isRedirect() && $response->headers->has('Location')) {
$response->headers->add(array(self::RESPONSE_HEADER => $response->headers->get('Location')));
}
}
}
61 changes: 61 additions & 0 deletions EventListener/RequestMethodListener.php
@@ -0,0 +1,61 @@
<?php

/*
* This file is part of the Helthe Turbolinks package.
*
* (c) Carl Alexander <carlalexander@helthe.co>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Helthe\Component\Turbolinks\EventListener;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;

/**
* Event listener that adds the necessary request method cookie to the response.
*
* @author Carl Alexander <carlalexander@helthe.co>
*/
class RequestMethodListener implements EventSubscriberInterface
{
/**
* Cookie attribute name for the request method.
*
* @var string
*/
const COOKIE_ATTR_NAME = 'request_method';

/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
KernelEvents::RESPONSE => 'onKernelResponse',
);
}

/**
* Filters the Response.
*
* @param FilterResponseEvent $event
*/
public function onKernelResponse(FilterResponseEvent $event)
{
if (!$event->isMasterRequest()) {
return;
}

$event->getResponse()->headers->setCookie(
new Cookie(
self::COOKIE_ATTR_NAME,
$event->getRequest()->getMethod()
)
);
}
}
19 changes: 19 additions & 0 deletions LICENSE
@@ -0,0 +1,19 @@
Copyright (c) 2013 Carl Alexander

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
94 changes: 94 additions & 0 deletions README.md
@@ -0,0 +1,94 @@
# HeltheTurbolinksBundle

The Helthe Turbolinks is a direct port of the rails [turbolinks](https://github.com/rails/turbolinks) gem
and the [jquery.turbolinks](https://github.com/kossnocorp/jquery.turbolinks) gem for
projects using the Symfony [HttpKernel Component](http://symfony.com/doc/current/components/http_kernel/introduction.html).

[![Build Status](https://secure.travis-ci.org/helthe/Turbolinks.png?branch=master)](http://travis-ci.org/helthe/Turbolinks)

## Versions

Current versions of the following gems are used:

* turbolinks: v2.1.0
* jquery.turbolinks: v2.0.1

## Performance

Turbolinks makes following links in your web application faster. Instead of letting
the browser recompile the JavaScript and CSS between each page change, it keeps
the current page instance alive and replaces only the body and the title in the head.

Performance improvements will vary depending on the amount of CSS and Javascript
you are using. You can get up to a 2X increase when using a lot of Javascript and
CSS. You can find the rails benchmarks [here](https://stevelabnik/turbolinks_test).

## Installation

### Using Composer

Add the following in your `componser.json`:

```json
{
"require": {
// ...
"helthe/turbolinks": "~1.0"
}
}
```

## Usage

Using turbolinks requires both the usage of the javascript library and the event listeners included with the component.

### Javascripts

Both the original coffeescript version and compiled version of each script are available for use.

#### Using turbolinks.js

To enable turbolinks, all you need to do is add the compiled turbolinks javascript to your layout in the `<head>`section.

#### Using jquery.turbolinks

If you need to use jquery.turbolinks, you need to add it before `turbolinks.js`.

### Event Listeners

All event listeners need to be added to the event dispatcher for turbolinks to work properly.

```php
<?php
use Helthe\Component\Turbolinks\EventListener\CrossDomainListener;
use Helthe\Component\Turbolinks\EventListener\RedirectListener;
use Helthe\Component\Turbolinks\EventListener\RequestMethodListener;

// ...

// Symfony\Component\EventDispatcher\EventDispatcherInterface
$dispatcher->addSubscriber(new CrossDomainListener());
$dispatcher->addSubscriber(new RedirectListener());
$dispatcher->addSubscriber(new RequestMethodListener());
```

## Compatibility

The turbolinks javascript is designed to work with any browser that fully supports
pushState and all the related APIs. This includes Safari 6.0+ (but not Safari 5.1.x!),
IE10, and latest Chromes and Firefoxes.

Do note that existing JavaScript libraries may not all be compatible with
Turbolinks out of the box due to the change in instantiation cycle. You might
very well have to modify them to work with Turbolinks' new set of events. For
help with this, check out the [Turbolinks Compatibility project](http://reed.github.io/turbolinks-compatibility).

## Additional Resources

Please refer to the [turbolinks](https://github.com/rails/turbolinks) and
[jquery.turbolinks](https://github.com/kossnocorp/jquery.turbolinks) projects
if you require additional information on the javascript libraries and their usage.

## Bugs

For bugs or feature requests, please [create an issue](https://github.com/helthe/Turbolinks/issues/new).

0 comments on commit 67720bf

Please sign in to comment.