diff --git a/composer.json b/composer.json
index ac5d6fa..443ede0 100644
--- a/composer.json
+++ b/composer.json
@@ -27,7 +27,8 @@
     "phpunit/phpunit-mock-objects": "dev-master#e60bb929c50ae4237aaf680a4f6773f4ee17f0a2",
     "zendframework/zend-feed": "2.2.*",
     "mikey179/vfsStream": "1.*",
-    "stack/builder": "1.0.*"
+    "stack/builder": "1.0.*",
+    "willdurand/stack-negotiation": "0.1.0"
   },
   "autoload": {
     "psr-4": {
diff --git a/composer.lock b/composer.lock
index 981d888..1f5cf7e 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "hash": "8afb97667c2791fec2fb1fc43853da24",
+    "hash": "05caecc0cbc2f838a2fdb4c459be011f",
     "packages": [
         {
             "name": "doctrine/annotations",
@@ -2351,6 +2351,104 @@
             "time": "2013-12-06 07:47:10"
         },
         {
+            "name": "willdurand/negotiation",
+            "version": "1.3.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/willdurand/Negotiation.git",
+                "reference": "a98fb6b9808610c1aa326c736893d3d77d9383b6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/willdurand/Negotiation/zipball/a98fb6b9808610c1aa326c736893d3d77d9383b6",
+                "reference": "a98fb6b9808610c1aa326c736893d3d77d9383b6",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.3-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Negotiation": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "William Durand",
+                    "email": "william.durand1@gmail.com",
+                    "homepage": "http://www.willdurand.fr"
+                }
+            ],
+            "description": "Content Negotiation tools for PHP provided as a standalone library.",
+            "homepage": "http://williamdurand.fr/Negotiation/",
+            "keywords": [
+                "accept",
+                "content",
+                "format",
+                "header",
+                "negotiation"
+            ],
+            "time": "2014-05-16 12:34:51"
+        },
+        {
+            "name": "willdurand/stack-negotiation",
+            "version": "0.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/willdurand/StackNegotiation.git",
+                "reference": "5e35c15b12f3fc6d4bc818f44d94bdf7c5c84fe0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/willdurand/StackNegotiation/zipball/5e35c15b12f3fc6d4bc818f44d94bdf7c5c84fe0",
+                "reference": "5e35c15b12f3fc6d4bc818f44d94bdf7c5c84fe0",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4.0",
+                "symfony/serializer": "~2.1",
+                "willdurand/negotiation": "~1.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~3.7",
+                "symfony/http-kernel": "~2.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Negotiation": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "William Durand",
+                    "email": "william.durand1@gmail.com",
+                    "homepage": "http://www.willdurand.fr"
+                }
+            ],
+            "description": "Stack middleware for content negotiation.",
+            "time": "2014-01-22 10:32:39"
+        },
+        {
             "name": "zendframework/zend-escaper",
             "version": "2.2.1",
             "target-dir": "Zend/Escaper",
diff --git a/core/core.services.yml b/core/core.services.yml
index c43da8c..be0b1c8 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -379,6 +379,19 @@ services:
   http_kernel.basic:
     class: Symfony\Component\HttpKernel\HttpKernel
     arguments: ['@event_dispatcher', '@controller_resolver', '@request_stack']
+  http_negotiation.format_negotiator:
+    class: Negotiation\FormatNegotiator
+    calls:
+      - [registerFormat, ['drupal_ajax', ['application/vnd.drupal-ajax']]]
+      - [registerFormat, ['drupal_dialog', ['application/vnd.drupal-dialog']]]
+      - [registerFormat, ['drupal_modal', ['application/vnd.drupal-modal']]]
+  http_negotiation.language_negotiator:
+    class: Negotiation\LanguageNegotiator
+  http_middleware.negotiation:
+    class: Negotiation\Stack\Negotiation
+    arguments: ['@http_negotiation.format_negotiator', '@http_negotiation.language_negotiator']
+    tags:
+      - { name: http_middleware, priority: 400 }
   http_middleware.reverse_proxy:
     class: Drupal\Core\StackMiddleware\ReverseProxyMiddleware
     arguments: ['@settings']
@@ -447,7 +460,7 @@ services:
       - { name: backend_overridable }
   router.route_preloader:
     class: Drupal\Core\Routing\RoutePreloader
-    arguments: ['@router.route_provider', '@state', '@content_negotiation']
+    arguments: ['@router.route_provider', '@state']
     tags:
       - { name: 'event_subscriber' }
   router.matcher.final_matcher:
@@ -542,7 +555,6 @@ services:
     arguments: [16]
   accept_header_matcher:
     class: Drupal\Core\Routing\AcceptHeaderMatcher
-    arguments: ['@content_negotiation']
     tags:
       - { name: route_filter }
   content_type_header_matcher:
@@ -604,7 +616,6 @@ services:
       - { name: route_enhancer, priority: 20 }
   route_content_controller_subscriber:
     class: Drupal\Core\EventSubscriber\ContentControllerSubscriber
-    arguments: ['@content_negotiation']
     tags:
       - { name: event_subscriber }
   route_content_form_controller_subscriber:
@@ -637,13 +648,11 @@ services:
     tags:
       - { name: event_subscriber }
     arguments: ['@router', '@router.request_context', NULL, '@request_stack']
-  content_negotiation:
-    class: Drupal\Core\ContentNegotiation
   view_subscriber:
     class: Drupal\Core\EventSubscriber\ViewSubscriber
+    arguments: ['@title_resolver', '@ajax_response_renderer']
     tags:
       - { name: event_subscriber }
-    arguments: ['@content_negotiation', '@title_resolver', '@ajax_response_renderer']
   html_view_subscriber:
     class: Drupal\Core\EventSubscriber\HtmlViewSubscriber
     tags:
@@ -738,7 +747,7 @@ services:
     arguments: ['@config.manager', '@config.storage', '@config.storage.snapshot']
   exception_controller:
     class: Drupal\Core\Controller\ExceptionController
-    arguments: ['@content_negotiation', '@title_resolver', '@html_page_renderer', '@html_fragment_renderer', '@string_translation', '@url_generator', '@logger.factory']
+    arguments: ['@title_resolver', '@html_page_renderer', '@html_fragment_renderer', '@string_translation', '@url_generator', '@logger.factory']
     calls:
       - [setContainer, ['@service_container']]
   exception_listener:
@@ -798,10 +807,6 @@ services:
       - { name: event_subscriber }
     calls:
       - [setContainer, ['@service_container']]
-  ajax.subscriber:
-    class: Drupal\Core\Ajax\AjaxSubscriber
-    tags:
-      - { name: event_subscriber }
   image.toolkit.manager:
     class: Drupal\Core\ImageToolkit\ImageToolkitManager
     arguments: ['@container.namespaces', '@cache.discovery', '@config.factory', '@module_handler', '@image.toolkit.operation.manager', '@logger.channel.image']
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 8c96031..208398c 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -366,10 +366,10 @@ function drupal_get_filename($type, $name, $filename = NULL) {
  *   The cid for this request.
  */
 function drupal_page_cache_get_cid(Request $request) {
-  $cid_parts = array(
+  $cid_parts = [
     $request->getUri(),
-    \Drupal::service('content_negotiation')->getContentType($request),
-  );
+    $request->getRequestFormat(),
+  ];
   return sha1(implode(':', $cid_parts));
 }
 
diff --git a/core/lib/Drupal/Core/Ajax/AjaxSubscriber.php b/core/lib/Drupal/Core/Ajax/AjaxSubscriber.php
deleted file mode 100644
index f298d88..0000000
--- a/core/lib/Drupal/Core/Ajax/AjaxSubscriber.php
+++ /dev/null
@@ -1,43 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Core\Ajax\AjaxSubscriber.
- */
-
-namespace Drupal\Core\Ajax;
-
-use Symfony\Component\HttpKernel\KernelEvents;
-use Symfony\Component\HttpKernel\Event\GetResponseEvent;
-use Symfony\Component\EventDispatcher\EventSubscriberInterface;
-
-/**
- * Subscribes to the kernel request event to add the Ajax media type.
- *
- * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
- *   The event to process.
- */
-class AjaxSubscriber implements EventSubscriberInterface {
-
-  /**
-   * Registers Ajax formats with the Request class.
-   */
-  public function onKernelRequest(GetResponseEvent $event) {
-    $request = $event->getRequest();
-    $request->setFormat('drupal_ajax', 'application/vnd.drupal-ajax');
-    $request->setFormat('drupal_dialog', 'application/vnd.drupal-dialog');
-    $request->setFormat('drupal_modal', 'application/vnd.drupal-modal');
-  }
-
-  /**
-   * Registers the methods in this class that should be listeners.
-   *
-   * @return array
-   *   An array of event listener definitions.
-   */
-  static function getSubscribedEvents(){
-    $events[KernelEvents::REQUEST][] = array('onKernelRequest', 50);
-    return $events;
-  }
-
-}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/ContentNegotiation.php b/core/lib/Drupal/Core/ContentNegotiation.php
deleted file mode 100644
index 500200c..0000000
--- a/core/lib/Drupal/Core/ContentNegotiation.php
+++ /dev/null
@@ -1,64 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\ContentNegotiation.
- */
-
-namespace Drupal\Core;
-
-use Symfony\Component\HttpFoundation\Request;
-
-/**
- * This class is a central library for content type negotiation.
- *
- * @todo Replace this class with a real content negotiation library based on
- *   mod_negotiation. Development of that is a work in progress.
- */
-class ContentNegotiation {
-
-  /**
-   * Gets the normalized type of a request.
-   *
-   * The normalized type is a short, lowercase version of the format, such as
-   * 'html', 'json' or 'atom'.
-   *
-   * @param Symfony\Component\HttpFoundation\Request $request
-   *   The request object from which to extract the content type.
-   *
-   * @return string
-   *   The normalized type of a given request.
-   */
-  public function getContentType(Request $request) {
-    // AJAX iframe uploads need special handling, because they contain a JSON
-    // response wrapped in <textarea>.
-    if ($request->get('ajax_iframe_upload', FALSE)) {
-      return 'iframeupload';
-    }
-
-    // Check all formats, if priority format is found return it.
-    $first_found_format = FALSE;
-    $priority = array('html', 'drupal_ajax', 'drupal_modal', 'drupal_dialog');
-    foreach ($request->getAcceptableContentTypes() as $mime_type) {
-      $format = $request->getFormat($mime_type);
-      if (in_array($format, $priority, TRUE)) {
-        return $format;
-      }
-      if (!is_null($format) && !$first_found_format) {
-        $first_found_format = $format;
-      }
-    }
-
-    // No HTML found, return first found.
-    if ($first_found_format) {
-      return $first_found_format;
-    }
-
-    if ($request->isXmlHttpRequest()) {
-      return 'ajax';
-    }
-
-    // Do HTML last so that it always wins.
-    return 'html';
-  }
-}
diff --git a/core/lib/Drupal/Core/Controller/ExceptionController.php b/core/lib/Drupal/Core/Controller/ExceptionController.php
index 69a477b..b26a3ff 100644
--- a/core/lib/Drupal/Core/Controller/ExceptionController.php
+++ b/core/lib/Drupal/Core/Controller/ExceptionController.php
@@ -21,7 +21,6 @@
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Component\Utility\String;
 use Symfony\Component\Debug\Exception\FlattenException;
-use Drupal\Core\ContentNegotiation;
 use Drupal\Core\Utility\Error;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\Core\StringTranslation\TranslationInterface;
@@ -33,13 +32,6 @@ class ExceptionController extends HtmlControllerBase implements ContainerAwareIn
   use StringTranslationTrait;
 
   /**
-   * The content negotiation library.
-   *
-   * @var \Drupal\Core\ContentNegotiation
-   */
-  protected $negotiation;
-
-  /**
    * The service container.
    *
    * @var \Symfony\Component\DependencyInjection\ContainerInterface
@@ -70,9 +62,6 @@ class ExceptionController extends HtmlControllerBase implements ContainerAwareIn
   /**
    * Constructor.
    *
-   * @param \Drupal\Core\ContentNegotiation $negotiation
-   *   The content negotiation library to use to determine the correct response
-   *   format.
    * @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver
    *   The title resolver.
    * @param \Drupal\Core\Page\HtmlPageRendererInterface $renderer
@@ -86,9 +75,8 @@ class ExceptionController extends HtmlControllerBase implements ContainerAwareIn
    * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
    *   The logger factory.
    */
-  public function __construct(ContentNegotiation $negotiation, TitleResolverInterface $title_resolver, HtmlPageRendererInterface $renderer, HtmlFragmentRendererInterface $fragment_renderer, TranslationInterface $string_translation, UrlGeneratorInterface $url_generator, LoggerChannelFactoryInterface $logger_factory) {
+  public function __construct(TitleResolverInterface $title_resolver, HtmlPageRendererInterface $renderer, HtmlFragmentRendererInterface $fragment_renderer, TranslationInterface $string_translation, UrlGeneratorInterface $url_generator, LoggerChannelFactoryInterface $logger_factory) {
     parent::__construct($title_resolver, $url_generator);
-    $this->negotiation = $negotiation;
     $this->htmlPageRenderer = $renderer;
     $this->fragmentRenderer = $fragment_renderer;
     $this->stringTranslation = $string_translation;
@@ -119,7 +107,7 @@ public function setContainer(ContainerInterface $container = NULL) {
    *   A response object.
    */
   public function execute(FlattenException $exception, Request $request) {
-    $method = 'on' . $exception->getStatusCode() . $this->negotiation->getContentType($request);
+    $method = 'on' . $exception->getStatusCode() . $request->getRequestFormat();
 
     if (method_exists($this, $method)) {
       return $this->$method($exception, $request);
diff --git a/core/lib/Drupal/Core/EventSubscriber/ContentControllerSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ContentControllerSubscriber.php
index 377ae93..d587949 100644
--- a/core/lib/Drupal/Core/EventSubscriber/ContentControllerSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/ContentControllerSubscriber.php
@@ -7,7 +7,6 @@
 
 namespace Drupal\Core\EventSubscriber;
 
-use Drupal\Core\ContentNegotiation;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 use Symfony\Component\HttpKernel\Event\GetResponseEvent;
 use Symfony\Component\HttpKernel\KernelEvents;
@@ -18,47 +17,16 @@
 class ContentControllerSubscriber implements EventSubscriberInterface {
 
   /**
-   * Content negotiation library.
-   *
-   * @var \Drupal\Core\ContentNegotiation
-   */
-  protected $negotiation;
-
-  /**
-   * Constructs a new ContentControllerSubscriber object.
-   *
-   * @param \Drupal\Core\ContentNegotiation $negotiation
-   *   The Content Negotiation service.
-   */
-  public function __construct(ContentNegotiation $negotiation) {
-    $this->negotiation = $negotiation;
-  }
-
-  /**
    * Associative array of supported mime types and their appropriate controller.
    *
    * @var array
    */
-  protected $types = array(
+  protected $types = [
     'drupal_dialog' => 'controller.dialog:dialog',
     'drupal_modal' => 'controller.dialog:modal',
     'html' => 'controller.page:content',
     'drupal_ajax' => 'controller.ajax:content',
-  );
-
-  /**
-   * Sets the derived request format on the request.
-   *
-   * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
-   *   The event to process.
-   */
-  public function onRequestDeriveFormat(GetResponseEvent $event) {
-    $request = $event->getRequest();
-
-    if (!$request->attributes->get('_format')) {
-      $request->setRequestFormat($this->negotiation->getContentType($request));
-    }
-  }
+  ];
 
   /**
    * Sets the _controller on a request based on the request format.
@@ -84,8 +52,7 @@ public function onRequestDeriveContentWrapper(GetResponseEvent $event) {
    *   An array of event listener definitions.
    */
   static function getSubscribedEvents() {
-    $events[KernelEvents::REQUEST][] = array('onRequestDeriveFormat', 31);
-    $events[KernelEvents::REQUEST][] = array('onRequestDeriveContentWrapper', 30);
+    $events[KernelEvents::REQUEST][] = ['onRequestDeriveContentWrapper', 30];
 
     return $events;
   }
diff --git a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php
index 84b94c9..696b104 100644
--- a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php
@@ -18,8 +18,6 @@
 use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 
-use Drupal\Core\ContentNegotiation;
-
 /**
  * Main subscriber for VIEW HTTP responses.
  *
@@ -30,13 +28,6 @@
 class ViewSubscriber implements EventSubscriberInterface {
 
   /**
-   * The content negotiation.
-   *
-   * @var \Drupal\Core\ContentNegotiation
-   */
-  protected $negotiation;
-
-  /**
    * The title resolver.
    *
    * @var \Drupal\Core\Controller\TitleResolverInterface
@@ -53,15 +44,12 @@ class ViewSubscriber implements EventSubscriberInterface {
   /**
    * Constructs a new ViewSubscriber.
    *
-   * @param \Drupal\Core\ContentNegotiation $negotiation
-   *   The content negotiation.
    * @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver
    *   The title resolver.
    * @param \Drupal\Core\Ajax\AjaxResponseRenderer $ajax_renderer
    *   The ajax response renderer.
    */
-  public function __construct(ContentNegotiation $negotiation, TitleResolverInterface $title_resolver, AjaxResponseRenderer $ajax_renderer) {
-    $this->negotiation = $negotiation;
+  public function __construct(TitleResolverInterface $title_resolver, AjaxResponseRenderer $ajax_renderer) {
     $this->titleResolver = $title_resolver;
     $this->ajaxRenderer = $ajax_renderer;
   }
@@ -88,7 +76,7 @@ public function onView(GetResponseForControllerResultEvent $event) {
     // object.  The subrequest's response will get dissected and placed into
     // the larger page as needed.
     if ($event->getRequestType() == HttpKernelInterface::MASTER_REQUEST) {
-      $method = 'on' . $this->negotiation->getContentType($request);
+      $method = 'on' . $request->getRequestFormat();
 
       if (method_exists($this, $method)) {
         $event->setResponse($this->$method($event));
diff --git a/core/lib/Drupal/Core/Routing/AcceptHeaderMatcher.php b/core/lib/Drupal/Core/Routing/AcceptHeaderMatcher.php
index 34f0ae5..17f5c3f 100644
--- a/core/lib/Drupal/Core/Routing/AcceptHeaderMatcher.php
+++ b/core/lib/Drupal/Core/Routing/AcceptHeaderMatcher.php
@@ -8,7 +8,6 @@
 namespace Drupal\Core\Routing;
 
 use Drupal\Component\Utility\String;
-use Drupal\Core\ContentNegotiation;
 use Symfony\Cmf\Component\Routing\NestedMatcher\RouteFilterInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
@@ -20,23 +19,6 @@
 class AcceptHeaderMatcher implements RouteFilterInterface {
 
   /**
-   * The content negotiation library.
-   *
-   * @var \Drupal\Core\ContentNegotiation
-   */
-  protected $contentNegotiation;
-
-  /**
-   * Constructs a new AcceptHeaderMatcher.
-   *
-   * @param \Drupal\Core\ContentNegotiation $cotent_negotiation
-   *   The content negotiation library.
-   */
-  public function __construct(ContentNegotiation $content_negotiation) {
-    $this->contentNegotiation = $content_negotiation;
-  }
-
-  /**
    * {@inheritdoc}
    */
   public function filter(RouteCollection $collection, Request $request) {
@@ -44,7 +26,7 @@ public function filter(RouteCollection $collection, Request $request) {
     // @todo replace by proper content negotiation library.
     $acceptable_mime_types = $request->getAcceptableContentTypes();
     $acceptable_formats = array_filter(array_map(array($request, 'getFormat'), $acceptable_mime_types));
-    $primary_format = $this->contentNegotiation->getContentType($request);
+    $primary_format = $request->getRequestFormat();
 
     foreach ($collection as $name => $route) {
       // _format could be a |-delimited list of supported formats.
diff --git a/core/lib/Drupal/Core/Routing/Enhancer/AjaxEnhancer.php b/core/lib/Drupal/Core/Routing/Enhancer/AjaxEnhancer.php
index 4d0c707..b12b15f 100644
--- a/core/lib/Drupal/Core/Routing/Enhancer/AjaxEnhancer.php
+++ b/core/lib/Drupal/Core/Routing/Enhancer/AjaxEnhancer.php
@@ -9,7 +9,6 @@
 
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface;
-use Drupal\Core\ContentNegotiation;
 
 /**
  * Enhances an ajax route with the appropriate controller.
@@ -17,30 +16,13 @@
 class AjaxEnhancer implements RouteEnhancerInterface {
 
   /**
-   * Content negotiation library.
-   *
-   * @var \Drupal\CoreContentNegotiation
-   */
-  protected $negotiation;
-
-  /**
-   * Constructs a new \Drupal\Core\Routing\Enhancer\AjaxEnhancer object.
-   *
-   * @param \Drupal\Core\ContentNegotiation $negotiation
-   *   The Content Negotiation service.
-   */
-  public function __construct(ContentNegotiation $negotiation) {
-    $this->negotiation = $negotiation;
-  }
-
-  /**
    * {@inheritdoc}
    */
   public function enhance(array $defaults, Request $request) {
     // A request can have the 'ajax' content type when the controller supports
     // basically both simple HTML and Ajax routes by returning a render array.
     // In those cases we want to convert it to a proper ajax response as well.
-    if (empty($defaults['_content']) && $defaults['_controller'] != 'controller.ajax:content' && in_array($this->negotiation->getContentType($request), array('drupal_ajax', 'ajax', 'iframeupload'))) {
+    if (empty($defaults['_content']) && $defaults['_controller'] != 'controller.ajax:content' && in_array($request->getRequestFormat(), ['drupal_ajax', 'ajax', 'iframeupload'])) {
       $defaults['_content'] = isset($defaults['_controller']) ? $defaults['_controller'] : NULL;
       $defaults['_controller'] = 'controller.ajax:content';
     }
diff --git a/core/lib/Drupal/Core/Routing/RoutePreloader.php b/core/lib/Drupal/Core/Routing/RoutePreloader.php
index 7952d17..1f223bf 100644
--- a/core/lib/Drupal/Core/Routing/RoutePreloader.php
+++ b/core/lib/Drupal/Core/Routing/RoutePreloader.php
@@ -7,7 +7,6 @@
 
 namespace Drupal\Core\Routing;
 
-use Drupal\Core\ContentNegotiation;
 use Drupal\Core\State\StateInterface;
 use Symfony\Component\EventDispatcher\Event;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@@ -38,13 +37,6 @@ class RoutePreloader implements EventSubscriberInterface {
   protected $state;
 
   /**
-   * The content negotiation.
-   *
-   * @var \Drupal\Core\ContentNegotiation
-   */
-  protected $negotiation;
-
-  /**
    * Contains the non-admin routes while rebuilding the routes.
    *
    * @var array
@@ -58,13 +50,10 @@ class RoutePreloader implements EventSubscriberInterface {
    *   The route provider.
    * @param \Drupal\Core\State\StateInterface $state
    *   The state key value store.
-   * @param \Drupal\Core\ContentNegotiation $negotiation
-   *   The content negotiation.
    */
-  public function __construct(RouteProviderInterface $route_provider, StateInterface $state, ContentNegotiation $negotiation) {
+  public function __construct(RouteProviderInterface $route_provider, StateInterface $state) {
     $this->routeProvider = $route_provider;
     $this->state = $state;
-    $this->negotiation = $negotiation;
   }
 
   /**
@@ -75,7 +64,7 @@ public function __construct(RouteProviderInterface $route_provider, StateInterfa
    */
   public function onRequest(KernelEvent $event) {
     // Just preload on normal HTML pages, as they will display menu links.
-    if ($this->negotiation->getContentType($event->getRequest()) == 'html') {
+    if ($event->getRequest()->getRequestFormat() == 'html') {
       $this->loadNonAdminRoutes();
     }
   }
diff --git a/core/modules/rest/src/Plugin/views/display/RestExport.php b/core/modules/rest/src/Plugin/views/display/RestExport.php
index d4f3d34..b2b96e1 100644
--- a/core/modules/rest/src/Plugin/views/display/RestExport.php
+++ b/core/modules/rest/src/Plugin/views/display/RestExport.php
@@ -10,7 +10,6 @@
 use Drupal\Component\Utility\String;
 use Drupal\Core\State\StateInterface;
 use Drupal\Core\Routing\RouteProviderInterface;
-use Drupal\Core\ContentNegotiation;
 use Drupal\views\ViewExecutable;
 use Drupal\views\Plugin\views\display\PathPluginBase;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -72,13 +71,6 @@ class RestExport extends PathPluginBase {
   protected $mimeType;
 
   /**
-   * The content negotiation library.
-   *
-   * @var \Drupal\Core\ContentNegotiation
-   */
-  protected $contentNegotiation;
-
-  /**
    * Constructs a Drupal\rest\Plugin\ResourceBase object.
    *
    * @param array $configuration
@@ -91,12 +83,9 @@ class RestExport extends PathPluginBase {
    *   The route provider
    * @param \Drupal\Core\State\StateInterface $state
    *   The state key value store.
-   * @param \Drupal\Core\ContentNegotiation $content_negotiation
-   *   The content negotiation library.
    */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition, RouteProviderInterface $route_provider, StateInterface $state, ContentNegotiation $content_negotiation) {
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, RouteProviderInterface $route_provider, StateInterface $state) {
     parent::__construct($configuration, $plugin_id, $plugin_definition, $route_provider, $state);
-    $this->contentNegotiation = $content_negotiation;
   }
 
   /**
@@ -108,8 +97,7 @@ public static function create(ContainerInterface $container, array $configuratio
       $plugin_id,
       $plugin_definition,
       $container->get('router.route_provider'),
-      $container->get('state'),
-      $container->get('content_negotiation')
+      $container->get('state')
     );
   }
 
@@ -119,7 +107,7 @@ public static function create(ContainerInterface $container, array $configuratio
   public function initDisplay(ViewExecutable $view, array &$display, array &$options = NULL) {
     parent::initDisplay($view, $display, $options);
 
-    $request_content_type = $this->contentNegotiation->getContentType($this->view->getRequest());
+    $request_content_type = $this->view->getRequest()->getRequestFormat();
     // Only use the requested content type if it's not 'html'. If it is then
     // default to 'json' to aid debugging.
     // @todo Remove the need for this when we have better content negotiation.
diff --git a/core/modules/rest/tests/src/Unit/CollectRoutesTest.php b/core/modules/rest/tests/src/Unit/CollectRoutesTest.php
index 7a91831..7a2cbae 100644
--- a/core/modules/rest/tests/src/Unit/CollectRoutesTest.php
+++ b/core/modules/rest/tests/src/Unit/CollectRoutesTest.php
@@ -40,16 +40,10 @@ protected function setUp() {
 
     $container = new ContainerBuilder();
 
-    $content_negotiation = $this->getMockBuilder('\Drupal\Core\ContentNegotiation')
-      ->disableOriginalConstructor()
-      ->getMock();
-
     $request = $this->getMockBuilder('\Symfony\Component\HttpFoundation\Request')
       ->disableOriginalConstructor()
       ->getMock();
 
-    $container->set('content_negotiation', $content_negotiation);
-
     $this->view = $this->getMock('\Drupal\views\Entity\View', array('initHandlers'), array(
       array('id' => 'test_view'),
       'view',
diff --git a/core/tests/Drupal/Tests/Core/Controller/ExceptionControllerTest.php b/core/tests/Drupal/Tests/Core/Controller/ExceptionControllerTest.php
index 3a796ec..b661ff6 100644
--- a/core/tests/Drupal/Tests/Core/Controller/ExceptionControllerTest.php
+++ b/core/tests/Drupal/Tests/Core/Controller/ExceptionControllerTest.php
@@ -31,13 +31,11 @@ public function test405HTML() {
     $url_generator = $this->getMock('Drupal\Core\Routing\UrlGeneratorInterface');
     $logger_factory = $this->getMock('Drupal\Core\Logger\LoggerChannelFactoryInterface');
 
-    $content_negotiation = $this->getMock('Drupal\Core\ContentNegotiation');
-    $content_negotiation->expects($this->any())
-      ->method('getContentType')
-      ->will($this->returnValue('html'));
+    $request = new Request();
+    $request->setRequestFormat('html');
 
-    $exception_controller = new ExceptionController($content_negotiation, $title_resolver, $html_page_renderer, $html_fragment_renderer, $translation, $url_generator, $logger_factory);
-    $response = $exception_controller->execute($flat_exception, new Request());
+    $exception_controller = new ExceptionController( $title_resolver, $html_page_renderer, $html_fragment_renderer, $translation, $url_generator, $logger_factory);
+    $response = $exception_controller->execute($flat_exception, $request);
     $this->assertEquals($response->getStatusCode(), 405, 'HTTP status of response is correct.');
     $this->assertEquals($response->getContent(), 'Method Not Allowed', 'HTTP response body is correct.');
   }
diff --git a/core/tests/Drupal/Tests/Core/Routing/AcceptHeaderMatcherTest.php b/core/tests/Drupal/Tests/Core/Routing/AcceptHeaderMatcherTest.php
index 6f47816..8970664 100644
--- a/core/tests/Drupal/Tests/Core/Routing/AcceptHeaderMatcherTest.php
+++ b/core/tests/Drupal/Tests/Core/Routing/AcceptHeaderMatcherTest.php
@@ -7,10 +7,10 @@
 
 namespace Drupal\Tests\Core\Routing;
 
-use Drupal\Core\ContentNegotiation;
 use Drupal\Core\Routing\AcceptHeaderMatcher;
 use Drupal\Tests\Core\Routing\RoutingFixtures;
 use Drupal\Tests\UnitTestCase;
+use Negotiation\FormatNegotiator;
 use Symfony\Component\HttpFoundation\Request;
 
 /**
@@ -35,13 +35,21 @@ class AcceptHeaderMatcherTest extends UnitTestCase {
   protected $matcher;
 
   /**
+   * The content type negotiation library.
+   *
+   * @var \Negotiation\FormatNegotiator
+   */
+  protected $negotiator;
+
+  /**
    * {@inheritdoc}
    */
   protected function setUp() {
     parent::setUp();
 
     $this->fixtures = new RoutingFixtures();
-    $this->matcher = new AcceptHeaderMatcher(new ContentNegotiation());
+    $this->matcher = new AcceptHeaderMatcher();
+    $this->negotiator = new FormatNegotiator();
   }
 
   /**
@@ -77,6 +85,8 @@ public function testAcceptFiltering($accept_header, $included_route, $excluded_r
 
     $request = Request::create('path/two', 'GET');
     $request->headers->set('Accept', $accept_header);
+    $format = $this->negotiator->getBest($accept_header = $request->headers->get('Accept'));
+    $request->setRequestFormat($request->getFormat($format->getValue()));
     $routes = $this->matcher->filter($collection, $request);
     $this->assertEquals(count($routes), 4, 'The correct number of routes was found.');
     $this->assertNotNull($routes->get($included_route), "Route $included_route was found when matching $accept_header.");
@@ -103,6 +113,8 @@ public function testNoRouteFound() {
 
     $request = Request::create('path/two', 'GET');
     $request->headers->set('Accept', 'application/json, text/xml;q=0.9');
+    $format = $this->negotiator->getBest($accept_header = $request->headers->get('Accept'));
+    $request->setRequestFormat($request->getFormat($format->getValue()));$this->matcher->filter($routes, $request);
     $this->matcher->filter($routes, $request);
     $this->fail('No exception was thrown.');
   }
diff --git a/core/tests/Drupal/Tests/Core/Routing/RoutePreloaderTest.php b/core/tests/Drupal/Tests/Core/Routing/RoutePreloaderTest.php
index a928c97..bc1feac 100644
--- a/core/tests/Drupal/Tests/Core/Routing/RoutePreloaderTest.php
+++ b/core/tests/Drupal/Tests/Core/Routing/RoutePreloaderTest.php
@@ -35,13 +35,6 @@ class RoutePreloaderTest extends UnitTestCase {
   protected $state;
 
   /**
-   * The mocked content negotiator.
-   *
-   * @var \Drupal\Core\ContentNegotiation|\PHPUnit_Framework_MockObject_MockObject
-   */
-  protected $negotiation;
-
-  /**
    * The tested preloader.
    *
    * @var \Drupal\Core\Routing\RoutePreloader
@@ -54,10 +47,7 @@ class RoutePreloaderTest extends UnitTestCase {
   protected function setUp() {
     $this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface');
     $this->state = $this->getMock('\Drupal\Core\State\StateInterface');
-    $this->negotiation = $this->getMockBuilder('\Drupal\Core\ContentNegotiation')
-      ->disableOriginalConstructor()
-      ->getMock();
-    $this->preloader = new RoutePreloader($this->routeProvider, $this->state, $this->negotiation);
+    $this->preloader = new RoutePreloader($this->routeProvider, $this->state);
   }
 
   /**
@@ -136,12 +126,10 @@ public function testOnRequestNonHtml() {
       ->disableOriginalConstructor()
       ->getMock();
     $request = new Request();
+    $request->setRequestFormat('non-html');
     $event->expects($this->any())
       ->method('getRequest')
       ->will($this->returnValue($request));
-    $this->negotiation->expects($this->once())
-      ->method('getContentType')
-      ->will($this->returnValue('non-html'));
 
     $this->routeProvider->expects($this->never())
       ->method('getRoutesByNames');
@@ -159,12 +147,10 @@ public function testOnRequestOnHtml() {
       ->disableOriginalConstructor()
       ->getMock();
     $request = new Request();
+    $request->setRequestFormat('html');
     $event->expects($this->any())
       ->method('getRequest')
       ->will($this->returnValue($request));
-    $this->negotiation->expects($this->once())
-      ->method('getContentType')
-      ->will($this->returnValue('html'));
 
     $this->routeProvider->expects($this->once())
       ->method('getRoutesByNames')
diff --git a/core/vendor/composer/autoload_namespaces.php b/core/vendor/composer/autoload_namespaces.php
index 5bb8438..9330730 100644
--- a/core/vendor/composer/autoload_namespaces.php
+++ b/core/vendor/composer/autoload_namespaces.php
@@ -28,6 +28,7 @@
     'Symfony\\Cmf\\Component\\Routing' => array($vendorDir . '/symfony-cmf/routing'),
     'Stack' => array($vendorDir . '/stack/builder/src'),
     'Psr\\Log\\' => array($vendorDir . '/psr/log'),
+    'Negotiation' => array($vendorDir . '/willdurand/negotiation/src', $vendorDir . '/willdurand/stack-negotiation/src'),
     'Gliph' => array($vendorDir . '/sdboyer/gliph/src'),
     'EasyRdf_' => array($vendorDir . '/easyrdf/easyrdf/lib'),
     'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/lib'),
diff --git a/core/vendor/composer/installed.json b/core/vendor/composer/installed.json
index 5b56c2a..3b52e71 100644
--- a/core/vendor/composer/installed.json
+++ b/core/vendor/composer/installed.json
@@ -2564,5 +2564,107 @@
         "keywords": [
             "stack"
         ]
+    },
+    {
+        "name": "willdurand/negotiation",
+        "version": "1.3.3",
+        "version_normalized": "1.3.3.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/willdurand/Negotiation.git",
+            "reference": "a98fb6b9808610c1aa326c736893d3d77d9383b6"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/willdurand/Negotiation/zipball/a98fb6b9808610c1aa326c736893d3d77d9383b6",
+            "reference": "a98fb6b9808610c1aa326c736893d3d77d9383b6",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.3.0"
+        },
+        "time": "2014-05-16 12:34:51",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "1.3-dev"
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "psr-0": {
+                "Negotiation": "src/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "William Durand",
+                "email": "william.durand1@gmail.com",
+                "homepage": "http://www.willdurand.fr"
+            }
+        ],
+        "description": "Content Negotiation tools for PHP provided as a standalone library.",
+        "homepage": "http://williamdurand.fr/Negotiation/",
+        "keywords": [
+            "accept",
+            "content",
+            "format",
+            "header",
+            "negotiation"
+        ]
+    },
+    {
+        "name": "willdurand/stack-negotiation",
+        "version": "0.1.0",
+        "version_normalized": "0.1.0.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/willdurand/StackNegotiation.git",
+            "reference": "5e35c15b12f3fc6d4bc818f44d94bdf7c5c84fe0"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/willdurand/StackNegotiation/zipball/5e35c15b12f3fc6d4bc818f44d94bdf7c5c84fe0",
+            "reference": "5e35c15b12f3fc6d4bc818f44d94bdf7c5c84fe0",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.4.0",
+            "symfony/serializer": "~2.1",
+            "willdurand/negotiation": "~1.3"
+        },
+        "require-dev": {
+            "phpunit/phpunit": "~3.7",
+            "symfony/http-kernel": "~2.1"
+        },
+        "time": "2014-01-22 10:32:39",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "1.0-dev"
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "psr-0": {
+                "Negotiation": "src/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "William Durand",
+                "email": "william.durand1@gmail.com",
+                "homepage": "http://www.willdurand.fr"
+            }
+        ],
+        "description": "Stack middleware for content negotiation."
     }
 ]
diff --git a/core/vendor/willdurand/negotiation/.gitignore b/core/vendor/willdurand/negotiation/.gitignore
new file mode 100644
index 0000000..57872d0
--- /dev/null
+++ b/core/vendor/willdurand/negotiation/.gitignore
@@ -0,0 +1 @@
+/vendor/
diff --git a/core/vendor/willdurand/negotiation/.travis.yml b/core/vendor/willdurand/negotiation/.travis.yml
new file mode 100644
index 0000000..0253fd1
--- /dev/null
+++ b/core/vendor/willdurand/negotiation/.travis.yml
@@ -0,0 +1,18 @@
+language: php
+
+php:
+    - 5.3
+    - 5.4
+    - 5.5
+    - 5.6
+    - hhvm
+
+matrix:
+    allow_failures:
+        - php: hhvm
+
+before_script:
+    - composer self-update
+    - composer install --dev --prefer-dist --no-interaction
+
+script: phpunit --coverage-text
diff --git a/core/vendor/willdurand/negotiation/CONTRIBUTING.md b/core/vendor/willdurand/negotiation/CONTRIBUTING.md
new file mode 100644
index 0000000..3677a2c
--- /dev/null
+++ b/core/vendor/willdurand/negotiation/CONTRIBUTING.md
@@ -0,0 +1,33 @@
+Contributing
+============
+
+First of all, **thank you** for contributing, **you are awesome**!
+
+Here are a few rules to follow in order to ease code reviews, and discussions before
+maintainers accept and merge your work.
+
+You MUST follow the [PSR-1](http://www.php-fig.org/psr/1/) and
+[PSR-2](http://www.php-fig.org/psr/2/). If you don't know about any of them, you
+should really read the recommendations. Can't wait? Use the [PHP-CS-Fixer
+tool](http://cs.sensiolabs.org/).
+
+You MUST run the test suite.
+
+You MUST write (or update) unit tests.
+
+You SHOULD write documentation.
+
+Please, write [commit messages that make
+sense](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html),
+and [rebase your branch](http://git-scm.com/book/en/Git-Branching-Rebasing)
+before submitting your Pull Request.
+
+One may ask you to [squash your
+commits](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html)
+too. This is used to "clean" your Pull Request before merging it (we don't want
+commits such as `fix tests`, `fix 2`, `fix 3`, etc.).
+
+Also, while creating your Pull Request on GitHub, you MUST write a description
+which gives the context and/or explains why you are creating it.
+
+Thank you!
diff --git a/core/vendor/willdurand/negotiation/LICENSE b/core/vendor/willdurand/negotiation/LICENSE
new file mode 100644
index 0000000..a27c662
--- /dev/null
+++ b/core/vendor/willdurand/negotiation/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2013 William Durand <william.durand1@gmail.com>
+
+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.
diff --git a/core/vendor/willdurand/negotiation/README.md b/core/vendor/willdurand/negotiation/README.md
new file mode 100644
index 0000000..2e78516
--- /dev/null
+++ b/core/vendor/willdurand/negotiation/README.md
@@ -0,0 +1,162 @@
+Negotiation
+===========
+
+[![Build Status](https://travis-ci.org/willdurand/Negotiation.png?branch=master)](http://travis-ci.org/willdurand/Negotiation)
+[![Total Downloads](https://poser.pugx.org/willdurand/Negotiation/downloads.png)](https://packagist.org/packages/willdurand/Negotiation)
+[![Latest Stable Version](https://poser.pugx.org/willdurand/Negotiation/v/stable.png)](https://packagist.org/packages/willdurand/Negotiation)
+
+**Negotiation** is a standalone library without any dependencies that allows you
+to implement [content
+negotiation](http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html) in your
+application, whatever framework you use.
+This library is based on [RFC
+2616](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html). Negotiation is
+easy to use, and extensively unit tested.
+
+
+Installation
+------------
+
+The recommended way to install Negotiation is through
+[Composer](http://getcomposer.org/):
+
+``` json
+{
+    "require": {
+        "willdurand/negotiation": "@stable"
+    }
+}
+```
+
+**Protip:** you should browse the
+[`willdurand/negotiation`](https://packagist.org/packages/willdurand/negotiation)
+page to choose a stable version to use, avoid the `@stable` meta constraint.
+
+
+Usage
+-----
+
+In a nutshell:
+
+``` php
+<?php
+
+$negotiator = new \Negotiation\Negotiator();
+$bestHeader = $negotiator->getBest('en; q=0.1, fr; q=0.4, fu; q=0.9, de; q=0.2');
+// $bestHeader = 'fu';
+```
+
+The `getBest()` method, part of the `NegotiatorInterface`, returns either `null`
+or `AcceptHeader` instances. An `AcceptHeader` object owns a `value` and a
+`quality`.
+
+
+### Format Negotiation
+
+The **Format Negotiation** is handled by the `FormatNegotiator` class.
+Basically, pass an `Accept` header and optionally a set of preferred media types
+to the `getBest()` method in order to retrieve the best **media type**:
+
+``` php
+<?php
+
+$negotiator   = new \Negotiation\FormatNegotiator();
+
+$acceptHeader = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8';
+$priorities   = array('text/html', 'application/json', '*/*');
+
+$format = $negotiator->getBest($acceptHeader, $priorities);
+// $format->getValue() = text/html
+```
+
+The `FormatNegotiator` class also provides a `getBestFormat()` method that
+returns the best format given an `Accept` header string and a set of
+preferred/allowed formats or mime types:
+
+``` php
+<?php
+
+$negotiator   = new \Negotiation\FormatNegotiator();
+
+$acceptHeader = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8';
+$priorities   = array('html', 'application/json', '*/*');
+
+$format = $negotiator->getBestFormat($acceptHeader, $priorities);
+// $format = html
+```
+
+#### Other Methods
+
+* `registerFormat($format, array $mimeTypes, $override = false)`: registers a new
+  format with its mime types;
+* `getFormat($mimeType)`: returns the format for a given mime type, or null if
+not found;
+* `normalizePriorities($priorities)`: ensures that any formats are converted to
+  mime types.
+
+### Language Negotiation
+
+Language negotiation is handled by the `LanguageNegotiator` class:
+
+``` php
+<?php
+
+$negotiator = new \Negotiation\LanguageNegotiator();
+$language   = $negotiator->getBest('da, en-gb;q=0.8, en;q=0.7');
+// $language = da
+```
+
+
+### Charset/Encoding Negotiation
+
+Charset/Encoding negotiation works out of the box using the `Negotiator` class:
+
+``` php
+<?php
+
+$negotiator = new \Negotiation\Negotiator();
+$priorities = array(
+    'utf-8',
+    'big5',
+    'shift-jis',
+);
+
+$bestHeader = $negotiator->getBest('ISO-8859-1, Big5;q=0.6,utf-8;q=0.7, *;q=0.5', $priorities);
+// $bestHeader = 'utf-8'
+```
+
+
+Unit Tests
+----------
+
+Setup the test suite using Composer:
+
+    $ composer install --dev
+
+Run it using PHPUnit:
+
+    $ phpunit
+
+
+Contributing
+------------
+
+See CONTRIBUTING file.
+
+
+Credits
+-------
+
+* Some parts of this library are inspired by:
+
+    * [Symfony](http://github.com/symfony/symfony) framework;
+    * [FOSRest](http://github.com/FriendsOfSymfony/FOSRest);
+    * [PEAR HTTP2](https://github.com/pear/HTTP2).
+
+* William Durand <william.durand1@gmail.com>
+
+
+License
+-------
+
+Negotiation is released under the MIT License. See the bundled LICENSE file for details.
diff --git a/core/vendor/willdurand/negotiation/composer.json b/core/vendor/willdurand/negotiation/composer.json
new file mode 100644
index 0000000..5490534
--- /dev/null
+++ b/core/vendor/willdurand/negotiation/composer.json
@@ -0,0 +1,24 @@
+{
+    "name": "willdurand/negotiation",
+    "description": "Content Negotiation tools for PHP provided as a standalone library.",
+    "keywords": [ "content", "negotiation", "format", "accept", "header" ],
+    "license": "MIT",
+    "homepage": "http://williamdurand.fr/Negotiation/",
+    "authors": [
+        {
+            "name": "William Durand",
+            "email": "william.durand1@gmail.com"
+        }
+    ],
+    "require": {
+        "php": ">=5.3.0"
+    },
+    "autoload": {
+        "psr-0": { "Negotiation": "src/" }
+    },
+    "extra": {
+        "branch-alias": {
+            "dev-master": "1.3-dev"
+        }
+    }
+}
diff --git a/core/vendor/willdurand/negotiation/phpunit.xml.dist b/core/vendor/willdurand/negotiation/phpunit.xml.dist
new file mode 100644
index 0000000..ac6f0f9
--- /dev/null
+++ b/core/vendor/willdurand/negotiation/phpunit.xml.dist
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit backupGlobals="false"
+    backupStaticAttributes="false"
+    colors="true"
+    convertErrorsToExceptions="true"
+    convertNoticesToExceptions="true"
+    convertWarningsToExceptions="true"
+    processIsolation="false"
+    stopOnFailure="false"
+    syntaxCheck="false"
+    bootstrap="tests/bootstrap.php"
+    >
+    <testsuites>
+        <testsuite name="Negotiation Test Suite">
+            <directory>./tests/</directory>
+        </testsuite>
+    </testsuites>
+    <filter>
+        <whitelist>
+            <directory>./src/Negotiation/</directory>
+        </whitelist>
+    </filter>
+</phpunit>
diff --git a/core/vendor/willdurand/negotiation/src/Negotiation/AcceptHeader.php b/core/vendor/willdurand/negotiation/src/Negotiation/AcceptHeader.php
new file mode 100644
index 0000000..b05e391
--- /dev/null
+++ b/core/vendor/willdurand/negotiation/src/Negotiation/AcceptHeader.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace Negotiation;
+
+/**
+ * @author William Durand <william.durand1@gmail.com>
+ */
+class AcceptHeader
+{
+    /**
+     * @var string
+     */
+    private $value;
+
+    /**
+     * @var float
+     */
+    private $quality;
+
+    /**
+     * @var array
+     */
+    private $parameters;
+
+    /**
+     * @param string $value
+     * @param float  $quality
+     * @param array  $parameters
+     */
+    public function __construct($value, $quality, array $parameters = array())
+    {
+        $this->value      = $value;
+        $this->quality    = $quality;
+        $this->parameters = $parameters;
+    }
+
+    /**
+      @return string
+     */
+    public function getValue()
+    {
+        return $this->value;
+    }
+
+    /**
+     * @return float
+     */
+    public function getQuality()
+    {
+        return $this->quality;
+    }
+
+    /**
+     * @return array
+     */
+    public function getParameters()
+    {
+        return $this->parameters;
+    }
+
+    /**
+     * @param string $key
+     * @param mixed  $default
+     *
+     * @return string|null
+     */
+    public function getParameter($key, $default = null)
+    {
+        return $this->hasParameter($key) ? $this->parameters[$key] : $default;
+    }
+
+    /**
+     * @param string $key
+     *
+     * @return boolean
+     */
+    public function hasParameter($key)
+    {
+        return isset($this->parameters[$key]);
+    }
+
+    /**
+     * @return boolean
+     */
+    public function isMediaRange()
+    {
+        return false !== strpos($this->value, '*');
+    }
+}
diff --git a/core/vendor/willdurand/negotiation/src/Negotiation/FormatNegotiator.php b/core/vendor/willdurand/negotiation/src/Negotiation/FormatNegotiator.php
new file mode 100644
index 0000000..4fc68ae
--- /dev/null
+++ b/core/vendor/willdurand/negotiation/src/Negotiation/FormatNegotiator.php
@@ -0,0 +1,184 @@
+<?php
+
+namespace Negotiation;
+
+/**
+ * @author William Durand <william.durand1@gmail.com>
+ */
+class FormatNegotiator extends Negotiator implements FormatNegotiatorInterface
+{
+    // https://github.com/symfony/symfony/blob/master/src/Symfony/Component/HttpFoundation/Request.php
+    protected $formats = array(
+        'html' => array('text/html', 'application/xhtml+xml'),
+        'txt'  => array('text/plain'),
+        'js'   => array('application/javascript', 'application/x-javascript', 'text/javascript'),
+        'css'  => array('text/css'),
+        'json' => array('application/json', 'application/x-json'),
+        'xml'  => array('text/xml', 'application/xml', 'application/x-xml'),
+        'rdf'  => array('application/rdf+xml'),
+        'atom' => array('application/atom+xml'),
+        'rss'  => array('application/rss+xml'),
+    );
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getBest($header, array $priorities = array())
+    {
+        $acceptHeaders   = $this->parseHeader($header);
+        $priorities      = $this->sanitize($priorities);
+        $catchAllEnabled = $this->isCatchAllEnabled($priorities);
+        $catchAllHeader  = null;
+
+        foreach ($acceptHeaders as $accept) {
+            $mimeType = $accept->getValue();
+
+            if (self::CATCH_ALL_VALUE === $mimeType) {
+                $catchAllHeader = $accept;
+            }
+
+            if ('/*' !== substr($mimeType, -2)) {
+                if (in_array($mimeType, $priorities)) {
+                    return $accept;
+                }
+
+                $regex = '#^' . preg_quote($mimeType) . '#';
+
+                foreach ($priorities as $priority) {
+                    if (self::CATCH_ALL_VALUE !== $priority && 1 === preg_match($regex, $priority)) {
+                        return new AcceptHeader($priority, $accept->getQuality(), $this->parseParameters($priority));
+                    }
+                }
+
+                continue;
+            }
+
+            if (false === $catchAllEnabled &&
+                self::CATCH_ALL_VALUE === $mimeType &&
+                self::CATCH_ALL_VALUE !== $value = array_shift($priorities)
+            ) {
+                return new AcceptHeader($value, $accept->getQuality(), $this->parseParameters($value));
+            }
+
+            if (false === $pos = strpos($mimeType, ';')) {
+                $pos = strpos($mimeType, '/');
+            }
+
+            $regex = '#^' . preg_quote(substr($mimeType, 0, $pos)) . '/#';
+
+            foreach ($priorities as $priority) {
+                if (self::CATCH_ALL_VALUE !== $priority && 1 === preg_match($regex, $priority)) {
+                    return new AcceptHeader($priority, $accept->getQuality(), $this->parseParameters($priority));
+                }
+            }
+        }
+
+        // if client sends `*/*` in Accept header, and nothing has been negotiated before
+        // then, return the first priority value if available
+        if (null !== $catchAllHeader) {
+            $value = array_shift($priorities);
+
+            if (null !== $value && self::CATCH_ALL_VALUE !== $value) {
+                return new AcceptHeader(
+                    $value,
+                    $catchAllHeader->getQuality(),
+                    $this->parseParameters($catchAllHeader->getValue())
+                );
+            }
+        }
+
+        // if `$priorities` is empty or contains a catch-all mime type
+        if ($catchAllEnabled) {
+            return array_shift($acceptHeaders) ?: null;
+        }
+
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getBestFormat($acceptHeader, array $priorities = array())
+    {
+        $mimeTypes = $this->normalizePriorities($priorities);
+
+        if (null !== $accept = $this->getBest($acceptHeader, $mimeTypes)) {
+            if (0.0 < $accept->getQuality() &&
+                null !== $format = $this->getFormat($accept->getValue())
+            ) {
+                if (in_array($format, $priorities) || $this->isCatchAllEnabled($priorities)) {
+                    return $format;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function registerFormat($format, array $mimeTypes, $override = false)
+    {
+        if (isset($this->formats[$format]) && false === $override) {
+            throw new \InvalidArgumentException(sprintf(
+                'Format "%s" already registered, and override was set to "false".',
+                $format
+            ));
+        }
+
+        $this->formats[$format] = $mimeTypes;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getFormat($mimeType)
+    {
+        if (false !== $pos = strpos($mimeType, ';')) {
+            $mimeType = substr($mimeType, 0, $pos);
+        }
+
+        foreach ($this->formats as $format => $mimeTypes) {
+            if (in_array($mimeType, (array) $mimeTypes)) {
+                return $format;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function normalizePriorities($priorities)
+    {
+        $priorities = $this->sanitize($priorities);
+
+        $mimeTypes = array();
+        foreach ($priorities as $priority) {
+            if (strpos($priority, '/')) {
+                $mimeTypes[] = $priority;
+                continue;
+            }
+
+            if (isset($this->formats[$priority])) {
+                foreach ($this->formats[$priority] as $mimeType) {
+                    $mimeTypes[] = $mimeType;
+                }
+            }
+        }
+
+        return $mimeTypes;
+    }
+
+    /**
+     * @param array $priorities
+     *
+     * @return boolean
+     */
+    private function isCatchAllEnabled(array $priorities)
+    {
+        return 0 === count($priorities) || in_array(self::CATCH_ALL_VALUE, $priorities);
+    }
+}
diff --git a/core/vendor/willdurand/negotiation/src/Negotiation/FormatNegotiatorInterface.php b/core/vendor/willdurand/negotiation/src/Negotiation/FormatNegotiatorInterface.php
new file mode 100644
index 0000000..05a6dd2
--- /dev/null
+++ b/core/vendor/willdurand/negotiation/src/Negotiation/FormatNegotiatorInterface.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Negotiation;
+
+/**
+ * @author William Durand <william.durand1@gmail.com>
+ */
+interface FormatNegotiatorInterface extends NegotiatorInterface
+{
+    /**
+     * Return the best format (as a string) based on a given `Accept` header,
+     * and a set of priorities. Priorities are "formats" such as `json`, `xml`,
+     * etc. or "mime types" such as `application/json`, `application/xml`, etc.
+     *
+     * @param string $acceptHeader A string containing an `Accept` header.
+     * @param array  $priorities   A set of priorities.
+     *
+     * @return string
+     */
+    public function getBestFormat($acceptHeader, array $priorities = array());
+
+    /**
+     * Register a new format with its mime types.
+     *
+     * @param string  $format
+     * @param array   $mimeTypes
+     * @param boolean $override
+     *
+     * @return void
+     */
+    public function registerFormat($format, array $mimeTypes, $override = false);
+
+    /**
+     * Return the format for a given mime type, or null
+     * if not found.
+     *
+     * @param string $mimeType
+     *
+     * @return string|null
+     */
+    public function getFormat($mimeType);
+
+    /**
+     * Ensure that any formats are converted to mime types.
+     *
+     * @param array $priorities
+     *
+     * @return array
+     */
+    public function normalizePriorities($priorities);
+}
diff --git a/core/vendor/willdurand/negotiation/src/Negotiation/LanguageNegotiator.php b/core/vendor/willdurand/negotiation/src/Negotiation/LanguageNegotiator.php
new file mode 100644
index 0000000..bd3e53d
--- /dev/null
+++ b/core/vendor/willdurand/negotiation/src/Negotiation/LanguageNegotiator.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace Negotiation;
+
+/**
+ * @author William Durand <william.durand1@gmail.com>
+ */
+class LanguageNegotiator extends Negotiator
+{
+    /**
+     * {@inheritDoc}
+     */
+    protected function parseHeader($header)
+    {
+        $acceptHeaders = array();
+
+        $header      = preg_replace('/\s+/', '', $header);
+        $acceptParts = array();
+
+        preg_match_all(
+            '/(?<=[, ]|^)([a-zA-Z-]+|\*)(?:;q=([0-9.]+))?(?:$|\s*,\s*)/i',
+            $header,
+            $acceptParts,
+            PREG_SET_ORDER
+        );
+
+        $index    = 0;
+        $catchAll = null;
+        foreach ($acceptParts as $acceptPart) {
+            $value   = $acceptPart[1];
+            $quality = isset($acceptPart[2]) ? (float) $acceptPart[2] : 1.0;
+
+            if ('*' === $value) {
+                $catchAll = new AcceptHeader($value, $quality);
+            } else {
+                $acceptHeaders[] = array(
+                    'item'  => new AcceptHeader($value, $quality),
+                    'index' => $index
+                );
+            }
+
+            $index++;
+        }
+
+        return $this->sortAcceptHeaders($acceptHeaders, $catchAll);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected function match(array $acceptHeaders, array $priorities = array())
+    {
+        $wildcardAccept  = null;
+
+        $prioritiesSet   = array();
+        $prioritiesSet[] = $priorities;
+        $prioritiesSet[] = array_map(function ($priority) {
+            return strtok($priority, '-');
+        }, $priorities);
+
+        foreach ($acceptHeaders as $accept) {
+            foreach ($prioritiesSet as $availablePriorities) {
+                $sanitizedPriorities = $this->sanitize($availablePriorities);
+
+                if (false !== $found = array_search(strtolower($accept->getValue()), $sanitizedPriorities)) {
+                    return $priorities[$found];
+                }
+
+                if ('*' === $accept->getValue()) {
+                    $wildcardAccept = $accept;
+                }
+            }
+        }
+
+        if (null !== $wildcardAccept) {
+            return reset($priorities);
+        }
+    }
+}
diff --git a/core/vendor/willdurand/negotiation/src/Negotiation/Negotiator.php b/core/vendor/willdurand/negotiation/src/Negotiation/Negotiator.php
new file mode 100644
index 0000000..0d01c2e
--- /dev/null
+++ b/core/vendor/willdurand/negotiation/src/Negotiation/Negotiator.php
@@ -0,0 +1,186 @@
+<?php
+
+namespace Negotiation;
+
+/**
+ * @author William Durand <william.durand1@gmail.com>
+ */
+class Negotiator implements NegotiatorInterface
+{
+    const CATCH_ALL_VALUE = '*/*';
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getBest($header, array $priorities = array())
+    {
+        $acceptHeaders = $this->parseHeader($header);
+        $best          = reset($acceptHeaders);
+
+        if (0 === count($acceptHeaders)) {
+            return null;
+        }
+
+        if (0 !== count($priorities)) {
+            $value = $this->match($acceptHeaders, $priorities);
+
+            if (!empty($value)) {
+                $best = new AcceptHeader($value, 1.0, $this->parseParameters($value));
+            }
+        }
+
+        return $best;
+    }
+
+    /**
+     * @param string $header A string that contains an `Accept|Accept-*` header.
+     *
+     * @return array[AcceptHeader]
+     */
+    protected function parseHeader($header)
+    {
+        $acceptHeaders = array();
+
+        $header      = preg_replace('/\s+/', '', $header);
+        $acceptParts = preg_split('/\s*(?:,*("[^"]+"),*|,*(\'[^\']+\'),*|,+)\s*/',
+            $header, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE
+        );
+
+        $index    = 0;
+        $catchAll = null;
+        foreach ($acceptParts as $acceptPart) {
+            $quality    = 1.0;
+            $parts      = preg_split('/;\s*q=/i', $acceptPart, 0, PREG_SPLIT_NO_EMPTY);
+            $parameters = $this->parseParameters($acceptPart);
+
+            if (2 === count($parts)) {
+                $value   = $parts[0];
+                $quality = (float) $parts[1];
+            } else {
+                $value = $acceptPart;
+
+                if (self::CATCH_ALL_VALUE === $value) {
+                    $quality = 0.01;
+                } elseif ('*' === substr($value, -1)) {
+                    $quality = 0.02;
+                }
+            }
+
+            if (self::CATCH_ALL_VALUE === $value) {
+                $catchAll = new AcceptHeader($value, $quality, $parameters);
+            } else {
+                $acceptHeaders[] = array(
+                    'item'  => new AcceptHeader($value, $quality, $parameters),
+                    'index' => $index
+                );
+            }
+
+            $index++;
+        }
+
+        return $this->sortAcceptHeaders($acceptHeaders, $catchAll);
+    }
+
+    /**
+     * @param array        $acceptHeaders A set of AcceptHeader objects to sort.
+     * @param AcceptHeader $catchAll      A special AcceptHeader that represents the "catch all".
+     *
+     * @return array[AcceptHeader]
+     */
+    protected function sortAcceptHeaders(array $acceptHeaders, AcceptHeader $catchAll = null)
+    {
+        uasort($acceptHeaders, function ($a, $b) {
+            $qA = $a['item']->getQuality();
+            $qB = $b['item']->getQuality();
+
+            $vA = $a['item']->getValue();
+            $vB = $b['item']->getValue();
+
+            // put specific media type before the classic one
+            // e.g. `text/html;level=1` first, then `text/html`
+            if (strstr($vA, $vB)) {
+                return -1;
+            }
+
+            if ($qA === $qB) {
+                return $a['index'] > $b['index'] ? 1 : -1;
+            }
+
+            return $qA > $qB ? -1 : 1;
+        });
+
+        // put the catch all header at the end if available
+        if (null !== $catchAll) {
+            array_push($acceptHeaders, array('item' => $catchAll));
+        }
+
+        return array_map(function ($accept) {
+            return $accept['item'];
+        }, array_values($acceptHeaders));
+    }
+
+    /**
+     * @param array $values
+     *
+     * @return array
+     */
+    protected function sanitize(array $values)
+    {
+        return array_map(function ($value) {
+            return preg_replace('/\s+/', '', strtolower($value));
+        }, $values);
+    }
+
+    /**
+     * @param string $value
+     *
+     * @return array
+     */
+    protected function parseParameters($value)
+    {
+        $parts = explode(';', preg_replace('/\s+/', '', $value));
+        array_shift($parts);
+
+        $parameters = array();
+        foreach ($parts as $part) {
+            $part = explode('=', $part);
+
+            if (2 !== count($part)) {
+                continue;
+            }
+
+            if ('q' !== $key = strtolower($part[0])) {
+                $parameters[$key] = $part[1];
+            }
+        }
+
+        return $parameters;
+    }
+
+    /**
+     * @param AcceptHeader[] $acceptHeaders Sorted by quality
+     * @param array          $priorities    Configured priorities
+     *
+     * @return string|null Header string matched
+     */
+    protected function match(array $acceptHeaders, array $priorities  = array())
+    {
+        $sanitizedPriorities = $this->sanitize($priorities);
+
+        foreach ($acceptHeaders as $accept) {
+            if (false !== $found = array_search(strtolower($accept->getValue()), $sanitizedPriorities)) {
+                return $priorities[$found];
+            }
+
+            if ('*' === $accept->getValue()) {
+                $wildcardAccept = $accept;
+            }
+        }
+
+        if (null !== $wildcardAccept) {
+            return reset($priorities);
+        }
+
+        return null;
+    }
+}
diff --git a/core/vendor/willdurand/negotiation/src/Negotiation/NegotiatorInterface.php b/core/vendor/willdurand/negotiation/src/Negotiation/NegotiatorInterface.php
new file mode 100644
index 0000000..bee4fef
--- /dev/null
+++ b/core/vendor/willdurand/negotiation/src/Negotiation/NegotiatorInterface.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Negotiation;
+
+/**
+ * @author William Durand <william.durand1@gmail.com>
+ */
+interface NegotiatorInterface
+{
+    /**
+     * @param string $header     A string containing an `Accept|Accept-*` header.
+     * @param array  $priorities A set of priorities.
+     *
+     * @return AcceptHeader
+     */
+    public function getBest($header, array $priorities = array());
+}
diff --git a/core/vendor/willdurand/negotiation/tests/Negotiation/Tests/AcceptHeaderTest.php b/core/vendor/willdurand/negotiation/tests/Negotiation/Tests/AcceptHeaderTest.php
new file mode 100644
index 0000000..be7035b
--- /dev/null
+++ b/core/vendor/willdurand/negotiation/tests/Negotiation/Tests/AcceptHeaderTest.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Negotiation\Tests;
+
+use Negotiation\AcceptHeader;
+
+class AcceptHeaderTest extends TestCase
+{
+    private $acceptHeader;
+
+    protected function setUp()
+    {
+        $this->acceptHeader = new AcceptHeader('foo', 1.0, array(
+            'hello' => 'world',
+        ));
+    }
+
+    public function testGetParameter()
+    {
+        $this->assertTrue($this->acceptHeader->hasParameter('hello'));
+        $this->assertEquals('world', $this->acceptHeader->getParameter('hello'));
+
+        $this->assertFalse($this->acceptHeader->hasParameter('unknown'));
+        $this->assertNull($this->acceptHeader->getParameter('unknown'));
+        $this->assertFalse($this->acceptHeader->getParameter('unknown', false));
+    }
+
+    /**
+     * @dataProvider dataProviderForTestIsMediaRange
+     */
+    public function testIsMediaRange($value, $expected)
+    {
+        $header = new AcceptHeader($value, 1.0);
+
+        $this->assertEquals($expected, $header->isMediaRange());
+    }
+
+    public static function dataProviderForTestIsMediaRange()
+    {
+        return array(
+            array('text/*', true),
+            array('*/*', true),
+            array('application/json', false),
+        );
+    }
+}
diff --git a/core/vendor/willdurand/negotiation/tests/Negotiation/Tests/FormatNegotiatorTest.php b/core/vendor/willdurand/negotiation/tests/Negotiation/Tests/FormatNegotiatorTest.php
new file mode 100644
index 0000000..d637588
--- /dev/null
+++ b/core/vendor/willdurand/negotiation/tests/Negotiation/Tests/FormatNegotiatorTest.php
@@ -0,0 +1,363 @@
+<?php
+
+namespace Negotiation\Tests;
+
+use Negotiation\FormatNegotiator;
+
+/**
+ * @author William Durand <william.durand1@gmail.com>
+ */
+class FormatNegotiatorTest extends TestCase
+{
+    private $negotiator;
+
+    protected function setUp()
+    {
+        $this->negotiator = new FormatNegotiator();
+    }
+
+    /**
+     * @dataProvider dataProviderForGetBest
+     */
+    public function testGetBest($acceptHeader, $priorities, $expected)
+    {
+        $acceptHeader = $this->negotiator->getBest($acceptHeader, $priorities);
+
+        if (null === $expected) {
+            $this->assertNull($acceptHeader);
+        } else {
+            $this->assertNotNull($acceptHeader);
+            if (is_array($expected)) {
+                $this->assertEquals($expected['value'],   $acceptHeader->getValue());
+                $this->assertEquals($expected['quality'], $acceptHeader->getQuality());
+
+                if (isset($expected['parameters'])) {
+                    foreach ($expected['parameters'] as $key => $value) {
+                        $this->assertTrue($acceptHeader->hasParameter($key));
+                        $this->assertEquals($value, $acceptHeader->getParameter($key));
+                    }
+
+                    $this->assertCount(count($expected['parameters']), $acceptHeader->getParameters());
+                }
+            } else {
+                $this->assertEquals($expected, $acceptHeader->getValue());
+            }
+        }
+    }
+
+    /**
+     * @dataProvider dataProviderForGetBestFormat
+     */
+    public function testGetBestFormat($acceptHeader, $priorities, $expected)
+    {
+        $bestFormat = $this->negotiator->getBestFormat($acceptHeader, $priorities);
+
+        $this->assertEquals($expected, $bestFormat);
+    }
+
+    public static function dataProviderForGetBest()
+    {
+        $pearAcceptHeader = 'text/html,application/xhtml+xml,application/xml;q=0.9,text/*;q=0.7,*/*,image/gif; q=0.8, image/jpeg; q=0.6, image/*';
+
+        return array(
+            // PEAR HTTP2 tests
+            array(
+                $pearAcceptHeader,
+                array(
+                    'image/gif',
+                    'image/png',
+                    'application/xhtml+xml',
+                    'application/xml',
+                    'text/html',
+                    'image/jpeg',
+                    'text/plain',
+                ),
+                'text/html'
+            ),
+            array(
+                $pearAcceptHeader,
+                array(
+                    'image/gif',
+                    'image/png',
+                    'application/xhtml+xml',
+                    'application/xml',
+                    'image/jpeg',
+                    'text/plain',
+                ),
+                'application/xhtml+xml'
+            ),
+            array(
+                $pearAcceptHeader,
+                array(
+                    'image/gif',
+                    'image/png',
+                    'application/xml',
+                    'image/jpeg',
+                    'text/plain',
+                ),
+                'application/xml'
+            ),
+            array(
+                $pearAcceptHeader,
+                array(
+                    'image/gif',
+                    'image/png',
+                    'image/jpeg',
+                    'text/plain',
+                ),
+                'image/gif'
+            ),
+            array(
+                $pearAcceptHeader,
+                array(
+                    'image/png',
+                    'image/jpeg',
+                    'text/plain',
+                ),
+                'text/plain'
+            ),
+            array(
+                $pearAcceptHeader,
+                array(
+                    'image/png',
+                    'image/jpeg',
+                ),
+                'image/jpeg'
+            ),
+            array(
+                $pearAcceptHeader,
+                array(
+                    'image/png',
+                ),
+                'image/png'
+            ),
+            array(
+                $pearAcceptHeader,
+                array(
+                    'audio/midi',
+                ),
+                'audio/midi'
+            ),
+            array(
+                'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
+                array(
+                    'application/rss+xml',
+                    '*/*',
+                ),
+                'application/rss+xml'
+            ),
+            // See: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
+            array(
+                'text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5',
+                array(),
+                array(
+                    'value'   => 'text/html;level=1',
+                    'quality' => 1,
+                )
+            ),
+            array(
+                'text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5',
+                array(
+                    'text/html'
+                ),
+                array(
+                    'value'   => 'text/html',
+                    'quality' => 0.7,
+                )
+            ),
+            array(
+                'text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5',
+                array(
+                    'text/plain'
+                ),
+                array(
+                    'value'   => 'text/plain',
+                    'quality' => 0.3,
+                )
+            ),
+            array(
+                'text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5',
+                array(
+                    'image/jpeg',
+                ),
+                array(
+                    'value'   => 'image/jpeg',
+                    'quality' => 0.5,
+                )
+            ),
+            array(
+                'text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5',
+                array(
+                    'text/html;level=2'
+                ),
+                array(
+                    'value'   => 'text/html;level=2',
+                    'quality' => 0.4,
+                )
+            ),
+            array(
+                'text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5',
+                array(
+                    'text/html;level=3'
+                ),
+                array(
+                    'value'      => 'text/html;level=3',
+                    'quality'    => 0.7,
+                    'parameters' => array(
+                        'level' => 3,
+                    ),
+                )
+            ),
+            // LWS / case sensitivity
+            array(
+                'text/* ; q=0.3, text/html ;Q=0.7, text/html ; level=1, text/html ;level = 2 ;q=0.4, */* ; q=0.5',
+                array(
+                    'text/html; level=2'
+                ),
+                array(
+                    'value'      => 'text/html;level=2',
+                    'quality'    => 0.4,
+                    'parameters' => array(
+                        'level' => 2,
+                    ),
+                )
+            ),
+            array(
+                'text/* ; q=0.3, text/html;Q=0.7, text/html ;level=1, text/html; level=2;q=0.4, */*;q=0.5',
+                array(
+                    'text/html; level=3'
+                ),
+                array(
+                    'value'      => 'text/html;level=3',
+                    'quality'    => 0.7,
+                    'parameters' => array(
+                        'level' => 3,
+                    ),
+                )
+            ),
+            array(
+                '*/*',
+                array(),
+                array(
+                    'value'      => '*/*',
+                    'quality'    => 0.01,
+                    'parameters' => array(),
+                ),
+            ),
+            // Incompatible
+            array(
+                'text/html',
+                array(
+                    'application/rss'
+                ),
+                null
+            ),
+            array(
+                'text/rdf+n3; q=0.8, application/rdf+json; q=0.8, text/turtle; q=1.0, text/n3; q=0.8, application/ld+json; q=0.5, application/rdf+xml; q=0.8',
+                array(),
+                'text/turtle'
+            ),
+            array(
+                'application/rdf+xml;q=0.5,text/html;q=.3',
+                array(),
+                'application/rdf+xml'
+            ),
+            array(
+                'application/xhtml+xml;q=0.5',
+                array(),
+                'application/xhtml+xml'
+            ),
+            array(
+                'application/rdf+xml;q=0.5,text/html;q=.5',
+                array(),
+                'application/rdf+xml'
+            ),
+            array(
+                'text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c',
+                array(),
+                'text/html',
+            ),
+            // IE8 Accept header
+            array(
+                'image/jpeg, application/x-ms-application, image/gif, application/xaml+xml, image/pjpeg, application/x-ms-xbap, */*',
+                array(
+                    'text/html',
+                    'application/xhtml+xml',
+                    '*/*'
+                ),
+                'text/html',
+            ),
+        );
+    }
+
+    public static function dataProviderForGetBestFormat()
+    {
+        return array(
+            array(null, array('html', 'json', '*/*'), null),
+            array('text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', array(), 'html'),
+            array('text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', array('html', 'json', '*/*'), 'html'),
+            array('text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', array('html', 'json', '*/*'), 'html'),
+            array('text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', array('rss', '*/*'), 'rss'),
+            array('text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', array('xml'), 'xml'),
+            // This shows clearly that the acceptheader is leading over server priorities
+            array('text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', array('xml', 'html'), 'html'),
+            array('text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', array('json', 'xml'), 'xml'),
+            array('text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', array('json'), 'json'),
+            array('text/html,application/xhtml+xml,application/xml;q=0.9,*/*', array('json'), 'json'),
+            array('text/html,application/xhtml+xml,application/xml;q=0.9,*/*', array('json'), 'json'),
+            array('text/html,application/xhtml+xml,application/xml', array('json'), null),
+            array('text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c', array('*/*'), 'html'),
+            array('text/html, application/json;q=0.8, text/csv;q=0.7', array(), 'html'),
+            array('text/html', array('text/xml'), null),
+            array('text/*, text/html, text/html;level=1, */*', array(), 'html'),
+            array('text/html; q=0.0', array(), null),
+        );
+    }
+
+    public function testGetFormat()
+    {
+        $this->assertEquals('html', $this->negotiator->getFormat('application/xhtml+xml'));
+    }
+
+    public function testGetFormatReturnsNullIfNotFound()
+    {
+        $this->assertNull($this->negotiator->getFormat('foo'));
+    }
+
+    public function testRegisterFormat()
+    {
+        $format   = 'foo';
+        $mimeType = 'foo/bar';
+
+        $this->negotiator->registerFormat($format, array($mimeType));
+        $this->assertEquals($format, $this->negotiator->getFormat($mimeType));
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     * @expectedExceptionMessage Format "html" already registered, and override was set to "false".
+     */
+    public function testRegisterFormatWithExistingFormat()
+    {
+        $this->negotiator->registerFormat('html', array());
+    }
+
+    /**
+     * @dataProvider dataProviderForNormalizePriorities
+     */
+    public function testNormalizePriorities($priorities, $expected)
+    {
+        $priorities = $this->negotiator->normalizePriorities($priorities);
+
+        $this->assertEquals($expected, $priorities);
+    }
+
+    public static function dataProviderForNormalizePriorities()
+    {
+        return array(
+            array(array('application/json', 'application/xml'), array('application/json', 'application/xml')),
+            array(array('json', 'application/xml', 'text/*', 'rdf', '*/*'), array('application/json', 'application/x-json', 'application/xml', 'text/*', 'application/rdf+xml', '*/*')),
+            array(array('json', 'html', '*/*'), array('application/json', 'application/x-json', 'text/html', 'application/xhtml+xml', '*/*')),
+        );
+    }
+}
diff --git a/core/vendor/willdurand/negotiation/tests/Negotiation/Tests/LanguageNegotiatorTest.php b/core/vendor/willdurand/negotiation/tests/Negotiation/Tests/LanguageNegotiatorTest.php
new file mode 100644
index 0000000..3a4efb8
--- /dev/null
+++ b/core/vendor/willdurand/negotiation/tests/Negotiation/Tests/LanguageNegotiatorTest.php
@@ -0,0 +1,110 @@
+<?php
+
+namespace Negotiation\Tests;
+
+use Negotiation\LanguageNegotiator;
+
+class LanguageNegotiatorTest extends TestCase
+{
+    private $negotiator;
+
+    protected function setUp()
+    {
+        $this->negotiator = new LanguageNegotiator();
+    }
+
+    /**
+     * 'fu' has a quality rating of 0.9 which is higher than the rest
+     * we expect Negotiator to return the 'fu' content.
+     *
+     * See: http://svn.apache.org/repos/asf/httpd/test/framework/trunk/t/modules/negotiation.t
+     */
+    public function testGetBestUsesQuality()
+    {
+        $acceptLanguageHeader = 'en; q=0.1, fr; q=0.4, fu; q=0.9, de; q=0.2';
+        $acceptHeader = $this->negotiator->getBest($acceptLanguageHeader);
+
+        $this->assertInstanceOf('Negotiation\AcceptHeader', $acceptHeader);
+        $this->assertEquals('fu', $acceptHeader->getValue());
+    }
+
+    /**
+     * 'bu' has the highest quality rating, but is non-existent,
+     * so we expect the next highest rated 'fr' content to be returned.
+     *
+     * See: http://svn.apache.org/repos/asf/httpd/test/framework/trunk/t/modules/negotiation.t
+     */
+    public function testGetBestIgnoresNonExistentContent()
+    {
+        $acceptLanguageHeader = 'en; q=0.1, fr; q=0.4, bu; q=1.0';
+        $acceptHeader = $this->negotiator->getBest($acceptLanguageHeader, array('en', 'fr'));
+
+        $this->assertInstanceOf('Negotiation\AcceptHeader', $acceptHeader);
+        $this->assertEquals('fr', $acceptHeader->getValue());
+    }
+
+    /**
+     * @dataProvider dataProviderForGetBest
+     */
+    public function testGetBest($acceptLanguageHeader, $expected)
+    {
+        $acceptHeader = $this->negotiator->getBest($acceptLanguageHeader);
+
+        if (null === $expected) {
+            $this->assertNull($acceptHeader);
+        } else {
+            $this->assertInstanceOf('Negotiation\AcceptHeader', $acceptHeader);
+            $this->assertEquals($expected, $acceptHeader->getValue());
+        }
+    }
+
+    /**
+     * Given a accept header containing a generic language (here 'en')
+     *  And priorities containing a localized version of that language
+     * Then the best language is mapped to 'en'
+     */
+    public function testGenericLanguageAreMappedToSpecific()
+    {
+        $acceptLanguageHeader = 'fr-FR, en;q=0.8';
+        $priorities           = array('en-US', 'de-DE');
+
+        $acceptHeader = $this->negotiator->getBest($acceptLanguageHeader, $priorities);
+
+        $this->assertInstanceOf('Negotiation\AcceptHeader', $acceptHeader);
+        $this->assertEquals('en-US', $acceptHeader->getValue());
+    }
+
+    public function testGetBestWithWildcard()
+    {
+        $acceptLanguageHeader = 'en, *;q=0.9';
+        $priorities           = array('fr');
+
+        $acceptHeader = $this->negotiator->getBest($acceptLanguageHeader, $priorities);
+
+        $this->assertInstanceOf('Negotiation\AcceptHeader', $acceptHeader);
+        $this->assertEquals('fr', $acceptHeader->getValue());
+    }
+
+    public function testGetBestDoesNotMatchPriorities()
+    {
+        $acceptLanguageHeader = 'en, de';
+        $priorities           = array('fr');
+
+        $acceptHeader = $this->negotiator->getBest($acceptLanguageHeader, $priorities);
+
+        $this->assertInstanceOf('Negotiation\AcceptHeader', $acceptHeader);
+        $this->assertEquals('en', $acceptHeader->getValue());
+    }
+
+    public static function dataProviderForGetBest()
+    {
+        return array(
+            array('da, en-gb;q=0.8, en;q=0.7', 'da'),
+            array('da, en-gb;q=0.8, en;q=0.7, *', 'da'),
+            array('es-ES;q=0.7, es; q=0.6 ,fr; q=1.0, en; q=0.5,dk , fr-CH', 'fr-CH'),
+            array('fr-FR,fr;q=0.1,en-US;q=0.6,en;q=0.4', 'fr-FR'),
+            array('', null),
+            array(null, null),
+        );
+    }
+}
diff --git a/core/vendor/willdurand/negotiation/tests/Negotiation/Tests/NegotiatorTest.php b/core/vendor/willdurand/negotiation/tests/Negotiation/Tests/NegotiatorTest.php
new file mode 100644
index 0000000..514e337
--- /dev/null
+++ b/core/vendor/willdurand/negotiation/tests/Negotiation/Tests/NegotiatorTest.php
@@ -0,0 +1,325 @@
+<?php
+
+namespace Negotiation\Tests;
+
+use Negotiation\Negotiator;
+
+/**
+ * @author William Durand <william.durand1@gmail.com>
+ */
+class NegotiatorTest extends TestCase
+{
+    private $negotiator;
+
+    protected function setUp()
+    {
+        $this->negotiator = new Negotiator();
+    }
+
+    public function testGetBestReturnsNullWithNullHeader()
+    {
+        $this->assertNull($this->negotiator->getBest(null));
+    }
+
+    public function testGetBestReturnsNullWithEmptyHeader()
+    {
+        $this->assertNull($this->negotiator->getBest(''));
+    }
+
+    public function testGetBestRespectsPriorities()
+    {
+        $acceptHeader = $this->negotiator->getBest('foo, bar, yo', array('yo'));
+
+        $this->assertInstanceOf('Negotiation\AcceptHeader', $acceptHeader);
+        $this->assertEquals('yo', $acceptHeader->getValue());
+    }
+
+    public function testGetBestInCaseInsensitive()
+    {
+        $acceptHeader = $this->negotiator->getBest('foo, bar, yo', array('YO'));
+
+        $this->assertInstanceOf('Negotiation\AcceptHeader', $acceptHeader);
+        $this->assertEquals('YO', $acceptHeader->getValue());
+    }
+
+    public function testGetBestWithQualities()
+    {
+        $acceptHeader = $this->negotiator->getBest('foo;q=0.1, bar, yo;q=0.9');
+
+        $this->assertInstanceOf('Negotiation\AcceptHeader', $acceptHeader);
+        $this->assertEquals('bar', $acceptHeader->getValue());
+        $this->assertFalse($acceptHeader->hasParameter('q'));
+    }
+
+    /**
+     * @dataProvider dataProviderForTestGetBest
+     */
+    public function testGetBest($acceptHeader, $priorities, $expected, $parameters = array())
+    {
+        $acceptHeader = $this->negotiator->getBest($acceptHeader, $priorities);
+
+        if (null === $expected) {
+            $this->assertNull($acceptHeader);
+        } else {
+            $this->assertEquals($expected, $acceptHeader->getValue());
+
+            foreach ($parameters as $k => $v) {
+                $this->assertEquals($v, $acceptHeader->getParameter($k));
+            }
+        }
+    }
+
+    /**
+     * @dataProvider dataProviderForTestParseAcceptHeader
+     */
+    public function testParseAcceptHeader($header, $expected)
+    {
+        $negotiator = new TestableNegotiator();
+        $accepts    = $negotiator->parseHeader($header);
+
+        $this->assertCount(count($expected), $accepts);
+        $this->assertEquals($expected, array_map(function ($result) {
+            return $result->getValue();
+        }, $accepts));
+    }
+
+    /**
+     * @dataProvider dataProviderForTestParseAcceptHeaderWithQualities
+     */
+    public function testParseAcceptHeaderWithQualities($header, $expected)
+    {
+        $negotiator = new TestableNegotiator();
+        $accepts    = $negotiator->parseHeader($header);
+
+        $this->assertEquals(count($expected), count($accepts));
+
+        $i = 0;
+        foreach ($expected as $value => $quality) {
+            $this->assertEquals($value, $accepts[$i]->getValue());
+            $this->assertEquals($quality, $accepts[$i]->getQuality());
+            $i++;
+        }
+    }
+
+    /**
+     * @dataProvider dataProviderForTestParseAcceptHeaderEnsuresPrecedence
+     */
+    public function testParseAcceptHeaderEnsuresPrecedence($header, $expected)
+    {
+        $negotiator = new TestableNegotiator();
+        $accepts    = $negotiator->parseHeader($header);
+
+        $this->assertCount(count($expected), $accepts);
+
+        $i = 0;
+        foreach ($expected as $value => $quality) {
+            $this->assertEquals($value,   $accepts[$i]->getValue());
+            $this->assertEquals($quality, $accepts[$i]->getQuality());
+
+            $i++;
+        }
+    }
+
+    /**
+     * @dataProvider dataProviderForParseParameters
+     */
+    public function testParseParameters($value, $expected)
+    {
+        $negotiator = new TestableNegotiator();
+        $parameters = $negotiator->parseParameters($value);
+
+        $this->assertCount(count($expected), $parameters);
+
+        foreach ($expected as $key => $value) {
+            $this->assertArrayHasKey($key, $parameters);
+            $this->assertEquals($value, $parameters[$key]);
+        }
+    }
+
+    public static function dataProviderForTestParseAcceptHeader()
+    {
+        return array(
+            array('gzip,deflate,sdch', array('gzip', 'deflate', 'sdch')),
+            array("gzip, deflate\t,sdch", array('gzip', 'deflate', 'sdch')),
+            array('"this;should,not=matter"', array('"this;should,not=matter"')),
+            array('*;q=0.3,ISO-8859-1,utf-8;q=0.7', array('ISO-8859-1', 'utf-8', '*')),
+            array('*;q=0.3,ISO-8859-1;q=0.7,utf-8;q=0.7',  array('ISO-8859-1', 'utf-8', '*')),
+            array('*;q=0.3,utf-8;q=0.7,ISO-8859-1;q=0.7',  array('utf-8', 'ISO-8859-1', '*')),
+        );
+    }
+
+    public static function dataProviderForTestParseAcceptHeaderWithQualities()
+    {
+        return array(
+            array('text/html;q=0.8', array('text/html' => 0.8)),
+            array('text/html;foo=bar;q=0.8 ', array('text/html;foo=bar' => 0.8)),
+            array('text/html;charset=utf-8; q=0.8', array('text/html;charset=utf-8' => 0.8)),
+            array('text/html,application/xml;q=0.9,*/*;charset=utf-8; q=0.8', array('text/html' => 1.0, 'application/xml' => 0.9, '*/*;charset=utf-8' => 0.8)),
+            array('text/html,application/xhtml+xml', array('text/html' => 1, 'application/xhtml+xml' => 1)),
+            array('text/html, application/json;q=0.8, text/csv;q=0.7', array('text/html' => 1, 'application/json' => 0.8, 'text/csv' => 0.7)),
+            array('iso-8859-5, unicode-1-1;q=0.8', array('iso-8859-5' => 1, 'unicode-1-1' => 0.8)),
+            array('gzip;q=1.0, identity; q=0.5, *;q=0', array('gzip' => 1, 'identity' => 0.5, '*' => 0)),
+        );
+    }
+
+    public static function dataProviderForTestGetBest()
+    {
+        $pearCharsetHeader  = 'ISO-8859-1, Big5;q=0.6,utf-8;q=0.7, *;q=0.5';
+        $pearCharsetHeader2 = 'ISO-8859-1, Big5;q=0.6,utf-8;q=0.7';
+
+        return array(
+            array(
+                $pearCharsetHeader,
+                array(
+                    'utf-8',
+                    'big5',
+                    'iso-8859-1',
+                    'shift-jis',
+                ),
+                'iso-8859-1'
+            ),
+            array(
+                $pearCharsetHeader,
+                array(
+                    'utf-8',
+                    'big5',
+                    'shift-jis',
+                ),
+                'utf-8'
+            ),
+            array(
+                $pearCharsetHeader,
+                array(
+                    'Big5',
+                    'shift-jis',
+                ),
+                'Big5'
+            ),
+            array(
+                $pearCharsetHeader,
+                array(
+                    'shift-jis',
+                ),
+                'shift-jis'
+            ),
+            array(
+                $pearCharsetHeader2,
+                array(
+                    'utf-8',
+                    'big5',
+                    'iso-8859-1',
+                    'shift-jis',
+                ),
+                'iso-8859-1'
+            ),
+            array(
+                $pearCharsetHeader2,
+                array(
+                    'utf-8',
+                    'big5',
+                    'shift-jis',
+                ),
+                'utf-8'
+            ),
+            array(
+                $pearCharsetHeader2,
+                array(
+                    'Big5',
+                    'shift-jis',
+                ),
+                'Big5'
+            ),
+            array(
+                'utf-8;q=0.6,iso-8859-5;q=0.9',
+                array(
+                    'iso-8859-5',
+                    'utf-8',
+                ),
+                'iso-8859-5'
+            ),
+            array(
+                '',
+                array(
+                    'iso-8859-5',
+                    'utf-8',
+                ),
+                null
+            ),
+            array(
+                'audio/*; q=0.2, audio/basic',
+                array(),
+                'audio/basic',
+            ),
+        );
+    }
+
+    public static function dataProviderForTestParseAcceptHeaderEnsuresPrecedence()
+    {
+        return array(
+            array(
+                'text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5',
+                array(
+                    'text/html;level=1' => 1,
+                    'text/html;level=2' => 0.4,
+                    'text/html'         => 0.7,
+                    'text/*'            => 0.3,
+                    '*/*'               => 0.5,
+                )
+            ),
+            array(
+                'text/html,application/xhtml+xml,application/xml;q=0.9,text/*;q=0.7,*/*,image/gif; q=0.8, image/jpeg; q=0.6, image/*',
+                array(
+                    'text/html'             => 1,
+                    'application/xhtml+xml' => 1,
+                    'application/xml'       => 0.9,
+                    'image/gif'             => 0.8,
+                    'text/*'                => 0.7,
+                    'image/jpeg'            => 0.6,
+                    'image/*'               => 0.02,
+                    '*/*'                   => 0.01,
+                )
+            ),
+        );
+    }
+
+    public static function dataProviderForParseParameters()
+    {
+        return array(
+            array(
+                'application/json ;q=1.0; level=2;foo= bar',
+                array(
+                    'level' => 2,
+                    'foo'   => 'bar',
+                ),
+            ),
+            array(
+                'application/json ;q = 1.0; level = 2;     FOO  = bAr',
+                array(
+                    'level' => 2,
+                    'foo'   => 'bAr',
+                ),
+            ),
+            array(
+                'application/json;q=1.0',
+                array(),
+            ),
+            array(
+                'application/json;foo',
+                array(),
+            ),
+        );
+    }
+}
+
+class TestableNegotiator extends Negotiator
+{
+    public function parseHeader($acceptHeader)
+    {
+        return parent::parseHeader($acceptHeader);
+    }
+
+    public function parseParameters($value)
+    {
+        return parent::parseParameters($value);
+    }
+}
diff --git a/core/vendor/willdurand/negotiation/tests/Negotiation/Tests/TestCase.php b/core/vendor/willdurand/negotiation/tests/Negotiation/Tests/TestCase.php
new file mode 100644
index 0000000..df8e032
--- /dev/null
+++ b/core/vendor/willdurand/negotiation/tests/Negotiation/Tests/TestCase.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace Negotiation\Tests;
+
+/**
+ * @author William Durand <william.durand1@gmail.com>
+ */
+class TestCase extends \PHPUnit_Framework_TestCase
+{
+}
diff --git a/core/vendor/willdurand/negotiation/tests/bootstrap.php b/core/vendor/willdurand/negotiation/tests/bootstrap.php
new file mode 100644
index 0000000..c63e17a
--- /dev/null
+++ b/core/vendor/willdurand/negotiation/tests/bootstrap.php
@@ -0,0 +1,15 @@
+<?php
+
+if (! ($loader = @include __DIR__ . '/../vendor/autoload.php')) {
+    die(<<<EOT
+You need to install the project dependencies using Composer:
+$ wget http://getcomposer.org/composer.phar
+OR
+$ curl -s https://getcomposer.org/installer | php
+$ php composer.phar install --dev
+$ phpunit
+EOT
+    );
+}
+
+$loader->add('Negotiation\Tests', __DIR__);
diff --git a/core/vendor/willdurand/stack-negotiation/.gitignore b/core/vendor/willdurand/stack-negotiation/.gitignore
new file mode 100644
index 0000000..3a9875b
--- /dev/null
+++ b/core/vendor/willdurand/stack-negotiation/.gitignore
@@ -0,0 +1,2 @@
+/vendor/
+composer.lock
diff --git a/core/vendor/willdurand/stack-negotiation/.travis.yml b/core/vendor/willdurand/stack-negotiation/.travis.yml
new file mode 100644
index 0000000..0ac4a71
--- /dev/null
+++ b/core/vendor/willdurand/stack-negotiation/.travis.yml
@@ -0,0 +1,16 @@
+language: php
+
+php:
+    - 5.4
+    - 5.5
+    - hhvm
+
+matrix:
+    allow_failures:
+        - php: hhvm
+
+before_script:
+    - composer self-update
+    - composer install --dev --prefer-dist --no-interaction
+
+script: phpunit --coverage-text
diff --git a/core/vendor/willdurand/stack-negotiation/CONTRIBUTING.md b/core/vendor/willdurand/stack-negotiation/CONTRIBUTING.md
new file mode 100644
index 0000000..3677a2c
--- /dev/null
+++ b/core/vendor/willdurand/stack-negotiation/CONTRIBUTING.md
@@ -0,0 +1,33 @@
+Contributing
+============
+
+First of all, **thank you** for contributing, **you are awesome**!
+
+Here are a few rules to follow in order to ease code reviews, and discussions before
+maintainers accept and merge your work.
+
+You MUST follow the [PSR-1](http://www.php-fig.org/psr/1/) and
+[PSR-2](http://www.php-fig.org/psr/2/). If you don't know about any of them, you
+should really read the recommendations. Can't wait? Use the [PHP-CS-Fixer
+tool](http://cs.sensiolabs.org/).
+
+You MUST run the test suite.
+
+You MUST write (or update) unit tests.
+
+You SHOULD write documentation.
+
+Please, write [commit messages that make
+sense](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html),
+and [rebase your branch](http://git-scm.com/book/en/Git-Branching-Rebasing)
+before submitting your Pull Request.
+
+One may ask you to [squash your
+commits](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html)
+too. This is used to "clean" your Pull Request before merging it (we don't want
+commits such as `fix tests`, `fix 2`, `fix 3`, etc.).
+
+Also, while creating your Pull Request on GitHub, you MUST write a description
+which gives the context and/or explains why you are creating it.
+
+Thank you!
diff --git a/core/vendor/willdurand/stack-negotiation/LICENSE b/core/vendor/willdurand/stack-negotiation/LICENSE
new file mode 100644
index 0000000..b6512cc
--- /dev/null
+++ b/core/vendor/willdurand/stack-negotiation/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) William Durand <william.durand1@gmail.com>
+
+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.
diff --git a/core/vendor/willdurand/stack-negotiation/README.md b/core/vendor/willdurand/stack-negotiation/README.md
new file mode 100644
index 0000000..ab9edf2
--- /dev/null
+++ b/core/vendor/willdurand/stack-negotiation/README.md
@@ -0,0 +1,90 @@
+StackNegotiation
+================
+
+[![Build
+Status](https://travis-ci.org/willdurand/StackNegotiation.png?branch=master)](http://travis-ci.org/willdurand/StackNegotiation)
+[![Latest Stable
+Version](https://poser.pugx.org/willdurand/stack-negotiation/v/stable.png)](https://packagist.org/packages/willdurand/stack-negotiation)
+
+[Stack](http://stackphp.com) middleware for content negotiation.
+
+
+Installation
+------------
+
+The recommended way to install StackNegotiation is through
+[Composer](http://getcomposer.org/):
+
+``` json
+{
+    "require": {
+        "willdurand/stack-negotiation": "@stable"
+    }
+}
+```
+
+**Protip:** you should browse the
+[`willdurand/stack-negotiation`](https://packagist.org/packages/willdurand/stack-negotiation)
+page to choose a stable version to use, avoid the `@stable` meta constraint.
+
+
+Usage
+-----
+
+```php
+use Negotiation\Stack\Negotiation;
+
+$app = new Negotiation($app);
+```
+
+### Headers
+
+#### `Accept` Header
+
+This middleware adds a `_accept` attribute to the request, containing a
+`AcceptHeader` object (see:
+[Negotiation](https://github.com/willdurand/Negotiation) library). It also adds
+a `_mime_type` attribute containing the mime type if it is not a media range, as
+well as a `_format` attribute containing the preferred format value.
+
+#### `Accept-Language` Header
+
+This middleware adds a `_accept_language` attribute to the request, containing a
+`AcceptHeader` object (see:
+[Negotiation](https://github.com/willdurand/Negotiation) library). It also adds
+a `_language` attribute containing the value itself.
+
+#### `Content-Type` Header
+
+This middleware is able to decode a request body, and fill in request data. It
+is inspired by Silex's recipe [Accepting a JSON Request
+Body](http://silex.sensiolabs.org/doc/cookbook/json_request_body.html) and
+[FOSRestBundle Body
+Listener](https://github.com/FriendsOfSymfony/FOSRestBundle/blob/master/Resources/doc/3-listener-support.md#body-listener).
+
+
+Unit Tests
+----------
+
+Setup the test suite using Composer:
+
+    $ composer install --dev
+
+Run it using PHPUnit:
+
+    $ ./vendor/bin/phpunit
+
+
+Contributing
+------------
+
+See
+[CONTRIBUTING](https://github.com/willdurand/StackNegotiation/blob/master/CONTRIBUTING.md)
+file.
+
+
+License
+-------
+
+StackNegotiation is released under the MIT License. See the bundled LICENSE file
+for details.
diff --git a/core/vendor/willdurand/stack-negotiation/composer.json b/core/vendor/willdurand/stack-negotiation/composer.json
new file mode 100644
index 0000000..e50b795
--- /dev/null
+++ b/core/vendor/willdurand/stack-negotiation/composer.json
@@ -0,0 +1,28 @@
+{
+    "name": "willdurand/stack-negotiation",
+    "description": "Stack middleware for content negotiation.",
+    "require": {
+        "php": ">=5.4.0",
+        "willdurand/negotiation": "~1.3",
+        "symfony/serializer": "~2.1"
+    },
+    "require-dev": {
+        "phpunit/phpunit": "~3.7",
+        "symfony/http-kernel": "~2.1"
+    },
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "William DURAND",
+            "email": "william.durand1@gmail.com"
+        }
+    ],
+    "autoload": {
+        "psr-0": { "Negotiation": "src/" }
+    },
+    "extra": {
+        "branch-alias": {
+            "dev-master": "1.0-dev"
+        }
+    }
+}
diff --git a/core/vendor/willdurand/stack-negotiation/phpunit.xml.dist b/core/vendor/willdurand/stack-negotiation/phpunit.xml.dist
new file mode 100644
index 0000000..cec04bd
--- /dev/null
+++ b/core/vendor/willdurand/stack-negotiation/phpunit.xml.dist
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit backupGlobals="false"
+    backupStaticAttributes="false"
+    colors="true"
+    convertErrorsToExceptions="true"
+    convertNoticesToExceptions="true"
+    convertWarningsToExceptions="true"
+    processIsolation="false"
+    stopOnFailure="false"
+    syntaxCheck="false"
+    bootstrap="tests/bootstrap.php"
+    >
+    <testsuites>
+        <testsuite name="StackNegotiation Test Suite">
+            <directory>./tests/</directory>
+        </testsuite>
+    </testsuites>
+    <filter>
+        <whitelist>
+            <directory>./src/Negotiation/</directory>
+        </whitelist>
+    </filter>
+</phpunit>
diff --git a/core/vendor/willdurand/stack-negotiation/src/Negotiation/Decoder/DecoderProvider.php b/core/vendor/willdurand/stack-negotiation/src/Negotiation/Decoder/DecoderProvider.php
new file mode 100644
index 0000000..75fc363
--- /dev/null
+++ b/core/vendor/willdurand/stack-negotiation/src/Negotiation/Decoder/DecoderProvider.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace Negotiation\Decoder;
+
+/**
+ * @author William Durand <william.durand1@gmail.com>
+ */
+class DecoderProvider implements DecoderProviderInterface
+{
+    /**
+     * @var array
+     */
+    private $decoders;
+
+    /**
+     * @param array $decoders List of key (format) value (instance) of decoders
+     */
+    public function __construct(array $decoders)
+    {
+        $this->decoders = $decoders;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function supports($format)
+    {
+        return isset($this->decoders[$format]);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getDecoder($format)
+    {
+        if (!$this->supports($format)) {
+            throw new \InvalidArgumentException(sprintf("Format '%s' is not supported", $format));
+        }
+
+        return $this->decoders[$format];
+    }
+}
diff --git a/core/vendor/willdurand/stack-negotiation/src/Negotiation/Decoder/DecoderProviderInterface.php b/core/vendor/willdurand/stack-negotiation/src/Negotiation/Decoder/DecoderProviderInterface.php
new file mode 100644
index 0000000..99dc417
--- /dev/null
+++ b/core/vendor/willdurand/stack-negotiation/src/Negotiation/Decoder/DecoderProviderInterface.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Negotiation\Decoder;
+
+/**
+ * @author William Durand <william.durand1@gmail.com>
+ */
+interface DecoderProviderInterface
+{
+    /**
+     * @param string $format format
+     *
+     * @return boolean
+     */
+    public function supports($format);
+
+    /**
+     * @param string $format format
+     *
+     * @return \Symfony\Component\Serializer\SerializerInterface
+     */
+    public function getDecoder($format);
+}
diff --git a/core/vendor/willdurand/stack-negotiation/src/Negotiation/Stack/Negotiation.php b/core/vendor/willdurand/stack-negotiation/src/Negotiation/Stack/Negotiation.php
new file mode 100644
index 0000000..772eb55
--- /dev/null
+++ b/core/vendor/willdurand/stack-negotiation/src/Negotiation/Stack/Negotiation.php
@@ -0,0 +1,121 @@
+<?php
+
+namespace Negotiation\Stack;
+
+use Negotiation\FormatNegotiator;
+use Negotiation\FormatNegotiatorInterface;
+use Negotiation\LanguageNegotiator;
+use Negotiation\NegotiatorInterface;
+use Negotiation\Decoder\DecoderProvider;
+use Negotiation\Decoder\DecoderProviderInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
+use Symfony\Component\Serializer\Encoder\JsonEncoder;
+use Symfony\Component\Serializer\Encoder\XmlEncoder;
+
+/**
+ * @author William Durand <william.durand1@gmail.com>
+ */
+class Negotiation implements HttpKernelInterface
+{
+    /**
+     * @var HttpKernelInterface
+     */
+    private $app;
+
+    /**
+     * @var FormatNegotiatorInterface
+     */
+    private $formatNegotiator;
+
+    /**
+     * @var NegotiatorInterface
+     */
+    private $languageNegotiator;
+
+    /**
+     * @var DecoderProviderInterface
+     */
+    private $decoderProvider;
+
+    public function __construct(
+        HttpKernelInterface $app,
+        FormatNegotiatorInterface $formatNegotiator = null,
+        NegotiatorInterface $languageNegotiator = null,
+        DecoderProviderInterface $decoderProvider = null
+    ) {
+        $this->app                = $app;
+        $this->formatNegotiator   = $formatNegotiator ?: new FormatNegotiator();
+        $this->languageNegotiator = $languageNegotiator ?: new LanguageNegotiator();
+        $this->decoderProvider    = $decoderProvider ?: new DecoderProvider([
+            'json' => new JsonEncoder(),
+            'xml'  => new XmlEncoder(),
+        ]);
+    }
+
+    public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
+    {
+        // `Accept` header
+        if (null !== $accept = $request->headers->get('Accept')) {
+            $accept = $this->formatNegotiator->getBest($accept);
+            $request->attributes->set('_accept', $accept);
+
+            if (null !== $accept && !$accept->isMediaRange()) {
+                $request->attributes->set('_mime_type', $accept->getValue());
+                $request->attributes->set('_format', $this->formatNegotiator->getFormat($accept->getValue()));
+            }
+        }
+
+        // `Accept-Language` header
+        if (null !== $accept = $request->headers->get('Accept-Language')) {
+            $accept = $this->languageNegotiator->getBest($accept);
+            $request->attributes->set('_accept_language', $accept);
+
+            if (null !== $accept) {
+                $request->attributes->set('_language', $accept->getValue());
+            }
+        }
+
+        try {
+            // `Content-Type` header
+            $this->decodeBody($request);
+        } catch (BadRequestHttpException $e) {
+            if (true === $catch) {
+                return new Response($e->getMessage(), Response::HTTP_BAD_REQUEST);
+            }
+        }
+
+        return $this->app->handle($request, $type, $catch);
+    }
+
+    private function decodeBody(Request $request)
+    {
+        if (in_array($request->getMethod(), [ 'POST', 'PUT', 'PATCH', 'DELETE' ])) {
+            $contentType = $request->headers->get('Content-Type');
+            $format      = $this->formatNegotiator->getFormat($contentType);
+
+            if (!$this->decoderProvider->supports($format)) {
+                return;
+            }
+
+            $decoder = $this->decoderProvider->getDecoder($format);
+            $content = $request->getContent();
+
+            if (!empty($content)) {
+                try {
+                    $data = $decoder->decode($content, $format);
+                } catch (\Exception $e) {
+                    $data = null;
+                }
+
+                if (is_array($data)) {
+                    $request->request->replace($data);
+                } else {
+                    throw new BadRequestHttpException('Invalid ' . $format . ' message received');
+                }
+            }
+        }
+    }
+}
diff --git a/core/vendor/willdurand/stack-negotiation/tests/Negotiation/Tests/Stack/NegotiationTest.php b/core/vendor/willdurand/stack-negotiation/tests/Negotiation/Tests/Stack/NegotiationTest.php
new file mode 100644
index 0000000..46b350d
--- /dev/null
+++ b/core/vendor/willdurand/stack-negotiation/tests/Negotiation/Tests/Stack/NegotiationTest.php
@@ -0,0 +1,102 @@
+<?php
+
+namespace Negotiation\Tests\Stack;
+
+use Negotiation\Stack\Negotiation;
+use Negotiation\Tests\TestCase;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+
+class NegotiationTest extends TestCase
+{
+    public function testAcceptHeader()
+    {
+        $app = $this->createStackedApp();
+        $req = $this->createRequest(null, [
+            'Accept' => 'application/json',
+        ]);
+
+        $app->handle($req);
+
+        $header = $req->attributes->get('_accept');
+        $this->assertInstanceOf('Negotiation\AcceptHeader', $header);
+        $this->assertEquals('application/json', $header->getValue());
+        $this->assertEquals('application/json', $req->attributes->get('_mime_type'));
+        $this->assertEquals('json', $req->attributes->get('_format'));
+    }
+
+    public function testAcceptLanguageHeader()
+    {
+        $app = $this->createStackedApp();
+        $req = $this->createRequest(null, [
+            'Accept-Language' => 'en; q=0.1, fr; q=0.4, fu; q=0.9, de; q=0.2',
+        ]);
+
+        $app->handle($req);
+
+        $header = $req->attributes->get('_accept_language');
+        $this->assertInstanceOf('Negotiation\AcceptHeader', $header);
+        $this->assertEquals('fu', $header->getValue());
+        $this->assertEquals('fu', $req->attributes->get('_language'));
+    }
+
+    /**
+     * @dataProvider dataProviderForTestDecodeBody
+     */
+    public function testDecodeBody($method, $content, $mimeType, $expected)
+    {
+        $app = $this->createStackedApp();
+        $req = $this->createRequest($content, [
+            'Content-Type' => $mimeType,
+            ]);
+        $req->setMethod($method);
+
+        $app->handle($req);
+
+        $this->assertEquals($expected, $req->request->all());
+    }
+
+    public function dataProviderForTestDecodeBody()
+    {
+        return [
+            [ 'POST', 'foo', 'application/json', [] ],
+            [ 'POST', '<response><foo>bar</foo></response>', 'application/xml', [ 'foo' => 'bar' ] ],
+            [ 'POST', '{ "foo": "bar" }', 'application/json', [ 'foo' => 'bar' ] ],
+            [ 'PUT', '', 'application/json', [] ],
+            [ 'GET', '{ "foo": "bar" }', 'application/json', [] ],
+            [ 'GET', '<response><foo>bar</foo></response>', 'application/xml', [] ],
+        ];
+    }
+
+    private function createStackedApp(array $responseHeaders = [])
+    {
+        return new Negotiation(new MockApp($responseHeaders));
+    }
+
+    private function createRequest($content = null, array $requestHeaders = [])
+    {
+        $request = new Request([], [], [], [], [], [], $content);
+        $request->headers->add($requestHeaders);
+
+        return $request;
+    }
+}
+
+class MockApp implements HttpKernelInterface
+{
+    private $responseHeaders;
+
+    public function __construct(array $responseHeaders)
+    {
+        $this->responseHeaders = $responseHeaders;
+    }
+
+    public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
+    {
+        $response = new Response();
+        $response->headers->add($this->responseHeaders);
+
+        return $response;
+    }
+}
diff --git a/core/vendor/willdurand/stack-negotiation/tests/Negotiation/Tests/TestCase.php b/core/vendor/willdurand/stack-negotiation/tests/Negotiation/Tests/TestCase.php
new file mode 100644
index 0000000..ef057b8
--- /dev/null
+++ b/core/vendor/willdurand/stack-negotiation/tests/Negotiation/Tests/TestCase.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace Negotiation\Tests;
+
+abstract class TestCase extends \PHPUnit_Framework_TestCase
+{
+}
diff --git a/core/vendor/willdurand/stack-negotiation/tests/bootstrap.php b/core/vendor/willdurand/stack-negotiation/tests/bootstrap.php
new file mode 100644
index 0000000..c63e17a
--- /dev/null
+++ b/core/vendor/willdurand/stack-negotiation/tests/bootstrap.php
@@ -0,0 +1,15 @@
+<?php
+
+if (! ($loader = @include __DIR__ . '/../vendor/autoload.php')) {
+    die(<<<EOT
+You need to install the project dependencies using Composer:
+$ wget http://getcomposer.org/composer.phar
+OR
+$ curl -s https://getcomposer.org/installer | php
+$ php composer.phar install --dev
+$ phpunit
+EOT
+    );
+}
+
+$loader->add('Negotiation\Tests', __DIR__);
