diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000000000000000000000000000000000000..86ac1c844afdecb1bd68426dc43a485d6e2002f4
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,8 @@
+{
+    "require": {
+        "symfony/http-foundation": "^3.4",
+        "hab/picarecord": "^1.1",
+        "hab/picareader": "^1.2",
+        "hab/picawriter": "^1.0"
+    }
+}
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000000000000000000000000000000000000..06f67393173a9f740b6f512ecfc662b44cc635a3
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,346 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "7b4e0ab48493485011f66d6e7fc1e6b2",
+    "packages": [
+        {
+            "name": "hab/picareader",
+            "version": "v1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/dmj/PicaReader.git",
+                "reference": "b5b669ccee96a1fdd6b85045795818f27867a684"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/dmj/PicaReader/zipball/b5b669ccee96a1fdd6b85045795818f27867a684",
+                "reference": "b5b669ccee96a1fdd6b85045795818f27867a684",
+                "shasum": ""
+            },
+            "require": {
+                "hab/picarecord": "~1.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-0": {
+                    "HAB\\Pica": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "GPL-3.0+"
+            ],
+            "authors": [
+                {
+                    "name": "David Maus",
+                    "email": "maus@hab.de",
+                    "role": "developer"
+                }
+            ],
+            "description": "Classes for reading Pica+ records encoded in Pica, PicaXML and PicaPlain",
+            "time": "2017-10-27T06:40:13+00:00"
+        },
+        {
+            "name": "hab/picarecord",
+            "version": "v1.1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/dmj/PicaRecord.git",
+                "reference": "a00e195d8f0f1b41d2bdd385334b0f88975e07c1"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/dmj/PicaRecord/zipball/a00e195d8f0f1b41d2bdd385334b0f88975e07c1",
+                "reference": "a00e195d8f0f1b41d2bdd385334b0f88975e07c1",
+                "shasum": ""
+            },
+            "type": "library",
+            "autoload": {
+                "psr-0": {
+                    "HAB\\Pica": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "GPL-3.0+"
+            ],
+            "authors": [
+                {
+                    "name": "David Maus",
+                    "email": "maus@hab.de",
+                    "role": "developer"
+                }
+            ],
+            "description": "Object oriented interface to Pica+ records, fields, and subfields",
+            "time": "2017-01-02T10:15:57+00:00"
+        },
+        {
+            "name": "hab/picawriter",
+            "version": "v1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/dmj/PicaWriter.git",
+                "reference": "8744cf9b24f20cb9a3430cc675f867b915ddcffa"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/dmj/PicaWriter/zipball/8744cf9b24f20cb9a3430cc675f867b915ddcffa",
+                "reference": "8744cf9b24f20cb9a3430cc675f867b915ddcffa",
+                "shasum": ""
+            },
+            "require": {
+                "hab/picarecord": "~1.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-0": {
+                    "HAB\\Pica": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "GPL-3.0+"
+            ],
+            "authors": [
+                {
+                    "name": "David Maus",
+                    "email": "maus@hab.de",
+                    "role": "developer"
+                }
+            ],
+            "description": "Classes for writing Pica+ records to PicaXML and PicaPlain",
+            "time": "2015-08-07T09:17:03+00:00"
+        },
+        {
+            "name": "paragonie/random_compat",
+            "version": "v2.0.12",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/paragonie/random_compat.git",
+                "reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/paragonie/random_compat/zipball/258c89a6b97de7dfaf5b8c7607d0478e236b04fb",
+                "reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.2.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "4.*|5.*"
+            },
+            "suggest": {
+                "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "lib/random.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Paragon Initiative Enterprises",
+                    "email": "security@paragonie.com",
+                    "homepage": "https://paragonie.com"
+                }
+            ],
+            "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
+            "keywords": [
+                "csprng",
+                "pseudorandom",
+                "random"
+            ],
+            "time": "2018-04-04T21:24:14+00:00"
+        },
+        {
+            "name": "symfony/http-foundation",
+            "version": "v3.4.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/http-foundation.git",
+                "reference": "b11e6d165ff4cbf5685d185ab19a90f2f3bb7d1e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/http-foundation/zipball/b11e6d165ff4cbf5685d185ab19a90f2f3bb7d1e",
+                "reference": "b11e6d165ff4cbf5685d185ab19a90f2f3bb7d1e",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.5.9|>=7.0.8",
+                "symfony/polyfill-mbstring": "~1.1",
+                "symfony/polyfill-php70": "~1.6"
+            },
+            "require-dev": {
+                "symfony/expression-language": "~2.8|~3.0|~4.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.4-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\HttpFoundation\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony HttpFoundation Component",
+            "homepage": "https://symfony.com",
+            "time": "2018-04-03T05:22:50+00:00"
+        },
+        {
+            "name": "symfony/polyfill-mbstring",
+            "version": "v1.7.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-mbstring.git",
+                "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/78be803ce01e55d3491c1397cf1c64beb9c1b63b",
+                "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "suggest": {
+                "ext-mbstring": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.7-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Mbstring\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for the Mbstring extension",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "mbstring",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "time": "2018-01-30T19:27:44+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php70",
+            "version": "v1.7.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php70.git",
+                "reference": "3532bfcd8f933a7816f3a0a59682fc404776600f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/3532bfcd8f933a7816f3a0a59682fc404776600f",
+                "reference": "3532bfcd8f933a7816f3a0a59682fc404776600f",
+                "shasum": ""
+            },
+            "require": {
+                "paragonie/random_compat": "~1.0|~2.0",
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.7-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php70\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ],
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "time": "2018-01-30T19:27:44+00:00"
+        }
+    ],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": [],
+    "platform-dev": []
+}
diff --git a/public/instance/proxy/opac-de-23/.htaccess b/public/instance/proxy/opac-de-23/.htaccess
new file mode 100644
index 0000000000000000000000000000000000000000..9704a8ff6944d5ce964bd563964c064ca8b1e05c
--- /dev/null
+++ b/public/instance/proxy/opac-de-23/.htaccess
@@ -0,0 +1 @@
+FallbackResource e:/hosting/development/2016/uri.hab.de/public/instance/proxy/opac-de-23/index.php
\ No newline at end of file
diff --git a/public/instance/proxy/opac-de-23/index.php b/public/instance/proxy/opac-de-23/index.php
new file mode 100644
index 0000000000000000000000000000000000000000..5a3b93b2ba1d3277f485c5c37a602d5a0f3467c5
--- /dev/null
+++ b/public/instance/proxy/opac-de-23/index.php
@@ -0,0 +1,56 @@
+<?php
+
+require_once __DIR__ . '/../../../../vendor/autoload.php';
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+
+use HAB\Pica\Record\Record;
+use HAB\Pica\Reader\PicaNormReader;
+use HAB\Pica\Writer\PicaXmlWriter;
+
+define('PSI_TEMPLATE', 'http://opac.lbs-braunschweig.gbv.de/DB=2/PLAIN=Y/CHARSET=UTF8/PLAINTTLCHARSET=UTF8/PPN?PPN=%s');
+
+function terminate (Request $request, Response $response) {
+    $response->prepare($request);
+    $response->send();
+    exit();
+}
+
+function load ($ident) {
+    $content = @file_get_contents(sprintf(PSI_TEMPLATE, $ident));
+    if ($content) {
+        $reader = new PicaNormReader();
+        $reader->open($content);
+        return $reader->read();
+    }
+}
+
+$request = Request::createFromGlobals();
+
+$route = basename($request->server->get('REQUEST_URI'));
+if (!preg_match('@^[0-9]{8}[0-9X]\.xml@', $route)) {
+    $response = new Response('<h1>400 Bad Request</h1>', 400, array('Content-Type' => 'text/html'));
+    terminate($request, $response);
+}
+
+list($ident, $format) = explode('.', $route);
+
+$record = load($ident);
+
+if (!$record) {
+    $response = new Response('<h1>404 Not Found</h1>', 404, array('Content-Type' => 'text/html'));
+    terminate($request, $response);
+}
+
+switch ($format) {
+    case 'xml':
+        $writer = new PicaXmlWriter();
+        $content = $writer->write($record);
+        $response = new Response($content, 200, array('Content-Type' => 'application/xml'));
+        break;
+    default:
+        $response = new Response('<h1>406 Not Acceptable</h1>', 406, array('Content-Type' => 'text/html'));
+}
+
+terminate($request, $response);
diff --git a/vendor/autoload.php b/vendor/autoload.php
new file mode 100644
index 0000000000000000000000000000000000000000..b00cf7a16e9b2203f7d7aef5de475a6d5ed4ea19
--- /dev/null
+++ b/vendor/autoload.php
@@ -0,0 +1,7 @@
+<?php
+
+// autoload.php @generated by Composer
+
+require_once __DIR__ . '/composer/autoload_real.php';
+
+return ComposerAutoloaderInit78daec5e1761b48dba06a78068db3ccf::getLoader();
diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php
new file mode 100644
index 0000000000000000000000000000000000000000..dc02dfb114fb6af2eacf89407a529c37ab8e7eb8
--- /dev/null
+++ b/vendor/composer/ClassLoader.php
@@ -0,0 +1,445 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ *     $loader = new \Composer\Autoload\ClassLoader();
+ *
+ *     // register classes with namespaces
+ *     $loader->add('Symfony\Component', __DIR__.'/component');
+ *     $loader->add('Symfony',           __DIR__.'/framework');
+ *
+ *     // activate the autoloader
+ *     $loader->register();
+ *
+ *     // to enable searching the include path (eg. for PEAR packages)
+ *     $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ * @see    http://www.php-fig.org/psr/psr-0/
+ * @see    http://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+    // PSR-4
+    private $prefixLengthsPsr4 = array();
+    private $prefixDirsPsr4 = array();
+    private $fallbackDirsPsr4 = array();
+
+    // PSR-0
+    private $prefixesPsr0 = array();
+    private $fallbackDirsPsr0 = array();
+
+    private $useIncludePath = false;
+    private $classMap = array();
+    private $classMapAuthoritative = false;
+    private $missingClasses = array();
+    private $apcuPrefix;
+
+    public function getPrefixes()
+    {
+        if (!empty($this->prefixesPsr0)) {
+            return call_user_func_array('array_merge', $this->prefixesPsr0);
+        }
+
+        return array();
+    }
+
+    public function getPrefixesPsr4()
+    {
+        return $this->prefixDirsPsr4;
+    }
+
+    public function getFallbackDirs()
+    {
+        return $this->fallbackDirsPsr0;
+    }
+
+    public function getFallbackDirsPsr4()
+    {
+        return $this->fallbackDirsPsr4;
+    }
+
+    public function getClassMap()
+    {
+        return $this->classMap;
+    }
+
+    /**
+     * @param array $classMap Class to filename map
+     */
+    public function addClassMap(array $classMap)
+    {
+        if ($this->classMap) {
+            $this->classMap = array_merge($this->classMap, $classMap);
+        } else {
+            $this->classMap = $classMap;
+        }
+    }
+
+    /**
+     * Registers a set of PSR-0 directories for a given prefix, either
+     * appending or prepending to the ones previously set for this prefix.
+     *
+     * @param string       $prefix  The prefix
+     * @param array|string $paths   The PSR-0 root directories
+     * @param bool         $prepend Whether to prepend the directories
+     */
+    public function add($prefix, $paths, $prepend = false)
+    {
+        if (!$prefix) {
+            if ($prepend) {
+                $this->fallbackDirsPsr0 = array_merge(
+                    (array) $paths,
+                    $this->fallbackDirsPsr0
+                );
+            } else {
+                $this->fallbackDirsPsr0 = array_merge(
+                    $this->fallbackDirsPsr0,
+                    (array) $paths
+                );
+            }
+
+            return;
+        }
+
+        $first = $prefix[0];
+        if (!isset($this->prefixesPsr0[$first][$prefix])) {
+            $this->prefixesPsr0[$first][$prefix] = (array) $paths;
+
+            return;
+        }
+        if ($prepend) {
+            $this->prefixesPsr0[$first][$prefix] = array_merge(
+                (array) $paths,
+                $this->prefixesPsr0[$first][$prefix]
+            );
+        } else {
+            $this->prefixesPsr0[$first][$prefix] = array_merge(
+                $this->prefixesPsr0[$first][$prefix],
+                (array) $paths
+            );
+        }
+    }
+
+    /**
+     * Registers a set of PSR-4 directories for a given namespace, either
+     * appending or prepending to the ones previously set for this namespace.
+     *
+     * @param string       $prefix  The prefix/namespace, with trailing '\\'
+     * @param array|string $paths   The PSR-4 base directories
+     * @param bool         $prepend Whether to prepend the directories
+     *
+     * @throws \InvalidArgumentException
+     */
+    public function addPsr4($prefix, $paths, $prepend = false)
+    {
+        if (!$prefix) {
+            // Register directories for the root namespace.
+            if ($prepend) {
+                $this->fallbackDirsPsr4 = array_merge(
+                    (array) $paths,
+                    $this->fallbackDirsPsr4
+                );
+            } else {
+                $this->fallbackDirsPsr4 = array_merge(
+                    $this->fallbackDirsPsr4,
+                    (array) $paths
+                );
+            }
+        } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+            // Register directories for a new namespace.
+            $length = strlen($prefix);
+            if ('\\' !== $prefix[$length - 1]) {
+                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+            }
+            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+            $this->prefixDirsPsr4[$prefix] = (array) $paths;
+        } elseif ($prepend) {
+            // Prepend directories for an already registered namespace.
+            $this->prefixDirsPsr4[$prefix] = array_merge(
+                (array) $paths,
+                $this->prefixDirsPsr4[$prefix]
+            );
+        } else {
+            // Append directories for an already registered namespace.
+            $this->prefixDirsPsr4[$prefix] = array_merge(
+                $this->prefixDirsPsr4[$prefix],
+                (array) $paths
+            );
+        }
+    }
+
+    /**
+     * Registers a set of PSR-0 directories for a given prefix,
+     * replacing any others previously set for this prefix.
+     *
+     * @param string       $prefix The prefix
+     * @param array|string $paths  The PSR-0 base directories
+     */
+    public function set($prefix, $paths)
+    {
+        if (!$prefix) {
+            $this->fallbackDirsPsr0 = (array) $paths;
+        } else {
+            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+        }
+    }
+
+    /**
+     * Registers a set of PSR-4 directories for a given namespace,
+     * replacing any others previously set for this namespace.
+     *
+     * @param string       $prefix The prefix/namespace, with trailing '\\'
+     * @param array|string $paths  The PSR-4 base directories
+     *
+     * @throws \InvalidArgumentException
+     */
+    public function setPsr4($prefix, $paths)
+    {
+        if (!$prefix) {
+            $this->fallbackDirsPsr4 = (array) $paths;
+        } else {
+            $length = strlen($prefix);
+            if ('\\' !== $prefix[$length - 1]) {
+                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+            }
+            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+            $this->prefixDirsPsr4[$prefix] = (array) $paths;
+        }
+    }
+
+    /**
+     * Turns on searching the include path for class files.
+     *
+     * @param bool $useIncludePath
+     */
+    public function setUseIncludePath($useIncludePath)
+    {
+        $this->useIncludePath = $useIncludePath;
+    }
+
+    /**
+     * Can be used to check if the autoloader uses the include path to check
+     * for classes.
+     *
+     * @return bool
+     */
+    public function getUseIncludePath()
+    {
+        return $this->useIncludePath;
+    }
+
+    /**
+     * Turns off searching the prefix and fallback directories for classes
+     * that have not been registered with the class map.
+     *
+     * @param bool $classMapAuthoritative
+     */
+    public function setClassMapAuthoritative($classMapAuthoritative)
+    {
+        $this->classMapAuthoritative = $classMapAuthoritative;
+    }
+
+    /**
+     * Should class lookup fail if not found in the current class map?
+     *
+     * @return bool
+     */
+    public function isClassMapAuthoritative()
+    {
+        return $this->classMapAuthoritative;
+    }
+
+    /**
+     * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+     *
+     * @param string|null $apcuPrefix
+     */
+    public function setApcuPrefix($apcuPrefix)
+    {
+        $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
+    }
+
+    /**
+     * The APCu prefix in use, or null if APCu caching is not enabled.
+     *
+     * @return string|null
+     */
+    public function getApcuPrefix()
+    {
+        return $this->apcuPrefix;
+    }
+
+    /**
+     * Registers this instance as an autoloader.
+     *
+     * @param bool $prepend Whether to prepend the autoloader or not
+     */
+    public function register($prepend = false)
+    {
+        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+    }
+
+    /**
+     * Unregisters this instance as an autoloader.
+     */
+    public function unregister()
+    {
+        spl_autoload_unregister(array($this, 'loadClass'));
+    }
+
+    /**
+     * Loads the given class or interface.
+     *
+     * @param  string    $class The name of the class
+     * @return bool|null True if loaded, null otherwise
+     */
+    public function loadClass($class)
+    {
+        if ($file = $this->findFile($class)) {
+            includeFile($file);
+
+            return true;
+        }
+    }
+
+    /**
+     * Finds the path to the file where the class is defined.
+     *
+     * @param string $class The name of the class
+     *
+     * @return string|false The path if found, false otherwise
+     */
+    public function findFile($class)
+    {
+        // class map lookup
+        if (isset($this->classMap[$class])) {
+            return $this->classMap[$class];
+        }
+        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+            return false;
+        }
+        if (null !== $this->apcuPrefix) {
+            $file = apcu_fetch($this->apcuPrefix.$class, $hit);
+            if ($hit) {
+                return $file;
+            }
+        }
+
+        $file = $this->findFileWithExtension($class, '.php');
+
+        // Search for Hack files if we are running on HHVM
+        if (false === $file && defined('HHVM_VERSION')) {
+            $file = $this->findFileWithExtension($class, '.hh');
+        }
+
+        if (null !== $this->apcuPrefix) {
+            apcu_add($this->apcuPrefix.$class, $file);
+        }
+
+        if (false === $file) {
+            // Remember that this class does not exist.
+            $this->missingClasses[$class] = true;
+        }
+
+        return $file;
+    }
+
+    private function findFileWithExtension($class, $ext)
+    {
+        // PSR-4 lookup
+        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+        $first = $class[0];
+        if (isset($this->prefixLengthsPsr4[$first])) {
+            $subPath = $class;
+            while (false !== $lastPos = strrpos($subPath, '\\')) {
+                $subPath = substr($subPath, 0, $lastPos);
+                $search = $subPath.'\\';
+                if (isset($this->prefixDirsPsr4[$search])) {
+                    $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
+                    foreach ($this->prefixDirsPsr4[$search] as $dir) {
+                        if (file_exists($file = $dir . $pathEnd)) {
+                            return $file;
+                        }
+                    }
+                }
+            }
+        }
+
+        // PSR-4 fallback dirs
+        foreach ($this->fallbackDirsPsr4 as $dir) {
+            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+                return $file;
+            }
+        }
+
+        // PSR-0 lookup
+        if (false !== $pos = strrpos($class, '\\')) {
+            // namespaced class name
+            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+        } else {
+            // PEAR-like class name
+            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+        }
+
+        if (isset($this->prefixesPsr0[$first])) {
+            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+                if (0 === strpos($class, $prefix)) {
+                    foreach ($dirs as $dir) {
+                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+                            return $file;
+                        }
+                    }
+                }
+            }
+        }
+
+        // PSR-0 fallback dirs
+        foreach ($this->fallbackDirsPsr0 as $dir) {
+            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+                return $file;
+            }
+        }
+
+        // PSR-0 include paths.
+        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+            return $file;
+        }
+
+        return false;
+    }
+}
+
+/**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ */
+function includeFile($file)
+{
+    include $file;
+}
diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..f27399a042d95c4708af3a8c74d35d338763cf8f
--- /dev/null
+++ b/vendor/composer/LICENSE
@@ -0,0 +1,21 @@
+
+Copyright (c) Nils Adermann, Jordi Boggiano
+
+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/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php
new file mode 100644
index 0000000000000000000000000000000000000000..3a1416586929fd3c2294d13d5c5f8fc3dfb6cda5
--- /dev/null
+++ b/vendor/composer/autoload_classmap.php
@@ -0,0 +1,16 @@
+<?php
+
+// autoload_classmap.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+    'ArithmeticError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php',
+    'AssertionError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/AssertionError.php',
+    'DivisionByZeroError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php',
+    'Error' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/Error.php',
+    'ParseError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/ParseError.php',
+    'SessionUpdateTimestampHandlerInterface' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php',
+    'TypeError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/TypeError.php',
+);
diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php
new file mode 100644
index 0000000000000000000000000000000000000000..776c6b55fcf53fa2cd3a6a30bc5f8ab08916bf25
--- /dev/null
+++ b/vendor/composer/autoload_files.php
@@ -0,0 +1,12 @@
+<?php
+
+// autoload_files.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+    '5255c38a0faeba867671b61dfda6d864' => $vendorDir . '/paragonie/random_compat/lib/random.php',
+    '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
+    '023d27dca8066ef29e6739335ea73bad' => $vendorDir . '/symfony/polyfill-php70/bootstrap.php',
+);
diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php
new file mode 100644
index 0000000000000000000000000000000000000000..90aac30367a146fdcd28dc73edfd5c0503a9ad40
--- /dev/null
+++ b/vendor/composer/autoload_namespaces.php
@@ -0,0 +1,10 @@
+<?php
+
+// autoload_namespaces.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+    'HAB\\Pica' => array($vendorDir . '/hab/picareader/src', $vendorDir . '/hab/picarecord/src', $vendorDir . '/hab/picawriter/src'),
+);
diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php
new file mode 100644
index 0000000000000000000000000000000000000000..f9ff74ebe09f8a86b860fc8866b986fb36906f79
--- /dev/null
+++ b/vendor/composer/autoload_psr4.php
@@ -0,0 +1,12 @@
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+    'Symfony\\Polyfill\\Php70\\' => array($vendorDir . '/symfony/polyfill-php70'),
+    'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
+    'Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'),
+);
diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php
new file mode 100644
index 0000000000000000000000000000000000000000..8887717c2da5130e0f7fae60238e23ec9ea4b680
--- /dev/null
+++ b/vendor/composer/autoload_real.php
@@ -0,0 +1,70 @@
+<?php
+
+// autoload_real.php @generated by Composer
+
+class ComposerAutoloaderInit78daec5e1761b48dba06a78068db3ccf
+{
+    private static $loader;
+
+    public static function loadClassLoader($class)
+    {
+        if ('Composer\Autoload\ClassLoader' === $class) {
+            require __DIR__ . '/ClassLoader.php';
+        }
+    }
+
+    public static function getLoader()
+    {
+        if (null !== self::$loader) {
+            return self::$loader;
+        }
+
+        spl_autoload_register(array('ComposerAutoloaderInit78daec5e1761b48dba06a78068db3ccf', 'loadClassLoader'), true, true);
+        self::$loader = $loader = new \Composer\Autoload\ClassLoader();
+        spl_autoload_unregister(array('ComposerAutoloaderInit78daec5e1761b48dba06a78068db3ccf', 'loadClassLoader'));
+
+        $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
+        if ($useStaticLoader) {
+            require_once __DIR__ . '/autoload_static.php';
+
+            call_user_func(\Composer\Autoload\ComposerStaticInit78daec5e1761b48dba06a78068db3ccf::getInitializer($loader));
+        } else {
+            $map = require __DIR__ . '/autoload_namespaces.php';
+            foreach ($map as $namespace => $path) {
+                $loader->set($namespace, $path);
+            }
+
+            $map = require __DIR__ . '/autoload_psr4.php';
+            foreach ($map as $namespace => $path) {
+                $loader->setPsr4($namespace, $path);
+            }
+
+            $classMap = require __DIR__ . '/autoload_classmap.php';
+            if ($classMap) {
+                $loader->addClassMap($classMap);
+            }
+        }
+
+        $loader->register(true);
+
+        if ($useStaticLoader) {
+            $includeFiles = Composer\Autoload\ComposerStaticInit78daec5e1761b48dba06a78068db3ccf::$files;
+        } else {
+            $includeFiles = require __DIR__ . '/autoload_files.php';
+        }
+        foreach ($includeFiles as $fileIdentifier => $file) {
+            composerRequire78daec5e1761b48dba06a78068db3ccf($fileIdentifier, $file);
+        }
+
+        return $loader;
+    }
+}
+
+function composerRequire78daec5e1761b48dba06a78068db3ccf($fileIdentifier, $file)
+{
+    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
+        require $file;
+
+        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
+    }
+}
diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php
new file mode 100644
index 0000000000000000000000000000000000000000..207e20e69bbfdf725337b5713e7e93f8307b4e28
--- /dev/null
+++ b/vendor/composer/autoload_static.php
@@ -0,0 +1,71 @@
+<?php
+
+// autoload_static.php @generated by Composer
+
+namespace Composer\Autoload;
+
+class ComposerStaticInit78daec5e1761b48dba06a78068db3ccf
+{
+    public static $files = array (
+        '5255c38a0faeba867671b61dfda6d864' => __DIR__ . '/..' . '/paragonie/random_compat/lib/random.php',
+        '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
+        '023d27dca8066ef29e6739335ea73bad' => __DIR__ . '/..' . '/symfony/polyfill-php70/bootstrap.php',
+    );
+
+    public static $prefixLengthsPsr4 = array (
+        'S' => 
+        array (
+            'Symfony\\Polyfill\\Php70\\' => 23,
+            'Symfony\\Polyfill\\Mbstring\\' => 26,
+            'Symfony\\Component\\HttpFoundation\\' => 33,
+        ),
+    );
+
+    public static $prefixDirsPsr4 = array (
+        'Symfony\\Polyfill\\Php70\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/symfony/polyfill-php70',
+        ),
+        'Symfony\\Polyfill\\Mbstring\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
+        ),
+        'Symfony\\Component\\HttpFoundation\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/symfony/http-foundation',
+        ),
+    );
+
+    public static $prefixesPsr0 = array (
+        'H' => 
+        array (
+            'HAB\\Pica' => 
+            array (
+                0 => __DIR__ . '/..' . '/hab/picareader/src',
+                1 => __DIR__ . '/..' . '/hab/picarecord/src',
+                2 => __DIR__ . '/..' . '/hab/picawriter/src',
+            ),
+        ),
+    );
+
+    public static $classMap = array (
+        'ArithmeticError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php',
+        'AssertionError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/AssertionError.php',
+        'DivisionByZeroError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php',
+        'Error' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/Error.php',
+        'ParseError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/ParseError.php',
+        'SessionUpdateTimestampHandlerInterface' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php',
+        'TypeError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/TypeError.php',
+    );
+
+    public static function getInitializer(ClassLoader $loader)
+    {
+        return \Closure::bind(function () use ($loader) {
+            $loader->prefixLengthsPsr4 = ComposerStaticInit78daec5e1761b48dba06a78068db3ccf::$prefixLengthsPsr4;
+            $loader->prefixDirsPsr4 = ComposerStaticInit78daec5e1761b48dba06a78068db3ccf::$prefixDirsPsr4;
+            $loader->prefixesPsr0 = ComposerStaticInit78daec5e1761b48dba06a78068db3ccf::$prefixesPsr0;
+            $loader->classMap = ComposerStaticInit78daec5e1761b48dba06a78068db3ccf::$classMap;
+
+        }, null, ClassLoader::class);
+    }
+}
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
new file mode 100644
index 0000000000000000000000000000000000000000..ddc76dc70045bc3b65b40e6369a432bdac69b3c2
--- /dev/null
+++ b/vendor/composer/installed.json
@@ -0,0 +1,344 @@
+[
+    {
+        "name": "hab/picareader",
+        "version": "v1.2.0",
+        "version_normalized": "1.2.0.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/dmj/PicaReader.git",
+            "reference": "b5b669ccee96a1fdd6b85045795818f27867a684"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/dmj/PicaReader/zipball/b5b669ccee96a1fdd6b85045795818f27867a684",
+            "reference": "b5b669ccee96a1fdd6b85045795818f27867a684",
+            "shasum": ""
+        },
+        "require": {
+            "hab/picarecord": "~1.0"
+        },
+        "time": "2017-10-27T06:40:13+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "psr-0": {
+                "HAB\\Pica": "src/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "GPL-3.0+"
+        ],
+        "authors": [
+            {
+                "name": "David Maus",
+                "email": "maus@hab.de",
+                "role": "developer"
+            }
+        ],
+        "description": "Classes for reading Pica+ records encoded in Pica, PicaXML and PicaPlain"
+    },
+    {
+        "name": "hab/picarecord",
+        "version": "v1.1.1",
+        "version_normalized": "1.1.1.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/dmj/PicaRecord.git",
+            "reference": "a00e195d8f0f1b41d2bdd385334b0f88975e07c1"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/dmj/PicaRecord/zipball/a00e195d8f0f1b41d2bdd385334b0f88975e07c1",
+            "reference": "a00e195d8f0f1b41d2bdd385334b0f88975e07c1",
+            "shasum": ""
+        },
+        "time": "2017-01-02T10:15:57+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "psr-0": {
+                "HAB\\Pica": "src/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "GPL-3.0+"
+        ],
+        "authors": [
+            {
+                "name": "David Maus",
+                "email": "maus@hab.de",
+                "role": "developer"
+            }
+        ],
+        "description": "Object oriented interface to Pica+ records, fields, and subfields"
+    },
+    {
+        "name": "hab/picawriter",
+        "version": "v1.0.0",
+        "version_normalized": "1.0.0.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/dmj/PicaWriter.git",
+            "reference": "8744cf9b24f20cb9a3430cc675f867b915ddcffa"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/dmj/PicaWriter/zipball/8744cf9b24f20cb9a3430cc675f867b915ddcffa",
+            "reference": "8744cf9b24f20cb9a3430cc675f867b915ddcffa",
+            "shasum": ""
+        },
+        "require": {
+            "hab/picarecord": "~1.0"
+        },
+        "time": "2015-08-07T09:17:03+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "psr-0": {
+                "HAB\\Pica": "src/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "GPL-3.0+"
+        ],
+        "authors": [
+            {
+                "name": "David Maus",
+                "email": "maus@hab.de",
+                "role": "developer"
+            }
+        ],
+        "description": "Classes for writing Pica+ records to PicaXML and PicaPlain"
+    },
+    {
+        "name": "paragonie/random_compat",
+        "version": "v2.0.12",
+        "version_normalized": "2.0.12.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/paragonie/random_compat.git",
+            "reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/paragonie/random_compat/zipball/258c89a6b97de7dfaf5b8c7607d0478e236b04fb",
+            "reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.2.0"
+        },
+        "require-dev": {
+            "phpunit/phpunit": "4.*|5.*"
+        },
+        "suggest": {
+            "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
+        },
+        "time": "2018-04-04T21:24:14+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "files": [
+                "lib/random.php"
+            ]
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Paragon Initiative Enterprises",
+                "email": "security@paragonie.com",
+                "homepage": "https://paragonie.com"
+            }
+        ],
+        "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
+        "keywords": [
+            "csprng",
+            "pseudorandom",
+            "random"
+        ]
+    },
+    {
+        "name": "symfony/http-foundation",
+        "version": "v3.4.8",
+        "version_normalized": "3.4.8.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/symfony/http-foundation.git",
+            "reference": "b11e6d165ff4cbf5685d185ab19a90f2f3bb7d1e"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/symfony/http-foundation/zipball/b11e6d165ff4cbf5685d185ab19a90f2f3bb7d1e",
+            "reference": "b11e6d165ff4cbf5685d185ab19a90f2f3bb7d1e",
+            "shasum": ""
+        },
+        "require": {
+            "php": "^5.5.9|>=7.0.8",
+            "symfony/polyfill-mbstring": "~1.1",
+            "symfony/polyfill-php70": "~1.6"
+        },
+        "require-dev": {
+            "symfony/expression-language": "~2.8|~3.0|~4.0"
+        },
+        "time": "2018-04-03T05:22:50+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "3.4-dev"
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Symfony\\Component\\HttpFoundation\\": ""
+            },
+            "exclude-from-classmap": [
+                "/Tests/"
+            ]
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Fabien Potencier",
+                "email": "fabien@symfony.com"
+            },
+            {
+                "name": "Symfony Community",
+                "homepage": "https://symfony.com/contributors"
+            }
+        ],
+        "description": "Symfony HttpFoundation Component",
+        "homepage": "https://symfony.com"
+    },
+    {
+        "name": "symfony/polyfill-mbstring",
+        "version": "v1.7.0",
+        "version_normalized": "1.7.0.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/symfony/polyfill-mbstring.git",
+            "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/78be803ce01e55d3491c1397cf1c64beb9c1b63b",
+            "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.3.3"
+        },
+        "suggest": {
+            "ext-mbstring": "For best performance"
+        },
+        "time": "2018-01-30T19:27:44+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "1.7-dev"
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Symfony\\Polyfill\\Mbstring\\": ""
+            },
+            "files": [
+                "bootstrap.php"
+            ]
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Nicolas Grekas",
+                "email": "p@tchwork.com"
+            },
+            {
+                "name": "Symfony Community",
+                "homepage": "https://symfony.com/contributors"
+            }
+        ],
+        "description": "Symfony polyfill for the Mbstring extension",
+        "homepage": "https://symfony.com",
+        "keywords": [
+            "compatibility",
+            "mbstring",
+            "polyfill",
+            "portable",
+            "shim"
+        ]
+    },
+    {
+        "name": "symfony/polyfill-php70",
+        "version": "v1.7.0",
+        "version_normalized": "1.7.0.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/symfony/polyfill-php70.git",
+            "reference": "3532bfcd8f933a7816f3a0a59682fc404776600f"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/3532bfcd8f933a7816f3a0a59682fc404776600f",
+            "reference": "3532bfcd8f933a7816f3a0a59682fc404776600f",
+            "shasum": ""
+        },
+        "require": {
+            "paragonie/random_compat": "~1.0|~2.0",
+            "php": ">=5.3.3"
+        },
+        "time": "2018-01-30T19:27:44+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "1.7-dev"
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Symfony\\Polyfill\\Php70\\": ""
+            },
+            "files": [
+                "bootstrap.php"
+            ],
+            "classmap": [
+                "Resources/stubs"
+            ]
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Nicolas Grekas",
+                "email": "p@tchwork.com"
+            },
+            {
+                "name": "Symfony Community",
+                "homepage": "https://symfony.com/contributors"
+            }
+        ],
+        "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions",
+        "homepage": "https://symfony.com",
+        "keywords": [
+            "compatibility",
+            "polyfill",
+            "portable",
+            "shim"
+        ]
+    }
+]
diff --git a/vendor/hab/picareader/.eproject b/vendor/hab/picareader/.eproject
new file mode 100644
index 0000000000000000000000000000000000000000..fac30dda3f7eae9bb30dac818e12c592b495add2
--- /dev/null
+++ b/vendor/hab/picareader/.eproject
@@ -0,0 +1 @@
+:project-name "PicaReader"
diff --git a/vendor/hab/picareader/.gitignore b/vendor/hab/picareader/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..1042105475ae0e18e9fe3b7d2363043c67844644
--- /dev/null
+++ b/vendor/hab/picareader/.gitignore
@@ -0,0 +1,11 @@
+*~
+\#*
+.\#*
+TAGS
+ChangeLog
+vendor
+review
+build
+composer.phar
+composer.lock
+test.php
diff --git a/vendor/hab/picareader/COPYING b/vendor/hab/picareader/COPYING
new file mode 100644
index 0000000000000000000000000000000000000000..94a9ed024d3859793618152ea559a168bbcbb5e2
--- /dev/null
+++ b/vendor/hab/picareader/COPYING
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/vendor/hab/picareader/README.org b/vendor/hab/picareader/README.org
new file mode 100644
index 0000000000000000000000000000000000000000..c0e7f2cefcf80f6d4430773c11d31379879552b2
--- /dev/null
+++ b/vendor/hab/picareader/README.org
@@ -0,0 +1,83 @@
+#+TITLE: PicaReader -- Classes for reading Pica+ records
+#+AUTHOR: David Maus
+#+EMAIL: maus@hab.de
+
+* About
+
+PicaReader provides classes for reading Pica+ records encoded in PicaXML and PicaPlain.
+
+PicaReader is copyright (c) 2012-2017 by Herzog August Bibliothek Wolfenbüttel and released under the
+terms of the GNU General Public License v3.
+
+* Installation
+
+You can install PicaReader via Composer.
+
+#+BEGIN_EXAMPLE
+composer require hab/picareader
+#+END_EXAMPLE
+
+* Usage
+
+All readers adhere to the same interface. You open the reader with a string of input data by calling
+=Reader::open()= and can call =Reader::read()= to read the next record in the input data. If the
+input does not contain (anymore) records =Reader::read()= returns =FALSE=. Otherwise it returns
+either a record object created with PicaRecord's =Record::factory()= function.
+
+#+BEGIN_SRC php
+  $reader = new \HAB\Pica\Reader\PicaXmlReader()
+  $reader->open(file_get_contents('http://unapi.gbv.de?id=opac-de-23:ppn:635012286&format=picaxml'));
+  $record = $reader->read();
+  $reader->close();
+#+END_SRC
+
+To filter out records or fields you can attach a filter to the reader via =Reader::setFilter()=. A
+filter is any valid PHP callback that takes an associative array representing the record as argument
+and returns a possibly modified array or =FALSE= if the entire record should be skipped.
+
+The array representation of a record is defined as follows:
+
+#+BEGIN_EXAMPLE
+RECORD   := array('fields' => array(FIELD, …))
+FIELD    := array('tag' => TAG, 'occurrence' => OCCURRENCE, 'subfields' => array(SUBFIELD, …))
+SUBFIELD := array('code' => CODE, 'value' => VALUE)
+#+END_EXAMPLE
+
+Where =TAG=, =OCCURRENCE=, =CODE=, and =VALUE= are the respective properties of a Pica+ field or
+subfield.
+
+For example, if your source delivers malformed PicaXML records like so:
+
+#+BEGIN_SRC xml
+  <?xml version="1.0" encoding="UTF-8"?>
+  <record xmlns="info:srw/schema/5/picaXML-v1.0">
+    <datafield tag="">
+    </datafield>
+    <datafield tag="001A">
+      <subfield code="0">0001:14-09-10</subfield>
+    </datafield>
+    …
+  </record>
+#+END_SRC
+
+You can attach a filter function to remove these fields with an invalid tag:
+
+#+BEGIN_SRC php
+  $reader = new PicaXmlReader();
+  $reader->setFilter(function (array $r) { 
+      return array('fields' => array_filter($r['fields'],
+                                            function (array $f) {
+                                              return isset($f['tag']) && \HAB\Pica\Record\Field::isValidFieldTag($f['tag']);
+                                            }));
+    });
+  $record = $reader->read(…);
+  $reader->close();
+#+END_SRC
+
+* Acknowledgements
+
+Large parts of this package would not have been possible without studying the source of
+[[http://search.cpan.org/dist/PICA-Record/][Pica::Record]], an open source Perl library for handling Pica+ records by Jakob Voß, and the practical
+knowledge of our library's catalogers.
+
+* Footnotes
diff --git a/vendor/hab/picareader/composer.json b/vendor/hab/picareader/composer.json
new file mode 100644
index 0000000000000000000000000000000000000000..de7c298f2290b0074560bf4208a0b1fa971ad7e4
--- /dev/null
+++ b/vendor/hab/picareader/composer.json
@@ -0,0 +1,24 @@
+{
+    "name": "hab/picareader",
+    "description": "Classes for reading Pica+ records encoded in Pica, PicaXML and PicaPlain",
+    "type": "library",
+    "license": "GPL-3.0+",
+    "authors": [
+	{
+	    "name": "David Maus",
+	    "email": "maus@hab.de",
+	    "role": "Developer"
+	}
+    ],
+    "support": {
+	"email": "maus@hab.de"
+    },
+    "require": {
+        "hab/picarecord": "~1.0"
+    },
+    "autoload": {
+        "psr-0": {
+            "HAB\\Pica": "src/"
+        }
+    }
+}
diff --git a/vendor/hab/picareader/phpunit.xml b/vendor/hab/picareader/phpunit.xml
new file mode 100644
index 0000000000000000000000000000000000000000..16d058a388443b5bc7d54508aa1c8321d6314561
--- /dev/null
+++ b/vendor/hab/picareader/phpunit.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<phpunit bootstrap="tests/bootstrap.php" strict="true">
+  <testsuites>
+    <testsuite name="Unit Tests">
+      <directory suffix="Test.php">tests</directory>
+    </testsuite>
+  </testsuites>
+  <filter>
+    <blacklist>
+      <directory suffix=".php">vendor</directory>
+      <directory suffix=".php">tests</directory>
+    </blacklist>
+    <whitelist addUncoveredFilesFromWhitelist="true">
+      <directory suffix=".php">bin</directory>
+      <directory suffix=".php">src</directory>
+    </whitelist>
+  </filter>
+  <logging>
+    <log type="coverage-html" target="review/code-coverage"/>
+  </logging>
+</phpunit>
diff --git a/vendor/hab/picareader/src/.empty b/vendor/hab/picareader/src/.empty
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/vendor/hab/picareader/src/HAB/Pica/Parser/PicaPlainParser.php b/vendor/hab/picareader/src/HAB/Pica/Parser/PicaPlainParser.php
new file mode 100644
index 0000000000000000000000000000000000000000..87c8b92e7d1f597c17b556d732115b7d1e7af1aa
--- /dev/null
+++ b/vendor/hab/picareader/src/HAB/Pica/Parser/PicaPlainParser.php
@@ -0,0 +1,95 @@
+<?php
+
+/**
+ * The PicaPlainParser class file.
+ *
+ * This file is part of PicaReader.
+ *
+ * PicaReader is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaReader is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaReader.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   PicaReader
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2012 - 2017 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.html GNU General Public License v3
+ */
+
+namespace HAB\Pica\Parser;
+
+use RuntimeException;
+
+class PicaPlainParser implements PicaPlainParserInterface
+{
+
+    /**
+     * {@inheritDoc}
+     */
+    public function parseField ($line) 
+    {
+        $field = array('subfields' => array());
+        $match = array();
+        if (preg_match('#^([012][0-9]{2}[A-Z@])(/([0-9]{2}))?\s+(\$.*)$#Du', $line, $match)) {
+            $field = array('tag' => $match[1],
+                           'occurrence' => $match[3] ?: null,
+                           'subfields' => $this->parseSubfields($match[4]));;
+        } else {
+            throw new RuntimeException("Invalid characters in PicaPlain record at line: {$line}");
+        }
+        return $field;
+    }
+
+    public function parseSubfields ($str) 
+    {
+        $subfields = array();
+        $subfield = null;
+        $pos = 0;
+        $max = strlen($str);
+        $state = '$';
+        do {
+            switch ($state) {
+                case '$':
+                    if (is_array($subfield)) {
+                        $subfields []= $subfield;
+                        $subfield = array();
+                    }
+                    $pos += 1;
+                    $state = 'code';
+                    break;
+                case 'code':
+                    $subfield['code'] = $str[$pos];
+                    $subfield['value'] = '';
+                    $pos += 1;
+                    $state = 'value';
+                    break;
+                case 'value':
+                    $next = strpos($str, '$', $pos);
+                    if ($next === false) {
+                        $subfield['value'] .= substr($str, $pos);
+                        $pos = $max;
+                    } else {
+                        $subfield['value'] .= substr($str, $pos, ($next - $pos));
+                        $pos = $next;
+                        if (isset($str[$pos + 1]) && $str[$pos + 1] === '$') {
+                            $subfield['value'] .= '$';
+                            $pos += 2;
+                        } else {
+                            $state = '$';
+                        }
+                    }
+                    break;
+            }
+        } while ($pos < $max);
+        $subfields []= $subfield;
+        return $subfields;
+    }
+}
\ No newline at end of file
diff --git a/vendor/hab/picareader/src/HAB/Pica/Parser/PicaPlainParserInterface.php b/vendor/hab/picareader/src/HAB/Pica/Parser/PicaPlainParserInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..66368140eb3da0764972952d782d727148cea6f5
--- /dev/null
+++ b/vendor/hab/picareader/src/HAB/Pica/Parser/PicaPlainParserInterface.php
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * This file is part of PicaReader.
+ *
+ * PicaReader is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaReader is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaReader.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @author    David Maus <maus@hab.de>
+ * @copyright (c) 2016 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.txt GNU General Public License v3 or higher
+ */
+
+namespace HAB\Pica\Parser;
+
+/**
+ * Interface of PicaPlain parsers.
+ *
+ * @author    David Maus <maus@hab.de>
+ * @copyright (c) 2016 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.txt GNU General Public License v3 or higher
+ */
+interface PicaPlainParserInterface
+{
+    /**
+     * Return array representation of the field encoded in a line.
+     *
+     * @throws RuntimeException Invalid characters in line
+     *
+     * @param  string $line PicaPlain record line
+     * @return array
+     */
+    public function parseField ($line);
+
+    /**
+     * Return array of array representations of the subfields encode in argument.
+     *
+     * @param  string $str Encoded subfields
+     * @return array
+     */
+    public function parseSubfields ($str);
+}
\ No newline at end of file
diff --git a/vendor/hab/picareader/src/HAB/Pica/Reader/PicaNormReader.php b/vendor/hab/picareader/src/HAB/Pica/Reader/PicaNormReader.php
new file mode 100644
index 0000000000000000000000000000000000000000..723748fe28fca79b3b503d383641f1f07acd246b
--- /dev/null
+++ b/vendor/hab/picareader/src/HAB/Pica/Reader/PicaNormReader.php
@@ -0,0 +1,256 @@
+<?php
+
+/**
+ * Reader for normalized Pica+ records.
+ *
+ * @see http://www.gbv.de/wikis/cls/PICA%2B#Normalisiertes_PICA.2B
+ *
+ * This file is part of PicaReader.
+ *
+ * PicaReader is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaReader is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaReader.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2013 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.txt GNU General Public License v3
+ */
+
+namespace HAB\Pica\Reader;
+
+use RuntimeException;
+use InvalidArgumentException;
+
+class PicaNormReader extends Reader
+{
+    /**
+     * Separators.
+     *
+     * @var string
+     */
+    const RECORD_SEPARATOR = "\x1d";
+    const FIELD_SEPARATOR  = "\x1e";
+    const SUBFIELD_SEPARATOR = "\x1f";
+
+    /**
+     * Input stream.
+     *
+     * @var resource
+     */
+    private $stream;
+
+    /**
+     * Read-buffer.
+     *
+     * @var string
+     */
+    private $buffer;
+
+    /**
+     * Read-buffer size.
+     *
+     * @var integer
+     */
+    private $bufferSize;
+
+    /**
+     * Position in read-buffer.
+     *
+     * @var integer
+     */
+    private $bufferPosition;
+
+    /**
+     * Regular expression to split a field.
+     *
+     * @var string
+     */
+    private $fieldRegexp = "|^([012][0-9]{2}[A-Z@])(/([0-9]{2}))? \x1f(.+)$|uD";
+
+    /**
+     * Constructor.
+     *
+     * @return void
+     */
+    public function __construct ()
+    {}
+
+    /**
+     * Open the reader with input stream.
+     *
+     * @throws InvalidArgumentException Invalid stream type
+     * @throws InvalidArgumentException Argument neither string nor stream
+     *
+     * @param  resource|string $stream
+     * @return void
+     */
+    public function open ($stream)
+    {
+        if (is_string($stream)) {
+            $stream = fopen('data://text/plain;base64,' . base64_encode($stream), 'rb');
+        }
+        if (!is_resource($stream)) {
+            throw new InvalidArgumentException(sprintf('Invalid type of argument: resource|string, %s', gettype($stream)));
+        }
+        $meta = stream_get_meta_data($stream);
+        if ($meta['stream_type'] !== 'STDIO' && $meta['stream_type'] !== 'RFC2397') {
+            throw new InvalidArgumentException(sprintf('Invalid stream type: STDIO|RFC297, %s', $meta['stream_type']));
+        }
+        $this->buffer         = null;
+        $this->stream         = $stream;
+        $this->bufferSize     = 0;
+        $this->bufferPosition = 0;
+        // Skip over preceeding whitespace
+        while (!ctype_alnum($this->getc(true))) {
+            $this->getc();
+        }
+    }
+
+    /**
+     * Close reader.
+     *
+     * @return void
+     */
+    public function close ()
+    {
+        if ($this->stream) {
+            fclose($this->stream);
+        }
+    }
+
+    /**
+     * Return next record from input stream.
+     *
+     * @return array
+     */
+    protected function next ()
+    {
+        if ($this->feof()) {
+            return false;
+        }
+
+        $record = array();
+        while (!$this->feof() && $this->peek() !== self::RECORD_SEPARATOR) {
+            $field = $this->field();
+            if ($field) {
+                $record['fields'] []= $field;
+            }
+        }
+        if (!$this->feof()) {
+            // Swallow record separator
+            $this->getc();
+        }
+        return empty($record) ? false : $record;
+    }
+
+    ///
+
+    /**
+     * Return Pica+ field.
+     *
+     * @return array|null
+     */
+    private function field ()
+    {
+        if ($this->feof()) {
+            return false;
+        }
+
+        $line  = '';
+        while (!$this->feof() && $this->peek() !== self::FIELD_SEPARATOR) {
+            $octet = $this->getc();
+            if ($octet !== null) {
+                $line .= $octet;
+            }
+        }
+        if (!$this->feof()) {
+            // Swallow field separator
+            $this->getc();
+        }
+
+        $matches = array();
+        if (!preg_match($this->fieldRegexp, $line, $matches)) {
+            throw new RuntimeException(sprintf('Unexpected data in input stream: %s', $line));
+        }
+        $subfields = array_map(array($this, 'splitSubfield'), explode(self::SUBFIELD_SEPARATOR, $matches[4]));
+        $field = array(
+            'tag' => $matches[1],
+            'occurrence' => $matches[3] ?: null,
+            'subfields' => $subfields
+        );
+        return $field;
+    }
+
+    /**
+     * Split subfields into array structures.
+     *
+     * @param  string $subfield
+     * @return array
+     */
+    private function splitSubfield ($subfield)
+    {
+        return array('code' => $subfield[0], 'value' => substr($subfield, 1));
+    }
+
+    /**
+     * Return next octet without moving pointer.
+     *
+     * @return string|null
+     */
+    private function peek ()
+    {
+        return $this->getc(true);
+    }
+
+    /**
+     * Return next octet.
+     *
+     * If argument is true, the internal pointer is not moved after reading
+     * the octet.
+     *
+     * @param  boolean $peek
+     * @return string|null
+     */
+    private function getc ($peek = false)
+    {
+        if ($this->feof()) {
+            return null;
+        }
+        if ($this->bufferPosition == $this->bufferSize) {
+            $buffer = fread($this->stream, 4096);
+            if ($buffer === false) {
+                throw new RuntimeException('Error reading input stream');
+            }
+            if (strlen($buffer) === 0) {
+                return null;
+            }
+            $this->bufferPosition = 0;
+            $this->bufferSize = strlen($buffer);
+            $this->buffer = $buffer;
+        }
+        $octet = $this->buffer[$this->bufferPosition];
+        if (!$peek) {
+            $this->bufferPosition++;
+        }
+        return $octet;
+    }
+
+    /**
+     * Return true if input stream and read-buffer exhausted.
+     *
+     * @return boolean
+     */
+    private function feof ()
+    {
+        return (feof($this->stream) && ($this->bufferPosition == $this->bufferSize));
+    }
+}
\ No newline at end of file
diff --git a/vendor/hab/picareader/src/HAB/Pica/Reader/PicaPlainReader.php b/vendor/hab/picareader/src/HAB/Pica/Reader/PicaPlainReader.php
new file mode 100644
index 0000000000000000000000000000000000000000..d051715677960f06d7ebc1171b327b10da0cc0e4
--- /dev/null
+++ b/vendor/hab/picareader/src/HAB/Pica/Reader/PicaPlainReader.php
@@ -0,0 +1,106 @@
+<?php
+
+/**
+ * The PicaPlainReader class file.
+ *
+ * This file is part of PicaReader.
+ *
+ * PicaReader is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaReader is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaReader.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   PicaReader
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2012 - 2017 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.html GNU General Public License v3
+ */
+
+namespace HAB\Pica\Reader;
+
+use HAB\Pica\Parser\PicaPlainParserInterface;
+use HAB\Pica\Parser\PicaPlainParser;
+
+class PicaPlainReader extends Reader
+{
+
+    /**
+     * Regular expression matching lines that should be ignore.
+     *
+     * @var string
+     */
+    public $ignoreLineRegexp = '/^$/';
+
+    /**
+     * Current input data.
+     *
+     * @var string
+     */
+    protected $_data;
+
+    /**
+     * Parser instance.
+     *
+     * @var PicaPlainParser
+     */
+    private $_parser;
+
+    /**
+     * Constructor.
+     *
+     * @param  PicaPlainParserInterface $parser Optional parser instance
+     * @return void
+     */
+    public function __construct (PicaPlainParserInterface $parser = null)
+    {
+        $this->_parser = $parser ?: new PicaPlainParser();
+    }
+
+    /**
+     * Open the reader with input stream.
+     *
+     * @param  resource|string $stream
+     * @return void
+     */
+    public function open ($data)
+    {
+        $this->_data = preg_split("/(?:\n\r|[\n\r])/", $data);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected function next ()
+    {
+        $record = false;
+        if (current($this->_data) !== false) {
+            $record = array('fields' => array());
+            do {
+                $line = current($this->_data);
+                if (!preg_match($this->ignoreLineRegexp, $line)) {
+                    $record['fields'] []= $this->_parser->parseField($line);
+                }
+            } while (next($this->_data));
+            next($this->_data);
+        }
+        return $record;
+    }
+
+    /**
+     * Close reader.
+     *
+     * @return void
+     */
+    public function close ()
+    {
+        $this->_data = null;
+    }
+}
diff --git a/vendor/hab/picareader/src/HAB/Pica/Reader/PicaXmlReader.php b/vendor/hab/picareader/src/HAB/Pica/Reader/PicaXmlReader.php
new file mode 100644
index 0000000000000000000000000000000000000000..2d7bf93546ecf76a796eea6d84c81d975fb9fc55
--- /dev/null
+++ b/vendor/hab/picareader/src/HAB/Pica/Reader/PicaXmlReader.php
@@ -0,0 +1,171 @@
+<?php
+
+/**
+ * The PicaXmlReader class file.
+ *
+ * This file is part of PicaReader.
+ *
+ * PicaReader is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaReader is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaReader.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   PicaReader
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2012 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.html GNU General Public License v3
+ */
+
+namespace HAB\Pica\Reader;
+
+use XMLReader;
+
+class PicaXmlReader extends Reader
+{
+
+    /**
+     * @var string XML namespace URI of PicaXML
+     */
+    const PICAXML_NAMESPACE_URI = 'info:srw/schema/5/picaXML-v1.0';
+
+    /**
+     * @var XMLReader XML Reader instance
+     */
+    private $_xmlReader;
+
+    /**
+     * Constructor.
+     *
+     * @return void
+     */
+    public function __construct ()
+    {
+        parent::__construct();
+        $this->_xmlReader = new XMLReader();
+    }
+
+    /**
+     * Open the reader with input stream.
+     *
+     * @param  resource|string $stream
+     * @return void
+     */
+    public function open ($stream)
+    {
+        if (is_resource($stream)) {
+            $stream = stream_get_contents($stream);
+        }
+        $this->_xmlReader->xml($stream);
+    }
+
+    /**
+     * Close current input data.
+     *
+     * @return void
+     */
+    public function close ()
+    {
+        $this->_xmlReader->close();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected function next ()
+    {
+        if ($this->forwardTo('record', self::PICAXML_NAMESPACE_URI)) {
+            $record = array('fields' => array());
+            while (!$this->atElementEnd('record', self::PICAXML_NAMESPACE_URI) && $this->_xmlReader->read()) {
+                if ($this->atElement('datafield', self::PICAXML_NAMESPACE_URI)) {
+                    $record['fields'] []= $this->readField();
+                }
+            }
+        } else {
+            $record = false;
+        }
+        return $record;
+    }
+
+    /**
+     * Return array representation of datafield at cursor.
+     *
+     * The cursor is expected to be positioned on the opening field element.
+     *
+     * @return array
+     */
+    protected function readField ()
+    {
+        $field = array('tag' => $this->_xmlReader->getAttribute('tag'),
+                       'occurrence' => $this->_xmlReader->getAttribute('occurrence'),
+                       'subfields' => array());
+        while (!$this->atElementEnd('datafield', self::PICAXML_NAMESPACE_URI) && $this->_xmlReader->read()) {
+            if ($this->atElement('subfield', self::PICAXML_NAMESPACE_URI)) {
+                $subfield = array('code' => $this->_xmlReader->getAttribute('code'), 'value' => '');
+                while (!$this->atElementEnd('subfield', self::PICAXML_NAMESPACE_URI) && $this->_xmlReader->read()) {
+                    switch ($this->_xmlReader->nodeType) {
+                        case XMLReader::TEXT:
+                        case XMLReader::SIGNIFICANT_WHITESPACE:
+                        case XMLReader::CDATA:
+                            $subfield['value'] .= $this->_xmlReader->value;
+                            break;
+                    }
+                }
+                $field['subfields'] []= $subfield;
+            }
+        }
+        return $field;
+    }
+
+    /**
+     * Move cursor forward to named element.
+     *
+     * The cursor is not moved if it is already positioned at the named
+     * element. Returns true if cursor is at specified element and false if
+     * cursor reached the end of the document
+     *
+     * @param  string $name Element local name
+     * @param  string $uri  Namespace URI
+     * @return boolean
+     */
+    protected function forwardTo ($name, $uri)
+    {
+        while (!$this->atElement($name, $uri) && $this->_xmlReader->read()) { }
+        return ($this->_xmlReader->nodeType === XMLReader::ELEMENT);
+    }
+
+    /**
+     * Return true if the cursor is positioned at the named element.
+     *
+     * @param  string $name Element local name
+     * @param  string $uri  Namespace URI
+     * @return boolean
+     */
+    protected function atElement ($name, $uri)
+    {
+        return ($this->_xmlReader->nodeType === XMLReader::ELEMENT &&
+                $this->_xmlReader->localName === $name &&
+                $this->_xmlReader->namespaceURI === $uri);
+    }
+
+    /**
+     * Return TRUE if the cursor is positioned at the end of the named element.
+     *
+     * @param  string $name Element local name
+     * @param  string $uri  Namespace URI
+     * @return boolean
+     */
+    protected function atElementEnd ($name, $uri)
+    {
+        return ($this->_xmlReader->nodeType === XMLReader::END_ELEMENT &&
+                $this->_xmlReader->localName === $name &&
+                $this->_xmlReader->namespaceURI === $uri);
+    }
+}
diff --git a/vendor/hab/picareader/src/HAB/Pica/Reader/Reader.php b/vendor/hab/picareader/src/HAB/Pica/Reader/Reader.php
new file mode 100644
index 0000000000000000000000000000000000000000..c202f37e4178f338b4617d28c7fb54e9172360b0
--- /dev/null
+++ b/vendor/hab/picareader/src/HAB/Pica/Reader/Reader.php
@@ -0,0 +1,176 @@
+<?php
+
+/**
+ * The Reader class file.
+ *
+ * This file is part of PicaReader.
+ *
+ * PicaReader is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaReader is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaReader.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   PicaReader
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2012, 2013 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.html GNU General Public License v3
+ */
+
+namespace HAB\Pica\Reader;
+
+use Exception;
+use RuntimeException;
+
+use HAB\Pica\Record\Record;
+
+abstract class Reader
+{
+
+    /**
+     * TRUE if the reader was opened with input data.
+     *
+     * @var boolean
+     */
+    protected $_isOpen = false;
+
+    /**
+     * Filter function or NULL if none set.
+     *
+     * @see Reader::setFilter()
+     *
+     * @var callback|null
+     */
+    protected $_filter = null;
+
+    /**
+     * Constructor.
+     *
+     * @return void
+     */
+    public function __construct ()
+    {}
+
+    /**
+     * Open the reader with input stream.
+     *
+     * @param  resource|string $stream
+     * @return void
+     */
+    abstract public function open ($stream);
+
+    /**
+     * Close reader.
+     *
+     * @return void
+     */
+    abstract public function close ();
+
+    /**
+     * Return next record in input data or FALSE if no more records.
+     *
+     * This function uses the Record::factory() method to create a record and
+     * applies a possible filter function to the input data.
+     *
+     * @see Reader::setFilter()
+     * @see Record::factory()
+     *
+     * @throws RuntimeException Error creating a record instance via factory function
+     * @return Record|false
+     */
+    public function read ()
+    {
+        $record = $this->next();
+        if (is_array($record)) {
+            $record = $this->applyFilter($record);
+            if (is_array($record)) {
+                try {
+                    return Record::factory($record);
+                } catch (Exception $e) {
+                    throw new RuntimeException("Error creating record instance in Record::factory()", -1, $e);
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Set a filter function.
+     *
+     * A filter function is every valid callback function that takes the array
+     * representation of a record as only argument and returns a possibly
+     * modifed array or false to skip the record.
+     *
+     * @param  callback $filter Filter function
+     * @return array|false
+     */
+    public function setFilter ($filter)
+    {
+        $this->_filter = $filter;
+    }
+
+    /**
+     * Return current filter function.
+     *
+     * @return callback|null
+     */
+    public function getFilter ()
+    {
+        return $this->_filter;
+    }
+
+    /**
+     * Unset the filter function.
+     *
+     * @return void
+     */
+    public function unsetFilter ()
+    {
+        $this->_filter = null;
+    }
+
+    /**
+     * Return true if the reader is open.
+     *
+     * @return boolean
+     */
+    public function isOpen ()
+    {
+        return $this->_isOpen;
+    }
+
+
+    /**
+     * Read the next record in input data.
+     *
+     * Returns array representation of the record or false if no more records.
+     *
+     * @return array|false
+     */
+    abstract protected function next ();
+
+    /**
+     * Return filtered record.
+     *
+     * Applies the filter function to the array representation of a record.
+     *
+     * @param  array $record Array representation of record
+     * @return array|false
+     */
+    protected function applyFilter (array $record)
+    {
+        $filter = $this->getFilter();
+        if ($filter) {
+            return call_user_func($filter, $record);
+        } else {
+            return $record;
+        }
+    }
+}
\ No newline at end of file
diff --git a/vendor/hab/picareader/tests/bootstrap.php b/vendor/hab/picareader/tests/bootstrap.php
new file mode 100644
index 0000000000000000000000000000000000000000..cf31ad8863e204daa01def64e08cdf71eb7298fe
--- /dev/null
+++ b/vendor/hab/picareader/tests/bootstrap.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * This file is part of PicaReader.
+ *
+ * PicaReader is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaReader is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaReader.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2013 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.txt GNU General Public License v3
+ */
+
+require_once realpath(__DIR__ . '/../vendor/autoload.php');
+
+define('PHPUNIT_FIXTURES', realpath(__DIR__ . '/fixtures'));
+
+$loader = new Composer\Autoload\ClassLoader();
+$loader->add('HAB', realpath(__DIR__ . '/src'));
+$loader->register();
diff --git a/vendor/hab/picareader/tests/fixtures/single_record.pp b/vendor/hab/picareader/tests/fixtures/single_record.pp
new file mode 100644
index 0000000000000000000000000000000000000000..7851ab6653a06f30fdc07596d7bdf58837c50ee4
--- /dev/null
+++ b/vendor/hab/picareader/tests/fixtures/single_record.pp
@@ -0,0 +1,377 @@
+001@ $015,21-23,31,39-40,62,65,69,78,90,98,100,110,120-121,130,136,181,285
+001A $02000:29-11-88
+001B $01999:18-12-11$t03:22:40.000
+001D $09999:99-99-99
+001U $0utf8
+001X $00
+002@ $0Aau
+003@  $0024836885
+003O $aOCoLC$019095997$v2009-06-15
+004A $03787307699$fDM 26.-
+004A $03787307702$fDM 68.-
+006G $0880712953
+006X $cSTH$0T00150053
+006Y $0BIST702229
+006Y $0pot10009585
+006Y $0hfmw001199
+006Y $0STHANT00150053
+007G $cGBV$0024836885
+009Q $S1$yInhaltsverzeichnis$0pdf$ahttp://www.gbv.de/dms/hebis-darmstadt/toc/7499264.pdf$mDE-603$nDE-17$qpdf/application$304$AHEB$B2
+010@ $ager
+011@ $a1988
+019@ $aXA-DE
+021A $a@Phänomenologie des Geistes$hGeorg Wilhelm Friedrich Hegel. Neu hrsg. von Hans-Friedrich Wessels und Heinrich Clairmont. Mit einer Einl. von Wolfgang Bonsiepen
+028A $dGeorg Wilhelm Friedrich$aHegel$9133763331$dGeorg Wilhelm Friedrich$aHegel$0118547739
+028C $dHans Friedrich$aWessels$BHrsg.
+033A $pHamburg$nMeiner
+034D $aXC, 631 S
+034I $a20 cm
+036E $a@Philosophische Bibliothek$l414
+036F $x41400$9130215368$aPhilosophische Bibliothek <Hamburg>$aPhilosophische Bibliothek$pHamburg$nMeiner$n1868-$05364036$l414
+041A $Ss$9104658835$aGeist$Ss$040198303
+044L $Ss$9104658835$aGeist$Ss
+045B/02 $aPHIL 825,4
+045E $a10
+045F $a193$AOCLC
+045M/10 $b9$aCG 4064
+045Q/01 $9106402641$a08.24$aNeue westliche Philosophie
+046M $aLiteraturverz. S. LXXI - XC
+046X $Sbema$Ces$aHamburg SUB 18$hEntsäuert 2011
+047I $aLiteraturverz. S. LXXI - XC
+101@ $a15$cPICA
+201B/01 $021-01-98$t06:24:39.349
+201C/01 $029-11-88$bD_
+201D/01 $023-12-91$bU_
+201U/01 $0utf8
+203@/01 $0032138016
+208@/01 $a23-12-91$bCC
+209F/01 $a3154
+101@ $a21$cPICA
+101B $029-04-96$t16:38:39.750
+101U $0utf8
+107F $0wd 9514
+145Z/30 $aphi 391 phg
+145Z/99 $a61
+150K $aPhänomenologie des Geistes
+201B/01 $029-04-96$t16:38:39.828
+203@/01 $0184799740
+208@/01 $a23-06-72$bxza
+209A/01 $aa phi 391 phg/514$du$x00
+209C/01 $awd 9514$x00
+201U/01 $0utf8
+101@ $a22$cPICA
+201B/01 $021-04-11$t08:57:13.000
+201C/01 $020-12-88
+201D/01 $021-04-11$b6018$a0018
+201U/01 $0utf8
+203@/01 $0083064362
+206X/01 $00590316
+208@/01 $a20-12-88$bzu
+209A/01 $fSUB$aX/345: 414.1988$du$x00
+220C/01 $a|bema| *es*Hamburg SUB 18 %Entsäuert 2011
+101@ $a23$cPICA
+201B/01 $013-03-01$t21:53:09.489
+201C/01 $031-10-95
+201D/01 $019-03-96$b6118$a0000
+201U/01 $0utf8
+203@/01 $0159724457
+208@/01 $a31-10-95$bz
+209A/01 $f27$a2772-8271$x00
+209G/01 $a830$$27728271$x00
+101@ $a31$cPICA
+101B $026-11-99$t10:10:37.493
+101D $026-11-99$b5652$a0000
+101U $0utf8
+107F $0JENA018596
+144Z $aPhänomenologie / Geist
+145Z $aPHI:HM:900
+145Z/01 $aSFB WJ:PHI:HM:900
+201B/02 $022-10-01$t07:06:43.685
+201C/02 $029-07-96
+201D/02 $022-10-01$b7628$a0000
+203@/02 $0192440268
+208@/02 $a08-04-97$br
+209A/02 $fJ 30/TP$aPHI:HM:900:H462:241:1988$dd$x00
+209A/02 $a96 NA 27808/1$x09
+209C/02 $aB96/R/30833$x00
+201U/02 $0utf8
+201B/03 $026-05-11$t14:33:05.000
+201C/03 $008-02-99
+201D/03 $026-05-11$b7659$a0027
+201U/03 $0utf8
+203@/03 $0319549925
+208@/03 $a08-02-99$bz
+209A/03 $fHA LAB$a99 A 743$dd$x00
+209C/03 $aA55672$x00
+209G/03 $a27$$004354621
+201B/04 $016-04-03$t13:56:40.000
+201C/04 $005-07-99
+201D/04 $016-04-03$b7628$a0027
+203@/04 $0346174503
+208@/04 $a05-07-99$bz
+209A/04 $fJ 30$aPHI:HM:900:H462:241:1988$dd$x00
+209C/04 $aD96/G/12935$x00
+209G/04 $a27$$00529469X
+201U/04 $0utf8
+101@ $a39$cPICA
+101B $015-05-98$t09:25:40.521
+101C $015-05-98
+101D $015-05-98$b5848$a0000
+101U $0utf8
+145Z $aCG 4064
+201B/01 $010-02-01$t02:19:06.264
+201C/01 $015-05-98
+201D/01 $011-02-01$b0813$a1999
+203@/01 $0283494271
+208@/01 $a15-05-98$bzen1
+209A/01 $fLS$aCG 4064 P53.988$db$x00
+209A/01 $aBibliothek der ehem. Kirchlichen Hochschule Naumburg$x05
+209C/01 $a31-Tb 0050 (414)$x00
+209G/01 $a547$$00376284X$x00
+245Z/01 $a31-Tb 0050$x00
+201U/01 $0utf8
+101@ $a40$cPICA
+101B $026-02-10$t12:15:52.000
+101C $018-07-95
+101D $026-02-10$bcsf_relate$a0007
+101U $0utf8
+145Z $9619943459$aDCH 600 $aPhilosophen, neuzeitliche {Abendländische Philosophie} 
+201B/01 $019-03-10$t00:30:22.000
+201C/01 $018-07-95
+201D/01 $019-03-10$btouchtitle$a1999
+201U/01 $0utf8
+203@/01 $0152069232
+208@/01 $a18-07-95$bx
+209A/01 $fLS1$aDCH 600 Heg:f = 8 Z PHIL 223:414$du$x00
+209A/01 $a8 Z PHIL 223:414$x09
+209G/01 $a7$$203755677$x00
+101@ $a62$cPICA
+201B/01 $008-10-09$t08:27:48.000
+201D/01 $008-10-09$b6481$a0028
+201U/01 $0utf8
+203@/01 $0232623651
+208@/01 $a01-04-97$bzi611
+209A/01 $f28/BB2$aCG 4064 P532$du$x00
+209A/01 $a93 A 11853$x09
+209G/01 $a28$$004892232
+201B/02 $010-02-12$t11:02:50.000
+201C/02 $016-09-98
+201D/02 $010-02-12$b6482$a0028
+201U/02 $0utf8
+203@/02 $0296745308
+208@/02 $a01-04-97$bzi611
+209A/02 $f28/BB2$aCG 4064 P532+2$du$x00
+209A/02 $a93 A 11853/1$x09
+209G/02 $a28$$004892240
+101@ $a65$cPICA
+201B/01 $019-10-00$t09:02:51.413
+201D/01 $019-10-00$b2453$a0000
+201U/01 $0utf8
+203@/01 $0253807840
+208@/01 $a31-08-97$bna044
+209A/01 $b3$j10$fHa 10$aE E 172$du$x00
+209C/01 $a90.4500/01$x00
+209G/01 $a3/44$$000874752
+201B/02 $030-09-11$t11:07:02.000
+201C/02 $019-10-00
+201D/02 $030-09-11$b2565$a0003/0075
+201U/02 $0utf8
+203@/02 $0406653836
+208@/02 $a03-11-00$bka075
+209A/02 $fHa 75$aSemesterapparat Ha 75 Dr. Seeberg O 220 P 118$dg$x00
+209A/02 $b3$j75$fHa 75$aO 220 P 118$x09
+209C/02 $a75/2000/00282$x00
+209G/02 $a3/75$$000063959
+101@ $a69$cPICA
+101B $015-09-04$t11:34:35.000
+101D $015-09-04$b5033$a0009
+101U $0utf8
+145Z $aCG 4064
+201B/40 $018-05-07$t11:32:40.000
+201D/40 $018-05-07$b6438$a0009
+201U/40 $0utf8
+203@/40 $066388148X
+208@/40 $a15-09-04$bni224
+209A/40 $fFB 450$a450/CG 4064 P53.988$du$x00
+209C/40 $a96.7233$x00
+209G/40 $a9$$05609615
+220B/40 $aLKZ 224
+101@ $a78$cPICA
+201B/01 $005-05-11$t06:19:38.000
+201D/01 $005-05-11$b./r78ausl$a1999
+201U/01 $0utf8
+201U/01 $0utf8
+203@/01 $01208620436
+208@/01 $a30-03-10$bn
+209A/01 $aPhil 825,4/18 d$du$x00
+201B/02 $029-11-10$t01:23:41.000
+201D/02 $016-12-10$bcsf_mm$a1999
+201U/02 $0utf8
+201U/02 $0utf8
+203@/02 $01208620444
+208@/02 $a30-03-10$bn
+209A/02 $e2$aPHIL 825,4$du$x00
+101@ $a90$cPICA
+101B $004-03-99$t16:07:48.233
+101D $004-03-99$b5415$a0000
+101U $0utf8
+107F $0HIL2038958
+145Z $aPHI 534
+201B/01 $001-08-06$t14:17:38.000
+201D/01 $001-08-06$b5429$a3090
+203@/01 $0131672703
+208@/01 $a29-05-94$bn
+209A/01 $aPHI 534:H16 : BP41$du$x00
+209C/01 $a93:03727$x00
+209G/01 $aHIL2$$93037279
+201U/01 $0utf8
+101@ $a98$cPICA
+101B $010-01-03$t13:45:23.391
+101C $001-01-01
+101D $022-10-02$b0098$a0000
+101U $0utf8
+201B/01 $010-01-03$t13:45:23.406
+203@/01 $0585039968
+208@/01 $a25-02-93$bn
+209A/01 $aT 18.657$du$x00
+209C/01 $a93:0509$x00
+209G/01 $aWIM8$$0177210
+220B/01 $a48,- DM
+201U/01 $0utf8
+101@ $a100$cPICA
+101B $018-11-97$t10:11:07.508
+101C $010-11-97
+101D $018-11-97$b4913$a0001
+101U $0utf8
+145Z $aF/E 232 Hege
+201B/01 $018-11-97$t10:11:07.647
+201C/01 $029-10-97
+201D/01 $018-11-97$b4913$a0001
+203@/01 $0262515172
+208@/01 $a18-11-97$bkf
+209A/01 $b3100$j1$fFGSE-FH$a97.17476$du$x00
+209A/01 $a1991 a 731:1$x09
+209G/01 $aMA9/1$$0301922$x00
+201U/01 $0utf8
+101@ $a110$cPICA
+101B $007-09-05$t08:21:22.000
+101C $014-01-98
+101D $007-09-05$b801$a1999
+101U $0utf8
+144Z/20 $aGeist
+145Z $aPhil 825
+145Z/20 $aSoW 335
+201B/01 $012-08-05$t20:42:19.000
+201C/01 $014-01-98
+201D/01 $012-08-05$b384906$a1999
+203@/01 $0269666141
+208@/01 $a14-01-98$bkz
+209A/01 $b3110$fCAM$aPhil 825.024$x00
+209C/01 $a97-6790$x00
+209G/01 $aLUN4$$03639797$x00
+201U/01 $0utf8
+201B/02 $011-08-05$t18:54:29.000
+201C/02 $004-06-96
+201D/02 $007-09-05$b801$a1999
+203@/02 $0188090088
+208@/02 $a04-06-96$bka
+209A/02 $b3110$j1$fROT$aSoW 335/168$du$x00
+209C/02 $a430/96$x00
+209G/02 $a961$$0130524$x00
+201U/02 $0utf8
+101@ $a120$cPICA
+101B $021-10-03$t07:48:10.000
+101D $021-10-03$b5004$a0715
+101U $0utf8
+145Z $aphi 391 phg
+201B/01 $021-10-03$t07:48:10.000
+201D/01 $021-10-03$b5004$a0715
+203@/01 $0619130725
+208@/01 $a21-10-03$bz
+209A/01 $aphi 391 phg CM 0791$du$x00
+201U/01 $0utf8
+101@ $a121$cPICA
+101B $022-08-02$t21:14:12.112
+101U $0utf8
+107F $0BISL06053518
+201B/01 $022-08-02$t21:14:12.156
+201C/01 $001-01-01
+203@/01 $078675642X
+208@/01 $a21-03-02$br
+209A/01 $a89-1164$du$x00
+209G/01 $a06053518
+201U/01 $0utf8
+101@ $a130$cPICA
+101B $030-01-98$t08:12:01.057
+101C $013-10-88$bD_
+101D $008-08-91$bUN
+101U $0utf8
+107F $07N75/16T
+145Z $aJHE E
+145Z/01 $aNOH 37
+150K/01 $n2$aPhaenomenologie des Geistes
+150K/01 $n2$aHegel, G.
+150K/01 $n2$aPhilosophische Bibliothek$b--414
+150K/01 $n1$aJHE E
+150K/01 $n1$aNOH 37
+201B/01 $020-11-04$t15:19:36.000
+201C/01 $013-10-88$bD_
+201D/01 $020-11-04$br130sso$a1999
+203@/01 $0032138040
+208@/01 $a08-08-91$bnb
+209A/01 $fB$aJHE E 4415-166 9$du$x00
+209C/01 $a4415-166 9$x00
+209G/01 $a700$$44151669$x00
+201U/01 $0utf8
+201B/02 $029-04-98$t00:10:09.853
+201C/02 $013-10-88$bD_
+201D/02 $008-08-91$bUN
+203@/02 $0032138059
+208@/02 $a08-08-91$bng
+209A/02 $fG$aNOH 37 4519-902 8$du$x00
+209C/02 $a4519-902 8$x00
+209G/02 $a700$$45199028$x00
+201U/02 $0utf8
+101@ $a136$cPICA
+101B $024-02-03$t12:21:11.000
+101D $024-02-03$b4349$a3526
+101U $0utf8
+144Z $aGeist
+144Z/01 $aPhänomenologie
+144Z/02 $aPhilosophie
+144Z/03 $aHegel, Georg Wilhelm Friedrich
+145Z $aTa:A4
+145Z/01 $aTa:G
+201B/01 $003-08-11$t12:23:06.000
+201C/01 $011-11-02
+201D/01 $003-08-11$b4346$a3526
+201U/01 $0utf8
+203@/01 $0577615866
+208@/01 $a23-01-03$bzpln
+209A/01 $a2002 A 3961$du$x00
+209G/01 $aWIS1$$1739883
+101@ $a181$cPICA
+201B/01 $012-01-10$t14:10:42.000
+201D/01 $012-01-10$b934384$a3181
+201U/01 $0utf8
+203@/01 $01109002394
+208@/01 $a12-01-10$brb010
+209A/01 $b3181$j010$fKB$aA 27 m kl$di$x00
+209A/01 $a:A:27:m:kl:$x10
+209C/01 $a91.2506$x00
+220B/01 $aRetro Wa
+101@ $a285$cPICA
+101B $014-11-02$t15:58:46.382
+101C $001-01-01
+101D $003-11-02$b0285$a0000
+101U $0utf8
+145Z $aCG 4064
+201B/01 $006-09-07$t13:07:58.000
+201C/01 $001-01-01
+201D/01 $006-09-07$b9445$a0517
+201U/01 $0utf8
+203@/01 $0791602559
+208@/01 $a15-02-95$br
+209A/01 $f1000$aCG 4064 PHAE$du$x00
+209C/01 $a95011347$x00
+209G/01 $a07901261
diff --git a/vendor/hab/picareader/tests/fixtures/single_record.xml b/vendor/hab/picareader/tests/fixtures/single_record.xml
new file mode 100644
index 0000000000000000000000000000000000000000..243aadbc6d3cc8be1f25f613f8738987769f89fd
--- /dev/null
+++ b/vendor/hab/picareader/tests/fixtures/single_record.xml
@@ -0,0 +1,1473 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<record xmlns="info:srw/schema/5/picaXML-v1.0">
+  <datafield tag="001@">
+    <subfield code="0">15,21-23,31,39-40,62,65,69,78,90,98,100,110,120-121,130,136,181,285</subfield>
+  </datafield>
+  <datafield tag="001A">
+    <subfield code="0">2000:29-11-88</subfield>
+  </datafield>
+  <datafield tag="001B">
+    <subfield code="0">1999:18-12-11</subfield>
+    <subfield code="t">03:22:40.000</subfield>
+  </datafield>
+  <datafield tag="001D">
+    <subfield code="0">9999:99-99-99</subfield>
+  </datafield>
+  <datafield tag="001U">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="001X">
+    <subfield code="0">0</subfield>
+  </datafield>
+  <datafield tag="002@">
+    <subfield code="0">Aau</subfield>
+  </datafield>
+  <datafield tag="003@">
+    <subfield code="0">024836885</subfield>
+  </datafield>
+  <datafield tag="003O">
+    <subfield code="a">OCoLC</subfield>
+    <subfield code="0">19095997</subfield>
+    <subfield code="v">2009-06-15</subfield>
+  </datafield>
+  <datafield tag="004A">
+    <subfield code="0">3787307699</subfield>
+    <subfield code="f">DM 26.-</subfield>
+  </datafield>
+  <datafield tag="004A">
+    <subfield code="0">3787307702</subfield>
+    <subfield code="f">DM 68.-</subfield>
+  </datafield>
+  <datafield tag="006G">
+    <subfield code="0">880712953</subfield>
+  </datafield>
+  <datafield tag="006X">
+    <subfield code="c">STH</subfield>
+    <subfield code="0">T00150053</subfield>
+  </datafield>
+  <datafield tag="006Y">
+    <subfield code="0">BIST702229</subfield>
+  </datafield>
+  <datafield tag="006Y">
+    <subfield code="0">pot10009585</subfield>
+  </datafield>
+  <datafield tag="006Y">
+    <subfield code="0">hfmw001199</subfield>
+  </datafield>
+  <datafield tag="006Y">
+    <subfield code="0">STHANT00150053</subfield>
+  </datafield>
+  <datafield tag="007G">
+    <subfield code="c">GBV</subfield>
+    <subfield code="0">024836885</subfield>
+  </datafield>
+  <datafield tag="009Q">
+    <subfield code="S">1</subfield>
+    <subfield code="y">Inhaltsverzeichnis</subfield>
+    <subfield code="0">pdf</subfield>
+    <subfield code="a">http://www.gbv.de/dms/hebis-darmstadt/toc/7499264.pdf</subfield>
+    <subfield code="m">DE-603</subfield>
+    <subfield code="n">DE-17</subfield>
+    <subfield code="q">pdf/application</subfield>
+    <subfield code="3">04</subfield>
+    <subfield code="A">HEB</subfield>
+    <subfield code="B">2</subfield>
+  </datafield>
+  <datafield tag="010@">
+    <subfield code="a">ger</subfield>
+  </datafield>
+  <datafield tag="011@">
+    <subfield code="a">1988</subfield>
+  </datafield>
+  <datafield tag="019@">
+    <subfield code="a">XA-DE</subfield>
+  </datafield>
+  <datafield tag="021A">
+    <subfield code="a">@Phänomenologie des Geistes</subfield>
+    <subfield code="h">Georg Wilhelm Friedrich Hegel. Neu hrsg. von Hans-Friedrich Wessels und Heinrich Clairmont. Mit einer Einl. von Wolfgang Bonsiepen</subfield>
+  </datafield>
+  <datafield tag="028A">
+    <subfield code="d">Georg Wilhelm Friedrich</subfield>
+    <subfield code="a">Hegel</subfield>
+    <subfield code="9">133763331</subfield>
+    <subfield code="d">Georg Wilhelm Friedrich</subfield>
+    <subfield code="a">Hegel</subfield>
+    <subfield code="0">118547739</subfield>
+  </datafield>
+  <datafield tag="028C">
+    <subfield code="d">Hans Friedrich</subfield>
+    <subfield code="a">Wessels</subfield>
+    <subfield code="B">Hrsg.</subfield>
+  </datafield>
+  <datafield tag="033A">
+    <subfield code="p">Hamburg</subfield>
+    <subfield code="n">Meiner</subfield>
+  </datafield>
+  <datafield tag="034D">
+    <subfield code="a">XC, 631 S</subfield>
+  </datafield>
+  <datafield tag="034I">
+    <subfield code="a">20 cm</subfield>
+  </datafield>
+  <datafield tag="036E">
+    <subfield code="a">@Philosophische Bibliothek</subfield>
+    <subfield code="l">414</subfield>
+  </datafield>
+  <datafield tag="036F">
+    <subfield code="x">41400</subfield>
+    <subfield code="9">130215368</subfield>
+    <subfield code="a">Philosophische Bibliothek &lt;Hamburg&gt;</subfield>
+    <subfield code="a">Philosophische Bibliothek</subfield>
+    <subfield code="p">Hamburg</subfield>
+    <subfield code="n">Meiner</subfield>
+    <subfield code="n">1868-</subfield>
+    <subfield code="0">5364036</subfield>
+    <subfield code="l">414</subfield>
+  </datafield>
+  <datafield tag="041A">
+    <subfield code="S">s</subfield>
+    <subfield code="9">104658835</subfield>
+    <subfield code="a">Geist</subfield>
+    <subfield code="S">s</subfield>
+    <subfield code="0">40198303</subfield>
+  </datafield>
+  <datafield tag="044L">
+    <subfield code="S">s</subfield>
+    <subfield code="9">104658835</subfield>
+    <subfield code="a">Geist</subfield>
+    <subfield code="S">s</subfield>
+  </datafield>
+  <datafield tag="045B" occurrence="02">
+    <subfield code="a">PHIL 825,4</subfield>
+  </datafield>
+  <datafield tag="045E">
+    <subfield code="a">10</subfield>
+  </datafield>
+  <datafield tag="045F">
+    <subfield code="a">193</subfield>
+    <subfield code="A">OCLC</subfield>
+  </datafield>
+  <datafield tag="045M" occurrence="10">
+    <subfield code="b">9</subfield>
+    <subfield code="a">CG 4064</subfield>
+  </datafield>
+  <datafield tag="045Q" occurrence="01">
+    <subfield code="9">106402641</subfield>
+    <subfield code="a">08.24</subfield>
+    <subfield code="a">Neue westliche Philosophie</subfield>
+  </datafield>
+  <datafield tag="046M">
+    <subfield code="a">Literaturverz. S. LXXI - XC</subfield>
+  </datafield>
+  <datafield tag="046X">
+    <subfield code="S">bema</subfield>
+    <subfield code="C">es</subfield>
+    <subfield code="a">Hamburg SUB 18</subfield>
+    <subfield code="h">Entsäuert 2011</subfield>
+  </datafield>
+  <datafield tag="047I">
+    <subfield code="a">Literaturverz. S. LXXI - XC</subfield>
+  </datafield>
+  <datafield tag="101@">
+    <subfield code="a">15</subfield>
+    <subfield code="c">PICA</subfield>
+  </datafield>
+  <datafield tag="201B" occurrence="01">
+    <subfield code="0">21-01-98</subfield>
+    <subfield code="t">06:24:39.349</subfield>
+  </datafield>
+  <datafield tag="201C" occurrence="01">
+    <subfield code="0">29-11-88</subfield>
+    <subfield code="b">D_</subfield>
+  </datafield>
+  <datafield tag="201D" occurrence="01">
+    <subfield code="0">23-12-91</subfield>
+    <subfield code="b">U_</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="01">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="203@" occurrence="01">
+    <subfield code="0">032138016</subfield>
+  </datafield>
+  <datafield tag="208@" occurrence="01">
+    <subfield code="a">23-12-91</subfield>
+    <subfield code="b">CC</subfield>
+  </datafield>
+  <datafield tag="209F" occurrence="01">
+    <subfield code="a">3154</subfield>
+  </datafield>
+  <datafield tag="101@">
+    <subfield code="a">21</subfield>
+    <subfield code="c">PICA</subfield>
+  </datafield>
+  <datafield tag="101B">
+    <subfield code="0">29-04-96</subfield>
+    <subfield code="t">16:38:39.750</subfield>
+  </datafield>
+  <datafield tag="101U">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="107F">
+    <subfield code="0">wd 9514</subfield>
+  </datafield>
+  <datafield tag="145Z" occurrence="30">
+    <subfield code="a">phi 391 phg</subfield>
+  </datafield>
+  <datafield tag="145Z" occurrence="99">
+    <subfield code="a">61</subfield>
+  </datafield>
+  <datafield tag="150K">
+    <subfield code="a">Phänomenologie des Geistes</subfield>
+  </datafield>
+  <datafield tag="201B" occurrence="01">
+    <subfield code="0">29-04-96</subfield>
+    <subfield code="t">16:38:39.828</subfield>
+  </datafield>
+  <datafield tag="203@" occurrence="01">
+    <subfield code="0">184799740</subfield>
+  </datafield>
+  <datafield tag="208@" occurrence="01">
+    <subfield code="a">23-06-72</subfield>
+    <subfield code="b">xza</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="01">
+    <subfield code="a">a phi 391 phg/514</subfield>
+    <subfield code="d">u</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209C" occurrence="01">
+    <subfield code="a">wd 9514</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="01">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="101@">
+    <subfield code="a">22</subfield>
+    <subfield code="c">PICA</subfield>
+  </datafield>
+  <datafield tag="201B" occurrence="01">
+    <subfield code="0">21-04-11</subfield>
+    <subfield code="t">08:57:13.000</subfield>
+  </datafield>
+  <datafield tag="201C" occurrence="01">
+    <subfield code="0">20-12-88</subfield>
+  </datafield>
+  <datafield tag="201D" occurrence="01">
+    <subfield code="0">21-04-11</subfield>
+    <subfield code="b">6018</subfield>
+    <subfield code="a">0018</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="01">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="203@" occurrence="01">
+    <subfield code="0">083064362</subfield>
+  </datafield>
+  <datafield tag="206X" occurrence="01">
+    <subfield code="0">0590316</subfield>
+  </datafield>
+  <datafield tag="208@" occurrence="01">
+    <subfield code="a">20-12-88</subfield>
+    <subfield code="b">zu</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="01">
+    <subfield code="f">SUB</subfield>
+    <subfield code="a">X/345: 414.1988</subfield>
+    <subfield code="d">u</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="220C" occurrence="01">
+    <subfield code="a">|bema| *es*Hamburg SUB 18 %Entsäuert 2011</subfield>
+  </datafield>
+  <datafield tag="101@">
+    <subfield code="a">23</subfield>
+    <subfield code="c">PICA</subfield>
+  </datafield>
+  <datafield tag="201B" occurrence="01">
+    <subfield code="0">13-03-01</subfield>
+    <subfield code="t">21:53:09.489</subfield>
+  </datafield>
+  <datafield tag="201C" occurrence="01">
+    <subfield code="0">31-10-95</subfield>
+  </datafield>
+  <datafield tag="201D" occurrence="01">
+    <subfield code="0">19-03-96</subfield>
+    <subfield code="b">6118</subfield>
+    <subfield code="a">0000</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="01">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="203@" occurrence="01">
+    <subfield code="0">159724457</subfield>
+  </datafield>
+  <datafield tag="208@" occurrence="01">
+    <subfield code="a">31-10-95</subfield>
+    <subfield code="b">z</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="01">
+    <subfield code="f">27</subfield>
+    <subfield code="a">2772-8271</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209G" occurrence="01">
+    <subfield code="a">830$27728271</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="101@">
+    <subfield code="a">31</subfield>
+    <subfield code="c">PICA</subfield>
+  </datafield>
+  <datafield tag="101B">
+    <subfield code="0">26-11-99</subfield>
+    <subfield code="t">10:10:37.493</subfield>
+  </datafield>
+  <datafield tag="101D">
+    <subfield code="0">26-11-99</subfield>
+    <subfield code="b">5652</subfield>
+    <subfield code="a">0000</subfield>
+  </datafield>
+  <datafield tag="101U">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="107F">
+    <subfield code="0">JENA018596</subfield>
+  </datafield>
+  <datafield tag="144Z">
+    <subfield code="a">Phänomenologie / Geist</subfield>
+  </datafield>
+  <datafield tag="145Z">
+    <subfield code="a">PHI:HM:900</subfield>
+  </datafield>
+  <datafield tag="145Z" occurrence="01">
+    <subfield code="a">SFB WJ:PHI:HM:900</subfield>
+  </datafield>
+  <datafield tag="201B" occurrence="02">
+    <subfield code="0">22-10-01</subfield>
+    <subfield code="t">07:06:43.685</subfield>
+  </datafield>
+  <datafield tag="201C" occurrence="02">
+    <subfield code="0">29-07-96</subfield>
+  </datafield>
+  <datafield tag="201D" occurrence="02">
+    <subfield code="0">22-10-01</subfield>
+    <subfield code="b">7628</subfield>
+    <subfield code="a">0000</subfield>
+  </datafield>
+  <datafield tag="203@" occurrence="02">
+    <subfield code="0">192440268</subfield>
+  </datafield>
+  <datafield tag="208@" occurrence="02">
+    <subfield code="a">08-04-97</subfield>
+    <subfield code="b">r</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="02">
+    <subfield code="f">J 30/TP</subfield>
+    <subfield code="a">PHI:HM:900:H462:241:1988</subfield>
+    <subfield code="d">d</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="02">
+    <subfield code="a">96 NA 27808/1</subfield>
+    <subfield code="x">09</subfield>
+  </datafield>
+  <datafield tag="209C" occurrence="02">
+    <subfield code="a">B96/R/30833</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="02">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="201B" occurrence="03">
+    <subfield code="0">26-05-11</subfield>
+    <subfield code="t">14:33:05.000</subfield>
+  </datafield>
+  <datafield tag="201C" occurrence="03">
+    <subfield code="0">08-02-99</subfield>
+  </datafield>
+  <datafield tag="201D" occurrence="03">
+    <subfield code="0">26-05-11</subfield>
+    <subfield code="b">7659</subfield>
+    <subfield code="a">0027</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="03">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="203@" occurrence="03">
+    <subfield code="0">319549925</subfield>
+  </datafield>
+  <datafield tag="208@" occurrence="03">
+    <subfield code="a">08-02-99</subfield>
+    <subfield code="b">z</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="03">
+    <subfield code="f">HA LAB</subfield>
+    <subfield code="a">99 A 743</subfield>
+    <subfield code="d">d</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209C" occurrence="03">
+    <subfield code="a">A55672</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209G" occurrence="03">
+    <subfield code="a">27$004354621</subfield>
+  </datafield>
+  <datafield tag="201B" occurrence="04">
+    <subfield code="0">16-04-03</subfield>
+    <subfield code="t">13:56:40.000</subfield>
+  </datafield>
+  <datafield tag="201C" occurrence="04">
+    <subfield code="0">05-07-99</subfield>
+  </datafield>
+  <datafield tag="201D" occurrence="04">
+    <subfield code="0">16-04-03</subfield>
+    <subfield code="b">7628</subfield>
+    <subfield code="a">0027</subfield>
+  </datafield>
+  <datafield tag="203@" occurrence="04">
+    <subfield code="0">346174503</subfield>
+  </datafield>
+  <datafield tag="208@" occurrence="04">
+    <subfield code="a">05-07-99</subfield>
+    <subfield code="b">z</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="04">
+    <subfield code="f">J 30</subfield>
+    <subfield code="a">PHI:HM:900:H462:241:1988</subfield>
+    <subfield code="d">d</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209C" occurrence="04">
+    <subfield code="a">D96/G/12935</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209G" occurrence="04">
+    <subfield code="a">27$00529469X</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="04">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="101@">
+    <subfield code="a">39</subfield>
+    <subfield code="c">PICA</subfield>
+  </datafield>
+  <datafield tag="101B">
+    <subfield code="0">15-05-98</subfield>
+    <subfield code="t">09:25:40.521</subfield>
+  </datafield>
+  <datafield tag="101C">
+    <subfield code="0">15-05-98</subfield>
+  </datafield>
+  <datafield tag="101D">
+    <subfield code="0">15-05-98</subfield>
+    <subfield code="b">5848</subfield>
+    <subfield code="a">0000</subfield>
+  </datafield>
+  <datafield tag="101U">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="145Z">
+    <subfield code="a">CG 4064</subfield>
+  </datafield>
+  <datafield tag="201B" occurrence="01">
+    <subfield code="0">10-02-01</subfield>
+    <subfield code="t">02:19:06.264</subfield>
+  </datafield>
+  <datafield tag="201C" occurrence="01">
+    <subfield code="0">15-05-98</subfield>
+  </datafield>
+  <datafield tag="201D" occurrence="01">
+    <subfield code="0">11-02-01</subfield>
+    <subfield code="b">0813</subfield>
+    <subfield code="a">1999</subfield>
+  </datafield>
+  <datafield tag="203@" occurrence="01">
+    <subfield code="0">283494271</subfield>
+  </datafield>
+  <datafield tag="208@" occurrence="01">
+    <subfield code="a">15-05-98</subfield>
+    <subfield code="b">zen1</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="01">
+    <subfield code="f">LS</subfield>
+    <subfield code="a">CG 4064 P53.988</subfield>
+    <subfield code="d">b</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="01">
+    <subfield code="a">Bibliothek der ehem. Kirchlichen Hochschule Naumburg</subfield>
+    <subfield code="x">05</subfield>
+  </datafield>
+  <datafield tag="209C" occurrence="01">
+    <subfield code="a">31-Tb 0050 (414)</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209G" occurrence="01">
+    <subfield code="a">547$00376284X</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="245Z" occurrence="01">
+    <subfield code="a">31-Tb 0050</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="01">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="101@">
+    <subfield code="a">40</subfield>
+    <subfield code="c">PICA</subfield>
+  </datafield>
+  <datafield tag="101B">
+    <subfield code="0">26-02-10</subfield>
+    <subfield code="t">12:15:52.000</subfield>
+  </datafield>
+  <datafield tag="101C">
+    <subfield code="0">18-07-95</subfield>
+  </datafield>
+  <datafield tag="101D">
+    <subfield code="0">26-02-10</subfield>
+    <subfield code="b">csf_relate</subfield>
+    <subfield code="a">0007</subfield>
+  </datafield>
+  <datafield tag="101U">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="145Z">
+    <subfield code="9">619943459</subfield>
+    <subfield code="a">DCH 600 </subfield>
+    <subfield code="a">Philosophen, neuzeitliche {Abendländische Philosophie} </subfield>
+  </datafield>
+  <datafield tag="201B" occurrence="01">
+    <subfield code="0">19-03-10</subfield>
+    <subfield code="t">00:30:22.000</subfield>
+  </datafield>
+  <datafield tag="201C" occurrence="01">
+    <subfield code="0">18-07-95</subfield>
+  </datafield>
+  <datafield tag="201D" occurrence="01">
+    <subfield code="0">19-03-10</subfield>
+    <subfield code="b">touchtitle</subfield>
+    <subfield code="a">1999</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="01">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="203@" occurrence="01">
+    <subfield code="0">152069232</subfield>
+  </datafield>
+  <datafield tag="208@" occurrence="01">
+    <subfield code="a">18-07-95</subfield>
+    <subfield code="b">x</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="01">
+    <subfield code="f">LS1</subfield>
+    <subfield code="a">DCH 600 Heg:f = 8 Z PHIL 223:414</subfield>
+    <subfield code="d">u</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="01">
+    <subfield code="a">8 Z PHIL 223:414</subfield>
+    <subfield code="x">09</subfield>
+  </datafield>
+  <datafield tag="209G" occurrence="01">
+    <subfield code="a">7$203755677</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="101@">
+    <subfield code="a">62</subfield>
+    <subfield code="c">PICA</subfield>
+  </datafield>
+  <datafield tag="201B" occurrence="01">
+    <subfield code="0">08-10-09</subfield>
+    <subfield code="t">08:27:48.000</subfield>
+  </datafield>
+  <datafield tag="201D" occurrence="01">
+    <subfield code="0">08-10-09</subfield>
+    <subfield code="b">6481</subfield>
+    <subfield code="a">0028</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="01">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="203@" occurrence="01">
+    <subfield code="0">232623651</subfield>
+  </datafield>
+  <datafield tag="208@" occurrence="01">
+    <subfield code="a">01-04-97</subfield>
+    <subfield code="b">zi611</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="01">
+    <subfield code="f">28/BB2</subfield>
+    <subfield code="a">CG 4064 P532</subfield>
+    <subfield code="d">u</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="01">
+    <subfield code="a">93 A 11853</subfield>
+    <subfield code="x">09</subfield>
+  </datafield>
+  <datafield tag="209G" occurrence="01">
+    <subfield code="a">28$004892232</subfield>
+  </datafield>
+  <datafield tag="201B" occurrence="02">
+    <subfield code="0">10-02-12</subfield>
+    <subfield code="t">11:02:50.000</subfield>
+  </datafield>
+  <datafield tag="201C" occurrence="02">
+    <subfield code="0">16-09-98</subfield>
+  </datafield>
+  <datafield tag="201D" occurrence="02">
+    <subfield code="0">10-02-12</subfield>
+    <subfield code="b">6482</subfield>
+    <subfield code="a">0028</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="02">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="203@" occurrence="02">
+    <subfield code="0">296745308</subfield>
+  </datafield>
+  <datafield tag="208@" occurrence="02">
+    <subfield code="a">01-04-97</subfield>
+    <subfield code="b">zi611</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="02">
+    <subfield code="f">28/BB2</subfield>
+    <subfield code="a">CG 4064 P532+2</subfield>
+    <subfield code="d">u</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="02">
+    <subfield code="a">93 A 11853/1</subfield>
+    <subfield code="x">09</subfield>
+  </datafield>
+  <datafield tag="209G" occurrence="02">
+    <subfield code="a">28$004892240</subfield>
+  </datafield>
+  <datafield tag="101@">
+    <subfield code="a">65</subfield>
+    <subfield code="c">PICA</subfield>
+  </datafield>
+  <datafield tag="201B" occurrence="01">
+    <subfield code="0">19-10-00</subfield>
+    <subfield code="t">09:02:51.413</subfield>
+  </datafield>
+  <datafield tag="201D" occurrence="01">
+    <subfield code="0">19-10-00</subfield>
+    <subfield code="b">2453</subfield>
+    <subfield code="a">0000</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="01">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="203@" occurrence="01">
+    <subfield code="0">253807840</subfield>
+  </datafield>
+  <datafield tag="208@" occurrence="01">
+    <subfield code="a">31-08-97</subfield>
+    <subfield code="b">na044</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="01">
+    <subfield code="b">3</subfield>
+    <subfield code="j">10</subfield>
+    <subfield code="f">Ha 10</subfield>
+    <subfield code="a">E E 172</subfield>
+    <subfield code="d">u</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209C" occurrence="01">
+    <subfield code="a">90.4500/01</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209G" occurrence="01">
+    <subfield code="a">3/44$000874752</subfield>
+  </datafield>
+  <datafield tag="201B" occurrence="02">
+    <subfield code="0">30-09-11</subfield>
+    <subfield code="t">11:07:02.000</subfield>
+  </datafield>
+  <datafield tag="201C" occurrence="02">
+    <subfield code="0">19-10-00</subfield>
+  </datafield>
+  <datafield tag="201D" occurrence="02">
+    <subfield code="0">30-09-11</subfield>
+    <subfield code="b">2565</subfield>
+    <subfield code="a">0003/0075</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="02">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="203@" occurrence="02">
+    <subfield code="0">406653836</subfield>
+  </datafield>
+  <datafield tag="208@" occurrence="02">
+    <subfield code="a">03-11-00</subfield>
+    <subfield code="b">ka075</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="02">
+    <subfield code="f">Ha 75</subfield>
+    <subfield code="a">Semesterapparat Ha 75 Dr. Seeberg O 220 P 118</subfield>
+    <subfield code="d">g</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="02">
+    <subfield code="b">3</subfield>
+    <subfield code="j">75</subfield>
+    <subfield code="f">Ha 75</subfield>
+    <subfield code="a">O 220 P 118</subfield>
+    <subfield code="x">09</subfield>
+  </datafield>
+  <datafield tag="209C" occurrence="02">
+    <subfield code="a">75/2000/00282</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209G" occurrence="02">
+    <subfield code="a">3/75$000063959</subfield>
+  </datafield>
+  <datafield tag="101@">
+    <subfield code="a">69</subfield>
+    <subfield code="c">PICA</subfield>
+  </datafield>
+  <datafield tag="101B">
+    <subfield code="0">15-09-04</subfield>
+    <subfield code="t">11:34:35.000</subfield>
+  </datafield>
+  <datafield tag="101D">
+    <subfield code="0">15-09-04</subfield>
+    <subfield code="b">5033</subfield>
+    <subfield code="a">0009</subfield>
+  </datafield>
+  <datafield tag="101U">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="145Z">
+    <subfield code="a">CG 4064</subfield>
+  </datafield>
+  <datafield tag="201B" occurrence="40">
+    <subfield code="0">18-05-07</subfield>
+    <subfield code="t">11:32:40.000</subfield>
+  </datafield>
+  <datafield tag="201D" occurrence="40">
+    <subfield code="0">18-05-07</subfield>
+    <subfield code="b">6438</subfield>
+    <subfield code="a">0009</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="40">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="203@" occurrence="40">
+    <subfield code="0">66388148X</subfield>
+  </datafield>
+  <datafield tag="208@" occurrence="40">
+    <subfield code="a">15-09-04</subfield>
+    <subfield code="b">ni224</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="40">
+    <subfield code="f">FB 450</subfield>
+    <subfield code="a">450/CG 4064 P53.988</subfield>
+    <subfield code="d">u</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209C" occurrence="40">
+    <subfield code="a">96.7233</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209G" occurrence="40">
+    <subfield code="a">9$05609615</subfield>
+  </datafield>
+  <datafield tag="220B" occurrence="40">
+    <subfield code="a">LKZ 224</subfield>
+  </datafield>
+  <datafield tag="101@">
+    <subfield code="a">78</subfield>
+    <subfield code="c">PICA</subfield>
+  </datafield>
+  <datafield tag="201B" occurrence="01">
+    <subfield code="0">05-05-11</subfield>
+    <subfield code="t">06:19:38.000</subfield>
+  </datafield>
+  <datafield tag="201D" occurrence="01">
+    <subfield code="0">05-05-11</subfield>
+    <subfield code="b">./r78ausl</subfield>
+    <subfield code="a">1999</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="01">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="01">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="203@" occurrence="01">
+    <subfield code="0">1208620436</subfield>
+  </datafield>
+  <datafield tag="208@" occurrence="01">
+    <subfield code="a">30-03-10</subfield>
+    <subfield code="b">n</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="01">
+    <subfield code="a">Phil 825,4/18 d</subfield>
+    <subfield code="d">u</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="201B" occurrence="02">
+    <subfield code="0">29-11-10</subfield>
+    <subfield code="t">01:23:41.000</subfield>
+  </datafield>
+  <datafield tag="201D" occurrence="02">
+    <subfield code="0">16-12-10</subfield>
+    <subfield code="b">csf_mm</subfield>
+    <subfield code="a">1999</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="02">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="02">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="203@" occurrence="02">
+    <subfield code="0">1208620444</subfield>
+  </datafield>
+  <datafield tag="208@" occurrence="02">
+    <subfield code="a">30-03-10</subfield>
+    <subfield code="b">n</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="02">
+    <subfield code="e">2</subfield>
+    <subfield code="a">PHIL 825,4</subfield>
+    <subfield code="d">u</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="101@">
+    <subfield code="a">90</subfield>
+    <subfield code="c">PICA</subfield>
+  </datafield>
+  <datafield tag="101B">
+    <subfield code="0">04-03-99</subfield>
+    <subfield code="t">16:07:48.233</subfield>
+  </datafield>
+  <datafield tag="101D">
+    <subfield code="0">04-03-99</subfield>
+    <subfield code="b">5415</subfield>
+    <subfield code="a">0000</subfield>
+  </datafield>
+  <datafield tag="101U">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="107F">
+    <subfield code="0">HIL2038958</subfield>
+  </datafield>
+  <datafield tag="145Z">
+    <subfield code="a">PHI 534</subfield>
+  </datafield>
+  <datafield tag="201B" occurrence="01">
+    <subfield code="0">01-08-06</subfield>
+    <subfield code="t">14:17:38.000</subfield>
+  </datafield>
+  <datafield tag="201D" occurrence="01">
+    <subfield code="0">01-08-06</subfield>
+    <subfield code="b">5429</subfield>
+    <subfield code="a">3090</subfield>
+  </datafield>
+  <datafield tag="203@" occurrence="01">
+    <subfield code="0">131672703</subfield>
+  </datafield>
+  <datafield tag="208@" occurrence="01">
+    <subfield code="a">29-05-94</subfield>
+    <subfield code="b">n</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="01">
+    <subfield code="a">PHI 534:H16 : BP41</subfield>
+    <subfield code="d">u</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209C" occurrence="01">
+    <subfield code="a">93:03727</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209G" occurrence="01">
+    <subfield code="a">HIL2$93037279</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="01">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="101@">
+    <subfield code="a">98</subfield>
+    <subfield code="c">PICA</subfield>
+  </datafield>
+  <datafield tag="101B">
+    <subfield code="0">10-01-03</subfield>
+    <subfield code="t">13:45:23.391</subfield>
+  </datafield>
+  <datafield tag="101C">
+    <subfield code="0">01-01-01</subfield>
+  </datafield>
+  <datafield tag="101D">
+    <subfield code="0">22-10-02</subfield>
+    <subfield code="b">0098</subfield>
+    <subfield code="a">0000</subfield>
+  </datafield>
+  <datafield tag="101U">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="201B" occurrence="01">
+    <subfield code="0">10-01-03</subfield>
+    <subfield code="t">13:45:23.406</subfield>
+  </datafield>
+  <datafield tag="203@" occurrence="01">
+    <subfield code="0">585039968</subfield>
+  </datafield>
+  <datafield tag="208@" occurrence="01">
+    <subfield code="a">25-02-93</subfield>
+    <subfield code="b">n</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="01">
+    <subfield code="a">T 18.657</subfield>
+    <subfield code="d">u</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209C" occurrence="01">
+    <subfield code="a">93:0509</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209G" occurrence="01">
+    <subfield code="a">WIM8$0177210</subfield>
+  </datafield>
+  <datafield tag="220B" occurrence="01">
+    <subfield code="a">48,- DM</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="01">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="101@">
+    <subfield code="a">100</subfield>
+    <subfield code="c">PICA</subfield>
+  </datafield>
+  <datafield tag="101B">
+    <subfield code="0">18-11-97</subfield>
+    <subfield code="t">10:11:07.508</subfield>
+  </datafield>
+  <datafield tag="101C">
+    <subfield code="0">10-11-97</subfield>
+  </datafield>
+  <datafield tag="101D">
+    <subfield code="0">18-11-97</subfield>
+    <subfield code="b">4913</subfield>
+    <subfield code="a">0001</subfield>
+  </datafield>
+  <datafield tag="101U">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="145Z">
+    <subfield code="a">F/E 232 Hege</subfield>
+  </datafield>
+  <datafield tag="201B" occurrence="01">
+    <subfield code="0">18-11-97</subfield>
+    <subfield code="t">10:11:07.647</subfield>
+  </datafield>
+  <datafield tag="201C" occurrence="01">
+    <subfield code="0">29-10-97</subfield>
+  </datafield>
+  <datafield tag="201D" occurrence="01">
+    <subfield code="0">18-11-97</subfield>
+    <subfield code="b">4913</subfield>
+    <subfield code="a">0001</subfield>
+  </datafield>
+  <datafield tag="203@" occurrence="01">
+    <subfield code="0">262515172</subfield>
+  </datafield>
+  <datafield tag="208@" occurrence="01">
+    <subfield code="a">18-11-97</subfield>
+    <subfield code="b">kf</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="01">
+    <subfield code="b">3100</subfield>
+    <subfield code="j">1</subfield>
+    <subfield code="f">FGSE-FH</subfield>
+    <subfield code="a">97.17476</subfield>
+    <subfield code="d">u</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="01">
+    <subfield code="a">1991 a 731:1</subfield>
+    <subfield code="x">09</subfield>
+  </datafield>
+  <datafield tag="209G" occurrence="01">
+    <subfield code="a">MA9/1$0301922</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="01">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="101@">
+    <subfield code="a">110</subfield>
+    <subfield code="c">PICA</subfield>
+  </datafield>
+  <datafield tag="101B">
+    <subfield code="0">07-09-05</subfield>
+    <subfield code="t">08:21:22.000</subfield>
+  </datafield>
+  <datafield tag="101C">
+    <subfield code="0">14-01-98</subfield>
+  </datafield>
+  <datafield tag="101D">
+    <subfield code="0">07-09-05</subfield>
+    <subfield code="b">801</subfield>
+    <subfield code="a">1999</subfield>
+  </datafield>
+  <datafield tag="101U">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="144Z" occurrence="20">
+    <subfield code="a">Geist</subfield>
+  </datafield>
+  <datafield tag="145Z">
+    <subfield code="a">Phil 825</subfield>
+  </datafield>
+  <datafield tag="145Z" occurrence="20">
+    <subfield code="a">SoW 335</subfield>
+  </datafield>
+  <datafield tag="201B" occurrence="01">
+    <subfield code="0">12-08-05</subfield>
+    <subfield code="t">20:42:19.000</subfield>
+  </datafield>
+  <datafield tag="201C" occurrence="01">
+    <subfield code="0">14-01-98</subfield>
+  </datafield>
+  <datafield tag="201D" occurrence="01">
+    <subfield code="0">12-08-05</subfield>
+    <subfield code="b">384906</subfield>
+    <subfield code="a">1999</subfield>
+  </datafield>
+  <datafield tag="203@" occurrence="01">
+    <subfield code="0">269666141</subfield>
+  </datafield>
+  <datafield tag="208@" occurrence="01">
+    <subfield code="a">14-01-98</subfield>
+    <subfield code="b">kz</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="01">
+    <subfield code="b">3110</subfield>
+    <subfield code="f">CAM</subfield>
+    <subfield code="a">Phil 825.024</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209C" occurrence="01">
+    <subfield code="a">97-6790</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209G" occurrence="01">
+    <subfield code="a">LUN4$03639797</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="01">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="201B" occurrence="02">
+    <subfield code="0">11-08-05</subfield>
+    <subfield code="t">18:54:29.000</subfield>
+  </datafield>
+  <datafield tag="201C" occurrence="02">
+    <subfield code="0">04-06-96</subfield>
+  </datafield>
+  <datafield tag="201D" occurrence="02">
+    <subfield code="0">07-09-05</subfield>
+    <subfield code="b">801</subfield>
+    <subfield code="a">1999</subfield>
+  </datafield>
+  <datafield tag="203@" occurrence="02">
+    <subfield code="0">188090088</subfield>
+  </datafield>
+  <datafield tag="208@" occurrence="02">
+    <subfield code="a">04-06-96</subfield>
+    <subfield code="b">ka</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="02">
+    <subfield code="b">3110</subfield>
+    <subfield code="j">1</subfield>
+    <subfield code="f">ROT</subfield>
+    <subfield code="a">SoW 335/168</subfield>
+    <subfield code="d">u</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209C" occurrence="02">
+    <subfield code="a">430/96</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209G" occurrence="02">
+    <subfield code="a">961$0130524</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="02">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="101@">
+    <subfield code="a">120</subfield>
+    <subfield code="c">PICA</subfield>
+  </datafield>
+  <datafield tag="101B">
+    <subfield code="0">21-10-03</subfield>
+    <subfield code="t">07:48:10.000</subfield>
+  </datafield>
+  <datafield tag="101D">
+    <subfield code="0">21-10-03</subfield>
+    <subfield code="b">5004</subfield>
+    <subfield code="a">0715</subfield>
+  </datafield>
+  <datafield tag="101U">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="145Z">
+    <subfield code="a">phi 391 phg</subfield>
+  </datafield>
+  <datafield tag="201B" occurrence="01">
+    <subfield code="0">21-10-03</subfield>
+    <subfield code="t">07:48:10.000</subfield>
+  </datafield>
+  <datafield tag="201D" occurrence="01">
+    <subfield code="0">21-10-03</subfield>
+    <subfield code="b">5004</subfield>
+    <subfield code="a">0715</subfield>
+  </datafield>
+  <datafield tag="203@" occurrence="01">
+    <subfield code="0">619130725</subfield>
+  </datafield>
+  <datafield tag="208@" occurrence="01">
+    <subfield code="a">21-10-03</subfield>
+    <subfield code="b">z</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="01">
+    <subfield code="a">phi 391 phg CM 0791</subfield>
+    <subfield code="d">u</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="01">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="101@">
+    <subfield code="a">121</subfield>
+    <subfield code="c">PICA</subfield>
+  </datafield>
+  <datafield tag="101B">
+    <subfield code="0">22-08-02</subfield>
+    <subfield code="t">21:14:12.112</subfield>
+  </datafield>
+  <datafield tag="101U">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="107F">
+    <subfield code="0">BISL06053518</subfield>
+  </datafield>
+  <datafield tag="201B" occurrence="01">
+    <subfield code="0">22-08-02</subfield>
+    <subfield code="t">21:14:12.156</subfield>
+  </datafield>
+  <datafield tag="201C" occurrence="01">
+    <subfield code="0">01-01-01</subfield>
+  </datafield>
+  <datafield tag="203@" occurrence="01">
+    <subfield code="0">78675642X</subfield>
+  </datafield>
+  <datafield tag="208@" occurrence="01">
+    <subfield code="a">21-03-02</subfield>
+    <subfield code="b">r</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="01">
+    <subfield code="a">89-1164</subfield>
+    <subfield code="d">u</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209G" occurrence="01">
+    <subfield code="a">06053518</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="01">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="101@">
+    <subfield code="a">130</subfield>
+    <subfield code="c">PICA</subfield>
+  </datafield>
+  <datafield tag="101B">
+    <subfield code="0">30-01-98</subfield>
+    <subfield code="t">08:12:01.057</subfield>
+  </datafield>
+  <datafield tag="101C">
+    <subfield code="0">13-10-88</subfield>
+    <subfield code="b">D_</subfield>
+  </datafield>
+  <datafield tag="101D">
+    <subfield code="0">08-08-91</subfield>
+    <subfield code="b">UN</subfield>
+  </datafield>
+  <datafield tag="101U">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="107F">
+    <subfield code="0">7N75/16T</subfield>
+  </datafield>
+  <datafield tag="145Z">
+    <subfield code="a">JHE E</subfield>
+  </datafield>
+  <datafield tag="145Z" occurrence="01">
+    <subfield code="a">NOH 37</subfield>
+  </datafield>
+  <datafield tag="150K" occurrence="01">
+    <subfield code="n">2</subfield>
+    <subfield code="a">Phaenomenologie des Geistes</subfield>
+  </datafield>
+  <datafield tag="150K" occurrence="01">
+    <subfield code="n">2</subfield>
+    <subfield code="a">Hegel, G.</subfield>
+  </datafield>
+  <datafield tag="150K" occurrence="01">
+    <subfield code="n">2</subfield>
+    <subfield code="a">Philosophische Bibliothek</subfield>
+    <subfield code="b">--414</subfield>
+  </datafield>
+  <datafield tag="150K" occurrence="01">
+    <subfield code="n">1</subfield>
+    <subfield code="a">JHE E</subfield>
+  </datafield>
+  <datafield tag="150K" occurrence="01">
+    <subfield code="n">1</subfield>
+    <subfield code="a">NOH 37</subfield>
+  </datafield>
+  <datafield tag="201B" occurrence="01">
+    <subfield code="0">20-11-04</subfield>
+    <subfield code="t">15:19:36.000</subfield>
+  </datafield>
+  <datafield tag="201C" occurrence="01">
+    <subfield code="0">13-10-88</subfield>
+    <subfield code="b">D_</subfield>
+  </datafield>
+  <datafield tag="201D" occurrence="01">
+    <subfield code="0">20-11-04</subfield>
+    <subfield code="b">r130sso</subfield>
+    <subfield code="a">1999</subfield>
+  </datafield>
+  <datafield tag="203@" occurrence="01">
+    <subfield code="0">032138040</subfield>
+  </datafield>
+  <datafield tag="208@" occurrence="01">
+    <subfield code="a">08-08-91</subfield>
+    <subfield code="b">nb</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="01">
+    <subfield code="f">B</subfield>
+    <subfield code="a">JHE E 4415-166 9</subfield>
+    <subfield code="d">u</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209C" occurrence="01">
+    <subfield code="a">4415-166 9</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209G" occurrence="01">
+    <subfield code="a">700$44151669</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="01">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="201B" occurrence="02">
+    <subfield code="0">29-04-98</subfield>
+    <subfield code="t">00:10:09.853</subfield>
+  </datafield>
+  <datafield tag="201C" occurrence="02">
+    <subfield code="0">13-10-88</subfield>
+    <subfield code="b">D_</subfield>
+  </datafield>
+  <datafield tag="201D" occurrence="02">
+    <subfield code="0">08-08-91</subfield>
+    <subfield code="b">UN</subfield>
+  </datafield>
+  <datafield tag="203@" occurrence="02">
+    <subfield code="0">032138059</subfield>
+  </datafield>
+  <datafield tag="208@" occurrence="02">
+    <subfield code="a">08-08-91</subfield>
+    <subfield code="b">ng</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="02">
+    <subfield code="f">G</subfield>
+    <subfield code="a">NOH 37 4519-902 8</subfield>
+    <subfield code="d">u</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209C" occurrence="02">
+    <subfield code="a">4519-902 8</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209G" occurrence="02">
+    <subfield code="a">700$45199028</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="02">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="101@">
+    <subfield code="a">136</subfield>
+    <subfield code="c">PICA</subfield>
+  </datafield>
+  <datafield tag="101B">
+    <subfield code="0">24-02-03</subfield>
+    <subfield code="t">12:21:11.000</subfield>
+  </datafield>
+  <datafield tag="101D">
+    <subfield code="0">24-02-03</subfield>
+    <subfield code="b">4349</subfield>
+    <subfield code="a">3526</subfield>
+  </datafield>
+  <datafield tag="101U">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="144Z">
+    <subfield code="a">Geist</subfield>
+  </datafield>
+  <datafield tag="144Z" occurrence="01">
+    <subfield code="a">Phänomenologie</subfield>
+  </datafield>
+  <datafield tag="144Z" occurrence="02">
+    <subfield code="a">Philosophie</subfield>
+  </datafield>
+  <datafield tag="144Z" occurrence="03">
+    <subfield code="a">Hegel, Georg Wilhelm Friedrich</subfield>
+  </datafield>
+  <datafield tag="145Z">
+    <subfield code="a">Ta:A4</subfield>
+  </datafield>
+  <datafield tag="145Z" occurrence="01">
+    <subfield code="a">Ta:G</subfield>
+  </datafield>
+  <datafield tag="201B" occurrence="01">
+    <subfield code="0">03-08-11</subfield>
+    <subfield code="t">12:23:06.000</subfield>
+  </datafield>
+  <datafield tag="201C" occurrence="01">
+    <subfield code="0">11-11-02</subfield>
+  </datafield>
+  <datafield tag="201D" occurrence="01">
+    <subfield code="0">03-08-11</subfield>
+    <subfield code="b">4346</subfield>
+    <subfield code="a">3526</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="01">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="203@" occurrence="01">
+    <subfield code="0">577615866</subfield>
+  </datafield>
+  <datafield tag="208@" occurrence="01">
+    <subfield code="a">23-01-03</subfield>
+    <subfield code="b">zpln</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="01">
+    <subfield code="a">2002 A 3961</subfield>
+    <subfield code="d">u</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209G" occurrence="01">
+    <subfield code="a">WIS1$1739883</subfield>
+  </datafield>
+  <datafield tag="101@">
+    <subfield code="a">181</subfield>
+    <subfield code="c">PICA</subfield>
+  </datafield>
+  <datafield tag="201B" occurrence="01">
+    <subfield code="0">12-01-10</subfield>
+    <subfield code="t">14:10:42.000</subfield>
+  </datafield>
+  <datafield tag="201D" occurrence="01">
+    <subfield code="0">12-01-10</subfield>
+    <subfield code="b">934384</subfield>
+    <subfield code="a">3181</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="01">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="203@" occurrence="01">
+    <subfield code="0">1109002394</subfield>
+  </datafield>
+  <datafield tag="208@" occurrence="01">
+    <subfield code="a">12-01-10</subfield>
+    <subfield code="b">rb010</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="01">
+    <subfield code="b">3181</subfield>
+    <subfield code="j">010</subfield>
+    <subfield code="f">KB</subfield>
+    <subfield code="a">A 27 m kl</subfield>
+    <subfield code="d">i</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="01">
+    <subfield code="a">:A:27:m:kl:</subfield>
+    <subfield code="x">10</subfield>
+  </datafield>
+  <datafield tag="209C" occurrence="01">
+    <subfield code="a">91.2506</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="220B" occurrence="01">
+    <subfield code="a">Retro Wa</subfield>
+  </datafield>
+  <datafield tag="101@">
+    <subfield code="a">285</subfield>
+    <subfield code="c">PICA</subfield>
+  </datafield>
+  <datafield tag="101B">
+    <subfield code="0">14-11-02</subfield>
+    <subfield code="t">15:58:46.382</subfield>
+  </datafield>
+  <datafield tag="101C">
+    <subfield code="0">01-01-01</subfield>
+  </datafield>
+  <datafield tag="101D">
+    <subfield code="0">03-11-02</subfield>
+    <subfield code="b">0285</subfield>
+    <subfield code="a">0000</subfield>
+  </datafield>
+  <datafield tag="101U">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="145Z">
+    <subfield code="a">CG 4064</subfield>
+  </datafield>
+  <datafield tag="201B" occurrence="01">
+    <subfield code="0">06-09-07</subfield>
+    <subfield code="t">13:07:58.000</subfield>
+  </datafield>
+  <datafield tag="201C" occurrence="01">
+    <subfield code="0">01-01-01</subfield>
+  </datafield>
+  <datafield tag="201D" occurrence="01">
+    <subfield code="0">06-09-07</subfield>
+    <subfield code="b">9445</subfield>
+    <subfield code="a">0517</subfield>
+  </datafield>
+  <datafield tag="201U" occurrence="01">
+    <subfield code="0">utf8</subfield>
+  </datafield>
+  <datafield tag="203@" occurrence="01">
+    <subfield code="0">791602559</subfield>
+  </datafield>
+  <datafield tag="208@" occurrence="01">
+    <subfield code="a">15-02-95</subfield>
+    <subfield code="b">r</subfield>
+  </datafield>
+  <datafield tag="209A" occurrence="01">
+    <subfield code="f">1000</subfield>
+    <subfield code="a">CG 4064 PHAE</subfield>
+    <subfield code="d">u</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209C" occurrence="01">
+    <subfield code="a">95011347</subfield>
+    <subfield code="x">00</subfield>
+  </datafield>
+  <datafield tag="209G" occurrence="01">
+    <subfield code="a">07901261</subfield>
+  </datafield>
+</record>
diff --git a/vendor/hab/picareader/tests/src/HAB/Pica/Reader/PicaNormReaderTest.php b/vendor/hab/picareader/tests/src/HAB/Pica/Reader/PicaNormReaderTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..1fb13f38c7fde75ca551d022e0832086a9d33fc3
--- /dev/null
+++ b/vendor/hab/picareader/tests/src/HAB/Pica/Reader/PicaNormReaderTest.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * Unit test for the PicaNormReader class.
+ *
+ * This file is part of PicaReader.
+ *
+ * PicaReader is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaReader is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaReader.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   PicaReader
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2012, 2013 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.html GNU General Public License v3
+ */
+
+namespace HAB\Pica\Reader;
+
+use PHPUnit_FrameWork_TestCase;
+
+class PicaNormReaderTest extends PHPUnit_FrameWork_TestCase
+{
+    public function testReadStringData ()
+    {
+        $data   = "003@ \x1f0test\x1e002@ \x1f0Aau";
+        $reader = new PicaNormReader();
+        $reader->open($data);
+        $record = $reader->read();
+        $this->assertInstanceOf('HAB\Pica\Record\TitleRecord', $record);
+        $reader->close();
+    }
+}
diff --git a/vendor/hab/picareader/tests/src/HAB/Pica/Reader/PicaPlainReaderTest.php b/vendor/hab/picareader/tests/src/HAB/Pica/Reader/PicaPlainReaderTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..92a51da4c7cf334740caaad91a25a69df4b94f84
--- /dev/null
+++ b/vendor/hab/picareader/tests/src/HAB/Pica/Reader/PicaPlainReaderTest.php
@@ -0,0 +1,140 @@
+<?php
+
+/**
+ * Unit test for the PicaPlainReader class.
+ *
+ * This file is part of PicaReader.
+ *
+ * PicaReader is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaReader is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaReader.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   PicaReader
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2012, 2013 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.html GNU General Public License v3
+ */
+
+namespace HAB\Pica\Reader;
+
+use PHPUnit_FrameWork_TestCase ;
+
+class PicaPlainReaderTest extends PHPUnit_FrameWork_TestCase
+{
+
+    protected $_reader;
+
+    public function setup ()
+    {
+        $this->_reader = new PicaPlainReader();
+    }
+
+    public function testRead ()
+    {
+        $this->_reader->open($this->getFixture('single_record'));
+        $record = $this->_reader->read();
+        $this->assertInstanceOf('\HAB\Pica\Record\TitleRecord', $record);
+        $this->assertFalse($this->_reader->read());
+        $this->assertEquals(377, count($record->getFields()));
+        $this->assertEquals(21, count($record->getLocalRecords()));
+        $this->assertEquals('024836885', $record->getPPN());
+        $this->assertEquals(3, count($record->getLocalRecordByILN(31)->getCopyRecords()));
+    }
+
+    public function testReadDoubleEncodedDollarSign ()
+    {
+        $this->_reader->open('002@/00 $0T$adouble$$dollar');
+        $record = $this->_reader->read();
+        $field = $record->getFirstMatchingField('002@/00');
+        $subfield = $field->getNthSubfield('a', 0);
+        $this->assertEquals('double$dollar', $subfield->getValue());
+    }
+
+    public function testReadDoubleEncodedDoubleDollarSign2x ()
+    {
+        $this->_reader->open('002@/00 $0T$adouble$$$$dollar');
+        $record = $this->_reader->read();
+        $field = $record->getFirstMatchingField('002@/00');
+        $subfield = $field->getNthSubfield('a', 0);
+        $this->assertEquals('double$$dollar', $subfield->getValue());
+    }
+
+    public function testReadDoubleEncodedDoubleDollarSignAtEnd ()
+    {
+        $this->_reader->open('002@/00 $0T$adoubledollar$$');
+        $record = $this->_reader->read();
+        $field = $record->getFirstMatchingField('002@/00');
+        $subfield = $field->getNthSubfield('a', 0);
+        $this->assertEquals('doubledollar$', $subfield->getValue());
+    }
+
+    public function testReadDoubleEncodedDoubleDollarSignOnly ()
+    {
+        $this->_reader->open('002@/00 $0T$a$$');
+        $record = $this->_reader->read();
+        $field = $record->getFirstMatchingField('002@/00');
+        $subfield = $field->getNthSubfield('a', 0);
+        $this->assertEquals('$', $subfield->getValue());
+    }
+
+    public function testReadFilterInvalidSubfieldCode ()
+    {
+        $filter = function (array $record) {
+            return array('fields' => array_map(function (array $field) {
+                return array('tag' => $field['tag'],
+                             'occurrence' => $field['occurrence'],
+                             'subfields' => array_filter($field['subfields'],
+                                                         function (array $subfield) {
+                                                         return \HAB\Pica\Record\Subfield::isValidSubfieldCode($subfield['code']);
+                                                     }));
+            }, $record['fields']));
+        };
+        $this->_reader->open("002@/00 \$0T\n000A/00 \$@FOOBAR");
+        $this->_reader->setFilter($filter);
+        $this->assertSame($filter, $this->_reader->getFilter());
+        $this->_reader->read();
+        $this->_reader->unsetFilter();
+    }
+
+    public function testReadIgnoreLines ()
+    {
+        $this->_reader->open("002@/00 \$0T\nfoo: foobar");
+        $this->_reader->ignoreLineRegexp = '/^[a-z]+:/';
+        $this->_reader->read();
+    }
+
+    ///
+
+    /**
+     * @expectedException RuntimeException
+     */
+    public function testReadMalformedSingleDollarAtEnd ()
+    {
+        $this->_reader->open('002@/00 $0T$aFOOBAR$');
+        $record = $this->_reader->read();
+    }
+    /**
+     * @expectedException RuntimeException
+     */
+    public function testReadMalformedLine ()
+    {
+        $this->_reader->open('');
+        $this->_reader->read();
+    }
+
+    ///
+
+    protected function getFixture ($fixture)
+    {
+        return file_get_contents(\PHPUNIT_FIXTURES . DIRECTORY_SEPARATOR . "{$fixture}.pp");
+    }
+}
diff --git a/vendor/hab/picareader/tests/src/HAB/Pica/Reader/PicaXmlReaderTest.php b/vendor/hab/picareader/tests/src/HAB/Pica/Reader/PicaXmlReaderTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..576eabfac51e57d56688767e4394b39f9ddef842
--- /dev/null
+++ b/vendor/hab/picareader/tests/src/HAB/Pica/Reader/PicaXmlReaderTest.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * Unit test for the PicaPlainReader class.
+ *
+ * This file is part of PicaReader.
+ *
+ * PicaReader is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaReader is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaReader.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   PicaReader
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2012, 2013 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.html GNU General Public License v3
+ */
+
+namespace HAB\Pica\Reader;
+
+use PHPUnit_FrameWork_TestCase;
+
+class PicaXmlReaderTest extends PHPUnit_FrameWork_TestCase
+{
+
+    protected $_reader;
+
+    public function setup ()
+    {
+        $this->_reader = new PicaXmlReader();
+    }
+
+    public function testRead ()
+    {
+        $this->_reader->open($this->getFixture('single_record'));
+        $record = $this->_reader->read();
+        $this->assertInstanceOf('\HAB\Pica\Record\TitleRecord', $record);
+        $this->assertFalse($this->_reader->read());
+        $this->assertEquals(377, count($record->getFields()));
+        $this->assertEquals(21, count($record->getLocalRecords()));
+        $this->assertEquals('024836885', $record->getPPN());
+        $this->assertEquals(3, count($record->getLocalRecordByILN(31)->getCopyRecords()));
+    }
+
+    protected function getFixture ($fixture)
+    {
+        return file_get_contents(\PHPUNIT_FIXTURES . DIRECTORY_SEPARATOR . "{$fixture}.xml");
+    }
+}
\ No newline at end of file
diff --git a/vendor/hab/picarecord/.eproject b/vendor/hab/picarecord/.eproject
new file mode 100644
index 0000000000000000000000000000000000000000..3b428359d6e186bf6d97b36590cad835f34244ad
--- /dev/null
+++ b/vendor/hab/picarecord/.eproject
@@ -0,0 +1 @@
+:project-name "PicaRecord"
diff --git a/vendor/hab/picarecord/.gitignore b/vendor/hab/picarecord/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..ac1c8a85dfcc5e9c20388b08d394924bbb65fa17
--- /dev/null
+++ b/vendor/hab/picarecord/.gitignore
@@ -0,0 +1,8 @@
+*~
+\#*
+.\#*
+TAGS
+ChangeLog
+vendor
+review
+build
diff --git a/vendor/hab/picarecord/COPYING b/vendor/hab/picarecord/COPYING
new file mode 100644
index 0000000000000000000000000000000000000000..94a9ed024d3859793618152ea559a168bbcbb5e2
--- /dev/null
+++ b/vendor/hab/picarecord/COPYING
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/vendor/hab/picarecord/README.org b/vendor/hab/picarecord/README.org
new file mode 100644
index 0000000000000000000000000000000000000000..9e635081f2780ee79d40fdeffccf9e53b445f560
--- /dev/null
+++ b/vendor/hab/picarecord/README.org
@@ -0,0 +1,148 @@
+#+TITLE: PicaRecord -- Object oriented interface to Pica+ records
+#+AUTHOR: David Maus
+#+EMAIL: maus@hab.de
+
+* About
+
+PicaRecord provides an object oriented interface to Pica+ records, fields, and subfields. It does
+not provide the means to read or write Pica+ records. In order to do so you need to install the
+packages PicaReader and PicaWriter that are available as dedicated composer packages.
+
+PicaRecord is copyright (c) 2012-2016 by Herzog August Bibliothek Wolfenbüttel and released under the
+terms of the GNU General Public License v3.
+
+* Installation
+
+You can install PicaRecord via Composer.
+
+#+BEGIN_EXAMPLE
+composer require hab/picarecord
+#+END_EXAMPLE
+
+* Usage
+
+** Records, Fields, and Subfields
+
+*** Subfields
+
+Subfields are represented by the =Subfield= class. A Pica+ subfield is a cons (pair) of an
+alphanumeric case-sensitive ASCII character and a non-empty string value. The code of a subfield is
+considered to be immutable and the value must not be the empty string.
+
+*** Fields
+
+Field are represented by the =Field= class. A Pica+ field is an ordered list of subfields and
+partially identified[1] by two properties, the field tag and the field occurrence. Both properties
+are set on instantiation and considered to be immutable.
+
+In the following text the /field shorthand/ refers to a string consisting of the field tag, followed
+by a forward slash (ASCII 47) followed by two digits denoting the field occurrence. For example the
+field shorthand of the field 021A with an occurrence of 0 is =021A/00=.
+
+**** Retrieving subfields
+
+=Field::getSubfields()= returns all subfields of a field when called with no arguments. To retrieve
+specific subfields you can pass an arbitrary number of arguments each representing a subfield
+code. If you do so the returned array will be constructed as follows:
+
+Each element of the returned array corresponds to a subfield code in the argument list. If the field
+has a subfield with the specified code the element of the returned array contains the subfield. If
+the field does not have a subfield with the specified code the element of the returned array
+contains =NULL=. In order to retrieve multiple subfields with the same subfield code you need to
+repeat the subfield code in the argument list. The first occurrence of the code in the argument list
+refers to the first subfield with the specified code, the second occurrence to the second subfield
+and so on.
+
+Because =Field::getSubfields()= when called with subfield codes as arguments returns an array with a
+known size (as much elements as arguments were passed to the function) you can conveniently use
+PHP's =list()= operation.
+
+Example: To retrieve the first and last name of the author encoded in the Pica+ field =028A/00= you
+can call =Field::getSubfields()= as follows:
+
+#+BEGIN_EXAMPLE
+list($firstName, $lastName, $personalName) = $field->getSubfields('d', 'a', '5');
+#+END_EXAMPLE
+
+**** Manipulating the subfield list
+
+You can use =Field::addSubfield()= to add a field to the end of the subfield list. If the subfield
+is already part of the subfield list an =InvalidArgumentException= is thrown. For more sophisticated
+manipulations of a field's subfield list you can use =Field::setSubfields()= to replace the subfield
+list of a field. 
+
+A subfield can be deleted using =Field::removeSubfield()= which takes the subfield to delete as sole
+argument and throws an =InvalidArgumentException= if the field does not contain the subfield.
+
+*** Records
+
+**** Record classes
+
+PicaRecord provides classes for the four record types of /title records/, /authority records/,
+/local records/, and /copy records/. The relationship of title, local, and copy records is as
+follows: A title record may contain zero or more local records. Each local record may contain up to
+99 copy records.
+
+Local records can be identified by the internal library number (ILN) of the library they belong to
+and retrieved either by =TitleRecord::getLocalRecord()=, which returns an array of all local
+records, or =TitleRecord::getLocalRecordByILN()= which retrieves a single local record identifed by
+its first argument.
+
+Copy records hold information about particular items (exemplars) and are identified by the number of
+the item in the local record. You can use =LocalRecord::getCopyRecords()= or
+=LocalRecord::getLocalRecordByItemNumber()= to retrieve an array with all copy records or a single
+copy record with a specified item number respectively.
+
+All record classes implement the two methods =Record::isEmpty()= which returns =TRUE= if a record is
+empty (does not contain any fields) and =Record::isValid()= which performs a preliminary validation
+of the record.
+
+**** Selecting and deleting fields
+
+=Record::getFields()= returns all fields of the record when called without arguments. If you call it
+with the body of a regular expression as argument it will only return the fields whose shorthand is
+matched by the regular expression.
+
+=Record::select()= provides a more generic access to a record's fields. It takes a predicate
+function as argument and returns all fields that fullfill the predicate. A predicate function can be
+any valid PHP callback that takes a Field as argument and return TRUE if the field fullfills the
+predicate or otherwise FALSE.
+
+=Record::delete()= deletes all fields that match a predicate function (see above).
+
+If a record contains other records, i.e. if a record is a title or local record, =Record::delete()=,
+=Record::select()=, and =Record::getFields()= operate on all fields of the record, including the
+fields of the contained records.
+
+**** Appending fields to a record
+
+Append fields to an existing record is not as straightforward as selecting or deleting fields. Each
+record class has its own restrictions when it comes to appending a field to it via the
+=Record::append()= function:
+
+- you can only append fields with a level of 0 to title and authority records
+- you can append fields with a level of 1 to local records
+- you can only append fields with a level of 2 to copy records; as an additional restriction the
+  occurrence value of the field must be equal to the item number of the copy record
+
+#+CAPTION: Allowed field levels per record class
+| Record class    | Allowed field level in append() |
+|-----------------+---------------------------------|
+| TitleRecord     | Level 0                         |
+| AuthorityRecord | Level 0                         |
+| LocalRecord     | Level 1                         |
+| CopyRecord      | Level 2                         |
+
+The attempt to add a field with a different level then the allowed level results in an
+=InvalidArgumentException= to be thrown.
+
+* Acknowledgments
+
+Large parts of this package would not have been possible without studying the source of
+[[http://search.cpan.org/dist/PICA-Record/][Pica::Record]], an open source Perl library for handling Pica+ records by Jakob Voß, and the practical
+knowledge of our library's catalogers.
+
+* Footnotes
+
+[1] E.g. a title record may contain zero or more fields with tag =101@= and occurrence =00=; fields with this
+shorthand indicate the start of a local record.
diff --git a/vendor/hab/picarecord/composer.json b/vendor/hab/picarecord/composer.json
new file mode 100644
index 0000000000000000000000000000000000000000..8e4fa7a33af3aefbf17d01c29713100d119b6bb6
--- /dev/null
+++ b/vendor/hab/picarecord/composer.json
@@ -0,0 +1,21 @@
+{
+    "name": "hab/picarecord",
+    "description": "Object oriented interface to Pica+ records, fields, and subfields",
+    "type": "library",
+    "license": "GPL-3.0+",
+    "authors": [
+	{
+	    "name": "David Maus",
+	    "email": "maus@hab.de",
+	    "role": "Developer"
+	}
+    ],
+    "support": {
+	"email": "maus@hab.de"
+    },
+    "autoload": {
+        "psr-0": {
+            "HAB\\Pica": "src/"
+        }
+    }
+}
diff --git a/vendor/hab/picarecord/phpunit.xml b/vendor/hab/picarecord/phpunit.xml
new file mode 100644
index 0000000000000000000000000000000000000000..50449de41c32091bf61232c8c53ca069a4ab714c
--- /dev/null
+++ b/vendor/hab/picarecord/phpunit.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<phpunit bootstrap="tests/unit-tests/bootstrap.php" strict="true">
+  <testsuites>
+    <testsuite name="Unit Tests">
+      <directory suffix="Test.php">tests</directory>
+    </testsuite>
+  </testsuites>
+  <filter>
+    <blacklist>
+      <directory suffix=".php">vendor</directory>
+      <directory suffix=".php">tests</directory>
+    </blacklist>
+    <whitelist addUncoveredFilesFromWhitelist="true">
+      <directory suffix=".php">bin</directory>
+      <directory suffix=".php">src</directory>
+    </whitelist>
+  </filter>
+  <logging>
+    <log type="coverage-html" target="review/code-coverage"/>
+  </logging>
+</phpunit>
diff --git a/vendor/hab/picarecord/src/HAB/Pica/Record/AuthorityRecord.php b/vendor/hab/picarecord/src/HAB/Pica/Record/AuthorityRecord.php
new file mode 100644
index 0000000000000000000000000000000000000000..a62980c12cf13ae2f897c11e18ea2985d7a75724
--- /dev/null
+++ b/vendor/hab/picarecord/src/HAB/Pica/Record/AuthorityRecord.php
@@ -0,0 +1,147 @@
+<?php
+
+/**
+ * Pica+ AuthorityRecord.
+ *
+ * This file is part of PicaRecord.
+ *
+ * PicaRecord is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaRecord is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaRecord.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   PicaRecord
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2012, 2013 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.html GNU General Public License v3
+ */
+
+namespace HAB\Pica\Record;
+
+use InvalidArgumentException;
+
+class AuthorityRecord extends Record
+{
+
+    /**
+     * Append a field to the record.
+     *
+     * @see Record::append()
+     *
+     * @throws InvalidArgumentException Field level other than 0
+     * @throws InvalidArgumentException Field already in record
+     *
+     * @param  Field $field Field to append
+     * @return void
+     */
+    public function append (Field $field)
+    {
+        if ($field->getLevel() !== 0) {
+            throw new InvalidArgumentException("Invalid field level {$field->getLevel()}");
+        }
+        return parent::append($field);
+    }
+
+    /**
+     * Return the Pica production number (record identifier).
+     *
+     * @return string|null Pica production number or NULL if none exists
+     */
+    public function getPPN ()
+    {
+        $ppnField = $this->getFirstMatchingField('003@/00');
+        if ($ppnField) {
+            $ppnSubfield = $ppnField->getNthSubfield('0', 0);
+            if ($ppnSubfield) {
+                return $ppnSubfield->getValue();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Set the Pica production number.
+     *
+     * Create a field 003@/00 if necessary.
+     *
+     * @param  string $ppn Pica production number
+     * @return void
+     */
+    public function setPPN ($ppn)
+    {
+        $ppnField = $this->getFirstMatchingField('003@/00');
+        if ($ppnField) {
+            $ppnSubfield = $ppnField->getNthSubfield('0', 0);
+            if ($ppnSubfield) {
+                $ppnSubfield->setValue($ppn);
+            } else {
+                $ppnField->append(new Subfield('0', $ppn));
+            }
+        } else {
+            $this->append(new Field('003@', 0, array(new Subfield('0', $ppn))));
+        }
+    }
+
+    /**
+     * Return TRUE if the record is valid.
+     *
+     * A valid authority record MUST have exactly one valid PPN field
+     * (003@/00$0) and exactly one type field (002@/0$0) with a type indicator
+     * `T'.
+     *
+     * @see AuthorityRecord::checkPPN();
+     * @see AuthorityRecord::checkType();
+     *
+     * @return boolean TRUE if the record is valid
+     */
+    public function isValid ()
+    {
+        return parent::isValid() && $this->checkPPN() && $this->checkType();
+    }
+
+    /**
+     * Return true if the record has exactly one PPN field (003@/00) with a
+     * subfield $0.
+     *
+     * @return boolean True if the record has a valid PPN field
+     */
+    protected function checkPPN ()
+    {
+        $ppnField = $this->getFields('003@/00');
+        if (count($ppnField) === 1) {
+            $ppnSubfield = reset($ppnField)->getNthSubfield('0', 0);
+            if ($ppnSubfield) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Return true if the record has exactly one type field (002@/00) with a
+     * subfield $0 whose first character is `T'.
+     *
+     * @return boolean True if the record has a valid type field
+     */
+    protected function checkType ()
+    {
+        $typeField = $this->getFields('002@/00');
+        if (count($typeField) === 1) {
+            $typeSubfield = reset($typeField)->getNthSubfield('0', 0);
+            if ($typeSubfield) {
+                $typeCode = $typeSubfield->getValue();
+                if ($typeCode === 'T') {
+                    return true;
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/vendor/hab/picarecord/src/HAB/Pica/Record/CopyRecord.php b/vendor/hab/picarecord/src/HAB/Pica/Record/CopyRecord.php
new file mode 100644
index 0000000000000000000000000000000000000000..d90bbd18fe585c8ea6676ffbcb7d8ee3be2bcb5f
--- /dev/null
+++ b/vendor/hab/picarecord/src/HAB/Pica/Record/CopyRecord.php
@@ -0,0 +1,170 @@
+<?php
+
+/**
+ * Pica+ CopyRecord.
+ *
+ * This file is part of PicaRecord.
+ *
+ * PicaRecord is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaRecord is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaRecord.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   PicaRecord
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2012, 2013 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.html GNU General Public License v3
+ */
+
+namespace HAB\Pica\Record;
+
+use InvalidArgumentException;
+
+class CopyRecord extends Record
+{
+
+    /**
+     * The item number.
+     *
+     * @var integer
+     */
+    protected $_itemNumber;
+
+    /**
+     * Append a field to the copy record.
+     *
+     * You can only append field of level 2 to a copy record.
+     *
+     * @see Record::append()
+     *
+     * @throws InvalidArgumentException Field level other than 2
+     * @throws InvalidArgumentException Item number mismatch
+     * @throws InvalidArgumentException Field already in record
+     *
+     * @param  Field $field Field to append
+     * @return void
+     */
+    public function append (Field $field)
+    {
+        if ($field->getLevel() !== 2) {
+            throw new InvalidArgumentException("Invalid field level: {$field->getLevel()}");
+        }
+        if ($this->getItemNumber() === null) {
+            $this->setItemNumber($field->getOccurrence());
+        }
+        If ($field->getOccurrence() != $this->getItemNumber()) {
+            throw new InvalidArgumentException("Item number mismatch: {$this->getItemNumber()}, {$field->getOccurrence()}");
+        }
+        return parent::append($field);
+    }
+
+    /**
+     * Return the item number.
+     *
+     * @return integer|null Item number
+     */
+    public function getItemNumber ()
+    {
+        return $this->_itemNumber;
+    }
+
+    /**
+     * Set the item number.
+     *
+     * @param  integer $itemNumber Item number
+     * @return void
+     */
+    protected function setItemNumber ($itemNumber)
+    {
+        $this->_itemNumber = (int)$itemNumber;
+    }
+
+    /**
+     * Return the exemplar production number (EPN).
+     *
+     * @return string Exemplar production number
+     */
+    public function getEPN ()
+    {
+        $epnField = $this->getFirstMatchingField('203@');
+        if ($epnField) {
+            $epnSubfield = $epnField->getNthSubfield('0', 0);
+            if ($epnSubfield) {
+                return $epnSubfield->getValue();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Set the exemplar production number (EPN).
+     *
+     * Create a field 203@ if necessary.
+     *
+     * @param  string $epn Exemplar production number
+     * @return void
+     */
+    public function setEPN ($epn)
+    {
+        $epnField = $this->getFirstMatchingField('203@');
+        if ($epnField) {
+            $epnSubfield = $epnField->getNthSubfield('0', 0);
+            if ($epnSubfield) {
+                $epnSubfield->setValue($epn);
+            } else {
+                $epnField->append(new Subfield('0', $epn));
+            }
+        } else {
+            $this->append(new Field('203@', $this->getItemNumber(), array(new Subfield('0', $epn))));
+        }
+    }
+
+    /**
+     * Set the containing local record.
+     *
+     * @param  LocalRecord $record Local record
+     * @return void
+     */
+    public function setLocalRecord (LocalRecord $record)
+    {
+        $this->unsetLocalRecord();
+        if (!$record->containsCopyRecord($this)) {
+            $record->addCopyRecord($this);
+        }
+        $this->_parent = $record;
+    }
+
+    /**
+     * Unset the containing local record.
+     *
+     * @return void
+     */
+    public function unsetLocalRecord ()
+    {
+        if ($this->_parent) {
+            if ($this->_parent->containsCopyRecord($this)) {
+                $this->_parent->removeCopyRecord($this);
+            }
+            $this->_parent = null;
+        }
+    }
+
+    /**
+     * Return the containing local record.
+     *
+     * @return LocalRecord|null
+     */
+    public function getLocalRecord ()
+    {
+        return $this->_parent;
+    }
+
+}
\ No newline at end of file
diff --git a/vendor/hab/picarecord/src/HAB/Pica/Record/Field.php b/vendor/hab/picarecord/src/HAB/Pica/Record/Field.php
new file mode 100644
index 0000000000000000000000000000000000000000..43a3a707f0a3c6513796e884a651b0da9b5fd550
--- /dev/null
+++ b/vendor/hab/picarecord/src/HAB/Pica/Record/Field.php
@@ -0,0 +1,370 @@
+<?php
+
+/**
+ * Pica+ Field.
+ *
+ * This file is part of PicaRecord.
+ *
+ * PicaRecord is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaRecord is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaRecord.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   PicaRecord
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2012-2016 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.html GNU General Public License v3
+ */
+
+namespace HAB\Pica\Record;
+
+use InvalidArgumentException;
+
+class Field 
+{
+
+    /**
+     * Regular expression matching a valid Pica+ field tag.
+     *
+     * @var string
+     */
+    const TAG_RE = '|^[012][0-9]{2}[A-Z@]$|D';
+
+    /**
+     * Return TRUE if argument is a valid field tag.
+     *
+     * @param  mixed $arg Variable to check
+     * @return boolean TRUE if argument is a valid field tag
+     */
+    public static function isValidFieldTag ($arg) 
+    {
+        return (bool)preg_match(self::TAG_RE, $arg);
+    }
+
+    /**
+     * Return TRUE if argument is a valid field occurrence.
+     *
+     * Argument is casted to int iff it is either null or a numeric string.
+     *
+     * @param  mixed $arg Variable to check
+     * @return boolean TRUE if argument is a valid field occurrence
+     */
+    public static function isValidFieldOccurrence ($arg) 
+    {
+        if ($arg === null || ctype_digit($arg)) {
+            $arg = (int)$arg;
+        }
+        return is_int($arg) && $arg >= 0 && $arg < 100;
+    }
+
+    /**
+     * Return predicate that matches a field shorthand against a regular
+     * expression.
+     *
+     * @param  string $reBody Body of regular expression
+     * @return callback Predicate
+     */
+    public static function match ($reBody) 
+    {
+        if (strpos($reBody, '#') !== false) {
+            $reBody = str_replace('#', '\#', $reBody);
+        }
+        $regexp = "#{$reBody}#D";
+        return function (Field $field) use ($regexp) {
+            return (bool)preg_match($regexp, $field->getShorthand());
+        };
+    }
+
+    /**
+     * Return a new field based on its array representation.
+     *
+     * @throws InvalidArgumentException Missing `tag', `occurrene', or `subfields' index
+     * @param  array $field Array representation of a field
+     * @return \HAB\Pica\Record\Field A shiny new field
+     */
+    public static function factory (array $field) 
+    {
+        foreach (array('tag', 'occurrence', 'subfields') as $index) {
+            if (!array_key_exists($index, $field)) {
+                throw new InvalidArgumentException("Missing '{$index}' index in field array");
+            }
+        }
+        return new Field($field['tag'],
+                         $field['occurrence'],
+                         array_map(array('HAB\Pica\Record\Subfield', 'factory'), $field['subfields']));
+    }
+
+    ///
+
+    /**
+     * The field tag.
+     *
+     * @var string
+     */
+    protected $_tag;
+
+    /**
+     * The field level.
+     *
+     * @var integer
+     */
+    protected $_level;
+
+    /**
+     * The field occurrence.
+     *
+     * @var integer
+     */
+    protected $_occurrence;
+
+    /**
+     * The field shorthand.
+     *
+     * @var string
+     */
+    protected $_shorthand;
+
+    /**
+     * Subfields.
+     *
+     * @var array
+     */
+    protected $_subfields;
+
+    /**
+     * Constructor.
+     *
+     * @throws InvalidArgumentException Invalid field tag or occurrence
+     * @param  string $tag Field tag
+     * @param  integer $occurrence Field occurrence
+     * @param  array $subfields Initial set of subfields
+     * @return void
+     */
+    public function __construct ($tag, $occurrence, array $subfields = array()) 
+    {
+        if (!self::isValidFieldTag($tag)) {
+            throw new InvalidArgumentException("Invalid field tag: $tag");
+        }
+        if (!self::isValidFieldOccurrence($occurrence)) {
+            throw new InvalidArgumentException("Invalid field occurrence: $occurrence");
+        }
+        $this->_tag = $tag;
+        $this->_occurrence = (int)$occurrence;
+        $this->_shorthand = sprintf('%4s/%02d', $tag, $occurrence);
+        $this->_level = (int)$tag[0];
+        $this->setSubfields($subfields);
+    }
+
+    /**
+     * Set the field subfields.
+     *
+     * Replaces the subfield list with subfields in argument.
+     *
+     * @param  array $subfields Subfields
+     * @return void
+     */
+    public function setSubfields (array $subfields) 
+    {
+        $this->_subfields = array();
+        foreach ($subfields as $subfield) {
+            $this->addSubfield($subfield);
+        }
+    }
+
+    /**
+     * Add a subfield to the end of the subfield list.
+     *
+     * @throws InvalidArgumentException Subfield already present in subfield list
+     * @param  \HAB\Pica\Record\Subfield $subfield Subfield to add
+     * @return void
+     */
+    public function addSubfield (\HAB\Pica\Record\Subfield $subfield) 
+    {
+        if (in_array($subfield, $this->getSubfields(), true)) {
+            throw new InvalidArgumentException("Cannot add subfield: Subfield already part of the subfield list");
+        }
+        $this->_subfields []= $subfield;
+    }
+
+    /**
+     * Remove a subfield.
+     *
+     * @throws InvalidArgumentException Subfield is not part of the subfield list
+     * @param  \HAB\Pica\Record\Subfield $subfield Subfield to delete
+     * @return void
+     */
+    public function removeSubfield (\HAB\Pica\Record\Subfield $subfield) 
+    {
+        $index = array_search($subfield, $this->_subfields, true);
+        if ($index === false) {
+            throw new InvalidArgumentException("Cannot remove subfield: Subfield not part of the subfield list");
+        }
+        unset($this->_subfields[$index]);
+    }
+
+    /**
+     * Return the field's subfields.
+     *
+     * Returns all subfields when called with no arguments.
+     *
+     * Otherwise the returned array is constructed as follows:
+     *
+     * Each argument is interpreted as a subfield code. The nth element of the
+     * returned array maps to the nth argument in the function call and contains
+     * NULL if the field does not have a subfield with the selected code, or the
+     * subfield if it exists. In order to retrieve multiple subfields with an
+     * identical code you repeat the subfield code in the argument list.
+     *
+     * @return array Subfields
+     */
+    public function getSubfields () 
+    {
+        if (func_num_args() === 0) {
+            return $this->_subfields;
+        } else {
+            $selected = array();
+            $codes = array();
+            $subfields = $this->getSubfields();
+            array_walk($subfields, function ($value, $index) use (&$codes) { $codes[$index] = $value->getCode(); });
+            foreach (func_get_args() as $arg) {
+                $index = array_search($arg, $codes, true);
+                if ($index === false) {
+                    $selected []= null;
+                } else {
+                    $selected []= $subfields[$index];
+                    unset($codes[$index]);
+                }
+            }
+            return $selected;
+        }
+    }
+
+    /**
+     * Return the nth occurrence of a subfield with specified code.
+     *
+     * @param  string $code Subfield code
+     * @param  integer $n Zero-based subfield index
+     * @return \HAB\Pica\record\Subfield|null The requested subfield or NULL if
+     *         none exists
+     */
+    public function getNthSubfield ($code, $n) 
+    {
+        $count = 0;
+        foreach ($this->getSubfields() as $subfield) {
+            if ($subfield->getCode() == $code) {
+                if ($count == $n) {
+                    return $subfield;
+                }
+                $count++;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Return true if a subfield with given code exists.
+     *
+     * If optional argument $n return true if the field has subfield
+     * at the specified position.
+     *
+     * @param  string $code Subfield code
+     * @param  integer $n Optional zero-based subfield index
+     * @return boolean
+     */
+    public function hasSubfield ($code, $n = 0)
+    {
+        return (boolean)$this->getNthSubfield($code, $n);
+    }
+
+    /**
+     * Return all subfields with the specified code.
+     *
+     * @param  string $code Subfield code
+     * @return array
+     */
+    public function getSubfieldsWithCode ($code)
+    {
+        return array_filter($this->_subfields, function (Subfield $s) use ($code) { return $s->getCode() == $code; });
+    }
+
+    /**
+     * Return the field tag.
+     *
+     * @return string Field tag
+     */
+    public function getTag () 
+    {
+        return $this->_tag;
+    }
+
+    /**
+     * Return the field occurrence.
+     *
+     * @return integer Field occurrence
+     */
+    public function getOccurrence () 
+    {
+        return $this->_occurrence;
+    }
+
+    /**
+     * Return the field level.
+     *
+     * @return integer Field level
+     */
+    public function getLevel () {
+        return $this->_level;
+    }
+
+    /**
+     * Return the field shorthand.
+     *
+     * @return string Field shorthand
+     */
+    public function getShorthand () 
+    {
+        return $this->_shorthand;
+    }
+
+    /**
+     * Return TRUE if the field is empty.
+     *
+     * A field is empty if it contains no subfields.
+     *
+     * @return boolean TRUE if the field is empty
+     */
+    public function isEmpty () 
+    {
+        return empty($this->_subfields);
+    }
+
+    /**
+     * Finalize the clone() operation.
+     *
+     * @return void
+     */
+    public function __clone () 
+    {
+        $this->_subfields = Helper::mapClone($this->_subfields);
+    }
+
+    /**
+     * Return a printable representation of the field.
+     *
+     * The printable representation of a field is its shorthand.
+     *
+     * @return string Printable representation of the field
+     */
+    public function __toString () 
+    {
+        return $this->getShorthand();
+    }
+}
\ No newline at end of file
diff --git a/vendor/hab/picarecord/src/HAB/Pica/Record/Helper.php b/vendor/hab/picarecord/src/HAB/Pica/Record/Helper.php
new file mode 100644
index 0000000000000000000000000000000000000000..4b9dcc3298f4b95ffc2303d4f4380af047989e78
--- /dev/null
+++ b/vendor/hab/picarecord/src/HAB/Pica/Record/Helper.php
@@ -0,0 +1,131 @@
+<?php
+
+/**
+ * Helper functions.
+ *
+ * This file is part of PicaRecord.
+ *
+ * PicaRecord is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaRecord is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaRecord.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   PicaRecord
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2012, 2013 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.html GNU General Public License v3
+ */
+
+namespace HAB\Pica\Record;
+
+abstract class Helper
+{
+
+    /**
+     * Return the complement of a function.
+     *
+     * The complement of a function is a function that takes the same arguments
+     * as the original function but returns a boolean with the opposite truth
+     * value.
+     *
+     * @param  callback $function Original function
+     * @return callback Complement function
+     */
+    public static function complement ($function)
+    {
+        return function () use ($function) { return !call_user_func_array($function, func_get_args()); };
+    }
+
+    /**
+     * Return an array of the results of calling a method for each element of a
+     * sequence.
+     *
+     * @param  array $sequence Sequence of objects
+     * @param  string $method Name of the method
+     * @param  array $arguments Optional array of method arguments
+     * @return array Result of calling method on each element of sequence
+     */
+    public static function mapMethod (array $sequence, $method, array $arguments = array())
+    {
+        if (empty($arguments)) {
+            $f = function ($element) use ($method) {
+                return $element->$method();
+            };
+        } else {
+            $f = function ($element) use ($method, $arguments) {
+                return call_user_func_array(array($element, $method), $arguments);
+            };
+        }
+        return array_map($f, $sequence);
+    }
+
+    /**
+     * Return an array with clones of each element in sequence.
+     *
+     * @param  array $sequence Sequence of objects
+     * @return array Sequence of clones
+     */
+    public static function mapClone (array $sequence)
+    {
+        return array_map(function ($element) { return clone($element); }, $sequence);
+    }
+
+    /**
+     * Return TRUE if at leat one element of sequence matches predicate.
+     *
+     * @todo   Make FALSE and TRUE self-evaluating, maybe
+     *
+     * @param  array $sequence Sequence
+     * @param  callback $predicate Predicate
+     * @return boolean TRUE if at least one element matches predicate
+     */
+    public static function some (array $sequence, $predicate)
+    {
+        foreach ($sequence as $element) {
+            if (call_user_func($predicate, $element)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Return TRUE if every element of sequence fullfills predicate.
+     *
+     * @todo   Make FALSE and TRUE self-evaluating, maybe
+     *
+     * @param  array $sequence Sequence
+     * @param  callback $predicate Predicate
+     * @return boolean TRUE if every element fullfills predicate
+     */
+    public static function every (array $sequence, $predicate)
+    {
+        foreach ($sequence as $element) {
+            if (!call_user_func($predicate, $element)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Flatten sequence.
+     *
+     * @param  array $sequence Sequence
+     * @return array Flattend sequence
+     */
+    public static function flatten (array $sequence)
+    {
+        $flat = array();
+        array_walk_recursive($sequence, function ($element) use (&$flat) { $flat []= $element; });
+        return $flat;
+    }
+}
\ No newline at end of file
diff --git a/vendor/hab/picarecord/src/HAB/Pica/Record/LocalRecord.php b/vendor/hab/picarecord/src/HAB/Pica/Record/LocalRecord.php
new file mode 100644
index 0000000000000000000000000000000000000000..ca94ecd479033d01a42ea19f9fb1c2fa870cfdde
--- /dev/null
+++ b/vendor/hab/picarecord/src/HAB/Pica/Record/LocalRecord.php
@@ -0,0 +1,196 @@
+<?php
+
+/**
+ * Pica+ LocalRecord.
+ *
+ * This file is part of PicaRecord.
+ *
+ * PicaRecord is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaRecord is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaRecord.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   PicaRecord
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2012, 2013 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.html GNU General Public License v3
+ */
+
+namespace HAB\Pica\Record;
+
+use InvalidArgumentException;
+
+class LocalRecord extends NestedRecord
+{
+
+    /**
+     * Append a field to the local record.
+     *
+     * You can only append field with a level of 0 to a local record.
+     *
+     * @see Record::append()
+     *
+     * @throws InvalidArgumentException Field level invalid
+     * @throws InvalidArgumentException Field already in record
+     *
+     * @param  Field $field Field to add
+     * @return void
+     */
+    public function append (Field $field)
+    {
+        if ($field->getLevel() !== 1) {
+            throw new InvalidArgumentException("Invalid field level: {$field->getLevel()}");
+        }
+        parent::append($field);
+    }
+
+    /**
+     * Add a copy record.
+     *
+     * @throws InvalidArgumentException Record already contains the copy record
+     * @throws InvalidArgumentException Record already contains a copy record with the same item number
+     *
+     * @param  CopyRecord $record Copy record to add
+     * @return void
+     */
+    public function addCopyRecord (CopyRecord $record)
+    {
+        if ($this->getCopyRecordByItemNumber($record->getItemNumber())) {
+            throw new InvalidArgumentException("Cannot add copy record: Copy record with item number {$record->getItemNumber()} already present");
+        }
+        $this->addRecord($record);
+        $record->setLocalRecord($this);
+    }
+
+    /**
+     * Remove a copy record.
+     *
+     * @param  CopyRecord $record Record to remove
+     * @return void
+     */
+    public function removeCopyRecord (CopyRecord $record)
+    {
+        $this->removeRecord($record);
+        $record->unsetLocalRecord();
+    }
+
+    /**
+     * Return copy record by item number.
+     *
+     * @param  integer $itemNumber Item number
+     * @return CopyRecord|null The copy record or null if none exists
+     */
+    public function getCopyRecordByItemNumber ($itemNumber)
+    {
+        foreach ($this->_records as $record) {
+            if ($record->getItemNumber() === $itemNumber) {
+                return $record;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Return all copy records.
+     *
+     * @return array Copy records
+     */
+    public function getCopyRecords ()
+    {
+        return $this->_records;
+    }
+
+    /**
+     * Return the ILN (internal library number) of the local record.
+     *
+     * @return integer|null ILN of the local record or NULL if none set
+     */
+    public function getILN ()
+    {
+        $ilnField = $this->getFirstMatchingField('101@/00');
+        if ($ilnField) {
+            $ilnSubfield = $ilnField->getNthSubfield('a', 0);
+            if ($ilnSubfield) {
+                return $ilnSubfield->getValue();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Return true if local record contains the copy record.
+     *
+     * @param  CopyRecord $record Copy record
+     * @return boolean
+     */
+    public function containsCopyRecord (CopyRecord $record)
+    {
+        return $this->containsRecord($record);
+    }
+
+    /**
+     * Set the containing title record.
+     *
+     * @param  TitleRecord $record Title record
+     * @return void
+     */
+    public function setTitleRecord (TitleRecord $record)
+    {
+        $this->unsetTitleRecord();
+        if (!$record->containsLocalRecord($this)) {
+            $record->addLocalRecord($this);
+        }
+        $this->_parent = $record;
+    }
+
+    /**
+     * Unset the containing title record.
+     *
+     * @return void
+     */
+    public function unsetTitleRecord ()
+    {
+        if ($this->_parent) {
+            if ($this->_parent->containsLocalRecord($this)) {
+                $this->_parent->removeLocalRecord($this);
+            }
+            $this->_parent = null;
+        }
+    }
+
+    /**
+     * Return the containing local record.
+     *
+     * @return TitleRecord|null
+     */
+    public function getTitleRecord ()
+    {
+        return $this->_parent;
+    }
+
+    /**
+     * Compare two copy records.
+     *
+     * Copyrecords are compared by their item number.
+     *
+     * @see CopyRecord::getItemNumber()
+     * @see NestedRecord::compareRecords()
+     *
+     * @param  Record $a First copy record
+     * @param  Record $b Second copy record
+     * @return integer Comparism value
+     */
+    protected function compareRecords (Record $a, Record $b)
+    {
+        return $a->getItemNumber() - $b->getItemNumber();
+    }
+
+}
\ No newline at end of file
diff --git a/vendor/hab/picarecord/src/HAB/Pica/Record/NestedRecord.php b/vendor/hab/picarecord/src/HAB/Pica/Record/NestedRecord.php
new file mode 100644
index 0000000000000000000000000000000000000000..d7ea6b2db2e3650de75fad77a8577e183ad0a922
--- /dev/null
+++ b/vendor/hab/picarecord/src/HAB/Pica/Record/NestedRecord.php
@@ -0,0 +1,192 @@
+<?php
+
+/**
+ * Abstract base class of nested records.
+ *
+ * A nested record is a record that contains zero or more other records. It is
+ * the base class of {@link TitleRecord title} and {@link LocalRecord local}
+ * records and implements internal accessors for the contained records and the
+ * propagation of field getters to the contained records.
+ *
+ * This file is part of PicaRecord.
+ *
+ * PicaRecord is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaRecord is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaRecord.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   PicaRecord
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2012, 2013 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.html GNU General Public License v3
+ */
+
+namespace HAB\Pica\Record;
+
+use InvalidArgumentException;
+
+abstract class NestedRecord extends Record 
+{
+
+    /**
+     * Contained records.
+     *
+     * @var array
+     */
+    protected $_records = array();
+
+    /**
+     * Delete fields matching predicate.
+     *
+     * The delete() is propagated down to all contained records.
+     *
+     * @see Record::delete()
+     *
+     * @param  callback $where Predicate
+     * @return void
+     */
+    public function delete ($where) 
+    {
+        parent::delete($where);
+        Helper::mapMethod($this->_records, 'delete', array($where));
+    }
+
+    /**
+     * Sort fields and contained records.
+     *
+     * The sort() is propagated down to all contained records. In addition the
+     * nested records are sorted themselves using the implementing class'
+     * compareNestedRecords() function.
+     *
+     * @see Record::sort()
+     * @see NestedRecord::compareNestedRecords()
+     *
+     * @return void
+     */
+    public function sort () 
+    {
+        parent::sort();
+        Helper::mapMethod($this->_records, 'sort');
+        usort($this->_records, array($this, 'compareRecords'));
+    }
+
+    /**
+     * Return true if the record is empty.
+     *
+     * A nested record is empty iff it contains no fields and no non-empty
+     * contained record.
+     *
+     * @return boolean
+     */
+    public function isEmpty () 
+    {
+        return parent::isEmpty() && Helper::every($this->_records, function (Record $record) { return $record->isEmpty(); });
+    }
+
+    /**
+     * Return true if the record is valid.
+     *
+     * A nested record is valid iff it and all contained records are valid.
+     *
+     * @see Record::isValid()
+     *
+     * @return boolean
+     */
+    public function isValid () 
+    {
+        return parent::isValid() && !Helper::every($this->_records, function (Record $record) { return $record->isValid(); });
+    }
+
+    /**
+     * Return fields of the record.
+     *
+     * @see Record::getFields()
+     *
+     * @param  string $selector Body of regular expression
+     * @return array Fields
+     */
+    public function getFields ($selector = null) 
+    {
+        if ($selector === null) {
+            return array_merge($this->_fields, Helper::flatten(Helper::mapMethod($this->_records, 'getFields')));
+        } else {
+            return $this->select(Field::match($selector));
+        }
+    }
+
+    /**
+     * Compare two contained records and return a comparism value suitable for
+     * usort().
+     *
+     * @see http://www.php.net/manual/en/function.usort.php
+     *
+     * @param  Record $a First record
+     * @param  Record $b Second record
+     * @return integer Comparism value
+     */
+    abstract protected function compareRecords (Record $a, Record $b);
+
+    /**
+     * Add a record as a contained record.
+     *
+     * @throws InvalidArgumentException Record already contains the record
+     *
+     * @param  Record $record Record to add
+     * @return void
+     */
+    protected function addRecord (Record $record) 
+    {
+        if ($this->containsRecord($record)) {
+            throw new InvalidArgumentException("{$this} already contains {$record}");
+        }
+        $this->_records []= $record;
+    }
+
+    /**
+     * Remove a contained record.
+     *
+     * @throws InvalidArgumentException Record does not contain the record
+     *
+     * @param  Record $record Record to remove
+     * @return void
+     */
+    protected function removeRecord (Record $record) 
+    {
+        $index = array_search($record, $this->_records, true);
+        if ($index === false) {
+            throw new InvalidArgumentException("{$this} does not contain {$record}");
+        }
+        unset($this->_records[$index]);
+    }
+
+    /**
+     * Return true if this record contains the requested record.
+     *
+     * @param  \HAB\Pica\Record\Record Record to check
+     * @return boolean
+     */
+    protected function containsRecord (Record $record) 
+    {
+        return in_array($record, $this->_records, true);
+    }
+
+    /**
+     * Finalize the clone() operation.
+     *
+     * Clone all contained records.
+     *
+     * @return void
+     */
+    public function __clone () 
+    {
+        $this->_records = Helper::mapClone($this->_records);
+    }
+}
\ No newline at end of file
diff --git a/vendor/hab/picarecord/src/HAB/Pica/Record/Record.php b/vendor/hab/picarecord/src/HAB/Pica/Record/Record.php
new file mode 100644
index 0000000000000000000000000000000000000000..bd44ed878bc210844226eaa3fd50421e573d81c3
--- /dev/null
+++ b/vendor/hab/picarecord/src/HAB/Pica/Record/Record.php
@@ -0,0 +1,286 @@
+<?php
+
+/**
+ * Abstract base class of all record structures.
+ *
+ * The abstract base class defines and partially implements the interface to
+ * all record structures. This class is the direct parent of records that do
+ * not contain other records, i.e. {@link AuthorityRecord authority} and
+ * {@link CopyRecord copy} records.
+ *
+ * This file is part of PicaRecord.
+ *
+ * PicaRecord is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaRecord is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaRecord.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   PicaRecord
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2012, 2013 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.html GNU General Public License v3
+ */
+
+namespace HAB\Pica\Record;
+
+use InvalidArgumentException;
+
+abstract class Record
+{
+
+    /**
+     * Return a new record based on its array representation.
+     *
+     * Returns either a {@link TitleRecord} or a {@link AuthorityRecord}
+     * depending on the field 002@ which encodes the record type.
+     *
+     * @throws InvalidArgumentException Missing type field
+     * @throws InvalidArgumentException Missing `fields' index
+     *
+     * @param  array $record Array representation of a record
+     * @return TitleRecord|AuthorityRecord New record instance
+     */
+    public static function factory (array $record)
+    {
+        if (!array_key_exists('fields', $record)) {
+            throw new InvalidArgumentException("Missing 'fields' index in record array");
+        }
+        $fields = array_map(array('HAB\Pica\Record\Field', 'factory'), $record['fields']);
+        $type = null;
+        $typePredicate = Field::match('002@/00');
+        foreach ($fields as $field) {
+            if ($typePredicate($field)) {
+                $typeSubfield = $field->getNthSubfield('0', 0);
+                if ($typeSubfield) {
+                    $type = $typeSubfield->getValue();
+                    break;
+                }
+            }
+        }
+        if ($type === null) {
+            throw new InvalidArgumentException("Missing type field (002@/00$0)");
+        }
+        if ($type[0] === 'T') {
+            return new AuthorityRecord($fields);
+        } else {
+            return new TitleRecord($fields);
+        }
+    }
+
+    ///
+
+    /**
+     * The record fields.
+     *
+     * @var array
+     */
+    protected $_fields = array();
+
+    /**
+     * The containing parent record, if any.
+     *
+     * @var \HAB\Pica\Record
+     */
+    protected $_parent;
+
+    /**
+     * Constructor.
+     *
+     * @param  array $fields Initial set of fields
+     * @return void
+     */
+    public function __construct (array $fields = array())
+    {
+        $this->setFields($fields);
+    }
+
+    /**
+     * Return array of fields matching predicate.
+     *
+     * @param  callback $where Predicate
+     * @return array Matching fields
+     */
+    public function select ($where)
+    {
+        return array_filter($this->getFields(), $where);
+    }
+
+    /**
+     * Delete fields matching predicate.
+     *
+     * @param  callback $where Predicate
+     * @return void
+     */
+    public function delete ($where)
+    {
+        $complement = Helper::complement($where);
+        $this->_fields = array_filter($this->_fields, $complement);
+    }
+
+    /**
+     * Append a field to the record.
+     *
+     * @throws InvalidArgumentException Field already in record
+     *
+     * @param  \HAB\Pica\Record\Field $field Field to append
+     * @return void
+     */
+    public function append (Field $field)
+    {
+        if (in_array($field, $this->_fields, true)) {
+            throw new InvalidArgumentException("{$this} already contains {$field}");
+        }
+        $this->_fields []= $field;
+    }
+
+    /**
+     * Sort the fields of the record.
+     *
+     * Fields are sorted by their shorthand.
+     *
+     * @see \HAB\Pica\Record\Field::getShorthand()
+     *
+     * @return void
+     */
+    public function sort ()
+    {
+        usort($this->_fields,
+              function (Field $fieldA, Field $fieldB) {
+              return strcmp($fieldA->getShorthand(), $fieldB->getShorthand());
+          });
+    }
+
+    /**
+     * Set the record fields.
+     *
+     * Removes the current set of fields and replaces it with the fields in
+     * argument.
+     *
+     * @param  array $fields Fields
+     * @return void
+     */
+    public function setFields (array $fields)
+    {
+        $this->_fields = array();
+        foreach ($fields as $field) {
+            $this->append($field);
+        }
+    }
+
+    /**
+     * Return the maximum occurrence value of a field.
+     *
+     * @throws InvalidArgumentException Invalid field tag
+     *
+     * @param  string $tag Field tag
+     * @return int|null Maximum occurrence of field or NULL if field does not
+     *         exist
+     */
+    public function getMaximumOccurrenceOf ($tag)
+    {
+        if (!preg_match(Field::TAG_RE, $tag)) {
+            throw new InvalidArgumentException("Invalid field tag: {$tag}");
+        }
+        return array_reduce($this->getFields($tag),
+                            function ($maxOccurrence, Field $field) {
+                            if ($field->getOccurrence() > $maxOccurrence || $maxOccurrence === null) {
+                                return $field->getOccurrence();
+                            } else {
+                                return $maxOccurrence;
+                            }
+                        }, null);
+    }
+
+    /**
+     * Return TRUE if the record is empty.
+     *
+     * A record is empty if it contains no fields.
+     *
+     * @return boolean TRUE if record is empty
+     */
+    public function isEmpty ()
+    {
+        return empty($this->_fields);
+    }
+
+    /**
+     * Return true if the record is valid.
+     *
+     * The base implementation checks that record is not empty and does not
+     * contain an empty field.
+     *
+     * @return boolean
+     */
+    public function isValid ()
+    {
+        return !$this->isEmpty() && !Helper::some($this->getFields(), function (Field $field) { return $field->isEmpty(); });
+    }
+
+    /**
+     * Return fields of the record.
+     *
+     * Optional argument $selector is the body of a regular expression. If set,
+     * this function returns only fields whose shorthand is matched by the
+     * regular expression.
+     *
+     * @see Field::match()
+     *
+     * @param  string $selector Body of regular expression
+     * @return array Fields
+     */
+    public function getFields ($selector = null)
+    {
+        if ($selector === null) {
+            return $this->_fields;
+        } else {
+            return $this->select(Field::match($selector));
+        }
+    }
+
+    /**
+     * Return the first field that matches a selector.
+     *
+     * @param  string $selector Body of regular expression
+     * @return Field|null The first matching field or NULL if no match
+     */
+    public function getFirstMatchingField ($selector)
+    {
+        $fields = $this->getFields($selector);
+        if (empty($fields)) {
+            return null;
+        } else {
+            return reset($fields);
+        }
+    }
+
+    /**
+     * Finalize the clone() operation.
+     *
+     * @return void
+     */
+    public function __clone ()
+    {
+        $this->_fields = Helper::mapClone($this->_fields);
+    }
+
+    /**
+     * Return a printable representation of the record.
+     *
+     * The printable representation of a record is the object hash prefixed by
+     * the class name.
+     *
+     * @return string Printable representation of the record
+     */
+    public function __toString ()
+    {
+        return get_class($this) . ':' . spl_object_hash($this);
+    }
+}
\ No newline at end of file
diff --git a/vendor/hab/picarecord/src/HAB/Pica/Record/Subfield.php b/vendor/hab/picarecord/src/HAB/Pica/Record/Subfield.php
new file mode 100644
index 0000000000000000000000000000000000000000..fa8087fd8729dc960b66fccf962a4a35bba22153
--- /dev/null
+++ b/vendor/hab/picarecord/src/HAB/Pica/Record/Subfield.php
@@ -0,0 +1,152 @@
+<?php
+
+/**
+ * Pica+ subfield.
+ *
+ * A subfield is a cons of an alphanumeric character and a possibly empty
+ * string value.
+ *
+ * This file is part of PicaRecord.
+ *
+ * PicaRecord is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaRecord is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaRecord.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   PicaRecord
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2012 - 2016 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.html GNU General Public License v3
+ */
+
+namespace HAB\Pica\Record;
+
+use InvalidArgumentException;
+
+class Subfield
+{
+    /**
+     * Regular expression matching a valid subfield code.
+     *
+     * @var string
+     */
+    public static $validSubfieldCodePattern = '/^[a-z0-9#]$/Di';
+
+    /**
+     * Return true if argument is a valid subfield code.
+     *
+     * @param  mixed $arg Variable to check
+     * @return boolean
+     */
+    public static function isValidSubfieldCode ($arg)
+    {
+        return (bool)preg_match(self::$validSubfieldCodePattern, $arg);
+    }
+
+    /**
+     * Return a new subfield based on its array representation.
+     *
+     * The array representation of a subfield is an associative array with the
+     * keys `code' and `value', holding the subfield code and value.
+     *
+     * @throws InvalidArgumentException Missing code or value index
+     *
+     * @param  array $subfield Array representation of a subfield
+     * @return Subfield New subfield
+     */
+    public static function factory (array $subfield)
+    {
+        if (!array_key_exists('code', $subfield)) {
+            throw new InvalidArgumentException("Missing 'code' index in subfield array");
+        }
+        if (!array_key_exists('value', $subfield)) {
+            throw new InvalidArgumentException("Missing 'value' index in subfield array");
+        }
+        return new Subfield($subfield['code'], $subfield['value']);
+    }
+
+    ///
+
+    /**
+     * The subfield code.
+     *
+     * @var string
+     */
+    protected $_code;
+
+    /**
+     * The subfield value.
+     *
+     * @var string Value
+     */
+    protected $_value;
+
+    /**
+     * Constructor.
+     *
+     * @throws InvalidArgumentException Invalid subfield code
+     *
+     * @param  string $code Subfield code
+     * @param  string $value Subfield value
+     * @return void
+     */
+    public function __construct ($code, $value)
+    {
+        if (!self::isValidSubfieldCode($code)) {
+            throw new InvalidArgumentException("Invalid subfield code: {$code}");
+        }
+        $this->_code = $code;
+        $this->setValue($value);
+    }
+
+    /**
+     * Set the subfield value.
+     *
+     * @param  string $value Subfield value
+     * @return void
+     */
+    public function setValue ($value)
+    {
+        $this->_value = $value;
+    }
+
+    /**
+     * Return the subfield value.
+     *
+     * @return string Subfield value
+     */
+    public function getValue ()
+    {
+        return $this->_value;
+    }
+
+    /**
+     * Return the subfield code.
+     *
+     * @return string Subfield code
+     */
+    public function getCode ()
+    {
+        return $this->_code;
+    }
+
+    /**
+     * Return printable representation of the subfield.
+     *
+     * The printable representation of a subfield is its value.
+     *
+     * @return string Subfield value
+     */
+    public function __toString ()
+    {
+        return $this->getValue();
+    }
+}
diff --git a/vendor/hab/picarecord/src/HAB/Pica/Record/TitleRecord.php b/vendor/hab/picarecord/src/HAB/Pica/Record/TitleRecord.php
new file mode 100644
index 0000000000000000000000000000000000000000..71cf43f0a456afebd21e8592f6380b8fff5bf7aa
--- /dev/null
+++ b/vendor/hab/picarecord/src/HAB/Pica/Record/TitleRecord.php
@@ -0,0 +1,220 @@
+<?php
+
+/**
+ * Pica+ title record.
+ *
+ * This file is part of PicaRecord.
+ *
+ * PicaRecord is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaRecord is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaRecord.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   PicaRecord
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2012, 2013 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.html GNU General Public License v3
+ */
+
+namespace HAB\Pica\Record;
+
+use InvalidArgumentException;
+
+class TitleRecord extends NestedRecord
+{
+
+    /**
+     * Append a field to the title record.
+     *
+     * @see Record::append()
+     *
+     * You can only directly add fields with a level of 0.
+     *
+     * @throws InvalidArgumentException Field level invalid
+     * @throws InvalidArgumentException Field already in record
+     *
+     * @param  Field $field Field to append
+     * @return void
+     */
+    public function append (Field $field)
+    {
+        if ($field->getLevel() !== 0) {
+            throw new InvalidArgumentException("Invalid field level: {$field->getLevel()}");
+        }
+        parent::append($field);
+    }
+
+    /**
+     * Set the record's fields.
+     *
+     * @todo   Relocate to \HAB\Pica\Record\Record::factory(), maybe
+     *
+     * @param  array $fields Field
+     * @return void
+     */
+    public function setFields (array $fields)
+    {
+        $this->_fields = array();
+        $this->_records = array();
+        $prevLevel = null;
+        foreach ($fields as $field) {
+            $level = $field->getLevel();
+            if ($level === 0) {
+                $this->append($field);
+            } else {
+                if ($level === 1 && $prevLevel !== 1) {
+                    $localRecord = new LocalRecord(array($field));
+                    $this->addLocalRecord($localRecord);
+                } else {
+                    $records = $this->getLocalRecords();
+                    // Handle malformed Pica record w/ missing local record field
+                    if (empty($records)) {
+                        $localRecord = new LocalRecord();
+                        $this->addLocalRecord($localRecord);
+                    } else {
+                        $localRecord = end($records);
+                    }
+                    if ($level === 1) {
+                        $localRecord->append($field);
+                    } else {
+                        $copyRecord = $localRecord->getCopyRecordByItemNumber($field->getOccurrence());
+                        if ($copyRecord) {
+                            $copyRecord->append($field);
+                        } else {
+                            $localRecord->addCopyRecord(new CopyRecord(array($field)));
+                        }
+                    }
+                }
+            }
+            $prevLevel = $level;
+        }
+    }
+
+    /**
+     * Add a local record.
+     *
+     * @throws InvalidArgumentException Record already contains the local record
+     *
+     * @param  LocalRecord $record Local record
+     * @return void
+     */
+    public function addLocalRecord (LocalRecord $record)
+    {
+        $this->addRecord($record);
+        $record->setTitleRecord($this);
+    }
+
+    /**
+     * Remove a local record.
+     *
+     * @param  LocalRecord $record Local record to remove
+     * @return void
+     */
+    public function removeLocalRecord (LocalRecord $record)
+    {
+        $this->removeRecord($record);
+        $record->unsetTitleRecord();
+    }
+
+    /**
+     * Return array of all local records.
+     *
+     * @return array Local records
+     */
+    public function getLocalRecords ()
+    {
+        return $this->_records;
+    }
+
+    /**
+     * Return a local record identified by its ILN.
+     *
+     * @param  integer $iln Intenal library number
+     * @return LocalRecord|null
+     */
+    public function getLocalRecordByILN ($iln)
+    {
+        foreach ($this->getLocalRecords() as $localRecord) {
+            if ($localRecord->getILN() == $iln) {
+                return $localRecord;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Return the Pica production number (record identifier).
+     *
+     * @return string|null
+     */
+    public function getPPN ()
+    {
+        $ppnField = $this->getFirstMatchingField('003@/00');
+        if ($ppnField) {
+            $ppnSubfield = $ppnField->getNthSubfield('0', 0);
+            if ($ppnSubfield) {
+                return $ppnSubfield->getValue();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Set the Pica production number.
+     *
+     * Create a field 003@/00 if necessary.
+     *
+     * @param  string $ppn Pica production number
+     * @return void
+     */
+    public function setPPN ($ppn)
+    {
+        $ppnField = $this->getFirstMatchingField('003@/00');
+        if ($ppnField) {
+            $ppnSubfield = $ppnField->getNthSubfield('0', 0);
+            if ($ppnSubfield) {
+                $ppnSubfield->setValue($ppn);
+            } else {
+                $ppnField->append(new Subfield('0', $ppn));
+            }
+        } else {
+            $this->append(new Field('003@', 0, array(new Subfield('0', $ppn))));
+        }
+    }
+
+    /**
+     * Return true if title record contains the local record.
+     *
+     * @param  LocalRecord $record Local record
+     * @return boolean
+     */
+    public function containsLocalRecord (LocalRecord $record)
+    {
+        return $this->containsRecord($record);
+    }
+
+    /**
+     * Compare two local records.
+     *
+     * @see NestedRecord::compareRecords()
+     *
+     * Local records are compared by their ILN.
+     *
+     * @param  Record $a First record
+     * @param  Record $b Second record
+     * @return Comparism value
+     */
+    protected function compareRecords (Record $a, Record $b)
+    {
+        return $a->getILN() - $b->getILN();
+    }
+
+}
\ No newline at end of file
diff --git a/vendor/hab/picarecord/tests/unit-tests/bootstrap.php b/vendor/hab/picarecord/tests/unit-tests/bootstrap.php
new file mode 100644
index 0000000000000000000000000000000000000000..4e4549b03f0c7fd1bff1e8471b7b9a855d0b59e6
--- /dev/null
+++ b/vendor/hab/picarecord/tests/unit-tests/bootstrap.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * This file is part of PicaRecord.
+ *
+ * PicaRecord is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaRecord is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaRecord.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2013 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.txt GNU General Public License v3
+ */
+
+$autoload = realpath(__DIR__ . '/../../vendor/autoload.php');
+require_once($autoload);
+
+define('PHPUNIT_FIXTURES', realpath(__DIR__ . '/fixtures'));
+
+$loader = new Composer\Autoload\ClassLoader();
+$loader->add('HAB', realpath(__DIR__ . '/src'));
+$loader->register();
diff --git a/vendor/hab/picarecord/tests/unit-tests/src/HAB/Pica/Record/AuthorityRecordTest.php b/vendor/hab/picarecord/tests/unit-tests/src/HAB/Pica/Record/AuthorityRecordTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..4d1f732885913130069086a4871f2761007f59c3
--- /dev/null
+++ b/vendor/hab/picarecord/tests/unit-tests/src/HAB/Pica/Record/AuthorityRecordTest.php
@@ -0,0 +1,153 @@
+<?php
+
+/**
+ * Unit test for the AuthorityRecord class.
+ *
+ * This file is part of PicaRecord.
+ *
+ * PicaRecord is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaRecord is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaRecord.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   PicaRecord
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2012, 2013 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.html GNU General Public License v3
+ */
+
+namespace HAB\Pica\Record;
+
+use PHPUnit_FrameWork_TestCase;
+
+class AuthorityRecordTest extends PHPUnit_FrameWork_TestCase 
+{
+
+    ///
+
+    public function testIsEmpty () 
+    {
+        $r = new AuthorityRecord();
+        $this->assertTrue($r->isEmpty());
+    }
+
+    public function testAppend () 
+    {
+        $r = new AuthorityRecord();
+        $r->append(new Field('000@', 0, array(new Subfield('0', 'valid'))));
+        $this->assertFalse($r->isEmpty());
+    }
+
+    public function testGetPPN () 
+    {
+        $r = new AuthorityRecord();
+        $this->assertNull($r->getPPN());
+        $r->append(new Field('003@', 0, array(new Subfield('0', 'valid'))));
+        $this->assertEquals('valid', $r->getPPN());
+    }
+
+    public function testDelete () 
+    {
+        $r = new AuthorityRecord();
+        $r->append(new Field('003@', 0, array(new Subfield('0', 'valid'))));
+        $this->assertFalse($r->isEmpty());
+        $r->delete(Field::match('..../..'));
+        $this->assertTrue($r->isEmpty());
+    }
+
+    ///
+
+    public function testSetPPN () 
+    {
+        $r = new AuthorityRecord();
+        $this->assertNull($r->getPPN());
+        $r->setPPN('something');
+        $this->assertEquals('something', $r->getPPN());
+        $r->setPPN('else');
+        $this->assertEquals('else', $r->getPPN());
+        $this->assertEquals(1, count($r->getFields('003@/00')));
+    }
+
+    public function testClone () 
+    {
+        $r = new AuthorityRecord();
+        $f = new Field('003@', 0);
+        $r->append($f);
+        $c = clone($r);
+        $this->assertNotSame($r, $c);
+        $fields = $c->getFields();
+        $this->assertNotSame($f, reset($fields));
+    }
+
+    public function testIsInvalidEmptyField () 
+    {
+        $r = new AuthorityRecord(array(new Field('003@', 0)));
+        $this->assertFalse($r->isValid());
+    }
+
+    public function testIsInvalidMissingPPN () 
+    {
+        $r = new AuthorityRecord(array(new Field('002@', 0, array(new Subfield('0', 'T')))));
+        $this->assertFalse($r->isValid());
+    }
+
+    public function testIsInvalidMissingType () 
+    {
+        $r = new AuthorityRecord(array(new Field('003@', 0, array(new Subfield('0', 'something')))));
+        $this->assertFalse($r->isValid());
+    }
+
+    public function testIsInvalidWrongType () 
+    {
+        $r = new AuthorityRecord(array(new Field('002@', 0, array(new Subfield('0', 'A')))));
+        $this->assertFalse($r->isValid());
+    }
+
+    public function testIsValid () 
+    {
+        $r = new AuthorityRecord(array(new Field('002@', 0, array(new Subfield('0', 'T'))),
+                                       new Field('003@', 0, array(new Subfield('0', 'valid')))));
+        $this->assertTrue($r->isValid());
+    }
+
+    public function testSort () 
+    {
+        $r = new AuthorityRecord(array(new Field('003@', 99, array(new Subfield('0', 'valid'))),
+                                       new Field('003@', 0, array(new Subfield('0', 'valid')))));
+        $r->sort();
+        $fields = $r->getFields('003@');
+        $this->assertEquals('003@/00', reset($fields)->getShorthand());
+        $this->assertEquals('003@/99', end($fields)->getShorthand());
+    }
+
+    ///
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testAppendThrowsExceptionOnDuplicateField () 
+    {
+        $r = new AuthorityRecord();
+        $f = new Field('003@', 0);
+        $r->append($f);
+        $r->append($f);
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testAppendThrowsExceptionOnInvalidLevel () 
+    {
+        $r = new AuthorityRecord();
+        $r->append(new Field('101@', 0));
+    }
+
+}
\ No newline at end of file
diff --git a/vendor/hab/picarecord/tests/unit-tests/src/HAB/Pica/Record/CopyRecordTest.php b/vendor/hab/picarecord/tests/unit-tests/src/HAB/Pica/Record/CopyRecordTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..7f2ad1e5180f59752cc278a0d58e32c4e21b48eb
--- /dev/null
+++ b/vendor/hab/picarecord/tests/unit-tests/src/HAB/Pica/Record/CopyRecordTest.php
@@ -0,0 +1,82 @@
+<?php
+
+/**
+ * Unit test for the CopyRecord class.
+ *
+ * This file is part of PicaRecord.
+ *
+ * PicaRecord is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaRecord is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaRecord.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   PicaRecord
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2012, 2013 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.html GNU General Public License v3
+ */
+
+namespace HAB\Pica\Record;
+
+use PHPUnit_FrameWork_TestCase;
+
+class CopyRecordTest extends PHPUnit_FrameWork_TestCase {
+
+    public function testGetEPN ()
+    {
+        $r = new CopyRecord();
+        $this->assertNull($r->getEPN());
+        $r->append(new Field('203@', 0, array(new Subfield('0', 'something'))));
+        $this->assertEquals('something', $r->getEPN());
+    }
+
+    public function testSetEPN ()
+    {
+        $r = new CopyRecord(array(new Field('203@', 0, array(new Subfield('0', 'something')))));
+        $this->assertEquals('something', $r->getEPN());
+        $r->setEPN('epn');
+        $this->assertEquals('epn', $r->getEPN());
+    }
+
+    public function testLocalRecordReference ()
+    {
+        $l = new LocalRecord();
+        $c = new CopyRecord();
+        $this->assertNull($c->getLocalRecord());
+        $l->addCopyRecord($c);
+        $this->assertSame($l, $c->getLocalRecord());
+        $c->unsetLocalRecord();
+        $this->assertNull($c->getLocalRecord());
+        $this->assertFalse($l->containsCopyRecord($c));
+    }
+
+    ///
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testAppendThrowsExceptionOnInvalidFieldLevel ()
+    {
+        $r = new CopyRecord();
+        $r->append(new Field('003@', 0));
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testAppendThrowsExceptionOnNumberMismatch ()
+    {
+        $r = new CopyRecord();
+        $r->append(new Field('201@', 0));
+        $r->append(new Field('202@', 1));
+    }
+
+}
diff --git a/vendor/hab/picarecord/tests/unit-tests/src/HAB/Pica/Record/FieldTest.php b/vendor/hab/picarecord/tests/unit-tests/src/HAB/Pica/Record/FieldTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..6fc57c773a19777ff84d6c92ef2d8a582ba89fd2
--- /dev/null
+++ b/vendor/hab/picarecord/tests/unit-tests/src/HAB/Pica/Record/FieldTest.php
@@ -0,0 +1,254 @@
+<?php
+
+/**
+ * Unit test for the Field class.
+ *
+ * This file is part of PicaRecord.
+ *
+ * PicaRecord is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaRecord is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaRecord.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   PicaRecord
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2012, 2013 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.html GNU General Public License v3
+ */
+
+namespace HAB\Pica\Record;
+
+use PHPUnit_FrameWork_TestCase;
+
+class FieldTest extends PHPUnit_FrameWork_TestCase
+{
+
+    public function testValidFieldOccurrenceCastNull () {
+        $this->assertTrue(Field::isValidFieldOccurrence(null));
+    }
+
+    public function testValidFieldOccurrenceCastString () {
+        $this->assertTrue(Field::isValidFieldOccurrence('10'));
+    }
+
+    public function testInvalidFieldOccurrenceCastString () {
+        $this->assertFalse(Field::isValidFieldOccurrence("10\n"));
+    }
+
+    public function testInvalidFieldOccurrenceLowerBound () {
+        $this->assertFalse(Field::isValidFieldOccurrence(-1));
+    }
+
+    public function testInvalidFieldOccurrenceUpperBound () {
+        $this->assertFalse(Field::isValidFieldOccurrence(100));
+    }
+
+    public function testInvalidFieldTagTrailingNewline () {
+        $this->assertFalse(Field::isValidFieldTag("003@\n"));
+    }
+
+    public function testMatch () {
+        $this->assertTrue(call_user_func(Field::match('003./..'), new Field('003@', 0)));
+        $this->assertTrue(call_user_func(Field::match('003./..'), new Field('003Z', 0)));
+        $this->assertTrue(call_user_func(Field::match('003./..'), new Field('003Z', 99)));
+    }
+
+    public function testFactory () {
+        $f = Field::factory(array('tag' => '003@', 'occurrence' => 10, 'subfields' => array()));
+        $this->assertInstanceOf('HAB\Pica\Record\Field', $f);
+        $this->assertEquals('003@/10', $f->getShorthand());
+    }
+
+    ///
+
+    public function testIsEmpty ()
+    {
+        $f = new Field('003@', 0);
+        $this->assertTrue($f->isEmpty());
+        $s = new Subfield('a', 'valid');
+        $f->addSubfield($s);
+        $this->assertFalse($f->isEmpty());
+        $f->removeSubfield($s);
+    }
+
+    public function testGetTag ()
+    {
+        $f = new Field('003@', 0);
+        $this->assertEquals('003@', $f->getTag());
+    }
+
+    public function testGetOccurrence ()
+    {
+        $f = new Field('003@', 0);
+        $this->assertEquals(0, $f->getOccurrence());
+    }
+
+    public function testGetLevel ()
+    {
+        $f = new Field('003@', 0);
+        $this->assertEquals(0, $f->getLevel());
+    }
+
+    public function testGetShorthand ()
+    {
+        $f = new Field('003@', 0);
+        $this->assertEquals('003@/00', $f->getShorthand());
+    }
+
+    ///
+
+    public function testSetSubfields ()
+    {
+        $f = new Field('003@', 0);
+        $f->setSubfields(array(new Subfield('a', 'first a'),
+                               new Subfield('d', 'first d'),
+                               new Subfield('a', 'second a')));
+        $this->assertFalse($f->isEmpty());
+        return $f;
+    }
+
+    public function testGetNthSubfield ()
+    {
+        $f = new Field('003@', 0, array(new Subfield('a', 'first a'),
+                                        new Subfield('b', 'first b'),
+                                        new Subfield('a', 'second a')));
+        $s = $f->getNthSubfield('a', 0);
+        $this->assertInstanceOf('HAB\Pica\Record\Subfield', $s);
+        $this->assertEquals('first a', $s->getValue());
+        $s = $f->getNthSubfield('a', 1);
+        $this->assertInstanceOf('HAB\Pica\Record\Subfield', $s);
+        $this->assertEquals('second a', $s->getValue());
+        $s = $f->getNthSubfield('a', 2);
+        $this->assertNull($s);
+    }
+
+    public function testHasSubfield ()
+    {
+        $f = new Field('003@', 0, array(new Subfield('a', 'first a'),
+                                        new Subfield('b', 'first b'),
+                                        new Subfield('a', 'second a')));
+        $this->assertTrue($f->hasSubfield('a'));
+        $this->assertTrue($f->hasSubfield('a', 1));
+        $this->assertFalse($f->hasSubfield('a', 2));
+        $this->assertFalse($f->hasSubfield('c'));
+    }
+
+    /**
+     * @depends testSetSubfields
+     */
+    public function testGetSubfields (Field $f)
+    {
+        $this->assertEquals(3, count($f->getSubfields()));
+        return $f;
+    }
+
+    /**
+     * @depends testGetSubfields
+     */
+    public function testGetSubfieldsCodes (Field $f)
+    {
+        $this->assertEquals(5, count($f->getSubfields('x', 'x', 'x', 'x', 'x')));
+        $s = $f->getSubfields('d');
+        $this->assertEquals('first d', reset($s));
+        $s = $f->getSubfields('a');
+        $this->assertEquals('first a', reset($s));
+        $s = $f->getSubfields('a', 'd', 'a');
+        $this->assertEquals('second a', end($s));;
+        return $f;
+    }
+
+
+    public function testGetSubfieldsWithCode ()
+    {
+        $f = new Field('003@', 0);
+        $f->setSubfields(array(new Subfield('a', 'first a'),
+                               new Subfield('d', 'first d'),
+                               new Subfield('a', 'second a')));
+        $this->assertEquals(2, count($f->getSubfieldsWithCode('a')));
+        $this->assertEquals(1, count($f->getSubfieldsWithCode('d')));
+        $this->assertEquals(0, count($f->getSubfieldsWithCode('x')));
+    }
+
+    ///
+
+    public function testClone ()
+    {
+        $f = new Field('003@', 0);
+        $s = new Subfield('a', 'valid');
+        $f->addSubfield($s);
+        $c = clone($f);
+        $this->assertNotSame($c, $f);
+        $this->assertNotSame($s, $c->getNthSubfield('a', 0));
+    }
+
+    ///
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testFactoryThrowsExceptionOnMissingTagIndex ()
+    {
+        Field::factory(array('occurrence' => 10, 'subfields' => array()));
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testFactoryThrowsExceptionOnMissingOccurrenceIndex ()
+    {
+        Field::factory(array('tag' => '003@', 'subfields' => array()));
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testFactoryThrowsExceptionOnMissingSubfieldIndex ()
+    {
+        Field::factory(array('tag' => '003@', 'occurrence' => 10));
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testContructorThrowsExceptionOnInvalidTag ()
+    {
+        new Field('invalid', 0);
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testConstructorThrowsExceptionOnInvalidOccurrence ()
+    {
+        new Field('003@', 1000);
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testAddSubfieldThrowsExceptionOnDuplicateSubfield ()
+    {
+        $f = new Field('003@', 0);
+        $s = new Subfield('a', 'valid');
+        $f->addSubfield($s);
+        $f->addSubfield($s);
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testRemoveSubfieldThrowsExceptionOnNonExistentField ()
+    {
+        $f = new Field('003@', 0);
+        $s = new Subfield('a', 'valid');
+        $f->removeSubfield($s);
+    }
+}
\ No newline at end of file
diff --git a/vendor/hab/picarecord/tests/unit-tests/src/HAB/Pica/Record/LocalRecordTest.php b/vendor/hab/picarecord/tests/unit-tests/src/HAB/Pica/Record/LocalRecordTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..c26199a3e661b8693fdd0a106f6a374aa4ba9fb2
--- /dev/null
+++ b/vendor/hab/picarecord/tests/unit-tests/src/HAB/Pica/Record/LocalRecordTest.php
@@ -0,0 +1,183 @@
+<?php
+
+/**
+ * Unit test for the LocalRecord class.
+ *
+ * This file is part of PicaRecord.
+ *
+ * PicaRecord is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaRecord is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaRecord.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   PicaRecord
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2012, 2013 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.html GNU General Public License v3
+ */
+
+namespace HAB\Pica\Record;
+
+use PHPUnit_FrameWork_TestCase;
+
+class LocalRecordTest extends PHPUnit_FrameWork_TestCase
+{
+
+    public function testClone ()
+    {
+        $r = new LocalRecord();
+        $c = new CopyRecord(array(new Field('200@', 11)));
+        $r->addCopyRecord($c);
+        $clone = clone($r);
+        $this->assertNotSame($clone, $r);
+        $this->assertNotSame($c, $clone->getCopyRecordByItemNumber(11));
+    }
+
+    public function testRemoveCopyRecord ()
+    {
+        $r = new LocalRecord();
+        $r->addCopyRecord(new CopyRecord(array(new Field('200@', 11))));
+        $this->assertEquals(1, count($r->getCopyRecords()));
+        $r->removeCopyRecord($r->getCopyRecordByItemNumber(11));
+    }
+
+    public function testSort ()
+    {
+        $r = new LocalRecord();
+        $a = new CopyRecord(array(new Field('200@', 11)));
+        $b = new CopyRecord(array(new Field('200@', 99)));
+        $r->addCopyRecord($b);
+        $r->addCopyRecord($a);
+        $c = $r->getCopyRecords();
+        $this->assertSame($b, reset($c));
+        $r->sort();
+        $c = $r->getCopyRecords();
+        $this->assertSame($a, reset($c));
+    }
+
+    public function testGetILN ()
+    {
+        $r = new LocalRecord();
+        $this->assertNull($r->getILN());
+        $r->append(new Field('101@', 0, array(new Subfield('a', '50'))));
+        $this->assertEquals(50, $r->getILN());
+    }
+
+    public function testSelectPropagatesDown ()
+    {
+        $r = new LocalRecord();
+        $c = new CopyRecord(array(new Field('200@', 11)));
+        $r->addCopyRecord($c);
+        $this->assertEquals(1, count($r->select(Field::match('200@/11'))));
+    }
+
+    public function testDeletePropagatesDown ()
+    {
+        $r = new LocalRecord();
+        $c = new CopyRecord(array(new Field('200@', 11)));
+        $r->addCopyRecord($c);
+        $this->assertFalse($c->isEmpty());
+        $r->delete(Field::match('200@/11'));
+        $this->assertTrue($c->isEmpty());
+    }
+
+    public function testIsEmpty ()
+    {
+        $r = new LocalRecord();
+        $this->assertTrue($r->isEmpty());
+        $r->addCopyRecord(new CopyRecord());
+        $this->assertTrue($r->isEmpty());
+        $r->addCopyRecord(new CopyRecord(array(new Field('200@', 11))));
+        $this->assertFalse($r->isEmpty());
+    }
+
+    public function testGetMaximumOccurrenceOf ()
+    {
+        $r = new LocalRecord();
+        $this->assertNull($r->getMaximumOccurrenceOf('144Z'));
+        $r->append(new Field('144Z', 0));
+        $this->assertEquals(0, $r->getMaximumOccurrenceOf('144Z'));
+        $r->append(new Field('144Z', 10));
+        $this->assertEquals(10, $r->getMaximumOccurrenceOf('144Z'));
+    }
+
+    public function testContainsCopyRecord ()
+    {
+        $r = new LocalRecord();
+        $c = new CopyRecord();
+        $this->assertFalse($r->containsCopyRecord($c));
+        $r->addCopyRecord($c);
+        $this->assertTrue($r->containsCopyRecord($c));
+    }
+
+    public function testTitleRecordReference ()
+    {
+        $t = new TitleRecord();
+        $l = new LocalRecord();
+        $this->assertNull($l->getTitleRecord());
+        $t->addLocalRecord($l);
+        $this->assertSame($t, $l->getTitleRecord());
+        $l->unsetTitleRecord();
+        $this->assertNull($l->getTitleRecord());
+        $this->assertFalse($t->containsLocalRecord($l));
+    }
+
+    ///
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testAddCopyRecordThrowsExceptionOnItemNumberCollision ()
+    {
+        $r = new LocalRecord();
+        $r->addCopyRecord(new CopyRecord(array(new Field('200@', 11))));
+        $r->addCopyRecord(new CopyRecord(array(new Field('200@', 11))));
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testAddCopyRecordThrowsExceptionOnDuplicateCopyRecord ()
+    {
+        $r = new LocalRecord();
+        $c = new CopyRecord();
+        $r->addCopyRecord($c);
+        $r->addCopyRecord($c);
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testRemoveCopyRecordThrowsExceptionOnCopyRecordNotContainedInRecord ()
+    {
+        $r = new LocalRecord();
+        $c = new CopyRecord();
+        $r->removeCopyRecord($c);
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testAppendThrowsExceptionOnInvalidLevel ()
+    {
+        $r = new LocalRecord();
+        $r->append(new Field('003@', 0));
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testGetMaximumOccurrenceOfThrowsExceptionOnInvalidFieldTag ()
+    {
+        $r = new LocalRecord();
+        $r->getMaximumOccurrenceOf('@@@@');
+    }
+}
\ No newline at end of file
diff --git a/vendor/hab/picarecord/tests/unit-tests/src/HAB/Pica/Record/RecordTest.php b/vendor/hab/picarecord/tests/unit-tests/src/HAB/Pica/Record/RecordTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..f6f2b338f2bcd09d0da8f000c6673b304161cd08
--- /dev/null
+++ b/vendor/hab/picarecord/tests/unit-tests/src/HAB/Pica/Record/RecordTest.php
@@ -0,0 +1,100 @@
+<?php
+
+/**
+ * Unit test for the AuthorityRecord class.
+ *
+ * This file is part of PicaRecord.
+ *
+ * PicaRecord is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaRecord is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaRecord.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   PicaRecord
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2012, 2013 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.html GNU General Public License v3
+ */
+
+namespace HAB\Pica\Record;
+
+use PHPUnit_FrameWork_TestCase;
+
+class RecordTest extends PHPUnit_FrameWork_TestCase
+{
+
+    public function testFactoryCreatesAuthorityRecord ()
+    {
+        $record = Record::factory(array('fields' => array(
+                                            array('tag' => '002@',
+                                                  'occurrence' => 0,
+                                                  'subfields' => array(
+                                                      array('code' => '0',
+                                                            'value' => 'T'))))));
+        $this->assertInstanceOf('HAB\Pica\Record\AuthorityRecord', $record);
+    }
+
+    public function testGetFirstMatchingField ()
+    {
+        $record = new AuthorityRecord(array(new Field('001@', 0),
+                                            new Field('001@', 1)));
+        $this->assertNull($record->getFirstMatchingField('002@/00'));
+        $this->assertInstanceOf('HAB\Pica\Record\Field', $record->getFirstMatchingField('001@'));
+    }
+
+    public function testSerialization ()
+    {
+        $record = Record::factory(
+            array('fields' =>
+                  array(
+                      array('tag' => '002@',
+                            'occurrence' => 0,
+                            'subfields' => array(array('code' => '0', 'value' => 'T'))),
+                      array('tag' => '003@',
+                            'occurrence' => 0,
+                            'subfields' => array(array('code' => '0', 'value' => '123456789X')))
+                  )
+            )
+        );
+        $data = serialize($record);
+        $record = unserialize($data);
+        $this->assertCount(2, $record->getFields());
+    }
+
+    ///
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testFactoryThrowsExceptionOnMissingFieldsIndex ()
+    {
+        Record::factory(array());
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testFactoryThrowsExceptionOnMissingTypeField ()
+    {
+        $record = Record::factory(array('fields' => array()));
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testFactoryThrowsExceptionOnMissingTypeSubfield ()
+    {
+        $record = Record::factory(array('fields' => array(
+                                            array('tag' => '002@',
+                                                  'occurrence' => 0,
+                                                  'subfields' => array()))));
+    }
+}
\ No newline at end of file
diff --git a/vendor/hab/picarecord/tests/unit-tests/src/HAB/Pica/Record/SubfieldTest.php b/vendor/hab/picarecord/tests/unit-tests/src/HAB/Pica/Record/SubfieldTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..d398b391639a07bd4288f38190025d79569474a1
--- /dev/null
+++ b/vendor/hab/picarecord/tests/unit-tests/src/HAB/Pica/Record/SubfieldTest.php
@@ -0,0 +1,97 @@
+<?php
+
+/**
+ * Unit test for the Subfield class.
+ *
+ * This file is part of PicaRecord.
+ *
+ * PicaRecord is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaRecord is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaRecord.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   PicaRecord
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2012, 2013 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.html GNU General Public License v3
+ */
+
+namespace HAB\Pica\Record;
+
+use PHPUnit_FrameWork_TestCase;
+
+class SubfieldTest extends PHPUnit_FrameWork_TestCase
+{
+
+    public function testValidSubfieldCodeZero ()
+    {
+        $this->assertTrue(Subfield::isValidSubfieldCode('0'));
+    }
+
+    public function testInvalidSubfieldCodeTrailingNewline ()
+    {
+        $this->assertFalse(Subfield::isValidSubfieldCode("a\n"));
+    }
+
+    public function testFactory ()
+    {
+        $s = Subfield::factory(array('code' => 'a', 'value' => 'valid'));
+        $this->assertInstanceOf('HAB\Pica\Record\Subfield', $s);
+        $this->assertEquals('a', $s->getCode());
+        $this->assertEquals('valid', $s->getValue());
+    }
+
+    ///
+
+    public function testGetValue ()
+    {
+        $s = new Subfield('a', 'valid');
+        $this->assertEquals('valid', $s->getValue());
+    }
+
+    public function testGetCode ()
+    {
+        $s = new Subfield('a', 'valid');
+        $this->assertEquals('a', $s->getCode());
+    }
+
+    public function testToString () {
+        $s = new Subfield('a', 'valid');
+        $this->assertEquals('valid', (string)$s);
+    }
+
+    ///
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testConstructorThrowsExceptionOnInvalidCode ()
+    {
+        new Subfield(null, 'valid');
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testFactoryThrowsExceptionOnMissingCodeIndex ()
+    {
+        Subfield::factory(array('value' => 'valid'));
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testFactoryThrowsExceptionOnMissingValueIndex ()
+    {
+        Subfield::factory(array('code' => 'a'));
+    }
+
+}
\ No newline at end of file
diff --git a/vendor/hab/picarecord/tests/unit-tests/src/HAB/Pica/Record/TitleRecordTest.php b/vendor/hab/picarecord/tests/unit-tests/src/HAB/Pica/Record/TitleRecordTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..8a06fcbbcda737f6564dc0c8aa1415560063604b
--- /dev/null
+++ b/vendor/hab/picarecord/tests/unit-tests/src/HAB/Pica/Record/TitleRecordTest.php
@@ -0,0 +1,134 @@
+<?php
+
+/**
+ * Unit test for the TitleRecord class.
+ *
+ * This file is part of PicaRecord.
+ *
+ * PicaRecord is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaRecord is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaRecord.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   PicaRecord
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2012, 2013 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.html GNU General Public License v3
+ */
+
+namespace HAB\Pica\Record;
+
+use PHPUnit_FrameWork_TestCase;
+
+class TitleRecordTest extends PHPUnit_FrameWork_TestCase
+{
+
+    public function testAppend ()
+    {
+        $r = new TitleRecord();
+        $r->append(new Field('003@', 0));
+        $this->assertEquals(1, count($r->getFields()));
+    }
+
+    public function testAddLocalRecord ()
+    {
+        $r = new TitleRecord();
+        $l = new LocalRecord();
+        $this->assertEquals(0, count($r->getLocalRecords()));
+        $r->addLocalRecord($l);
+        $this->assertEquals(1, count($r->getLocalRecords()));
+        return $r;
+    }
+
+    /**
+     * @depends testAddLocalRecord
+     */
+    public function testRemoveLocalRecord (TitleRecord $r)
+    {
+        $l = $r->getLocalRecords();
+        $l = end($l);
+        $r->removeLocalRecord($l);
+        $this->assertEquals(0, count($r->getLocalRecords()));
+    }
+
+    public function testSetFields ()
+    {
+        $r = new TitleRecord();
+        $r->append(new Field('003@', 0));
+        $this->assertEquals(1, count($r->getFields()));
+    }
+
+    public function testSetFieldsCreatesNewLocalRecord ()
+    {
+        $r = new TitleRecord();
+        $fields = array();
+        $fields []= new Field('003@', 0);
+        $r->setFields($fields);
+        $this->assertEquals(0, count($r->getLocalRecords()));
+        $fields []= new Field('101@', 0, array(new Subfield('a', 1)));
+        $r->setFields($fields);
+        $this->assertEquals(1, count($r->getLocalRecords()));
+        $fields [] = new Field('200@', 0);
+        $r->setFields($fields);
+        $this->assertEquals(1, count($r->getLocalRecords()));
+        $fields []= new Field('101@', 0, array(new Subfield('a', 2)));
+        $r->setFields($fields);
+        $this->assertEquals(2, count($r->getLocalRecords()));
+    }
+
+    public function testGetLocalRecordByILN ()
+    {
+        $r = new TitleRecord();
+        $r->addLocalRecord(new LocalRecord(array(new Field('101@', 0, array(new Subfield('a', 11))))));
+        $r->addLocalRecord(new LocalRecord(array(new Field('101@', 0, array(new Subfield('a', 99))))));
+        $l = $r->getLocalRecordByILN(11);
+        $this->assertInstanceOf('HAB\\Pica\\Record\\LocalRecord', $l);
+        $this->assertEquals(11, $l->getILN());
+        $l = $r->getLocalRecordByILN(33);
+        $this->assertNull($l);
+    }
+
+    public function testGetPPN ()
+    {
+        $r = new TitleRecord();
+        $this->assertNull($r->getPPN());
+        $r->append(new Field('003@', 0, array(new Subfield('0', 'something'))));
+        $this->assertEquals('something', $r->getPPN());
+    }
+
+    public function testSetPPN ()
+    {
+        $r = new TitleRecord();
+        $r->setPPN('something');
+        $this->assertEquals(1, count($r->getFields('003@/00')));
+        $r->setPPN('something else');
+        $this->assertEquals('something else', $r->getPPN());
+    }
+
+    public function testContainsLocalRecord ()
+    {
+        $r = new TitleRecord();
+        $l = new LocalRecord();
+        $this->assertFalse($r->containsLocalRecord($l));
+        $r->addLocalRecord($l);
+        $this->assertTrue($r->containsLocalRecord($l));
+    }
+
+    public function testTitleRecordWithNoLocalRecordIndicator ()
+    {
+        $record = new TitleRecord();
+        $fields = array();
+        $fields []= new Field('003@', 0);
+        $fields []= new Field('200@', 0);
+        $record->setFields($fields);
+        $this->assertNotEmpty($record->getLocalRecords());
+    }
+}
diff --git a/vendor/hab/picawriter/.eproject b/vendor/hab/picawriter/.eproject
new file mode 100644
index 0000000000000000000000000000000000000000..63786ac7bf838566a518ee486d7942000fd3fb7c
--- /dev/null
+++ b/vendor/hab/picawriter/.eproject
@@ -0,0 +1 @@
+:project-name "PicaWriter"
diff --git a/vendor/hab/picawriter/.gitignore b/vendor/hab/picawriter/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..1042105475ae0e18e9fe3b7d2363043c67844644
--- /dev/null
+++ b/vendor/hab/picawriter/.gitignore
@@ -0,0 +1,11 @@
+*~
+\#*
+.\#*
+TAGS
+ChangeLog
+vendor
+review
+build
+composer.phar
+composer.lock
+test.php
diff --git a/vendor/hab/picawriter/COPYING b/vendor/hab/picawriter/COPYING
new file mode 100644
index 0000000000000000000000000000000000000000..94a9ed024d3859793618152ea559a168bbcbb5e2
--- /dev/null
+++ b/vendor/hab/picawriter/COPYING
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/vendor/hab/picawriter/README.org b/vendor/hab/picawriter/README.org
new file mode 100644
index 0000000000000000000000000000000000000000..fe71f35a9d231734b88b11b0d3960ab299ccac1b
--- /dev/null
+++ b/vendor/hab/picawriter/README.org
@@ -0,0 +1,67 @@
+#+TITLE: PicaWriter -- Classes for writing Pica+ records
+#+AUTHOR: David Maus
+#+EMAIL: maus@hab.de
+
+* About
+
+PicaWriter provides classes for writing Pica+ records to PicaXML and PicaPlain.
+
+PicaWriter is copyright (c) 2012 by Herzog August Bibliothek Wolfenbüttel and released under the
+terms of the GNU General Public License v3.
+
+* Installation
+
+PicaWriter should be installed using the [[http://pear.php.net][PEAR Installer]]. This installer is the PHP community's
+de-facto standard for installing PHP packages.
+
+#+BEGIN_EXAMPLE
+pear channel-discover hab20.hab.de/service/pear
+pear install --alldeps hab20.hab.de/service/pear/PicaWriter
+#+END_EXAMPLE
+
+* Usage
+
+All writers adhere to the same simple interface: You call the =Writer::write()= function with a
+record instance as argument and the function returns the record encoded in the respective output
+format.
+
+* Development
+
+If you want to patch or enhance this component, you will need to create a suitable development
+environment. The easiest way to do that is to install phix4componentdev:
+
+#+BEGIN_EXAMPLE
+apt-get install php5-xdebug
+apt-get install php5-imagick
+pear channel-discover pear.phix-project.org
+pear -D auto_discover=1 install -Ba phix/phix4componentdev
+#+END_EXAMPLE
+
+You can then clone the Git repository:
+
+#+BEGIN_EXAMPLE
+git clone git://gitorious.org/php-pica/picawriter.git
+#+END_EXAMPLE
+
+Then, install a local copy of the package's dependencies to complete the development environment:
+
+#+BEGIN_EXAMPLE
+phing build-vendor
+#+END_EXAMPLE
+
+To make life easier for you, common tasks (such as running unit tests, generating code review
+analytics, and creating the PEAR package) have been automated using [[http://phing.info][Phing]]. You'll find the
+automated steps inside the build.xml file that ships with the component.
+
+Run the command 'phing' in the component's top-level folder to see the full list of available
+automated tasks.
+
+* Acknowledgements
+
+The [[http://phix-project.org][Phix project]] makes it easy to setup and maintain a package repository for a PEAR-installable
+package and integrates important tools such as [[http://phpunit.de][PHPUnit]], [[http://phing.info][Phing]], [[http://pear.php.net][PEAR]], and [[http://pirum.sensiolabs.org/][Pirum]]. Large parts of this
+package would not have been possible without studying the source of [[http://search.cpan.org/dist/PICA-Record/][Pica::Record]], an open source
+Perl library for handling Pica+ records by Jakob Voß, and the practical knowledge of our library's
+catalogers.
+
+* Footnotes
diff --git a/vendor/hab/picawriter/composer.json b/vendor/hab/picawriter/composer.json
new file mode 100644
index 0000000000000000000000000000000000000000..bc67bd52b767a689bdb938fa4131e66ffaab7bda
--- /dev/null
+++ b/vendor/hab/picawriter/composer.json
@@ -0,0 +1,24 @@
+{
+    "name": "hab/picawriter",
+    "description": "Classes for writing Pica+ records to PicaXML and PicaPlain",
+    "type": "library",
+    "license": "GPL-3.0+",
+    "authors": [
+    {
+        "name": "David Maus",
+        "email": "maus@hab.de",
+        "role": "Developer"
+    }
+    ],
+    "support": {
+        "email": "maus@hab.de"
+    },
+    "require": {
+        "hab/picarecord": "~1.0"
+    },
+    "autoload": {
+        "psr-0": {
+            "HAB\\Pica": "src/"
+        }
+    }
+}
diff --git a/vendor/hab/picawriter/phpunit.xml b/vendor/hab/picawriter/phpunit.xml
new file mode 100644
index 0000000000000000000000000000000000000000..16d058a388443b5bc7d54508aa1c8321d6314561
--- /dev/null
+++ b/vendor/hab/picawriter/phpunit.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<phpunit bootstrap="tests/bootstrap.php" strict="true">
+  <testsuites>
+    <testsuite name="Unit Tests">
+      <directory suffix="Test.php">tests</directory>
+    </testsuite>
+  </testsuites>
+  <filter>
+    <blacklist>
+      <directory suffix=".php">vendor</directory>
+      <directory suffix=".php">tests</directory>
+    </blacklist>
+    <whitelist addUncoveredFilesFromWhitelist="true">
+      <directory suffix=".php">bin</directory>
+      <directory suffix=".php">src</directory>
+    </whitelist>
+  </filter>
+  <logging>
+    <log type="coverage-html" target="review/code-coverage"/>
+  </logging>
+</phpunit>
diff --git a/vendor/hab/picawriter/src/.empty b/vendor/hab/picawriter/src/.empty
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/vendor/hab/picawriter/src/HAB/Pica/Writer/PicaNormWriter.php b/vendor/hab/picawriter/src/HAB/Pica/Writer/PicaNormWriter.php
new file mode 100644
index 0000000000000000000000000000000000000000..3a31e025d3c364a6075df273dbb8fdb71561e247
--- /dev/null
+++ b/vendor/hab/picawriter/src/HAB/Pica/Writer/PicaNormWriter.php
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * This file is part of PicaWriter.
+ *
+ * PicaWriter is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaWriter is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaWriter.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2013 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.txt GNU General Public License v3
+ */
+
+namespace HAB\Pica\Writer;
+
+use HAB\Pica\Record\Subfield;
+use HAB\Pica\Record\Record;
+use HAB\Pica\Record\Field;
+
+class PicaNormWriter extends Writer
+{
+    /**
+     * Separators.
+     *
+     * @var string
+     */
+    const FIELD_SEPARATOR    = "\x1e";
+    const RECORD_SEPARATOR   = "\x1d";
+    const SUBFIELD_SEPARATOR = "\x1f";
+
+    /**
+     * Write the record.
+     *
+     * An implementing class SHOULD either return the written record or a
+     * boolean true if the record was successfully written to an output stream
+     * or buffer.
+     *
+     * @param  Record $record The Pica+ record to write
+     * @return string|boolean
+     */
+    public function write (Record $record)
+    {
+        $buffer = '';
+        foreach ($record->getFields() as $field) {
+            $buffer .= $this->writeField($field);
+        }
+        $buffer .= self::RECORD_SEPARATOR;
+        return $buffer;
+    }
+
+    /**
+     * Write a single Pica+ field.
+     *
+     * @param  Field $field The Pica+ field to write
+     * @return string|boolean
+     */
+    public function writeField (Field $field)
+    {
+        $buffer = $field->getTag();
+        if ($field->getOccurrence()) {
+            $buffer .= sprintf('/%02d', $field->getOccurrence());
+        }
+        $buffer .= ' ';
+        foreach ($field->getSubfields() as $subfield) {
+            $buffer .= self::SUBFIELD_SEPARATOR . $subfield->getCode() . $subfield->getValue();
+        }
+        $buffer .= self::FIELD_SEPARATOR;
+        return $buffer;
+    }
+}
\ No newline at end of file
diff --git a/vendor/hab/picawriter/src/HAB/Pica/Writer/PicaPlainWriter.php b/vendor/hab/picawriter/src/HAB/Pica/Writer/PicaPlainWriter.php
new file mode 100644
index 0000000000000000000000000000000000000000..88917f118d7f45c659173146ed6ea9c0e7137899
--- /dev/null
+++ b/vendor/hab/picawriter/src/HAB/Pica/Writer/PicaPlainWriter.php
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * This file is part of PicaWriter.
+ *
+ * PicaWriter is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaWriter is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaWriter.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   PicaWriter
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2012, 2013 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.html GNU General Public License v3
+ */
+
+namespace HAB\Pica\Writer;
+
+use HAB\Pica\Record\Subfield;
+use HAB\Pica\Record\Record;
+use HAB\Pica\Record\Field;
+
+class PicaPlainWriter extends Writer
+{
+
+    /**
+     * Newline characters to separate fields.
+     *
+     * @var string
+     */
+    const NEWLINE = "\r\n";
+
+    /**
+     * Return the written PicaPlain record.
+     *
+     * @see Writer::write()
+     *
+     * @param  Record $record Record to write
+     * @return string
+     */
+    public function write (Record $record)
+    {
+        return implode($this->getNewline(), array_map(array($this, 'writeField'), $record->getFields()));
+    }
+
+    /**
+     * Return a field encoded in PicaPlain.
+     *
+     * @param  Field The Pica+ field
+     * @return string
+     */
+    public function writeField (Field $field)
+    {
+        $line = $field->getTag();
+        if ($field->getOccurrence() != 0 || !$this->_omitOccurrenceIfZero) {
+            $line .= sprintf('/%02d', $field->getOccurrence());
+        }
+        return $line . ' ' . implode('', array_map(array($this, 'writeSubfield'), $field->getSubfields()));
+    }
+
+    /**
+     * Return a subfield encoded in PicaPlain.
+     *
+     * @param  Subfield $subfield The Pica+ subfield
+     * @return string
+     */
+    protected function writeSubfield (Subfield $subfield) {
+        return '$' . $subfield->getCode() . str_replace('$', '$$', $subfield->getValue());
+    }
+
+    /**
+     * Return the writer's newline characters to separate fields.
+     *
+     * This currently always returns the constant PicaPlainWriter::NEWLINE.
+     *
+     * @return string Newline character(s)
+     */
+    public function getNewline() {
+        return self::NEWLINE;
+    }
+
+}
\ No newline at end of file
diff --git a/vendor/hab/picawriter/src/HAB/Pica/Writer/PicaXmlWriter.php b/vendor/hab/picawriter/src/HAB/Pica/Writer/PicaXmlWriter.php
new file mode 100644
index 0000000000000000000000000000000000000000..ed49d48b6d1d4ed66797a4751f9415e1d4e349e9
--- /dev/null
+++ b/vendor/hab/picawriter/src/HAB/Pica/Writer/PicaXmlWriter.php
@@ -0,0 +1,171 @@
+<?php
+
+/**
+ * This file is part of PicaWriter.
+ *
+ * PicaWriter is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaWriter is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaWriter.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   PicaWriter
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2012, 2013 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.html GNU General Public License v3
+ */
+
+namespace HAB\Pica\Writer;
+
+use HAB\Pica\Record\Record;
+use HAB\Pica\Record\Field;
+use XMLWriter;
+
+class PicaXmlWriter extends Writer
+{
+
+    /**
+     * XML namespace URI of PicaXML.
+     *
+     * @var string
+     */
+    const PICAXML_NAMESPACE_URI = 'info:srw/schema/5/picaXML-v1.0';
+
+    /**
+     * XML namespace prefix for PicaXML tags.
+     *
+     * Defaults to 'pica'.
+     *
+     * @var string
+     */
+    protected $_namespacePrefix = 'pica';
+
+    /**
+     * The XMLWriter instance.
+     *
+     * @var XMLWriter
+     */
+    protected $_xmlWriter;
+
+    /**
+     * Return record encoded in PicaXML.
+     *
+     * @see Writer::write()
+     *
+     * @param  Record $record Record to write
+     * @return string
+     */
+    public function write (Record $record)
+    {
+        $writer = $this->getXmlWriter();
+        $nsPrefix = $this->getNamespacePrefix();
+        $writer->startElementNS($nsPrefix, 'record', self::PICAXML_NAMESPACE_URI);
+        foreach ($record->getFields() as $field) {
+            $this->writeField($field, $writer);
+        }
+        $writer->endElement();
+        return $writer->flush();
+    }
+
+    /**
+     * Return field encoded in PicaXML.
+     *
+     * @see Writer::writeField()
+     *
+     * @param  Record    $record    Record to write
+     * @param  XMLWriter $outbuf    XML Writer instance acting as output buffer
+     * @param  boolean   $declareNS Declare PicaXML namespace if set to TRUE
+     * @return string
+     */
+    public function writeField (Field $field, XMLWriter $outbuf = null, $declareNS = false)
+    {
+        $writer = $outbuf ?: $this->getXmlWriter();
+
+        if ($declareNS) {
+            $writer->startElementNS($this->getNamespacePrefix(), 'datafield', self::PICAXML_NAMESPACE_URI);
+        } else {
+            $writer->startElement($this->getQualifiedName('datafield'));
+        }
+
+        $writer->writeAttribute('tag', $field->getTag());
+        if ($field->getOccurrence() != 0 || !$this->_omitOccurrenceIfZero) {
+            $writer->writeAttribute('occurrence', sprintf('%02d', $field->getOccurrence()));
+        }
+        foreach ($field->getSubfields() as $subfield) {
+            $writer->startElement($this->getQualifiedName('subfield'));
+            $writer->writeAttribute('code', $subfield->getCode());
+            $writer->text($subfield->getValue());
+            $writer->endElement();
+        }
+        $writer->endElement();
+        return !!$outbuf ?: $writer->flush();
+    }
+
+    /**
+     * Return the XMLWriter instance or create a new one.
+     *
+     * @return XMLWriter
+     */
+    public function getXmlWriter ()
+    {
+        if (!$this->_xmlWriter) {
+            $writer = new XMLWriter();
+            $writer->openMemory();
+            $this->setXmlWriter($writer);
+        }
+        return $this->_xmlWriter;
+    }
+
+    /**
+     * Return the namespace prefix for PicaXML tags.
+     *
+     * @return string|null
+     */
+    public function getNamespacePrefix ()
+    {
+        return $this->_namespacePrefix;
+    }
+
+    /**
+     * Set the namespace prefix for PicaXML tags.
+     *
+     * To use the default namespace set the namespace prefix to NULL.
+     *
+     * @param  string $nsPrefix Namespace prefix
+     * @return void
+     */
+    public function setNamespacePrefix ($nsPrefix)
+    {
+        $this->_namespacePrefix = $nsPrefix;
+    }
+
+    /**
+     * Set the XMLWriter instance to be used to write XML record.
+     *
+     * @param  XMLWriter $xmlWriter XMLWriter instance
+     * @return void
+     */
+    protected function setXmlWriter (XMLWriter $xmlWriter)
+    {
+        $this->_xmlWriter = $xmlWriter;
+    }
+
+    /**
+     * Return the qualified name of a XML tag.
+     *
+     * @param  string $lname XML local name of the tag
+     * @return string
+     */
+    protected function getQualifiedName ($lname)
+    {
+        $nsPrefix = $this->getNamespacePrefix();
+        return $nsPrefix ? "{$nsPrefix}:{$lname}" : $lname;
+    }
+}
\ No newline at end of file
diff --git a/vendor/hab/picawriter/src/HAB/Pica/Writer/Writer.php b/vendor/hab/picawriter/src/HAB/Pica/Writer/Writer.php
new file mode 100644
index 0000000000000000000000000000000000000000..752460ae0a258ce0e90ee0f26c4a87ef51607363
--- /dev/null
+++ b/vendor/hab/picawriter/src/HAB/Pica/Writer/Writer.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * This file is part of PicaWriter.
+ *
+ * PicaWriter is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaWriter is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaWriter.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   PicaWriter
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2012, 2013 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.html GNU General Public License v3
+ */
+
+namespace HAB\Pica\Writer;
+
+use HAB\Pica\Record\Record;
+use HAB\Pica\Record\Field;
+
+abstract class Writer {
+
+    /**
+     * True if occurrence of 0 should be ommited in the output.
+     *
+     * @var boolean
+     */
+    protected $_omitOccurrenceIfZero = true;
+
+    /**
+     * Write the record.
+     *
+     * An implementing class SHOULD either return the written record or a
+     * boolean true if the record was successfully written to an output stream
+     * or buffer.
+     *
+     * @param  Record $record The Pica+ record to write
+     * @return string|boolean
+     */
+    abstract public function write (Record $record);
+
+    /**
+     * Write a single Pica+ field.
+     *
+     * @param  Field $field The Pica+ field to write
+     * @return string|boolean
+     */
+    abstract public function writeField (Field $field);
+}
\ No newline at end of file
diff --git a/vendor/hab/picawriter/tests/bootstrap.php b/vendor/hab/picawriter/tests/bootstrap.php
new file mode 100644
index 0000000000000000000000000000000000000000..30e9550b45e7d1931be2caf1d6fa9254ed1705a8
--- /dev/null
+++ b/vendor/hab/picawriter/tests/bootstrap.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * This file is part of PicaWriter.
+ *
+ * PicaWriter is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaWriter is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaWriter.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2013 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.txt GNU General Public License v3
+ */
+
+require_once realpath(__DIR__ . '/../vendor/autoload.php');
+
+define('PHPUNIT_FIXTURES', realpath(__DIR__ . '/fixtures'));
+
+$loader = new Composer\Autoload\ClassLoader();
+$loader->add('HAB', realpath(__DIR__ . '/src'));
+$loader->register();
diff --git a/vendor/hab/picawriter/tests/src/HAB/Pica/Writer/PicaPlainWriterTest.php b/vendor/hab/picawriter/tests/src/HAB/Pica/Writer/PicaPlainWriterTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..50fe0f2cabbcef0e40604557afb13bcabb4db86d
--- /dev/null
+++ b/vendor/hab/picawriter/tests/src/HAB/Pica/Writer/PicaPlainWriterTest.php
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ * Unit test for the PicaPlainWriter class.
+ *
+ * This file is part of PicaWriter.
+ *
+ * PicaWriter is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaWriter is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaWriter.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   PicaWriter
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2012, 2013 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.html GNU General Public License v3
+ */
+
+namespace HAB\Pica\Writer;
+
+use \HAB\Pica\Record\TitleRecord;
+use \HAB\Pica\Record\Field;
+use \HAB\Pica\Record\Subfield;
+
+use PHPUnit_FrameWork_TestCase;
+
+class PicaPlainWriterTest extends PHPUnit_FrameWork_TestCase
+{
+
+    protected $_writer;
+
+    public function setup () 
+    {
+        $this->_writer = new PicaPlainWriter();
+    }
+
+    public function testWrite () 
+    {
+        $r = new TitleRecord(array(new Field('003@', 0, array(new Subfield('0', 'something')))));
+        $plain = $this->_writer->write($r);
+        $this->assertInternalType('string', $plain);
+        $this->assertEquals('003@ $0something', $plain);
+    }
+
+    public function testWriteDollarSign () 
+    {
+        $r = new TitleRecord(array(new Field('003@', 0, array(new Subfield('0', 'some$thing')))));
+        $plain = $this->_writer->write($r);
+        $this->assertInternalType('string', $plain);
+        $this->assertEquals('003@ $0some$$thing', $plain);
+    }
+
+    public function testWriteOccurrence () 
+    {
+        $r = new TitleRecord(array(new Field('003@', 10, array(new Subfield('0', 'some$thing')))));
+        $plain = $this->_writer->write($r);
+        $this->assertInternalType('string', $plain);
+        $this->assertEquals('003@/10 $0some$$thing', $plain);
+    }
+
+    public function testWriteField () 
+    {
+        $f = new Field('003@', 10, array(new Subfield('0', 'something')));
+        $this->assertEquals('003@/10 $0something', $this->_writer->writeField($f));
+    }
+
+}
\ No newline at end of file
diff --git a/vendor/hab/picawriter/tests/src/HAB/Pica/Writer/PicaXmlWriterTest.php b/vendor/hab/picawriter/tests/src/HAB/Pica/Writer/PicaXmlWriterTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..d770796cca6a319cf29ec742102244866ccefc7f
--- /dev/null
+++ b/vendor/hab/picawriter/tests/src/HAB/Pica/Writer/PicaXmlWriterTest.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * Unit test for the PicaXmlWriter class.
+ *
+ * This file is part of PicaWriter.
+ *
+ * PicaWriter is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PicaWriter is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PicaWriter.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   PicaWriter
+ * @author    David Maus <maus@hab.de>
+ * @copyright Copyright (c) 2012, 2013 by Herzog August Bibliothek Wolfenbüttel
+ * @license   http://www.gnu.org/licenses/gpl.html GNU General Public License v3
+ */
+
+namespace HAB\Pica\Writer;
+
+use HAB\Pica\Record\TitleRecord;
+use HAB\Pica\Record\Field;
+use HAB\Pica\Record\Subfield;
+
+use PHPUnit_FrameWork_TestCase;
+
+class PicaXmlWriterTest extends PHPUnit_FrameWork_TestCase
+{
+
+    protected $_writer;
+
+    public function SetUp ()
+    {
+        $this->_writer = new PicaXmlWriter();
+    }
+
+    public function testWrite ()
+    {
+        $r = new TitleRecord(array(new Field('003@', 0, array(new Subfield('0', 'something')))));
+        $xml = $this->_writer->write($r);
+        $this->assertEquals('<pica:record xmlns:pica="info:srw/schema/5/picaXML-v1.0"><pica:datafield tag="003@"><pica:subfield code="0">something</pica:subfield></pica:datafield></pica:record>', $xml);
+    }
+
+    public function testWriteField ()
+    {
+        $f = new Field('003@', 0, array(new Subfield('0', 'something')));
+        $xml = $this->_writer->writeField($f);
+        $this->assertInternalType('string', $xml);
+    }
+}
\ No newline at end of file
diff --git a/vendor/paragonie/random_compat/LICENSE b/vendor/paragonie/random_compat/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..45c7017dfb33dd003ee038cc9232d2430ffbf9a2
--- /dev/null
+++ b/vendor/paragonie/random_compat/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Paragon Initiative Enterprises
+
+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/vendor/paragonie/random_compat/build-phar.sh b/vendor/paragonie/random_compat/build-phar.sh
new file mode 100644
index 0000000000000000000000000000000000000000..b4a5ba31cc7bbd042027b4c111e224c4c99f4fc5
--- /dev/null
+++ b/vendor/paragonie/random_compat/build-phar.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+basedir=$( dirname $( readlink -f ${BASH_SOURCE[0]} ) )
+
+php -dphar.readonly=0 "$basedir/other/build_phar.php" $*
\ No newline at end of file
diff --git a/vendor/paragonie/random_compat/composer.json b/vendor/paragonie/random_compat/composer.json
new file mode 100644
index 0000000000000000000000000000000000000000..1c5978c6fbce9c3466a223748b85bc05a9682221
--- /dev/null
+++ b/vendor/paragonie/random_compat/composer.json
@@ -0,0 +1,37 @@
+{
+  "name":         "paragonie/random_compat",
+  "description":  "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
+  "keywords": [
+    "csprng",
+    "random",
+    "pseudorandom"
+  ],
+  "license":      "MIT",
+  "type":         "library",
+  "authors": [
+    {
+      "name":     "Paragon Initiative Enterprises",
+      "email":    "security@paragonie.com",
+      "homepage": "https://paragonie.com"
+    }
+  ],
+  "support": {
+    "issues":     "https://github.com/paragonie/random_compat/issues",
+    "email":      "info@paragonie.com",
+    "source":     "https://github.com/paragonie/random_compat"
+  },
+  "require": {
+    "php": ">=5.2.0"
+  },
+  "require-dev": {
+    "phpunit/phpunit": "4.*|5.*"
+  },
+  "suggest": {
+    "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
+  },
+  "autoload": {
+    "files": [
+      "lib/random.php"
+    ]
+  }
+}
diff --git a/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey b/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey
new file mode 100644
index 0000000000000000000000000000000000000000..eb50ebfcd6d53169b6ddcf0013977f3ffa1542e8
--- /dev/null
+++ b/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey
@@ -0,0 +1,5 @@
+-----BEGIN PUBLIC KEY-----
+MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEEd+wCqJDrx5B4OldM0dQE0ZMX+lx1ZWm
+pui0SUqD4G29L3NGsz9UhJ/0HjBdbnkhIK5xviT0X5vtjacF6ajgcCArbTB+ds+p
++h7Q084NuSuIpNb6YPfoUFgC/CL9kAoc
+-----END PUBLIC KEY-----
diff --git a/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc b/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc
new file mode 100644
index 0000000000000000000000000000000000000000..6a1d7f300675f3b42775d0b5aae49fc40768bc56
--- /dev/null
+++ b/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc
@@ -0,0 +1,11 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v2.0.22 (MingW32)
+
+iQEcBAABAgAGBQJWtW1hAAoJEGuXocKCZATaJf0H+wbZGgskK1dcRTsuVJl9IWip
+QwGw/qIKI280SD6/ckoUMxKDCJiFuPR14zmqnS36k7N5UNPnpdTJTS8T11jttSpg
+1LCmgpbEIpgaTah+cELDqFCav99fS+bEiAL5lWDAHBTE/XPjGVCqeehyPYref4IW
+NDBIEsvnHPHPLsn6X5jq4+Yj5oUixgxaMPiR+bcO4Sh+RzOVB6i2D0upWfRXBFXA
+NNnsg9/zjvoC7ZW73y9uSH+dPJTt/Vgfeiv52/v41XliyzbUyLalf02GNPY+9goV
+JHG1ulEEBJOCiUD9cE1PUIJwHA/HqyhHIvV350YoEFiHl8iSwm7SiZu5kPjaq74=
+=B6+8
+-----END PGP SIGNATURE-----
diff --git a/vendor/paragonie/random_compat/lib/byte_safe_strings.php b/vendor/paragonie/random_compat/lib/byte_safe_strings.php
new file mode 100644
index 0000000000000000000000000000000000000000..3de86b223c5b130016dee7b84055d8a0f890ca7a
--- /dev/null
+++ b/vendor/paragonie/random_compat/lib/byte_safe_strings.php
@@ -0,0 +1,181 @@
+<?php
+/**
+ * Random_* Compatibility Library
+ * for using the new PHP 7 random_* API in PHP 5 projects
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
+ *
+ * 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.
+ */
+
+if (!is_callable('RandomCompat_strlen')) {
+    if (
+        defined('MB_OVERLOAD_STRING') &&
+        ini_get('mbstring.func_overload') & MB_OVERLOAD_STRING
+    ) {
+        /**
+         * strlen() implementation that isn't brittle to mbstring.func_overload
+         *
+         * This version uses mb_strlen() in '8bit' mode to treat strings as raw
+         * binary rather than UTF-8, ISO-8859-1, etc
+         *
+         * @param string $binary_string
+         *
+         * @throws TypeError
+         *
+         * @return int
+         */
+        function RandomCompat_strlen($binary_string)
+        {
+            if (!is_string($binary_string)) {
+                throw new TypeError(
+                    'RandomCompat_strlen() expects a string'
+                );
+            }
+
+            return (int) mb_strlen($binary_string, '8bit');
+        }
+
+    } else {
+        /**
+         * strlen() implementation that isn't brittle to mbstring.func_overload
+         *
+         * This version just used the default strlen()
+         *
+         * @param string $binary_string
+         *
+         * @throws TypeError
+         *
+         * @return int
+         */
+        function RandomCompat_strlen($binary_string)
+        {
+            if (!is_string($binary_string)) {
+                throw new TypeError(
+                    'RandomCompat_strlen() expects a string'
+                );
+            }
+            return (int) strlen($binary_string);
+        }
+    }
+}
+
+if (!is_callable('RandomCompat_substr')) {
+
+    if (
+        defined('MB_OVERLOAD_STRING')
+        &&
+        ini_get('mbstring.func_overload') & MB_OVERLOAD_STRING
+    ) {
+        /**
+         * substr() implementation that isn't brittle to mbstring.func_overload
+         *
+         * This version uses mb_substr() in '8bit' mode to treat strings as raw
+         * binary rather than UTF-8, ISO-8859-1, etc
+         *
+         * @param string $binary_string
+         * @param int $start
+         * @param int $length (optional)
+         *
+         * @throws TypeError
+         *
+         * @return string
+         */
+        function RandomCompat_substr($binary_string, $start, $length = null)
+        {
+            if (!is_string($binary_string)) {
+                throw new TypeError(
+                    'RandomCompat_substr(): First argument should be a string'
+                );
+            }
+
+            if (!is_int($start)) {
+                throw new TypeError(
+                    'RandomCompat_substr(): Second argument should be an integer'
+                );
+            }
+
+            if ($length === null) {
+                /**
+                 * mb_substr($str, 0, NULL, '8bit') returns an empty string on
+                 * PHP 5.3, so we have to find the length ourselves.
+                 */
+                $length = RandomCompat_strlen($binary_string) - $start;
+            } elseif (!is_int($length)) {
+                throw new TypeError(
+                    'RandomCompat_substr(): Third argument should be an integer, or omitted'
+                );
+            }
+
+            // Consistency with PHP's behavior
+            if ($start === RandomCompat_strlen($binary_string) && $length === 0) {
+                return '';
+            }
+            if ($start > RandomCompat_strlen($binary_string)) {
+                return '';
+            }
+
+            return (string) mb_substr($binary_string, $start, $length, '8bit');
+        }
+
+    } else {
+
+        /**
+         * substr() implementation that isn't brittle to mbstring.func_overload
+         *
+         * This version just uses the default substr()
+         *
+         * @param string $binary_string
+         * @param int $start
+         * @param int $length (optional)
+         *
+         * @throws TypeError
+         *
+         * @return string
+         */
+        function RandomCompat_substr($binary_string, $start, $length = null)
+        {
+            if (!is_string($binary_string)) {
+                throw new TypeError(
+                    'RandomCompat_substr(): First argument should be a string'
+                );
+            }
+
+            if (!is_int($start)) {
+                throw new TypeError(
+                    'RandomCompat_substr(): Second argument should be an integer'
+                );
+            }
+
+            if ($length !== null) {
+                if (!is_int($length)) {
+                    throw new TypeError(
+                        'RandomCompat_substr(): Third argument should be an integer, or omitted'
+                    );
+                }
+
+                return (string) substr($binary_string, $start, $length);
+            }
+
+            return (string) substr($binary_string, $start);
+        }
+    }
+}
diff --git a/vendor/paragonie/random_compat/lib/cast_to_int.php b/vendor/paragonie/random_compat/lib/cast_to_int.php
new file mode 100644
index 0000000000000000000000000000000000000000..9a4fab99197125859b0c08484c1fe0abb284ad9d
--- /dev/null
+++ b/vendor/paragonie/random_compat/lib/cast_to_int.php
@@ -0,0 +1,75 @@
+<?php
+/**
+ * Random_* Compatibility Library
+ * for using the new PHP 7 random_* API in PHP 5 projects
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
+ *
+ * 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.
+ */
+
+if (!is_callable('RandomCompat_intval')) {
+    
+    /**
+     * Cast to an integer if we can, safely.
+     * 
+     * If you pass it a float in the range (~PHP_INT_MAX, PHP_INT_MAX)
+     * (non-inclusive), it will sanely cast it to an int. If you it's equal to
+     * ~PHP_INT_MAX or PHP_INT_MAX, we let it fail as not an integer. Floats 
+     * lose precision, so the <= and => operators might accidentally let a float
+     * through.
+     * 
+     * @param int|float $number    The number we want to convert to an int
+     * @param bool      $fail_open Set to true to not throw an exception
+     * 
+     * @return float|int
+     * @psalm-suppress InvalidReturnType
+     *
+     * @throws TypeError
+     */
+    function RandomCompat_intval($number, $fail_open = false)
+    {
+        if (is_int($number) || is_float($number)) {
+            $number += 0;
+        } elseif (is_numeric($number)) {
+            $number += 0;
+        }
+
+        if (
+            is_float($number)
+            &&
+            $number > ~PHP_INT_MAX
+            &&
+            $number < PHP_INT_MAX
+        ) {
+            $number = (int) $number;
+        }
+
+        if (is_int($number)) {
+            return (int) $number;
+        } elseif (!$fail_open) {
+            throw new TypeError(
+                'Expected an integer.'
+            );
+        }
+        return $number;
+    }
+}
diff --git a/vendor/paragonie/random_compat/lib/error_polyfill.php b/vendor/paragonie/random_compat/lib/error_polyfill.php
new file mode 100644
index 0000000000000000000000000000000000000000..6a91990ce6b7c13e98826257a31eb113ce7c5afc
--- /dev/null
+++ b/vendor/paragonie/random_compat/lib/error_polyfill.php
@@ -0,0 +1,49 @@
+<?php
+/**
+ * Random_* Compatibility Library 
+ * for using the new PHP 7 random_* API in PHP 5 projects
+ * 
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
+ * 
+ * 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.
+ */
+
+if (!class_exists('Error', false)) {
+    // We can't really avoid making this extend Exception in PHP 5.
+    class Error extends Exception
+    {
+        
+    }
+}
+
+if (!class_exists('TypeError', false)) {
+    if (is_subclass_of('Error', 'Exception')) {
+        class TypeError extends Error
+        {
+            
+        }
+    } else {
+        class TypeError extends Exception
+        {
+            
+        }
+    }
+}
diff --git a/vendor/paragonie/random_compat/lib/random.php b/vendor/paragonie/random_compat/lib/random.php
new file mode 100644
index 0000000000000000000000000000000000000000..080b87c199d47a85a41bb4fb1da09689d4116883
--- /dev/null
+++ b/vendor/paragonie/random_compat/lib/random.php
@@ -0,0 +1,225 @@
+<?php
+/**
+ * Random_* Compatibility Library
+ * for using the new PHP 7 random_* API in PHP 5 projects
+ *
+ * @version 2.0.10
+ * @released 2017-03-13
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
+ *
+ * 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.
+ */
+
+if (!defined('PHP_VERSION_ID')) {
+    // This constant was introduced in PHP 5.2.7
+    $RandomCompatversion = array_map('intval', explode('.', PHP_VERSION));
+    define(
+        'PHP_VERSION_ID',
+        $RandomCompatversion[0] * 10000
+        + $RandomCompatversion[1] * 100
+        + $RandomCompatversion[2]
+    );
+    $RandomCompatversion = null;
+}
+
+/**
+ * PHP 7.0.0 and newer have these functions natively.
+ */
+if (PHP_VERSION_ID >= 70000) {
+    return;
+}
+
+if (!defined('RANDOM_COMPAT_READ_BUFFER')) {
+    define('RANDOM_COMPAT_READ_BUFFER', 8);
+}
+
+$RandomCompatDIR = dirname(__FILE__);
+
+require_once $RandomCompatDIR . '/byte_safe_strings.php';
+require_once $RandomCompatDIR . '/cast_to_int.php';
+require_once $RandomCompatDIR . '/error_polyfill.php';
+
+if (!is_callable('random_bytes')) {
+    /**
+     * PHP 5.2.0 - 5.6.x way to implement random_bytes()
+     *
+     * We use conditional statements here to define the function in accordance
+     * to the operating environment. It's a micro-optimization.
+     *
+     * In order of preference:
+     *   1. Use libsodium if available.
+     *   2. fread() /dev/urandom if available (never on Windows)
+     *   3. mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM)
+     *   4. COM('CAPICOM.Utilities.1')->GetRandom()
+     *
+     * See RATIONALE.md for our reasoning behind this particular order
+     */
+    if (extension_loaded('libsodium')) {
+        // See random_bytes_libsodium.php
+        if (PHP_VERSION_ID >= 50300 && is_callable('\\Sodium\\randombytes_buf')) {
+            require_once $RandomCompatDIR . '/random_bytes_libsodium.php';
+        } elseif (method_exists('Sodium', 'randombytes_buf')) {
+            require_once $RandomCompatDIR . '/random_bytes_libsodium_legacy.php';
+        }
+    }
+
+    /**
+     * Reading directly from /dev/urandom:
+     */
+    if (DIRECTORY_SEPARATOR === '/') {
+        // DIRECTORY_SEPARATOR === '/' on Unix-like OSes -- this is a fast
+        // way to exclude Windows.
+        $RandomCompatUrandom = true;
+        $RandomCompat_basedir = ini_get('open_basedir');
+
+        if (!empty($RandomCompat_basedir)) {
+            $RandomCompat_open_basedir = explode(
+                PATH_SEPARATOR,
+                strtolower($RandomCompat_basedir)
+            );
+            $RandomCompatUrandom = (array() !== array_intersect(
+                array('/dev', '/dev/', '/dev/urandom'),
+                $RandomCompat_open_basedir
+            ));
+            $RandomCompat_open_basedir = null;
+        }
+
+        if (
+            !is_callable('random_bytes')
+            &&
+            $RandomCompatUrandom
+            &&
+            @is_readable('/dev/urandom')
+        ) {
+            // Error suppression on is_readable() in case of an open_basedir
+            // or safe_mode failure. All we care about is whether or not we
+            // can read it at this point. If the PHP environment is going to
+            // panic over trying to see if the file can be read in the first
+            // place, that is not helpful to us here.
+
+            // See random_bytes_dev_urandom.php
+            require_once $RandomCompatDIR . '/random_bytes_dev_urandom.php';
+        }
+        // Unset variables after use
+        $RandomCompat_basedir = null;
+    } else {
+        $RandomCompatUrandom = false;
+    }
+
+    /**
+     * mcrypt_create_iv()
+     *
+     * We only want to use mcypt_create_iv() if:
+     *
+     * - random_bytes() hasn't already been defined
+     * - the mcrypt extensions is loaded
+     * - One of these two conditions is true:
+     *   - We're on Windows (DIRECTORY_SEPARATOR !== '/')
+     *   - We're not on Windows and /dev/urandom is readabale
+     *     (i.e. we're not in a chroot jail)
+     * - Special case:
+     *   - If we're not on Windows, but the PHP version is between
+     *     5.6.10 and 5.6.12, we don't want to use mcrypt. It will
+     *     hang indefinitely. This is bad.
+     *   - If we're on Windows, we want to use PHP >= 5.3.7 or else
+     *     we get insufficient entropy errors.
+     */
+    if (
+        !is_callable('random_bytes')
+        &&
+        // Windows on PHP < 5.3.7 is broken, but non-Windows is not known to be.
+        (DIRECTORY_SEPARATOR === '/' || PHP_VERSION_ID >= 50307)
+        &&
+        // Prevent this code from hanging indefinitely on non-Windows;
+        // see https://bugs.php.net/bug.php?id=69833
+        (
+            DIRECTORY_SEPARATOR !== '/' ||
+            (PHP_VERSION_ID <= 50609 || PHP_VERSION_ID >= 50613)
+        )
+        &&
+        extension_loaded('mcrypt')
+    ) {
+        // See random_bytes_mcrypt.php
+        require_once $RandomCompatDIR . '/random_bytes_mcrypt.php';
+    }
+    $RandomCompatUrandom = null;
+
+    /**
+     * This is a Windows-specific fallback, for when the mcrypt extension
+     * isn't loaded.
+     */
+    if (
+        !is_callable('random_bytes')
+        &&
+        extension_loaded('com_dotnet')
+        &&
+        class_exists('COM')
+    ) {
+        $RandomCompat_disabled_classes = preg_split(
+            '#\s*,\s*#',
+            strtolower(ini_get('disable_classes'))
+        );
+
+        if (!in_array('com', $RandomCompat_disabled_classes)) {
+            try {
+                $RandomCompatCOMtest = new COM('CAPICOM.Utilities.1');
+                if (method_exists($RandomCompatCOMtest, 'GetRandom')) {
+                    // See random_bytes_com_dotnet.php
+                    require_once $RandomCompatDIR . '/random_bytes_com_dotnet.php';
+                }
+            } catch (com_exception $e) {
+                // Don't try to use it.
+            }
+        }
+        $RandomCompat_disabled_classes = null;
+        $RandomCompatCOMtest = null;
+    }
+
+    /**
+     * throw new Exception
+     */
+    if (!is_callable('random_bytes')) {
+        /**
+         * We don't have any more options, so let's throw an exception right now
+         * and hope the developer won't let it fail silently.
+         *
+         * @param mixed $length
+         * @psalm-suppress MissingReturnType
+         * @throws Exception
+         * @return string
+         */
+        function random_bytes($length)
+        {
+            unset($length); // Suppress "variable not used" warnings.
+            throw new Exception(
+                'There is no suitable CSPRNG installed on your system'
+            );
+            return '';
+        }
+    }
+}
+
+if (!is_callable('random_int')) {
+    require_once $RandomCompatDIR . '/random_int.php';
+}
+
+$RandomCompatDIR = null;
diff --git a/vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php b/vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php
new file mode 100644
index 0000000000000000000000000000000000000000..fc1926e5cac064e4f8d6190338ea3857cffa0cdf
--- /dev/null
+++ b/vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php
@@ -0,0 +1,88 @@
+<?php
+/**
+ * Random_* Compatibility Library 
+ * for using the new PHP 7 random_* API in PHP 5 projects
+ * 
+ * The MIT License (MIT)
+ * 
+ * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
+ * 
+ * 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.
+ */
+
+if (!is_callable('random_bytes')) {
+    /**
+     * Windows with PHP < 5.3.0 will not have the function
+     * openssl_random_pseudo_bytes() available, so let's use
+     * CAPICOM to work around this deficiency.
+     *
+     * @param int $bytes
+     *
+     * @throws Exception
+     *
+     * @return string
+     */
+    function random_bytes($bytes)
+    {
+        try {
+            $bytes = RandomCompat_intval($bytes);
+        } catch (TypeError $ex) {
+            throw new TypeError(
+                'random_bytes(): $bytes must be an integer'
+            );
+        }
+
+        if ($bytes < 1) {
+            throw new Error(
+                'Length must be greater than 0'
+            );
+        }
+
+        $buf = '';
+        if (!class_exists('COM')) {
+            throw new Error(
+                'COM does not exist'
+            );
+        }
+        $util = new COM('CAPICOM.Utilities.1');
+        $execCount = 0;
+
+        /**
+         * Let's not let it loop forever. If we run N times and fail to
+         * get N bytes of random data, then CAPICOM has failed us.
+         */
+        do {
+            $buf .= base64_decode($util->GetRandom($bytes, 0));
+            if (RandomCompat_strlen($buf) >= $bytes) {
+                /**
+                 * Return our random entropy buffer here:
+                 */
+                return RandomCompat_substr($buf, 0, $bytes);
+            }
+            ++$execCount;
+        } while ($execCount < $bytes);
+
+        /**
+         * If we reach here, PHP has failed us.
+         */
+        throw new Exception(
+            'Could not gather sufficient random data'
+        );
+    }
+}
\ No newline at end of file
diff --git a/vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php b/vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php
new file mode 100644
index 0000000000000000000000000000000000000000..df5b91524e893831af5598318e89cd8ee0c5876f
--- /dev/null
+++ b/vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php
@@ -0,0 +1,167 @@
+<?php
+/**
+ * Random_* Compatibility Library 
+ * for using the new PHP 7 random_* API in PHP 5 projects
+ * 
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
+ * 
+ * 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.
+ */
+
+if (!defined('RANDOM_COMPAT_READ_BUFFER')) {
+    define('RANDOM_COMPAT_READ_BUFFER', 8);
+}
+
+if (!is_callable('random_bytes')) {
+    /**
+     * Unless open_basedir is enabled, use /dev/urandom for
+     * random numbers in accordance with best practices
+     *
+     * Why we use /dev/urandom and not /dev/random
+     * @ref http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers
+     *
+     * @param int $bytes
+     *
+     * @throws Exception
+     *
+     * @return string
+     */
+    function random_bytes($bytes)
+    {
+        static $fp = null;
+        /**
+         * This block should only be run once
+         */
+        if (empty($fp)) {
+            /**
+             * We use /dev/urandom if it is a char device.
+             * We never fall back to /dev/random
+             */
+            $fp = fopen('/dev/urandom', 'rb');
+            if (!empty($fp)) {
+                $st = fstat($fp);
+                if (($st['mode'] & 0170000) !== 020000) {
+                    fclose($fp);
+                    $fp = false;
+                }
+            }
+
+            if (!empty($fp)) {
+                /**
+                 * stream_set_read_buffer() does not exist in HHVM
+                 *
+                 * If we don't set the stream's read buffer to 0, PHP will
+                 * internally buffer 8192 bytes, which can waste entropy
+                 *
+                 * stream_set_read_buffer returns 0 on success
+                 */
+                if (is_callable('stream_set_read_buffer')) {
+                    stream_set_read_buffer($fp, RANDOM_COMPAT_READ_BUFFER);
+                }
+                if (is_callable('stream_set_chunk_size')) {
+                    stream_set_chunk_size($fp, RANDOM_COMPAT_READ_BUFFER);
+                }
+            }
+        }
+
+        try {
+            $bytes = RandomCompat_intval($bytes);
+        } catch (TypeError $ex) {
+            throw new TypeError(
+                'random_bytes(): $bytes must be an integer'
+            );
+        }
+
+        if ($bytes < 1) {
+            throw new Error(
+                'Length must be greater than 0'
+            );
+        }
+
+        /**
+         * This if() block only runs if we managed to open a file handle
+         *
+         * It does not belong in an else {} block, because the above
+         * if (empty($fp)) line is logic that should only be run once per
+         * page load.
+         */
+        if (!empty($fp)) {
+            /**
+             * @var int
+             */
+            $remaining = $bytes;
+
+            /**
+             * @var string|bool
+             */
+            $buf = '';
+
+            /**
+             * We use fread() in a loop to protect against partial reads
+             */
+            do {
+                /**
+                 * @var string|bool
+                 */
+                $read = fread($fp, $remaining);
+                if (!is_string($read)) {
+                    if ($read === false) {
+                        /**
+                         * We cannot safely read from the file. Exit the
+                         * do-while loop and trigger the exception condition
+                         *
+                         * @var string|bool
+                         */
+                        $buf = false;
+                        break;
+                    }
+                }
+                /**
+                 * Decrease the number of bytes returned from remaining
+                 */
+                $remaining -= RandomCompat_strlen($read);
+                /**
+                 * @var string|bool
+                 */
+                $buf = $buf . $read;
+            } while ($remaining > 0);
+
+            /**
+             * Is our result valid?
+             */
+            if (is_string($buf)) {
+                if (RandomCompat_strlen($buf) === $bytes) {
+                    /**
+                     * Return our random entropy buffer here:
+                     */
+                    return $buf;
+                }
+            }
+        }
+
+        /**
+         * If we reach here, PHP has failed us.
+         */
+        throw new Exception(
+            'Error reading from source device'
+        );
+    }
+}
diff --git a/vendor/paragonie/random_compat/lib/random_bytes_libsodium.php b/vendor/paragonie/random_compat/lib/random_bytes_libsodium.php
new file mode 100644
index 0000000000000000000000000000000000000000..4af1a242279f7e2f759f13e4d45336d1e2889b9c
--- /dev/null
+++ b/vendor/paragonie/random_compat/lib/random_bytes_libsodium.php
@@ -0,0 +1,88 @@
+<?php
+/**
+ * Random_* Compatibility Library 
+ * for using the new PHP 7 random_* API in PHP 5 projects
+ * 
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
+ * 
+ * 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.
+ */
+
+if (!is_callable('random_bytes')) {
+    /**
+     * If the libsodium PHP extension is loaded, we'll use it above any other
+     * solution.
+     *
+     * libsodium-php project:
+     * @ref https://github.com/jedisct1/libsodium-php
+     *
+     * @param int $bytes
+     *
+     * @throws Exception
+     *
+     * @return string
+     */
+    function random_bytes($bytes)
+    {
+        try {
+            $bytes = RandomCompat_intval($bytes);
+        } catch (TypeError $ex) {
+            throw new TypeError(
+                'random_bytes(): $bytes must be an integer'
+            );
+        }
+
+        if ($bytes < 1) {
+            throw new Error(
+                'Length must be greater than 0'
+            );
+        }
+
+        /**
+         * \Sodium\randombytes_buf() doesn't allow more than 2147483647 bytes to be
+         * generated in one invocation.
+         */
+        if ($bytes > 2147483647) {
+            $buf = '';
+            for ($i = 0; $i < $bytes; $i += 1073741824) {
+                $n = ($bytes - $i) > 1073741824
+                    ? 1073741824
+                    : $bytes - $i;
+                $buf .= \Sodium\randombytes_buf($n);
+            }
+        } else {
+            $buf = \Sodium\randombytes_buf($bytes);
+        }
+
+        if ($buf !== false) {
+            if (RandomCompat_strlen($buf) === $bytes) {
+                return $buf;
+            }
+        }
+
+        /**
+         * If we reach here, PHP has failed us.
+         */
+        throw new Exception(
+            'Could not gather sufficient random data'
+        );
+    }
+}
diff --git a/vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php b/vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php
new file mode 100644
index 0000000000000000000000000000000000000000..705af5262bde0e092ca26aff660ae38b8a6e3e87
--- /dev/null
+++ b/vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php
@@ -0,0 +1,92 @@
+<?php
+/**
+ * Random_* Compatibility Library 
+ * for using the new PHP 7 random_* API in PHP 5 projects
+ * 
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
+ * 
+ * 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.
+ */
+
+if (!is_callable('random_bytes')) {
+    /**
+     * If the libsodium PHP extension is loaded, we'll use it above any other
+     * solution.
+     *
+     * libsodium-php project:
+     * @ref https://github.com/jedisct1/libsodium-php
+     *
+     * @param int $bytes
+     *
+     * @throws Exception
+     *
+     * @return string
+     */
+    function random_bytes($bytes)
+    {
+        try {
+            $bytes = RandomCompat_intval($bytes);
+        } catch (TypeError $ex) {
+            throw new TypeError(
+                'random_bytes(): $bytes must be an integer'
+            );
+        }
+
+        if ($bytes < 1) {
+            throw new Error(
+                'Length must be greater than 0'
+            );
+        }
+
+        /**
+         * @var string
+         */
+        $buf = '';
+
+        /**
+         * \Sodium\randombytes_buf() doesn't allow more than 2147483647 bytes to be
+         * generated in one invocation.
+         */
+        if ($bytes > 2147483647) {
+            for ($i = 0; $i < $bytes; $i += 1073741824) {
+                $n = ($bytes - $i) > 1073741824
+                    ? 1073741824
+                    : $bytes - $i;
+                $buf .= Sodium::randombytes_buf((int) $n);
+            }
+        } else {
+            $buf .= Sodium::randombytes_buf((int) $bytes);
+        }
+
+        if (is_string($buf)) {
+            if (RandomCompat_strlen($buf) === $bytes) {
+                return $buf;
+            }
+        }
+
+        /**
+         * If we reach here, PHP has failed us.
+         */
+        throw new Exception(
+            'Could not gather sufficient random data'
+        );
+    }
+}
diff --git a/vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php b/vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php
new file mode 100644
index 0000000000000000000000000000000000000000..aac9c013d4d71037eca77fce585efad4e14d84dc
--- /dev/null
+++ b/vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Random_* Compatibility Library 
+ * for using the new PHP 7 random_* API in PHP 5 projects
+ * 
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
+ * 
+ * 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.
+ */
+
+if (!is_callable('random_bytes')) {
+    /**
+     * Powered by ext/mcrypt (and thankfully NOT libmcrypt)
+     *
+     * @ref https://bugs.php.net/bug.php?id=55169
+     * @ref https://github.com/php/php-src/blob/c568ffe5171d942161fc8dda066bce844bdef676/ext/mcrypt/mcrypt.c#L1321-L1386
+     *
+     * @param int $bytes
+     *
+     * @throws Exception
+     *
+     * @return string
+     */
+    function random_bytes($bytes)
+    {
+        try {
+            $bytes = RandomCompat_intval($bytes);
+        } catch (TypeError $ex) {
+            throw new TypeError(
+                'random_bytes(): $bytes must be an integer'
+            );
+        }
+
+        if ($bytes < 1) {
+            throw new Error(
+                'Length must be greater than 0'
+            );
+        }
+
+        $buf = @mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM);
+        if (
+            $buf !== false
+            &&
+            RandomCompat_strlen($buf) === $bytes
+        ) {
+            /**
+             * Return our random entropy buffer here:
+             */
+            return $buf;
+        }
+
+        /**
+         * If we reach here, PHP has failed us.
+         */
+        throw new Exception(
+            'Could not gather sufficient random data'
+        );
+    }
+}
diff --git a/vendor/paragonie/random_compat/lib/random_int.php b/vendor/paragonie/random_compat/lib/random_int.php
new file mode 100644
index 0000000000000000000000000000000000000000..5b2143a16297a5ee79ba48f997ef2f39e5d97696
--- /dev/null
+++ b/vendor/paragonie/random_compat/lib/random_int.php
@@ -0,0 +1,190 @@
+<?php
+
+if (!is_callable('random_int')) {
+    /**
+     * Random_* Compatibility Library
+     * for using the new PHP 7 random_* API in PHP 5 projects
+     *
+     * The MIT License (MIT)
+     *
+     * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
+     *
+     * 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.
+     */
+
+    /**
+     * Fetch a random integer between $min and $max inclusive
+     *
+     * @param int $min
+     * @param int $max
+     *
+     * @throws Exception
+     *
+     * @return int
+     */
+    function random_int($min, $max)
+    {
+        /**
+         * Type and input logic checks
+         *
+         * If you pass it a float in the range (~PHP_INT_MAX, PHP_INT_MAX)
+         * (non-inclusive), it will sanely cast it to an int. If you it's equal to
+         * ~PHP_INT_MAX or PHP_INT_MAX, we let it fail as not an integer. Floats
+         * lose precision, so the <= and => operators might accidentally let a float
+         * through.
+         */
+
+        try {
+            $min = RandomCompat_intval($min);
+        } catch (TypeError $ex) {
+            throw new TypeError(
+                'random_int(): $min must be an integer'
+            );
+        }
+
+        try {
+            $max = RandomCompat_intval($max);
+        } catch (TypeError $ex) {
+            throw new TypeError(
+                'random_int(): $max must be an integer'
+            );
+        }
+
+        /**
+         * Now that we've verified our weak typing system has given us an integer,
+         * let's validate the logic then we can move forward with generating random
+         * integers along a given range.
+         */
+        if ($min > $max) {
+            throw new Error(
+                'Minimum value must be less than or equal to the maximum value'
+            );
+        }
+
+        if ($max === $min) {
+            return (int) $min;
+        }
+
+        /**
+         * Initialize variables to 0
+         *
+         * We want to store:
+         * $bytes => the number of random bytes we need
+         * $mask => an integer bitmask (for use with the &) operator
+         *          so we can minimize the number of discards
+         */
+        $attempts = $bits = $bytes = $mask = $valueShift = 0;
+
+        /**
+         * At this point, $range is a positive number greater than 0. It might
+         * overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to
+         * a float and we will lose some precision.
+         */
+        $range = $max - $min;
+
+        /**
+         * Test for integer overflow:
+         */
+        if (!is_int($range)) {
+
+            /**
+             * Still safely calculate wider ranges.
+             * Provided by @CodesInChaos, @oittaa
+             *
+             * @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435
+             *
+             * We use ~0 as a mask in this case because it generates all 1s
+             *
+             * @ref https://eval.in/400356 (32-bit)
+             * @ref http://3v4l.org/XX9r5  (64-bit)
+             */
+            $bytes = PHP_INT_SIZE;
+            $mask = ~0;
+
+        } else {
+
+            /**
+             * $bits is effectively ceil(log($range, 2)) without dealing with
+             * type juggling
+             */
+            while ($range > 0) {
+                if ($bits % 8 === 0) {
+                    ++$bytes;
+                }
+                ++$bits;
+                $range >>= 1;
+                $mask = $mask << 1 | 1;
+            }
+            $valueShift = $min;
+        }
+
+        $val = 0;
+        /**
+         * Now that we have our parameters set up, let's begin generating
+         * random integers until one falls between $min and $max
+         */
+        do {
+            /**
+             * The rejection probability is at most 0.5, so this corresponds
+             * to a failure probability of 2^-128 for a working RNG
+             */
+            if ($attempts > 128) {
+                throw new Exception(
+                    'random_int: RNG is broken - too many rejections'
+                );
+            }
+
+            /**
+             * Let's grab the necessary number of random bytes
+             */
+            $randomByteString = random_bytes($bytes);
+
+            /**
+             * Let's turn $randomByteString into an integer
+             *
+             * This uses bitwise operators (<< and |) to build an integer
+             * out of the values extracted from ord()
+             *
+             * Example: [9F] | [6D] | [32] | [0C] =>
+             *   159 + 27904 + 3276800 + 201326592 =>
+             *   204631455
+             */
+            $val &= 0;
+            for ($i = 0; $i < $bytes; ++$i) {
+                $val |= ord($randomByteString[$i]) << ($i * 8);
+            }
+
+            /**
+             * Apply mask
+             */
+            $val &= $mask;
+            $val += $valueShift;
+
+            ++$attempts;
+            /**
+             * If $val overflows to a floating point number,
+             * ... or is larger than $max,
+             * ... or smaller than $min,
+             * then try again.
+             */
+        } while (!is_int($val) || $val > $max || $val < $min);
+
+        return (int) $val;
+    }
+}
diff --git a/vendor/paragonie/random_compat/other/build_phar.php b/vendor/paragonie/random_compat/other/build_phar.php
new file mode 100644
index 0000000000000000000000000000000000000000..70ef4b2ed838a6bf32d64e3da4c26f9d367e6ab5
--- /dev/null
+++ b/vendor/paragonie/random_compat/other/build_phar.php
@@ -0,0 +1,57 @@
+<?php
+$dist = dirname(__DIR__).'/dist';
+if (!is_dir($dist)) {
+    mkdir($dist, 0755);
+}
+if (file_exists($dist.'/random_compat.phar')) {
+    unlink($dist.'/random_compat.phar');
+}
+$phar = new Phar(
+    $dist.'/random_compat.phar',
+    FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::KEY_AS_FILENAME,
+    'random_compat.phar'
+);
+rename(
+    dirname(__DIR__).'/lib/random.php', 
+    dirname(__DIR__).'/lib/index.php'
+);
+$phar->buildFromDirectory(dirname(__DIR__).'/lib');
+rename(
+    dirname(__DIR__).'/lib/index.php', 
+    dirname(__DIR__).'/lib/random.php'
+);
+
+/**
+ * If we pass an (optional) path to a private key as a second argument, we will
+ * sign the Phar with OpenSSL.
+ * 
+ * If you leave this out, it will produce an unsigned .phar!
+ */
+if ($argc > 1) {
+    if (!@is_readable($argv[1])) {
+        echo 'Could not read the private key file:', $argv[1], "\n";
+        exit(255);
+    }
+    $pkeyFile = file_get_contents($argv[1]);
+    
+    $private = openssl_get_privatekey($pkeyFile);
+    if ($private !== false) {
+        $pkey = '';
+        openssl_pkey_export($private, $pkey);
+        $phar->setSignatureAlgorithm(Phar::OPENSSL, $pkey);
+        
+        /**
+         * Save the corresponding public key to the file
+         */
+        if (!@is_readable($dist.'/random_compat.phar.pubkey')) {
+            $details = openssl_pkey_get_details($private);
+            file_put_contents(
+                $dist.'/random_compat.phar.pubkey',
+                $details['key']
+            );
+        }
+    } else {
+        echo 'An error occurred reading the private key from OpenSSL.', "\n";
+        exit(255);
+    }
+}
diff --git a/vendor/paragonie/random_compat/psalm-autoload.php b/vendor/paragonie/random_compat/psalm-autoload.php
new file mode 100644
index 0000000000000000000000000000000000000000..d71d1b818c311072207e3ee4abff9f82a10d2fa9
--- /dev/null
+++ b/vendor/paragonie/random_compat/psalm-autoload.php
@@ -0,0 +1,9 @@
+<?php
+
+require_once 'lib/byte_safe_strings.php';
+require_once 'lib/cast_to_int.php';
+require_once 'lib/error_polyfill.php';
+require_once 'other/ide_stubs/libsodium.php';
+require_once 'lib/random.php';
+
+$int = random_int(0, 65536);
diff --git a/vendor/paragonie/random_compat/psalm.xml b/vendor/paragonie/random_compat/psalm.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ee072a972adf83d77da058b68d5bfea1a3bcd860
--- /dev/null
+++ b/vendor/paragonie/random_compat/psalm.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<psalm
+    autoloader="psalm-autoload.php"
+    stopOnFirstError="false"
+    useDocblockTypes="true"
+>
+    <projectFiles>
+        <directory name="lib" />
+    </projectFiles>
+    <issueHandlers>
+        <RedundantConditionGivenDocblockType errorLevel="info" />
+        <UnresolvableInclude errorLevel="info" />
+        <DuplicateClass errorLevel="info" />
+        <InvalidOperand errorLevel="info" />
+        <UndefinedConstant errorLevel="info" />
+        <MissingReturnType errorLevel="info" />
+    </issueHandlers>
+</psalm>
diff --git a/vendor/symfony/http-foundation/.gitignore b/vendor/symfony/http-foundation/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..c49a5d8df5c6548379f00c77fe572a7217bce218
--- /dev/null
+++ b/vendor/symfony/http-foundation/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/vendor/symfony/http-foundation/AcceptHeader.php b/vendor/symfony/http-foundation/AcceptHeader.php
new file mode 100644
index 0000000000000000000000000000000000000000..d1740266b7a806d56372c3af4395d551f65d0a0f
--- /dev/null
+++ b/vendor/symfony/http-foundation/AcceptHeader.php
@@ -0,0 +1,168 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Represents an Accept-* header.
+ *
+ * An accept header is compound with a list of items,
+ * sorted by descending quality.
+ *
+ * @author Jean-François Simon <contact@jfsimon.fr>
+ */
+class AcceptHeader
+{
+    /**
+     * @var AcceptHeaderItem[]
+     */
+    private $items = array();
+
+    /**
+     * @var bool
+     */
+    private $sorted = true;
+
+    /**
+     * @param AcceptHeaderItem[] $items
+     */
+    public function __construct(array $items)
+    {
+        foreach ($items as $item) {
+            $this->add($item);
+        }
+    }
+
+    /**
+     * Builds an AcceptHeader instance from a string.
+     *
+     * @param string $headerValue
+     *
+     * @return self
+     */
+    public static function fromString($headerValue)
+    {
+        $index = 0;
+
+        return new self(array_map(function ($itemValue) use (&$index) {
+            $item = AcceptHeaderItem::fromString($itemValue);
+            $item->setIndex($index++);
+
+            return $item;
+        }, preg_split('/\s*(?:,*("[^"]+"),*|,*(\'[^\']+\'),*|,+)\s*/', $headerValue, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE)));
+    }
+
+    /**
+     * Returns header value's string representation.
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+        return implode(',', $this->items);
+    }
+
+    /**
+     * Tests if header has given value.
+     *
+     * @param string $value
+     *
+     * @return bool
+     */
+    public function has($value)
+    {
+        return isset($this->items[$value]);
+    }
+
+    /**
+     * Returns given value's item, if exists.
+     *
+     * @param string $value
+     *
+     * @return AcceptHeaderItem|null
+     */
+    public function get($value)
+    {
+        return isset($this->items[$value]) ? $this->items[$value] : null;
+    }
+
+    /**
+     * Adds an item.
+     *
+     * @return $this
+     */
+    public function add(AcceptHeaderItem $item)
+    {
+        $this->items[$item->getValue()] = $item;
+        $this->sorted = false;
+
+        return $this;
+    }
+
+    /**
+     * Returns all items.
+     *
+     * @return AcceptHeaderItem[]
+     */
+    public function all()
+    {
+        $this->sort();
+
+        return $this->items;
+    }
+
+    /**
+     * Filters items on their value using given regex.
+     *
+     * @param string $pattern
+     *
+     * @return self
+     */
+    public function filter($pattern)
+    {
+        return new self(array_filter($this->items, function (AcceptHeaderItem $item) use ($pattern) {
+            return preg_match($pattern, $item->getValue());
+        }));
+    }
+
+    /**
+     * Returns first item.
+     *
+     * @return AcceptHeaderItem|null
+     */
+    public function first()
+    {
+        $this->sort();
+
+        return !empty($this->items) ? reset($this->items) : null;
+    }
+
+    /**
+     * Sorts items by descending quality.
+     */
+    private function sort()
+    {
+        if (!$this->sorted) {
+            uasort($this->items, function (AcceptHeaderItem $a, AcceptHeaderItem $b) {
+                $qA = $a->getQuality();
+                $qB = $b->getQuality();
+
+                if ($qA === $qB) {
+                    return $a->getIndex() > $b->getIndex() ? 1 : -1;
+                }
+
+                return $qA > $qB ? -1 : 1;
+            });
+
+            $this->sorted = true;
+        }
+    }
+}
diff --git a/vendor/symfony/http-foundation/AcceptHeaderItem.php b/vendor/symfony/http-foundation/AcceptHeaderItem.php
new file mode 100644
index 0000000000000000000000000000000000000000..c69dbbba3566366fc1f9d4e2f12271851793dcd2
--- /dev/null
+++ b/vendor/symfony/http-foundation/AcceptHeaderItem.php
@@ -0,0 +1,209 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Represents an Accept-* header item.
+ *
+ * @author Jean-François Simon <contact@jfsimon.fr>
+ */
+class AcceptHeaderItem
+{
+    private $value;
+    private $quality = 1.0;
+    private $index = 0;
+    private $attributes = array();
+
+    /**
+     * @param string $value
+     * @param array  $attributes
+     */
+    public function __construct($value, array $attributes = array())
+    {
+        $this->value = $value;
+        foreach ($attributes as $name => $value) {
+            $this->setAttribute($name, $value);
+        }
+    }
+
+    /**
+     * Builds an AcceptHeaderInstance instance from a string.
+     *
+     * @param string $itemValue
+     *
+     * @return self
+     */
+    public static function fromString($itemValue)
+    {
+        $bits = preg_split('/\s*(?:;*("[^"]+");*|;*(\'[^\']+\');*|;+)\s*/', $itemValue, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
+        $value = array_shift($bits);
+        $attributes = array();
+
+        $lastNullAttribute = null;
+        foreach ($bits as $bit) {
+            if (($start = substr($bit, 0, 1)) === ($end = substr($bit, -1)) && ('"' === $start || '\'' === $start)) {
+                $attributes[$lastNullAttribute] = substr($bit, 1, -1);
+            } elseif ('=' === $end) {
+                $lastNullAttribute = $bit = substr($bit, 0, -1);
+                $attributes[$bit] = null;
+            } else {
+                $parts = explode('=', $bit);
+                $attributes[$parts[0]] = isset($parts[1]) && strlen($parts[1]) > 0 ? $parts[1] : '';
+            }
+        }
+
+        return new self(($start = substr($value, 0, 1)) === ($end = substr($value, -1)) && ('"' === $start || '\'' === $start) ? substr($value, 1, -1) : $value, $attributes);
+    }
+
+    /**
+     * Returns header  value's string representation.
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+        $string = $this->value.($this->quality < 1 ? ';q='.$this->quality : '');
+        if (count($this->attributes) > 0) {
+            $string .= ';'.implode(';', array_map(function ($name, $value) {
+                return sprintf(preg_match('/[,;=]/', $value) ? '%s="%s"' : '%s=%s', $name, $value);
+            }, array_keys($this->attributes), $this->attributes));
+        }
+
+        return $string;
+    }
+
+    /**
+     * Set the item value.
+     *
+     * @param string $value
+     *
+     * @return $this
+     */
+    public function setValue($value)
+    {
+        $this->value = $value;
+
+        return $this;
+    }
+
+    /**
+     * Returns the item value.
+     *
+     * @return string
+     */
+    public function getValue()
+    {
+        return $this->value;
+    }
+
+    /**
+     * Set the item quality.
+     *
+     * @param float $quality
+     *
+     * @return $this
+     */
+    public function setQuality($quality)
+    {
+        $this->quality = $quality;
+
+        return $this;
+    }
+
+    /**
+     * Returns the item quality.
+     *
+     * @return float
+     */
+    public function getQuality()
+    {
+        return $this->quality;
+    }
+
+    /**
+     * Set the item index.
+     *
+     * @param int $index
+     *
+     * @return $this
+     */
+    public function setIndex($index)
+    {
+        $this->index = $index;
+
+        return $this;
+    }
+
+    /**
+     * Returns the item index.
+     *
+     * @return int
+     */
+    public function getIndex()
+    {
+        return $this->index;
+    }
+
+    /**
+     * Tests if an attribute exists.
+     *
+     * @param string $name
+     *
+     * @return bool
+     */
+    public function hasAttribute($name)
+    {
+        return isset($this->attributes[$name]);
+    }
+
+    /**
+     * Returns an attribute by its name.
+     *
+     * @param string $name
+     * @param mixed  $default
+     *
+     * @return mixed
+     */
+    public function getAttribute($name, $default = null)
+    {
+        return isset($this->attributes[$name]) ? $this->attributes[$name] : $default;
+    }
+
+    /**
+     * Returns all attributes.
+     *
+     * @return array
+     */
+    public function getAttributes()
+    {
+        return $this->attributes;
+    }
+
+    /**
+     * Set an attribute.
+     *
+     * @param string $name
+     * @param string $value
+     *
+     * @return $this
+     */
+    public function setAttribute($name, $value)
+    {
+        if ('q' === $name) {
+            $this->quality = (float) $value;
+        } else {
+            $this->attributes[$name] = (string) $value;
+        }
+
+        return $this;
+    }
+}
diff --git a/vendor/symfony/http-foundation/ApacheRequest.php b/vendor/symfony/http-foundation/ApacheRequest.php
new file mode 100644
index 0000000000000000000000000000000000000000..84803ebae28fdca5f79f93934317d50ae9109c3f
--- /dev/null
+++ b/vendor/symfony/http-foundation/ApacheRequest.php
@@ -0,0 +1,43 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Request represents an HTTP request from an Apache server.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class ApacheRequest extends Request
+{
+    /**
+     * {@inheritdoc}
+     */
+    protected function prepareRequestUri()
+    {
+        return $this->server->get('REQUEST_URI');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function prepareBaseUrl()
+    {
+        $baseUrl = $this->server->get('SCRIPT_NAME');
+
+        if (false === strpos($this->server->get('REQUEST_URI'), $baseUrl)) {
+            // assume mod_rewrite
+            return rtrim(dirname($baseUrl), '/\\');
+        }
+
+        return $baseUrl;
+    }
+}
diff --git a/vendor/symfony/http-foundation/BinaryFileResponse.php b/vendor/symfony/http-foundation/BinaryFileResponse.php
new file mode 100644
index 0000000000000000000000000000000000000000..1010223042ef574dbb7b01ba009481de79b509f7
--- /dev/null
+++ b/vendor/symfony/http-foundation/BinaryFileResponse.php
@@ -0,0 +1,359 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+use Symfony\Component\HttpFoundation\File\File;
+use Symfony\Component\HttpFoundation\File\Exception\FileException;
+
+/**
+ * BinaryFileResponse represents an HTTP response delivering a file.
+ *
+ * @author Niklas Fiekas <niklas.fiekas@tu-clausthal.de>
+ * @author stealth35 <stealth35-php@live.fr>
+ * @author Igor Wiedler <igor@wiedler.ch>
+ * @author Jordan Alliot <jordan.alliot@gmail.com>
+ * @author Sergey Linnik <linniksa@gmail.com>
+ */
+class BinaryFileResponse extends Response
+{
+    protected static $trustXSendfileTypeHeader = false;
+
+    /**
+     * @var File
+     */
+    protected $file;
+    protected $offset;
+    protected $maxlen;
+    protected $deleteFileAfterSend = false;
+
+    /**
+     * @param \SplFileInfo|string $file               The file to stream
+     * @param int                 $status             The response status code
+     * @param array               $headers            An array of response headers
+     * @param bool                $public             Files are public by default
+     * @param null|string         $contentDisposition The type of Content-Disposition to set automatically with the filename
+     * @param bool                $autoEtag           Whether the ETag header should be automatically set
+     * @param bool                $autoLastModified   Whether the Last-Modified header should be automatically set
+     */
+    public function __construct($file, $status = 200, $headers = array(), $public = true, $contentDisposition = null, $autoEtag = false, $autoLastModified = true)
+    {
+        parent::__construct(null, $status, $headers);
+
+        $this->setFile($file, $contentDisposition, $autoEtag, $autoLastModified);
+
+        if ($public) {
+            $this->setPublic();
+        }
+    }
+
+    /**
+     * @param \SplFileInfo|string $file               The file to stream
+     * @param int                 $status             The response status code
+     * @param array               $headers            An array of response headers
+     * @param bool                $public             Files are public by default
+     * @param null|string         $contentDisposition The type of Content-Disposition to set automatically with the filename
+     * @param bool                $autoEtag           Whether the ETag header should be automatically set
+     * @param bool                $autoLastModified   Whether the Last-Modified header should be automatically set
+     *
+     * @return static
+     */
+    public static function create($file = null, $status = 200, $headers = array(), $public = true, $contentDisposition = null, $autoEtag = false, $autoLastModified = true)
+    {
+        return new static($file, $status, $headers, $public, $contentDisposition, $autoEtag, $autoLastModified);
+    }
+
+    /**
+     * Sets the file to stream.
+     *
+     * @param \SplFileInfo|string $file               The file to stream
+     * @param string              $contentDisposition
+     * @param bool                $autoEtag
+     * @param bool                $autoLastModified
+     *
+     * @return $this
+     *
+     * @throws FileException
+     */
+    public function setFile($file, $contentDisposition = null, $autoEtag = false, $autoLastModified = true)
+    {
+        if (!$file instanceof File) {
+            if ($file instanceof \SplFileInfo) {
+                $file = new File($file->getPathname());
+            } else {
+                $file = new File((string) $file);
+            }
+        }
+
+        if (!$file->isReadable()) {
+            throw new FileException('File must be readable.');
+        }
+
+        $this->file = $file;
+
+        if ($autoEtag) {
+            $this->setAutoEtag();
+        }
+
+        if ($autoLastModified) {
+            $this->setAutoLastModified();
+        }
+
+        if ($contentDisposition) {
+            $this->setContentDisposition($contentDisposition);
+        }
+
+        return $this;
+    }
+
+    /**
+     * Gets the file.
+     *
+     * @return File The file to stream
+     */
+    public function getFile()
+    {
+        return $this->file;
+    }
+
+    /**
+     * Automatically sets the Last-Modified header according the file modification date.
+     */
+    public function setAutoLastModified()
+    {
+        $this->setLastModified(\DateTime::createFromFormat('U', $this->file->getMTime()));
+
+        return $this;
+    }
+
+    /**
+     * Automatically sets the ETag header according to the checksum of the file.
+     */
+    public function setAutoEtag()
+    {
+        $this->setEtag(base64_encode(hash_file('sha256', $this->file->getPathname(), true)));
+
+        return $this;
+    }
+
+    /**
+     * Sets the Content-Disposition header with the given filename.
+     *
+     * @param string $disposition      ResponseHeaderBag::DISPOSITION_INLINE or ResponseHeaderBag::DISPOSITION_ATTACHMENT
+     * @param string $filename         Optionally use this UTF-8 encoded filename instead of the real name of the file
+     * @param string $filenameFallback A fallback filename, containing only ASCII characters. Defaults to an automatically encoded filename
+     *
+     * @return $this
+     */
+    public function setContentDisposition($disposition, $filename = '', $filenameFallback = '')
+    {
+        if ('' === $filename) {
+            $filename = $this->file->getFilename();
+        }
+
+        if ('' === $filenameFallback && (!preg_match('/^[\x20-\x7e]*$/', $filename) || false !== strpos($filename, '%'))) {
+            $encoding = mb_detect_encoding($filename, null, true) ?: '8bit';
+
+            for ($i = 0, $filenameLength = mb_strlen($filename, $encoding); $i < $filenameLength; ++$i) {
+                $char = mb_substr($filename, $i, 1, $encoding);
+
+                if ('%' === $char || ord($char) < 32 || ord($char) > 126) {
+                    $filenameFallback .= '_';
+                } else {
+                    $filenameFallback .= $char;
+                }
+            }
+        }
+
+        $dispositionHeader = $this->headers->makeDisposition($disposition, $filename, $filenameFallback);
+        $this->headers->set('Content-Disposition', $dispositionHeader);
+
+        return $this;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function prepare(Request $request)
+    {
+        if (!$this->headers->has('Content-Type')) {
+            $this->headers->set('Content-Type', $this->file->getMimeType() ?: 'application/octet-stream');
+        }
+
+        if ('HTTP/1.0' !== $request->server->get('SERVER_PROTOCOL')) {
+            $this->setProtocolVersion('1.1');
+        }
+
+        $this->ensureIEOverSSLCompatibility($request);
+
+        $this->offset = 0;
+        $this->maxlen = -1;
+
+        if (false === $fileSize = $this->file->getSize()) {
+            return $this;
+        }
+        $this->headers->set('Content-Length', $fileSize);
+
+        if (!$this->headers->has('Accept-Ranges')) {
+            // Only accept ranges on safe HTTP methods
+            $this->headers->set('Accept-Ranges', $request->isMethodSafe(false) ? 'bytes' : 'none');
+        }
+
+        if (self::$trustXSendfileTypeHeader && $request->headers->has('X-Sendfile-Type')) {
+            // Use X-Sendfile, do not send any content.
+            $type = $request->headers->get('X-Sendfile-Type');
+            $path = $this->file->getRealPath();
+            // Fall back to scheme://path for stream wrapped locations.
+            if (false === $path) {
+                $path = $this->file->getPathname();
+            }
+            if ('x-accel-redirect' === strtolower($type)) {
+                // Do X-Accel-Mapping substitutions.
+                // @link http://wiki.nginx.org/X-accel#X-Accel-Redirect
+                foreach (explode(',', $request->headers->get('X-Accel-Mapping', '')) as $mapping) {
+                    $mapping = explode('=', $mapping, 2);
+
+                    if (2 === count($mapping)) {
+                        $pathPrefix = trim($mapping[0]);
+                        $location = trim($mapping[1]);
+
+                        if (substr($path, 0, strlen($pathPrefix)) === $pathPrefix) {
+                            $path = $location.substr($path, strlen($pathPrefix));
+                            break;
+                        }
+                    }
+                }
+            }
+            $this->headers->set($type, $path);
+            $this->maxlen = 0;
+        } elseif ($request->headers->has('Range')) {
+            // Process the range headers.
+            if (!$request->headers->has('If-Range') || $this->hasValidIfRangeHeader($request->headers->get('If-Range'))) {
+                $range = $request->headers->get('Range');
+
+                list($start, $end) = explode('-', substr($range, 6), 2) + array(0);
+
+                $end = ('' === $end) ? $fileSize - 1 : (int) $end;
+
+                if ('' === $start) {
+                    $start = $fileSize - $end;
+                    $end = $fileSize - 1;
+                } else {
+                    $start = (int) $start;
+                }
+
+                if ($start <= $end) {
+                    if ($start < 0 || $end > $fileSize - 1) {
+                        $this->setStatusCode(416);
+                        $this->headers->set('Content-Range', sprintf('bytes */%s', $fileSize));
+                    } elseif (0 !== $start || $end !== $fileSize - 1) {
+                        $this->maxlen = $end < $fileSize ? $end - $start + 1 : -1;
+                        $this->offset = $start;
+
+                        $this->setStatusCode(206);
+                        $this->headers->set('Content-Range', sprintf('bytes %s-%s/%s', $start, $end, $fileSize));
+                        $this->headers->set('Content-Length', $end - $start + 1);
+                    }
+                }
+            }
+        }
+
+        return $this;
+    }
+
+    private function hasValidIfRangeHeader($header)
+    {
+        if ($this->getEtag() === $header) {
+            return true;
+        }
+
+        if (null === $lastModified = $this->getLastModified()) {
+            return false;
+        }
+
+        return $lastModified->format('D, d M Y H:i:s').' GMT' === $header;
+    }
+
+    /**
+     * Sends the file.
+     *
+     * {@inheritdoc}
+     */
+    public function sendContent()
+    {
+        if (!$this->isSuccessful()) {
+            return parent::sendContent();
+        }
+
+        if (0 === $this->maxlen) {
+            return $this;
+        }
+
+        $out = fopen('php://output', 'wb');
+        $file = fopen($this->file->getPathname(), 'rb');
+
+        stream_copy_to_stream($file, $out, $this->maxlen, $this->offset);
+
+        fclose($out);
+        fclose($file);
+
+        if ($this->deleteFileAfterSend) {
+            unlink($this->file->getPathname());
+        }
+
+        return $this;
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * @throws \LogicException when the content is not null
+     */
+    public function setContent($content)
+    {
+        if (null !== $content) {
+            throw new \LogicException('The content cannot be set on a BinaryFileResponse instance.');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * @return false
+     */
+    public function getContent()
+    {
+        return false;
+    }
+
+    /**
+     * Trust X-Sendfile-Type header.
+     */
+    public static function trustXSendfileTypeHeader()
+    {
+        self::$trustXSendfileTypeHeader = true;
+    }
+
+    /**
+     * If this is set to true, the file will be unlinked after the request is send
+     * Note: If the X-Sendfile header is used, the deleteFileAfterSend setting will not be used.
+     *
+     * @param bool $shouldDelete
+     *
+     * @return $this
+     */
+    public function deleteFileAfterSend($shouldDelete)
+    {
+        $this->deleteFileAfterSend = $shouldDelete;
+
+        return $this;
+    }
+}
diff --git a/vendor/symfony/http-foundation/CHANGELOG.md b/vendor/symfony/http-foundation/CHANGELOG.md
new file mode 100644
index 0000000000000000000000000000000000000000..ee5b6cecf2e85e43883791f0adb62df64d874386
--- /dev/null
+++ b/vendor/symfony/http-foundation/CHANGELOG.md
@@ -0,0 +1,159 @@
+CHANGELOG
+=========
+
+3.4.0
+-----
+
+ * implemented PHP 7.0's `SessionUpdateTimestampHandlerInterface` with a new
+   `AbstractSessionHandler` base class and a new `StrictSessionHandler` wrapper
+ * deprecated the `WriteCheckSessionHandler`, `NativeSessionHandler` and `NativeProxy` classes
+ * deprecated setting session save handlers that do not implement `\SessionHandlerInterface` in `NativeSessionStorage::setSaveHandler()`
+ * deprecated using `MongoDbSessionHandler` with the legacy mongo extension; use it with the mongodb/mongodb package and ext-mongodb instead
+ * deprecated `MemcacheSessionHandler`; use `MemcachedSessionHandler` instead
+
+3.3.0
+-----
+
+ * the `Request::setTrustedProxies()` method takes a new `$trustedHeaderSet` argument,
+   see http://symfony.com/doc/current/components/http_foundation/trusting_proxies.html for more info,
+ * deprecated the `Request::setTrustedHeaderName()` and `Request::getTrustedHeaderName()` methods,
+ * added `File\Stream`, to be passed to `BinaryFileResponse` when the size of the served file is unknown,
+   disabling `Range` and `Content-Length` handling, switching to chunked encoding instead
+ * added the `Cookie::fromString()` method that allows to create a cookie from a
+   raw header string
+
+3.1.0
+-----
+
+ * Added support for creating `JsonResponse` with a string of JSON data
+
+3.0.0
+-----
+
+ * The precedence of parameters returned from `Request::get()` changed from "GET, PATH, BODY" to "PATH, GET, BODY"
+
+2.8.0
+-----
+
+ * Finding deep items in `ParameterBag::get()` is deprecated since version 2.8 and
+   will be removed in 3.0.
+
+2.6.0
+-----
+
+ * PdoSessionHandler changes
+   - implemented different session locking strategies to prevent loss of data by concurrent access to the same session
+   - [BC BREAK] save session data in a binary column without base64_encode
+   - [BC BREAK] added lifetime column to the session table which allows to have different lifetimes for each session
+   - implemented lazy connections that are only opened when a session is used by either passing a dsn string
+     explicitly or falling back to session.save_path ini setting
+   - added a createTable method that initializes a correctly defined table depending on the database vendor
+
+2.5.0
+-----
+
+ * added `JsonResponse::setEncodingOptions()` & `JsonResponse::getEncodingOptions()` for easier manipulation
+   of the options used while encoding data to JSON format.
+
+2.4.0
+-----
+
+ * added RequestStack
+ * added Request::getEncodings()
+ * added accessors methods to session handlers
+
+2.3.0
+-----
+
+ * added support for ranges of IPs in trusted proxies
+ * `UploadedFile::isValid` now returns false if the file was not uploaded via HTTP (in a non-test mode)
+ * Improved error-handling of `\Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler`
+   to ensure the supplied PDO handler throws Exceptions on error (as the class expects). Added related test cases
+   to verify that Exceptions are properly thrown when the PDO queries fail.
+
+2.2.0
+-----
+
+ * fixed the Request::create() precedence (URI information always take precedence now)
+ * added Request::getTrustedProxies()
+ * deprecated Request::isProxyTrusted()
+ * [BC BREAK] JsonResponse does not turn a top level empty array to an object anymore, use an ArrayObject to enforce objects
+ * added a IpUtils class to check if an IP belongs to a CIDR
+ * added Request::getRealMethod() to get the "real" HTTP method (getMethod() returns the "intended" HTTP method)
+ * disabled _method request parameter support by default (call Request::enableHttpMethodParameterOverride() to
+   enable it, and Request::getHttpMethodParameterOverride() to check if it is supported)
+ * Request::splitHttpAcceptHeader() method is deprecated and will be removed in 2.3
+ * Deprecated Flashbag::count() and \Countable interface, will be removed in 2.3
+
+2.1.0
+-----
+
+ * added Request::getSchemeAndHttpHost() and Request::getUserInfo()
+ * added a fluent interface to the Response class
+ * added Request::isProxyTrusted()
+ * added JsonResponse
+ * added a getTargetUrl method to RedirectResponse
+ * added support for streamed responses
+ * made Response::prepare() method the place to enforce HTTP specification
+ * [BC BREAK] moved management of the locale from the Session class to the Request class
+ * added a generic access to the PHP built-in filter mechanism: ParameterBag::filter()
+ * made FileBinaryMimeTypeGuesser command configurable
+ * added Request::getUser() and Request::getPassword()
+ * added support for the PATCH method in Request
+ * removed the ContentTypeMimeTypeGuesser class as it is deprecated and never used on PHP 5.3
+ * added ResponseHeaderBag::makeDisposition() (implements RFC 6266)
+ * made mimetype to extension conversion configurable
+ * [BC BREAK] Moved all session related classes and interfaces into own namespace, as
+   `Symfony\Component\HttpFoundation\Session` and renamed classes accordingly.
+   Session handlers are located in the subnamespace `Symfony\Component\HttpFoundation\Session\Handler`.
+ * SessionHandlers must implement `\SessionHandlerInterface` or extend from the
+   `Symfony\Component\HttpFoundation\Storage\Handler\NativeSessionHandler` base class.
+ * Added internal storage driver proxy mechanism for forward compatibility with
+   PHP 5.4 `\SessionHandler` class.
+ * Added session handlers for custom Memcache, Memcached and Null session save handlers.
+ * [BC BREAK] Removed `NativeSessionStorage` and replaced with `NativeFileSessionHandler`.
+ * [BC BREAK] `SessionStorageInterface` methods removed: `write()`, `read()` and
+   `remove()`.  Added `getBag()`, `registerBag()`.  The `NativeSessionStorage` class
+   is a mediator for the session storage internals including the session handlers
+   which do the real work of participating in the internal PHP session workflow.
+ * [BC BREAK] Introduced mock implementations of `SessionStorage` to enable unit
+   and functional testing without starting real PHP sessions.  Removed
+   `ArraySessionStorage`, and replaced with `MockArraySessionStorage` for unit
+   tests; removed `FilesystemSessionStorage`, and replaced with`MockFileSessionStorage`
+   for functional tests.  These do not interact with global session ini
+   configuration values, session functions or `$_SESSION` superglobal. This means
+   they can be configured directly allowing multiple instances to work without
+   conflicting in the same PHP process.
+ * [BC BREAK] Removed the `close()` method from the `Session` class, as this is
+   now redundant.
+ * Deprecated the following methods from the Session class: `setFlash()`, `setFlashes()`
+   `getFlash()`, `hasFlash()`, and `removeFlash()`. Use `getFlashBag()` instead
+   which returns a `FlashBagInterface`.
+ * `Session->clear()` now only clears session attributes as before it cleared
+   flash messages and attributes. `Session->getFlashBag()->all()` clears flashes now.
+ * Session data is now managed by `SessionBagInterface` to better encapsulate
+   session data.
+ * Refactored session attribute and flash messages system to their own
+  `SessionBagInterface` implementations.
+ * Added `FlashBag`. Flashes expire when retrieved by `get()` or `all()`. This
+   implementation is ESI compatible.
+ * Added `AutoExpireFlashBag` (default) to replicate Symfony 2.0.x auto expire
+   behaviour of messages auto expiring after one page page load.  Messages must
+   be retrieved by `get()` or `all()`.
+ * Added `Symfony\Component\HttpFoundation\Attribute\AttributeBag` to replicate
+   attributes storage behaviour from 2.0.x (default).
+ * Added `Symfony\Component\HttpFoundation\Attribute\NamespacedAttributeBag` for
+   namespace session attributes.
+ * Flash API can stores messages in an array so there may be multiple messages
+   per flash type.  The old `Session` class API remains without BC break as it
+   will allow single messages as before.
+ * Added basic session meta-data to the session to record session create time,
+   last updated time, and the lifetime of the session cookie that was provided
+   to the client.
+ * Request::getClientIp() method doesn't take a parameter anymore but bases
+   itself on the trustProxy parameter.
+ * Added isMethod() to Request object.
+ * [BC BREAK] The methods `getPathInfo()`, `getBaseUrl()` and `getBasePath()` of
+   a `Request` now all return a raw value (vs a urldecoded value before). Any call
+   to one of these methods must be checked and wrapped in a `rawurldecode()` if
+   needed.
diff --git a/vendor/symfony/http-foundation/Cookie.php b/vendor/symfony/http-foundation/Cookie.php
new file mode 100644
index 0000000000000000000000000000000000000000..4519a6adaeda59aee7b3c48eb7b2b213965fca82
--- /dev/null
+++ b/vendor/symfony/http-foundation/Cookie.php
@@ -0,0 +1,289 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Represents a cookie.
+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class Cookie
+{
+    protected $name;
+    protected $value;
+    protected $domain;
+    protected $expire;
+    protected $path;
+    protected $secure;
+    protected $httpOnly;
+    private $raw;
+    private $sameSite;
+
+    const SAMESITE_LAX = 'lax';
+    const SAMESITE_STRICT = 'strict';
+
+    /**
+     * Creates cookie from raw header string.
+     *
+     * @param string $cookie
+     * @param bool   $decode
+     *
+     * @return static
+     */
+    public static function fromString($cookie, $decode = false)
+    {
+        $data = array(
+            'expires' => 0,
+            'path' => '/',
+            'domain' => null,
+            'secure' => false,
+            'httponly' => false,
+            'raw' => !$decode,
+            'samesite' => null,
+        );
+        foreach (explode(';', $cookie) as $part) {
+            if (false === strpos($part, '=')) {
+                $key = trim($part);
+                $value = true;
+            } else {
+                list($key, $value) = explode('=', trim($part), 2);
+                $key = trim($key);
+                $value = trim($value);
+            }
+            if (!isset($data['name'])) {
+                $data['name'] = $decode ? urldecode($key) : $key;
+                $data['value'] = true === $value ? null : ($decode ? urldecode($value) : $value);
+                continue;
+            }
+            switch ($key = strtolower($key)) {
+                case 'name':
+                case 'value':
+                    break;
+                case 'max-age':
+                    $data['expires'] = time() + (int) $value;
+                    break;
+                default:
+                    $data[$key] = $value;
+                    break;
+            }
+        }
+
+        return new static($data['name'], $data['value'], $data['expires'], $data['path'], $data['domain'], $data['secure'], $data['httponly'], $data['raw'], $data['samesite']);
+    }
+
+    /**
+     * @param string                        $name     The name of the cookie
+     * @param string|null                   $value    The value of the cookie
+     * @param int|string|\DateTimeInterface $expire   The time the cookie expires
+     * @param string                        $path     The path on the server in which the cookie will be available on
+     * @param string|null                   $domain   The domain that the cookie is available to
+     * @param bool                          $secure   Whether the cookie should only be transmitted over a secure HTTPS connection from the client
+     * @param bool                          $httpOnly Whether the cookie will be made accessible only through the HTTP protocol
+     * @param bool                          $raw      Whether the cookie value should be sent with no url encoding
+     * @param string|null                   $sameSite Whether the cookie will be available for cross-site requests
+     *
+     * @throws \InvalidArgumentException
+     */
+    public function __construct($name, $value = null, $expire = 0, $path = '/', $domain = null, $secure = false, $httpOnly = true, $raw = false, $sameSite = null)
+    {
+        // from PHP source code
+        if (preg_match("/[=,; \t\r\n\013\014]/", $name)) {
+            throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name));
+        }
+
+        if (empty($name)) {
+            throw new \InvalidArgumentException('The cookie name cannot be empty.');
+        }
+
+        // convert expiration time to a Unix timestamp
+        if ($expire instanceof \DateTimeInterface) {
+            $expire = $expire->format('U');
+        } elseif (!is_numeric($expire)) {
+            $expire = strtotime($expire);
+
+            if (false === $expire) {
+                throw new \InvalidArgumentException('The cookie expiration time is not valid.');
+            }
+        }
+
+        $this->name = $name;
+        $this->value = $value;
+        $this->domain = $domain;
+        $this->expire = 0 < $expire ? (int) $expire : 0;
+        $this->path = empty($path) ? '/' : $path;
+        $this->secure = (bool) $secure;
+        $this->httpOnly = (bool) $httpOnly;
+        $this->raw = (bool) $raw;
+
+        if (null !== $sameSite) {
+            $sameSite = strtolower($sameSite);
+        }
+
+        if (!in_array($sameSite, array(self::SAMESITE_LAX, self::SAMESITE_STRICT, null), true)) {
+            throw new \InvalidArgumentException('The "sameSite" parameter value is not valid.');
+        }
+
+        $this->sameSite = $sameSite;
+    }
+
+    /**
+     * Returns the cookie as a string.
+     *
+     * @return string The cookie
+     */
+    public function __toString()
+    {
+        $str = ($this->isRaw() ? $this->getName() : urlencode($this->getName())).'=';
+
+        if ('' === (string) $this->getValue()) {
+            $str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; max-age=-31536001';
+        } else {
+            $str .= $this->isRaw() ? $this->getValue() : rawurlencode($this->getValue());
+
+            if (0 !== $this->getExpiresTime()) {
+                $str .= '; expires='.gmdate('D, d-M-Y H:i:s T', $this->getExpiresTime()).'; max-age='.$this->getMaxAge();
+            }
+        }
+
+        if ($this->getPath()) {
+            $str .= '; path='.$this->getPath();
+        }
+
+        if ($this->getDomain()) {
+            $str .= '; domain='.$this->getDomain();
+        }
+
+        if (true === $this->isSecure()) {
+            $str .= '; secure';
+        }
+
+        if (true === $this->isHttpOnly()) {
+            $str .= '; httponly';
+        }
+
+        if (null !== $this->getSameSite()) {
+            $str .= '; samesite='.$this->getSameSite();
+        }
+
+        return $str;
+    }
+
+    /**
+     * Gets the name of the cookie.
+     *
+     * @return string
+     */
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    /**
+     * Gets the value of the cookie.
+     *
+     * @return string|null
+     */
+    public function getValue()
+    {
+        return $this->value;
+    }
+
+    /**
+     * Gets the domain that the cookie is available to.
+     *
+     * @return string|null
+     */
+    public function getDomain()
+    {
+        return $this->domain;
+    }
+
+    /**
+     * Gets the time the cookie expires.
+     *
+     * @return int
+     */
+    public function getExpiresTime()
+    {
+        return $this->expire;
+    }
+
+    /**
+     * Gets the max-age attribute.
+     *
+     * @return int
+     */
+    public function getMaxAge()
+    {
+        return 0 !== $this->expire ? $this->expire - time() : 0;
+    }
+
+    /**
+     * Gets the path on the server in which the cookie will be available on.
+     *
+     * @return string
+     */
+    public function getPath()
+    {
+        return $this->path;
+    }
+
+    /**
+     * Checks whether the cookie should only be transmitted over a secure HTTPS connection from the client.
+     *
+     * @return bool
+     */
+    public function isSecure()
+    {
+        return $this->secure;
+    }
+
+    /**
+     * Checks whether the cookie will be made accessible only through the HTTP protocol.
+     *
+     * @return bool
+     */
+    public function isHttpOnly()
+    {
+        return $this->httpOnly;
+    }
+
+    /**
+     * Whether this cookie is about to be cleared.
+     *
+     * @return bool
+     */
+    public function isCleared()
+    {
+        return $this->expire < time();
+    }
+
+    /**
+     * Checks if the cookie value should be sent with no url encoding.
+     *
+     * @return bool
+     */
+    public function isRaw()
+    {
+        return $this->raw;
+    }
+
+    /**
+     * Gets the SameSite attribute.
+     *
+     * @return string|null
+     */
+    public function getSameSite()
+    {
+        return $this->sameSite;
+    }
+}
diff --git a/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php b/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php
new file mode 100644
index 0000000000000000000000000000000000000000..5fcf5b42695e7d7c2928f9f24d270d86deca5fa4
--- /dev/null
+++ b/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Exception;
+
+/**
+ * The HTTP request contains headers with conflicting information.
+ *
+ * @author Magnus Nordlander <magnus@fervo.se>
+ */
+class ConflictingHeadersException extends \UnexpectedValueException implements RequestExceptionInterface
+{
+}
diff --git a/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php b/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..478d0dc7e4e6b34bf0cd661483d9219da07a3666
--- /dev/null
+++ b/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Exception;
+
+/**
+ * Interface for Request exceptions.
+ *
+ * Exceptions implementing this interface should trigger an HTTP 400 response in the application code.
+ */
+interface RequestExceptionInterface
+{
+}
diff --git a/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php b/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php
new file mode 100644
index 0000000000000000000000000000000000000000..ae7a5f1332b1fd618304ce98104580adae58b992
--- /dev/null
+++ b/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php
@@ -0,0 +1,20 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Exception;
+
+/**
+ * Raised when a user has performed an operation that should be considered
+ * suspicious from a security perspective.
+ */
+class SuspiciousOperationException extends \UnexpectedValueException implements RequestExceptionInterface
+{
+}
diff --git a/vendor/symfony/http-foundation/ExpressionRequestMatcher.php b/vendor/symfony/http-foundation/ExpressionRequestMatcher.php
new file mode 100644
index 0000000000000000000000000000000000000000..e9c8441ce314b2f5a9eb88350b888fe83030636a
--- /dev/null
+++ b/vendor/symfony/http-foundation/ExpressionRequestMatcher.php
@@ -0,0 +1,47 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
+
+/**
+ * ExpressionRequestMatcher uses an expression to match a Request.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class ExpressionRequestMatcher extends RequestMatcher
+{
+    private $language;
+    private $expression;
+
+    public function setExpression(ExpressionLanguage $language, $expression)
+    {
+        $this->language = $language;
+        $this->expression = $expression;
+    }
+
+    public function matches(Request $request)
+    {
+        if (!$this->language) {
+            throw new \LogicException('Unable to match the request as the expression language is not available.');
+        }
+
+        return $this->language->evaluate($this->expression, array(
+            'request' => $request,
+            'method' => $request->getMethod(),
+            'path' => rawurldecode($request->getPathInfo()),
+            'host' => $request->getHost(),
+            'ip' => $request->getClientIp(),
+            'attributes' => $request->attributes->all(),
+        )) && parent::matches($request);
+    }
+}
diff --git a/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php b/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php
new file mode 100644
index 0000000000000000000000000000000000000000..3b8e41d4a2cf99f40a99fed96a4fa660f8213885
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php
@@ -0,0 +1,28 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when the access on a file was denied.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class AccessDeniedException extends FileException
+{
+    /**
+     * @param string $path The path to the accessed file
+     */
+    public function __construct($path)
+    {
+        parent::__construct(sprintf('The file %s could not be accessed', $path));
+    }
+}
diff --git a/vendor/symfony/http-foundation/File/Exception/FileException.php b/vendor/symfony/http-foundation/File/Exception/FileException.php
new file mode 100644
index 0000000000000000000000000000000000000000..fad5133e1b745c108b33a2662564eeb1828a789b
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/Exception/FileException.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when an error occurred in the component File.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class FileException extends \RuntimeException
+{
+}
diff --git a/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php b/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php
new file mode 100644
index 0000000000000000000000000000000000000000..bfcc37ec66ea04583d11de82a10f3294d875f13f
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php
@@ -0,0 +1,28 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when a file was not found.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class FileNotFoundException extends FileException
+{
+    /**
+     * @param string $path The path to the file that was not found
+     */
+    public function __construct($path)
+    {
+        parent::__construct(sprintf('The file "%s" does not exist', $path));
+    }
+}
diff --git a/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php b/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php
new file mode 100644
index 0000000000000000000000000000000000000000..0444b8778218fb5e63d9ef8370f147c261921515
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php
@@ -0,0 +1,20 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+class UnexpectedTypeException extends FileException
+{
+    public function __construct($value, $expectedType)
+    {
+        parent::__construct(sprintf('Expected argument of type %s, %s given', $expectedType, is_object($value) ? get_class($value) : gettype($value)));
+    }
+}
diff --git a/vendor/symfony/http-foundation/File/Exception/UploadException.php b/vendor/symfony/http-foundation/File/Exception/UploadException.php
new file mode 100644
index 0000000000000000000000000000000000000000..7074e7653d0eda28f662fcd6e168d927dbc2e706
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/Exception/UploadException.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when an error occurred during file upload.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class UploadException extends FileException
+{
+}
diff --git a/vendor/symfony/http-foundation/File/File.php b/vendor/symfony/http-foundation/File/File.php
new file mode 100644
index 0000000000000000000000000000000000000000..e2a67684fcda640de0cc7a71ea32562867501f49
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/File.php
@@ -0,0 +1,136 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File;
+
+use Symfony\Component\HttpFoundation\File\Exception\FileException;
+use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
+use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser;
+use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser;
+
+/**
+ * A file in the file system.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class File extends \SplFileInfo
+{
+    /**
+     * Constructs a new file from the given path.
+     *
+     * @param string $path      The path to the file
+     * @param bool   $checkPath Whether to check the path or not
+     *
+     * @throws FileNotFoundException If the given path is not a file
+     */
+    public function __construct($path, $checkPath = true)
+    {
+        if ($checkPath && !is_file($path)) {
+            throw new FileNotFoundException($path);
+        }
+
+        parent::__construct($path);
+    }
+
+    /**
+     * Returns the extension based on the mime type.
+     *
+     * If the mime type is unknown, returns null.
+     *
+     * This method uses the mime type as guessed by getMimeType()
+     * to guess the file extension.
+     *
+     * @return string|null The guessed extension or null if it cannot be guessed
+     *
+     * @see ExtensionGuesser
+     * @see getMimeType()
+     */
+    public function guessExtension()
+    {
+        $type = $this->getMimeType();
+        $guesser = ExtensionGuesser::getInstance();
+
+        return $guesser->guess($type);
+    }
+
+    /**
+     * Returns the mime type of the file.
+     *
+     * The mime type is guessed using a MimeTypeGuesser instance, which uses finfo(),
+     * mime_content_type() and the system binary "file" (in this order), depending on
+     * which of those are available.
+     *
+     * @return string|null The guessed mime type (e.g. "application/pdf")
+     *
+     * @see MimeTypeGuesser
+     */
+    public function getMimeType()
+    {
+        $guesser = MimeTypeGuesser::getInstance();
+
+        return $guesser->guess($this->getPathname());
+    }
+
+    /**
+     * Moves the file to a new location.
+     *
+     * @param string $directory The destination folder
+     * @param string $name      The new file name
+     *
+     * @return self A File object representing the new file
+     *
+     * @throws FileException if the target file could not be created
+     */
+    public function move($directory, $name = null)
+    {
+        $target = $this->getTargetFile($directory, $name);
+
+        if (!@rename($this->getPathname(), $target)) {
+            $error = error_get_last();
+            throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error['message'])));
+        }
+
+        @chmod($target, 0666 & ~umask());
+
+        return $target;
+    }
+
+    protected function getTargetFile($directory, $name = null)
+    {
+        if (!is_dir($directory)) {
+            if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) {
+                throw new FileException(sprintf('Unable to create the "%s" directory', $directory));
+            }
+        } elseif (!is_writable($directory)) {
+            throw new FileException(sprintf('Unable to write in the "%s" directory', $directory));
+        }
+
+        $target = rtrim($directory, '/\\').DIRECTORY_SEPARATOR.(null === $name ? $this->getBasename() : $this->getName($name));
+
+        return new self($target, false);
+    }
+
+    /**
+     * Returns locale independent base name of the given path.
+     *
+     * @param string $name The new file name
+     *
+     * @return string containing
+     */
+    protected function getName($name)
+    {
+        $originalName = str_replace('\\', '/', $name);
+        $pos = strrpos($originalName, '/');
+        $originalName = false === $pos ? $originalName : substr($originalName, $pos + 1);
+
+        return $originalName;
+    }
+}
diff --git a/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesser.php b/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesser.php
new file mode 100644
index 0000000000000000000000000000000000000000..263fb321c574515925cb98a8e102f58f026cb628
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesser.php
@@ -0,0 +1,94 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\MimeType;
+
+/**
+ * A singleton mime type to file extension guesser.
+ *
+ * A default guesser is provided.
+ * You can register custom guessers by calling the register()
+ * method on the singleton instance:
+ *
+ *     $guesser = ExtensionGuesser::getInstance();
+ *     $guesser->register(new MyCustomExtensionGuesser());
+ *
+ * The last registered guesser is preferred over previously registered ones.
+ */
+class ExtensionGuesser implements ExtensionGuesserInterface
+{
+    /**
+     * The singleton instance.
+     *
+     * @var ExtensionGuesser
+     */
+    private static $instance = null;
+
+    /**
+     * All registered ExtensionGuesserInterface instances.
+     *
+     * @var array
+     */
+    protected $guessers = array();
+
+    /**
+     * Returns the singleton instance.
+     *
+     * @return self
+     */
+    public static function getInstance()
+    {
+        if (null === self::$instance) {
+            self::$instance = new self();
+        }
+
+        return self::$instance;
+    }
+
+    /**
+     * Registers all natively provided extension guessers.
+     */
+    private function __construct()
+    {
+        $this->register(new MimeTypeExtensionGuesser());
+    }
+
+    /**
+     * Registers a new extension guesser.
+     *
+     * When guessing, this guesser is preferred over previously registered ones.
+     */
+    public function register(ExtensionGuesserInterface $guesser)
+    {
+        array_unshift($this->guessers, $guesser);
+    }
+
+    /**
+     * Tries to guess the extension.
+     *
+     * The mime type is passed to each registered mime type guesser in reverse order
+     * of their registration (last registered is queried first). Once a guesser
+     * returns a value that is not NULL, this method terminates and returns the
+     * value.
+     *
+     * @param string $mimeType The mime type
+     *
+     * @return string The guessed extension or NULL, if none could be guessed
+     */
+    public function guess($mimeType)
+    {
+        foreach ($this->guessers as $guesser) {
+            if (null !== $extension = $guesser->guess($mimeType)) {
+                return $extension;
+            }
+        }
+    }
+}
diff --git a/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php b/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..d19a0e5371349302b9aaac37ed9ae51562258b40
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php
@@ -0,0 +1,27 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\MimeType;
+
+/**
+ * Guesses the file extension corresponding to a given mime type.
+ */
+interface ExtensionGuesserInterface
+{
+    /**
+     * Makes a best guess for a file extension, given a mime type.
+     *
+     * @param string $mimeType The mime type
+     *
+     * @return string The guessed extension or NULL, if none could be guessed
+     */
+    public function guess($mimeType);
+}
diff --git a/vendor/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php b/vendor/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php
new file mode 100644
index 0000000000000000000000000000000000000000..c2ac6768c30133db07434dc10d51610a862ff1f2
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php
@@ -0,0 +1,85 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\MimeType;
+
+use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
+use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
+
+/**
+ * Guesses the mime type with the binary "file" (only available on *nix).
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface
+{
+    private $cmd;
+
+    /**
+     * The $cmd pattern must contain a "%s" string that will be replaced
+     * with the file name to guess.
+     *
+     * The command output must start with the mime type of the file.
+     *
+     * @param string $cmd The command to run to get the mime type of a file
+     */
+    public function __construct($cmd = 'file -b --mime %s 2>/dev/null')
+    {
+        $this->cmd = $cmd;
+    }
+
+    /**
+     * Returns whether this guesser is supported on the current OS.
+     *
+     * @return bool
+     */
+    public static function isSupported()
+    {
+        return '\\' !== DIRECTORY_SEPARATOR && function_exists('passthru') && function_exists('escapeshellarg');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function guess($path)
+    {
+        if (!is_file($path)) {
+            throw new FileNotFoundException($path);
+        }
+
+        if (!is_readable($path)) {
+            throw new AccessDeniedException($path);
+        }
+
+        if (!self::isSupported()) {
+            return;
+        }
+
+        ob_start();
+
+        // need to use --mime instead of -i. see #6641
+        passthru(sprintf($this->cmd, escapeshellarg($path)), $return);
+        if ($return > 0) {
+            ob_end_clean();
+
+            return;
+        }
+
+        $type = trim(ob_get_clean());
+
+        if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-\.]+)#i', $type, $match)) {
+            // it's not a type, but an error message
+            return;
+        }
+
+        return $match[1];
+    }
+}
diff --git a/vendor/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php b/vendor/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php
new file mode 100644
index 0000000000000000000000000000000000000000..9b42835e4304418e4b9211307f9726cb5f6d221a
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php
@@ -0,0 +1,69 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\MimeType;
+
+use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
+use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
+
+/**
+ * Guesses the mime type using the PECL extension FileInfo.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class FileinfoMimeTypeGuesser implements MimeTypeGuesserInterface
+{
+    private $magicFile;
+
+    /**
+     * @param string $magicFile A magic file to use with the finfo instance
+     *
+     * @see http://www.php.net/manual/en/function.finfo-open.php
+     */
+    public function __construct($magicFile = null)
+    {
+        $this->magicFile = $magicFile;
+    }
+
+    /**
+     * Returns whether this guesser is supported on the current OS/PHP setup.
+     *
+     * @return bool
+     */
+    public static function isSupported()
+    {
+        return function_exists('finfo_open');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function guess($path)
+    {
+        if (!is_file($path)) {
+            throw new FileNotFoundException($path);
+        }
+
+        if (!is_readable($path)) {
+            throw new AccessDeniedException($path);
+        }
+
+        if (!self::isSupported()) {
+            return;
+        }
+
+        if (!$finfo = new \finfo(FILEINFO_MIME_TYPE, $this->magicFile)) {
+            return;
+        }
+
+        return $finfo->file($path);
+    }
+}
diff --git a/vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php b/vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php
new file mode 100644
index 0000000000000000000000000000000000000000..77bf51b1e5a032b633db261269fc5344e59eabc4
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php
@@ -0,0 +1,808 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\MimeType;
+
+/**
+ * Provides a best-guess mapping of mime type to file extension.
+ */
+class MimeTypeExtensionGuesser implements ExtensionGuesserInterface
+{
+    /**
+     * A map of mime types and their default extensions.
+     *
+     * This list has been placed under the public domain by the Apache HTTPD project.
+     * This list has been updated from upstream on 2013-04-23.
+     *
+     * @see http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
+     */
+    protected $defaultExtensions = array(
+        'application/andrew-inset' => 'ez',
+        'application/applixware' => 'aw',
+        'application/atom+xml' => 'atom',
+        'application/atomcat+xml' => 'atomcat',
+        'application/atomsvc+xml' => 'atomsvc',
+        'application/ccxml+xml' => 'ccxml',
+        'application/cdmi-capability' => 'cdmia',
+        'application/cdmi-container' => 'cdmic',
+        'application/cdmi-domain' => 'cdmid',
+        'application/cdmi-object' => 'cdmio',
+        'application/cdmi-queue' => 'cdmiq',
+        'application/cu-seeme' => 'cu',
+        'application/davmount+xml' => 'davmount',
+        'application/docbook+xml' => 'dbk',
+        'application/dssc+der' => 'dssc',
+        'application/dssc+xml' => 'xdssc',
+        'application/ecmascript' => 'ecma',
+        'application/emma+xml' => 'emma',
+        'application/epub+zip' => 'epub',
+        'application/exi' => 'exi',
+        'application/font-tdpfr' => 'pfr',
+        'application/gml+xml' => 'gml',
+        'application/gpx+xml' => 'gpx',
+        'application/gxf' => 'gxf',
+        'application/hyperstudio' => 'stk',
+        'application/inkml+xml' => 'ink',
+        'application/ipfix' => 'ipfix',
+        'application/java-archive' => 'jar',
+        'application/java-serialized-object' => 'ser',
+        'application/java-vm' => 'class',
+        'application/javascript' => 'js',
+        'application/json' => 'json',
+        'application/jsonml+json' => 'jsonml',
+        'application/lost+xml' => 'lostxml',
+        'application/mac-binhex40' => 'hqx',
+        'application/mac-compactpro' => 'cpt',
+        'application/mads+xml' => 'mads',
+        'application/marc' => 'mrc',
+        'application/marcxml+xml' => 'mrcx',
+        'application/mathematica' => 'ma',
+        'application/mathml+xml' => 'mathml',
+        'application/mbox' => 'mbox',
+        'application/mediaservercontrol+xml' => 'mscml',
+        'application/metalink+xml' => 'metalink',
+        'application/metalink4+xml' => 'meta4',
+        'application/mets+xml' => 'mets',
+        'application/mods+xml' => 'mods',
+        'application/mp21' => 'm21',
+        'application/mp4' => 'mp4s',
+        'application/msword' => 'doc',
+        'application/mxf' => 'mxf',
+        'application/octet-stream' => 'bin',
+        'application/oda' => 'oda',
+        'application/oebps-package+xml' => 'opf',
+        'application/ogg' => 'ogx',
+        'application/omdoc+xml' => 'omdoc',
+        'application/onenote' => 'onetoc',
+        'application/oxps' => 'oxps',
+        'application/patch-ops-error+xml' => 'xer',
+        'application/pdf' => 'pdf',
+        'application/pgp-encrypted' => 'pgp',
+        'application/pgp-signature' => 'asc',
+        'application/pics-rules' => 'prf',
+        'application/pkcs10' => 'p10',
+        'application/pkcs7-mime' => 'p7m',
+        'application/pkcs7-signature' => 'p7s',
+        'application/pkcs8' => 'p8',
+        'application/pkix-attr-cert' => 'ac',
+        'application/pkix-cert' => 'cer',
+        'application/pkix-crl' => 'crl',
+        'application/pkix-pkipath' => 'pkipath',
+        'application/pkixcmp' => 'pki',
+        'application/pls+xml' => 'pls',
+        'application/postscript' => 'ai',
+        'application/prs.cww' => 'cww',
+        'application/pskc+xml' => 'pskcxml',
+        'application/rdf+xml' => 'rdf',
+        'application/reginfo+xml' => 'rif',
+        'application/relax-ng-compact-syntax' => 'rnc',
+        'application/resource-lists+xml' => 'rl',
+        'application/resource-lists-diff+xml' => 'rld',
+        'application/rls-services+xml' => 'rs',
+        'application/rpki-ghostbusters' => 'gbr',
+        'application/rpki-manifest' => 'mft',
+        'application/rpki-roa' => 'roa',
+        'application/rsd+xml' => 'rsd',
+        'application/rss+xml' => 'rss',
+        'application/rtf' => 'rtf',
+        'application/sbml+xml' => 'sbml',
+        'application/scvp-cv-request' => 'scq',
+        'application/scvp-cv-response' => 'scs',
+        'application/scvp-vp-request' => 'spq',
+        'application/scvp-vp-response' => 'spp',
+        'application/sdp' => 'sdp',
+        'application/set-payment-initiation' => 'setpay',
+        'application/set-registration-initiation' => 'setreg',
+        'application/shf+xml' => 'shf',
+        'application/smil+xml' => 'smi',
+        'application/sparql-query' => 'rq',
+        'application/sparql-results+xml' => 'srx',
+        'application/srgs' => 'gram',
+        'application/srgs+xml' => 'grxml',
+        'application/sru+xml' => 'sru',
+        'application/ssdl+xml' => 'ssdl',
+        'application/ssml+xml' => 'ssml',
+        'application/tei+xml' => 'tei',
+        'application/thraud+xml' => 'tfi',
+        'application/timestamped-data' => 'tsd',
+        'application/vnd.3gpp.pic-bw-large' => 'plb',
+        'application/vnd.3gpp.pic-bw-small' => 'psb',
+        'application/vnd.3gpp.pic-bw-var' => 'pvb',
+        'application/vnd.3gpp2.tcap' => 'tcap',
+        'application/vnd.3m.post-it-notes' => 'pwn',
+        'application/vnd.accpac.simply.aso' => 'aso',
+        'application/vnd.accpac.simply.imp' => 'imp',
+        'application/vnd.acucobol' => 'acu',
+        'application/vnd.acucorp' => 'atc',
+        'application/vnd.adobe.air-application-installer-package+zip' => 'air',
+        'application/vnd.adobe.formscentral.fcdt' => 'fcdt',
+        'application/vnd.adobe.fxp' => 'fxp',
+        'application/vnd.adobe.xdp+xml' => 'xdp',
+        'application/vnd.adobe.xfdf' => 'xfdf',
+        'application/vnd.ahead.space' => 'ahead',
+        'application/vnd.airzip.filesecure.azf' => 'azf',
+        'application/vnd.airzip.filesecure.azs' => 'azs',
+        'application/vnd.amazon.ebook' => 'azw',
+        'application/vnd.americandynamics.acc' => 'acc',
+        'application/vnd.amiga.ami' => 'ami',
+        'application/vnd.android.package-archive' => 'apk',
+        'application/vnd.anser-web-certificate-issue-initiation' => 'cii',
+        'application/vnd.anser-web-funds-transfer-initiation' => 'fti',
+        'application/vnd.antix.game-component' => 'atx',
+        'application/vnd.apple.installer+xml' => 'mpkg',
+        'application/vnd.apple.mpegurl' => 'm3u8',
+        'application/vnd.aristanetworks.swi' => 'swi',
+        'application/vnd.astraea-software.iota' => 'iota',
+        'application/vnd.audiograph' => 'aep',
+        'application/vnd.blueice.multipass' => 'mpm',
+        'application/vnd.bmi' => 'bmi',
+        'application/vnd.businessobjects' => 'rep',
+        'application/vnd.chemdraw+xml' => 'cdxml',
+        'application/vnd.chipnuts.karaoke-mmd' => 'mmd',
+        'application/vnd.cinderella' => 'cdy',
+        'application/vnd.claymore' => 'cla',
+        'application/vnd.cloanto.rp9' => 'rp9',
+        'application/vnd.clonk.c4group' => 'c4g',
+        'application/vnd.cluetrust.cartomobile-config' => 'c11amc',
+        'application/vnd.cluetrust.cartomobile-config-pkg' => 'c11amz',
+        'application/vnd.commonspace' => 'csp',
+        'application/vnd.contact.cmsg' => 'cdbcmsg',
+        'application/vnd.cosmocaller' => 'cmc',
+        'application/vnd.crick.clicker' => 'clkx',
+        'application/vnd.crick.clicker.keyboard' => 'clkk',
+        'application/vnd.crick.clicker.palette' => 'clkp',
+        'application/vnd.crick.clicker.template' => 'clkt',
+        'application/vnd.crick.clicker.wordbank' => 'clkw',
+        'application/vnd.criticaltools.wbs+xml' => 'wbs',
+        'application/vnd.ctc-posml' => 'pml',
+        'application/vnd.cups-ppd' => 'ppd',
+        'application/vnd.curl.car' => 'car',
+        'application/vnd.curl.pcurl' => 'pcurl',
+        'application/vnd.dart' => 'dart',
+        'application/vnd.data-vision.rdz' => 'rdz',
+        'application/vnd.dece.data' => 'uvf',
+        'application/vnd.dece.ttml+xml' => 'uvt',
+        'application/vnd.dece.unspecified' => 'uvx',
+        'application/vnd.dece.zip' => 'uvz',
+        'application/vnd.denovo.fcselayout-link' => 'fe_launch',
+        'application/vnd.dna' => 'dna',
+        'application/vnd.dolby.mlp' => 'mlp',
+        'application/vnd.dpgraph' => 'dpg',
+        'application/vnd.dreamfactory' => 'dfac',
+        'application/vnd.ds-keypoint' => 'kpxx',
+        'application/vnd.dvb.ait' => 'ait',
+        'application/vnd.dvb.service' => 'svc',
+        'application/vnd.dynageo' => 'geo',
+        'application/vnd.ecowin.chart' => 'mag',
+        'application/vnd.enliven' => 'nml',
+        'application/vnd.epson.esf' => 'esf',
+        'application/vnd.epson.msf' => 'msf',
+        'application/vnd.epson.quickanime' => 'qam',
+        'application/vnd.epson.salt' => 'slt',
+        'application/vnd.epson.ssf' => 'ssf',
+        'application/vnd.eszigno3+xml' => 'es3',
+        'application/vnd.ezpix-album' => 'ez2',
+        'application/vnd.ezpix-package' => 'ez3',
+        'application/vnd.fdf' => 'fdf',
+        'application/vnd.fdsn.mseed' => 'mseed',
+        'application/vnd.fdsn.seed' => 'seed',
+        'application/vnd.flographit' => 'gph',
+        'application/vnd.fluxtime.clip' => 'ftc',
+        'application/vnd.framemaker' => 'fm',
+        'application/vnd.frogans.fnc' => 'fnc',
+        'application/vnd.frogans.ltf' => 'ltf',
+        'application/vnd.fsc.weblaunch' => 'fsc',
+        'application/vnd.fujitsu.oasys' => 'oas',
+        'application/vnd.fujitsu.oasys2' => 'oa2',
+        'application/vnd.fujitsu.oasys3' => 'oa3',
+        'application/vnd.fujitsu.oasysgp' => 'fg5',
+        'application/vnd.fujitsu.oasysprs' => 'bh2',
+        'application/vnd.fujixerox.ddd' => 'ddd',
+        'application/vnd.fujixerox.docuworks' => 'xdw',
+        'application/vnd.fujixerox.docuworks.binder' => 'xbd',
+        'application/vnd.fuzzysheet' => 'fzs',
+        'application/vnd.genomatix.tuxedo' => 'txd',
+        'application/vnd.geogebra.file' => 'ggb',
+        'application/vnd.geogebra.tool' => 'ggt',
+        'application/vnd.geometry-explorer' => 'gex',
+        'application/vnd.geonext' => 'gxt',
+        'application/vnd.geoplan' => 'g2w',
+        'application/vnd.geospace' => 'g3w',
+        'application/vnd.gmx' => 'gmx',
+        'application/vnd.google-earth.kml+xml' => 'kml',
+        'application/vnd.google-earth.kmz' => 'kmz',
+        'application/vnd.grafeq' => 'gqf',
+        'application/vnd.groove-account' => 'gac',
+        'application/vnd.groove-help' => 'ghf',
+        'application/vnd.groove-identity-message' => 'gim',
+        'application/vnd.groove-injector' => 'grv',
+        'application/vnd.groove-tool-message' => 'gtm',
+        'application/vnd.groove-tool-template' => 'tpl',
+        'application/vnd.groove-vcard' => 'vcg',
+        'application/vnd.hal+xml' => 'hal',
+        'application/vnd.handheld-entertainment+xml' => 'zmm',
+        'application/vnd.hbci' => 'hbci',
+        'application/vnd.hhe.lesson-player' => 'les',
+        'application/vnd.hp-hpgl' => 'hpgl',
+        'application/vnd.hp-hpid' => 'hpid',
+        'application/vnd.hp-hps' => 'hps',
+        'application/vnd.hp-jlyt' => 'jlt',
+        'application/vnd.hp-pcl' => 'pcl',
+        'application/vnd.hp-pclxl' => 'pclxl',
+        'application/vnd.hydrostatix.sof-data' => 'sfd-hdstx',
+        'application/vnd.ibm.minipay' => 'mpy',
+        'application/vnd.ibm.modcap' => 'afp',
+        'application/vnd.ibm.rights-management' => 'irm',
+        'application/vnd.ibm.secure-container' => 'sc',
+        'application/vnd.iccprofile' => 'icc',
+        'application/vnd.igloader' => 'igl',
+        'application/vnd.immervision-ivp' => 'ivp',
+        'application/vnd.immervision-ivu' => 'ivu',
+        'application/vnd.insors.igm' => 'igm',
+        'application/vnd.intercon.formnet' => 'xpw',
+        'application/vnd.intergeo' => 'i2g',
+        'application/vnd.intu.qbo' => 'qbo',
+        'application/vnd.intu.qfx' => 'qfx',
+        'application/vnd.ipunplugged.rcprofile' => 'rcprofile',
+        'application/vnd.irepository.package+xml' => 'irp',
+        'application/vnd.is-xpr' => 'xpr',
+        'application/vnd.isac.fcs' => 'fcs',
+        'application/vnd.jam' => 'jam',
+        'application/vnd.jcp.javame.midlet-rms' => 'rms',
+        'application/vnd.jisp' => 'jisp',
+        'application/vnd.joost.joda-archive' => 'joda',
+        'application/vnd.kahootz' => 'ktz',
+        'application/vnd.kde.karbon' => 'karbon',
+        'application/vnd.kde.kchart' => 'chrt',
+        'application/vnd.kde.kformula' => 'kfo',
+        'application/vnd.kde.kivio' => 'flw',
+        'application/vnd.kde.kontour' => 'kon',
+        'application/vnd.kde.kpresenter' => 'kpr',
+        'application/vnd.kde.kspread' => 'ksp',
+        'application/vnd.kde.kword' => 'kwd',
+        'application/vnd.kenameaapp' => 'htke',
+        'application/vnd.kidspiration' => 'kia',
+        'application/vnd.kinar' => 'kne',
+        'application/vnd.koan' => 'skp',
+        'application/vnd.kodak-descriptor' => 'sse',
+        'application/vnd.las.las+xml' => 'lasxml',
+        'application/vnd.llamagraphics.life-balance.desktop' => 'lbd',
+        'application/vnd.llamagraphics.life-balance.exchange+xml' => 'lbe',
+        'application/vnd.lotus-1-2-3' => '123',
+        'application/vnd.lotus-approach' => 'apr',
+        'application/vnd.lotus-freelance' => 'pre',
+        'application/vnd.lotus-notes' => 'nsf',
+        'application/vnd.lotus-organizer' => 'org',
+        'application/vnd.lotus-screencam' => 'scm',
+        'application/vnd.lotus-wordpro' => 'lwp',
+        'application/vnd.macports.portpkg' => 'portpkg',
+        'application/vnd.mcd' => 'mcd',
+        'application/vnd.medcalcdata' => 'mc1',
+        'application/vnd.mediastation.cdkey' => 'cdkey',
+        'application/vnd.mfer' => 'mwf',
+        'application/vnd.mfmp' => 'mfm',
+        'application/vnd.micrografx.flo' => 'flo',
+        'application/vnd.micrografx.igx' => 'igx',
+        'application/vnd.mif' => 'mif',
+        'application/vnd.mobius.daf' => 'daf',
+        'application/vnd.mobius.dis' => 'dis',
+        'application/vnd.mobius.mbk' => 'mbk',
+        'application/vnd.mobius.mqy' => 'mqy',
+        'application/vnd.mobius.msl' => 'msl',
+        'application/vnd.mobius.plc' => 'plc',
+        'application/vnd.mobius.txf' => 'txf',
+        'application/vnd.mophun.application' => 'mpn',
+        'application/vnd.mophun.certificate' => 'mpc',
+        'application/vnd.mozilla.xul+xml' => 'xul',
+        'application/vnd.ms-artgalry' => 'cil',
+        'application/vnd.ms-cab-compressed' => 'cab',
+        'application/vnd.ms-excel' => 'xls',
+        'application/vnd.ms-excel.addin.macroenabled.12' => 'xlam',
+        'application/vnd.ms-excel.sheet.binary.macroenabled.12' => 'xlsb',
+        'application/vnd.ms-excel.sheet.macroenabled.12' => 'xlsm',
+        'application/vnd.ms-excel.template.macroenabled.12' => 'xltm',
+        'application/vnd.ms-fontobject' => 'eot',
+        'application/vnd.ms-htmlhelp' => 'chm',
+        'application/vnd.ms-ims' => 'ims',
+        'application/vnd.ms-lrm' => 'lrm',
+        'application/vnd.ms-officetheme' => 'thmx',
+        'application/vnd.ms-pki.seccat' => 'cat',
+        'application/vnd.ms-pki.stl' => 'stl',
+        'application/vnd.ms-powerpoint' => 'ppt',
+        'application/vnd.ms-powerpoint.addin.macroenabled.12' => 'ppam',
+        'application/vnd.ms-powerpoint.presentation.macroenabled.12' => 'pptm',
+        'application/vnd.ms-powerpoint.slide.macroenabled.12' => 'sldm',
+        'application/vnd.ms-powerpoint.slideshow.macroenabled.12' => 'ppsm',
+        'application/vnd.ms-powerpoint.template.macroenabled.12' => 'potm',
+        'application/vnd.ms-project' => 'mpp',
+        'application/vnd.ms-word.document.macroenabled.12' => 'docm',
+        'application/vnd.ms-word.template.macroenabled.12' => 'dotm',
+        'application/vnd.ms-works' => 'wps',
+        'application/vnd.ms-wpl' => 'wpl',
+        'application/vnd.ms-xpsdocument' => 'xps',
+        'application/vnd.mseq' => 'mseq',
+        'application/vnd.musician' => 'mus',
+        'application/vnd.muvee.style' => 'msty',
+        'application/vnd.mynfc' => 'taglet',
+        'application/vnd.neurolanguage.nlu' => 'nlu',
+        'application/vnd.nitf' => 'ntf',
+        'application/vnd.noblenet-directory' => 'nnd',
+        'application/vnd.noblenet-sealer' => 'nns',
+        'application/vnd.noblenet-web' => 'nnw',
+        'application/vnd.nokia.n-gage.data' => 'ngdat',
+        'application/vnd.nokia.n-gage.symbian.install' => 'n-gage',
+        'application/vnd.nokia.radio-preset' => 'rpst',
+        'application/vnd.nokia.radio-presets' => 'rpss',
+        'application/vnd.novadigm.edm' => 'edm',
+        'application/vnd.novadigm.edx' => 'edx',
+        'application/vnd.novadigm.ext' => 'ext',
+        'application/vnd.oasis.opendocument.chart' => 'odc',
+        'application/vnd.oasis.opendocument.chart-template' => 'otc',
+        'application/vnd.oasis.opendocument.database' => 'odb',
+        'application/vnd.oasis.opendocument.formula' => 'odf',
+        'application/vnd.oasis.opendocument.formula-template' => 'odft',
+        'application/vnd.oasis.opendocument.graphics' => 'odg',
+        'application/vnd.oasis.opendocument.graphics-template' => 'otg',
+        'application/vnd.oasis.opendocument.image' => 'odi',
+        'application/vnd.oasis.opendocument.image-template' => 'oti',
+        'application/vnd.oasis.opendocument.presentation' => 'odp',
+        'application/vnd.oasis.opendocument.presentation-template' => 'otp',
+        'application/vnd.oasis.opendocument.spreadsheet' => 'ods',
+        'application/vnd.oasis.opendocument.spreadsheet-template' => 'ots',
+        'application/vnd.oasis.opendocument.text' => 'odt',
+        'application/vnd.oasis.opendocument.text-master' => 'odm',
+        'application/vnd.oasis.opendocument.text-template' => 'ott',
+        'application/vnd.oasis.opendocument.text-web' => 'oth',
+        'application/vnd.olpc-sugar' => 'xo',
+        'application/vnd.oma.dd2+xml' => 'dd2',
+        'application/vnd.openofficeorg.extension' => 'oxt',
+        'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx',
+        'application/vnd.openxmlformats-officedocument.presentationml.slide' => 'sldx',
+        'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => 'ppsx',
+        'application/vnd.openxmlformats-officedocument.presentationml.template' => 'potx',
+        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx',
+        'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => 'xltx',
+        'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx',
+        'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => 'dotx',
+        'application/vnd.osgeo.mapguide.package' => 'mgp',
+        'application/vnd.osgi.dp' => 'dp',
+        'application/vnd.osgi.subsystem' => 'esa',
+        'application/vnd.palm' => 'pdb',
+        'application/vnd.pawaafile' => 'paw',
+        'application/vnd.pg.format' => 'str',
+        'application/vnd.pg.osasli' => 'ei6',
+        'application/vnd.picsel' => 'efif',
+        'application/vnd.pmi.widget' => 'wg',
+        'application/vnd.pocketlearn' => 'plf',
+        'application/vnd.powerbuilder6' => 'pbd',
+        'application/vnd.previewsystems.box' => 'box',
+        'application/vnd.proteus.magazine' => 'mgz',
+        'application/vnd.publishare-delta-tree' => 'qps',
+        'application/vnd.pvi.ptid1' => 'ptid',
+        'application/vnd.quark.quarkxpress' => 'qxd',
+        'application/vnd.realvnc.bed' => 'bed',
+        'application/vnd.recordare.musicxml' => 'mxl',
+        'application/vnd.recordare.musicxml+xml' => 'musicxml',
+        'application/vnd.rig.cryptonote' => 'cryptonote',
+        'application/vnd.rim.cod' => 'cod',
+        'application/vnd.rn-realmedia' => 'rm',
+        'application/vnd.rn-realmedia-vbr' => 'rmvb',
+        'application/vnd.route66.link66+xml' => 'link66',
+        'application/vnd.sailingtracker.track' => 'st',
+        'application/vnd.seemail' => 'see',
+        'application/vnd.sema' => 'sema',
+        'application/vnd.semd' => 'semd',
+        'application/vnd.semf' => 'semf',
+        'application/vnd.shana.informed.formdata' => 'ifm',
+        'application/vnd.shana.informed.formtemplate' => 'itp',
+        'application/vnd.shana.informed.interchange' => 'iif',
+        'application/vnd.shana.informed.package' => 'ipk',
+        'application/vnd.simtech-mindmapper' => 'twd',
+        'application/vnd.smaf' => 'mmf',
+        'application/vnd.smart.teacher' => 'teacher',
+        'application/vnd.solent.sdkm+xml' => 'sdkm',
+        'application/vnd.spotfire.dxp' => 'dxp',
+        'application/vnd.spotfire.sfs' => 'sfs',
+        'application/vnd.stardivision.calc' => 'sdc',
+        'application/vnd.stardivision.draw' => 'sda',
+        'application/vnd.stardivision.impress' => 'sdd',
+        'application/vnd.stardivision.math' => 'smf',
+        'application/vnd.stardivision.writer' => 'sdw',
+        'application/vnd.stardivision.writer-global' => 'sgl',
+        'application/vnd.stepmania.package' => 'smzip',
+        'application/vnd.stepmania.stepchart' => 'sm',
+        'application/vnd.sun.xml.calc' => 'sxc',
+        'application/vnd.sun.xml.calc.template' => 'stc',
+        'application/vnd.sun.xml.draw' => 'sxd',
+        'application/vnd.sun.xml.draw.template' => 'std',
+        'application/vnd.sun.xml.impress' => 'sxi',
+        'application/vnd.sun.xml.impress.template' => 'sti',
+        'application/vnd.sun.xml.math' => 'sxm',
+        'application/vnd.sun.xml.writer' => 'sxw',
+        'application/vnd.sun.xml.writer.global' => 'sxg',
+        'application/vnd.sun.xml.writer.template' => 'stw',
+        'application/vnd.sus-calendar' => 'sus',
+        'application/vnd.svd' => 'svd',
+        'application/vnd.symbian.install' => 'sis',
+        'application/vnd.syncml+xml' => 'xsm',
+        'application/vnd.syncml.dm+wbxml' => 'bdm',
+        'application/vnd.syncml.dm+xml' => 'xdm',
+        'application/vnd.tao.intent-module-archive' => 'tao',
+        'application/vnd.tcpdump.pcap' => 'pcap',
+        'application/vnd.tmobile-livetv' => 'tmo',
+        'application/vnd.trid.tpt' => 'tpt',
+        'application/vnd.triscape.mxs' => 'mxs',
+        'application/vnd.trueapp' => 'tra',
+        'application/vnd.ufdl' => 'ufd',
+        'application/vnd.uiq.theme' => 'utz',
+        'application/vnd.umajin' => 'umj',
+        'application/vnd.unity' => 'unityweb',
+        'application/vnd.uoml+xml' => 'uoml',
+        'application/vnd.vcx' => 'vcx',
+        'application/vnd.visio' => 'vsd',
+        'application/vnd.visionary' => 'vis',
+        'application/vnd.vsf' => 'vsf',
+        'application/vnd.wap.wbxml' => 'wbxml',
+        'application/vnd.wap.wmlc' => 'wmlc',
+        'application/vnd.wap.wmlscriptc' => 'wmlsc',
+        'application/vnd.webturbo' => 'wtb',
+        'application/vnd.wolfram.player' => 'nbp',
+        'application/vnd.wordperfect' => 'wpd',
+        'application/vnd.wqd' => 'wqd',
+        'application/vnd.wt.stf' => 'stf',
+        'application/vnd.xara' => 'xar',
+        'application/vnd.xfdl' => 'xfdl',
+        'application/vnd.yamaha.hv-dic' => 'hvd',
+        'application/vnd.yamaha.hv-script' => 'hvs',
+        'application/vnd.yamaha.hv-voice' => 'hvp',
+        'application/vnd.yamaha.openscoreformat' => 'osf',
+        'application/vnd.yamaha.openscoreformat.osfpvg+xml' => 'osfpvg',
+        'application/vnd.yamaha.smaf-audio' => 'saf',
+        'application/vnd.yamaha.smaf-phrase' => 'spf',
+        'application/vnd.yellowriver-custom-menu' => 'cmp',
+        'application/vnd.zul' => 'zir',
+        'application/vnd.zzazz.deck+xml' => 'zaz',
+        'application/voicexml+xml' => 'vxml',
+        'application/widget' => 'wgt',
+        'application/winhlp' => 'hlp',
+        'application/wsdl+xml' => 'wsdl',
+        'application/wspolicy+xml' => 'wspolicy',
+        'application/x-7z-compressed' => '7z',
+        'application/x-abiword' => 'abw',
+        'application/x-ace-compressed' => 'ace',
+        'application/x-apple-diskimage' => 'dmg',
+        'application/x-authorware-bin' => 'aab',
+        'application/x-authorware-map' => 'aam',
+        'application/x-authorware-seg' => 'aas',
+        'application/x-bcpio' => 'bcpio',
+        'application/x-bittorrent' => 'torrent',
+        'application/x-blorb' => 'blb',
+        'application/x-bzip' => 'bz',
+        'application/x-bzip2' => 'bz2',
+        'application/x-cbr' => 'cbr',
+        'application/x-cdlink' => 'vcd',
+        'application/x-cfs-compressed' => 'cfs',
+        'application/x-chat' => 'chat',
+        'application/x-chess-pgn' => 'pgn',
+        'application/x-conference' => 'nsc',
+        'application/x-cpio' => 'cpio',
+        'application/x-csh' => 'csh',
+        'application/x-debian-package' => 'deb',
+        'application/x-dgc-compressed' => 'dgc',
+        'application/x-director' => 'dir',
+        'application/x-doom' => 'wad',
+        'application/x-dtbncx+xml' => 'ncx',
+        'application/x-dtbook+xml' => 'dtb',
+        'application/x-dtbresource+xml' => 'res',
+        'application/x-dvi' => 'dvi',
+        'application/x-envoy' => 'evy',
+        'application/x-eva' => 'eva',
+        'application/x-font-bdf' => 'bdf',
+        'application/x-font-ghostscript' => 'gsf',
+        'application/x-font-linux-psf' => 'psf',
+        'application/x-font-otf' => 'otf',
+        'application/x-font-pcf' => 'pcf',
+        'application/x-font-snf' => 'snf',
+        'application/x-font-ttf' => 'ttf',
+        'application/x-font-type1' => 'pfa',
+        'application/x-font-woff' => 'woff',
+        'application/x-freearc' => 'arc',
+        'application/x-futuresplash' => 'spl',
+        'application/x-gca-compressed' => 'gca',
+        'application/x-glulx' => 'ulx',
+        'application/x-gnumeric' => 'gnumeric',
+        'application/x-gramps-xml' => 'gramps',
+        'application/x-gtar' => 'gtar',
+        'application/x-hdf' => 'hdf',
+        'application/x-install-instructions' => 'install',
+        'application/x-iso9660-image' => 'iso',
+        'application/x-java-jnlp-file' => 'jnlp',
+        'application/x-latex' => 'latex',
+        'application/x-lzh-compressed' => 'lzh',
+        'application/x-mie' => 'mie',
+        'application/x-mobipocket-ebook' => 'prc',
+        'application/x-ms-application' => 'application',
+        'application/x-ms-shortcut' => 'lnk',
+        'application/x-ms-wmd' => 'wmd',
+        'application/x-ms-wmz' => 'wmz',
+        'application/x-ms-xbap' => 'xbap',
+        'application/x-msaccess' => 'mdb',
+        'application/x-msbinder' => 'obd',
+        'application/x-mscardfile' => 'crd',
+        'application/x-msclip' => 'clp',
+        'application/x-msdownload' => 'exe',
+        'application/x-msmediaview' => 'mvb',
+        'application/x-msmetafile' => 'wmf',
+        'application/x-msmoney' => 'mny',
+        'application/x-mspublisher' => 'pub',
+        'application/x-msschedule' => 'scd',
+        'application/x-msterminal' => 'trm',
+        'application/x-mswrite' => 'wri',
+        'application/x-netcdf' => 'nc',
+        'application/x-nzb' => 'nzb',
+        'application/x-pkcs12' => 'p12',
+        'application/x-pkcs7-certificates' => 'p7b',
+        'application/x-pkcs7-certreqresp' => 'p7r',
+        'application/x-rar-compressed' => 'rar',
+        'application/x-rar' => 'rar',
+        'application/x-research-info-systems' => 'ris',
+        'application/x-sh' => 'sh',
+        'application/x-shar' => 'shar',
+        'application/x-shockwave-flash' => 'swf',
+        'application/x-silverlight-app' => 'xap',
+        'application/x-sql' => 'sql',
+        'application/x-stuffit' => 'sit',
+        'application/x-stuffitx' => 'sitx',
+        'application/x-subrip' => 'srt',
+        'application/x-sv4cpio' => 'sv4cpio',
+        'application/x-sv4crc' => 'sv4crc',
+        'application/x-t3vm-image' => 't3',
+        'application/x-tads' => 'gam',
+        'application/x-tar' => 'tar',
+        'application/x-tcl' => 'tcl',
+        'application/x-tex' => 'tex',
+        'application/x-tex-tfm' => 'tfm',
+        'application/x-texinfo' => 'texinfo',
+        'application/x-tgif' => 'obj',
+        'application/x-ustar' => 'ustar',
+        'application/x-wais-source' => 'src',
+        'application/x-x509-ca-cert' => 'der',
+        'application/x-xfig' => 'fig',
+        'application/x-xliff+xml' => 'xlf',
+        'application/x-xpinstall' => 'xpi',
+        'application/x-xz' => 'xz',
+        'application/x-zip-compressed' => 'zip',
+        'application/x-zmachine' => 'z1',
+        'application/xaml+xml' => 'xaml',
+        'application/xcap-diff+xml' => 'xdf',
+        'application/xenc+xml' => 'xenc',
+        'application/xhtml+xml' => 'xhtml',
+        'application/xml' => 'xml',
+        'application/xml-dtd' => 'dtd',
+        'application/xop+xml' => 'xop',
+        'application/xproc+xml' => 'xpl',
+        'application/xslt+xml' => 'xslt',
+        'application/xspf+xml' => 'xspf',
+        'application/xv+xml' => 'mxml',
+        'application/yang' => 'yang',
+        'application/yin+xml' => 'yin',
+        'application/zip' => 'zip',
+        'audio/adpcm' => 'adp',
+        'audio/basic' => 'au',
+        'audio/midi' => 'mid',
+        'audio/mp4' => 'mp4a',
+        'audio/mpeg' => 'mpga',
+        'audio/ogg' => 'oga',
+        'audio/s3m' => 's3m',
+        'audio/silk' => 'sil',
+        'audio/vnd.dece.audio' => 'uva',
+        'audio/vnd.digital-winds' => 'eol',
+        'audio/vnd.dra' => 'dra',
+        'audio/vnd.dts' => 'dts',
+        'audio/vnd.dts.hd' => 'dtshd',
+        'audio/vnd.lucent.voice' => 'lvp',
+        'audio/vnd.ms-playready.media.pya' => 'pya',
+        'audio/vnd.nuera.ecelp4800' => 'ecelp4800',
+        'audio/vnd.nuera.ecelp7470' => 'ecelp7470',
+        'audio/vnd.nuera.ecelp9600' => 'ecelp9600',
+        'audio/vnd.rip' => 'rip',
+        'audio/webm' => 'weba',
+        'audio/x-aac' => 'aac',
+        'audio/x-aiff' => 'aif',
+        'audio/x-caf' => 'caf',
+        'audio/x-flac' => 'flac',
+        'audio/x-matroska' => 'mka',
+        'audio/x-mpegurl' => 'm3u',
+        'audio/x-ms-wax' => 'wax',
+        'audio/x-ms-wma' => 'wma',
+        'audio/x-pn-realaudio' => 'ram',
+        'audio/x-pn-realaudio-plugin' => 'rmp',
+        'audio/x-wav' => 'wav',
+        'audio/xm' => 'xm',
+        'chemical/x-cdx' => 'cdx',
+        'chemical/x-cif' => 'cif',
+        'chemical/x-cmdf' => 'cmdf',
+        'chemical/x-cml' => 'cml',
+        'chemical/x-csml' => 'csml',
+        'chemical/x-xyz' => 'xyz',
+        'image/bmp' => 'bmp',
+        'image/x-ms-bmp' => 'bmp',
+        'image/cgm' => 'cgm',
+        'image/g3fax' => 'g3',
+        'image/gif' => 'gif',
+        'image/ief' => 'ief',
+        'image/jpeg' => 'jpeg',
+        'image/pjpeg' => 'jpeg',
+        'image/ktx' => 'ktx',
+        'image/png' => 'png',
+        'image/prs.btif' => 'btif',
+        'image/sgi' => 'sgi',
+        'image/svg+xml' => 'svg',
+        'image/tiff' => 'tiff',
+        'image/vnd.adobe.photoshop' => 'psd',
+        'image/vnd.dece.graphic' => 'uvi',
+        'image/vnd.dvb.subtitle' => 'sub',
+        'image/vnd.djvu' => 'djvu',
+        'image/vnd.dwg' => 'dwg',
+        'image/vnd.dxf' => 'dxf',
+        'image/vnd.fastbidsheet' => 'fbs',
+        'image/vnd.fpx' => 'fpx',
+        'image/vnd.fst' => 'fst',
+        'image/vnd.fujixerox.edmics-mmr' => 'mmr',
+        'image/vnd.fujixerox.edmics-rlc' => 'rlc',
+        'image/vnd.ms-modi' => 'mdi',
+        'image/vnd.ms-photo' => 'wdp',
+        'image/vnd.net-fpx' => 'npx',
+        'image/vnd.wap.wbmp' => 'wbmp',
+        'image/vnd.xiff' => 'xif',
+        'image/webp' => 'webp',
+        'image/x-3ds' => '3ds',
+        'image/x-cmu-raster' => 'ras',
+        'image/x-cmx' => 'cmx',
+        'image/x-freehand' => 'fh',
+        'image/x-icon' => 'ico',
+        'image/x-mrsid-image' => 'sid',
+        'image/x-pcx' => 'pcx',
+        'image/x-pict' => 'pic',
+        'image/x-portable-anymap' => 'pnm',
+        'image/x-portable-bitmap' => 'pbm',
+        'image/x-portable-graymap' => 'pgm',
+        'image/x-portable-pixmap' => 'ppm',
+        'image/x-rgb' => 'rgb',
+        'image/x-tga' => 'tga',
+        'image/x-xbitmap' => 'xbm',
+        'image/x-xpixmap' => 'xpm',
+        'image/x-xwindowdump' => 'xwd',
+        'message/rfc822' => 'eml',
+        'model/iges' => 'igs',
+        'model/mesh' => 'msh',
+        'model/vnd.collada+xml' => 'dae',
+        'model/vnd.dwf' => 'dwf',
+        'model/vnd.gdl' => 'gdl',
+        'model/vnd.gtw' => 'gtw',
+        'model/vnd.mts' => 'mts',
+        'model/vnd.vtu' => 'vtu',
+        'model/vrml' => 'wrl',
+        'model/x3d+binary' => 'x3db',
+        'model/x3d+vrml' => 'x3dv',
+        'model/x3d+xml' => 'x3d',
+        'text/cache-manifest' => 'appcache',
+        'text/calendar' => 'ics',
+        'text/css' => 'css',
+        'text/csv' => 'csv',
+        'text/html' => 'html',
+        'text/n3' => 'n3',
+        'text/plain' => 'txt',
+        'text/prs.lines.tag' => 'dsc',
+        'text/richtext' => 'rtx',
+        'text/rtf' => 'rtf',
+        'text/sgml' => 'sgml',
+        'text/tab-separated-values' => 'tsv',
+        'text/troff' => 't',
+        'text/turtle' => 'ttl',
+        'text/uri-list' => 'uri',
+        'text/vcard' => 'vcard',
+        'text/vnd.curl' => 'curl',
+        'text/vnd.curl.dcurl' => 'dcurl',
+        'text/vnd.curl.scurl' => 'scurl',
+        'text/vnd.curl.mcurl' => 'mcurl',
+        'text/vnd.dvb.subtitle' => 'sub',
+        'text/vnd.fly' => 'fly',
+        'text/vnd.fmi.flexstor' => 'flx',
+        'text/vnd.graphviz' => 'gv',
+        'text/vnd.in3d.3dml' => '3dml',
+        'text/vnd.in3d.spot' => 'spot',
+        'text/vnd.sun.j2me.app-descriptor' => 'jad',
+        'text/vnd.wap.wml' => 'wml',
+        'text/vnd.wap.wmlscript' => 'wmls',
+        'text/vtt' => 'vtt',
+        'text/x-asm' => 's',
+        'text/x-c' => 'c',
+        'text/x-fortran' => 'f',
+        'text/x-pascal' => 'p',
+        'text/x-java-source' => 'java',
+        'text/x-opml' => 'opml',
+        'text/x-nfo' => 'nfo',
+        'text/x-setext' => 'etx',
+        'text/x-sfv' => 'sfv',
+        'text/x-uuencode' => 'uu',
+        'text/x-vcalendar' => 'vcs',
+        'text/x-vcard' => 'vcf',
+        'video/3gpp' => '3gp',
+        'video/3gpp2' => '3g2',
+        'video/h261' => 'h261',
+        'video/h263' => 'h263',
+        'video/h264' => 'h264',
+        'video/jpeg' => 'jpgv',
+        'video/jpm' => 'jpm',
+        'video/mj2' => 'mj2',
+        'video/mp4' => 'mp4',
+        'video/mpeg' => 'mpeg',
+        'video/ogg' => 'ogv',
+        'video/quicktime' => 'qt',
+        'video/vnd.dece.hd' => 'uvh',
+        'video/vnd.dece.mobile' => 'uvm',
+        'video/vnd.dece.pd' => 'uvp',
+        'video/vnd.dece.sd' => 'uvs',
+        'video/vnd.dece.video' => 'uvv',
+        'video/vnd.dvb.file' => 'dvb',
+        'video/vnd.fvt' => 'fvt',
+        'video/vnd.mpegurl' => 'mxu',
+        'video/vnd.ms-playready.media.pyv' => 'pyv',
+        'video/vnd.uvvu.mp4' => 'uvu',
+        'video/vnd.vivo' => 'viv',
+        'video/webm' => 'webm',
+        'video/x-f4v' => 'f4v',
+        'video/x-fli' => 'fli',
+        'video/x-flv' => 'flv',
+        'video/x-m4v' => 'm4v',
+        'video/x-matroska' => 'mkv',
+        'video/x-mng' => 'mng',
+        'video/x-ms-asf' => 'asf',
+        'video/x-ms-vob' => 'vob',
+        'video/x-ms-wm' => 'wm',
+        'video/x-ms-wmv' => 'wmv',
+        'video/x-ms-wmx' => 'wmx',
+        'video/x-ms-wvx' => 'wvx',
+        'video/x-msvideo' => 'avi',
+        'video/x-sgi-movie' => 'movie',
+        'video/x-smv' => 'smv',
+        'x-conference/x-cooltalk' => 'ice',
+    );
+
+    /**
+     * {@inheritdoc}
+     */
+    public function guess($mimeType)
+    {
+        return isset($this->defaultExtensions[$mimeType]) ? $this->defaultExtensions[$mimeType] : null;
+    }
+}
diff --git a/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php b/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php
new file mode 100644
index 0000000000000000000000000000000000000000..e3ef45ef672cfbd48e4a7c76974bc31639235678
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php
@@ -0,0 +1,142 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\MimeType;
+
+use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
+use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
+
+/**
+ * A singleton mime type guesser.
+ *
+ * By default, all mime type guessers provided by the framework are installed
+ * (if available on the current OS/PHP setup).
+ *
+ * You can register custom guessers by calling the register() method on the
+ * singleton instance. Custom guessers are always called before any default ones.
+ *
+ *     $guesser = MimeTypeGuesser::getInstance();
+ *     $guesser->register(new MyCustomMimeTypeGuesser());
+ *
+ * If you want to change the order of the default guessers, just re-register your
+ * preferred one as a custom one. The last registered guesser is preferred over
+ * previously registered ones.
+ *
+ * Re-registering a built-in guesser also allows you to configure it:
+ *
+ *     $guesser = MimeTypeGuesser::getInstance();
+ *     $guesser->register(new FileinfoMimeTypeGuesser('/path/to/magic/file'));
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class MimeTypeGuesser implements MimeTypeGuesserInterface
+{
+    /**
+     * The singleton instance.
+     *
+     * @var MimeTypeGuesser
+     */
+    private static $instance = null;
+
+    /**
+     * All registered MimeTypeGuesserInterface instances.
+     *
+     * @var array
+     */
+    protected $guessers = array();
+
+    /**
+     * Returns the singleton instance.
+     *
+     * @return self
+     */
+    public static function getInstance()
+    {
+        if (null === self::$instance) {
+            self::$instance = new self();
+        }
+
+        return self::$instance;
+    }
+
+    /**
+     * Resets the singleton instance.
+     */
+    public static function reset()
+    {
+        self::$instance = null;
+    }
+
+    /**
+     * Registers all natively provided mime type guessers.
+     */
+    private function __construct()
+    {
+        if (FileBinaryMimeTypeGuesser::isSupported()) {
+            $this->register(new FileBinaryMimeTypeGuesser());
+        }
+
+        if (FileinfoMimeTypeGuesser::isSupported()) {
+            $this->register(new FileinfoMimeTypeGuesser());
+        }
+    }
+
+    /**
+     * Registers a new mime type guesser.
+     *
+     * When guessing, this guesser is preferred over previously registered ones.
+     */
+    public function register(MimeTypeGuesserInterface $guesser)
+    {
+        array_unshift($this->guessers, $guesser);
+    }
+
+    /**
+     * Tries to guess the mime type of the given file.
+     *
+     * The file is passed to each registered mime type guesser in reverse order
+     * of their registration (last registered is queried first). Once a guesser
+     * returns a value that is not NULL, this method terminates and returns the
+     * value.
+     *
+     * @param string $path The path to the file
+     *
+     * @return string The mime type or NULL, if none could be guessed
+     *
+     * @throws \LogicException
+     * @throws FileNotFoundException
+     * @throws AccessDeniedException
+     */
+    public function guess($path)
+    {
+        if (!is_file($path)) {
+            throw new FileNotFoundException($path);
+        }
+
+        if (!is_readable($path)) {
+            throw new AccessDeniedException($path);
+        }
+
+        if (!$this->guessers) {
+            $msg = 'Unable to guess the mime type as no guessers are available';
+            if (!FileinfoMimeTypeGuesser::isSupported()) {
+                $msg .= ' (Did you enable the php_fileinfo extension?)';
+            }
+            throw new \LogicException($msg);
+        }
+
+        foreach ($this->guessers as $guesser) {
+            if (null !== $mimeType = $guesser->guess($path)) {
+                return $mimeType;
+            }
+        }
+    }
+}
diff --git a/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php b/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..f8c3ad2289cc93fe77ddb2af14ff40e0535c4940
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php
@@ -0,0 +1,35 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\MimeType;
+
+use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
+use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
+
+/**
+ * Guesses the mime type of a file.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+interface MimeTypeGuesserInterface
+{
+    /**
+     * Guesses the mime type of the file with the given path.
+     *
+     * @param string $path The path to the file
+     *
+     * @return string The mime type or NULL, if none could be guessed
+     *
+     * @throws FileNotFoundException If the file does not exist
+     * @throws AccessDeniedException If the file could not be read
+     */
+    public function guess($path);
+}
diff --git a/vendor/symfony/http-foundation/File/Stream.php b/vendor/symfony/http-foundation/File/Stream.php
new file mode 100644
index 0000000000000000000000000000000000000000..69ae74c110bb8681afc171e300e7b2cf47b98384
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/Stream.php
@@ -0,0 +1,28 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File;
+
+/**
+ * A PHP stream of unknown size.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class Stream extends File
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function getSize()
+    {
+        return false;
+    }
+}
diff --git a/vendor/symfony/http-foundation/File/UploadedFile.php b/vendor/symfony/http-foundation/File/UploadedFile.php
new file mode 100644
index 0000000000000000000000000000000000000000..082d8d534e17a361ff910d9d32d230fae75cb6eb
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/UploadedFile.php
@@ -0,0 +1,266 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File;
+
+use Symfony\Component\HttpFoundation\File\Exception\FileException;
+use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
+use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser;
+
+/**
+ * A file uploaded through a form.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ * @author Florian Eckerstorfer <florian@eckerstorfer.org>
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class UploadedFile extends File
+{
+    private $test = false;
+    private $originalName;
+    private $mimeType;
+    private $size;
+    private $error;
+
+    /**
+     * Accepts the information of the uploaded file as provided by the PHP global $_FILES.
+     *
+     * The file object is only created when the uploaded file is valid (i.e. when the
+     * isValid() method returns true). Otherwise the only methods that could be called
+     * on an UploadedFile instance are:
+     *
+     *   * getClientOriginalName,
+     *   * getClientMimeType,
+     *   * isValid,
+     *   * getError.
+     *
+     * Calling any other method on an non-valid instance will cause an unpredictable result.
+     *
+     * @param string      $path         The full temporary path to the file
+     * @param string      $originalName The original file name of the uploaded file
+     * @param string|null $mimeType     The type of the file as provided by PHP; null defaults to application/octet-stream
+     * @param int|null    $size         The file size provided by the uploader
+     * @param int|null    $error        The error constant of the upload (one of PHP's UPLOAD_ERR_XXX constants); null defaults to UPLOAD_ERR_OK
+     * @param bool        $test         Whether the test mode is active
+     *                                  Local files are used in test mode hence the code should not enforce HTTP uploads
+     *
+     * @throws FileException         If file_uploads is disabled
+     * @throws FileNotFoundException If the file does not exist
+     */
+    public function __construct($path, $originalName, $mimeType = null, $size = null, $error = null, $test = false)
+    {
+        $this->originalName = $this->getName($originalName);
+        $this->mimeType = $mimeType ?: 'application/octet-stream';
+        $this->size = $size;
+        $this->error = $error ?: UPLOAD_ERR_OK;
+        $this->test = (bool) $test;
+
+        parent::__construct($path, UPLOAD_ERR_OK === $this->error);
+    }
+
+    /**
+     * Returns the original file name.
+     *
+     * It is extracted from the request from which the file has been uploaded.
+     * Then it should not be considered as a safe value.
+     *
+     * @return string|null The original name
+     */
+    public function getClientOriginalName()
+    {
+        return $this->originalName;
+    }
+
+    /**
+     * Returns the original file extension.
+     *
+     * It is extracted from the original file name that was uploaded.
+     * Then it should not be considered as a safe value.
+     *
+     * @return string The extension
+     */
+    public function getClientOriginalExtension()
+    {
+        return pathinfo($this->originalName, PATHINFO_EXTENSION);
+    }
+
+    /**
+     * Returns the file mime type.
+     *
+     * The client mime type is extracted from the request from which the file
+     * was uploaded, so it should not be considered as a safe value.
+     *
+     * For a trusted mime type, use getMimeType() instead (which guesses the mime
+     * type based on the file content).
+     *
+     * @return string|null The mime type
+     *
+     * @see getMimeType()
+     */
+    public function getClientMimeType()
+    {
+        return $this->mimeType;
+    }
+
+    /**
+     * Returns the extension based on the client mime type.
+     *
+     * If the mime type is unknown, returns null.
+     *
+     * This method uses the mime type as guessed by getClientMimeType()
+     * to guess the file extension. As such, the extension returned
+     * by this method cannot be trusted.
+     *
+     * For a trusted extension, use guessExtension() instead (which guesses
+     * the extension based on the guessed mime type for the file).
+     *
+     * @return string|null The guessed extension or null if it cannot be guessed
+     *
+     * @see guessExtension()
+     * @see getClientMimeType()
+     */
+    public function guessClientExtension()
+    {
+        $type = $this->getClientMimeType();
+        $guesser = ExtensionGuesser::getInstance();
+
+        return $guesser->guess($type);
+    }
+
+    /**
+     * Returns the file size.
+     *
+     * It is extracted from the request from which the file has been uploaded.
+     * Then it should not be considered as a safe value.
+     *
+     * @return int|null The file size
+     */
+    public function getClientSize()
+    {
+        return $this->size;
+    }
+
+    /**
+     * Returns the upload error.
+     *
+     * If the upload was successful, the constant UPLOAD_ERR_OK is returned.
+     * Otherwise one of the other UPLOAD_ERR_XXX constants is returned.
+     *
+     * @return int The upload error
+     */
+    public function getError()
+    {
+        return $this->error;
+    }
+
+    /**
+     * Returns whether the file was uploaded successfully.
+     *
+     * @return bool True if the file has been uploaded with HTTP and no error occurred
+     */
+    public function isValid()
+    {
+        $isOk = UPLOAD_ERR_OK === $this->error;
+
+        return $this->test ? $isOk : $isOk && is_uploaded_file($this->getPathname());
+    }
+
+    /**
+     * Moves the file to a new location.
+     *
+     * @param string $directory The destination folder
+     * @param string $name      The new file name
+     *
+     * @return File A File object representing the new file
+     *
+     * @throws FileException if, for any reason, the file could not have been moved
+     */
+    public function move($directory, $name = null)
+    {
+        if ($this->isValid()) {
+            if ($this->test) {
+                return parent::move($directory, $name);
+            }
+
+            $target = $this->getTargetFile($directory, $name);
+
+            if (!@move_uploaded_file($this->getPathname(), $target)) {
+                $error = error_get_last();
+                throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error['message'])));
+            }
+
+            @chmod($target, 0666 & ~umask());
+
+            return $target;
+        }
+
+        throw new FileException($this->getErrorMessage());
+    }
+
+    /**
+     * Returns the maximum size of an uploaded file as configured in php.ini.
+     *
+     * @return int The maximum size of an uploaded file in bytes
+     */
+    public static function getMaxFilesize()
+    {
+        $iniMax = strtolower(ini_get('upload_max_filesize'));
+
+        if ('' === $iniMax) {
+            return PHP_INT_MAX;
+        }
+
+        $max = ltrim($iniMax, '+');
+        if (0 === strpos($max, '0x')) {
+            $max = intval($max, 16);
+        } elseif (0 === strpos($max, '0')) {
+            $max = intval($max, 8);
+        } else {
+            $max = (int) $max;
+        }
+
+        switch (substr($iniMax, -1)) {
+            case 't': $max *= 1024;
+            // no break
+            case 'g': $max *= 1024;
+            // no break
+            case 'm': $max *= 1024;
+            // no break
+            case 'k': $max *= 1024;
+        }
+
+        return $max;
+    }
+
+    /**
+     * Returns an informative upload error message.
+     *
+     * @return string The error message regarding the specified error code
+     */
+    public function getErrorMessage()
+    {
+        static $errors = array(
+            UPLOAD_ERR_INI_SIZE => 'The file "%s" exceeds your upload_max_filesize ini directive (limit is %d KiB).',
+            UPLOAD_ERR_FORM_SIZE => 'The file "%s" exceeds the upload limit defined in your form.',
+            UPLOAD_ERR_PARTIAL => 'The file "%s" was only partially uploaded.',
+            UPLOAD_ERR_NO_FILE => 'No file was uploaded.',
+            UPLOAD_ERR_CANT_WRITE => 'The file "%s" could not be written on disk.',
+            UPLOAD_ERR_NO_TMP_DIR => 'File could not be uploaded: missing temporary directory.',
+            UPLOAD_ERR_EXTENSION => 'File upload was stopped by a PHP extension.',
+        );
+
+        $errorCode = $this->error;
+        $maxFilesize = UPLOAD_ERR_INI_SIZE === $errorCode ? self::getMaxFilesize() / 1024 : 0;
+        $message = isset($errors[$errorCode]) ? $errors[$errorCode] : 'The file "%s" was not uploaded due to an unknown error.';
+
+        return sprintf($message, $this->getClientOriginalName(), $maxFilesize);
+    }
+}
diff --git a/vendor/symfony/http-foundation/FileBag.php b/vendor/symfony/http-foundation/FileBag.php
new file mode 100644
index 0000000000000000000000000000000000000000..5edd0e6210c52551e2500591514e8e703fa74c66
--- /dev/null
+++ b/vendor/symfony/http-foundation/FileBag.php
@@ -0,0 +1,144 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+use Symfony\Component\HttpFoundation\File\UploadedFile;
+
+/**
+ * FileBag is a container for uploaded files.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Bulat Shakirzyanov <mallluhuct@gmail.com>
+ */
+class FileBag extends ParameterBag
+{
+    private static $fileKeys = array('error', 'name', 'size', 'tmp_name', 'type');
+
+    /**
+     * @param array $parameters An array of HTTP files
+     */
+    public function __construct(array $parameters = array())
+    {
+        $this->replace($parameters);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function replace(array $files = array())
+    {
+        $this->parameters = array();
+        $this->add($files);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function set($key, $value)
+    {
+        if (!is_array($value) && !$value instanceof UploadedFile) {
+            throw new \InvalidArgumentException('An uploaded file must be an array or an instance of UploadedFile.');
+        }
+
+        parent::set($key, $this->convertFileInformation($value));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function add(array $files = array())
+    {
+        foreach ($files as $key => $file) {
+            $this->set($key, $file);
+        }
+    }
+
+    /**
+     * Converts uploaded files to UploadedFile instances.
+     *
+     * @param array|UploadedFile $file A (multi-dimensional) array of uploaded file information
+     *
+     * @return UploadedFile[]|UploadedFile|null A (multi-dimensional) array of UploadedFile instances
+     */
+    protected function convertFileInformation($file)
+    {
+        if ($file instanceof UploadedFile) {
+            return $file;
+        }
+
+        $file = $this->fixPhpFilesArray($file);
+        if (is_array($file)) {
+            $keys = array_keys($file);
+            sort($keys);
+
+            if ($keys == self::$fileKeys) {
+                if (UPLOAD_ERR_NO_FILE == $file['error']) {
+                    $file = null;
+                } else {
+                    $file = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['size'], $file['error']);
+                }
+            } else {
+                $file = array_map(array($this, 'convertFileInformation'), $file);
+                if (array_keys($keys) === $keys) {
+                    $file = array_filter($file);
+                }
+            }
+        }
+
+        return $file;
+    }
+
+    /**
+     * Fixes a malformed PHP $_FILES array.
+     *
+     * PHP has a bug that the format of the $_FILES array differs, depending on
+     * whether the uploaded file fields had normal field names or array-like
+     * field names ("normal" vs. "parent[child]").
+     *
+     * This method fixes the array to look like the "normal" $_FILES array.
+     *
+     * It's safe to pass an already converted array, in which case this method
+     * just returns the original array unmodified.
+     *
+     * @return array
+     */
+    protected function fixPhpFilesArray($data)
+    {
+        if (!is_array($data)) {
+            return $data;
+        }
+
+        $keys = array_keys($data);
+        sort($keys);
+
+        if (self::$fileKeys != $keys || !isset($data['name']) || !is_array($data['name'])) {
+            return $data;
+        }
+
+        $files = $data;
+        foreach (self::$fileKeys as $k) {
+            unset($files[$k]);
+        }
+
+        foreach ($data['name'] as $key => $name) {
+            $files[$key] = $this->fixPhpFilesArray(array(
+                'error' => $data['error'][$key],
+                'name' => $name,
+                'type' => $data['type'][$key],
+                'tmp_name' => $data['tmp_name'][$key],
+                'size' => $data['size'][$key],
+            ));
+        }
+
+        return $files;
+    }
+}
diff --git a/vendor/symfony/http-foundation/HeaderBag.php b/vendor/symfony/http-foundation/HeaderBag.php
new file mode 100644
index 0000000000000000000000000000000000000000..7aaa52ae56c11e86ac5f3e0e53e5257677927f11
--- /dev/null
+++ b/vendor/symfony/http-foundation/HeaderBag.php
@@ -0,0 +1,331 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * HeaderBag is a container for HTTP headers.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class HeaderBag implements \IteratorAggregate, \Countable
+{
+    protected $headers = array();
+    protected $cacheControl = array();
+
+    /**
+     * @param array $headers An array of HTTP headers
+     */
+    public function __construct(array $headers = array())
+    {
+        foreach ($headers as $key => $values) {
+            $this->set($key, $values);
+        }
+    }
+
+    /**
+     * Returns the headers as a string.
+     *
+     * @return string The headers
+     */
+    public function __toString()
+    {
+        if (!$headers = $this->all()) {
+            return '';
+        }
+
+        ksort($headers);
+        $max = max(array_map('strlen', array_keys($headers))) + 1;
+        $content = '';
+        foreach ($headers as $name => $values) {
+            $name = implode('-', array_map('ucfirst', explode('-', $name)));
+            foreach ($values as $value) {
+                $content .= sprintf("%-{$max}s %s\r\n", $name.':', $value);
+            }
+        }
+
+        return $content;
+    }
+
+    /**
+     * Returns the headers.
+     *
+     * @return array An array of headers
+     */
+    public function all()
+    {
+        return $this->headers;
+    }
+
+    /**
+     * Returns the parameter keys.
+     *
+     * @return array An array of parameter keys
+     */
+    public function keys()
+    {
+        return array_keys($this->all());
+    }
+
+    /**
+     * Replaces the current HTTP headers by a new set.
+     *
+     * @param array $headers An array of HTTP headers
+     */
+    public function replace(array $headers = array())
+    {
+        $this->headers = array();
+        $this->add($headers);
+    }
+
+    /**
+     * Adds new headers the current HTTP headers set.
+     *
+     * @param array $headers An array of HTTP headers
+     */
+    public function add(array $headers)
+    {
+        foreach ($headers as $key => $values) {
+            $this->set($key, $values);
+        }
+    }
+
+    /**
+     * Returns a header value by name.
+     *
+     * @param string          $key     The header name
+     * @param string|string[] $default The default value
+     * @param bool            $first   Whether to return the first value or all header values
+     *
+     * @return string|string[] The first header value or default value if $first is true, an array of values otherwise
+     */
+    public function get($key, $default = null, $first = true)
+    {
+        $key = str_replace('_', '-', strtolower($key));
+        $headers = $this->all();
+
+        if (!array_key_exists($key, $headers)) {
+            if (null === $default) {
+                return $first ? null : array();
+            }
+
+            return $first ? $default : array($default);
+        }
+
+        if ($first) {
+            return \count($headers[$key]) ? $headers[$key][0] : $default;
+        }
+
+        return $headers[$key];
+    }
+
+    /**
+     * Sets a header by name.
+     *
+     * @param string          $key     The key
+     * @param string|string[] $values  The value or an array of values
+     * @param bool            $replace Whether to replace the actual value or not (true by default)
+     */
+    public function set($key, $values, $replace = true)
+    {
+        $key = str_replace('_', '-', strtolower($key));
+
+        if (\is_array($values)) {
+            $values = array_values($values);
+
+            if (true === $replace || !isset($this->headers[$key])) {
+                $this->headers[$key] = $values;
+            } else {
+                $this->headers[$key] = array_merge($this->headers[$key], $values);
+            }
+        } else {
+            if (true === $replace || !isset($this->headers[$key])) {
+                $this->headers[$key] = array($values);
+            } else {
+                $this->headers[$key][] = $values;
+            }
+        }
+
+        if ('cache-control' === $key) {
+            $this->cacheControl = $this->parseCacheControl(implode(', ', $this->headers[$key]));
+        }
+    }
+
+    /**
+     * Returns true if the HTTP header is defined.
+     *
+     * @param string $key The HTTP header
+     *
+     * @return bool true if the parameter exists, false otherwise
+     */
+    public function has($key)
+    {
+        return array_key_exists(str_replace('_', '-', strtolower($key)), $this->all());
+    }
+
+    /**
+     * Returns true if the given HTTP header contains the given value.
+     *
+     * @param string $key   The HTTP header name
+     * @param string $value The HTTP value
+     *
+     * @return bool true if the value is contained in the header, false otherwise
+     */
+    public function contains($key, $value)
+    {
+        return in_array($value, $this->get($key, null, false));
+    }
+
+    /**
+     * Removes a header.
+     *
+     * @param string $key The HTTP header name
+     */
+    public function remove($key)
+    {
+        $key = str_replace('_', '-', strtolower($key));
+
+        unset($this->headers[$key]);
+
+        if ('cache-control' === $key) {
+            $this->cacheControl = array();
+        }
+    }
+
+    /**
+     * Returns the HTTP header value converted to a date.
+     *
+     * @param string    $key     The parameter key
+     * @param \DateTime $default The default value
+     *
+     * @return null|\DateTime The parsed DateTime or the default value if the header does not exist
+     *
+     * @throws \RuntimeException When the HTTP header is not parseable
+     */
+    public function getDate($key, \DateTime $default = null)
+    {
+        if (null === $value = $this->get($key)) {
+            return $default;
+        }
+
+        if (false === $date = \DateTime::createFromFormat(DATE_RFC2822, $value)) {
+            throw new \RuntimeException(sprintf('The %s HTTP header is not parseable (%s).', $key, $value));
+        }
+
+        return $date;
+    }
+
+    /**
+     * Adds a custom Cache-Control directive.
+     *
+     * @param string $key   The Cache-Control directive name
+     * @param mixed  $value The Cache-Control directive value
+     */
+    public function addCacheControlDirective($key, $value = true)
+    {
+        $this->cacheControl[$key] = $value;
+
+        $this->set('Cache-Control', $this->getCacheControlHeader());
+    }
+
+    /**
+     * Returns true if the Cache-Control directive is defined.
+     *
+     * @param string $key The Cache-Control directive
+     *
+     * @return bool true if the directive exists, false otherwise
+     */
+    public function hasCacheControlDirective($key)
+    {
+        return array_key_exists($key, $this->cacheControl);
+    }
+
+    /**
+     * Returns a Cache-Control directive value by name.
+     *
+     * @param string $key The directive name
+     *
+     * @return mixed|null The directive value if defined, null otherwise
+     */
+    public function getCacheControlDirective($key)
+    {
+        return array_key_exists($key, $this->cacheControl) ? $this->cacheControl[$key] : null;
+    }
+
+    /**
+     * Removes a Cache-Control directive.
+     *
+     * @param string $key The Cache-Control directive
+     */
+    public function removeCacheControlDirective($key)
+    {
+        unset($this->cacheControl[$key]);
+
+        $this->set('Cache-Control', $this->getCacheControlHeader());
+    }
+
+    /**
+     * Returns an iterator for headers.
+     *
+     * @return \ArrayIterator An \ArrayIterator instance
+     */
+    public function getIterator()
+    {
+        return new \ArrayIterator($this->headers);
+    }
+
+    /**
+     * Returns the number of headers.
+     *
+     * @return int The number of headers
+     */
+    public function count()
+    {
+        return count($this->headers);
+    }
+
+    protected function getCacheControlHeader()
+    {
+        $parts = array();
+        ksort($this->cacheControl);
+        foreach ($this->cacheControl as $key => $value) {
+            if (true === $value) {
+                $parts[] = $key;
+            } else {
+                if (preg_match('#[^a-zA-Z0-9._-]#', $value)) {
+                    $value = '"'.$value.'"';
+                }
+
+                $parts[] = "$key=$value";
+            }
+        }
+
+        return implode(', ', $parts);
+    }
+
+    /**
+     * Parses a Cache-Control HTTP header.
+     *
+     * @param string $header The value of the Cache-Control HTTP header
+     *
+     * @return array An array representing the attribute values
+     */
+    protected function parseCacheControl($header)
+    {
+        $cacheControl = array();
+        preg_match_all('#([a-zA-Z][a-zA-Z_-]*)\s*(?:=(?:"([^"]*)"|([^ \t",;]*)))?#', $header, $matches, PREG_SET_ORDER);
+        foreach ($matches as $match) {
+            $cacheControl[strtolower($match[1])] = isset($match[3]) ? $match[3] : (isset($match[2]) ? $match[2] : true);
+        }
+
+        return $cacheControl;
+    }
+}
diff --git a/vendor/symfony/http-foundation/IpUtils.php b/vendor/symfony/http-foundation/IpUtils.php
new file mode 100644
index 0000000000000000000000000000000000000000..86d135b2d3afd8e0fb65470ae691439811be9a2d
--- /dev/null
+++ b/vendor/symfony/http-foundation/IpUtils.php
@@ -0,0 +1,156 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Http utility functions.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class IpUtils
+{
+    private static $checkedIps = array();
+
+    /**
+     * This class should not be instantiated.
+     */
+    private function __construct()
+    {
+    }
+
+    /**
+     * Checks if an IPv4 or IPv6 address is contained in the list of given IPs or subnets.
+     *
+     * @param string       $requestIp IP to check
+     * @param string|array $ips       List of IPs or subnets (can be a string if only a single one)
+     *
+     * @return bool Whether the IP is valid
+     */
+    public static function checkIp($requestIp, $ips)
+    {
+        if (!is_array($ips)) {
+            $ips = array($ips);
+        }
+
+        $method = substr_count($requestIp, ':') > 1 ? 'checkIp6' : 'checkIp4';
+
+        foreach ($ips as $ip) {
+            if (self::$method($requestIp, $ip)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Compares two IPv4 addresses.
+     * In case a subnet is given, it checks if it contains the request IP.
+     *
+     * @param string $requestIp IPv4 address to check
+     * @param string $ip        IPv4 address or subnet in CIDR notation
+     *
+     * @return bool Whether the request IP matches the IP, or whether the request IP is within the CIDR subnet
+     */
+    public static function checkIp4($requestIp, $ip)
+    {
+        $cacheKey = $requestIp.'-'.$ip;
+        if (isset(self::$checkedIps[$cacheKey])) {
+            return self::$checkedIps[$cacheKey];
+        }
+
+        if (!filter_var($requestIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
+            return self::$checkedIps[$cacheKey] = false;
+        }
+
+        if (false !== strpos($ip, '/')) {
+            list($address, $netmask) = explode('/', $ip, 2);
+
+            if ('0' === $netmask) {
+                return self::$checkedIps[$cacheKey] = filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
+            }
+
+            if ($netmask < 0 || $netmask > 32) {
+                return self::$checkedIps[$cacheKey] = false;
+            }
+        } else {
+            $address = $ip;
+            $netmask = 32;
+        }
+
+        if (false === ip2long($address)) {
+            return self::$checkedIps[$cacheKey] = false;
+        }
+
+        return self::$checkedIps[$cacheKey] = 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask);
+    }
+
+    /**
+     * Compares two IPv6 addresses.
+     * In case a subnet is given, it checks if it contains the request IP.
+     *
+     * @author David Soria Parra <dsp at php dot net>
+     *
+     * @see https://github.com/dsp/v6tools
+     *
+     * @param string $requestIp IPv6 address to check
+     * @param string $ip        IPv6 address or subnet in CIDR notation
+     *
+     * @return bool Whether the IP is valid
+     *
+     * @throws \RuntimeException When IPV6 support is not enabled
+     */
+    public static function checkIp6($requestIp, $ip)
+    {
+        $cacheKey = $requestIp.'-'.$ip;
+        if (isset(self::$checkedIps[$cacheKey])) {
+            return self::$checkedIps[$cacheKey];
+        }
+
+        if (!((extension_loaded('sockets') && defined('AF_INET6')) || @inet_pton('::1'))) {
+            throw new \RuntimeException('Unable to check Ipv6. Check that PHP was not compiled with option "disable-ipv6".');
+        }
+
+        if (false !== strpos($ip, '/')) {
+            list($address, $netmask) = explode('/', $ip, 2);
+
+            if ('0' === $netmask) {
+                return (bool) unpack('n*', @inet_pton($address));
+            }
+
+            if ($netmask < 1 || $netmask > 128) {
+                return self::$checkedIps[$cacheKey] = false;
+            }
+        } else {
+            $address = $ip;
+            $netmask = 128;
+        }
+
+        $bytesAddr = unpack('n*', @inet_pton($address));
+        $bytesTest = unpack('n*', @inet_pton($requestIp));
+
+        if (!$bytesAddr || !$bytesTest) {
+            return self::$checkedIps[$cacheKey] = false;
+        }
+
+        for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; ++$i) {
+            $left = $netmask - 16 * ($i - 1);
+            $left = ($left <= 16) ? $left : 16;
+            $mask = ~(0xffff >> $left) & 0xffff;
+            if (($bytesAddr[$i] & $mask) != ($bytesTest[$i] & $mask)) {
+                return self::$checkedIps[$cacheKey] = false;
+            }
+        }
+
+        return self::$checkedIps[$cacheKey] = true;
+    }
+}
diff --git a/vendor/symfony/http-foundation/JsonResponse.php b/vendor/symfony/http-foundation/JsonResponse.php
new file mode 100644
index 0000000000000000000000000000000000000000..137ac33c46ed0c57734d3746e3a8a59bef86d804
--- /dev/null
+++ b/vendor/symfony/http-foundation/JsonResponse.php
@@ -0,0 +1,220 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Response represents an HTTP response in JSON format.
+ *
+ * Note that this class does not force the returned JSON content to be an
+ * object. It is however recommended that you do return an object as it
+ * protects yourself against XSSI and JSON-JavaScript Hijacking.
+ *
+ * @see https://www.owasp.org/index.php/OWASP_AJAX_Security_Guidelines#Always_return_JSON_with_an_Object_on_the_outside
+ *
+ * @author Igor Wiedler <igor@wiedler.ch>
+ */
+class JsonResponse extends Response
+{
+    protected $data;
+    protected $callback;
+
+    // Encode <, >, ', &, and " characters in the JSON, making it also safe to be embedded into HTML.
+    // 15 === JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT
+    const DEFAULT_ENCODING_OPTIONS = 15;
+
+    protected $encodingOptions = self::DEFAULT_ENCODING_OPTIONS;
+
+    /**
+     * @param mixed $data    The response data
+     * @param int   $status  The response status code
+     * @param array $headers An array of response headers
+     * @param bool  $json    If the data is already a JSON string
+     */
+    public function __construct($data = null, $status = 200, $headers = array(), $json = false)
+    {
+        parent::__construct('', $status, $headers);
+
+        if (null === $data) {
+            $data = new \ArrayObject();
+        }
+
+        $json ? $this->setJson($data) : $this->setData($data);
+    }
+
+    /**
+     * Factory method for chainability.
+     *
+     * Example:
+     *
+     *     return JsonResponse::create($data, 200)
+     *         ->setSharedMaxAge(300);
+     *
+     * @param mixed $data    The json response data
+     * @param int   $status  The response status code
+     * @param array $headers An array of response headers
+     *
+     * @return static
+     */
+    public static function create($data = null, $status = 200, $headers = array())
+    {
+        return new static($data, $status, $headers);
+    }
+
+    /**
+     * Make easier the creation of JsonResponse from raw json.
+     */
+    public static function fromJsonString($data = null, $status = 200, $headers = array())
+    {
+        return new static($data, $status, $headers, true);
+    }
+
+    /**
+     * Sets the JSONP callback.
+     *
+     * @param string|null $callback The JSONP callback or null to use none
+     *
+     * @return $this
+     *
+     * @throws \InvalidArgumentException When the callback name is not valid
+     */
+    public function setCallback($callback = null)
+    {
+        if (null !== $callback) {
+            // partially taken from http://www.geekality.net/2011/08/03/valid-javascript-identifier/
+            // partially taken from https://github.com/willdurand/JsonpCallbackValidator
+            //      JsonpCallbackValidator is released under the MIT License. See https://github.com/willdurand/JsonpCallbackValidator/blob/v1.1.0/LICENSE for details.
+            //      (c) William Durand <william.durand1@gmail.com>
+            $pattern = '/^[$_\p{L}][$_\p{L}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{200C}\x{200D}]*(?:\[(?:"(?:\\\.|[^"\\\])*"|\'(?:\\\.|[^\'\\\])*\'|\d+)\])*?$/u';
+            $reserved = array(
+                'break', 'do', 'instanceof', 'typeof', 'case', 'else', 'new', 'var', 'catch', 'finally', 'return', 'void', 'continue', 'for', 'switch', 'while',
+                'debugger', 'function', 'this', 'with', 'default', 'if', 'throw', 'delete', 'in', 'try', 'class', 'enum', 'extends', 'super',  'const', 'export',
+                'import', 'implements', 'let', 'private', 'public', 'yield', 'interface', 'package', 'protected', 'static', 'null', 'true', 'false',
+            );
+            $parts = explode('.', $callback);
+            foreach ($parts as $part) {
+                if (!preg_match($pattern, $part) || in_array($part, $reserved, true)) {
+                    throw new \InvalidArgumentException('The callback name is not valid.');
+                }
+            }
+        }
+
+        $this->callback = $callback;
+
+        return $this->update();
+    }
+
+    /**
+     * Sets a raw string containing a JSON document to be sent.
+     *
+     * @param string $json
+     *
+     * @return $this
+     *
+     * @throws \InvalidArgumentException
+     */
+    public function setJson($json)
+    {
+        $this->data = $json;
+
+        return $this->update();
+    }
+
+    /**
+     * Sets the data to be sent as JSON.
+     *
+     * @param mixed $data
+     *
+     * @return $this
+     *
+     * @throws \InvalidArgumentException
+     */
+    public function setData($data = array())
+    {
+        if (defined('HHVM_VERSION')) {
+            // HHVM does not trigger any warnings and let exceptions
+            // thrown from a JsonSerializable object pass through.
+            // If only PHP did the same...
+            $data = json_encode($data, $this->encodingOptions);
+        } else {
+            if (!interface_exists('JsonSerializable', false)) {
+                set_error_handler(function () { return false; });
+                try {
+                    $data = @json_encode($data, $this->encodingOptions);
+                } finally {
+                    restore_error_handler();
+                }
+            } else {
+                try {
+                    $data = json_encode($data, $this->encodingOptions);
+                } catch (\Exception $e) {
+                    if ('Exception' === get_class($e) && 0 === strpos($e->getMessage(), 'Failed calling ')) {
+                        throw $e->getPrevious() ?: $e;
+                    }
+                    throw $e;
+                }
+            }
+        }
+
+        if (JSON_ERROR_NONE !== json_last_error()) {
+            throw new \InvalidArgumentException(json_last_error_msg());
+        }
+
+        return $this->setJson($data);
+    }
+
+    /**
+     * Returns options used while encoding data to JSON.
+     *
+     * @return int
+     */
+    public function getEncodingOptions()
+    {
+        return $this->encodingOptions;
+    }
+
+    /**
+     * Sets options used while encoding data to JSON.
+     *
+     * @param int $encodingOptions
+     *
+     * @return $this
+     */
+    public function setEncodingOptions($encodingOptions)
+    {
+        $this->encodingOptions = (int) $encodingOptions;
+
+        return $this->setData(json_decode($this->data));
+    }
+
+    /**
+     * Updates the content and headers according to the JSON data and callback.
+     *
+     * @return $this
+     */
+    protected function update()
+    {
+        if (null !== $this->callback) {
+            // Not using application/javascript for compatibility reasons with older browsers.
+            $this->headers->set('Content-Type', 'text/javascript');
+
+            return $this->setContent(sprintf('/**/%s(%s);', $this->callback, $this->data));
+        }
+
+        // Only set the header when there is none or when it equals 'text/javascript' (from a previous update with callback)
+        // in order to not overwrite a custom definition.
+        if (!$this->headers->has('Content-Type') || 'text/javascript' === $this->headers->get('Content-Type')) {
+            $this->headers->set('Content-Type', 'application/json');
+        }
+
+        return $this->setContent($this->data);
+    }
+}
diff --git a/vendor/symfony/http-foundation/LICENSE b/vendor/symfony/http-foundation/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..21d7fb9e2f29b50caca3a76f0647e94e2cc8ddc1
--- /dev/null
+++ b/vendor/symfony/http-foundation/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2018 Fabien Potencier
+
+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/vendor/symfony/http-foundation/ParameterBag.php b/vendor/symfony/http-foundation/ParameterBag.php
new file mode 100644
index 0000000000000000000000000000000000000000..257ef8bceaa43cd7498a19e1c4c7923983b9915f
--- /dev/null
+++ b/vendor/symfony/http-foundation/ParameterBag.php
@@ -0,0 +1,234 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * ParameterBag is a container for key/value pairs.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class ParameterBag implements \IteratorAggregate, \Countable
+{
+    /**
+     * Parameter storage.
+     */
+    protected $parameters;
+
+    /**
+     * @param array $parameters An array of parameters
+     */
+    public function __construct(array $parameters = array())
+    {
+        $this->parameters = $parameters;
+    }
+
+    /**
+     * Returns the parameters.
+     *
+     * @return array An array of parameters
+     */
+    public function all()
+    {
+        return $this->parameters;
+    }
+
+    /**
+     * Returns the parameter keys.
+     *
+     * @return array An array of parameter keys
+     */
+    public function keys()
+    {
+        return array_keys($this->parameters);
+    }
+
+    /**
+     * Replaces the current parameters by a new set.
+     *
+     * @param array $parameters An array of parameters
+     */
+    public function replace(array $parameters = array())
+    {
+        $this->parameters = $parameters;
+    }
+
+    /**
+     * Adds parameters.
+     *
+     * @param array $parameters An array of parameters
+     */
+    public function add(array $parameters = array())
+    {
+        $this->parameters = array_replace($this->parameters, $parameters);
+    }
+
+    /**
+     * Returns a parameter by name.
+     *
+     * @param string $key     The key
+     * @param mixed  $default The default value if the parameter key does not exist
+     *
+     * @return mixed
+     */
+    public function get($key, $default = null)
+    {
+        return array_key_exists($key, $this->parameters) ? $this->parameters[$key] : $default;
+    }
+
+    /**
+     * Sets a parameter by name.
+     *
+     * @param string $key   The key
+     * @param mixed  $value The value
+     */
+    public function set($key, $value)
+    {
+        $this->parameters[$key] = $value;
+    }
+
+    /**
+     * Returns true if the parameter is defined.
+     *
+     * @param string $key The key
+     *
+     * @return bool true if the parameter exists, false otherwise
+     */
+    public function has($key)
+    {
+        return array_key_exists($key, $this->parameters);
+    }
+
+    /**
+     * Removes a parameter.
+     *
+     * @param string $key The key
+     */
+    public function remove($key)
+    {
+        unset($this->parameters[$key]);
+    }
+
+    /**
+     * Returns the alphabetic characters of the parameter value.
+     *
+     * @param string $key     The parameter key
+     * @param string $default The default value if the parameter key does not exist
+     *
+     * @return string The filtered value
+     */
+    public function getAlpha($key, $default = '')
+    {
+        return preg_replace('/[^[:alpha:]]/', '', $this->get($key, $default));
+    }
+
+    /**
+     * Returns the alphabetic characters and digits of the parameter value.
+     *
+     * @param string $key     The parameter key
+     * @param string $default The default value if the parameter key does not exist
+     *
+     * @return string The filtered value
+     */
+    public function getAlnum($key, $default = '')
+    {
+        return preg_replace('/[^[:alnum:]]/', '', $this->get($key, $default));
+    }
+
+    /**
+     * Returns the digits of the parameter value.
+     *
+     * @param string $key     The parameter key
+     * @param string $default The default value if the parameter key does not exist
+     *
+     * @return string The filtered value
+     */
+    public function getDigits($key, $default = '')
+    {
+        // we need to remove - and + because they're allowed in the filter
+        return str_replace(array('-', '+'), '', $this->filter($key, $default, FILTER_SANITIZE_NUMBER_INT));
+    }
+
+    /**
+     * Returns the parameter value converted to integer.
+     *
+     * @param string $key     The parameter key
+     * @param int    $default The default value if the parameter key does not exist
+     *
+     * @return int The filtered value
+     */
+    public function getInt($key, $default = 0)
+    {
+        return (int) $this->get($key, $default);
+    }
+
+    /**
+     * Returns the parameter value converted to boolean.
+     *
+     * @param string $key     The parameter key
+     * @param mixed  $default The default value if the parameter key does not exist
+     *
+     * @return bool The filtered value
+     */
+    public function getBoolean($key, $default = false)
+    {
+        return $this->filter($key, $default, FILTER_VALIDATE_BOOLEAN);
+    }
+
+    /**
+     * Filter key.
+     *
+     * @param string $key     Key
+     * @param mixed  $default Default = null
+     * @param int    $filter  FILTER_* constant
+     * @param mixed  $options Filter options
+     *
+     * @see http://php.net/manual/en/function.filter-var.php
+     *
+     * @return mixed
+     */
+    public function filter($key, $default = null, $filter = FILTER_DEFAULT, $options = array())
+    {
+        $value = $this->get($key, $default);
+
+        // Always turn $options into an array - this allows filter_var option shortcuts.
+        if (!is_array($options) && $options) {
+            $options = array('flags' => $options);
+        }
+
+        // Add a convenience check for arrays.
+        if (is_array($value) && !isset($options['flags'])) {
+            $options['flags'] = FILTER_REQUIRE_ARRAY;
+        }
+
+        return filter_var($value, $filter, $options);
+    }
+
+    /**
+     * Returns an iterator for parameters.
+     *
+     * @return \ArrayIterator An \ArrayIterator instance
+     */
+    public function getIterator()
+    {
+        return new \ArrayIterator($this->parameters);
+    }
+
+    /**
+     * Returns the number of parameters.
+     *
+     * @return int The number of parameters
+     */
+    public function count()
+    {
+        return count($this->parameters);
+    }
+}
diff --git a/vendor/symfony/http-foundation/README.md b/vendor/symfony/http-foundation/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..8907f0b9678966d1ca561d4963ddfb239da99609
--- /dev/null
+++ b/vendor/symfony/http-foundation/README.md
@@ -0,0 +1,14 @@
+HttpFoundation Component
+========================
+
+The HttpFoundation component defines an object-oriented layer for the HTTP
+specification.
+
+Resources
+---------
+
+  * [Documentation](https://symfony.com/doc/current/components/http_foundation/index.html)
+  * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+  * [Report issues](https://github.com/symfony/symfony/issues) and
+    [send Pull Requests](https://github.com/symfony/symfony/pulls)
+    in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/vendor/symfony/http-foundation/RedirectResponse.php b/vendor/symfony/http-foundation/RedirectResponse.php
new file mode 100644
index 0000000000000000000000000000000000000000..01681dcdf787a1314c716bd6051b1cfc0f69c954
--- /dev/null
+++ b/vendor/symfony/http-foundation/RedirectResponse.php
@@ -0,0 +1,109 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * RedirectResponse represents an HTTP response doing a redirect.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class RedirectResponse extends Response
+{
+    protected $targetUrl;
+
+    /**
+     * Creates a redirect response so that it conforms to the rules defined for a redirect status code.
+     *
+     * @param string $url     The URL to redirect to. The URL should be a full URL, with schema etc.,
+     *                        but practically every browser redirects on paths only as well
+     * @param int    $status  The status code (302 by default)
+     * @param array  $headers The headers (Location is always set to the given URL)
+     *
+     * @throws \InvalidArgumentException
+     *
+     * @see http://tools.ietf.org/html/rfc2616#section-10.3
+     */
+    public function __construct($url, $status = 302, $headers = array())
+    {
+        parent::__construct('', $status, $headers);
+
+        $this->setTargetUrl($url);
+
+        if (!$this->isRedirect()) {
+            throw new \InvalidArgumentException(sprintf('The HTTP status code is not a redirect ("%s" given).', $status));
+        }
+
+        if (301 == $status && !array_key_exists('cache-control', $headers)) {
+            $this->headers->remove('cache-control');
+        }
+    }
+
+    /**
+     * Factory method for chainability.
+     *
+     * @param string $url     The url to redirect to
+     * @param int    $status  The response status code
+     * @param array  $headers An array of response headers
+     *
+     * @return static
+     */
+    public static function create($url = '', $status = 302, $headers = array())
+    {
+        return new static($url, $status, $headers);
+    }
+
+    /**
+     * Returns the target URL.
+     *
+     * @return string target URL
+     */
+    public function getTargetUrl()
+    {
+        return $this->targetUrl;
+    }
+
+    /**
+     * Sets the redirect target of this response.
+     *
+     * @param string $url The URL to redirect to
+     *
+     * @return $this
+     *
+     * @throws \InvalidArgumentException
+     */
+    public function setTargetUrl($url)
+    {
+        if (empty($url)) {
+            throw new \InvalidArgumentException('Cannot redirect to an empty URL.');
+        }
+
+        $this->targetUrl = $url;
+
+        $this->setContent(
+            sprintf('<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="UTF-8" />
+        <meta http-equiv="refresh" content="0;url=%1$s" />
+
+        <title>Redirecting to %1$s</title>
+    </head>
+    <body>
+        Redirecting to <a href="%1$s">%1$s</a>.
+    </body>
+</html>', htmlspecialchars($url, ENT_QUOTES, 'UTF-8')));
+
+        $this->headers->set('Location', $url);
+
+        return $this;
+    }
+}
diff --git a/vendor/symfony/http-foundation/Request.php b/vendor/symfony/http-foundation/Request.php
new file mode 100644
index 0000000000000000000000000000000000000000..164fb4e0e333a2ccd0750088f9237859f6a4c86a
--- /dev/null
+++ b/vendor/symfony/http-foundation/Request.php
@@ -0,0 +1,2154 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+use Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException;
+use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
+use Symfony\Component\HttpFoundation\Session\SessionInterface;
+
+/**
+ * Request represents an HTTP request.
+ *
+ * The methods dealing with URL accept / return a raw path (% encoded):
+ *   * getBasePath
+ *   * getBaseUrl
+ *   * getPathInfo
+ *   * getRequestUri
+ *   * getUri
+ *   * getUriForPath
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class Request
+{
+    const HEADER_FORWARDED = 0b00001; // When using RFC 7239
+    const HEADER_X_FORWARDED_FOR = 0b00010;
+    const HEADER_X_FORWARDED_HOST = 0b00100;
+    const HEADER_X_FORWARDED_PROTO = 0b01000;
+    const HEADER_X_FORWARDED_PORT = 0b10000;
+    const HEADER_X_FORWARDED_ALL = 0b11110; // All "X-Forwarded-*" headers
+    const HEADER_X_FORWARDED_AWS_ELB = 0b11010; // AWS ELB doesn't send X-Forwarded-Host
+
+    /** @deprecated since version 3.3, to be removed in 4.0 */
+    const HEADER_CLIENT_IP = self::HEADER_X_FORWARDED_FOR;
+    /** @deprecated since version 3.3, to be removed in 4.0 */
+    const HEADER_CLIENT_HOST = self::HEADER_X_FORWARDED_HOST;
+    /** @deprecated since version 3.3, to be removed in 4.0 */
+    const HEADER_CLIENT_PROTO = self::HEADER_X_FORWARDED_PROTO;
+    /** @deprecated since version 3.3, to be removed in 4.0 */
+    const HEADER_CLIENT_PORT = self::HEADER_X_FORWARDED_PORT;
+
+    const METHOD_HEAD = 'HEAD';
+    const METHOD_GET = 'GET';
+    const METHOD_POST = 'POST';
+    const METHOD_PUT = 'PUT';
+    const METHOD_PATCH = 'PATCH';
+    const METHOD_DELETE = 'DELETE';
+    const METHOD_PURGE = 'PURGE';
+    const METHOD_OPTIONS = 'OPTIONS';
+    const METHOD_TRACE = 'TRACE';
+    const METHOD_CONNECT = 'CONNECT';
+
+    /**
+     * @var string[]
+     */
+    protected static $trustedProxies = array();
+
+    /**
+     * @var string[]
+     */
+    protected static $trustedHostPatterns = array();
+
+    /**
+     * @var string[]
+     */
+    protected static $trustedHosts = array();
+
+    /**
+     * Names for headers that can be trusted when
+     * using trusted proxies.
+     *
+     * The FORWARDED header is the standard as of rfc7239.
+     *
+     * The other headers are non-standard, but widely used
+     * by popular reverse proxies (like Apache mod_proxy or Amazon EC2).
+     *
+     * @deprecated since version 3.3, to be removed in 4.0
+     */
+    protected static $trustedHeaders = array(
+        self::HEADER_FORWARDED => 'FORWARDED',
+        self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR',
+        self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST',
+        self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO',
+        self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT',
+    );
+
+    protected static $httpMethodParameterOverride = false;
+
+    /**
+     * Custom parameters.
+     *
+     * @var \Symfony\Component\HttpFoundation\ParameterBag
+     */
+    public $attributes;
+
+    /**
+     * Request body parameters ($_POST).
+     *
+     * @var \Symfony\Component\HttpFoundation\ParameterBag
+     */
+    public $request;
+
+    /**
+     * Query string parameters ($_GET).
+     *
+     * @var \Symfony\Component\HttpFoundation\ParameterBag
+     */
+    public $query;
+
+    /**
+     * Server and execution environment parameters ($_SERVER).
+     *
+     * @var \Symfony\Component\HttpFoundation\ServerBag
+     */
+    public $server;
+
+    /**
+     * Uploaded files ($_FILES).
+     *
+     * @var \Symfony\Component\HttpFoundation\FileBag
+     */
+    public $files;
+
+    /**
+     * Cookies ($_COOKIE).
+     *
+     * @var \Symfony\Component\HttpFoundation\ParameterBag
+     */
+    public $cookies;
+
+    /**
+     * Headers (taken from the $_SERVER).
+     *
+     * @var \Symfony\Component\HttpFoundation\HeaderBag
+     */
+    public $headers;
+
+    /**
+     * @var string|resource|false|null
+     */
+    protected $content;
+
+    /**
+     * @var array
+     */
+    protected $languages;
+
+    /**
+     * @var array
+     */
+    protected $charsets;
+
+    /**
+     * @var array
+     */
+    protected $encodings;
+
+    /**
+     * @var array
+     */
+    protected $acceptableContentTypes;
+
+    /**
+     * @var string
+     */
+    protected $pathInfo;
+
+    /**
+     * @var string
+     */
+    protected $requestUri;
+
+    /**
+     * @var string
+     */
+    protected $baseUrl;
+
+    /**
+     * @var string
+     */
+    protected $basePath;
+
+    /**
+     * @var string
+     */
+    protected $method;
+
+    /**
+     * @var string
+     */
+    protected $format;
+
+    /**
+     * @var \Symfony\Component\HttpFoundation\Session\SessionInterface
+     */
+    protected $session;
+
+    /**
+     * @var string
+     */
+    protected $locale;
+
+    /**
+     * @var string
+     */
+    protected $defaultLocale = 'en';
+
+    /**
+     * @var array
+     */
+    protected static $formats;
+
+    protected static $requestFactory;
+
+    private $isHostValid = true;
+    private $isForwardedValid = true;
+
+    private static $trustedHeaderSet = -1;
+
+    /** @deprecated since version 3.3, to be removed in 4.0 */
+    private static $trustedHeaderNames = array(
+        self::HEADER_FORWARDED => 'FORWARDED',
+        self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR',
+        self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST',
+        self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO',
+        self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT',
+    );
+
+    private static $forwardedParams = array(
+        self::HEADER_X_FORWARDED_FOR => 'for',
+        self::HEADER_X_FORWARDED_HOST => 'host',
+        self::HEADER_X_FORWARDED_PROTO => 'proto',
+        self::HEADER_X_FORWARDED_PORT => 'host',
+    );
+
+    /**
+     * @param array                $query      The GET parameters
+     * @param array                $request    The POST parameters
+     * @param array                $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
+     * @param array                $cookies    The COOKIE parameters
+     * @param array                $files      The FILES parameters
+     * @param array                $server     The SERVER parameters
+     * @param string|resource|null $content    The raw body data
+     */
+    public function __construct(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
+    {
+        $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content);
+    }
+
+    /**
+     * Sets the parameters for this request.
+     *
+     * This method also re-initializes all properties.
+     *
+     * @param array                $query      The GET parameters
+     * @param array                $request    The POST parameters
+     * @param array                $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
+     * @param array                $cookies    The COOKIE parameters
+     * @param array                $files      The FILES parameters
+     * @param array                $server     The SERVER parameters
+     * @param string|resource|null $content    The raw body data
+     */
+    public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
+    {
+        $this->request = new ParameterBag($request);
+        $this->query = new ParameterBag($query);
+        $this->attributes = new ParameterBag($attributes);
+        $this->cookies = new ParameterBag($cookies);
+        $this->files = new FileBag($files);
+        $this->server = new ServerBag($server);
+        $this->headers = new HeaderBag($this->server->getHeaders());
+
+        $this->content = $content;
+        $this->languages = null;
+        $this->charsets = null;
+        $this->encodings = null;
+        $this->acceptableContentTypes = null;
+        $this->pathInfo = null;
+        $this->requestUri = null;
+        $this->baseUrl = null;
+        $this->basePath = null;
+        $this->method = null;
+        $this->format = null;
+    }
+
+    /**
+     * Creates a new request with values from PHP's super globals.
+     *
+     * @return static
+     */
+    public static function createFromGlobals()
+    {
+        // With the php's bug #66606, the php's built-in web server
+        // stores the Content-Type and Content-Length header values in
+        // HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH fields.
+        $server = $_SERVER;
+        if ('cli-server' === PHP_SAPI) {
+            if (array_key_exists('HTTP_CONTENT_LENGTH', $_SERVER)) {
+                $server['CONTENT_LENGTH'] = $_SERVER['HTTP_CONTENT_LENGTH'];
+            }
+            if (array_key_exists('HTTP_CONTENT_TYPE', $_SERVER)) {
+                $server['CONTENT_TYPE'] = $_SERVER['HTTP_CONTENT_TYPE'];
+            }
+        }
+
+        $request = self::createRequestFromFactory($_GET, $_POST, array(), $_COOKIE, $_FILES, $server);
+
+        if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded')
+            && in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), array('PUT', 'DELETE', 'PATCH'))
+        ) {
+            parse_str($request->getContent(), $data);
+            $request->request = new ParameterBag($data);
+        }
+
+        return $request;
+    }
+
+    /**
+     * Creates a Request based on a given URI and configuration.
+     *
+     * The information contained in the URI always take precedence
+     * over the other information (server and parameters).
+     *
+     * @param string               $uri        The URI
+     * @param string               $method     The HTTP method
+     * @param array                $parameters The query (GET) or request (POST) parameters
+     * @param array                $cookies    The request cookies ($_COOKIE)
+     * @param array                $files      The request files ($_FILES)
+     * @param array                $server     The server parameters ($_SERVER)
+     * @param string|resource|null $content    The raw body data
+     *
+     * @return static
+     */
+    public static function create($uri, $method = 'GET', $parameters = array(), $cookies = array(), $files = array(), $server = array(), $content = null)
+    {
+        $server = array_replace(array(
+            'SERVER_NAME' => 'localhost',
+            'SERVER_PORT' => 80,
+            'HTTP_HOST' => 'localhost',
+            'HTTP_USER_AGENT' => 'Symfony/3.X',
+            'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
+            'HTTP_ACCEPT_LANGUAGE' => 'en-us,en;q=0.5',
+            'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
+            'REMOTE_ADDR' => '127.0.0.1',
+            'SCRIPT_NAME' => '',
+            'SCRIPT_FILENAME' => '',
+            'SERVER_PROTOCOL' => 'HTTP/1.1',
+            'REQUEST_TIME' => time(),
+        ), $server);
+
+        $server['PATH_INFO'] = '';
+        $server['REQUEST_METHOD'] = strtoupper($method);
+
+        $components = parse_url($uri);
+        if (isset($components['host'])) {
+            $server['SERVER_NAME'] = $components['host'];
+            $server['HTTP_HOST'] = $components['host'];
+        }
+
+        if (isset($components['scheme'])) {
+            if ('https' === $components['scheme']) {
+                $server['HTTPS'] = 'on';
+                $server['SERVER_PORT'] = 443;
+            } else {
+                unset($server['HTTPS']);
+                $server['SERVER_PORT'] = 80;
+            }
+        }
+
+        if (isset($components['port'])) {
+            $server['SERVER_PORT'] = $components['port'];
+            $server['HTTP_HOST'] = $server['HTTP_HOST'].':'.$components['port'];
+        }
+
+        if (isset($components['user'])) {
+            $server['PHP_AUTH_USER'] = $components['user'];
+        }
+
+        if (isset($components['pass'])) {
+            $server['PHP_AUTH_PW'] = $components['pass'];
+        }
+
+        if (!isset($components['path'])) {
+            $components['path'] = '/';
+        }
+
+        switch (strtoupper($method)) {
+            case 'POST':
+            case 'PUT':
+            case 'DELETE':
+                if (!isset($server['CONTENT_TYPE'])) {
+                    $server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
+                }
+                // no break
+            case 'PATCH':
+                $request = $parameters;
+                $query = array();
+                break;
+            default:
+                $request = array();
+                $query = $parameters;
+                break;
+        }
+
+        $queryString = '';
+        if (isset($components['query'])) {
+            parse_str(html_entity_decode($components['query']), $qs);
+
+            if ($query) {
+                $query = array_replace($qs, $query);
+                $queryString = http_build_query($query, '', '&');
+            } else {
+                $query = $qs;
+                $queryString = $components['query'];
+            }
+        } elseif ($query) {
+            $queryString = http_build_query($query, '', '&');
+        }
+
+        $server['REQUEST_URI'] = $components['path'].('' !== $queryString ? '?'.$queryString : '');
+        $server['QUERY_STRING'] = $queryString;
+
+        return self::createRequestFromFactory($query, $request, array(), $cookies, $files, $server, $content);
+    }
+
+    /**
+     * Sets a callable able to create a Request instance.
+     *
+     * This is mainly useful when you need to override the Request class
+     * to keep BC with an existing system. It should not be used for any
+     * other purpose.
+     *
+     * @param callable|null $callable A PHP callable
+     */
+    public static function setFactory($callable)
+    {
+        self::$requestFactory = $callable;
+    }
+
+    /**
+     * Clones a request and overrides some of its parameters.
+     *
+     * @param array $query      The GET parameters
+     * @param array $request    The POST parameters
+     * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
+     * @param array $cookies    The COOKIE parameters
+     * @param array $files      The FILES parameters
+     * @param array $server     The SERVER parameters
+     *
+     * @return static
+     */
+    public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null)
+    {
+        $dup = clone $this;
+        if (null !== $query) {
+            $dup->query = new ParameterBag($query);
+        }
+        if (null !== $request) {
+            $dup->request = new ParameterBag($request);
+        }
+        if (null !== $attributes) {
+            $dup->attributes = new ParameterBag($attributes);
+        }
+        if (null !== $cookies) {
+            $dup->cookies = new ParameterBag($cookies);
+        }
+        if (null !== $files) {
+            $dup->files = new FileBag($files);
+        }
+        if (null !== $server) {
+            $dup->server = new ServerBag($server);
+            $dup->headers = new HeaderBag($dup->server->getHeaders());
+        }
+        $dup->languages = null;
+        $dup->charsets = null;
+        $dup->encodings = null;
+        $dup->acceptableContentTypes = null;
+        $dup->pathInfo = null;
+        $dup->requestUri = null;
+        $dup->baseUrl = null;
+        $dup->basePath = null;
+        $dup->method = null;
+        $dup->format = null;
+
+        if (!$dup->get('_format') && $this->get('_format')) {
+            $dup->attributes->set('_format', $this->get('_format'));
+        }
+
+        if (!$dup->getRequestFormat(null)) {
+            $dup->setRequestFormat($this->getRequestFormat(null));
+        }
+
+        return $dup;
+    }
+
+    /**
+     * Clones the current request.
+     *
+     * Note that the session is not cloned as duplicated requests
+     * are most of the time sub-requests of the main one.
+     */
+    public function __clone()
+    {
+        $this->query = clone $this->query;
+        $this->request = clone $this->request;
+        $this->attributes = clone $this->attributes;
+        $this->cookies = clone $this->cookies;
+        $this->files = clone $this->files;
+        $this->server = clone $this->server;
+        $this->headers = clone $this->headers;
+    }
+
+    /**
+     * Returns the request as a string.
+     *
+     * @return string The request
+     */
+    public function __toString()
+    {
+        try {
+            $content = $this->getContent();
+        } catch (\LogicException $e) {
+            return trigger_error($e, E_USER_ERROR);
+        }
+
+        $cookieHeader = '';
+        $cookies = array();
+
+        foreach ($this->cookies as $k => $v) {
+            $cookies[] = $k.'='.$v;
+        }
+
+        if (!empty($cookies)) {
+            $cookieHeader = 'Cookie: '.implode('; ', $cookies)."\r\n";
+        }
+
+        return
+            sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n".
+            $this->headers.
+            $cookieHeader."\r\n".
+            $content;
+    }
+
+    /**
+     * Overrides the PHP global variables according to this request instance.
+     *
+     * It overrides $_GET, $_POST, $_REQUEST, $_SERVER, $_COOKIE.
+     * $_FILES is never overridden, see rfc1867
+     */
+    public function overrideGlobals()
+    {
+        $this->server->set('QUERY_STRING', static::normalizeQueryString(http_build_query($this->query->all(), '', '&')));
+
+        $_GET = $this->query->all();
+        $_POST = $this->request->all();
+        $_SERVER = $this->server->all();
+        $_COOKIE = $this->cookies->all();
+
+        foreach ($this->headers->all() as $key => $value) {
+            $key = strtoupper(str_replace('-', '_', $key));
+            if (in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH'))) {
+                $_SERVER[$key] = implode(', ', $value);
+            } else {
+                $_SERVER['HTTP_'.$key] = implode(', ', $value);
+            }
+        }
+
+        $request = array('g' => $_GET, 'p' => $_POST, 'c' => $_COOKIE);
+
+        $requestOrder = ini_get('request_order') ?: ini_get('variables_order');
+        $requestOrder = preg_replace('#[^cgp]#', '', strtolower($requestOrder)) ?: 'gp';
+
+        $_REQUEST = array();
+        foreach (str_split($requestOrder) as $order) {
+            $_REQUEST = array_merge($_REQUEST, $request[$order]);
+        }
+    }
+
+    /**
+     * Sets a list of trusted proxies.
+     *
+     * You should only list the reverse proxies that you manage directly.
+     *
+     * @param array $proxies          A list of trusted proxies
+     * @param int   $trustedHeaderSet A bit field of Request::HEADER_*, to set which headers to trust from your proxies
+     *
+     * @throws \InvalidArgumentException When $trustedHeaderSet is invalid
+     */
+    public static function setTrustedProxies(array $proxies/*, int $trustedHeaderSet*/)
+    {
+        self::$trustedProxies = $proxies;
+
+        if (2 > func_num_args()) {
+            @trigger_error(sprintf('The %s() method expects a bit field of Request::HEADER_* as second argument since Symfony 3.3. Defining it will be required in 4.0. ', __METHOD__), E_USER_DEPRECATED);
+
+            return;
+        }
+        $trustedHeaderSet = (int) func_get_arg(1);
+
+        foreach (self::$trustedHeaderNames as $header => $name) {
+            self::$trustedHeaders[$header] = $header & $trustedHeaderSet ? $name : null;
+        }
+        self::$trustedHeaderSet = $trustedHeaderSet;
+    }
+
+    /**
+     * Gets the list of trusted proxies.
+     *
+     * @return array An array of trusted proxies
+     */
+    public static function getTrustedProxies()
+    {
+        return self::$trustedProxies;
+    }
+
+    /**
+     * Gets the set of trusted headers from trusted proxies.
+     *
+     * @return int A bit field of Request::HEADER_* that defines which headers are trusted from your proxies
+     */
+    public static function getTrustedHeaderSet()
+    {
+        return self::$trustedHeaderSet;
+    }
+
+    /**
+     * Sets a list of trusted host patterns.
+     *
+     * You should only list the hosts you manage using regexs.
+     *
+     * @param array $hostPatterns A list of trusted host patterns
+     */
+    public static function setTrustedHosts(array $hostPatterns)
+    {
+        self::$trustedHostPatterns = array_map(function ($hostPattern) {
+            return sprintf('#%s#i', $hostPattern);
+        }, $hostPatterns);
+        // we need to reset trusted hosts on trusted host patterns change
+        self::$trustedHosts = array();
+    }
+
+    /**
+     * Gets the list of trusted host patterns.
+     *
+     * @return array An array of trusted host patterns
+     */
+    public static function getTrustedHosts()
+    {
+        return self::$trustedHostPatterns;
+    }
+
+    /**
+     * Sets the name for trusted headers.
+     *
+     * The following header keys are supported:
+     *
+     *  * Request::HEADER_CLIENT_IP:    defaults to X-Forwarded-For   (see getClientIp())
+     *  * Request::HEADER_CLIENT_HOST:  defaults to X-Forwarded-Host  (see getHost())
+     *  * Request::HEADER_CLIENT_PORT:  defaults to X-Forwarded-Port  (see getPort())
+     *  * Request::HEADER_CLIENT_PROTO: defaults to X-Forwarded-Proto (see getScheme() and isSecure())
+     *  * Request::HEADER_FORWARDED:    defaults to Forwarded         (see RFC 7239)
+     *
+     * Setting an empty value allows to disable the trusted header for the given key.
+     *
+     * @param string $key   The header key
+     * @param string $value The header name
+     *
+     * @throws \InvalidArgumentException
+     *
+     * @deprecated since version 3.3, to be removed in 4.0. Use the $trustedHeaderSet argument of the Request::setTrustedProxies() method instead.
+     */
+    public static function setTrustedHeaderName($key, $value)
+    {
+        @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the $trustedHeaderSet argument of the Request::setTrustedProxies() method instead.', __METHOD__), E_USER_DEPRECATED);
+
+        if ('forwarded' === $key) {
+            $key = self::HEADER_FORWARDED;
+        } elseif ('client_ip' === $key) {
+            $key = self::HEADER_CLIENT_IP;
+        } elseif ('client_host' === $key) {
+            $key = self::HEADER_CLIENT_HOST;
+        } elseif ('client_proto' === $key) {
+            $key = self::HEADER_CLIENT_PROTO;
+        } elseif ('client_port' === $key) {
+            $key = self::HEADER_CLIENT_PORT;
+        } elseif (!array_key_exists($key, self::$trustedHeaders)) {
+            throw new \InvalidArgumentException(sprintf('Unable to set the trusted header name for key "%s".', $key));
+        }
+
+        self::$trustedHeaders[$key] = $value;
+
+        if (null !== $value) {
+            self::$trustedHeaderNames[$key] = $value;
+            self::$trustedHeaderSet |= $key;
+        } else {
+            self::$trustedHeaderSet &= ~$key;
+        }
+    }
+
+    /**
+     * Gets the trusted proxy header name.
+     *
+     * @param string $key The header key
+     *
+     * @return string The header name
+     *
+     * @throws \InvalidArgumentException
+     *
+     * @deprecated since version 3.3, to be removed in 4.0. Use the Request::getTrustedHeaderSet() method instead.
+     */
+    public static function getTrustedHeaderName($key)
+    {
+        if (2 > func_num_args() || func_get_arg(1)) {
+            @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the Request::getTrustedHeaderSet() method instead.', __METHOD__), E_USER_DEPRECATED);
+        }
+
+        if (!array_key_exists($key, self::$trustedHeaders)) {
+            throw new \InvalidArgumentException(sprintf('Unable to get the trusted header name for key "%s".', $key));
+        }
+
+        return self::$trustedHeaders[$key];
+    }
+
+    /**
+     * Normalizes a query string.
+     *
+     * It builds a normalized query string, where keys/value pairs are alphabetized,
+     * have consistent escaping and unneeded delimiters are removed.
+     *
+     * @param string $qs Query string
+     *
+     * @return string A normalized query string for the Request
+     */
+    public static function normalizeQueryString($qs)
+    {
+        if ('' == $qs) {
+            return '';
+        }
+
+        $parts = array();
+        $order = array();
+
+        foreach (explode('&', $qs) as $param) {
+            if ('' === $param || '=' === $param[0]) {
+                // Ignore useless delimiters, e.g. "x=y&".
+                // Also ignore pairs with empty key, even if there was a value, e.g. "=value", as such nameless values cannot be retrieved anyway.
+                // PHP also does not include them when building _GET.
+                continue;
+            }
+
+            $keyValuePair = explode('=', $param, 2);
+
+            // GET parameters, that are submitted from a HTML form, encode spaces as "+" by default (as defined in enctype application/x-www-form-urlencoded).
+            // PHP also converts "+" to spaces when filling the global _GET or when using the function parse_str. This is why we use urldecode and then normalize to
+            // RFC 3986 with rawurlencode.
+            $parts[] = isset($keyValuePair[1]) ?
+                rawurlencode(urldecode($keyValuePair[0])).'='.rawurlencode(urldecode($keyValuePair[1])) :
+                rawurlencode(urldecode($keyValuePair[0]));
+            $order[] = urldecode($keyValuePair[0]);
+        }
+
+        array_multisort($order, SORT_ASC, $parts);
+
+        return implode('&', $parts);
+    }
+
+    /**
+     * Enables support for the _method request parameter to determine the intended HTTP method.
+     *
+     * Be warned that enabling this feature might lead to CSRF issues in your code.
+     * Check that you are using CSRF tokens when required.
+     * If the HTTP method parameter override is enabled, an html-form with method "POST" can be altered
+     * and used to send a "PUT" or "DELETE" request via the _method request parameter.
+     * If these methods are not protected against CSRF, this presents a possible vulnerability.
+     *
+     * The HTTP method can only be overridden when the real HTTP method is POST.
+     */
+    public static function enableHttpMethodParameterOverride()
+    {
+        self::$httpMethodParameterOverride = true;
+    }
+
+    /**
+     * Checks whether support for the _method request parameter is enabled.
+     *
+     * @return bool True when the _method request parameter is enabled, false otherwise
+     */
+    public static function getHttpMethodParameterOverride()
+    {
+        return self::$httpMethodParameterOverride;
+    }
+
+    /**
+     * Gets a "parameter" value from any bag.
+     *
+     * This method is mainly useful for libraries that want to provide some flexibility. If you don't need the
+     * flexibility in controllers, it is better to explicitly get request parameters from the appropriate
+     * public property instead (attributes, query, request).
+     *
+     * Order of precedence: PATH (routing placeholders or custom attributes), GET, BODY
+     *
+     * @param string $key     The key
+     * @param mixed  $default The default value if the parameter key does not exist
+     *
+     * @return mixed
+     */
+    public function get($key, $default = null)
+    {
+        if ($this !== $result = $this->attributes->get($key, $this)) {
+            return $result;
+        }
+
+        if ($this !== $result = $this->query->get($key, $this)) {
+            return $result;
+        }
+
+        if ($this !== $result = $this->request->get($key, $this)) {
+            return $result;
+        }
+
+        return $default;
+    }
+
+    /**
+     * Gets the Session.
+     *
+     * @return SessionInterface|null The session
+     */
+    public function getSession()
+    {
+        return $this->session;
+    }
+
+    /**
+     * Whether the request contains a Session which was started in one of the
+     * previous requests.
+     *
+     * @return bool
+     */
+    public function hasPreviousSession()
+    {
+        // the check for $this->session avoids malicious users trying to fake a session cookie with proper name
+        return $this->hasSession() && $this->cookies->has($this->session->getName());
+    }
+
+    /**
+     * Whether the request contains a Session object.
+     *
+     * This method does not give any information about the state of the session object,
+     * like whether the session is started or not. It is just a way to check if this Request
+     * is associated with a Session instance.
+     *
+     * @return bool true when the Request contains a Session object, false otherwise
+     */
+    public function hasSession()
+    {
+        return null !== $this->session;
+    }
+
+    /**
+     * Sets the Session.
+     *
+     * @param SessionInterface $session The Session
+     */
+    public function setSession(SessionInterface $session)
+    {
+        $this->session = $session;
+    }
+
+    /**
+     * Returns the client IP addresses.
+     *
+     * In the returned array the most trusted IP address is first, and the
+     * least trusted one last. The "real" client IP address is the last one,
+     * but this is also the least trusted one. Trusted proxies are stripped.
+     *
+     * Use this method carefully; you should use getClientIp() instead.
+     *
+     * @return array The client IP addresses
+     *
+     * @see getClientIp()
+     */
+    public function getClientIps()
+    {
+        $ip = $this->server->get('REMOTE_ADDR');
+
+        if (!$this->isFromTrustedProxy()) {
+            return array($ip);
+        }
+
+        return $this->getTrustedValues(self::HEADER_CLIENT_IP, $ip) ?: array($ip);
+    }
+
+    /**
+     * Returns the client IP address.
+     *
+     * This method can read the client IP address from the "X-Forwarded-For" header
+     * when trusted proxies were set via "setTrustedProxies()". The "X-Forwarded-For"
+     * header value is a comma+space separated list of IP addresses, the left-most
+     * being the original client, and each successive proxy that passed the request
+     * adding the IP address where it received the request from.
+     *
+     * If your reverse proxy uses a different header name than "X-Forwarded-For",
+     * ("Client-Ip" for instance), configure it via the $trustedHeaderSet
+     * argument of the Request::setTrustedProxies() method instead.
+     *
+     * @return string|null The client IP address
+     *
+     * @see getClientIps()
+     * @see http://en.wikipedia.org/wiki/X-Forwarded-For
+     */
+    public function getClientIp()
+    {
+        $ipAddresses = $this->getClientIps();
+
+        return $ipAddresses[0];
+    }
+
+    /**
+     * Returns current script name.
+     *
+     * @return string
+     */
+    public function getScriptName()
+    {
+        return $this->server->get('SCRIPT_NAME', $this->server->get('ORIG_SCRIPT_NAME', ''));
+    }
+
+    /**
+     * Returns the path being requested relative to the executed script.
+     *
+     * The path info always starts with a /.
+     *
+     * Suppose this request is instantiated from /mysite on localhost:
+     *
+     *  * http://localhost/mysite              returns an empty string
+     *  * http://localhost/mysite/about        returns '/about'
+     *  * http://localhost/mysite/enco%20ded   returns '/enco%20ded'
+     *  * http://localhost/mysite/about?var=1  returns '/about'
+     *
+     * @return string The raw path (i.e. not urldecoded)
+     */
+    public function getPathInfo()
+    {
+        if (null === $this->pathInfo) {
+            $this->pathInfo = $this->preparePathInfo();
+        }
+
+        return $this->pathInfo;
+    }
+
+    /**
+     * Returns the root path from which this request is executed.
+     *
+     * Suppose that an index.php file instantiates this request object:
+     *
+     *  * http://localhost/index.php         returns an empty string
+     *  * http://localhost/index.php/page    returns an empty string
+     *  * http://localhost/web/index.php     returns '/web'
+     *  * http://localhost/we%20b/index.php  returns '/we%20b'
+     *
+     * @return string The raw path (i.e. not urldecoded)
+     */
+    public function getBasePath()
+    {
+        if (null === $this->basePath) {
+            $this->basePath = $this->prepareBasePath();
+        }
+
+        return $this->basePath;
+    }
+
+    /**
+     * Returns the root URL from which this request is executed.
+     *
+     * The base URL never ends with a /.
+     *
+     * This is similar to getBasePath(), except that it also includes the
+     * script filename (e.g. index.php) if one exists.
+     *
+     * @return string The raw URL (i.e. not urldecoded)
+     */
+    public function getBaseUrl()
+    {
+        if (null === $this->baseUrl) {
+            $this->baseUrl = $this->prepareBaseUrl();
+        }
+
+        return $this->baseUrl;
+    }
+
+    /**
+     * Gets the request's scheme.
+     *
+     * @return string
+     */
+    public function getScheme()
+    {
+        return $this->isSecure() ? 'https' : 'http';
+    }
+
+    /**
+     * Returns the port on which the request is made.
+     *
+     * This method can read the client port from the "X-Forwarded-Port" header
+     * when trusted proxies were set via "setTrustedProxies()".
+     *
+     * The "X-Forwarded-Port" header must contain the client port.
+     *
+     * If your reverse proxy uses a different header name than "X-Forwarded-Port",
+     * configure it via via the $trustedHeaderSet argument of the
+     * Request::setTrustedProxies() method instead.
+     *
+     * @return int|string can be a string if fetched from the server bag
+     */
+    public function getPort()
+    {
+        if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_PORT)) {
+            $host = $host[0];
+        } elseif ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_HOST)) {
+            $host = $host[0];
+        } elseif (!$host = $this->headers->get('HOST')) {
+            return $this->server->get('SERVER_PORT');
+        }
+
+        if ('[' === $host[0]) {
+            $pos = strpos($host, ':', strrpos($host, ']'));
+        } else {
+            $pos = strrpos($host, ':');
+        }
+
+        if (false !== $pos) {
+            return (int) substr($host, $pos + 1);
+        }
+
+        return 'https' === $this->getScheme() ? 443 : 80;
+    }
+
+    /**
+     * Returns the user.
+     *
+     * @return string|null
+     */
+    public function getUser()
+    {
+        return $this->headers->get('PHP_AUTH_USER');
+    }
+
+    /**
+     * Returns the password.
+     *
+     * @return string|null
+     */
+    public function getPassword()
+    {
+        return $this->headers->get('PHP_AUTH_PW');
+    }
+
+    /**
+     * Gets the user info.
+     *
+     * @return string A user name and, optionally, scheme-specific information about how to gain authorization to access the server
+     */
+    public function getUserInfo()
+    {
+        $userinfo = $this->getUser();
+
+        $pass = $this->getPassword();
+        if ('' != $pass) {
+            $userinfo .= ":$pass";
+        }
+
+        return $userinfo;
+    }
+
+    /**
+     * Returns the HTTP host being requested.
+     *
+     * The port name will be appended to the host if it's non-standard.
+     *
+     * @return string
+     */
+    public function getHttpHost()
+    {
+        $scheme = $this->getScheme();
+        $port = $this->getPort();
+
+        if (('http' == $scheme && 80 == $port) || ('https' == $scheme && 443 == $port)) {
+            return $this->getHost();
+        }
+
+        return $this->getHost().':'.$port;
+    }
+
+    /**
+     * Returns the requested URI (path and query string).
+     *
+     * @return string The raw URI (i.e. not URI decoded)
+     */
+    public function getRequestUri()
+    {
+        if (null === $this->requestUri) {
+            $this->requestUri = $this->prepareRequestUri();
+        }
+
+        return $this->requestUri;
+    }
+
+    /**
+     * Gets the scheme and HTTP host.
+     *
+     * If the URL was called with basic authentication, the user
+     * and the password are not added to the generated string.
+     *
+     * @return string The scheme and HTTP host
+     */
+    public function getSchemeAndHttpHost()
+    {
+        return $this->getScheme().'://'.$this->getHttpHost();
+    }
+
+    /**
+     * Generates a normalized URI (URL) for the Request.
+     *
+     * @return string A normalized URI (URL) for the Request
+     *
+     * @see getQueryString()
+     */
+    public function getUri()
+    {
+        if (null !== $qs = $this->getQueryString()) {
+            $qs = '?'.$qs;
+        }
+
+        return $this->getSchemeAndHttpHost().$this->getBaseUrl().$this->getPathInfo().$qs;
+    }
+
+    /**
+     * Generates a normalized URI for the given path.
+     *
+     * @param string $path A path to use instead of the current one
+     *
+     * @return string The normalized URI for the path
+     */
+    public function getUriForPath($path)
+    {
+        return $this->getSchemeAndHttpHost().$this->getBaseUrl().$path;
+    }
+
+    /**
+     * Returns the path as relative reference from the current Request path.
+     *
+     * Only the URIs path component (no schema, host etc.) is relevant and must be given.
+     * Both paths must be absolute and not contain relative parts.
+     * Relative URLs from one resource to another are useful when generating self-contained downloadable document archives.
+     * Furthermore, they can be used to reduce the link size in documents.
+     *
+     * Example target paths, given a base path of "/a/b/c/d":
+     * - "/a/b/c/d"     -> ""
+     * - "/a/b/c/"      -> "./"
+     * - "/a/b/"        -> "../"
+     * - "/a/b/c/other" -> "other"
+     * - "/a/x/y"       -> "../../x/y"
+     *
+     * @param string $path The target path
+     *
+     * @return string The relative target path
+     */
+    public function getRelativeUriForPath($path)
+    {
+        // be sure that we are dealing with an absolute path
+        if (!isset($path[0]) || '/' !== $path[0]) {
+            return $path;
+        }
+
+        if ($path === $basePath = $this->getPathInfo()) {
+            return '';
+        }
+
+        $sourceDirs = explode('/', isset($basePath[0]) && '/' === $basePath[0] ? substr($basePath, 1) : $basePath);
+        $targetDirs = explode('/', isset($path[0]) && '/' === $path[0] ? substr($path, 1) : $path);
+        array_pop($sourceDirs);
+        $targetFile = array_pop($targetDirs);
+
+        foreach ($sourceDirs as $i => $dir) {
+            if (isset($targetDirs[$i]) && $dir === $targetDirs[$i]) {
+                unset($sourceDirs[$i], $targetDirs[$i]);
+            } else {
+                break;
+            }
+        }
+
+        $targetDirs[] = $targetFile;
+        $path = str_repeat('../', count($sourceDirs)).implode('/', $targetDirs);
+
+        // A reference to the same base directory or an empty subdirectory must be prefixed with "./".
+        // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used
+        // as the first segment of a relative-path reference, as it would be mistaken for a scheme name
+        // (see http://tools.ietf.org/html/rfc3986#section-4.2).
+        return !isset($path[0]) || '/' === $path[0]
+            || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos)
+            ? "./$path" : $path;
+    }
+
+    /**
+     * Generates the normalized query string for the Request.
+     *
+     * It builds a normalized query string, where keys/value pairs are alphabetized
+     * and have consistent escaping.
+     *
+     * @return string|null A normalized query string for the Request
+     */
+    public function getQueryString()
+    {
+        $qs = static::normalizeQueryString($this->server->get('QUERY_STRING'));
+
+        return '' === $qs ? null : $qs;
+    }
+
+    /**
+     * Checks whether the request is secure or not.
+     *
+     * This method can read the client protocol from the "X-Forwarded-Proto" header
+     * when trusted proxies were set via "setTrustedProxies()".
+     *
+     * The "X-Forwarded-Proto" header must contain the protocol: "https" or "http".
+     *
+     * If your reverse proxy uses a different header name than "X-Forwarded-Proto"
+     * ("SSL_HTTPS" for instance), configure it via the $trustedHeaderSet
+     * argument of the Request::setTrustedProxies() method instead.
+     *
+     * @return bool
+     */
+    public function isSecure()
+    {
+        if ($this->isFromTrustedProxy() && $proto = $this->getTrustedValues(self::HEADER_CLIENT_PROTO)) {
+            return in_array(strtolower($proto[0]), array('https', 'on', 'ssl', '1'), true);
+        }
+
+        $https = $this->server->get('HTTPS');
+
+        return !empty($https) && 'off' !== strtolower($https);
+    }
+
+    /**
+     * Returns the host name.
+     *
+     * This method can read the client host name from the "X-Forwarded-Host" header
+     * when trusted proxies were set via "setTrustedProxies()".
+     *
+     * The "X-Forwarded-Host" header must contain the client host name.
+     *
+     * If your reverse proxy uses a different header name than "X-Forwarded-Host",
+     * configure it via the $trustedHeaderSet argument of the
+     * Request::setTrustedProxies() method instead.
+     *
+     * @return string
+     *
+     * @throws SuspiciousOperationException when the host name is invalid or not trusted
+     */
+    public function getHost()
+    {
+        if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_HOST)) {
+            $host = $host[0];
+        } elseif (!$host = $this->headers->get('HOST')) {
+            if (!$host = $this->server->get('SERVER_NAME')) {
+                $host = $this->server->get('SERVER_ADDR', '');
+            }
+        }
+
+        // trim and remove port number from host
+        // host is lowercase as per RFC 952/2181
+        $host = strtolower(preg_replace('/:\d+$/', '', trim($host)));
+
+        // as the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user)
+        // check that it does not contain forbidden characters (see RFC 952 and RFC 2181)
+        // use preg_replace() instead of preg_match() to prevent DoS attacks with long host names
+        if ($host && '' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) {
+            if (!$this->isHostValid) {
+                return '';
+            }
+            $this->isHostValid = false;
+
+            throw new SuspiciousOperationException(sprintf('Invalid Host "%s".', $host));
+        }
+
+        if (count(self::$trustedHostPatterns) > 0) {
+            // to avoid host header injection attacks, you should provide a list of trusted host patterns
+
+            if (in_array($host, self::$trustedHosts)) {
+                return $host;
+            }
+
+            foreach (self::$trustedHostPatterns as $pattern) {
+                if (preg_match($pattern, $host)) {
+                    self::$trustedHosts[] = $host;
+
+                    return $host;
+                }
+            }
+
+            if (!$this->isHostValid) {
+                return '';
+            }
+            $this->isHostValid = false;
+
+            throw new SuspiciousOperationException(sprintf('Untrusted Host "%s".', $host));
+        }
+
+        return $host;
+    }
+
+    /**
+     * Sets the request method.
+     *
+     * @param string $method
+     */
+    public function setMethod($method)
+    {
+        $this->method = null;
+        $this->server->set('REQUEST_METHOD', $method);
+    }
+
+    /**
+     * Gets the request "intended" method.
+     *
+     * If the X-HTTP-Method-Override header is set, and if the method is a POST,
+     * then it is used to determine the "real" intended HTTP method.
+     *
+     * The _method request parameter can also be used to determine the HTTP method,
+     * but only if enableHttpMethodParameterOverride() has been called.
+     *
+     * The method is always an uppercased string.
+     *
+     * @return string The request method
+     *
+     * @see getRealMethod()
+     */
+    public function getMethod()
+    {
+        if (null === $this->method) {
+            $this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET'));
+
+            if ('POST' === $this->method) {
+                if ($method = $this->headers->get('X-HTTP-METHOD-OVERRIDE')) {
+                    $this->method = strtoupper($method);
+                } elseif (self::$httpMethodParameterOverride) {
+                    $this->method = strtoupper($this->request->get('_method', $this->query->get('_method', 'POST')));
+                }
+            }
+        }
+
+        return $this->method;
+    }
+
+    /**
+     * Gets the "real" request method.
+     *
+     * @return string The request method
+     *
+     * @see getMethod()
+     */
+    public function getRealMethod()
+    {
+        return strtoupper($this->server->get('REQUEST_METHOD', 'GET'));
+    }
+
+    /**
+     * Gets the mime type associated with the format.
+     *
+     * @param string $format The format
+     *
+     * @return string The associated mime type (null if not found)
+     */
+    public function getMimeType($format)
+    {
+        if (null === static::$formats) {
+            static::initializeFormats();
+        }
+
+        return isset(static::$formats[$format]) ? static::$formats[$format][0] : null;
+    }
+
+    /**
+     * Gets the mime types associated with the format.
+     *
+     * @param string $format The format
+     *
+     * @return array The associated mime types
+     */
+    public static function getMimeTypes($format)
+    {
+        if (null === static::$formats) {
+            static::initializeFormats();
+        }
+
+        return isset(static::$formats[$format]) ? static::$formats[$format] : array();
+    }
+
+    /**
+     * Gets the format associated with the mime type.
+     *
+     * @param string $mimeType The associated mime type
+     *
+     * @return string|null The format (null if not found)
+     */
+    public function getFormat($mimeType)
+    {
+        $canonicalMimeType = null;
+        if (false !== $pos = strpos($mimeType, ';')) {
+            $canonicalMimeType = substr($mimeType, 0, $pos);
+        }
+
+        if (null === static::$formats) {
+            static::initializeFormats();
+        }
+
+        foreach (static::$formats as $format => $mimeTypes) {
+            if (in_array($mimeType, (array) $mimeTypes)) {
+                return $format;
+            }
+            if (null !== $canonicalMimeType && in_array($canonicalMimeType, (array) $mimeTypes)) {
+                return $format;
+            }
+        }
+    }
+
+    /**
+     * Associates a format with mime types.
+     *
+     * @param string       $format    The format
+     * @param string|array $mimeTypes The associated mime types (the preferred one must be the first as it will be used as the content type)
+     */
+    public function setFormat($format, $mimeTypes)
+    {
+        if (null === static::$formats) {
+            static::initializeFormats();
+        }
+
+        static::$formats[$format] = is_array($mimeTypes) ? $mimeTypes : array($mimeTypes);
+    }
+
+    /**
+     * Gets the request format.
+     *
+     * Here is the process to determine the format:
+     *
+     *  * format defined by the user (with setRequestFormat())
+     *  * _format request attribute
+     *  * $default
+     *
+     * @param string $default The default format
+     *
+     * @return string The request format
+     */
+    public function getRequestFormat($default = 'html')
+    {
+        if (null === $this->format) {
+            $this->format = $this->attributes->get('_format');
+        }
+
+        return null === $this->format ? $default : $this->format;
+    }
+
+    /**
+     * Sets the request format.
+     *
+     * @param string $format The request format
+     */
+    public function setRequestFormat($format)
+    {
+        $this->format = $format;
+    }
+
+    /**
+     * Gets the format associated with the request.
+     *
+     * @return string|null The format (null if no content type is present)
+     */
+    public function getContentType()
+    {
+        return $this->getFormat($this->headers->get('CONTENT_TYPE'));
+    }
+
+    /**
+     * Sets the default locale.
+     *
+     * @param string $locale
+     */
+    public function setDefaultLocale($locale)
+    {
+        $this->defaultLocale = $locale;
+
+        if (null === $this->locale) {
+            $this->setPhpDefaultLocale($locale);
+        }
+    }
+
+    /**
+     * Get the default locale.
+     *
+     * @return string
+     */
+    public function getDefaultLocale()
+    {
+        return $this->defaultLocale;
+    }
+
+    /**
+     * Sets the locale.
+     *
+     * @param string $locale
+     */
+    public function setLocale($locale)
+    {
+        $this->setPhpDefaultLocale($this->locale = $locale);
+    }
+
+    /**
+     * Get the locale.
+     *
+     * @return string
+     */
+    public function getLocale()
+    {
+        return null === $this->locale ? $this->defaultLocale : $this->locale;
+    }
+
+    /**
+     * Checks if the request method is of specified type.
+     *
+     * @param string $method Uppercase request method (GET, POST etc)
+     *
+     * @return bool
+     */
+    public function isMethod($method)
+    {
+        return $this->getMethod() === strtoupper($method);
+    }
+
+    /**
+     * Checks whether or not the method is safe.
+     *
+     * @see https://tools.ietf.org/html/rfc7231#section-4.2.1
+     *
+     * @param bool $andCacheable Adds the additional condition that the method should be cacheable. True by default.
+     *
+     * @return bool
+     */
+    public function isMethodSafe(/* $andCacheable = true */)
+    {
+        if (!func_num_args() || func_get_arg(0)) {
+            // This deprecation should be turned into a BadMethodCallException in 4.0 (without adding the argument in the signature)
+            // then setting $andCacheable to false should be deprecated in 4.1
+            @trigger_error('Checking only for cacheable HTTP methods with Symfony\Component\HttpFoundation\Request::isMethodSafe() is deprecated since Symfony 3.2 and will throw an exception in 4.0. Disable checking only for cacheable methods by calling the method with `false` as first argument or use the Request::isMethodCacheable() instead.', E_USER_DEPRECATED);
+
+            return in_array($this->getMethod(), array('GET', 'HEAD'));
+        }
+
+        return in_array($this->getMethod(), array('GET', 'HEAD', 'OPTIONS', 'TRACE'));
+    }
+
+    /**
+     * Checks whether or not the method is idempotent.
+     *
+     * @return bool
+     */
+    public function isMethodIdempotent()
+    {
+        return in_array($this->getMethod(), array('HEAD', 'GET', 'PUT', 'DELETE', 'TRACE', 'OPTIONS', 'PURGE'));
+    }
+
+    /**
+     * Checks whether the method is cacheable or not.
+     *
+     * @see https://tools.ietf.org/html/rfc7231#section-4.2.3
+     *
+     * @return bool
+     */
+    public function isMethodCacheable()
+    {
+        return in_array($this->getMethod(), array('GET', 'HEAD'));
+    }
+
+    /**
+     * Returns the protocol version.
+     *
+     * If the application is behind a proxy, the protocol version used in the
+     * requests between the client and the proxy and between the proxy and the
+     * server might be different. This returns the former (from the "Via" header)
+     * if the proxy is trusted (see "setTrustedProxies()"), otherwise it returns
+     * the latter (from the "SERVER_PROTOCOL" server parameter).
+     *
+     * @return string
+     */
+    public function getProtocolVersion()
+    {
+        if ($this->isFromTrustedProxy()) {
+            preg_match('~^(HTTP/)?([1-9]\.[0-9]) ~', $this->headers->get('Via'), $matches);
+
+            if ($matches) {
+                return 'HTTP/'.$matches[2];
+            }
+        }
+
+        return $this->server->get('SERVER_PROTOCOL');
+    }
+
+    /**
+     * Returns the request body content.
+     *
+     * @param bool $asResource If true, a resource will be returned
+     *
+     * @return string|resource The request body content or a resource to read the body stream
+     *
+     * @throws \LogicException
+     */
+    public function getContent($asResource = false)
+    {
+        $currentContentIsResource = is_resource($this->content);
+        if (\PHP_VERSION_ID < 50600 && false === $this->content) {
+            throw new \LogicException('getContent() can only be called once when using the resource return type and PHP below 5.6.');
+        }
+
+        if (true === $asResource) {
+            if ($currentContentIsResource) {
+                rewind($this->content);
+
+                return $this->content;
+            }
+
+            // Content passed in parameter (test)
+            if (is_string($this->content)) {
+                $resource = fopen('php://temp', 'r+');
+                fwrite($resource, $this->content);
+                rewind($resource);
+
+                return $resource;
+            }
+
+            $this->content = false;
+
+            return fopen('php://input', 'rb');
+        }
+
+        if ($currentContentIsResource) {
+            rewind($this->content);
+
+            return stream_get_contents($this->content);
+        }
+
+        if (null === $this->content || false === $this->content) {
+            $this->content = file_get_contents('php://input');
+        }
+
+        return $this->content;
+    }
+
+    /**
+     * Gets the Etags.
+     *
+     * @return array The entity tags
+     */
+    public function getETags()
+    {
+        return preg_split('/\s*,\s*/', $this->headers->get('if_none_match'), null, PREG_SPLIT_NO_EMPTY);
+    }
+
+    /**
+     * @return bool
+     */
+    public function isNoCache()
+    {
+        return $this->headers->hasCacheControlDirective('no-cache') || 'no-cache' == $this->headers->get('Pragma');
+    }
+
+    /**
+     * Returns the preferred language.
+     *
+     * @param array $locales An array of ordered available locales
+     *
+     * @return string|null The preferred locale
+     */
+    public function getPreferredLanguage(array $locales = null)
+    {
+        $preferredLanguages = $this->getLanguages();
+
+        if (empty($locales)) {
+            return isset($preferredLanguages[0]) ? $preferredLanguages[0] : null;
+        }
+
+        if (!$preferredLanguages) {
+            return $locales[0];
+        }
+
+        $extendedPreferredLanguages = array();
+        foreach ($preferredLanguages as $language) {
+            $extendedPreferredLanguages[] = $language;
+            if (false !== $position = strpos($language, '_')) {
+                $superLanguage = substr($language, 0, $position);
+                if (!in_array($superLanguage, $preferredLanguages)) {
+                    $extendedPreferredLanguages[] = $superLanguage;
+                }
+            }
+        }
+
+        $preferredLanguages = array_values(array_intersect($extendedPreferredLanguages, $locales));
+
+        return isset($preferredLanguages[0]) ? $preferredLanguages[0] : $locales[0];
+    }
+
+    /**
+     * Gets a list of languages acceptable by the client browser.
+     *
+     * @return array Languages ordered in the user browser preferences
+     */
+    public function getLanguages()
+    {
+        if (null !== $this->languages) {
+            return $this->languages;
+        }
+
+        $languages = AcceptHeader::fromString($this->headers->get('Accept-Language'))->all();
+        $this->languages = array();
+        foreach ($languages as $lang => $acceptHeaderItem) {
+            if (false !== strpos($lang, '-')) {
+                $codes = explode('-', $lang);
+                if ('i' === $codes[0]) {
+                    // Language not listed in ISO 639 that are not variants
+                    // of any listed language, which can be registered with the
+                    // i-prefix, such as i-cherokee
+                    if (count($codes) > 1) {
+                        $lang = $codes[1];
+                    }
+                } else {
+                    for ($i = 0, $max = count($codes); $i < $max; ++$i) {
+                        if (0 === $i) {
+                            $lang = strtolower($codes[0]);
+                        } else {
+                            $lang .= '_'.strtoupper($codes[$i]);
+                        }
+                    }
+                }
+            }
+
+            $this->languages[] = $lang;
+        }
+
+        return $this->languages;
+    }
+
+    /**
+     * Gets a list of charsets acceptable by the client browser.
+     *
+     * @return array List of charsets in preferable order
+     */
+    public function getCharsets()
+    {
+        if (null !== $this->charsets) {
+            return $this->charsets;
+        }
+
+        return $this->charsets = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Charset'))->all());
+    }
+
+    /**
+     * Gets a list of encodings acceptable by the client browser.
+     *
+     * @return array List of encodings in preferable order
+     */
+    public function getEncodings()
+    {
+        if (null !== $this->encodings) {
+            return $this->encodings;
+        }
+
+        return $this->encodings = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Encoding'))->all());
+    }
+
+    /**
+     * Gets a list of content types acceptable by the client browser.
+     *
+     * @return array List of content types in preferable order
+     */
+    public function getAcceptableContentTypes()
+    {
+        if (null !== $this->acceptableContentTypes) {
+            return $this->acceptableContentTypes;
+        }
+
+        return $this->acceptableContentTypes = array_keys(AcceptHeader::fromString($this->headers->get('Accept'))->all());
+    }
+
+    /**
+     * Returns true if the request is a XMLHttpRequest.
+     *
+     * It works if your JavaScript library sets an X-Requested-With HTTP header.
+     * It is known to work with common JavaScript frameworks:
+     *
+     * @see http://en.wikipedia.org/wiki/List_of_Ajax_frameworks#JavaScript
+     *
+     * @return bool true if the request is an XMLHttpRequest, false otherwise
+     */
+    public function isXmlHttpRequest()
+    {
+        return 'XMLHttpRequest' == $this->headers->get('X-Requested-With');
+    }
+
+    /*
+     * The following methods are derived from code of the Zend Framework (1.10dev - 2010-01-24)
+     *
+     * Code subject to the new BSD license (http://framework.zend.com/license/new-bsd).
+     *
+     * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+     */
+
+    protected function prepareRequestUri()
+    {
+        $requestUri = '';
+
+        if ($this->headers->has('X_ORIGINAL_URL')) {
+            // IIS with Microsoft Rewrite Module
+            $requestUri = $this->headers->get('X_ORIGINAL_URL');
+            $this->headers->remove('X_ORIGINAL_URL');
+            $this->server->remove('HTTP_X_ORIGINAL_URL');
+            $this->server->remove('UNENCODED_URL');
+            $this->server->remove('IIS_WasUrlRewritten');
+        } elseif ($this->headers->has('X_REWRITE_URL')) {
+            // IIS with ISAPI_Rewrite
+            $requestUri = $this->headers->get('X_REWRITE_URL');
+            $this->headers->remove('X_REWRITE_URL');
+        } elseif ('1' == $this->server->get('IIS_WasUrlRewritten') && '' != $this->server->get('UNENCODED_URL')) {
+            // IIS7 with URL Rewrite: make sure we get the unencoded URL (double slash problem)
+            $requestUri = $this->server->get('UNENCODED_URL');
+            $this->server->remove('UNENCODED_URL');
+            $this->server->remove('IIS_WasUrlRewritten');
+        } elseif ($this->server->has('REQUEST_URI')) {
+            $requestUri = $this->server->get('REQUEST_URI');
+            // HTTP proxy reqs setup request URI with scheme and host [and port] + the URL path, only use URL path
+            $schemeAndHttpHost = $this->getSchemeAndHttpHost();
+            if (0 === strpos($requestUri, $schemeAndHttpHost)) {
+                $requestUri = substr($requestUri, strlen($schemeAndHttpHost));
+            }
+        } elseif ($this->server->has('ORIG_PATH_INFO')) {
+            // IIS 5.0, PHP as CGI
+            $requestUri = $this->server->get('ORIG_PATH_INFO');
+            if ('' != $this->server->get('QUERY_STRING')) {
+                $requestUri .= '?'.$this->server->get('QUERY_STRING');
+            }
+            $this->server->remove('ORIG_PATH_INFO');
+        }
+
+        // normalize the request URI to ease creating sub-requests from this request
+        $this->server->set('REQUEST_URI', $requestUri);
+
+        return $requestUri;
+    }
+
+    /**
+     * Prepares the base URL.
+     *
+     * @return string
+     */
+    protected function prepareBaseUrl()
+    {
+        $filename = basename($this->server->get('SCRIPT_FILENAME'));
+
+        if (basename($this->server->get('SCRIPT_NAME')) === $filename) {
+            $baseUrl = $this->server->get('SCRIPT_NAME');
+        } elseif (basename($this->server->get('PHP_SELF')) === $filename) {
+            $baseUrl = $this->server->get('PHP_SELF');
+        } elseif (basename($this->server->get('ORIG_SCRIPT_NAME')) === $filename) {
+            $baseUrl = $this->server->get('ORIG_SCRIPT_NAME'); // 1and1 shared hosting compatibility
+        } else {
+            // Backtrack up the script_filename to find the portion matching
+            // php_self
+            $path = $this->server->get('PHP_SELF', '');
+            $file = $this->server->get('SCRIPT_FILENAME', '');
+            $segs = explode('/', trim($file, '/'));
+            $segs = array_reverse($segs);
+            $index = 0;
+            $last = count($segs);
+            $baseUrl = '';
+            do {
+                $seg = $segs[$index];
+                $baseUrl = '/'.$seg.$baseUrl;
+                ++$index;
+            } while ($last > $index && (false !== $pos = strpos($path, $baseUrl)) && 0 != $pos);
+        }
+
+        // Does the baseUrl have anything in common with the request_uri?
+        $requestUri = $this->getRequestUri();
+        if ('' !== $requestUri && '/' !== $requestUri[0]) {
+            $requestUri = '/'.$requestUri;
+        }
+
+        if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, $baseUrl)) {
+            // full $baseUrl matches
+            return $prefix;
+        }
+
+        if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, rtrim(dirname($baseUrl), '/'.DIRECTORY_SEPARATOR).'/')) {
+            // directory portion of $baseUrl matches
+            return rtrim($prefix, '/'.DIRECTORY_SEPARATOR);
+        }
+
+        $truncatedRequestUri = $requestUri;
+        if (false !== $pos = strpos($requestUri, '?')) {
+            $truncatedRequestUri = substr($requestUri, 0, $pos);
+        }
+
+        $basename = basename($baseUrl);
+        if (empty($basename) || !strpos(rawurldecode($truncatedRequestUri), $basename)) {
+            // no match whatsoever; set it blank
+            return '';
+        }
+
+        // If using mod_rewrite or ISAPI_Rewrite strip the script filename
+        // out of baseUrl. $pos !== 0 makes sure it is not matching a value
+        // from PATH_INFO or QUERY_STRING
+        if (strlen($requestUri) >= strlen($baseUrl) && (false !== $pos = strpos($requestUri, $baseUrl)) && 0 !== $pos) {
+            $baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl));
+        }
+
+        return rtrim($baseUrl, '/'.DIRECTORY_SEPARATOR);
+    }
+
+    /**
+     * Prepares the base path.
+     *
+     * @return string base path
+     */
+    protected function prepareBasePath()
+    {
+        $baseUrl = $this->getBaseUrl();
+        if (empty($baseUrl)) {
+            return '';
+        }
+
+        $filename = basename($this->server->get('SCRIPT_FILENAME'));
+        if (basename($baseUrl) === $filename) {
+            $basePath = dirname($baseUrl);
+        } else {
+            $basePath = $baseUrl;
+        }
+
+        if ('\\' === DIRECTORY_SEPARATOR) {
+            $basePath = str_replace('\\', '/', $basePath);
+        }
+
+        return rtrim($basePath, '/');
+    }
+
+    /**
+     * Prepares the path info.
+     *
+     * @return string path info
+     */
+    protected function preparePathInfo()
+    {
+        if (null === ($requestUri = $this->getRequestUri())) {
+            return '/';
+        }
+
+        // Remove the query string from REQUEST_URI
+        if (false !== $pos = strpos($requestUri, '?')) {
+            $requestUri = substr($requestUri, 0, $pos);
+        }
+        if ('' !== $requestUri && '/' !== $requestUri[0]) {
+            $requestUri = '/'.$requestUri;
+        }
+
+        if (null === ($baseUrl = $this->getBaseUrl())) {
+            return $requestUri;
+        }
+
+        $pathInfo = substr($requestUri, strlen($baseUrl));
+        if (false === $pathInfo || '' === $pathInfo) {
+            // If substr() returns false then PATH_INFO is set to an empty string
+            return '/';
+        }
+
+        return (string) $pathInfo;
+    }
+
+    /**
+     * Initializes HTTP request formats.
+     */
+    protected static function initializeFormats()
+    {
+        static::$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'),
+            'jsonld' => array('application/ld+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'),
+            'form' => array('application/x-www-form-urlencoded'),
+        );
+    }
+
+    /**
+     * Sets the default PHP locale.
+     *
+     * @param string $locale
+     */
+    private function setPhpDefaultLocale($locale)
+    {
+        // if either the class Locale doesn't exist, or an exception is thrown when
+        // setting the default locale, the intl module is not installed, and
+        // the call can be ignored:
+        try {
+            if (class_exists('Locale', false)) {
+                \Locale::setDefault($locale);
+            }
+        } catch (\Exception $e) {
+        }
+    }
+
+    /*
+     * Returns the prefix as encoded in the string when the string starts with
+     * the given prefix, false otherwise.
+     *
+     * @param string $string The urlencoded string
+     * @param string $prefix The prefix not encoded
+     *
+     * @return string|false The prefix as it is encoded in $string, or false
+     */
+    private function getUrlencodedPrefix($string, $prefix)
+    {
+        if (0 !== strpos(rawurldecode($string), $prefix)) {
+            return false;
+        }
+
+        $len = strlen($prefix);
+
+        if (preg_match(sprintf('#^(%%[[:xdigit:]]{2}|.){%d}#', $len), $string, $match)) {
+            return $match[0];
+        }
+
+        return false;
+    }
+
+    private static function createRequestFromFactory(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
+    {
+        if (self::$requestFactory) {
+            $request = call_user_func(self::$requestFactory, $query, $request, $attributes, $cookies, $files, $server, $content);
+
+            if (!$request instanceof self) {
+                throw new \LogicException('The Request factory must return an instance of Symfony\Component\HttpFoundation\Request.');
+            }
+
+            return $request;
+        }
+
+        return new static($query, $request, $attributes, $cookies, $files, $server, $content);
+    }
+
+    /**
+     * Indicates whether this request originated from a trusted proxy.
+     *
+     * This can be useful to determine whether or not to trust the
+     * contents of a proxy-specific header.
+     *
+     * @return bool true if the request came from a trusted proxy, false otherwise
+     */
+    public function isFromTrustedProxy()
+    {
+        return self::$trustedProxies && IpUtils::checkIp($this->server->get('REMOTE_ADDR'), self::$trustedProxies);
+    }
+
+    private function getTrustedValues($type, $ip = null)
+    {
+        $clientValues = array();
+        $forwardedValues = array();
+
+        if (self::$trustedHeaders[$type] && $this->headers->has(self::$trustedHeaders[$type])) {
+            foreach (explode(',', $this->headers->get(self::$trustedHeaders[$type])) as $v) {
+                $clientValues[] = (self::HEADER_CLIENT_PORT === $type ? '0.0.0.0:' : '').trim($v);
+            }
+        }
+
+        if (self::$trustedHeaders[self::HEADER_FORWARDED] && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) {
+            $forwardedValues = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]);
+            $forwardedValues = preg_match_all(sprintf('{(?:%s)=(?:"?\[?)([a-zA-Z0-9\.:_\-/]*+)}', self::$forwardedParams[$type]), $forwardedValues, $matches) ? $matches[1] : array();
+        }
+
+        if (null !== $ip) {
+            $clientValues = $this->normalizeAndFilterClientIps($clientValues, $ip);
+            $forwardedValues = $this->normalizeAndFilterClientIps($forwardedValues, $ip);
+        }
+
+        if ($forwardedValues === $clientValues || !$clientValues) {
+            return $forwardedValues;
+        }
+
+        if (!$forwardedValues) {
+            return $clientValues;
+        }
+
+        if (!$this->isForwardedValid) {
+            return null !== $ip ? array('0.0.0.0', $ip) : array();
+        }
+        $this->isForwardedValid = false;
+
+        throw new ConflictingHeadersException(sprintf('The request has both a trusted "%s" header and a trusted "%s" header, conflicting with each other. You should either configure your proxy to remove one of them, or configure your project to distrust the offending one.', self::$trustedHeaders[self::HEADER_FORWARDED], self::$trustedHeaders[$type]));
+    }
+
+    private function normalizeAndFilterClientIps(array $clientIps, $ip)
+    {
+        if (!$clientIps) {
+            return array();
+        }
+        $clientIps[] = $ip; // Complete the IP chain with the IP the request actually came from
+        $firstTrustedIp = null;
+
+        foreach ($clientIps as $key => $clientIp) {
+            // Remove port (unfortunately, it does happen)
+            if (preg_match('{((?:\d+\.){3}\d+)\:\d+}', $clientIp, $match)) {
+                $clientIps[$key] = $clientIp = $match[1];
+            }
+
+            if (!filter_var($clientIp, FILTER_VALIDATE_IP)) {
+                unset($clientIps[$key]);
+
+                continue;
+            }
+
+            if (IpUtils::checkIp($clientIp, self::$trustedProxies)) {
+                unset($clientIps[$key]);
+
+                // Fallback to this when the client IP falls into the range of trusted proxies
+                if (null === $firstTrustedIp) {
+                    $firstTrustedIp = $clientIp;
+                }
+            }
+        }
+
+        // Now the IP chain contains only untrusted proxies and the client IP
+        return $clientIps ? array_reverse($clientIps) : array($firstTrustedIp);
+    }
+}
diff --git a/vendor/symfony/http-foundation/RequestMatcher.php b/vendor/symfony/http-foundation/RequestMatcher.php
new file mode 100644
index 0000000000000000000000000000000000000000..076d077c7d0728a399dc8e822d35b83dcb85f931
--- /dev/null
+++ b/vendor/symfony/http-foundation/RequestMatcher.php
@@ -0,0 +1,178 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * RequestMatcher compares a pre-defined set of checks against a Request instance.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class RequestMatcher implements RequestMatcherInterface
+{
+    /**
+     * @var string|null
+     */
+    private $path;
+
+    /**
+     * @var string|null
+     */
+    private $host;
+
+    /**
+     * @var string[]
+     */
+    private $methods = array();
+
+    /**
+     * @var string[]
+     */
+    private $ips = array();
+
+    /**
+     * @var array
+     */
+    private $attributes = array();
+
+    /**
+     * @var string[]
+     */
+    private $schemes = array();
+
+    /**
+     * @param string|null          $path
+     * @param string|null          $host
+     * @param string|string[]|null $methods
+     * @param string|string[]|null $ips
+     * @param array                $attributes
+     * @param string|string[]|null $schemes
+     */
+    public function __construct($path = null, $host = null, $methods = null, $ips = null, array $attributes = array(), $schemes = null)
+    {
+        $this->matchPath($path);
+        $this->matchHost($host);
+        $this->matchMethod($methods);
+        $this->matchIps($ips);
+        $this->matchScheme($schemes);
+
+        foreach ($attributes as $k => $v) {
+            $this->matchAttribute($k, $v);
+        }
+    }
+
+    /**
+     * Adds a check for the HTTP scheme.
+     *
+     * @param string|string[]|null $scheme An HTTP scheme or an array of HTTP schemes
+     */
+    public function matchScheme($scheme)
+    {
+        $this->schemes = null !== $scheme ? array_map('strtolower', (array) $scheme) : array();
+    }
+
+    /**
+     * Adds a check for the URL host name.
+     *
+     * @param string|null $regexp A Regexp
+     */
+    public function matchHost($regexp)
+    {
+        $this->host = $regexp;
+    }
+
+    /**
+     * Adds a check for the URL path info.
+     *
+     * @param string|null $regexp A Regexp
+     */
+    public function matchPath($regexp)
+    {
+        $this->path = $regexp;
+    }
+
+    /**
+     * Adds a check for the client IP.
+     *
+     * @param string $ip A specific IP address or a range specified using IP/netmask like 192.168.1.0/24
+     */
+    public function matchIp($ip)
+    {
+        $this->matchIps($ip);
+    }
+
+    /**
+     * Adds a check for the client IP.
+     *
+     * @param string|string[]|null $ips A specific IP address or a range specified using IP/netmask like 192.168.1.0/24
+     */
+    public function matchIps($ips)
+    {
+        $this->ips = null !== $ips ? (array) $ips : array();
+    }
+
+    /**
+     * Adds a check for the HTTP method.
+     *
+     * @param string|string[]|null $method An HTTP method or an array of HTTP methods
+     */
+    public function matchMethod($method)
+    {
+        $this->methods = null !== $method ? array_map('strtoupper', (array) $method) : array();
+    }
+
+    /**
+     * Adds a check for request attribute.
+     *
+     * @param string $key    The request attribute name
+     * @param string $regexp A Regexp
+     */
+    public function matchAttribute($key, $regexp)
+    {
+        $this->attributes[$key] = $regexp;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function matches(Request $request)
+    {
+        if ($this->schemes && !in_array($request->getScheme(), $this->schemes, true)) {
+            return false;
+        }
+
+        if ($this->methods && !in_array($request->getMethod(), $this->methods, true)) {
+            return false;
+        }
+
+        foreach ($this->attributes as $key => $pattern) {
+            if (!preg_match('{'.$pattern.'}', $request->attributes->get($key))) {
+                return false;
+            }
+        }
+
+        if (null !== $this->path && !preg_match('{'.$this->path.'}', rawurldecode($request->getPathInfo()))) {
+            return false;
+        }
+
+        if (null !== $this->host && !preg_match('{'.$this->host.'}i', $request->getHost())) {
+            return false;
+        }
+
+        if (IpUtils::checkIp($request->getClientIp(), $this->ips)) {
+            return true;
+        }
+
+        // Note to future implementors: add additional checks above the
+        // foreach above or else your check might not be run!
+        return 0 === count($this->ips);
+    }
+}
diff --git a/vendor/symfony/http-foundation/RequestMatcherInterface.php b/vendor/symfony/http-foundation/RequestMatcherInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..c26db3e6f4e66e819f01bf1560bbb933b11c093d
--- /dev/null
+++ b/vendor/symfony/http-foundation/RequestMatcherInterface.php
@@ -0,0 +1,27 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * RequestMatcherInterface is an interface for strategies to match a Request.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+interface RequestMatcherInterface
+{
+    /**
+     * Decides whether the rule(s) implemented by the strategy matches the supplied request.
+     *
+     * @return bool true if the request matches, false otherwise
+     */
+    public function matches(Request $request);
+}
diff --git a/vendor/symfony/http-foundation/RequestStack.php b/vendor/symfony/http-foundation/RequestStack.php
new file mode 100644
index 0000000000000000000000000000000000000000..3d9cfd0c641944728f0d21dcbbdbe89e5d0a3d85
--- /dev/null
+++ b/vendor/symfony/http-foundation/RequestStack.php
@@ -0,0 +1,103 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Request stack that controls the lifecycle of requests.
+ *
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class RequestStack
+{
+    /**
+     * @var Request[]
+     */
+    private $requests = array();
+
+    /**
+     * Pushes a Request on the stack.
+     *
+     * This method should generally not be called directly as the stack
+     * management should be taken care of by the application itself.
+     */
+    public function push(Request $request)
+    {
+        $this->requests[] = $request;
+    }
+
+    /**
+     * Pops the current request from the stack.
+     *
+     * This operation lets the current request go out of scope.
+     *
+     * This method should generally not be called directly as the stack
+     * management should be taken care of by the application itself.
+     *
+     * @return Request|null
+     */
+    public function pop()
+    {
+        if (!$this->requests) {
+            return;
+        }
+
+        return array_pop($this->requests);
+    }
+
+    /**
+     * @return Request|null
+     */
+    public function getCurrentRequest()
+    {
+        return end($this->requests) ?: null;
+    }
+
+    /**
+     * Gets the master Request.
+     *
+     * Be warned that making your code aware of the master request
+     * might make it un-compatible with other features of your framework
+     * like ESI support.
+     *
+     * @return Request|null
+     */
+    public function getMasterRequest()
+    {
+        if (!$this->requests) {
+            return;
+        }
+
+        return $this->requests[0];
+    }
+
+    /**
+     * Returns the parent request of the current.
+     *
+     * Be warned that making your code aware of the parent request
+     * might make it un-compatible with other features of your framework
+     * like ESI support.
+     *
+     * If current Request is the master request, it returns null.
+     *
+     * @return Request|null
+     */
+    public function getParentRequest()
+    {
+        $pos = count($this->requests) - 2;
+
+        if (!isset($this->requests[$pos])) {
+            return;
+        }
+
+        return $this->requests[$pos];
+    }
+}
diff --git a/vendor/symfony/http-foundation/Response.php b/vendor/symfony/http-foundation/Response.php
new file mode 100644
index 0000000000000000000000000000000000000000..6f8a6239593b1dacdb0518350c66060f3ba01b39
--- /dev/null
+++ b/vendor/symfony/http-foundation/Response.php
@@ -0,0 +1,1298 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Response represents an HTTP response.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class Response
+{
+    const HTTP_CONTINUE = 100;
+    const HTTP_SWITCHING_PROTOCOLS = 101;
+    const HTTP_PROCESSING = 102;            // RFC2518
+    const HTTP_OK = 200;
+    const HTTP_CREATED = 201;
+    const HTTP_ACCEPTED = 202;
+    const HTTP_NON_AUTHORITATIVE_INFORMATION = 203;
+    const HTTP_NO_CONTENT = 204;
+    const HTTP_RESET_CONTENT = 205;
+    const HTTP_PARTIAL_CONTENT = 206;
+    const HTTP_MULTI_STATUS = 207;          // RFC4918
+    const HTTP_ALREADY_REPORTED = 208;      // RFC5842
+    const HTTP_IM_USED = 226;               // RFC3229
+    const HTTP_MULTIPLE_CHOICES = 300;
+    const HTTP_MOVED_PERMANENTLY = 301;
+    const HTTP_FOUND = 302;
+    const HTTP_SEE_OTHER = 303;
+    const HTTP_NOT_MODIFIED = 304;
+    const HTTP_USE_PROXY = 305;
+    const HTTP_RESERVED = 306;
+    const HTTP_TEMPORARY_REDIRECT = 307;
+    const HTTP_PERMANENTLY_REDIRECT = 308;  // RFC7238
+    const HTTP_BAD_REQUEST = 400;
+    const HTTP_UNAUTHORIZED = 401;
+    const HTTP_PAYMENT_REQUIRED = 402;
+    const HTTP_FORBIDDEN = 403;
+    const HTTP_NOT_FOUND = 404;
+    const HTTP_METHOD_NOT_ALLOWED = 405;
+    const HTTP_NOT_ACCEPTABLE = 406;
+    const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407;
+    const HTTP_REQUEST_TIMEOUT = 408;
+    const HTTP_CONFLICT = 409;
+    const HTTP_GONE = 410;
+    const HTTP_LENGTH_REQUIRED = 411;
+    const HTTP_PRECONDITION_FAILED = 412;
+    const HTTP_REQUEST_ENTITY_TOO_LARGE = 413;
+    const HTTP_REQUEST_URI_TOO_LONG = 414;
+    const HTTP_UNSUPPORTED_MEDIA_TYPE = 415;
+    const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
+    const HTTP_EXPECTATION_FAILED = 417;
+    const HTTP_I_AM_A_TEAPOT = 418;                                               // RFC2324
+    const HTTP_MISDIRECTED_REQUEST = 421;                                         // RFC7540
+    const HTTP_UNPROCESSABLE_ENTITY = 422;                                        // RFC4918
+    const HTTP_LOCKED = 423;                                                      // RFC4918
+    const HTTP_FAILED_DEPENDENCY = 424;                                           // RFC4918
+    const HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL = 425;   // RFC2817
+    const HTTP_UPGRADE_REQUIRED = 426;                                            // RFC2817
+    const HTTP_PRECONDITION_REQUIRED = 428;                                       // RFC6585
+    const HTTP_TOO_MANY_REQUESTS = 429;                                           // RFC6585
+    const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431;                             // RFC6585
+    const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451;
+    const HTTP_INTERNAL_SERVER_ERROR = 500;
+    const HTTP_NOT_IMPLEMENTED = 501;
+    const HTTP_BAD_GATEWAY = 502;
+    const HTTP_SERVICE_UNAVAILABLE = 503;
+    const HTTP_GATEWAY_TIMEOUT = 504;
+    const HTTP_VERSION_NOT_SUPPORTED = 505;
+    const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506;                        // RFC2295
+    const HTTP_INSUFFICIENT_STORAGE = 507;                                        // RFC4918
+    const HTTP_LOOP_DETECTED = 508;                                               // RFC5842
+    const HTTP_NOT_EXTENDED = 510;                                                // RFC2774
+    const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511;                             // RFC6585
+
+    /**
+     * @var \Symfony\Component\HttpFoundation\ResponseHeaderBag
+     */
+    public $headers;
+
+    /**
+     * @var string
+     */
+    protected $content;
+
+    /**
+     * @var string
+     */
+    protected $version;
+
+    /**
+     * @var int
+     */
+    protected $statusCode;
+
+    /**
+     * @var string
+     */
+    protected $statusText;
+
+    /**
+     * @var string
+     */
+    protected $charset;
+
+    /**
+     * Status codes translation table.
+     *
+     * The list of codes is complete according to the
+     * {@link http://www.iana.org/assignments/http-status-codes/ Hypertext Transfer Protocol (HTTP) Status Code Registry}
+     * (last updated 2016-03-01).
+     *
+     * Unless otherwise noted, the status code is defined in RFC2616.
+     *
+     * @var array
+     */
+    public static $statusTexts = array(
+        100 => 'Continue',
+        101 => 'Switching Protocols',
+        102 => 'Processing',            // RFC2518
+        103 => 'Early Hints',
+        200 => 'OK',
+        201 => 'Created',
+        202 => 'Accepted',
+        203 => 'Non-Authoritative Information',
+        204 => 'No Content',
+        205 => 'Reset Content',
+        206 => 'Partial Content',
+        207 => 'Multi-Status',          // RFC4918
+        208 => 'Already Reported',      // RFC5842
+        226 => 'IM Used',               // RFC3229
+        300 => 'Multiple Choices',
+        301 => 'Moved Permanently',
+        302 => 'Found',
+        303 => 'See Other',
+        304 => 'Not Modified',
+        305 => 'Use Proxy',
+        307 => 'Temporary Redirect',
+        308 => 'Permanent Redirect',    // RFC7238
+        400 => 'Bad Request',
+        401 => 'Unauthorized',
+        402 => 'Payment Required',
+        403 => 'Forbidden',
+        404 => 'Not Found',
+        405 => 'Method Not Allowed',
+        406 => 'Not Acceptable',
+        407 => 'Proxy Authentication Required',
+        408 => 'Request Timeout',
+        409 => 'Conflict',
+        410 => 'Gone',
+        411 => 'Length Required',
+        412 => 'Precondition Failed',
+        413 => 'Payload Too Large',
+        414 => 'URI Too Long',
+        415 => 'Unsupported Media Type',
+        416 => 'Range Not Satisfiable',
+        417 => 'Expectation Failed',
+        418 => 'I\'m a teapot',                                               // RFC2324
+        421 => 'Misdirected Request',                                         // RFC7540
+        422 => 'Unprocessable Entity',                                        // RFC4918
+        423 => 'Locked',                                                      // RFC4918
+        424 => 'Failed Dependency',                                           // RFC4918
+        425 => 'Reserved for WebDAV advanced collections expired proposal',   // RFC2817
+        426 => 'Upgrade Required',                                            // RFC2817
+        428 => 'Precondition Required',                                       // RFC6585
+        429 => 'Too Many Requests',                                           // RFC6585
+        431 => 'Request Header Fields Too Large',                             // RFC6585
+        451 => 'Unavailable For Legal Reasons',                               // RFC7725
+        500 => 'Internal Server Error',
+        501 => 'Not Implemented',
+        502 => 'Bad Gateway',
+        503 => 'Service Unavailable',
+        504 => 'Gateway Timeout',
+        505 => 'HTTP Version Not Supported',
+        506 => 'Variant Also Negotiates',                                     // RFC2295
+        507 => 'Insufficient Storage',                                        // RFC4918
+        508 => 'Loop Detected',                                               // RFC5842
+        510 => 'Not Extended',                                                // RFC2774
+        511 => 'Network Authentication Required',                             // RFC6585
+    );
+
+    /**
+     * @param mixed $content The response content, see setContent()
+     * @param int   $status  The response status code
+     * @param array $headers An array of response headers
+     *
+     * @throws \InvalidArgumentException When the HTTP status code is not valid
+     */
+    public function __construct($content = '', $status = 200, $headers = array())
+    {
+        $this->headers = new ResponseHeaderBag($headers);
+        $this->setContent($content);
+        $this->setStatusCode($status);
+        $this->setProtocolVersion('1.0');
+    }
+
+    /**
+     * Factory method for chainability.
+     *
+     * Example:
+     *
+     *     return Response::create($body, 200)
+     *         ->setSharedMaxAge(300);
+     *
+     * @param mixed $content The response content, see setContent()
+     * @param int   $status  The response status code
+     * @param array $headers An array of response headers
+     *
+     * @return static
+     */
+    public static function create($content = '', $status = 200, $headers = array())
+    {
+        return new static($content, $status, $headers);
+    }
+
+    /**
+     * Returns the Response as an HTTP string.
+     *
+     * The string representation of the Response is the same as the
+     * one that will be sent to the client only if the prepare() method
+     * has been called before.
+     *
+     * @return string The Response as an HTTP string
+     *
+     * @see prepare()
+     */
+    public function __toString()
+    {
+        return
+            sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n".
+            $this->headers."\r\n".
+            $this->getContent();
+    }
+
+    /**
+     * Clones the current Response instance.
+     */
+    public function __clone()
+    {
+        $this->headers = clone $this->headers;
+    }
+
+    /**
+     * Prepares the Response before it is sent to the client.
+     *
+     * This method tweaks the Response to ensure that it is
+     * compliant with RFC 2616. Most of the changes are based on
+     * the Request that is "associated" with this Response.
+     *
+     * @return $this
+     */
+    public function prepare(Request $request)
+    {
+        $headers = $this->headers;
+
+        if ($this->isInformational() || $this->isEmpty()) {
+            $this->setContent(null);
+            $headers->remove('Content-Type');
+            $headers->remove('Content-Length');
+        } else {
+            // Content-type based on the Request
+            if (!$headers->has('Content-Type')) {
+                $format = $request->getRequestFormat();
+                if (null !== $format && $mimeType = $request->getMimeType($format)) {
+                    $headers->set('Content-Type', $mimeType);
+                }
+            }
+
+            // Fix Content-Type
+            $charset = $this->charset ?: 'UTF-8';
+            if (!$headers->has('Content-Type')) {
+                $headers->set('Content-Type', 'text/html; charset='.$charset);
+            } elseif (0 === stripos($headers->get('Content-Type'), 'text/') && false === stripos($headers->get('Content-Type'), 'charset')) {
+                // add the charset
+                $headers->set('Content-Type', $headers->get('Content-Type').'; charset='.$charset);
+            }
+
+            // Fix Content-Length
+            if ($headers->has('Transfer-Encoding')) {
+                $headers->remove('Content-Length');
+            }
+
+            if ($request->isMethod('HEAD')) {
+                // cf. RFC2616 14.13
+                $length = $headers->get('Content-Length');
+                $this->setContent(null);
+                if ($length) {
+                    $headers->set('Content-Length', $length);
+                }
+            }
+        }
+
+        // Fix protocol
+        if ('HTTP/1.0' != $request->server->get('SERVER_PROTOCOL')) {
+            $this->setProtocolVersion('1.1');
+        }
+
+        // Check if we need to send extra expire info headers
+        if ('1.0' == $this->getProtocolVersion() && false !== strpos($this->headers->get('Cache-Control'), 'no-cache')) {
+            $this->headers->set('pragma', 'no-cache');
+            $this->headers->set('expires', -1);
+        }
+
+        $this->ensureIEOverSSLCompatibility($request);
+
+        return $this;
+    }
+
+    /**
+     * Sends HTTP headers.
+     *
+     * @return $this
+     */
+    public function sendHeaders()
+    {
+        // headers have already been sent by the developer
+        if (headers_sent()) {
+            return $this;
+        }
+
+        // headers
+        foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) {
+            foreach ($values as $value) {
+                header($name.': '.$value, false, $this->statusCode);
+            }
+        }
+
+        // status
+        header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);
+
+        // cookies
+        foreach ($this->headers->getCookies() as $cookie) {
+            if ($cookie->isRaw()) {
+                setrawcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly());
+            } else {
+                setcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly());
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * Sends content for the current web response.
+     *
+     * @return $this
+     */
+    public function sendContent()
+    {
+        echo $this->content;
+
+        return $this;
+    }
+
+    /**
+     * Sends HTTP headers and content.
+     *
+     * @return $this
+     */
+    public function send()
+    {
+        $this->sendHeaders();
+        $this->sendContent();
+
+        if (function_exists('fastcgi_finish_request')) {
+            fastcgi_finish_request();
+        } elseif (!\in_array(PHP_SAPI, array('cli', 'phpdbg'), true)) {
+            static::closeOutputBuffers(0, true);
+        }
+
+        return $this;
+    }
+
+    /**
+     * Sets the response content.
+     *
+     * Valid types are strings, numbers, null, and objects that implement a __toString() method.
+     *
+     * @param mixed $content Content that can be cast to string
+     *
+     * @return $this
+     *
+     * @throws \UnexpectedValueException
+     */
+    public function setContent($content)
+    {
+        if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable(array($content, '__toString'))) {
+            throw new \UnexpectedValueException(sprintf('The Response content must be a string or object implementing __toString(), "%s" given.', gettype($content)));
+        }
+
+        $this->content = (string) $content;
+
+        return $this;
+    }
+
+    /**
+     * Gets the current response content.
+     *
+     * @return string Content
+     */
+    public function getContent()
+    {
+        return $this->content;
+    }
+
+    /**
+     * Sets the HTTP protocol version (1.0 or 1.1).
+     *
+     * @param string $version The HTTP protocol version
+     *
+     * @return $this
+     *
+     * @final since version 3.2
+     */
+    public function setProtocolVersion($version)
+    {
+        $this->version = $version;
+
+        return $this;
+    }
+
+    /**
+     * Gets the HTTP protocol version.
+     *
+     * @return string The HTTP protocol version
+     *
+     * @final since version 3.2
+     */
+    public function getProtocolVersion()
+    {
+        return $this->version;
+    }
+
+    /**
+     * Sets the response status code.
+     *
+     * If the status text is null it will be automatically populated for the known
+     * status codes and left empty otherwise.
+     *
+     * @param int   $code HTTP status code
+     * @param mixed $text HTTP status text
+     *
+     * @return $this
+     *
+     * @throws \InvalidArgumentException When the HTTP status code is not valid
+     *
+     * @final since version 3.2
+     */
+    public function setStatusCode($code, $text = null)
+    {
+        $this->statusCode = $code = (int) $code;
+        if ($this->isInvalid()) {
+            throw new \InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $code));
+        }
+
+        if (null === $text) {
+            $this->statusText = isset(self::$statusTexts[$code]) ? self::$statusTexts[$code] : 'unknown status';
+
+            return $this;
+        }
+
+        if (false === $text) {
+            $this->statusText = '';
+
+            return $this;
+        }
+
+        $this->statusText = $text;
+
+        return $this;
+    }
+
+    /**
+     * Retrieves the status code for the current web response.
+     *
+     * @return int Status code
+     *
+     * @final since version 3.2
+     */
+    public function getStatusCode()
+    {
+        return $this->statusCode;
+    }
+
+    /**
+     * Sets the response charset.
+     *
+     * @param string $charset Character set
+     *
+     * @return $this
+     *
+     * @final since version 3.2
+     */
+    public function setCharset($charset)
+    {
+        $this->charset = $charset;
+
+        return $this;
+    }
+
+    /**
+     * Retrieves the response charset.
+     *
+     * @return string Character set
+     *
+     * @final since version 3.2
+     */
+    public function getCharset()
+    {
+        return $this->charset;
+    }
+
+    /**
+     * Returns true if the response is worth caching under any circumstance.
+     *
+     * Responses marked "private" with an explicit Cache-Control directive are
+     * considered uncacheable.
+     *
+     * Responses with neither a freshness lifetime (Expires, max-age) nor cache
+     * validator (Last-Modified, ETag) are considered uncacheable.
+     *
+     * @return bool true if the response is worth caching, false otherwise
+     *
+     * @final since version 3.3
+     */
+    public function isCacheable()
+    {
+        if (!in_array($this->statusCode, array(200, 203, 300, 301, 302, 404, 410))) {
+            return false;
+        }
+
+        if ($this->headers->hasCacheControlDirective('no-store') || $this->headers->getCacheControlDirective('private')) {
+            return false;
+        }
+
+        return $this->isValidateable() || $this->isFresh();
+    }
+
+    /**
+     * Returns true if the response is "fresh".
+     *
+     * Fresh responses may be served from cache without any interaction with the
+     * origin. A response is considered fresh when it includes a Cache-Control/max-age
+     * indicator or Expires header and the calculated age is less than the freshness lifetime.
+     *
+     * @return bool true if the response is fresh, false otherwise
+     *
+     * @final since version 3.3
+     */
+    public function isFresh()
+    {
+        return $this->getTtl() > 0;
+    }
+
+    /**
+     * Returns true if the response includes headers that can be used to validate
+     * the response with the origin server using a conditional GET request.
+     *
+     * @return bool true if the response is validateable, false otherwise
+     *
+     * @final since version 3.3
+     */
+    public function isValidateable()
+    {
+        return $this->headers->has('Last-Modified') || $this->headers->has('ETag');
+    }
+
+    /**
+     * Marks the response as "private".
+     *
+     * It makes the response ineligible for serving other clients.
+     *
+     * @return $this
+     *
+     * @final since version 3.2
+     */
+    public function setPrivate()
+    {
+        $this->headers->removeCacheControlDirective('public');
+        $this->headers->addCacheControlDirective('private');
+
+        return $this;
+    }
+
+    /**
+     * Marks the response as "public".
+     *
+     * It makes the response eligible for serving other clients.
+     *
+     * @return $this
+     *
+     * @final since version 3.2
+     */
+    public function setPublic()
+    {
+        $this->headers->addCacheControlDirective('public');
+        $this->headers->removeCacheControlDirective('private');
+
+        return $this;
+    }
+
+    /**
+     * Marks the response as "immutable".
+     *
+     * @param bool $immutable enables or disables the immutable directive
+     *
+     * @return $this
+     *
+     * @final
+     */
+    public function setImmutable($immutable = true)
+    {
+        if ($immutable) {
+            $this->headers->addCacheControlDirective('immutable');
+        } else {
+            $this->headers->removeCacheControlDirective('immutable');
+        }
+
+        return $this;
+    }
+
+    /**
+     * Returns true if the response is marked as "immutable".
+     *
+     * @return bool returns true if the response is marked as "immutable"; otherwise false
+     *
+     * @final
+     */
+    public function isImmutable()
+    {
+        return $this->headers->hasCacheControlDirective('immutable');
+    }
+
+    /**
+     * Returns true if the response must be revalidated by caches.
+     *
+     * This method indicates that the response must not be served stale by a
+     * cache in any circumstance without first revalidating with the origin.
+     * When present, the TTL of the response should not be overridden to be
+     * greater than the value provided by the origin.
+     *
+     * @return bool true if the response must be revalidated by a cache, false otherwise
+     *
+     * @final since version 3.3
+     */
+    public function mustRevalidate()
+    {
+        return $this->headers->hasCacheControlDirective('must-revalidate') || $this->headers->hasCacheControlDirective('proxy-revalidate');
+    }
+
+    /**
+     * Returns the Date header as a DateTime instance.
+     *
+     * @return \DateTime A \DateTime instance
+     *
+     * @throws \RuntimeException When the header is not parseable
+     *
+     * @final since version 3.2
+     */
+    public function getDate()
+    {
+        return $this->headers->getDate('Date');
+    }
+
+    /**
+     * Sets the Date header.
+     *
+     * @return $this
+     *
+     * @final since version 3.2
+     */
+    public function setDate(\DateTime $date)
+    {
+        $date->setTimezone(new \DateTimeZone('UTC'));
+        $this->headers->set('Date', $date->format('D, d M Y H:i:s').' GMT');
+
+        return $this;
+    }
+
+    /**
+     * Returns the age of the response.
+     *
+     * @return int The age of the response in seconds
+     *
+     * @final since version 3.2
+     */
+    public function getAge()
+    {
+        if (null !== $age = $this->headers->get('Age')) {
+            return (int) $age;
+        }
+
+        return max(time() - $this->getDate()->format('U'), 0);
+    }
+
+    /**
+     * Marks the response stale by setting the Age header to be equal to the maximum age of the response.
+     *
+     * @return $this
+     */
+    public function expire()
+    {
+        if ($this->isFresh()) {
+            $this->headers->set('Age', $this->getMaxAge());
+        }
+
+        return $this;
+    }
+
+    /**
+     * Returns the value of the Expires header as a DateTime instance.
+     *
+     * @return \DateTime|null A DateTime instance or null if the header does not exist
+     *
+     * @final since version 3.2
+     */
+    public function getExpires()
+    {
+        try {
+            return $this->headers->getDate('Expires');
+        } catch (\RuntimeException $e) {
+            // according to RFC 2616 invalid date formats (e.g. "0" and "-1") must be treated as in the past
+            return \DateTime::createFromFormat(DATE_RFC2822, 'Sat, 01 Jan 00 00:00:00 +0000');
+        }
+    }
+
+    /**
+     * Sets the Expires HTTP header with a DateTime instance.
+     *
+     * Passing null as value will remove the header.
+     *
+     * @param \DateTime|null $date A \DateTime instance or null to remove the header
+     *
+     * @return $this
+     *
+     * @final since version 3.2
+     */
+    public function setExpires(\DateTime $date = null)
+    {
+        if (null === $date) {
+            $this->headers->remove('Expires');
+        } else {
+            $date = clone $date;
+            $date->setTimezone(new \DateTimeZone('UTC'));
+            $this->headers->set('Expires', $date->format('D, d M Y H:i:s').' GMT');
+        }
+
+        return $this;
+    }
+
+    /**
+     * Returns the number of seconds after the time specified in the response's Date
+     * header when the response should no longer be considered fresh.
+     *
+     * First, it checks for a s-maxage directive, then a max-age directive, and then it falls
+     * back on an expires header. It returns null when no maximum age can be established.
+     *
+     * @return int|null Number of seconds
+     *
+     * @final since version 3.2
+     */
+    public function getMaxAge()
+    {
+        if ($this->headers->hasCacheControlDirective('s-maxage')) {
+            return (int) $this->headers->getCacheControlDirective('s-maxage');
+        }
+
+        if ($this->headers->hasCacheControlDirective('max-age')) {
+            return (int) $this->headers->getCacheControlDirective('max-age');
+        }
+
+        if (null !== $this->getExpires()) {
+            return $this->getExpires()->format('U') - $this->getDate()->format('U');
+        }
+    }
+
+    /**
+     * Sets the number of seconds after which the response should no longer be considered fresh.
+     *
+     * This methods sets the Cache-Control max-age directive.
+     *
+     * @param int $value Number of seconds
+     *
+     * @return $this
+     *
+     * @final since version 3.2
+     */
+    public function setMaxAge($value)
+    {
+        $this->headers->addCacheControlDirective('max-age', $value);
+
+        return $this;
+    }
+
+    /**
+     * Sets the number of seconds after which the response should no longer be considered fresh by shared caches.
+     *
+     * This methods sets the Cache-Control s-maxage directive.
+     *
+     * @param int $value Number of seconds
+     *
+     * @return $this
+     *
+     * @final since version 3.2
+     */
+    public function setSharedMaxAge($value)
+    {
+        $this->setPublic();
+        $this->headers->addCacheControlDirective('s-maxage', $value);
+
+        return $this;
+    }
+
+    /**
+     * Returns the response's time-to-live in seconds.
+     *
+     * It returns null when no freshness information is present in the response.
+     *
+     * When the responses TTL is <= 0, the response may not be served from cache without first
+     * revalidating with the origin.
+     *
+     * @return int|null The TTL in seconds
+     *
+     * @final since version 3.2
+     */
+    public function getTtl()
+    {
+        if (null !== $maxAge = $this->getMaxAge()) {
+            return $maxAge - $this->getAge();
+        }
+    }
+
+    /**
+     * Sets the response's time-to-live for shared caches.
+     *
+     * This method adjusts the Cache-Control/s-maxage directive.
+     *
+     * @param int $seconds Number of seconds
+     *
+     * @return $this
+     *
+     * @final since version 3.2
+     */
+    public function setTtl($seconds)
+    {
+        $this->setSharedMaxAge($this->getAge() + $seconds);
+
+        return $this;
+    }
+
+    /**
+     * Sets the response's time-to-live for private/client caches.
+     *
+     * This method adjusts the Cache-Control/max-age directive.
+     *
+     * @param int $seconds Number of seconds
+     *
+     * @return $this
+     *
+     * @final since version 3.2
+     */
+    public function setClientTtl($seconds)
+    {
+        $this->setMaxAge($this->getAge() + $seconds);
+
+        return $this;
+    }
+
+    /**
+     * Returns the Last-Modified HTTP header as a DateTime instance.
+     *
+     * @return \DateTime|null A DateTime instance or null if the header does not exist
+     *
+     * @throws \RuntimeException When the HTTP header is not parseable
+     *
+     * @final since version 3.2
+     */
+    public function getLastModified()
+    {
+        return $this->headers->getDate('Last-Modified');
+    }
+
+    /**
+     * Sets the Last-Modified HTTP header with a DateTime instance.
+     *
+     * Passing null as value will remove the header.
+     *
+     * @param \DateTime|null $date A \DateTime instance or null to remove the header
+     *
+     * @return $this
+     *
+     * @final since version 3.2
+     */
+    public function setLastModified(\DateTime $date = null)
+    {
+        if (null === $date) {
+            $this->headers->remove('Last-Modified');
+        } else {
+            $date = clone $date;
+            $date->setTimezone(new \DateTimeZone('UTC'));
+            $this->headers->set('Last-Modified', $date->format('D, d M Y H:i:s').' GMT');
+        }
+
+        return $this;
+    }
+
+    /**
+     * Returns the literal value of the ETag HTTP header.
+     *
+     * @return string|null The ETag HTTP header or null if it does not exist
+     *
+     * @final since version 3.2
+     */
+    public function getEtag()
+    {
+        return $this->headers->get('ETag');
+    }
+
+    /**
+     * Sets the ETag value.
+     *
+     * @param string|null $etag The ETag unique identifier or null to remove the header
+     * @param bool        $weak Whether you want a weak ETag or not
+     *
+     * @return $this
+     *
+     * @final since version 3.2
+     */
+    public function setEtag($etag = null, $weak = false)
+    {
+        if (null === $etag) {
+            $this->headers->remove('Etag');
+        } else {
+            if (0 !== strpos($etag, '"')) {
+                $etag = '"'.$etag.'"';
+            }
+
+            $this->headers->set('ETag', (true === $weak ? 'W/' : '').$etag);
+        }
+
+        return $this;
+    }
+
+    /**
+     * Sets the response's cache headers (validation and/or expiration).
+     *
+     * Available options are: etag, last_modified, max_age, s_maxage, private, public and immutable.
+     *
+     * @param array $options An array of cache options
+     *
+     * @return $this
+     *
+     * @throws \InvalidArgumentException
+     *
+     * @final since version 3.3
+     */
+    public function setCache(array $options)
+    {
+        if ($diff = array_diff(array_keys($options), array('etag', 'last_modified', 'max_age', 's_maxage', 'private', 'public', 'immutable'))) {
+            throw new \InvalidArgumentException(sprintf('Response does not support the following options: "%s".', implode('", "', array_values($diff))));
+        }
+
+        if (isset($options['etag'])) {
+            $this->setEtag($options['etag']);
+        }
+
+        if (isset($options['last_modified'])) {
+            $this->setLastModified($options['last_modified']);
+        }
+
+        if (isset($options['max_age'])) {
+            $this->setMaxAge($options['max_age']);
+        }
+
+        if (isset($options['s_maxage'])) {
+            $this->setSharedMaxAge($options['s_maxage']);
+        }
+
+        if (isset($options['public'])) {
+            if ($options['public']) {
+                $this->setPublic();
+            } else {
+                $this->setPrivate();
+            }
+        }
+
+        if (isset($options['private'])) {
+            if ($options['private']) {
+                $this->setPrivate();
+            } else {
+                $this->setPublic();
+            }
+        }
+
+        if (isset($options['immutable'])) {
+            $this->setImmutable((bool) $options['immutable']);
+        }
+
+        return $this;
+    }
+
+    /**
+     * Modifies the response so that it conforms to the rules defined for a 304 status code.
+     *
+     * This sets the status, removes the body, and discards any headers
+     * that MUST NOT be included in 304 responses.
+     *
+     * @return $this
+     *
+     * @see http://tools.ietf.org/html/rfc2616#section-10.3.5
+     *
+     * @final since version 3.3
+     */
+    public function setNotModified()
+    {
+        $this->setStatusCode(304);
+        $this->setContent(null);
+
+        // remove headers that MUST NOT be included with 304 Not Modified responses
+        foreach (array('Allow', 'Content-Encoding', 'Content-Language', 'Content-Length', 'Content-MD5', 'Content-Type', 'Last-Modified') as $header) {
+            $this->headers->remove($header);
+        }
+
+        return $this;
+    }
+
+    /**
+     * Returns true if the response includes a Vary header.
+     *
+     * @return bool true if the response includes a Vary header, false otherwise
+     *
+     * @final since version 3.2
+     */
+    public function hasVary()
+    {
+        return null !== $this->headers->get('Vary');
+    }
+
+    /**
+     * Returns an array of header names given in the Vary header.
+     *
+     * @return array An array of Vary names
+     *
+     * @final since version 3.2
+     */
+    public function getVary()
+    {
+        if (!$vary = $this->headers->get('Vary', null, false)) {
+            return array();
+        }
+
+        $ret = array();
+        foreach ($vary as $item) {
+            $ret = array_merge($ret, preg_split('/[\s,]+/', $item));
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Sets the Vary header.
+     *
+     * @param string|array $headers
+     * @param bool         $replace Whether to replace the actual value or not (true by default)
+     *
+     * @return $this
+     *
+     * @final since version 3.2
+     */
+    public function setVary($headers, $replace = true)
+    {
+        $this->headers->set('Vary', $headers, $replace);
+
+        return $this;
+    }
+
+    /**
+     * Determines if the Response validators (ETag, Last-Modified) match
+     * a conditional value specified in the Request.
+     *
+     * If the Response is not modified, it sets the status code to 304 and
+     * removes the actual content by calling the setNotModified() method.
+     *
+     * @return bool true if the Response validators match the Request, false otherwise
+     *
+     * @final since version 3.3
+     */
+    public function isNotModified(Request $request)
+    {
+        if (!$request->isMethodCacheable()) {
+            return false;
+        }
+
+        $notModified = false;
+        $lastModified = $this->headers->get('Last-Modified');
+        $modifiedSince = $request->headers->get('If-Modified-Since');
+
+        if ($etags = $request->getETags()) {
+            $notModified = in_array($this->getEtag(), $etags) || in_array('*', $etags);
+        }
+
+        if ($modifiedSince && $lastModified) {
+            $notModified = strtotime($modifiedSince) >= strtotime($lastModified) && (!$etags || $notModified);
+        }
+
+        if ($notModified) {
+            $this->setNotModified();
+        }
+
+        return $notModified;
+    }
+
+    /**
+     * Is response invalid?
+     *
+     * @return bool
+     *
+     * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
+     *
+     * @final since version 3.2
+     */
+    public function isInvalid()
+    {
+        return $this->statusCode < 100 || $this->statusCode >= 600;
+    }
+
+    /**
+     * Is response informative?
+     *
+     * @return bool
+     *
+     * @final since version 3.3
+     */
+    public function isInformational()
+    {
+        return $this->statusCode >= 100 && $this->statusCode < 200;
+    }
+
+    /**
+     * Is response successful?
+     *
+     * @return bool
+     *
+     * @final since version 3.2
+     */
+    public function isSuccessful()
+    {
+        return $this->statusCode >= 200 && $this->statusCode < 300;
+    }
+
+    /**
+     * Is the response a redirect?
+     *
+     * @return bool
+     *
+     * @final since version 3.2
+     */
+    public function isRedirection()
+    {
+        return $this->statusCode >= 300 && $this->statusCode < 400;
+    }
+
+    /**
+     * Is there a client error?
+     *
+     * @return bool
+     *
+     * @final since version 3.2
+     */
+    public function isClientError()
+    {
+        return $this->statusCode >= 400 && $this->statusCode < 500;
+    }
+
+    /**
+     * Was there a server side error?
+     *
+     * @return bool
+     *
+     * @final since version 3.3
+     */
+    public function isServerError()
+    {
+        return $this->statusCode >= 500 && $this->statusCode < 600;
+    }
+
+    /**
+     * Is the response OK?
+     *
+     * @return bool
+     *
+     * @final since version 3.2
+     */
+    public function isOk()
+    {
+        return 200 === $this->statusCode;
+    }
+
+    /**
+     * Is the response forbidden?
+     *
+     * @return bool
+     *
+     * @final since version 3.2
+     */
+    public function isForbidden()
+    {
+        return 403 === $this->statusCode;
+    }
+
+    /**
+     * Is the response a not found error?
+     *
+     * @return bool
+     *
+     * @final since version 3.2
+     */
+    public function isNotFound()
+    {
+        return 404 === $this->statusCode;
+    }
+
+    /**
+     * Is the response a redirect of some form?
+     *
+     * @param string $location
+     *
+     * @return bool
+     *
+     * @final since version 3.2
+     */
+    public function isRedirect($location = null)
+    {
+        return in_array($this->statusCode, array(201, 301, 302, 303, 307, 308)) && (null === $location ?: $location == $this->headers->get('Location'));
+    }
+
+    /**
+     * Is the response empty?
+     *
+     * @return bool
+     *
+     * @final since version 3.2
+     */
+    public function isEmpty()
+    {
+        return in_array($this->statusCode, array(204, 304));
+    }
+
+    /**
+     * Cleans or flushes output buffers up to target level.
+     *
+     * Resulting level can be greater than target level if a non-removable buffer has been encountered.
+     *
+     * @param int  $targetLevel The target output buffering level
+     * @param bool $flush       Whether to flush or clean the buffers
+     *
+     * @final since version 3.3
+     */
+    public static function closeOutputBuffers($targetLevel, $flush)
+    {
+        $status = ob_get_status(true);
+        $level = count($status);
+        // PHP_OUTPUT_HANDLER_* are not defined on HHVM 3.3
+        $flags = defined('PHP_OUTPUT_HANDLER_REMOVABLE') ? PHP_OUTPUT_HANDLER_REMOVABLE | ($flush ? PHP_OUTPUT_HANDLER_FLUSHABLE : PHP_OUTPUT_HANDLER_CLEANABLE) : -1;
+
+        while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || ($s['flags'] & $flags) === $flags : $s['del'])) {
+            if ($flush) {
+                ob_end_flush();
+            } else {
+                ob_end_clean();
+            }
+        }
+    }
+
+    /**
+     * Checks if we need to remove Cache-Control for SSL encrypted downloads when using IE < 9.
+     *
+     * @see http://support.microsoft.com/kb/323308
+     *
+     * @final since version 3.3
+     */
+    protected function ensureIEOverSSLCompatibility(Request $request)
+    {
+        if (false !== stripos($this->headers->get('Content-Disposition'), 'attachment') && 1 == preg_match('/MSIE (.*?);/i', $request->server->get('HTTP_USER_AGENT'), $match) && true === $request->isSecure()) {
+            if ((int) preg_replace('/(MSIE )(.*?);/', '$2', $match[0]) < 9) {
+                $this->headers->remove('Cache-Control');
+            }
+        }
+    }
+}
diff --git a/vendor/symfony/http-foundation/ResponseHeaderBag.php b/vendor/symfony/http-foundation/ResponseHeaderBag.php
new file mode 100644
index 0000000000000000000000000000000000000000..11a859326b047d2cd3ab6401a9fab116b16801a6
--- /dev/null
+++ b/vendor/symfony/http-foundation/ResponseHeaderBag.php
@@ -0,0 +1,340 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * ResponseHeaderBag is a container for Response HTTP headers.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class ResponseHeaderBag extends HeaderBag
+{
+    const COOKIES_FLAT = 'flat';
+    const COOKIES_ARRAY = 'array';
+
+    const DISPOSITION_ATTACHMENT = 'attachment';
+    const DISPOSITION_INLINE = 'inline';
+
+    protected $computedCacheControl = array();
+    protected $cookies = array();
+    protected $headerNames = array();
+
+    public function __construct(array $headers = array())
+    {
+        parent::__construct($headers);
+
+        if (!isset($this->headers['cache-control'])) {
+            $this->set('Cache-Control', '');
+        }
+
+        /* RFC2616 - 14.18 says all Responses need to have a Date */
+        if (!isset($this->headers['date'])) {
+            $this->initDate();
+        }
+    }
+
+    /**
+     * Returns the headers, with original capitalizations.
+     *
+     * @return array An array of headers
+     */
+    public function allPreserveCase()
+    {
+        $headers = array();
+        foreach ($this->all() as $name => $value) {
+            $headers[isset($this->headerNames[$name]) ? $this->headerNames[$name] : $name] = $value;
+        }
+
+        return $headers;
+    }
+
+    public function allPreserveCaseWithoutCookies()
+    {
+        $headers = $this->allPreserveCase();
+        if (isset($this->headerNames['set-cookie'])) {
+            unset($headers[$this->headerNames['set-cookie']]);
+        }
+
+        return $headers;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function replace(array $headers = array())
+    {
+        $this->headerNames = array();
+
+        parent::replace($headers);
+
+        if (!isset($this->headers['cache-control'])) {
+            $this->set('Cache-Control', '');
+        }
+
+        if (!isset($this->headers['date'])) {
+            $this->initDate();
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function all()
+    {
+        $headers = parent::all();
+        foreach ($this->getCookies() as $cookie) {
+            $headers['set-cookie'][] = (string) $cookie;
+        }
+
+        return $headers;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function set($key, $values, $replace = true)
+    {
+        $uniqueKey = str_replace('_', '-', strtolower($key));
+
+        if ('set-cookie' === $uniqueKey) {
+            if ($replace) {
+                $this->cookies = array();
+            }
+            foreach ((array) $values as $cookie) {
+                $this->setCookie(Cookie::fromString($cookie));
+            }
+            $this->headerNames[$uniqueKey] = $key;
+
+            return;
+        }
+
+        $this->headerNames[$uniqueKey] = $key;
+
+        parent::set($key, $values, $replace);
+
+        // ensure the cache-control header has sensible defaults
+        if (\in_array($uniqueKey, array('cache-control', 'etag', 'last-modified', 'expires'), true)) {
+            $computed = $this->computeCacheControlValue();
+            $this->headers['cache-control'] = array($computed);
+            $this->headerNames['cache-control'] = 'Cache-Control';
+            $this->computedCacheControl = $this->parseCacheControl($computed);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function remove($key)
+    {
+        $uniqueKey = str_replace('_', '-', strtolower($key));
+        unset($this->headerNames[$uniqueKey]);
+
+        if ('set-cookie' === $uniqueKey) {
+            $this->cookies = array();
+
+            return;
+        }
+
+        parent::remove($key);
+
+        if ('cache-control' === $uniqueKey) {
+            $this->computedCacheControl = array();
+        }
+
+        if ('date' === $uniqueKey) {
+            $this->initDate();
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasCacheControlDirective($key)
+    {
+        return array_key_exists($key, $this->computedCacheControl);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getCacheControlDirective($key)
+    {
+        return array_key_exists($key, $this->computedCacheControl) ? $this->computedCacheControl[$key] : null;
+    }
+
+    public function setCookie(Cookie $cookie)
+    {
+        $this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie;
+        $this->headerNames['set-cookie'] = 'Set-Cookie';
+    }
+
+    /**
+     * Removes a cookie from the array, but does not unset it in the browser.
+     *
+     * @param string $name
+     * @param string $path
+     * @param string $domain
+     */
+    public function removeCookie($name, $path = '/', $domain = null)
+    {
+        if (null === $path) {
+            $path = '/';
+        }
+
+        unset($this->cookies[$domain][$path][$name]);
+
+        if (empty($this->cookies[$domain][$path])) {
+            unset($this->cookies[$domain][$path]);
+
+            if (empty($this->cookies[$domain])) {
+                unset($this->cookies[$domain]);
+            }
+        }
+
+        if (empty($this->cookies)) {
+            unset($this->headerNames['set-cookie']);
+        }
+    }
+
+    /**
+     * Returns an array with all cookies.
+     *
+     * @param string $format
+     *
+     * @return array
+     *
+     * @throws \InvalidArgumentException When the $format is invalid
+     */
+    public function getCookies($format = self::COOKIES_FLAT)
+    {
+        if (!in_array($format, array(self::COOKIES_FLAT, self::COOKIES_ARRAY))) {
+            throw new \InvalidArgumentException(sprintf('Format "%s" invalid (%s).', $format, implode(', ', array(self::COOKIES_FLAT, self::COOKIES_ARRAY))));
+        }
+
+        if (self::COOKIES_ARRAY === $format) {
+            return $this->cookies;
+        }
+
+        $flattenedCookies = array();
+        foreach ($this->cookies as $path) {
+            foreach ($path as $cookies) {
+                foreach ($cookies as $cookie) {
+                    $flattenedCookies[] = $cookie;
+                }
+            }
+        }
+
+        return $flattenedCookies;
+    }
+
+    /**
+     * Clears a cookie in the browser.
+     *
+     * @param string $name
+     * @param string $path
+     * @param string $domain
+     * @param bool   $secure
+     * @param bool   $httpOnly
+     */
+    public function clearCookie($name, $path = '/', $domain = null, $secure = false, $httpOnly = true)
+    {
+        $this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly));
+    }
+
+    /**
+     * Generates a HTTP Content-Disposition field-value.
+     *
+     * @param string $disposition      One of "inline" or "attachment"
+     * @param string $filename         A unicode string
+     * @param string $filenameFallback A string containing only ASCII characters that
+     *                                 is semantically equivalent to $filename. If the filename is already ASCII,
+     *                                 it can be omitted, or just copied from $filename
+     *
+     * @return string A string suitable for use as a Content-Disposition field-value
+     *
+     * @throws \InvalidArgumentException
+     *
+     * @see RFC 6266
+     */
+    public function makeDisposition($disposition, $filename, $filenameFallback = '')
+    {
+        if (!in_array($disposition, array(self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE))) {
+            throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE));
+        }
+
+        if ('' == $filenameFallback) {
+            $filenameFallback = $filename;
+        }
+
+        // filenameFallback is not ASCII.
+        if (!preg_match('/^[\x20-\x7e]*$/', $filenameFallback)) {
+            throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.');
+        }
+
+        // percent characters aren't safe in fallback.
+        if (false !== strpos($filenameFallback, '%')) {
+            throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.');
+        }
+
+        // path separators aren't allowed in either.
+        if (false !== strpos($filename, '/') || false !== strpos($filename, '\\') || false !== strpos($filenameFallback, '/') || false !== strpos($filenameFallback, '\\')) {
+            throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.');
+        }
+
+        $output = sprintf('%s; filename="%s"', $disposition, str_replace('"', '\\"', $filenameFallback));
+
+        if ($filename !== $filenameFallback) {
+            $output .= sprintf("; filename*=utf-8''%s", rawurlencode($filename));
+        }
+
+        return $output;
+    }
+
+    /**
+     * Returns the calculated value of the cache-control header.
+     *
+     * This considers several other headers and calculates or modifies the
+     * cache-control header to a sensible, conservative value.
+     *
+     * @return string
+     */
+    protected function computeCacheControlValue()
+    {
+        if (!$this->cacheControl && !$this->has('ETag') && !$this->has('Last-Modified') && !$this->has('Expires')) {
+            return 'no-cache, private';
+        }
+
+        if (!$this->cacheControl) {
+            // conservative by default
+            return 'private, must-revalidate';
+        }
+
+        $header = $this->getCacheControlHeader();
+        if (isset($this->cacheControl['public']) || isset($this->cacheControl['private'])) {
+            return $header;
+        }
+
+        // public if s-maxage is defined, private otherwise
+        if (!isset($this->cacheControl['s-maxage'])) {
+            return $header.', private';
+        }
+
+        return $header;
+    }
+
+    private function initDate()
+    {
+        $now = \DateTime::createFromFormat('U', time());
+        $now->setTimezone(new \DateTimeZone('UTC'));
+        $this->set('Date', $now->format('D, d M Y H:i:s').' GMT');
+    }
+}
diff --git a/vendor/symfony/http-foundation/ServerBag.php b/vendor/symfony/http-foundation/ServerBag.php
new file mode 100644
index 0000000000000000000000000000000000000000..19d2022ef7ddb4ccb91651a34547bf20458830cb
--- /dev/null
+++ b/vendor/symfony/http-foundation/ServerBag.php
@@ -0,0 +1,102 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * ServerBag is a container for HTTP headers from the $_SERVER variable.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Bulat Shakirzyanov <mallluhuct@gmail.com>
+ * @author Robert Kiss <kepten@gmail.com>
+ */
+class ServerBag extends ParameterBag
+{
+    /**
+     * Gets the HTTP headers.
+     *
+     * @return array
+     */
+    public function getHeaders()
+    {
+        $headers = array();
+        $contentHeaders = array('CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true);
+        foreach ($this->parameters as $key => $value) {
+            if (0 === strpos($key, 'HTTP_')) {
+                $headers[substr($key, 5)] = $value;
+            }
+            // CONTENT_* are not prefixed with HTTP_
+            elseif (isset($contentHeaders[$key])) {
+                $headers[$key] = $value;
+            }
+        }
+
+        if (isset($this->parameters['PHP_AUTH_USER'])) {
+            $headers['PHP_AUTH_USER'] = $this->parameters['PHP_AUTH_USER'];
+            $headers['PHP_AUTH_PW'] = isset($this->parameters['PHP_AUTH_PW']) ? $this->parameters['PHP_AUTH_PW'] : '';
+        } else {
+            /*
+             * php-cgi under Apache does not pass HTTP Basic user/pass to PHP by default
+             * For this workaround to work, add these lines to your .htaccess file:
+             * RewriteCond %{HTTP:Authorization} ^(.+)$
+             * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
+             *
+             * A sample .htaccess file:
+             * RewriteEngine On
+             * RewriteCond %{HTTP:Authorization} ^(.+)$
+             * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
+             * RewriteCond %{REQUEST_FILENAME} !-f
+             * RewriteRule ^(.*)$ app.php [QSA,L]
+             */
+
+            $authorizationHeader = null;
+            if (isset($this->parameters['HTTP_AUTHORIZATION'])) {
+                $authorizationHeader = $this->parameters['HTTP_AUTHORIZATION'];
+            } elseif (isset($this->parameters['REDIRECT_HTTP_AUTHORIZATION'])) {
+                $authorizationHeader = $this->parameters['REDIRECT_HTTP_AUTHORIZATION'];
+            }
+
+            if (null !== $authorizationHeader) {
+                if (0 === stripos($authorizationHeader, 'basic ')) {
+                    // Decode AUTHORIZATION header into PHP_AUTH_USER and PHP_AUTH_PW when authorization header is basic
+                    $exploded = explode(':', base64_decode(substr($authorizationHeader, 6)), 2);
+                    if (2 == count($exploded)) {
+                        list($headers['PHP_AUTH_USER'], $headers['PHP_AUTH_PW']) = $exploded;
+                    }
+                } elseif (empty($this->parameters['PHP_AUTH_DIGEST']) && (0 === stripos($authorizationHeader, 'digest '))) {
+                    // In some circumstances PHP_AUTH_DIGEST needs to be set
+                    $headers['PHP_AUTH_DIGEST'] = $authorizationHeader;
+                    $this->parameters['PHP_AUTH_DIGEST'] = $authorizationHeader;
+                } elseif (0 === stripos($authorizationHeader, 'bearer ')) {
+                    /*
+                     * XXX: Since there is no PHP_AUTH_BEARER in PHP predefined variables,
+                     *      I'll just set $headers['AUTHORIZATION'] here.
+                     *      http://php.net/manual/en/reserved.variables.server.php
+                     */
+                    $headers['AUTHORIZATION'] = $authorizationHeader;
+                }
+            }
+        }
+
+        if (isset($headers['AUTHORIZATION'])) {
+            return $headers;
+        }
+
+        // PHP_AUTH_USER/PHP_AUTH_PW
+        if (isset($headers['PHP_AUTH_USER'])) {
+            $headers['AUTHORIZATION'] = 'Basic '.base64_encode($headers['PHP_AUTH_USER'].':'.$headers['PHP_AUTH_PW']);
+        } elseif (isset($headers['PHP_AUTH_DIGEST'])) {
+            $headers['AUTHORIZATION'] = $headers['PHP_AUTH_DIGEST'];
+        }
+
+        return $headers;
+    }
+}
diff --git a/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php b/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php
new file mode 100644
index 0000000000000000000000000000000000000000..ea1fda290fdfe94d2d16006ef3d31f56f5b60805
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php
@@ -0,0 +1,148 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Attribute;
+
+/**
+ * This class relates to session attribute storage.
+ */
+class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Countable
+{
+    private $name = 'attributes';
+    private $storageKey;
+
+    protected $attributes = array();
+
+    /**
+     * @param string $storageKey The key used to store attributes in the session
+     */
+    public function __construct($storageKey = '_sf2_attributes')
+    {
+        $this->storageKey = $storageKey;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    public function setName($name)
+    {
+        $this->name = $name;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function initialize(array &$attributes)
+    {
+        $this->attributes = &$attributes;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getStorageKey()
+    {
+        return $this->storageKey;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function has($name)
+    {
+        return array_key_exists($name, $this->attributes);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function get($name, $default = null)
+    {
+        return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function set($name, $value)
+    {
+        $this->attributes[$name] = $value;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function all()
+    {
+        return $this->attributes;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function replace(array $attributes)
+    {
+        $this->attributes = array();
+        foreach ($attributes as $key => $value) {
+            $this->set($key, $value);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function remove($name)
+    {
+        $retval = null;
+        if (array_key_exists($name, $this->attributes)) {
+            $retval = $this->attributes[$name];
+            unset($this->attributes[$name]);
+        }
+
+        return $retval;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function clear()
+    {
+        $return = $this->attributes;
+        $this->attributes = array();
+
+        return $return;
+    }
+
+    /**
+     * Returns an iterator for attributes.
+     *
+     * @return \ArrayIterator An \ArrayIterator instance
+     */
+    public function getIterator()
+    {
+        return new \ArrayIterator($this->attributes);
+    }
+
+    /**
+     * Returns the number of attributes.
+     *
+     * @return int The number of attributes
+     */
+    public function count()
+    {
+        return count($this->attributes);
+    }
+}
diff --git a/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php b/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..0d8d17991be704f5f64d41b13bfacc6a70944393
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php
@@ -0,0 +1,72 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Attribute;
+
+use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
+
+/**
+ * Attributes store.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+interface AttributeBagInterface extends SessionBagInterface
+{
+    /**
+     * Checks if an attribute is defined.
+     *
+     * @param string $name The attribute name
+     *
+     * @return bool true if the attribute is defined, false otherwise
+     */
+    public function has($name);
+
+    /**
+     * Returns an attribute.
+     *
+     * @param string $name    The attribute name
+     * @param mixed  $default The default value if not found
+     *
+     * @return mixed
+     */
+    public function get($name, $default = null);
+
+    /**
+     * Sets an attribute.
+     *
+     * @param string $name
+     * @param mixed  $value
+     */
+    public function set($name, $value);
+
+    /**
+     * Returns attributes.
+     *
+     * @return array Attributes
+     */
+    public function all();
+
+    /**
+     * Sets attributes.
+     *
+     * @param array $attributes Attributes
+     */
+    public function replace(array $attributes);
+
+    /**
+     * Removes an attribute.
+     *
+     * @param string $name
+     *
+     * @return mixed The removed value or null when it does not exist
+     */
+    public function remove($name);
+}
diff --git a/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php b/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php
new file mode 100644
index 0000000000000000000000000000000000000000..abbf37ee7c33cf4be0a671ecf17c134a5bbdac55
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php
@@ -0,0 +1,153 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Attribute;
+
+/**
+ * This class provides structured storage of session attributes using
+ * a name spacing character in the key.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class NamespacedAttributeBag extends AttributeBag
+{
+    private $namespaceCharacter;
+
+    /**
+     * @param string $storageKey         Session storage key
+     * @param string $namespaceCharacter Namespace character to use in keys
+     */
+    public function __construct($storageKey = '_sf2_attributes', $namespaceCharacter = '/')
+    {
+        $this->namespaceCharacter = $namespaceCharacter;
+        parent::__construct($storageKey);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function has($name)
+    {
+        // reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is
+        $attributes = $this->resolveAttributePath($name);
+        $name = $this->resolveKey($name);
+
+        if (null === $attributes) {
+            return false;
+        }
+
+        return array_key_exists($name, $attributes);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function get($name, $default = null)
+    {
+        // reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is
+        $attributes = $this->resolveAttributePath($name);
+        $name = $this->resolveKey($name);
+
+        if (null === $attributes) {
+            return $default;
+        }
+
+        return array_key_exists($name, $attributes) ? $attributes[$name] : $default;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function set($name, $value)
+    {
+        $attributes = &$this->resolveAttributePath($name, true);
+        $name = $this->resolveKey($name);
+        $attributes[$name] = $value;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function remove($name)
+    {
+        $retval = null;
+        $attributes = &$this->resolveAttributePath($name);
+        $name = $this->resolveKey($name);
+        if (null !== $attributes && array_key_exists($name, $attributes)) {
+            $retval = $attributes[$name];
+            unset($attributes[$name]);
+        }
+
+        return $retval;
+    }
+
+    /**
+     * Resolves a path in attributes property and returns it as a reference.
+     *
+     * This method allows structured namespacing of session attributes.
+     *
+     * @param string $name         Key name
+     * @param bool   $writeContext Write context, default false
+     *
+     * @return array
+     */
+    protected function &resolveAttributePath($name, $writeContext = false)
+    {
+        $array = &$this->attributes;
+        $name = (0 === strpos($name, $this->namespaceCharacter)) ? substr($name, 1) : $name;
+
+        // Check if there is anything to do, else return
+        if (!$name) {
+            return $array;
+        }
+
+        $parts = explode($this->namespaceCharacter, $name);
+        if (count($parts) < 2) {
+            if (!$writeContext) {
+                return $array;
+            }
+
+            $array[$parts[0]] = array();
+
+            return $array;
+        }
+
+        unset($parts[count($parts) - 1]);
+
+        foreach ($parts as $part) {
+            if (null !== $array && !array_key_exists($part, $array)) {
+                $array[$part] = $writeContext ? array() : null;
+            }
+
+            $array = &$array[$part];
+        }
+
+        return $array;
+    }
+
+    /**
+     * Resolves the key from the name.
+     *
+     * This is the last part in a dot separated string.
+     *
+     * @param string $name
+     *
+     * @return string
+     */
+    protected function resolveKey($name)
+    {
+        if (false !== $pos = strrpos($name, $this->namespaceCharacter)) {
+            $name = substr($name, $pos + 1);
+        }
+
+        return $name;
+    }
+}
diff --git a/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php b/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php
new file mode 100644
index 0000000000000000000000000000000000000000..77521c24789c681def801d829b4315354b585b97
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php
@@ -0,0 +1,161 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Flash;
+
+/**
+ * AutoExpireFlashBag flash message container.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class AutoExpireFlashBag implements FlashBagInterface
+{
+    private $name = 'flashes';
+    private $flashes = array('display' => array(), 'new' => array());
+    private $storageKey;
+
+    /**
+     * @param string $storageKey The key used to store flashes in the session
+     */
+    public function __construct($storageKey = '_symfony_flashes')
+    {
+        $this->storageKey = $storageKey;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    public function setName($name)
+    {
+        $this->name = $name;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function initialize(array &$flashes)
+    {
+        $this->flashes = &$flashes;
+
+        // The logic: messages from the last request will be stored in new, so we move them to previous
+        // This request we will show what is in 'display'.  What is placed into 'new' this time round will
+        // be moved to display next time round.
+        $this->flashes['display'] = array_key_exists('new', $this->flashes) ? $this->flashes['new'] : array();
+        $this->flashes['new'] = array();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function add($type, $message)
+    {
+        $this->flashes['new'][$type][] = $message;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function peek($type, array $default = array())
+    {
+        return $this->has($type) ? $this->flashes['display'][$type] : $default;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function peekAll()
+    {
+        return array_key_exists('display', $this->flashes) ? (array) $this->flashes['display'] : array();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function get($type, array $default = array())
+    {
+        $return = $default;
+
+        if (!$this->has($type)) {
+            return $return;
+        }
+
+        if (isset($this->flashes['display'][$type])) {
+            $return = $this->flashes['display'][$type];
+            unset($this->flashes['display'][$type]);
+        }
+
+        return $return;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function all()
+    {
+        $return = $this->flashes['display'];
+        $this->flashes['display'] = array();
+
+        return $return;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setAll(array $messages)
+    {
+        $this->flashes['new'] = $messages;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function set($type, $messages)
+    {
+        $this->flashes['new'][$type] = (array) $messages;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function has($type)
+    {
+        return array_key_exists($type, $this->flashes['display']) && $this->flashes['display'][$type];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function keys()
+    {
+        return array_keys($this->flashes['display']);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getStorageKey()
+    {
+        return $this->storageKey;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function clear()
+    {
+        return $this->all();
+    }
+}
diff --git a/vendor/symfony/http-foundation/Session/Flash/FlashBag.php b/vendor/symfony/http-foundation/Session/Flash/FlashBag.php
new file mode 100644
index 0000000000000000000000000000000000000000..12fb740c5220c4c7b9908ff445a5283d0d5b6aae
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Flash/FlashBag.php
@@ -0,0 +1,152 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Flash;
+
+/**
+ * FlashBag flash message container.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class FlashBag implements FlashBagInterface
+{
+    private $name = 'flashes';
+    private $flashes = array();
+    private $storageKey;
+
+    /**
+     * @param string $storageKey The key used to store flashes in the session
+     */
+    public function __construct($storageKey = '_symfony_flashes')
+    {
+        $this->storageKey = $storageKey;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    public function setName($name)
+    {
+        $this->name = $name;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function initialize(array &$flashes)
+    {
+        $this->flashes = &$flashes;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function add($type, $message)
+    {
+        $this->flashes[$type][] = $message;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function peek($type, array $default = array())
+    {
+        return $this->has($type) ? $this->flashes[$type] : $default;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function peekAll()
+    {
+        return $this->flashes;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function get($type, array $default = array())
+    {
+        if (!$this->has($type)) {
+            return $default;
+        }
+
+        $return = $this->flashes[$type];
+
+        unset($this->flashes[$type]);
+
+        return $return;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function all()
+    {
+        $return = $this->peekAll();
+        $this->flashes = array();
+
+        return $return;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function set($type, $messages)
+    {
+        $this->flashes[$type] = (array) $messages;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setAll(array $messages)
+    {
+        $this->flashes = $messages;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function has($type)
+    {
+        return array_key_exists($type, $this->flashes) && $this->flashes[$type];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function keys()
+    {
+        return array_keys($this->flashes);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getStorageKey()
+    {
+        return $this->storageKey;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function clear()
+    {
+        return $this->all();
+    }
+}
diff --git a/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php b/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..80e97f17cdff35d292b1c13109bc5018ad736203
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php
@@ -0,0 +1,93 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Flash;
+
+use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
+
+/**
+ * FlashBagInterface.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+interface FlashBagInterface extends SessionBagInterface
+{
+    /**
+     * Adds a flash message for type.
+     *
+     * @param string $type
+     * @param string $message
+     */
+    public function add($type, $message);
+
+    /**
+     * Registers a message for a given type.
+     *
+     * @param string       $type
+     * @param string|array $message
+     */
+    public function set($type, $message);
+
+    /**
+     * Gets flash messages for a given type.
+     *
+     * @param string $type    Message category type
+     * @param array  $default Default value if $type does not exist
+     *
+     * @return array
+     */
+    public function peek($type, array $default = array());
+
+    /**
+     * Gets all flash messages.
+     *
+     * @return array
+     */
+    public function peekAll();
+
+    /**
+     * Gets and clears flash from the stack.
+     *
+     * @param string $type
+     * @param array  $default Default value if $type does not exist
+     *
+     * @return array
+     */
+    public function get($type, array $default = array());
+
+    /**
+     * Gets and clears flashes from the stack.
+     *
+     * @return array
+     */
+    public function all();
+
+    /**
+     * Sets all flash messages.
+     */
+    public function setAll(array $messages);
+
+    /**
+     * Has flash messages for a given type?
+     *
+     * @param string $type
+     *
+     * @return bool
+     */
+    public function has($type);
+
+    /**
+     * Returns a list of all defined types.
+     *
+     * @return array
+     */
+    public function keys();
+}
diff --git a/vendor/symfony/http-foundation/Session/Session.php b/vendor/symfony/http-foundation/Session/Session.php
new file mode 100644
index 0000000000000000000000000000000000000000..a46cffbb8dbd6d03fa24b929f8c41c0a9b992f0f
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Session.php
@@ -0,0 +1,273 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session;
+
+use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface;
+use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
+use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface;
+use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
+use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;
+use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Drak <drak@zikula.org>
+ */
+class Session implements SessionInterface, \IteratorAggregate, \Countable
+{
+    protected $storage;
+
+    private $flashName;
+    private $attributeName;
+    private $data = array();
+    private $hasBeenStarted;
+
+    /**
+     * @param SessionStorageInterface $storage    A SessionStorageInterface instance
+     * @param AttributeBagInterface   $attributes An AttributeBagInterface instance, (defaults null for default AttributeBag)
+     * @param FlashBagInterface       $flashes    A FlashBagInterface instance (defaults null for default FlashBag)
+     */
+    public function __construct(SessionStorageInterface $storage = null, AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null)
+    {
+        $this->storage = $storage ?: new NativeSessionStorage();
+
+        $attributes = $attributes ?: new AttributeBag();
+        $this->attributeName = $attributes->getName();
+        $this->registerBag($attributes);
+
+        $flashes = $flashes ?: new FlashBag();
+        $this->flashName = $flashes->getName();
+        $this->registerBag($flashes);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function start()
+    {
+        return $this->storage->start();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function has($name)
+    {
+        return $this->getAttributeBag()->has($name);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function get($name, $default = null)
+    {
+        return $this->getAttributeBag()->get($name, $default);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function set($name, $value)
+    {
+        $this->getAttributeBag()->set($name, $value);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function all()
+    {
+        return $this->getAttributeBag()->all();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function replace(array $attributes)
+    {
+        $this->getAttributeBag()->replace($attributes);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function remove($name)
+    {
+        return $this->getAttributeBag()->remove($name);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function clear()
+    {
+        $this->getAttributeBag()->clear();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function isStarted()
+    {
+        return $this->storage->isStarted();
+    }
+
+    /**
+     * Returns an iterator for attributes.
+     *
+     * @return \ArrayIterator An \ArrayIterator instance
+     */
+    public function getIterator()
+    {
+        return new \ArrayIterator($this->getAttributeBag()->all());
+    }
+
+    /**
+     * Returns the number of attributes.
+     *
+     * @return int The number of attributes
+     */
+    public function count()
+    {
+        return count($this->getAttributeBag()->all());
+    }
+
+    /**
+     * @return bool
+     *
+     * @internal
+     */
+    public function hasBeenStarted()
+    {
+        return $this->hasBeenStarted;
+    }
+
+    /**
+     * @return bool
+     *
+     * @internal
+     */
+    public function isEmpty()
+    {
+        foreach ($this->data as &$data) {
+            if (!empty($data)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function invalidate($lifetime = null)
+    {
+        $this->storage->clear();
+
+        return $this->migrate(true, $lifetime);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function migrate($destroy = false, $lifetime = null)
+    {
+        return $this->storage->regenerate($destroy, $lifetime);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function save()
+    {
+        $this->storage->save();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getId()
+    {
+        return $this->storage->getId();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setId($id)
+    {
+        $this->storage->setId($id);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getName()
+    {
+        return $this->storage->getName();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setName($name)
+    {
+        $this->storage->setName($name);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getMetadataBag()
+    {
+        return $this->storage->getMetadataBag();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function registerBag(SessionBagInterface $bag)
+    {
+        $this->storage->registerBag(new SessionBagProxy($bag, $this->data, $this->hasBeenStarted));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getBag($name)
+    {
+        return $this->storage->getBag($name)->getBag();
+    }
+
+    /**
+     * Gets the flashbag interface.
+     *
+     * @return FlashBagInterface
+     */
+    public function getFlashBag()
+    {
+        return $this->getBag($this->flashName);
+    }
+
+    /**
+     * Gets the attributebag interface.
+     *
+     * Note that this method was added to help with IDE autocompletion.
+     *
+     * @return AttributeBagInterface
+     */
+    private function getAttributeBag()
+    {
+        return $this->getBag($this->attributeName);
+    }
+}
diff --git a/vendor/symfony/http-foundation/Session/SessionBagInterface.php b/vendor/symfony/http-foundation/Session/SessionBagInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..8e37d06d65da3e729528188503e68e8dae9e10fc
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/SessionBagInterface.php
@@ -0,0 +1,46 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session;
+
+/**
+ * Session Bag store.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+interface SessionBagInterface
+{
+    /**
+     * Gets this bag's name.
+     *
+     * @return string
+     */
+    public function getName();
+
+    /**
+     * Initializes the Bag.
+     */
+    public function initialize(array &$array);
+
+    /**
+     * Gets the storage key for this bag.
+     *
+     * @return string
+     */
+    public function getStorageKey();
+
+    /**
+     * Clears out data from bag.
+     *
+     * @return mixed Whatever data was contained
+     */
+    public function clear();
+}
diff --git a/vendor/symfony/http-foundation/Session/SessionBagProxy.php b/vendor/symfony/http-foundation/Session/SessionBagProxy.php
new file mode 100644
index 0000000000000000000000000000000000000000..307836d5f94616e22404631fea345f02a64fcb8c
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/SessionBagProxy.php
@@ -0,0 +1,82 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @internal
+ */
+final class SessionBagProxy implements SessionBagInterface
+{
+    private $bag;
+    private $data;
+    private $hasBeenStarted;
+
+    public function __construct(SessionBagInterface $bag, array &$data, &$hasBeenStarted)
+    {
+        $this->bag = $bag;
+        $this->data = &$data;
+        $this->hasBeenStarted = &$hasBeenStarted;
+    }
+
+    /**
+     * @return SessionBagInterface
+     */
+    public function getBag()
+    {
+        return $this->bag;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isEmpty()
+    {
+        return empty($this->data[$this->bag->getStorageKey()]);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getName()
+    {
+        return $this->bag->getName();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function initialize(array &$array)
+    {
+        $this->hasBeenStarted = true;
+        $this->data[$this->bag->getStorageKey()] = &$array;
+
+        $this->bag->initialize($array);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getStorageKey()
+    {
+        return $this->bag->getStorageKey();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function clear()
+    {
+        return $this->bag->clear();
+    }
+}
diff --git a/vendor/symfony/http-foundation/Session/SessionInterface.php b/vendor/symfony/http-foundation/Session/SessionInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..95fca857e24303ada742d0f6ae00a3829ee5433b
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/SessionInterface.php
@@ -0,0 +1,180 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session;
+
+use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag;
+
+/**
+ * Interface for the session.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+interface SessionInterface
+{
+    /**
+     * Starts the session storage.
+     *
+     * @return bool True if session started
+     *
+     * @throws \RuntimeException if session fails to start
+     */
+    public function start();
+
+    /**
+     * Returns the session ID.
+     *
+     * @return string The session ID
+     */
+    public function getId();
+
+    /**
+     * Sets the session ID.
+     *
+     * @param string $id
+     */
+    public function setId($id);
+
+    /**
+     * Returns the session name.
+     *
+     * @return mixed The session name
+     */
+    public function getName();
+
+    /**
+     * Sets the session name.
+     *
+     * @param string $name
+     */
+    public function setName($name);
+
+    /**
+     * Invalidates the current session.
+     *
+     * Clears all session attributes and flashes and regenerates the
+     * session and deletes the old session from persistence.
+     *
+     * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value
+     *                      will leave the system settings unchanged, 0 sets the cookie
+     *                      to expire with browser session. Time is in seconds, and is
+     *                      not a Unix timestamp.
+     *
+     * @return bool True if session invalidated, false if error
+     */
+    public function invalidate($lifetime = null);
+
+    /**
+     * Migrates the current session to a new session id while maintaining all
+     * session attributes.
+     *
+     * @param bool $destroy  Whether to delete the old session or leave it to garbage collection
+     * @param int  $lifetime Sets the cookie lifetime for the session cookie. A null value
+     *                       will leave the system settings unchanged, 0 sets the cookie
+     *                       to expire with browser session. Time is in seconds, and is
+     *                       not a Unix timestamp.
+     *
+     * @return bool True if session migrated, false if error
+     */
+    public function migrate($destroy = false, $lifetime = null);
+
+    /**
+     * Force the session to be saved and closed.
+     *
+     * This method is generally not required for real sessions as
+     * the session will be automatically saved at the end of
+     * code execution.
+     */
+    public function save();
+
+    /**
+     * Checks if an attribute is defined.
+     *
+     * @param string $name The attribute name
+     *
+     * @return bool true if the attribute is defined, false otherwise
+     */
+    public function has($name);
+
+    /**
+     * Returns an attribute.
+     *
+     * @param string $name    The attribute name
+     * @param mixed  $default The default value if not found
+     *
+     * @return mixed
+     */
+    public function get($name, $default = null);
+
+    /**
+     * Sets an attribute.
+     *
+     * @param string $name
+     * @param mixed  $value
+     */
+    public function set($name, $value);
+
+    /**
+     * Returns attributes.
+     *
+     * @return array Attributes
+     */
+    public function all();
+
+    /**
+     * Sets attributes.
+     *
+     * @param array $attributes Attributes
+     */
+    public function replace(array $attributes);
+
+    /**
+     * Removes an attribute.
+     *
+     * @param string $name
+     *
+     * @return mixed The removed value or null when it does not exist
+     */
+    public function remove($name);
+
+    /**
+     * Clears all attributes.
+     */
+    public function clear();
+
+    /**
+     * Checks if the session was started.
+     *
+     * @return bool
+     */
+    public function isStarted();
+
+    /**
+     * Registers a SessionBagInterface with the session.
+     */
+    public function registerBag(SessionBagInterface $bag);
+
+    /**
+     * Gets a bag instance by name.
+     *
+     * @param string $name
+     *
+     * @return SessionBagInterface
+     */
+    public function getBag($name);
+
+    /**
+     * Gets session meta.
+     *
+     * @return MetadataBag
+     */
+    public function getMetadataBag();
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..6ae1355816fdbb2d9fa45aaf7681b291b1b3ddd5
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php
@@ -0,0 +1,168 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+/**
+ * This abstract session handler provides a generic implementation
+ * of the PHP 7.0 SessionUpdateTimestampHandlerInterface,
+ * enabling strict and lazy session handling.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+abstract class AbstractSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface
+{
+    private $sessionName;
+    private $prefetchId;
+    private $prefetchData;
+    private $newSessionId;
+    private $igbinaryEmptyData;
+
+    /**
+     * {@inheritdoc}
+     */
+    public function open($savePath, $sessionName)
+    {
+        $this->sessionName = $sessionName;
+        if (!headers_sent() && !ini_get('session.cache_limiter') && '0' !== ini_get('session.cache_limiter')) {
+            header(sprintf('Cache-Control: max-age=%d, private, must-revalidate', 60 * (int) ini_get('session.cache_expire')));
+        }
+
+        return true;
+    }
+
+    /**
+     * @param string $sessionId
+     *
+     * @return string
+     */
+    abstract protected function doRead($sessionId);
+
+    /**
+     * @param string $sessionId
+     * @param string $data
+     *
+     * @return bool
+     */
+    abstract protected function doWrite($sessionId, $data);
+
+    /**
+     * @param string $sessionId
+     *
+     * @return bool
+     */
+    abstract protected function doDestroy($sessionId);
+
+    /**
+     * {@inheritdoc}
+     */
+    public function validateId($sessionId)
+    {
+        $this->prefetchData = $this->read($sessionId);
+        $this->prefetchId = $sessionId;
+
+        return '' !== $this->prefetchData;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function read($sessionId)
+    {
+        if (null !== $this->prefetchId) {
+            $prefetchId = $this->prefetchId;
+            $prefetchData = $this->prefetchData;
+            $this->prefetchId = $this->prefetchData = null;
+
+            if ($prefetchId === $sessionId || '' === $prefetchData) {
+                $this->newSessionId = '' === $prefetchData ? $sessionId : null;
+
+                return $prefetchData;
+            }
+        }
+
+        $data = $this->doRead($sessionId);
+        $this->newSessionId = '' === $data ? $sessionId : null;
+        if (\PHP_VERSION_ID < 70000) {
+            $this->prefetchData = $data;
+        }
+
+        return $data;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function write($sessionId, $data)
+    {
+        if (\PHP_VERSION_ID < 70000 && $this->prefetchData) {
+            $readData = $this->prefetchData;
+            $this->prefetchData = null;
+
+            if ($readData === $data) {
+                return $this->updateTimestamp($sessionId, $data);
+            }
+        }
+        if (null === $this->igbinaryEmptyData) {
+            // see https://github.com/igbinary/igbinary/issues/146
+            $this->igbinaryEmptyData = \function_exists('igbinary_serialize') ? igbinary_serialize(array()) : '';
+        }
+        if ('' === $data || $this->igbinaryEmptyData === $data) {
+            return $this->destroy($sessionId);
+        }
+        $this->newSessionId = null;
+
+        return $this->doWrite($sessionId, $data);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function destroy($sessionId)
+    {
+        if (\PHP_VERSION_ID < 70000) {
+            $this->prefetchData = null;
+        }
+        if (!headers_sent() && ini_get('session.use_cookies')) {
+            if (!$this->sessionName) {
+                throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', get_class($this)));
+            }
+            $sessionCookie = sprintf(' %s=', urlencode($this->sessionName));
+            $sessionCookieWithId = sprintf('%s%s;', $sessionCookie, urlencode($sessionId));
+            $sessionCookieFound = false;
+            $otherCookies = array();
+            foreach (headers_list() as $h) {
+                if (0 !== stripos($h, 'Set-Cookie:')) {
+                    continue;
+                }
+                if (11 === strpos($h, $sessionCookie, 11)) {
+                    $sessionCookieFound = true;
+
+                    if (11 !== strpos($h, $sessionCookieWithId, 11)) {
+                        $otherCookies[] = $h;
+                    }
+                } else {
+                    $otherCookies[] = $h;
+                }
+            }
+            if ($sessionCookieFound) {
+                header_remove('Set-Cookie');
+                foreach ($otherCookies as $h) {
+                    header($h, false);
+                }
+            } else {
+                setcookie($this->sessionName, '', 0, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), ini_get('session.cookie_secure'), ini_get('session.cookie_httponly'));
+            }
+        }
+
+        return $this->newSessionId === $sessionId || $this->doDestroy($sessionId);
+    }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..90726beb04efe668a3017281e28323f457c246ea
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php
@@ -0,0 +1,120 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+@trigger_error(sprintf('The class %s is deprecated since Symfony 3.4 and will be removed in 4.0. Use Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcachedSessionHandler instead.', MemcacheSessionHandler::class), E_USER_DEPRECATED);
+
+/**
+ * @author Drak <drak@zikula.org>
+ *
+ * @deprecated since version 3.4, to be removed in 4.0. Use Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcachedSessionHandler instead.
+ */
+class MemcacheSessionHandler implements \SessionHandlerInterface
+{
+    private $memcache;
+
+    /**
+     * @var int Time to live in seconds
+     */
+    private $ttl;
+
+    /**
+     * @var string Key prefix for shared environments
+     */
+    private $prefix;
+
+    /**
+     * Constructor.
+     *
+     * List of available options:
+     *  * prefix: The prefix to use for the memcache keys in order to avoid collision
+     *  * expiretime: The time to live in seconds
+     *
+     * @param \Memcache $memcache A \Memcache instance
+     * @param array     $options  An associative array of Memcache options
+     *
+     * @throws \InvalidArgumentException When unsupported options are passed
+     */
+    public function __construct(\Memcache $memcache, array $options = array())
+    {
+        if ($diff = array_diff(array_keys($options), array('prefix', 'expiretime'))) {
+            throw new \InvalidArgumentException(sprintf(
+                'The following options are not supported "%s"', implode(', ', $diff)
+            ));
+        }
+
+        $this->memcache = $memcache;
+        $this->ttl = isset($options['expiretime']) ? (int) $options['expiretime'] : 86400;
+        $this->prefix = isset($options['prefix']) ? $options['prefix'] : 'sf2s';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function open($savePath, $sessionName)
+    {
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function close()
+    {
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function read($sessionId)
+    {
+        return $this->memcache->get($this->prefix.$sessionId) ?: '';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function write($sessionId, $data)
+    {
+        return $this->memcache->set($this->prefix.$sessionId, $data, 0, time() + $this->ttl);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function destroy($sessionId)
+    {
+        $this->memcache->delete($this->prefix.$sessionId);
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function gc($maxlifetime)
+    {
+        // not required here because memcache will auto expire the records anyhow.
+        return true;
+    }
+
+    /**
+     * Return a Memcache instance.
+     *
+     * @return \Memcache
+     */
+    protected function getMemcache()
+    {
+        return $this->memcache;
+    }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..dd37eae14e10d7dc067155583f2dc643f0a332fb
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php
@@ -0,0 +1,124 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+/**
+ * Memcached based session storage handler based on the Memcached class
+ * provided by the PHP memcached extension.
+ *
+ * @see http://php.net/memcached
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class MemcachedSessionHandler extends AbstractSessionHandler
+{
+    private $memcached;
+
+    /**
+     * @var int Time to live in seconds
+     */
+    private $ttl;
+
+    /**
+     * @var string Key prefix for shared environments
+     */
+    private $prefix;
+
+    /**
+     * Constructor.
+     *
+     * List of available options:
+     *  * prefix: The prefix to use for the memcached keys in order to avoid collision
+     *  * expiretime: The time to live in seconds.
+     *
+     * @param \Memcached $memcached A \Memcached instance
+     * @param array      $options   An associative array of Memcached options
+     *
+     * @throws \InvalidArgumentException When unsupported options are passed
+     */
+    public function __construct(\Memcached $memcached, array $options = array())
+    {
+        $this->memcached = $memcached;
+
+        if ($diff = array_diff(array_keys($options), array('prefix', 'expiretime'))) {
+            throw new \InvalidArgumentException(sprintf(
+                'The following options are not supported "%s"', implode(', ', $diff)
+            ));
+        }
+
+        $this->ttl = isset($options['expiretime']) ? (int) $options['expiretime'] : 86400;
+        $this->prefix = isset($options['prefix']) ? $options['prefix'] : 'sf2s';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function close()
+    {
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doRead($sessionId)
+    {
+        return $this->memcached->get($this->prefix.$sessionId) ?: '';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function updateTimestamp($sessionId, $data)
+    {
+        $this->memcached->touch($this->prefix.$sessionId, time() + $this->ttl);
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doWrite($sessionId, $data)
+    {
+        return $this->memcached->set($this->prefix.$sessionId, $data, time() + $this->ttl);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doDestroy($sessionId)
+    {
+        $result = $this->memcached->delete($this->prefix.$sessionId);
+
+        return $result || \Memcached::RES_NOTFOUND == $this->memcached->getResultCode();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function gc($maxlifetime)
+    {
+        // not required here because memcached will auto expire the records anyhow.
+        return true;
+    }
+
+    /**
+     * Return a Memcached instance.
+     *
+     * @return \Memcached
+     */
+    protected function getMemcached()
+    {
+        return $this->memcached;
+    }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..7d3fa218a269d62fef8b23cad74b672b63fe2e35
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php
@@ -0,0 +1,255 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+/**
+ * Session handler using the mongodb/mongodb package and MongoDB driver extension.
+ *
+ * @author Markus Bachmann <markus.bachmann@bachi.biz>
+ *
+ * @see https://packagist.org/packages/mongodb/mongodb
+ * @see http://php.net/manual/en/set.mongodb.php
+ */
+class MongoDbSessionHandler extends AbstractSessionHandler
+{
+    private $mongo;
+
+    /**
+     * @var \MongoCollection
+     */
+    private $collection;
+
+    /**
+     * @var array
+     */
+    private $options;
+
+    /**
+     * Constructor.
+     *
+     * List of available options:
+     *  * database: The name of the database [required]
+     *  * collection: The name of the collection [required]
+     *  * id_field: The field name for storing the session id [default: _id]
+     *  * data_field: The field name for storing the session data [default: data]
+     *  * time_field: The field name for storing the timestamp [default: time]
+     *  * expiry_field: The field name for storing the expiry-timestamp [default: expires_at].
+     *
+     * It is strongly recommended to put an index on the `expiry_field` for
+     * garbage-collection. Alternatively it's possible to automatically expire
+     * the sessions in the database as described below:
+     *
+     * A TTL collections can be used on MongoDB 2.2+ to cleanup expired sessions
+     * automatically. Such an index can for example look like this:
+     *
+     *     db.<session-collection>.ensureIndex(
+     *         { "<expiry-field>": 1 },
+     *         { "expireAfterSeconds": 0 }
+     *     )
+     *
+     * More details on: http://docs.mongodb.org/manual/tutorial/expire-data/
+     *
+     * If you use such an index, you can drop `gc_probability` to 0 since
+     * no garbage-collection is required.
+     *
+     * @param \MongoDB\Client $mongo   A MongoDB\Client instance
+     * @param array           $options An associative array of field options
+     *
+     * @throws \InvalidArgumentException When MongoClient or Mongo instance not provided
+     * @throws \InvalidArgumentException When "database" or "collection" not provided
+     */
+    public function __construct($mongo, array $options)
+    {
+        if ($mongo instanceof \MongoClient || $mongo instanceof \Mongo) {
+            @trigger_error(sprintf('Using %s with the legacy mongo extension is deprecated as of 3.4 and will be removed in 4.0. Use it with the mongodb/mongodb package and ext-mongodb instead.', __CLASS__), E_USER_DEPRECATED);
+        }
+
+        if (!($mongo instanceof \MongoDB\Client || $mongo instanceof \MongoClient || $mongo instanceof \Mongo)) {
+            throw new \InvalidArgumentException('MongoClient or Mongo instance required');
+        }
+
+        if (!isset($options['database']) || !isset($options['collection'])) {
+            throw new \InvalidArgumentException('You must provide the "database" and "collection" option for MongoDBSessionHandler');
+        }
+
+        $this->mongo = $mongo;
+
+        $this->options = array_merge(array(
+            'id_field' => '_id',
+            'data_field' => 'data',
+            'time_field' => 'time',
+            'expiry_field' => 'expires_at',
+        ), $options);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function close()
+    {
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doDestroy($sessionId)
+    {
+        $methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteOne' : 'remove';
+
+        $this->getCollection()->$methodName(array(
+            $this->options['id_field'] => $sessionId,
+        ));
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function gc($maxlifetime)
+    {
+        $methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteMany' : 'remove';
+
+        $this->getCollection()->$methodName(array(
+            $this->options['expiry_field'] => array('$lt' => $this->createDateTime()),
+        ));
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doWrite($sessionId, $data)
+    {
+        $expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime'));
+
+        $fields = array(
+            $this->options['time_field'] => $this->createDateTime(),
+            $this->options['expiry_field'] => $expiry,
+        );
+
+        $options = array('upsert' => true);
+
+        if ($this->mongo instanceof \MongoDB\Client) {
+            $fields[$this->options['data_field']] = new \MongoDB\BSON\Binary($data, \MongoDB\BSON\Binary::TYPE_OLD_BINARY);
+        } else {
+            $fields[$this->options['data_field']] = new \MongoBinData($data, \MongoBinData::BYTE_ARRAY);
+            $options['multiple'] = false;
+        }
+
+        $methodName = $this->mongo instanceof \MongoDB\Client ? 'updateOne' : 'update';
+
+        $this->getCollection()->$methodName(
+            array($this->options['id_field'] => $sessionId),
+            array('$set' => $fields),
+            $options
+        );
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function updateTimestamp($sessionId, $data)
+    {
+        $expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime'));
+
+        if ($this->mongo instanceof \MongoDB\Client) {
+            $methodName = 'updateOne';
+            $options = array();
+        } else {
+            $methodName = 'update';
+            $options = array('multiple' => false);
+        }
+
+        $this->getCollection()->$methodName(
+            array($this->options['id_field'] => $sessionId),
+            array('$set' => array(
+                $this->options['time_field'] => $this->createDateTime(),
+                $this->options['expiry_field'] => $expiry,
+            )),
+            $options
+        );
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doRead($sessionId)
+    {
+        $dbData = $this->getCollection()->findOne(array(
+            $this->options['id_field'] => $sessionId,
+            $this->options['expiry_field'] => array('$gte' => $this->createDateTime()),
+        ));
+
+        if (null === $dbData) {
+            return '';
+        }
+
+        if ($dbData[$this->options['data_field']] instanceof \MongoDB\BSON\Binary) {
+            return $dbData[$this->options['data_field']]->getData();
+        }
+
+        return $dbData[$this->options['data_field']]->bin;
+    }
+
+    /**
+     * Return a "MongoCollection" instance.
+     *
+     * @return \MongoCollection
+     */
+    private function getCollection()
+    {
+        if (null === $this->collection) {
+            $this->collection = $this->mongo->selectCollection($this->options['database'], $this->options['collection']);
+        }
+
+        return $this->collection;
+    }
+
+    /**
+     * Return a Mongo instance.
+     *
+     * @return \Mongo|\MongoClient|\MongoDB\Client
+     */
+    protected function getMongo()
+    {
+        return $this->mongo;
+    }
+
+    /**
+     * Create a date object using the class appropriate for the current mongo connection.
+     *
+     * Return an instance of a MongoDate or \MongoDB\BSON\UTCDateTime
+     *
+     * @param int $seconds An integer representing UTC seconds since Jan 1 1970.  Defaults to now.
+     *
+     * @return \MongoDate|\MongoDB\BSON\UTCDateTime
+     */
+    private function createDateTime($seconds = null)
+    {
+        if (null === $seconds) {
+            $seconds = time();
+        }
+
+        if ($this->mongo instanceof \MongoDB\Client) {
+            return new \MongoDB\BSON\UTCDateTime($seconds * 1000);
+        }
+
+        return new \MongoDate($seconds);
+    }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..4e9704bd5858f38066cf001ae91d88450992040f
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php
@@ -0,0 +1,55 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+/**
+ * Native session handler using PHP's built in file storage.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class NativeFileSessionHandler extends NativeSessionHandler
+{
+    /**
+     * @param string $savePath Path of directory to save session files
+     *                         Default null will leave setting as defined by PHP.
+     *                         '/path', 'N;/path', or 'N;octal-mode;/path
+     *
+     * @see http://php.net/session.configuration.php#ini.session.save-path for further details.
+     *
+     * @throws \InvalidArgumentException On invalid $savePath
+     * @throws \RuntimeException         When failing to create the save directory
+     */
+    public function __construct($savePath = null)
+    {
+        if (null === $savePath) {
+            $savePath = ini_get('session.save_path');
+        }
+
+        $baseDir = $savePath;
+
+        if ($count = substr_count($savePath, ';')) {
+            if ($count > 2) {
+                throw new \InvalidArgumentException(sprintf('Invalid argument $savePath \'%s\'', $savePath));
+            }
+
+            // characters after last ';' are the path
+            $baseDir = ltrim(strrchr($savePath, ';'), ';');
+        }
+
+        if ($baseDir && !is_dir($baseDir) && !@mkdir($baseDir, 0777, true) && !is_dir($baseDir)) {
+            throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s"', $baseDir));
+        }
+
+        ini_set('session.save_path', $savePath);
+        ini_set('session.save_handler', 'files');
+    }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..9be4528aeb436567ca1fd5d30c00ba8b8b0af8a7
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php
@@ -0,0 +1,24 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+/**
+ * @deprecated since version 3.4, to be removed in 4.0. Use \SessionHandler instead.
+ * @see http://php.net/sessionhandler
+ */
+class NativeSessionHandler extends \SessionHandler
+{
+    public function __construct()
+    {
+        @trigger_error('The '.__NAMESPACE__.'\NativeSessionHandler class is deprecated since Symfony 3.4 and will be removed in 4.0. Use the \SessionHandler class instead.', E_USER_DEPRECATED);
+    }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..8d193155b090f2d596ce937ec3d3040a86f75733
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php
@@ -0,0 +1,76 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+/**
+ * Can be used in unit testing or in a situations where persisted sessions are not desired.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class NullSessionHandler extends AbstractSessionHandler
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function close()
+    {
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function validateId($sessionId)
+    {
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doRead($sessionId)
+    {
+        return '';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function updateTimestamp($sessionId, $data)
+    {
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doWrite($sessionId, $data)
+    {
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doDestroy($sessionId)
+    {
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function gc($maxlifetime)
+    {
+        return true;
+    }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..c8737be18e110244227e6e9492d432649e2ba1bb
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php
@@ -0,0 +1,910 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+/**
+ * Session handler using a PDO connection to read and write data.
+ *
+ * It works with MySQL, PostgreSQL, Oracle, SQL Server and SQLite and implements
+ * different locking strategies to handle concurrent access to the same session.
+ * Locking is necessary to prevent loss of data due to race conditions and to keep
+ * the session data consistent between read() and write(). With locking, requests
+ * for the same session will wait until the other one finished writing. For this
+ * reason it's best practice to close a session as early as possible to improve
+ * concurrency. PHPs internal files session handler also implements locking.
+ *
+ * Attention: Since SQLite does not support row level locks but locks the whole database,
+ * it means only one session can be accessed at a time. Even different sessions would wait
+ * for another to finish. So saving session in SQLite should only be considered for
+ * development or prototypes.
+ *
+ * Session data is a binary string that can contain non-printable characters like the null byte.
+ * For this reason it must be saved in a binary column in the database like BLOB in MySQL.
+ * Saving it in a character column could corrupt the data. You can use createTable()
+ * to initialize a correctly defined table.
+ *
+ * @see http://php.net/sessionhandlerinterface
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Michael Williams <michael.williams@funsational.com>
+ * @author Tobias Schultze <http://tobion.de>
+ */
+class PdoSessionHandler extends AbstractSessionHandler
+{
+    /**
+     * No locking is done. This means sessions are prone to loss of data due to
+     * race conditions of concurrent requests to the same session. The last session
+     * write will win in this case. It might be useful when you implement your own
+     * logic to deal with this like an optimistic approach.
+     */
+    const LOCK_NONE = 0;
+
+    /**
+     * Creates an application-level lock on a session. The disadvantage is that the
+     * lock is not enforced by the database and thus other, unaware parts of the
+     * application could still concurrently modify the session. The advantage is it
+     * does not require a transaction.
+     * This mode is not available for SQLite and not yet implemented for oci and sqlsrv.
+     */
+    const LOCK_ADVISORY = 1;
+
+    /**
+     * Issues a real row lock. Since it uses a transaction between opening and
+     * closing a session, you have to be careful when you use same database connection
+     * that you also use for your application logic. This mode is the default because
+     * it's the only reliable solution across DBMSs.
+     */
+    const LOCK_TRANSACTIONAL = 2;
+
+    /**
+     * @var \PDO|null PDO instance or null when not connected yet
+     */
+    private $pdo;
+
+    /**
+     * @var string|null|false DSN string or null for session.save_path or false when lazy connection disabled
+     */
+    private $dsn = false;
+
+    /**
+     * @var string Database driver
+     */
+    private $driver;
+
+    /**
+     * @var string Table name
+     */
+    private $table = 'sessions';
+
+    /**
+     * @var string Column for session id
+     */
+    private $idCol = 'sess_id';
+
+    /**
+     * @var string Column for session data
+     */
+    private $dataCol = 'sess_data';
+
+    /**
+     * @var string Column for lifetime
+     */
+    private $lifetimeCol = 'sess_lifetime';
+
+    /**
+     * @var string Column for timestamp
+     */
+    private $timeCol = 'sess_time';
+
+    /**
+     * @var string Username when lazy-connect
+     */
+    private $username = '';
+
+    /**
+     * @var string Password when lazy-connect
+     */
+    private $password = '';
+
+    /**
+     * @var array Connection options when lazy-connect
+     */
+    private $connectionOptions = array();
+
+    /**
+     * @var int The strategy for locking, see constants
+     */
+    private $lockMode = self::LOCK_TRANSACTIONAL;
+
+    /**
+     * It's an array to support multiple reads before closing which is manual, non-standard usage.
+     *
+     * @var \PDOStatement[] An array of statements to release advisory locks
+     */
+    private $unlockStatements = array();
+
+    /**
+     * @var bool True when the current session exists but expired according to session.gc_maxlifetime
+     */
+    private $sessionExpired = false;
+
+    /**
+     * @var bool Whether a transaction is active
+     */
+    private $inTransaction = false;
+
+    /**
+     * @var bool Whether gc() has been called
+     */
+    private $gcCalled = false;
+
+    /**
+     * You can either pass an existing database connection as PDO instance or
+     * pass a DSN string that will be used to lazy-connect to the database
+     * when the session is actually used. Furthermore it's possible to pass null
+     * which will then use the session.save_path ini setting as PDO DSN parameter.
+     *
+     * List of available options:
+     *  * db_table: The name of the table [default: sessions]
+     *  * db_id_col: The column where to store the session id [default: sess_id]
+     *  * db_data_col: The column where to store the session data [default: sess_data]
+     *  * db_lifetime_col: The column where to store the lifetime [default: sess_lifetime]
+     *  * db_time_col: The column where to store the timestamp [default: sess_time]
+     *  * db_username: The username when lazy-connect [default: '']
+     *  * db_password: The password when lazy-connect [default: '']
+     *  * db_connection_options: An array of driver-specific connection options [default: array()]
+     *  * lock_mode: The strategy for locking, see constants [default: LOCK_TRANSACTIONAL]
+     *
+     * @param \PDO|string|null $pdoOrDsn A \PDO instance or DSN string or URL string or null
+     * @param array            $options  An associative array of options
+     *
+     * @throws \InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION
+     */
+    public function __construct($pdoOrDsn = null, array $options = array())
+    {
+        if ($pdoOrDsn instanceof \PDO) {
+            if (\PDO::ERRMODE_EXCEPTION !== $pdoOrDsn->getAttribute(\PDO::ATTR_ERRMODE)) {
+                throw new \InvalidArgumentException(sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION))', __CLASS__));
+            }
+
+            $this->pdo = $pdoOrDsn;
+            $this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
+        } elseif (is_string($pdoOrDsn) && false !== strpos($pdoOrDsn, '://')) {
+            $this->dsn = $this->buildDsnFromUrl($pdoOrDsn);
+        } else {
+            $this->dsn = $pdoOrDsn;
+        }
+
+        $this->table = isset($options['db_table']) ? $options['db_table'] : $this->table;
+        $this->idCol = isset($options['db_id_col']) ? $options['db_id_col'] : $this->idCol;
+        $this->dataCol = isset($options['db_data_col']) ? $options['db_data_col'] : $this->dataCol;
+        $this->lifetimeCol = isset($options['db_lifetime_col']) ? $options['db_lifetime_col'] : $this->lifetimeCol;
+        $this->timeCol = isset($options['db_time_col']) ? $options['db_time_col'] : $this->timeCol;
+        $this->username = isset($options['db_username']) ? $options['db_username'] : $this->username;
+        $this->password = isset($options['db_password']) ? $options['db_password'] : $this->password;
+        $this->connectionOptions = isset($options['db_connection_options']) ? $options['db_connection_options'] : $this->connectionOptions;
+        $this->lockMode = isset($options['lock_mode']) ? $options['lock_mode'] : $this->lockMode;
+    }
+
+    /**
+     * Creates the table to store sessions which can be called once for setup.
+     *
+     * Session ID is saved in a column of maximum length 128 because that is enough even
+     * for a 512 bit configured session.hash_function like Whirlpool. Session data is
+     * saved in a BLOB. One could also use a shorter inlined varbinary column
+     * if one was sure the data fits into it.
+     *
+     * @throws \PDOException    When the table already exists
+     * @throws \DomainException When an unsupported PDO driver is used
+     */
+    public function createTable()
+    {
+        // connect if we are not yet
+        $this->getConnection();
+
+        switch ($this->driver) {
+            case 'mysql':
+                // We use varbinary for the ID column because it prevents unwanted conversions:
+                // - character set conversions between server and client
+                // - trailing space removal
+                // - case-insensitivity
+                // - language processing like é == e
+                $sql = "CREATE TABLE $this->table ($this->idCol VARBINARY(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol MEDIUMINT NOT NULL, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8_bin, ENGINE = InnoDB";
+                break;
+            case 'sqlite':
+                $sql = "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)";
+                break;
+            case 'pgsql':
+                $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)";
+                break;
+            case 'oci':
+                $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR2(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)";
+                break;
+            case 'sqlsrv':
+                $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)";
+                break;
+            default:
+                throw new \DomainException(sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver));
+        }
+
+        try {
+            $this->pdo->exec($sql);
+        } catch (\PDOException $e) {
+            $this->rollback();
+
+            throw $e;
+        }
+    }
+
+    /**
+     * Returns true when the current session exists but expired according to session.gc_maxlifetime.
+     *
+     * Can be used to distinguish between a new session and one that expired due to inactivity.
+     *
+     * @return bool Whether current session expired
+     */
+    public function isSessionExpired()
+    {
+        return $this->sessionExpired;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function open($savePath, $sessionName)
+    {
+        $this->sessionExpired = false;
+
+        if (null === $this->pdo) {
+            $this->connect($this->dsn ?: $savePath);
+        }
+
+        return parent::open($savePath, $sessionName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function read($sessionId)
+    {
+        try {
+            return parent::read($sessionId);
+        } catch (\PDOException $e) {
+            $this->rollback();
+
+            throw $e;
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function gc($maxlifetime)
+    {
+        // We delay gc() to close() so that it is executed outside the transactional and blocking read-write process.
+        // This way, pruning expired sessions does not block them from being started while the current session is used.
+        $this->gcCalled = true;
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doDestroy($sessionId)
+    {
+        // delete the record associated with this id
+        $sql = "DELETE FROM $this->table WHERE $this->idCol = :id";
+
+        try {
+            $stmt = $this->pdo->prepare($sql);
+            $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
+            $stmt->execute();
+        } catch (\PDOException $e) {
+            $this->rollback();
+
+            throw $e;
+        }
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doWrite($sessionId, $data)
+    {
+        $maxlifetime = (int) ini_get('session.gc_maxlifetime');
+
+        try {
+            // We use a single MERGE SQL query when supported by the database.
+            $mergeStmt = $this->getMergeStatement($sessionId, $data, $maxlifetime);
+            if (null !== $mergeStmt) {
+                $mergeStmt->execute();
+
+                return true;
+            }
+
+            $updateStmt = $this->getUpdateStatement($sessionId, $data, $maxlifetime);
+            $updateStmt->execute();
+
+            // When MERGE is not supported, like in Postgres < 9.5, we have to use this approach that can result in
+            // duplicate key errors when the same session is written simultaneously (given the LOCK_NONE behavior).
+            // We can just catch such an error and re-execute the update. This is similar to a serializable
+            // transaction with retry logic on serialization failures but without the overhead and without possible
+            // false positives due to longer gap locking.
+            if (!$updateStmt->rowCount()) {
+                try {
+                    $insertStmt = $this->getInsertStatement($sessionId, $data, $maxlifetime);
+                    $insertStmt->execute();
+                } catch (\PDOException $e) {
+                    // Handle integrity violation SQLSTATE 23000 (or a subclass like 23505 in Postgres) for duplicate keys
+                    if (0 === strpos($e->getCode(), '23')) {
+                        $updateStmt->execute();
+                    } else {
+                        throw $e;
+                    }
+                }
+            }
+        } catch (\PDOException $e) {
+            $this->rollback();
+
+            throw $e;
+        }
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function updateTimestamp($sessionId, $data)
+    {
+        $maxlifetime = (int) ini_get('session.gc_maxlifetime');
+
+        try {
+            $updateStmt = $this->pdo->prepare(
+                "UPDATE $this->table SET $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id"
+            );
+            $updateStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
+            $updateStmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT);
+            $updateStmt->bindValue(':time', time(), \PDO::PARAM_INT);
+            $updateStmt->execute();
+        } catch (\PDOException $e) {
+            $this->rollback();
+
+            throw $e;
+        }
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function close()
+    {
+        $this->commit();
+
+        while ($unlockStmt = array_shift($this->unlockStatements)) {
+            $unlockStmt->execute();
+        }
+
+        if ($this->gcCalled) {
+            $this->gcCalled = false;
+
+            // delete the session records that have expired
+            if ('mysql' === $this->driver) {
+                $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol < :time";
+            } else {
+                $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol < :time - $this->timeCol";
+            }
+
+            $stmt = $this->pdo->prepare($sql);
+            $stmt->bindValue(':time', time(), \PDO::PARAM_INT);
+            $stmt->execute();
+        }
+
+        if (false !== $this->dsn) {
+            $this->pdo = null; // only close lazy-connection
+        }
+
+        return true;
+    }
+
+    /**
+     * Lazy-connects to the database.
+     *
+     * @param string $dsn DSN string
+     */
+    private function connect($dsn)
+    {
+        $this->pdo = new \PDO($dsn, $this->username, $this->password, $this->connectionOptions);
+        $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
+        $this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
+    }
+
+    /**
+     * Builds a PDO DSN from a URL-like connection string.
+     *
+     * @param string $dsnOrUrl
+     *
+     * @return string
+     *
+     * @todo implement missing support for oci DSN (which look totally different from other PDO ones)
+     */
+    private function buildDsnFromUrl($dsnOrUrl)
+    {
+        // (pdo_)?sqlite3?:///... => (pdo_)?sqlite3?://localhost/... or else the URL will be invalid
+        $url = preg_replace('#^((?:pdo_)?sqlite3?):///#', '$1://localhost/', $dsnOrUrl);
+
+        $params = parse_url($url);
+
+        if (false === $params) {
+            return $dsnOrUrl; // If the URL is not valid, let's assume it might be a DSN already.
+        }
+
+        $params = array_map('rawurldecode', $params);
+
+        // Override the default username and password. Values passed through options will still win over these in the constructor.
+        if (isset($params['user'])) {
+            $this->username = $params['user'];
+        }
+
+        if (isset($params['pass'])) {
+            $this->password = $params['pass'];
+        }
+
+        if (!isset($params['scheme'])) {
+            throw new \InvalidArgumentException('URLs without scheme are not supported to configure the PdoSessionHandler');
+        }
+
+        $driverAliasMap = array(
+            'mssql' => 'sqlsrv',
+            'mysql2' => 'mysql', // Amazon RDS, for some weird reason
+            'postgres' => 'pgsql',
+            'postgresql' => 'pgsql',
+            'sqlite3' => 'sqlite',
+        );
+
+        $driver = isset($driverAliasMap[$params['scheme']]) ? $driverAliasMap[$params['scheme']] : $params['scheme'];
+
+        // Doctrine DBAL supports passing its internal pdo_* driver names directly too (allowing both dashes and underscores). This allows supporting the same here.
+        if (0 === strpos($driver, 'pdo_') || 0 === strpos($driver, 'pdo-')) {
+            $driver = substr($driver, 4);
+        }
+
+        switch ($driver) {
+            case 'mysql':
+            case 'pgsql':
+                $dsn = $driver.':';
+
+                if (isset($params['host']) && '' !== $params['host']) {
+                    $dsn .= 'host='.$params['host'].';';
+                }
+
+                if (isset($params['port']) && '' !== $params['port']) {
+                    $dsn .= 'port='.$params['port'].';';
+                }
+
+                if (isset($params['path'])) {
+                    $dbName = substr($params['path'], 1); // Remove the leading slash
+                    $dsn .= 'dbname='.$dbName.';';
+                }
+
+                return $dsn;
+
+            case 'sqlite':
+                return 'sqlite:'.substr($params['path'], 1);
+
+            case 'sqlsrv':
+                $dsn = 'sqlsrv:server=';
+
+                if (isset($params['host'])) {
+                    $dsn .= $params['host'];
+                }
+
+                if (isset($params['port']) && '' !== $params['port']) {
+                    $dsn .= ','.$params['port'];
+                }
+
+                if (isset($params['path'])) {
+                    $dbName = substr($params['path'], 1); // Remove the leading slash
+                    $dsn .= ';Database='.$dbName;
+                }
+
+                return $dsn;
+
+            default:
+                throw new \InvalidArgumentException(sprintf('The scheme "%s" is not supported by the PdoSessionHandler URL configuration. Pass a PDO DSN directly.', $params['scheme']));
+        }
+    }
+
+    /**
+     * Helper method to begin a transaction.
+     *
+     * Since SQLite does not support row level locks, we have to acquire a reserved lock
+     * on the database immediately. Because of https://bugs.php.net/42766 we have to create
+     * such a transaction manually which also means we cannot use PDO::commit or
+     * PDO::rollback or PDO::inTransaction for SQLite.
+     *
+     * Also MySQLs default isolation, REPEATABLE READ, causes deadlock for different sessions
+     * due to http://www.mysqlperformanceblog.com/2013/12/12/one-more-innodb-gap-lock-to-avoid/ .
+     * So we change it to READ COMMITTED.
+     */
+    private function beginTransaction()
+    {
+        if (!$this->inTransaction) {
+            if ('sqlite' === $this->driver) {
+                $this->pdo->exec('BEGIN IMMEDIATE TRANSACTION');
+            } else {
+                if ('mysql' === $this->driver) {
+                    $this->pdo->exec('SET TRANSACTION ISOLATION LEVEL READ COMMITTED');
+                }
+                $this->pdo->beginTransaction();
+            }
+            $this->inTransaction = true;
+        }
+    }
+
+    /**
+     * Helper method to commit a transaction.
+     */
+    private function commit()
+    {
+        if ($this->inTransaction) {
+            try {
+                // commit read-write transaction which also releases the lock
+                if ('sqlite' === $this->driver) {
+                    $this->pdo->exec('COMMIT');
+                } else {
+                    $this->pdo->commit();
+                }
+                $this->inTransaction = false;
+            } catch (\PDOException $e) {
+                $this->rollback();
+
+                throw $e;
+            }
+        }
+    }
+
+    /**
+     * Helper method to rollback a transaction.
+     */
+    private function rollback()
+    {
+        // We only need to rollback if we are in a transaction. Otherwise the resulting
+        // error would hide the real problem why rollback was called. We might not be
+        // in a transaction when not using the transactional locking behavior or when
+        // two callbacks (e.g. destroy and write) are invoked that both fail.
+        if ($this->inTransaction) {
+            if ('sqlite' === $this->driver) {
+                $this->pdo->exec('ROLLBACK');
+            } else {
+                $this->pdo->rollBack();
+            }
+            $this->inTransaction = false;
+        }
+    }
+
+    /**
+     * Reads the session data in respect to the different locking strategies.
+     *
+     * We need to make sure we do not return session data that is already considered garbage according
+     * to the session.gc_maxlifetime setting because gc() is called after read() and only sometimes.
+     *
+     * @param string $sessionId Session ID
+     *
+     * @return string The session data
+     */
+    protected function doRead($sessionId)
+    {
+        if (self::LOCK_ADVISORY === $this->lockMode) {
+            $this->unlockStatements[] = $this->doAdvisoryLock($sessionId);
+        }
+
+        $selectSql = $this->getSelectSql();
+        $selectStmt = $this->pdo->prepare($selectSql);
+        $selectStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
+
+        do {
+            $selectStmt->execute();
+            $sessionRows = $selectStmt->fetchAll(\PDO::FETCH_NUM);
+
+            if ($sessionRows) {
+                if ($sessionRows[0][1] + $sessionRows[0][2] < time()) {
+                    $this->sessionExpired = true;
+
+                    return '';
+                }
+
+                return is_resource($sessionRows[0][0]) ? stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0];
+            }
+
+            if (!ini_get('session.use_strict_mode') && self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) {
+                // In strict mode, session fixation is not possible: new sessions always start with a unique
+                // random id, so that concurrency is not possible and this code path can be skipped.
+                // Exclusive-reading of non-existent rows does not block, so we need to do an insert to block
+                // until other connections to the session are committed.
+                try {
+                    $insertStmt = $this->getInsertStatement($sessionId, '', 0);
+                    $insertStmt->execute();
+                } catch (\PDOException $e) {
+                    // Catch duplicate key error because other connection created the session already.
+                    // It would only not be the case when the other connection destroyed the session.
+                    if (0 === strpos($e->getCode(), '23')) {
+                        // Retrieve finished session data written by concurrent connection by restarting the loop.
+                        // We have to start a new transaction as a failed query will mark the current transaction as
+                        // aborted in PostgreSQL and disallow further queries within it.
+                        $this->rollback();
+                        $this->beginTransaction();
+                        continue;
+                    }
+
+                    throw $e;
+                }
+            }
+
+            return '';
+        } while (true);
+    }
+
+    /**
+     * Executes an application-level lock on the database.
+     *
+     * @param string $sessionId Session ID
+     *
+     * @return \PDOStatement The statement that needs to be executed later to release the lock
+     *
+     * @throws \DomainException When an unsupported PDO driver is used
+     *
+     * @todo implement missing advisory locks
+     *       - for oci using DBMS_LOCK.REQUEST
+     *       - for sqlsrv using sp_getapplock with LockOwner = Session
+     */
+    private function doAdvisoryLock($sessionId)
+    {
+        switch ($this->driver) {
+            case 'mysql':
+                // should we handle the return value? 0 on timeout, null on error
+                // we use a timeout of 50 seconds which is also the default for innodb_lock_wait_timeout
+                $stmt = $this->pdo->prepare('SELECT GET_LOCK(:key, 50)');
+                $stmt->bindValue(':key', $sessionId, \PDO::PARAM_STR);
+                $stmt->execute();
+
+                $releaseStmt = $this->pdo->prepare('DO RELEASE_LOCK(:key)');
+                $releaseStmt->bindValue(':key', $sessionId, \PDO::PARAM_STR);
+
+                return $releaseStmt;
+            case 'pgsql':
+                // Obtaining an exclusive session level advisory lock requires an integer key.
+                // When session.sid_bits_per_character > 4, the session id can contain non-hex-characters.
+                // So we cannot just use hexdec().
+                if (4 === \PHP_INT_SIZE) {
+                    $sessionInt1 = $this->convertStringToInt($sessionId);
+                    $sessionInt2 = $this->convertStringToInt(substr($sessionId, 4, 4));
+
+                    $stmt = $this->pdo->prepare('SELECT pg_advisory_lock(:key1, :key2)');
+                    $stmt->bindValue(':key1', $sessionInt1, \PDO::PARAM_INT);
+                    $stmt->bindValue(':key2', $sessionInt2, \PDO::PARAM_INT);
+                    $stmt->execute();
+
+                    $releaseStmt = $this->pdo->prepare('SELECT pg_advisory_unlock(:key1, :key2)');
+                    $releaseStmt->bindValue(':key1', $sessionInt1, \PDO::PARAM_INT);
+                    $releaseStmt->bindValue(':key2', $sessionInt2, \PDO::PARAM_INT);
+                } else {
+                    $sessionBigInt = $this->convertStringToInt($sessionId);
+
+                    $stmt = $this->pdo->prepare('SELECT pg_advisory_lock(:key)');
+                    $stmt->bindValue(':key', $sessionBigInt, \PDO::PARAM_INT);
+                    $stmt->execute();
+
+                    $releaseStmt = $this->pdo->prepare('SELECT pg_advisory_unlock(:key)');
+                    $releaseStmt->bindValue(':key', $sessionBigInt, \PDO::PARAM_INT);
+                }
+
+                return $releaseStmt;
+            case 'sqlite':
+                throw new \DomainException('SQLite does not support advisory locks.');
+            default:
+                throw new \DomainException(sprintf('Advisory locks are currently not implemented for PDO driver "%s".', $this->driver));
+        }
+    }
+
+    /**
+     * Encodes the first 4 (when PHP_INT_SIZE == 4) or 8 characters of the string as an integer.
+     *
+     * Keep in mind, PHP integers are signed.
+     *
+     * @param string $string
+     *
+     * @return int
+     */
+    private function convertStringToInt($string)
+    {
+        if (4 === \PHP_INT_SIZE) {
+            return (ord($string[3]) << 24) + (ord($string[2]) << 16) + (ord($string[1]) << 8) + ord($string[0]);
+        }
+
+        $int1 = (ord($string[7]) << 24) + (ord($string[6]) << 16) + (ord($string[5]) << 8) + ord($string[4]);
+        $int2 = (ord($string[3]) << 24) + (ord($string[2]) << 16) + (ord($string[1]) << 8) + ord($string[0]);
+
+        return $int2 + ($int1 << 32);
+    }
+
+    /**
+     * Return a locking or nonlocking SQL query to read session information.
+     *
+     * @return string The SQL string
+     *
+     * @throws \DomainException When an unsupported PDO driver is used
+     */
+    private function getSelectSql()
+    {
+        if (self::LOCK_TRANSACTIONAL === $this->lockMode) {
+            $this->beginTransaction();
+
+            switch ($this->driver) {
+                case 'mysql':
+                case 'oci':
+                case 'pgsql':
+                    return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WHERE $this->idCol = :id FOR UPDATE";
+                case 'sqlsrv':
+                    return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WITH (UPDLOCK, ROWLOCK) WHERE $this->idCol = :id";
+                case 'sqlite':
+                    // we already locked when starting transaction
+                    break;
+                default:
+                    throw new \DomainException(sprintf('Transactional locks are currently not implemented for PDO driver "%s".', $this->driver));
+            }
+        }
+
+        return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WHERE $this->idCol = :id";
+    }
+
+    /**
+     * Returns an insert statement supported by the database for writing session data.
+     *
+     * @param string $sessionId   Session ID
+     * @param string $sessionData Encoded session data
+     * @param int    $maxlifetime session.gc_maxlifetime
+     *
+     * @return \PDOStatement The insert statement
+     */
+    private function getInsertStatement($sessionId, $sessionData, $maxlifetime)
+    {
+        switch ($this->driver) {
+            case 'oci':
+                $data = fopen('php://memory', 'r+');
+                fwrite($data, $sessionData);
+                rewind($data);
+                $sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, EMPTY_BLOB(), :lifetime, :time) RETURNING $this->dataCol into :data";
+                break;
+            default:
+                $data = $sessionData;
+                $sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)";
+                break;
+        }
+
+        $stmt = $this->pdo->prepare($sql);
+        $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
+        $stmt->bindParam(':data', $data, \PDO::PARAM_LOB);
+        $stmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT);
+        $stmt->bindValue(':time', time(), \PDO::PARAM_INT);
+
+        return $stmt;
+    }
+
+    /**
+     * Returns an update statement supported by the database for writing session data.
+     *
+     * @param string $sessionId   Session ID
+     * @param string $sessionData Encoded session data
+     * @param int    $maxlifetime session.gc_maxlifetime
+     *
+     * @return \PDOStatement The update statement
+     */
+    private function getUpdateStatement($sessionId, $sessionData, $maxlifetime)
+    {
+        switch ($this->driver) {
+            case 'oci':
+                $data = fopen('php://memory', 'r+');
+                fwrite($data, $sessionData);
+                rewind($data);
+                $sql = "UPDATE $this->table SET $this->dataCol = EMPTY_BLOB(), $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id RETURNING $this->dataCol into :data";
+                break;
+            default:
+                $data = $sessionData;
+                $sql = "UPDATE $this->table SET $this->dataCol = :data, $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id";
+                break;
+        }
+
+        $stmt = $this->pdo->prepare($sql);
+        $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
+        $stmt->bindParam(':data', $data, \PDO::PARAM_LOB);
+        $stmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT);
+        $stmt->bindValue(':time', time(), \PDO::PARAM_INT);
+
+        return $stmt;
+    }
+
+    /**
+     * Returns a merge/upsert (i.e. insert or update) statement when supported by the database for writing session data.
+     *
+     * @param string $sessionId   Session ID
+     * @param string $data        Encoded session data
+     * @param int    $maxlifetime session.gc_maxlifetime
+     *
+     * @return \PDOStatement|null The merge statement or null when not supported
+     */
+    private function getMergeStatement($sessionId, $data, $maxlifetime)
+    {
+        switch (true) {
+            case 'mysql' === $this->driver:
+                $mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ".
+                    "ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)";
+                break;
+            case 'sqlsrv' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '10', '>='):
+                // MERGE is only available since SQL Server 2008 and must be terminated by semicolon
+                // It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx
+                $mergeSql = "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = ?) ".
+                    "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ".
+                    "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;";
+                break;
+            case 'sqlite' === $this->driver:
+                $mergeSql = "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)";
+                break;
+            case 'pgsql' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '9.5', '>='):
+                $mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ".
+                    "ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)";
+                break;
+            default:
+                // MERGE is not supported with LOBs: http://www.oracle.com/technetwork/articles/fuecks-lobs-095315.html
+                return null;
+        }
+
+        $mergeStmt = $this->pdo->prepare($mergeSql);
+
+        if ('sqlsrv' === $this->driver) {
+            $mergeStmt->bindParam(1, $sessionId, \PDO::PARAM_STR);
+            $mergeStmt->bindParam(2, $sessionId, \PDO::PARAM_STR);
+            $mergeStmt->bindParam(3, $data, \PDO::PARAM_LOB);
+            $mergeStmt->bindParam(4, $maxlifetime, \PDO::PARAM_INT);
+            $mergeStmt->bindValue(5, time(), \PDO::PARAM_INT);
+            $mergeStmt->bindParam(6, $data, \PDO::PARAM_LOB);
+            $mergeStmt->bindParam(7, $maxlifetime, \PDO::PARAM_INT);
+            $mergeStmt->bindValue(8, time(), \PDO::PARAM_INT);
+        } else {
+            $mergeStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
+            $mergeStmt->bindParam(':data', $data, \PDO::PARAM_LOB);
+            $mergeStmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT);
+            $mergeStmt->bindValue(':time', time(), \PDO::PARAM_INT);
+        }
+
+        return $mergeStmt;
+    }
+
+    /**
+     * Return a PDO instance.
+     *
+     * @return \PDO
+     */
+    protected function getConnection()
+    {
+        if (null === $this->pdo) {
+            $this->connect($this->dsn ?: ini_get('session.save_path'));
+        }
+
+        return $this->pdo;
+    }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..228119297d85a0162aefe69be19859c9297bf96d
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php
@@ -0,0 +1,103 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+/**
+ * Adds basic `SessionUpdateTimestampHandlerInterface` behaviors to another `SessionHandlerInterface`.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class StrictSessionHandler extends AbstractSessionHandler
+{
+    private $handler;
+    private $doDestroy;
+
+    public function __construct(\SessionHandlerInterface $handler)
+    {
+        if ($handler instanceof \SessionUpdateTimestampHandlerInterface) {
+            throw new \LogicException(sprintf('"%s" is already an instance of "SessionUpdateTimestampHandlerInterface", you cannot wrap it with "%s".', get_class($handler), self::class));
+        }
+
+        $this->handler = $handler;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function open($savePath, $sessionName)
+    {
+        parent::open($savePath, $sessionName);
+
+        return $this->handler->open($savePath, $sessionName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doRead($sessionId)
+    {
+        return $this->handler->read($sessionId);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function updateTimestamp($sessionId, $data)
+    {
+        return $this->write($sessionId, $data);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doWrite($sessionId, $data)
+    {
+        return $this->handler->write($sessionId, $data);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function destroy($sessionId)
+    {
+        $this->doDestroy = true;
+        $destroyed = parent::destroy($sessionId);
+
+        return $this->doDestroy ? $this->doDestroy($sessionId) : $destroyed;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doDestroy($sessionId)
+    {
+        $this->doDestroy = false;
+
+        return $this->handler->destroy($sessionId);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function close()
+    {
+        return $this->handler->close();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function gc($maxlifetime)
+    {
+        return $this->handler->gc($maxlifetime);
+    }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..1541ec4e0aa495803454a00b16e74c09a1e55ed3
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php
@@ -0,0 +1,92 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+@trigger_error(sprintf('The %s class is deprecated since Symfony 3.4 and will be removed in 4.0. Implement `SessionUpdateTimestampHandlerInterface` or extend `AbstractSessionHandler` instead.', WriteCheckSessionHandler::class), E_USER_DEPRECATED);
+
+/**
+ * Wraps another SessionHandlerInterface to only write the session when it has been modified.
+ *
+ * @author Adrien Brault <adrien.brault@gmail.com>
+ *
+ * @deprecated since version 3.4, to be removed in 4.0. Implement `SessionUpdateTimestampHandlerInterface` or extend `AbstractSessionHandler` instead.
+ */
+class WriteCheckSessionHandler implements \SessionHandlerInterface
+{
+    private $wrappedSessionHandler;
+
+    /**
+     * @var array sessionId => session
+     */
+    private $readSessions;
+
+    public function __construct(\SessionHandlerInterface $wrappedSessionHandler)
+    {
+        $this->wrappedSessionHandler = $wrappedSessionHandler;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function close()
+    {
+        return $this->wrappedSessionHandler->close();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function destroy($sessionId)
+    {
+        return $this->wrappedSessionHandler->destroy($sessionId);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function gc($maxlifetime)
+    {
+        return $this->wrappedSessionHandler->gc($maxlifetime);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function open($savePath, $sessionName)
+    {
+        return $this->wrappedSessionHandler->open($savePath, $sessionName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function read($sessionId)
+    {
+        $session = $this->wrappedSessionHandler->read($sessionId);
+
+        $this->readSessions[$sessionId] = $session;
+
+        return $session;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function write($sessionId, $data)
+    {
+        if (isset($this->readSessions[$sessionId]) && $data === $this->readSessions[$sessionId]) {
+            return true;
+        }
+
+        return $this->wrappedSessionHandler->write($sessionId, $data);
+    }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php b/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php
new file mode 100644
index 0000000000000000000000000000000000000000..6f59af486981ebb1b2cb9c2687cc3ef2d40250d4
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php
@@ -0,0 +1,168 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage;
+
+use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
+
+/**
+ * Metadata container.
+ *
+ * Adds metadata to the session.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class MetadataBag implements SessionBagInterface
+{
+    const CREATED = 'c';
+    const UPDATED = 'u';
+    const LIFETIME = 'l';
+
+    /**
+     * @var string
+     */
+    private $name = '__metadata';
+
+    /**
+     * @var string
+     */
+    private $storageKey;
+
+    /**
+     * @var array
+     */
+    protected $meta = array(self::CREATED => 0, self::UPDATED => 0, self::LIFETIME => 0);
+
+    /**
+     * Unix timestamp.
+     *
+     * @var int
+     */
+    private $lastUsed;
+
+    /**
+     * @var int
+     */
+    private $updateThreshold;
+
+    /**
+     * @param string $storageKey      The key used to store bag in the session
+     * @param int    $updateThreshold The time to wait between two UPDATED updates
+     */
+    public function __construct($storageKey = '_sf2_meta', $updateThreshold = 0)
+    {
+        $this->storageKey = $storageKey;
+        $this->updateThreshold = $updateThreshold;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function initialize(array &$array)
+    {
+        $this->meta = &$array;
+
+        if (isset($array[self::CREATED])) {
+            $this->lastUsed = $this->meta[self::UPDATED];
+
+            $timeStamp = time();
+            if ($timeStamp - $array[self::UPDATED] >= $this->updateThreshold) {
+                $this->meta[self::UPDATED] = $timeStamp;
+            }
+        } else {
+            $this->stampCreated();
+        }
+    }
+
+    /**
+     * Gets the lifetime that the session cookie was set with.
+     *
+     * @return int
+     */
+    public function getLifetime()
+    {
+        return $this->meta[self::LIFETIME];
+    }
+
+    /**
+     * Stamps a new session's metadata.
+     *
+     * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value
+     *                      will leave the system settings unchanged, 0 sets the cookie
+     *                      to expire with browser session. Time is in seconds, and is
+     *                      not a Unix timestamp.
+     */
+    public function stampNew($lifetime = null)
+    {
+        $this->stampCreated($lifetime);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getStorageKey()
+    {
+        return $this->storageKey;
+    }
+
+    /**
+     * Gets the created timestamp metadata.
+     *
+     * @return int Unix timestamp
+     */
+    public function getCreated()
+    {
+        return $this->meta[self::CREATED];
+    }
+
+    /**
+     * Gets the last used metadata.
+     *
+     * @return int Unix timestamp
+     */
+    public function getLastUsed()
+    {
+        return $this->lastUsed;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function clear()
+    {
+        // nothing to do
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    /**
+     * Sets name.
+     *
+     * @param string $name
+     */
+    public function setName($name)
+    {
+        $this->name = $name;
+    }
+
+    private function stampCreated($lifetime = null)
+    {
+        $timeStamp = time();
+        $this->meta[self::CREATED] = $this->meta[self::UPDATED] = $this->lastUsed = $timeStamp;
+        $this->meta[self::LIFETIME] = (null === $lifetime) ? ini_get('session.cookie_lifetime') : $lifetime;
+    }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php b/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php
new file mode 100644
index 0000000000000000000000000000000000000000..027f4efffce51b9958b3cda5133d162bfcad888e
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php
@@ -0,0 +1,256 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage;
+
+use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
+
+/**
+ * MockArraySessionStorage mocks the session for unit tests.
+ *
+ * No PHP session is actually started since a session can be initialized
+ * and shutdown only once per PHP execution cycle.
+ *
+ * When doing functional testing, you should use MockFileSessionStorage instead.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Bulat Shakirzyanov <mallluhuct@gmail.com>
+ * @author Drak <drak@zikula.org>
+ */
+class MockArraySessionStorage implements SessionStorageInterface
+{
+    /**
+     * @var string
+     */
+    protected $id = '';
+
+    /**
+     * @var string
+     */
+    protected $name;
+
+    /**
+     * @var bool
+     */
+    protected $started = false;
+
+    /**
+     * @var bool
+     */
+    protected $closed = false;
+
+    /**
+     * @var array
+     */
+    protected $data = array();
+
+    /**
+     * @var MetadataBag
+     */
+    protected $metadataBag;
+
+    /**
+     * @var array|SessionBagInterface[]
+     */
+    protected $bags = array();
+
+    /**
+     * @param string      $name    Session name
+     * @param MetadataBag $metaBag MetadataBag instance
+     */
+    public function __construct($name = 'MOCKSESSID', MetadataBag $metaBag = null)
+    {
+        $this->name = $name;
+        $this->setMetadataBag($metaBag);
+    }
+
+    public function setSessionData(array $array)
+    {
+        $this->data = $array;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function start()
+    {
+        if ($this->started) {
+            return true;
+        }
+
+        if (empty($this->id)) {
+            $this->id = $this->generateId();
+        }
+
+        $this->loadSession();
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function regenerate($destroy = false, $lifetime = null)
+    {
+        if (!$this->started) {
+            $this->start();
+        }
+
+        $this->metadataBag->stampNew($lifetime);
+        $this->id = $this->generateId();
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setId($id)
+    {
+        if ($this->started) {
+            throw new \LogicException('Cannot set session ID after the session has started.');
+        }
+
+        $this->id = $id;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setName($name)
+    {
+        $this->name = $name;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function save()
+    {
+        if (!$this->started || $this->closed) {
+            throw new \RuntimeException('Trying to save a session that was not started yet or was already closed');
+        }
+        // nothing to do since we don't persist the session data
+        $this->closed = false;
+        $this->started = false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function clear()
+    {
+        // clear out the bags
+        foreach ($this->bags as $bag) {
+            $bag->clear();
+        }
+
+        // clear out the session
+        $this->data = array();
+
+        // reconnect the bags to the session
+        $this->loadSession();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function registerBag(SessionBagInterface $bag)
+    {
+        $this->bags[$bag->getName()] = $bag;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getBag($name)
+    {
+        if (!isset($this->bags[$name])) {
+            throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name));
+        }
+
+        if (!$this->started) {
+            $this->start();
+        }
+
+        return $this->bags[$name];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function isStarted()
+    {
+        return $this->started;
+    }
+
+    public function setMetadataBag(MetadataBag $bag = null)
+    {
+        if (null === $bag) {
+            $bag = new MetadataBag();
+        }
+
+        $this->metadataBag = $bag;
+    }
+
+    /**
+     * Gets the MetadataBag.
+     *
+     * @return MetadataBag
+     */
+    public function getMetadataBag()
+    {
+        return $this->metadataBag;
+    }
+
+    /**
+     * Generates a session ID.
+     *
+     * This doesn't need to be particularly cryptographically secure since this is just
+     * a mock.
+     *
+     * @return string
+     */
+    protected function generateId()
+    {
+        return hash('sha256', uniqid('ss_mock_', true));
+    }
+
+    protected function loadSession()
+    {
+        $bags = array_merge($this->bags, array($this->metadataBag));
+
+        foreach ($bags as $bag) {
+            $key = $bag->getStorageKey();
+            $this->data[$key] = isset($this->data[$key]) ? $this->data[$key] : array();
+            $bag->initialize($this->data[$key]);
+        }
+
+        $this->started = true;
+        $this->closed = false;
+    }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php b/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php
new file mode 100644
index 0000000000000000000000000000000000000000..14f427007ba82d97351a0e77ac4f89cacb710c8c
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php
@@ -0,0 +1,152 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage;
+
+/**
+ * MockFileSessionStorage is used to mock sessions for
+ * functional testing when done in a single PHP process.
+ *
+ * No PHP session is actually started since a session can be initialized
+ * and shutdown only once per PHP execution cycle and this class does
+ * not pollute any session related globals, including session_*() functions
+ * or session.* PHP ini directives.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class MockFileSessionStorage extends MockArraySessionStorage
+{
+    private $savePath;
+
+    /**
+     * @param string      $savePath Path of directory to save session files
+     * @param string      $name     Session name
+     * @param MetadataBag $metaBag  MetadataBag instance
+     */
+    public function __construct($savePath = null, $name = 'MOCKSESSID', MetadataBag $metaBag = null)
+    {
+        if (null === $savePath) {
+            $savePath = sys_get_temp_dir();
+        }
+
+        if (!is_dir($savePath) && !@mkdir($savePath, 0777, true) && !is_dir($savePath)) {
+            throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s"', $savePath));
+        }
+
+        $this->savePath = $savePath;
+
+        parent::__construct($name, $metaBag);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function start()
+    {
+        if ($this->started) {
+            return true;
+        }
+
+        if (!$this->id) {
+            $this->id = $this->generateId();
+        }
+
+        $this->read();
+
+        $this->started = true;
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function regenerate($destroy = false, $lifetime = null)
+    {
+        if (!$this->started) {
+            $this->start();
+        }
+
+        if ($destroy) {
+            $this->destroy();
+        }
+
+        return parent::regenerate($destroy, $lifetime);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function save()
+    {
+        if (!$this->started) {
+            throw new \RuntimeException('Trying to save a session that was not started yet or was already closed');
+        }
+
+        $data = $this->data;
+
+        foreach ($this->bags as $bag) {
+            if (empty($data[$key = $bag->getStorageKey()])) {
+                unset($data[$key]);
+            }
+        }
+        if (array($key = $this->metadataBag->getStorageKey()) === array_keys($data)) {
+            unset($data[$key]);
+        }
+
+        try {
+            if ($data) {
+                file_put_contents($this->getFilePath(), serialize($data));
+            } else {
+                $this->destroy();
+            }
+        } finally {
+            $this->data = $data;
+        }
+
+        // this is needed for Silex, where the session object is re-used across requests
+        // in functional tests. In Symfony, the container is rebooted, so we don't have
+        // this issue
+        $this->started = false;
+    }
+
+    /**
+     * Deletes a session from persistent storage.
+     * Deliberately leaves session data in memory intact.
+     */
+    private function destroy()
+    {
+        if (is_file($this->getFilePath())) {
+            unlink($this->getFilePath());
+        }
+    }
+
+    /**
+     * Calculate path to file.
+     *
+     * @return string File path
+     */
+    private function getFilePath()
+    {
+        return $this->savePath.'/'.$this->id.'.mocksess';
+    }
+
+    /**
+     * Reads session from storage and loads session.
+     */
+    private function read()
+    {
+        $filePath = $this->getFilePath();
+        $this->data = is_readable($filePath) && is_file($filePath) ? unserialize(file_get_contents($filePath)) : array();
+
+        $this->loadSession();
+    }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php b/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php
new file mode 100644
index 0000000000000000000000000000000000000000..0dfad9acb353282c7d679bb82557824c3e08debe
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php
@@ -0,0 +1,445 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage;
+
+use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler;
+use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
+use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
+
+/**
+ * This provides a base class for session attribute storage.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class NativeSessionStorage implements SessionStorageInterface
+{
+    /**
+     * @var SessionBagInterface[]
+     */
+    protected $bags = array();
+
+    /**
+     * @var bool
+     */
+    protected $started = false;
+
+    /**
+     * @var bool
+     */
+    protected $closed = false;
+
+    /**
+     * @var AbstractProxy|\SessionHandlerInterface
+     */
+    protected $saveHandler;
+
+    /**
+     * @var MetadataBag
+     */
+    protected $metadataBag;
+
+    /**
+     * Depending on how you want the storage driver to behave you probably
+     * want to override this constructor entirely.
+     *
+     * List of options for $options array with their defaults.
+     *
+     * @see http://php.net/session.configuration for options
+     * but we omit 'session.' from the beginning of the keys for convenience.
+     *
+     * ("auto_start", is not supported as it tells PHP to start a session before
+     * PHP starts to execute user-land code. Setting during runtime has no effect).
+     *
+     * cache_limiter, "" (use "0" to prevent headers from being sent entirely).
+     * cache_expire, "0"
+     * cookie_domain, ""
+     * cookie_httponly, ""
+     * cookie_lifetime, "0"
+     * cookie_path, "/"
+     * cookie_secure, ""
+     * entropy_file, ""
+     * entropy_length, "0"
+     * gc_divisor, "100"
+     * gc_maxlifetime, "1440"
+     * gc_probability, "1"
+     * hash_bits_per_character, "4"
+     * hash_function, "0"
+     * lazy_write, "1"
+     * name, "PHPSESSID"
+     * referer_check, ""
+     * serialize_handler, "php"
+     * use_strict_mode, "0"
+     * use_cookies, "1"
+     * use_only_cookies, "1"
+     * use_trans_sid, "0"
+     * upload_progress.enabled, "1"
+     * upload_progress.cleanup, "1"
+     * upload_progress.prefix, "upload_progress_"
+     * upload_progress.name, "PHP_SESSION_UPLOAD_PROGRESS"
+     * upload_progress.freq, "1%"
+     * upload_progress.min-freq, "1"
+     * url_rewriter.tags, "a=href,area=href,frame=src,form=,fieldset="
+     * sid_length, "32"
+     * sid_bits_per_character, "5"
+     * trans_sid_hosts, $_SERVER['HTTP_HOST']
+     * trans_sid_tags, "a=href,area=href,frame=src,form="
+     *
+     * @param array                         $options Session configuration options
+     * @param \SessionHandlerInterface|null $handler
+     * @param MetadataBag                   $metaBag MetadataBag
+     */
+    public function __construct(array $options = array(), $handler = null, MetadataBag $metaBag = null)
+    {
+        $options += array(
+            'cache_limiter' => '',
+            'cache_expire' => 0,
+            'use_cookies' => 1,
+            'lazy_write' => 1,
+        );
+
+        session_register_shutdown();
+
+        $this->setMetadataBag($metaBag);
+        $this->setOptions($options);
+        $this->setSaveHandler($handler);
+    }
+
+    /**
+     * Gets the save handler instance.
+     *
+     * @return AbstractProxy|\SessionHandlerInterface
+     */
+    public function getSaveHandler()
+    {
+        return $this->saveHandler;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function start()
+    {
+        if ($this->started) {
+            return true;
+        }
+
+        if (\PHP_SESSION_ACTIVE === session_status()) {
+            throw new \RuntimeException('Failed to start the session: already started by PHP.');
+        }
+
+        if (ini_get('session.use_cookies') && headers_sent($file, $line)) {
+            throw new \RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line));
+        }
+
+        // ok to try and start the session
+        if (!session_start()) {
+            throw new \RuntimeException('Failed to start the session');
+        }
+
+        $this->loadSession();
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getId()
+    {
+        return $this->saveHandler->getId();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setId($id)
+    {
+        $this->saveHandler->setId($id);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getName()
+    {
+        return $this->saveHandler->getName();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setName($name)
+    {
+        $this->saveHandler->setName($name);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function regenerate($destroy = false, $lifetime = null)
+    {
+        // Cannot regenerate the session ID for non-active sessions.
+        if (\PHP_SESSION_ACTIVE !== session_status()) {
+            return false;
+        }
+
+        if (headers_sent()) {
+            return false;
+        }
+
+        if (null !== $lifetime) {
+            ini_set('session.cookie_lifetime', $lifetime);
+        }
+
+        if ($destroy) {
+            $this->metadataBag->stampNew();
+        }
+
+        $isRegenerated = session_regenerate_id($destroy);
+
+        // The reference to $_SESSION in session bags is lost in PHP7 and we need to re-create it.
+        // @see https://bugs.php.net/bug.php?id=70013
+        $this->loadSession();
+
+        return $isRegenerated;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function save()
+    {
+        $session = $_SESSION;
+
+        foreach ($this->bags as $bag) {
+            if (empty($_SESSION[$key = $bag->getStorageKey()])) {
+                unset($_SESSION[$key]);
+            }
+        }
+        if (array($key = $this->metadataBag->getStorageKey()) === array_keys($_SESSION)) {
+            unset($_SESSION[$key]);
+        }
+
+        // Register custom error handler to catch a possible failure warning during session write
+        set_error_handler(function ($errno, $errstr, $errfile, $errline) {
+            throw new \ErrorException($errstr, $errno, E_WARNING, $errfile, $errline);
+        }, E_WARNING);
+
+        try {
+            $e = null;
+            session_write_close();
+        } catch (\ErrorException $e) {
+        } finally {
+            restore_error_handler();
+            $_SESSION = $session;
+        }
+        if (null !== $e) {
+            // The default PHP error message is not very helpful, as it does not give any information on the current save handler.
+            // Therefore, we catch this error and trigger a warning with a better error message
+            $handler = $this->getSaveHandler();
+            if ($handler instanceof SessionHandlerProxy) {
+                $handler = $handler->getHandler();
+            }
+
+            trigger_error(sprintf('session_write_close(): Failed to write session data with %s handler', get_class($handler)), E_USER_WARNING);
+        }
+
+        $this->closed = true;
+        $this->started = false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function clear()
+    {
+        // clear out the bags
+        foreach ($this->bags as $bag) {
+            $bag->clear();
+        }
+
+        // clear out the session
+        $_SESSION = array();
+
+        // reconnect the bags to the session
+        $this->loadSession();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function registerBag(SessionBagInterface $bag)
+    {
+        if ($this->started) {
+            throw new \LogicException('Cannot register a bag when the session is already started.');
+        }
+
+        $this->bags[$bag->getName()] = $bag;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getBag($name)
+    {
+        if (!isset($this->bags[$name])) {
+            throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name));
+        }
+
+        if (!$this->started && $this->saveHandler->isActive()) {
+            $this->loadSession();
+        } elseif (!$this->started) {
+            $this->start();
+        }
+
+        return $this->bags[$name];
+    }
+
+    public function setMetadataBag(MetadataBag $metaBag = null)
+    {
+        if (null === $metaBag) {
+            $metaBag = new MetadataBag();
+        }
+
+        $this->metadataBag = $metaBag;
+    }
+
+    /**
+     * Gets the MetadataBag.
+     *
+     * @return MetadataBag
+     */
+    public function getMetadataBag()
+    {
+        return $this->metadataBag;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function isStarted()
+    {
+        return $this->started;
+    }
+
+    /**
+     * Sets session.* ini variables.
+     *
+     * For convenience we omit 'session.' from the beginning of the keys.
+     * Explicitly ignores other ini keys.
+     *
+     * @param array $options Session ini directives array(key => value)
+     *
+     * @see http://php.net/session.configuration
+     */
+    public function setOptions(array $options)
+    {
+        if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
+            return;
+        }
+
+        $validOptions = array_flip(array(
+            'cache_limiter', 'cache_expire', 'cookie_domain', 'cookie_httponly',
+            'cookie_lifetime', 'cookie_path', 'cookie_secure',
+            'entropy_file', 'entropy_length', 'gc_divisor',
+            'gc_maxlifetime', 'gc_probability', 'hash_bits_per_character',
+            'hash_function', 'lazy_write', 'name', 'referer_check',
+            'serialize_handler', 'use_strict_mode', 'use_cookies',
+            'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled',
+            'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name',
+            'upload_progress.freq', 'upload_progress.min-freq', 'url_rewriter.tags',
+            'sid_length', 'sid_bits_per_character', 'trans_sid_hosts', 'trans_sid_tags',
+        ));
+
+        foreach ($options as $key => $value) {
+            if (isset($validOptions[$key])) {
+                ini_set('session.'.$key, $value);
+            }
+        }
+    }
+
+    /**
+     * Registers session save handler as a PHP session handler.
+     *
+     * To use internal PHP session save handlers, override this method using ini_set with
+     * session.save_handler and session.save_path e.g.
+     *
+     *     ini_set('session.save_handler', 'files');
+     *     ini_set('session.save_path', '/tmp');
+     *
+     * or pass in a \SessionHandler instance which configures session.save_handler in the
+     * constructor, for a template see NativeFileSessionHandler or use handlers in
+     * composer package drak/native-session
+     *
+     * @see http://php.net/session-set-save-handler
+     * @see http://php.net/sessionhandlerinterface
+     * @see http://php.net/sessionhandler
+     * @see http://github.com/drak/NativeSession
+     *
+     * @param \SessionHandlerInterface|null $saveHandler
+     *
+     * @throws \InvalidArgumentException
+     */
+    public function setSaveHandler($saveHandler = null)
+    {
+        if (!$saveHandler instanceof AbstractProxy &&
+            !$saveHandler instanceof \SessionHandlerInterface &&
+            null !== $saveHandler) {
+            throw new \InvalidArgumentException('Must be instance of AbstractProxy; implement \SessionHandlerInterface; or be null.');
+        }
+
+        // Wrap $saveHandler in proxy and prevent double wrapping of proxy
+        if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) {
+            $saveHandler = new SessionHandlerProxy($saveHandler);
+        } elseif (!$saveHandler instanceof AbstractProxy) {
+            $saveHandler = new SessionHandlerProxy(new StrictSessionHandler(new \SessionHandler()));
+        }
+        $this->saveHandler = $saveHandler;
+
+        if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
+            return;
+        }
+
+        if ($this->saveHandler instanceof SessionHandlerProxy) {
+            session_set_save_handler($this->saveHandler->getHandler(), false);
+        } elseif ($this->saveHandler instanceof \SessionHandlerInterface) {
+            session_set_save_handler($this->saveHandler, false);
+        }
+    }
+
+    /**
+     * Load the session with attributes.
+     *
+     * After starting the session, PHP retrieves the session from whatever handlers
+     * are set to (either PHP's internal, or a custom save handler set with session_set_save_handler()).
+     * PHP takes the return value from the read() handler, unserializes it
+     * and populates $_SESSION with the result automatically.
+     */
+    protected function loadSession(array &$session = null)
+    {
+        if (null === $session) {
+            $session = &$_SESSION;
+        }
+
+        $bags = array_merge($this->bags, array($this->metadataBag));
+
+        foreach ($bags as $bag) {
+            $key = $bag->getStorageKey();
+            $session[$key] = isset($session[$key]) ? $session[$key] : array();
+            $bag->initialize($session[$key]);
+        }
+
+        $this->started = true;
+        $this->closed = false;
+    }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php b/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php
new file mode 100644
index 0000000000000000000000000000000000000000..662ed5015adecdb293cc112e1d49239d1615097a
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php
@@ -0,0 +1,59 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage;
+
+/**
+ * Allows session to be started by PHP and managed by Symfony.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class PhpBridgeSessionStorage extends NativeSessionStorage
+{
+    /**
+     * @param \SessionHandlerInterface|null $handler
+     * @param MetadataBag                   $metaBag MetadataBag
+     */
+    public function __construct($handler = null, MetadataBag $metaBag = null)
+    {
+        $this->setMetadataBag($metaBag);
+        $this->setSaveHandler($handler);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function start()
+    {
+        if ($this->started) {
+            return true;
+        }
+
+        $this->loadSession();
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function clear()
+    {
+        // clear out the bags and nothing else that may be set
+        // since the purpose of this driver is to share a handler
+        foreach ($this->bags as $bag) {
+            $bag->clear();
+        }
+
+        // reconnect the bags to the session
+        $this->loadSession();
+    }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php b/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php
new file mode 100644
index 0000000000000000000000000000000000000000..09c92483c7575f6b9bf7083c19a75c9ad6d9f58c
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php
@@ -0,0 +1,122 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy;
+
+/**
+ * @author Drak <drak@zikula.org>
+ */
+abstract class AbstractProxy
+{
+    /**
+     * Flag if handler wraps an internal PHP session handler (using \SessionHandler).
+     *
+     * @var bool
+     */
+    protected $wrapper = false;
+
+    /**
+     * @var string
+     */
+    protected $saveHandlerName;
+
+    /**
+     * Gets the session.save_handler name.
+     *
+     * @return string
+     */
+    public function getSaveHandlerName()
+    {
+        return $this->saveHandlerName;
+    }
+
+    /**
+     * Is this proxy handler and instance of \SessionHandlerInterface.
+     *
+     * @return bool
+     */
+    public function isSessionHandlerInterface()
+    {
+        return $this instanceof \SessionHandlerInterface;
+    }
+
+    /**
+     * Returns true if this handler wraps an internal PHP session save handler using \SessionHandler.
+     *
+     * @return bool
+     */
+    public function isWrapper()
+    {
+        return $this->wrapper;
+    }
+
+    /**
+     * Has a session started?
+     *
+     * @return bool
+     */
+    public function isActive()
+    {
+        return \PHP_SESSION_ACTIVE === session_status();
+    }
+
+    /**
+     * Gets the session ID.
+     *
+     * @return string
+     */
+    public function getId()
+    {
+        return session_id();
+    }
+
+    /**
+     * Sets the session ID.
+     *
+     * @param string $id
+     *
+     * @throws \LogicException
+     */
+    public function setId($id)
+    {
+        if ($this->isActive()) {
+            throw new \LogicException('Cannot change the ID of an active session');
+        }
+
+        session_id($id);
+    }
+
+    /**
+     * Gets the session name.
+     *
+     * @return string
+     */
+    public function getName()
+    {
+        return session_name();
+    }
+
+    /**
+     * Sets the session name.
+     *
+     * @param string $name
+     *
+     * @throws \LogicException
+     */
+    public function setName($name)
+    {
+        if ($this->isActive()) {
+            throw new \LogicException('Cannot change the name of an active session');
+        }
+
+        session_name($name);
+    }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php b/vendor/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php
new file mode 100644
index 0000000000000000000000000000000000000000..082eed143eb4340bf611eb7bac81864fb49d2e57
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php
@@ -0,0 +1,40 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy;
+
+@trigger_error('The '.__NAMESPACE__.'\NativeProxy class is deprecated since Symfony 3.4 and will be removed in 4.0. Use your session handler implementation directly.', E_USER_DEPRECATED);
+
+/**
+ * This proxy is built-in session handlers in PHP 5.3.x.
+ *
+ * @deprecated since version 3.4, to be removed in 4.0. Use your session handler implementation directly.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class NativeProxy extends AbstractProxy
+{
+    public function __construct()
+    {
+        // this makes an educated guess as to what the handler is since it should already be set.
+        $this->saveHandlerName = ini_get('session.save_handler');
+    }
+
+    /**
+     * Returns true if this handler wraps an internal PHP session save handler using \SessionHandler.
+     *
+     * @return bool False
+     */
+    public function isWrapper()
+    {
+        return false;
+    }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php b/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php
new file mode 100644
index 0000000000000000000000000000000000000000..53c1209a1c07d1fdaeacd0932764175943bdd4b0
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php
@@ -0,0 +1,85 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy;
+
+/**
+ * @author Drak <drak@zikula.org>
+ */
+class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterface
+{
+    protected $handler;
+
+    public function __construct(\SessionHandlerInterface $handler)
+    {
+        $this->handler = $handler;
+        $this->wrapper = ($handler instanceof \SessionHandler);
+        $this->saveHandlerName = $this->wrapper ? ini_get('session.save_handler') : 'user';
+    }
+
+    /**
+     * @return \SessionHandlerInterface
+     */
+    public function getHandler()
+    {
+        return $this->handler;
+    }
+
+    // \SessionHandlerInterface
+
+    /**
+     * {@inheritdoc}
+     */
+    public function open($savePath, $sessionName)
+    {
+        return (bool) $this->handler->open($savePath, $sessionName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function close()
+    {
+        return (bool) $this->handler->close();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function read($sessionId)
+    {
+        return (string) $this->handler->read($sessionId);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function write($sessionId, $data)
+    {
+        return (bool) $this->handler->write($sessionId, $data);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function destroy($sessionId)
+    {
+        return (bool) $this->handler->destroy($sessionId);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function gc($maxlifetime)
+    {
+        return (bool) $this->handler->gc($maxlifetime);
+    }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php b/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..66e8b33dd2bed4a6ed324025a591c81eaa39dbb2
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php
@@ -0,0 +1,137 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage;
+
+use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
+
+/**
+ * StorageInterface.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Drak <drak@zikula.org>
+ */
+interface SessionStorageInterface
+{
+    /**
+     * Starts the session.
+     *
+     * @return bool True if started
+     *
+     * @throws \RuntimeException if something goes wrong starting the session
+     */
+    public function start();
+
+    /**
+     * Checks if the session is started.
+     *
+     * @return bool True if started, false otherwise
+     */
+    public function isStarted();
+
+    /**
+     * Returns the session ID.
+     *
+     * @return string The session ID or empty
+     */
+    public function getId();
+
+    /**
+     * Sets the session ID.
+     *
+     * @param string $id
+     */
+    public function setId($id);
+
+    /**
+     * Returns the session name.
+     *
+     * @return mixed The session name
+     */
+    public function getName();
+
+    /**
+     * Sets the session name.
+     *
+     * @param string $name
+     */
+    public function setName($name);
+
+    /**
+     * Regenerates id that represents this storage.
+     *
+     * This method must invoke session_regenerate_id($destroy) unless
+     * this interface is used for a storage object designed for unit
+     * or functional testing where a real PHP session would interfere
+     * with testing.
+     *
+     * Note regenerate+destroy should not clear the session data in memory
+     * only delete the session data from persistent storage.
+     *
+     * Care: When regenerating the session ID no locking is involved in PHP's
+     * session design. See https://bugs.php.net/bug.php?id=61470 for a discussion.
+     * So you must make sure the regenerated session is saved BEFORE sending the
+     * headers with the new ID. Symfony's HttpKernel offers a listener for this.
+     * See Symfony\Component\HttpKernel\EventListener\SaveSessionListener.
+     * Otherwise session data could get lost again for concurrent requests with the
+     * new ID. One result could be that you get logged out after just logging in.
+     *
+     * @param bool $destroy  Destroy session when regenerating?
+     * @param int  $lifetime Sets the cookie lifetime for the session cookie. A null value
+     *                       will leave the system settings unchanged, 0 sets the cookie
+     *                       to expire with browser session. Time is in seconds, and is
+     *                       not a Unix timestamp.
+     *
+     * @return bool True if session regenerated, false if error
+     *
+     * @throws \RuntimeException If an error occurs while regenerating this storage
+     */
+    public function regenerate($destroy = false, $lifetime = null);
+
+    /**
+     * Force the session to be saved and closed.
+     *
+     * This method must invoke session_write_close() unless this interface is
+     * used for a storage object design for unit or functional testing where
+     * a real PHP session would interfere with testing, in which case
+     * it should actually persist the session data if required.
+     *
+     * @throws \RuntimeException if the session is saved without being started, or if the session
+     *                           is already closed
+     */
+    public function save();
+
+    /**
+     * Clear all session data in memory.
+     */
+    public function clear();
+
+    /**
+     * Gets a SessionBagInterface by name.
+     *
+     * @param string $name
+     *
+     * @return SessionBagInterface
+     *
+     * @throws \InvalidArgumentException If the bag does not exist
+     */
+    public function getBag($name);
+
+    /**
+     * Registers a SessionBagInterface for use.
+     */
+    public function registerBag(SessionBagInterface $bag);
+
+    /**
+     * @return MetadataBag
+     */
+    public function getMetadataBag();
+}
diff --git a/vendor/symfony/http-foundation/StreamedResponse.php b/vendor/symfony/http-foundation/StreamedResponse.php
new file mode 100644
index 0000000000000000000000000000000000000000..92868d33e48144666e7a49e4ce00377079aa82f5
--- /dev/null
+++ b/vendor/symfony/http-foundation/StreamedResponse.php
@@ -0,0 +1,144 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * StreamedResponse represents a streamed HTTP response.
+ *
+ * A StreamedResponse uses a callback for its content.
+ *
+ * The callback should use the standard PHP functions like echo
+ * to stream the response back to the client. The flush() method
+ * can also be used if needed.
+ *
+ * @see flush()
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class StreamedResponse extends Response
+{
+    protected $callback;
+    protected $streamed;
+    private $headersSent;
+
+    /**
+     * @param callable|null $callback A valid PHP callback or null to set it later
+     * @param int           $status   The response status code
+     * @param array         $headers  An array of response headers
+     */
+    public function __construct(callable $callback = null, $status = 200, $headers = array())
+    {
+        parent::__construct(null, $status, $headers);
+
+        if (null !== $callback) {
+            $this->setCallback($callback);
+        }
+        $this->streamed = false;
+        $this->headersSent = false;
+    }
+
+    /**
+     * Factory method for chainability.
+     *
+     * @param callable|null $callback A valid PHP callback or null to set it later
+     * @param int           $status   The response status code
+     * @param array         $headers  An array of response headers
+     *
+     * @return static
+     */
+    public static function create($callback = null, $status = 200, $headers = array())
+    {
+        return new static($callback, $status, $headers);
+    }
+
+    /**
+     * Sets the PHP callback associated with this Response.
+     *
+     * @param callable $callback A valid PHP callback
+     *
+     * @return $this
+     */
+    public function setCallback(callable $callback)
+    {
+        $this->callback = $callback;
+
+        return $this;
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * This method only sends the headers once.
+     *
+     * @return $this
+     */
+    public function sendHeaders()
+    {
+        if ($this->headersSent) {
+            return $this;
+        }
+
+        $this->headersSent = true;
+
+        return parent::sendHeaders();
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * This method only sends the content once.
+     *
+     * @return $this
+     */
+    public function sendContent()
+    {
+        if ($this->streamed) {
+            return $this;
+        }
+
+        $this->streamed = true;
+
+        if (null === $this->callback) {
+            throw new \LogicException('The Response callback must not be null.');
+        }
+
+        call_user_func($this->callback);
+
+        return $this;
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * @throws \LogicException when the content is not null
+     *
+     * @return $this
+     */
+    public function setContent($content)
+    {
+        if (null !== $content) {
+            throw new \LogicException('The content cannot be set on a StreamedResponse instance.');
+        }
+
+        return $this;
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * @return false
+     */
+    public function getContent()
+    {
+        return false;
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/AcceptHeaderItemTest.php b/vendor/symfony/http-foundation/Tests/AcceptHeaderItemTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..cb43bb35168a795b90967623d6d8164bdfe491ff
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/AcceptHeaderItemTest.php
@@ -0,0 +1,113 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\AcceptHeaderItem;
+
+class AcceptHeaderItemTest extends TestCase
+{
+    /**
+     * @dataProvider provideFromStringData
+     */
+    public function testFromString($string, $value, array $attributes)
+    {
+        $item = AcceptHeaderItem::fromString($string);
+        $this->assertEquals($value, $item->getValue());
+        $this->assertEquals($attributes, $item->getAttributes());
+    }
+
+    public function provideFromStringData()
+    {
+        return array(
+            array(
+                'text/html',
+                'text/html', array(),
+            ),
+            array(
+                '"this;should,not=matter"',
+                'this;should,not=matter', array(),
+            ),
+            array(
+                "text/plain; charset=utf-8;param=\"this;should,not=matter\";\tfootnotes=true",
+                'text/plain', array('charset' => 'utf-8', 'param' => 'this;should,not=matter', 'footnotes' => 'true'),
+            ),
+            array(
+                '"this;should,not=matter";charset=utf-8',
+                'this;should,not=matter', array('charset' => 'utf-8'),
+            ),
+        );
+    }
+
+    /**
+     * @dataProvider provideToStringData
+     */
+    public function testToString($value, array $attributes, $string)
+    {
+        $item = new AcceptHeaderItem($value, $attributes);
+        $this->assertEquals($string, (string) $item);
+    }
+
+    public function provideToStringData()
+    {
+        return array(
+            array(
+                'text/html', array(),
+                'text/html',
+            ),
+            array(
+                'text/plain', array('charset' => 'utf-8', 'param' => 'this;should,not=matter', 'footnotes' => 'true'),
+                'text/plain;charset=utf-8;param="this;should,not=matter";footnotes=true',
+            ),
+        );
+    }
+
+    public function testValue()
+    {
+        $item = new AcceptHeaderItem('value', array());
+        $this->assertEquals('value', $item->getValue());
+
+        $item->setValue('new value');
+        $this->assertEquals('new value', $item->getValue());
+
+        $item->setValue(1);
+        $this->assertEquals('1', $item->getValue());
+    }
+
+    public function testQuality()
+    {
+        $item = new AcceptHeaderItem('value', array());
+        $this->assertEquals(1.0, $item->getQuality());
+
+        $item->setQuality(0.5);
+        $this->assertEquals(0.5, $item->getQuality());
+
+        $item->setAttribute('q', 0.75);
+        $this->assertEquals(0.75, $item->getQuality());
+        $this->assertFalse($item->hasAttribute('q'));
+    }
+
+    public function testAttribute()
+    {
+        $item = new AcceptHeaderItem('value', array());
+        $this->assertEquals(array(), $item->getAttributes());
+        $this->assertFalse($item->hasAttribute('test'));
+        $this->assertNull($item->getAttribute('test'));
+        $this->assertEquals('default', $item->getAttribute('test', 'default'));
+
+        $item->setAttribute('test', 'value');
+        $this->assertEquals(array('test' => 'value'), $item->getAttributes());
+        $this->assertTrue($item->hasAttribute('test'));
+        $this->assertEquals('value', $item->getAttribute('test'));
+        $this->assertEquals('value', $item->getAttribute('test', 'default'));
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/AcceptHeaderTest.php b/vendor/symfony/http-foundation/Tests/AcceptHeaderTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..9929eac28ef01feabe869324fbcbf1d492597060
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/AcceptHeaderTest.php
@@ -0,0 +1,103 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\AcceptHeader;
+use Symfony\Component\HttpFoundation\AcceptHeaderItem;
+
+class AcceptHeaderTest extends TestCase
+{
+    public function testFirst()
+    {
+        $header = AcceptHeader::fromString('text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c');
+        $this->assertSame('text/html', $header->first()->getValue());
+    }
+
+    /**
+     * @dataProvider provideFromStringData
+     */
+    public function testFromString($string, array $items)
+    {
+        $header = AcceptHeader::fromString($string);
+        $parsed = array_values($header->all());
+        // reset index since the fixtures don't have them set
+        foreach ($parsed as $item) {
+            $item->setIndex(0);
+        }
+        $this->assertEquals($items, $parsed);
+    }
+
+    public function provideFromStringData()
+    {
+        return array(
+            array('', array()),
+            array('gzip', array(new AcceptHeaderItem('gzip'))),
+            array('gzip,deflate,sdch', array(new AcceptHeaderItem('gzip'), new AcceptHeaderItem('deflate'), new AcceptHeaderItem('sdch'))),
+            array("gzip, deflate\t,sdch", array(new AcceptHeaderItem('gzip'), new AcceptHeaderItem('deflate'), new AcceptHeaderItem('sdch'))),
+            array('"this;should,not=matter"', array(new AcceptHeaderItem('this;should,not=matter'))),
+        );
+    }
+
+    /**
+     * @dataProvider provideToStringData
+     */
+    public function testToString(array $items, $string)
+    {
+        $header = new AcceptHeader($items);
+        $this->assertEquals($string, (string) $header);
+    }
+
+    public function provideToStringData()
+    {
+        return array(
+            array(array(), ''),
+            array(array(new AcceptHeaderItem('gzip')), 'gzip'),
+            array(array(new AcceptHeaderItem('gzip'), new AcceptHeaderItem('deflate'), new AcceptHeaderItem('sdch')), 'gzip,deflate,sdch'),
+            array(array(new AcceptHeaderItem('this;should,not=matter')), 'this;should,not=matter'),
+        );
+    }
+
+    /**
+     * @dataProvider provideFilterData
+     */
+    public function testFilter($string, $filter, array $values)
+    {
+        $header = AcceptHeader::fromString($string)->filter($filter);
+        $this->assertEquals($values, array_keys($header->all()));
+    }
+
+    public function provideFilterData()
+    {
+        return array(
+            array('fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4', '/fr.*/', array('fr-FR', 'fr')),
+        );
+    }
+
+    /**
+     * @dataProvider provideSortingData
+     */
+    public function testSorting($string, array $values)
+    {
+        $header = AcceptHeader::fromString($string);
+        $this->assertEquals($values, array_keys($header->all()));
+    }
+
+    public function provideSortingData()
+    {
+        return array(
+            'quality has priority' => array('*;q=0.3,ISO-8859-1,utf-8;q=0.7',  array('ISO-8859-1', 'utf-8', '*')),
+            'order matters when q is equal' => array('*;q=0.3,ISO-8859-1;q=0.7,utf-8;q=0.7',  array('ISO-8859-1', 'utf-8', '*')),
+            'order matters when q is equal2' => array('*;q=0.3,utf-8;q=0.7,ISO-8859-1;q=0.7',  array('utf-8', 'ISO-8859-1', '*')),
+        );
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/ApacheRequestTest.php b/vendor/symfony/http-foundation/Tests/ApacheRequestTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..157ab90ec59a2b0d2faf9108b0f5f04ec0580c3a
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/ApacheRequestTest.php
@@ -0,0 +1,93 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\ApacheRequest;
+
+class ApacheRequestTest extends TestCase
+{
+    /**
+     * @dataProvider provideServerVars
+     */
+    public function testUriMethods($server, $expectedRequestUri, $expectedBaseUrl, $expectedPathInfo)
+    {
+        $request = new ApacheRequest();
+        $request->server->replace($server);
+
+        $this->assertEquals($expectedRequestUri, $request->getRequestUri(), '->getRequestUri() is correct');
+        $this->assertEquals($expectedBaseUrl, $request->getBaseUrl(), '->getBaseUrl() is correct');
+        $this->assertEquals($expectedPathInfo, $request->getPathInfo(), '->getPathInfo() is correct');
+    }
+
+    public function provideServerVars()
+    {
+        return array(
+            array(
+                array(
+                    'REQUEST_URI' => '/foo/app_dev.php/bar',
+                    'SCRIPT_NAME' => '/foo/app_dev.php',
+                    'PATH_INFO' => '/bar',
+                ),
+                '/foo/app_dev.php/bar',
+                '/foo/app_dev.php',
+                '/bar',
+            ),
+            array(
+                array(
+                    'REQUEST_URI' => '/foo/bar',
+                    'SCRIPT_NAME' => '/foo/app_dev.php',
+                ),
+                '/foo/bar',
+                '/foo',
+                '/bar',
+            ),
+            array(
+                array(
+                    'REQUEST_URI' => '/app_dev.php/foo/bar',
+                    'SCRIPT_NAME' => '/app_dev.php',
+                    'PATH_INFO' => '/foo/bar',
+                ),
+                '/app_dev.php/foo/bar',
+                '/app_dev.php',
+                '/foo/bar',
+            ),
+            array(
+                array(
+                    'REQUEST_URI' => '/foo/bar',
+                    'SCRIPT_NAME' => '/app_dev.php',
+                ),
+                '/foo/bar',
+                '',
+                '/foo/bar',
+            ),
+            array(
+                array(
+                    'REQUEST_URI' => '/app_dev.php',
+                    'SCRIPT_NAME' => '/app_dev.php',
+                ),
+                '/app_dev.php',
+                '/app_dev.php',
+                '/',
+            ),
+            array(
+                array(
+                    'REQUEST_URI' => '/',
+                    'SCRIPT_NAME' => '/app_dev.php',
+                ),
+                '/',
+                '',
+                '/',
+            ),
+        );
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/BinaryFileResponseTest.php b/vendor/symfony/http-foundation/Tests/BinaryFileResponseTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..1b9e58991cc6d5ba033d7c753ee9391c942fadc5
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/BinaryFileResponseTest.php
@@ -0,0 +1,352 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use Symfony\Component\HttpFoundation\BinaryFileResponse;
+use Symfony\Component\HttpFoundation\File\Stream;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\ResponseHeaderBag;
+use Symfony\Component\HttpFoundation\Tests\File\FakeFile;
+
+class BinaryFileResponseTest extends ResponseTestCase
+{
+    public function testConstruction()
+    {
+        $file = __DIR__.'/../README.md';
+        $response = new BinaryFileResponse($file, 404, array('X-Header' => 'Foo'), true, null, true, true);
+        $this->assertEquals(404, $response->getStatusCode());
+        $this->assertEquals('Foo', $response->headers->get('X-Header'));
+        $this->assertTrue($response->headers->has('ETag'));
+        $this->assertTrue($response->headers->has('Last-Modified'));
+        $this->assertFalse($response->headers->has('Content-Disposition'));
+
+        $response = BinaryFileResponse::create($file, 404, array(), true, ResponseHeaderBag::DISPOSITION_INLINE);
+        $this->assertEquals(404, $response->getStatusCode());
+        $this->assertFalse($response->headers->has('ETag'));
+        $this->assertEquals('inline; filename="README.md"', $response->headers->get('Content-Disposition'));
+    }
+
+    public function testConstructWithNonAsciiFilename()
+    {
+        touch(sys_get_temp_dir().'/fööö.html');
+
+        $response = new BinaryFileResponse(sys_get_temp_dir().'/fööö.html', 200, array(), true, 'attachment');
+
+        @unlink(sys_get_temp_dir().'/fööö.html');
+
+        $this->assertSame('fööö.html', $response->getFile()->getFilename());
+    }
+
+    /**
+     * @expectedException \LogicException
+     */
+    public function testSetContent()
+    {
+        $response = new BinaryFileResponse(__FILE__);
+        $response->setContent('foo');
+    }
+
+    public function testGetContent()
+    {
+        $response = new BinaryFileResponse(__FILE__);
+        $this->assertFalse($response->getContent());
+    }
+
+    public function testSetContentDispositionGeneratesSafeFallbackFilename()
+    {
+        $response = new BinaryFileResponse(__FILE__);
+        $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'föö.html');
+
+        $this->assertSame('attachment; filename="f__.html"; filename*=utf-8\'\'f%C3%B6%C3%B6.html', $response->headers->get('Content-Disposition'));
+    }
+
+    public function testSetContentDispositionGeneratesSafeFallbackFilenameForWronglyEncodedFilename()
+    {
+        $response = new BinaryFileResponse(__FILE__);
+
+        $iso88591EncodedFilename = utf8_decode('föö.html');
+        $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $iso88591EncodedFilename);
+
+        // the parameter filename* is invalid in this case (rawurldecode('f%F6%F6') does not provide a UTF-8 string but an ISO-8859-1 encoded one)
+        $this->assertSame('attachment; filename="f__.html"; filename*=utf-8\'\'f%F6%F6.html', $response->headers->get('Content-Disposition'));
+    }
+
+    /**
+     * @dataProvider provideRanges
+     */
+    public function testRequests($requestRange, $offset, $length, $responseRange)
+    {
+        $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'))->setAutoEtag();
+
+        // do a request to get the ETag
+        $request = Request::create('/');
+        $response->prepare($request);
+        $etag = $response->headers->get('ETag');
+
+        // prepare a request for a range of the testing file
+        $request = Request::create('/');
+        $request->headers->set('If-Range', $etag);
+        $request->headers->set('Range', $requestRange);
+
+        $file = fopen(__DIR__.'/File/Fixtures/test.gif', 'r');
+        fseek($file, $offset);
+        $data = fread($file, $length);
+        fclose($file);
+
+        $this->expectOutputString($data);
+        $response = clone $response;
+        $response->prepare($request);
+        $response->sendContent();
+
+        $this->assertEquals(206, $response->getStatusCode());
+        $this->assertEquals($responseRange, $response->headers->get('Content-Range'));
+        $this->assertSame($length, $response->headers->get('Content-Length'));
+    }
+
+    /**
+     * @dataProvider provideRanges
+     */
+    public function testRequestsWithoutEtag($requestRange, $offset, $length, $responseRange)
+    {
+        $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'));
+
+        // do a request to get the LastModified
+        $request = Request::create('/');
+        $response->prepare($request);
+        $lastModified = $response->headers->get('Last-Modified');
+
+        // prepare a request for a range of the testing file
+        $request = Request::create('/');
+        $request->headers->set('If-Range', $lastModified);
+        $request->headers->set('Range', $requestRange);
+
+        $file = fopen(__DIR__.'/File/Fixtures/test.gif', 'r');
+        fseek($file, $offset);
+        $data = fread($file, $length);
+        fclose($file);
+
+        $this->expectOutputString($data);
+        $response = clone $response;
+        $response->prepare($request);
+        $response->sendContent();
+
+        $this->assertEquals(206, $response->getStatusCode());
+        $this->assertEquals($responseRange, $response->headers->get('Content-Range'));
+    }
+
+    public function provideRanges()
+    {
+        return array(
+            array('bytes=1-4', 1, 4, 'bytes 1-4/35'),
+            array('bytes=-5', 30, 5, 'bytes 30-34/35'),
+            array('bytes=30-', 30, 5, 'bytes 30-34/35'),
+            array('bytes=30-30', 30, 1, 'bytes 30-30/35'),
+            array('bytes=30-34', 30, 5, 'bytes 30-34/35'),
+        );
+    }
+
+    public function testRangeRequestsWithoutLastModifiedDate()
+    {
+        // prevent auto last modified
+        $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'), true, null, false, false);
+
+        // prepare a request for a range of the testing file
+        $request = Request::create('/');
+        $request->headers->set('If-Range', date('D, d M Y H:i:s').' GMT');
+        $request->headers->set('Range', 'bytes=1-4');
+
+        $this->expectOutputString(file_get_contents(__DIR__.'/File/Fixtures/test.gif'));
+        $response = clone $response;
+        $response->prepare($request);
+        $response->sendContent();
+
+        $this->assertEquals(200, $response->getStatusCode());
+        $this->assertNull($response->headers->get('Content-Range'));
+    }
+
+    /**
+     * @dataProvider provideFullFileRanges
+     */
+    public function testFullFileRequests($requestRange)
+    {
+        $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'))->setAutoEtag();
+
+        // prepare a request for a range of the testing file
+        $request = Request::create('/');
+        $request->headers->set('Range', $requestRange);
+
+        $file = fopen(__DIR__.'/File/Fixtures/test.gif', 'r');
+        $data = fread($file, 35);
+        fclose($file);
+
+        $this->expectOutputString($data);
+        $response = clone $response;
+        $response->prepare($request);
+        $response->sendContent();
+
+        $this->assertEquals(200, $response->getStatusCode());
+    }
+
+    public function provideFullFileRanges()
+    {
+        return array(
+            array('bytes=0-'),
+            array('bytes=0-34'),
+            array('bytes=-35'),
+            // Syntactical invalid range-request should also return the full resource
+            array('bytes=20-10'),
+            array('bytes=50-40'),
+        );
+    }
+
+    /**
+     * @dataProvider provideInvalidRanges
+     */
+    public function testInvalidRequests($requestRange)
+    {
+        $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'))->setAutoEtag();
+
+        // prepare a request for a range of the testing file
+        $request = Request::create('/');
+        $request->headers->set('Range', $requestRange);
+
+        $response = clone $response;
+        $response->prepare($request);
+        $response->sendContent();
+
+        $this->assertEquals(416, $response->getStatusCode());
+        $this->assertEquals('bytes */35', $response->headers->get('Content-Range'));
+    }
+
+    public function provideInvalidRanges()
+    {
+        return array(
+            array('bytes=-40'),
+            array('bytes=30-40'),
+        );
+    }
+
+    /**
+     * @dataProvider provideXSendfileFiles
+     */
+    public function testXSendfile($file)
+    {
+        $request = Request::create('/');
+        $request->headers->set('X-Sendfile-Type', 'X-Sendfile');
+
+        BinaryFileResponse::trustXSendfileTypeHeader();
+        $response = BinaryFileResponse::create($file, 200, array('Content-Type' => 'application/octet-stream'));
+        $response->prepare($request);
+
+        $this->expectOutputString('');
+        $response->sendContent();
+
+        $this->assertContains('README.md', $response->headers->get('X-Sendfile'));
+    }
+
+    public function provideXSendfileFiles()
+    {
+        return array(
+            array(__DIR__.'/../README.md'),
+            array('file://'.__DIR__.'/../README.md'),
+        );
+    }
+
+    /**
+     * @dataProvider getSampleXAccelMappings
+     */
+    public function testXAccelMapping($realpath, $mapping, $virtual)
+    {
+        $request = Request::create('/');
+        $request->headers->set('X-Sendfile-Type', 'X-Accel-Redirect');
+        $request->headers->set('X-Accel-Mapping', $mapping);
+
+        $file = new FakeFile($realpath, __DIR__.'/File/Fixtures/test');
+
+        BinaryFileResponse::trustXSendfileTypeHeader();
+        $response = new BinaryFileResponse($file, 200, array('Content-Type' => 'application/octet-stream'));
+        $reflection = new \ReflectionObject($response);
+        $property = $reflection->getProperty('file');
+        $property->setAccessible(true);
+        $property->setValue($response, $file);
+
+        $response->prepare($request);
+        $this->assertEquals($virtual, $response->headers->get('X-Accel-Redirect'));
+    }
+
+    public function testDeleteFileAfterSend()
+    {
+        $request = Request::create('/');
+
+        $path = __DIR__.'/File/Fixtures/to_delete';
+        touch($path);
+        $realPath = realpath($path);
+        $this->assertFileExists($realPath);
+
+        $response = new BinaryFileResponse($realPath, 200, array('Content-Type' => 'application/octet-stream'));
+        $response->deleteFileAfterSend(true);
+
+        $response->prepare($request);
+        $response->sendContent();
+
+        $this->assertFileNotExists($path);
+    }
+
+    public function testAcceptRangeOnUnsafeMethods()
+    {
+        $request = Request::create('/', 'POST');
+        $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'));
+        $response->prepare($request);
+
+        $this->assertEquals('none', $response->headers->get('Accept-Ranges'));
+    }
+
+    public function testAcceptRangeNotOverriden()
+    {
+        $request = Request::create('/', 'POST');
+        $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'));
+        $response->headers->set('Accept-Ranges', 'foo');
+        $response->prepare($request);
+
+        $this->assertEquals('foo', $response->headers->get('Accept-Ranges'));
+    }
+
+    public function getSampleXAccelMappings()
+    {
+        return array(
+            array('/var/www/var/www/files/foo.txt', '/var/www/=/files/', '/files/var/www/files/foo.txt'),
+            array('/home/foo/bar.txt', '/var/www/=/files/,/home/foo/=/baz/', '/baz/bar.txt'),
+        );
+    }
+
+    public function testStream()
+    {
+        $request = Request::create('/');
+        $response = new BinaryFileResponse(new Stream(__DIR__.'/../README.md'), 200, array('Content-Type' => 'text/plain'));
+        $response->prepare($request);
+
+        $this->assertNull($response->headers->get('Content-Length'));
+    }
+
+    protected function provideResponse()
+    {
+        return new BinaryFileResponse(__DIR__.'/../README.md', 200, array('Content-Type' => 'application/octet-stream'));
+    }
+
+    public static function tearDownAfterClass()
+    {
+        $path = __DIR__.'/../Fixtures/to_delete';
+        if (file_exists($path)) {
+            @unlink($path);
+        }
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/CookieTest.php b/vendor/symfony/http-foundation/Tests/CookieTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..070b7dd4290e64271a440674efb7da53c86b9c5d
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/CookieTest.php
@@ -0,0 +1,223 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Cookie;
+
+/**
+ * CookieTest.
+ *
+ * @author John Kary <john@johnkary.net>
+ * @author Hugo Hamon <hugo.hamon@sensio.com>
+ *
+ * @group time-sensitive
+ */
+class CookieTest extends TestCase
+{
+    public function invalidNames()
+    {
+        return array(
+            array(''),
+            array(',MyName'),
+            array(';MyName'),
+            array(' MyName'),
+            array("\tMyName"),
+            array("\rMyName"),
+            array("\nMyName"),
+            array("\013MyName"),
+            array("\014MyName"),
+        );
+    }
+
+    /**
+     * @dataProvider invalidNames
+     * @expectedException \InvalidArgumentException
+     */
+    public function testInstantiationThrowsExceptionIfCookieNameContainsInvalidCharacters($name)
+    {
+        new Cookie($name);
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     */
+    public function testInvalidExpiration()
+    {
+        new Cookie('MyCookie', 'foo', 'bar');
+    }
+
+    public function testNegativeExpirationIsNotPossible()
+    {
+        $cookie = new Cookie('foo', 'bar', -100);
+
+        $this->assertSame(0, $cookie->getExpiresTime());
+    }
+
+    public function testGetValue()
+    {
+        $value = 'MyValue';
+        $cookie = new Cookie('MyCookie', $value);
+
+        $this->assertSame($value, $cookie->getValue(), '->getValue() returns the proper value');
+    }
+
+    public function testGetPath()
+    {
+        $cookie = new Cookie('foo', 'bar');
+
+        $this->assertSame('/', $cookie->getPath(), '->getPath() returns / as the default path');
+    }
+
+    public function testGetExpiresTime()
+    {
+        $cookie = new Cookie('foo', 'bar');
+
+        $this->assertEquals(0, $cookie->getExpiresTime(), '->getExpiresTime() returns the default expire date');
+
+        $cookie = new Cookie('foo', 'bar', $expire = time() + 3600);
+
+        $this->assertEquals($expire, $cookie->getExpiresTime(), '->getExpiresTime() returns the expire date');
+    }
+
+    public function testGetExpiresTimeIsCastToInt()
+    {
+        $cookie = new Cookie('foo', 'bar', 3600.9);
+
+        $this->assertSame(3600, $cookie->getExpiresTime(), '->getExpiresTime() returns the expire date as an integer');
+    }
+
+    public function testConstructorWithDateTime()
+    {
+        $expire = new \DateTime();
+        $cookie = new Cookie('foo', 'bar', $expire);
+
+        $this->assertEquals($expire->format('U'), $cookie->getExpiresTime(), '->getExpiresTime() returns the expire date');
+    }
+
+    /**
+     * @requires PHP 5.5
+     */
+    public function testConstructorWithDateTimeImmutable()
+    {
+        $expire = new \DateTimeImmutable();
+        $cookie = new Cookie('foo', 'bar', $expire);
+
+        $this->assertEquals($expire->format('U'), $cookie->getExpiresTime(), '->getExpiresTime() returns the expire date');
+    }
+
+    public function testGetExpiresTimeWithStringValue()
+    {
+        $value = '+1 day';
+        $cookie = new Cookie('foo', 'bar', $value);
+        $expire = strtotime($value);
+
+        $this->assertEquals($expire, $cookie->getExpiresTime(), '->getExpiresTime() returns the expire date', 1);
+    }
+
+    public function testGetDomain()
+    {
+        $cookie = new Cookie('foo', 'bar', 0, '/', '.myfoodomain.com');
+
+        $this->assertEquals('.myfoodomain.com', $cookie->getDomain(), '->getDomain() returns the domain name on which the cookie is valid');
+    }
+
+    public function testIsSecure()
+    {
+        $cookie = new Cookie('foo', 'bar', 0, '/', '.myfoodomain.com', true);
+
+        $this->assertTrue($cookie->isSecure(), '->isSecure() returns whether the cookie is transmitted over HTTPS');
+    }
+
+    public function testIsHttpOnly()
+    {
+        $cookie = new Cookie('foo', 'bar', 0, '/', '.myfoodomain.com', false, true);
+
+        $this->assertTrue($cookie->isHttpOnly(), '->isHttpOnly() returns whether the cookie is only transmitted over HTTP');
+    }
+
+    public function testCookieIsNotCleared()
+    {
+        $cookie = new Cookie('foo', 'bar', time() + 3600 * 24);
+
+        $this->assertFalse($cookie->isCleared(), '->isCleared() returns false if the cookie did not expire yet');
+    }
+
+    public function testCookieIsCleared()
+    {
+        $cookie = new Cookie('foo', 'bar', time() - 20);
+
+        $this->assertTrue($cookie->isCleared(), '->isCleared() returns true if the cookie has expired');
+    }
+
+    public function testToString()
+    {
+        $cookie = new Cookie('foo', 'bar', $expire = strtotime('Fri, 20-May-2011 15:25:52 GMT'), '/', '.myfoodomain.com', true);
+        $this->assertEquals('foo=bar; expires=Fri, 20-May-2011 15:25:52 GMT; max-age='.($expire - time()).'; path=/; domain=.myfoodomain.com; secure; httponly', (string) $cookie, '->__toString() returns string representation of the cookie');
+
+        $cookie = new Cookie('foo', 'bar with white spaces', strtotime('Fri, 20-May-2011 15:25:52 GMT'), '/', '.myfoodomain.com', true);
+        $this->assertEquals('foo=bar%20with%20white%20spaces; expires=Fri, 20-May-2011 15:25:52 GMT; max-age='.($expire - time()).'; path=/; domain=.myfoodomain.com; secure; httponly', (string) $cookie, '->__toString() encodes the value of the cookie according to RFC 3986 (white space = %20)');
+
+        $cookie = new Cookie('foo', null, 1, '/admin/', '.myfoodomain.com');
+        $this->assertEquals('foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', $expire = time() - 31536001).'; max-age='.($expire - time()).'; path=/admin/; domain=.myfoodomain.com; httponly', (string) $cookie, '->__toString() returns string representation of a cleared cookie if value is NULL');
+
+        $cookie = new Cookie('foo', 'bar', 0, '/', '');
+        $this->assertEquals('foo=bar; path=/; httponly', (string) $cookie);
+    }
+
+    public function testRawCookie()
+    {
+        $cookie = new Cookie('foo', 'b a r', 0, '/', null, false, false);
+        $this->assertFalse($cookie->isRaw());
+        $this->assertEquals('foo=b%20a%20r; path=/', (string) $cookie);
+
+        $cookie = new Cookie('foo', 'b+a+r', 0, '/', null, false, false, true);
+        $this->assertTrue($cookie->isRaw());
+        $this->assertEquals('foo=b+a+r; path=/', (string) $cookie);
+    }
+
+    public function testGetMaxAge()
+    {
+        $cookie = new Cookie('foo', 'bar');
+        $this->assertEquals(0, $cookie->getMaxAge());
+
+        $cookie = new Cookie('foo', 'bar', $expire = time() + 100);
+        $this->assertEquals($expire - time(), $cookie->getMaxAge());
+
+        $cookie = new Cookie('foo', 'bar', $expire = time() - 100);
+        $this->assertEquals($expire - time(), $cookie->getMaxAge());
+    }
+
+    public function testFromString()
+    {
+        $cookie = Cookie::fromString('foo=bar; expires=Fri, 20-May-2011 15:25:52 GMT; path=/; domain=.myfoodomain.com; secure; httponly');
+        $this->assertEquals(new Cookie('foo', 'bar', strtotime('Fri, 20-May-2011 15:25:52 GMT'), '/', '.myfoodomain.com', true, true, true), $cookie);
+
+        $cookie = Cookie::fromString('foo=bar', true);
+        $this->assertEquals(new Cookie('foo', 'bar', 0, '/', null, false, false), $cookie);
+    }
+
+    public function testFromStringWithHttpOnly()
+    {
+        $cookie = Cookie::fromString('foo=bar; expires=Fri, 20-May-2011 15:25:52 GMT; path=/; domain=.myfoodomain.com; secure; httponly');
+        $this->assertTrue($cookie->isHttpOnly());
+
+        $cookie = Cookie::fromString('foo=bar; expires=Fri, 20-May-2011 15:25:52 GMT; path=/; domain=.myfoodomain.com; secure');
+        $this->assertFalse($cookie->isHttpOnly());
+    }
+
+    public function testSameSiteAttributeIsCaseInsensitive()
+    {
+        $cookie = new Cookie('foo', 'bar', 0, '/', null, false, true, false, 'Lax');
+        $this->assertEquals('lax', $cookie->getSameSite());
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/ExpressionRequestMatcherTest.php b/vendor/symfony/http-foundation/Tests/ExpressionRequestMatcherTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..1152e46c0be7e5e9adef75a28f1ba4856e0672c8
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/ExpressionRequestMatcherTest.php
@@ -0,0 +1,69 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
+use Symfony\Component\HttpFoundation\ExpressionRequestMatcher;
+use Symfony\Component\HttpFoundation\Request;
+
+class ExpressionRequestMatcherTest extends TestCase
+{
+    /**
+     * @expectedException \LogicException
+     */
+    public function testWhenNoExpressionIsSet()
+    {
+        $expressionRequestMatcher = new ExpressionRequestMatcher();
+        $expressionRequestMatcher->matches(new Request());
+    }
+
+    /**
+     * @dataProvider provideExpressions
+     */
+    public function testMatchesWhenParentMatchesIsTrue($expression, $expected)
+    {
+        $request = Request::create('/foo');
+        $expressionRequestMatcher = new ExpressionRequestMatcher();
+
+        $expressionRequestMatcher->setExpression(new ExpressionLanguage(), $expression);
+        $this->assertSame($expected, $expressionRequestMatcher->matches($request));
+    }
+
+    /**
+     * @dataProvider provideExpressions
+     */
+    public function testMatchesWhenParentMatchesIsFalse($expression)
+    {
+        $request = Request::create('/foo');
+        $request->attributes->set('foo', 'foo');
+        $expressionRequestMatcher = new ExpressionRequestMatcher();
+        $expressionRequestMatcher->matchAttribute('foo', 'bar');
+
+        $expressionRequestMatcher->setExpression(new ExpressionLanguage(), $expression);
+        $this->assertFalse($expressionRequestMatcher->matches($request));
+    }
+
+    public function provideExpressions()
+    {
+        return array(
+            array('request.getMethod() == method', true),
+            array('request.getPathInfo() == path', true),
+            array('request.getHost() == host', true),
+            array('request.getClientIp() == ip', true),
+            array('request.attributes.all() == attributes', true),
+            array('request.getMethod() == method && request.getPathInfo() == path && request.getHost() == host && request.getClientIp() == ip &&  request.attributes.all() == attributes', true),
+            array('request.getMethod() != method', false),
+            array('request.getMethod() != method && request.getPathInfo() == path && request.getHost() == host && request.getClientIp() == ip &&  request.attributes.all() == attributes', false),
+        );
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/File/FakeFile.php b/vendor/symfony/http-foundation/Tests/File/FakeFile.php
new file mode 100644
index 0000000000000000000000000000000000000000..c415989f2f7f8320929c940fc69842dd530fb758
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/File/FakeFile.php
@@ -0,0 +1,45 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\File;
+
+use Symfony\Component\HttpFoundation\File\File as OrigFile;
+
+class FakeFile extends OrigFile
+{
+    private $realpath;
+
+    public function __construct($realpath, $path)
+    {
+        $this->realpath = $realpath;
+        parent::__construct($path, false);
+    }
+
+    public function isReadable()
+    {
+        return true;
+    }
+
+    public function getRealpath()
+    {
+        return $this->realpath;
+    }
+
+    public function getSize()
+    {
+        return 42;
+    }
+
+    public function getMTime()
+    {
+        return time();
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/File/FileTest.php b/vendor/symfony/http-foundation/Tests/File/FileTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..dbd9c44bd802cd53b92ec59189f36464b4ede843
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/File/FileTest.php
@@ -0,0 +1,180 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\File;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\File\File;
+use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser;
+
+class FileTest extends TestCase
+{
+    protected $file;
+
+    public function testGetMimeTypeUsesMimeTypeGuessers()
+    {
+        $file = new File(__DIR__.'/Fixtures/test.gif');
+        $guesser = $this->createMockGuesser($file->getPathname(), 'image/gif');
+
+        MimeTypeGuesser::getInstance()->register($guesser);
+
+        $this->assertEquals('image/gif', $file->getMimeType());
+    }
+
+    public function testGuessExtensionWithoutGuesser()
+    {
+        $file = new File(__DIR__.'/Fixtures/directory/.empty');
+
+        $this->assertNull($file->guessExtension());
+    }
+
+    public function testGuessExtensionIsBasedOnMimeType()
+    {
+        $file = new File(__DIR__.'/Fixtures/test');
+        $guesser = $this->createMockGuesser($file->getPathname(), 'image/gif');
+
+        MimeTypeGuesser::getInstance()->register($guesser);
+
+        $this->assertEquals('gif', $file->guessExtension());
+    }
+
+    /**
+     * @requires extension fileinfo
+     */
+    public function testGuessExtensionWithReset()
+    {
+        $file = new File(__DIR__.'/Fixtures/other-file.example');
+        $guesser = $this->createMockGuesser($file->getPathname(), 'image/gif');
+        MimeTypeGuesser::getInstance()->register($guesser);
+
+        $this->assertEquals('gif', $file->guessExtension());
+
+        MimeTypeGuesser::reset();
+
+        $this->assertNull($file->guessExtension());
+    }
+
+    public function testConstructWhenFileNotExists()
+    {
+        $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException');
+
+        new File(__DIR__.'/Fixtures/not_here');
+    }
+
+    public function testMove()
+    {
+        $path = __DIR__.'/Fixtures/test.copy.gif';
+        $targetDir = __DIR__.'/Fixtures/directory';
+        $targetPath = $targetDir.'/test.copy.gif';
+        @unlink($path);
+        @unlink($targetPath);
+        copy(__DIR__.'/Fixtures/test.gif', $path);
+
+        $file = new File($path);
+        $movedFile = $file->move($targetDir);
+        $this->assertInstanceOf('Symfony\Component\HttpFoundation\File\File', $movedFile);
+
+        $this->assertFileExists($targetPath);
+        $this->assertFileNotExists($path);
+        $this->assertEquals(realpath($targetPath), $movedFile->getRealPath());
+
+        @unlink($targetPath);
+    }
+
+    public function testMoveWithNewName()
+    {
+        $path = __DIR__.'/Fixtures/test.copy.gif';
+        $targetDir = __DIR__.'/Fixtures/directory';
+        $targetPath = $targetDir.'/test.newname.gif';
+        @unlink($path);
+        @unlink($targetPath);
+        copy(__DIR__.'/Fixtures/test.gif', $path);
+
+        $file = new File($path);
+        $movedFile = $file->move($targetDir, 'test.newname.gif');
+
+        $this->assertFileExists($targetPath);
+        $this->assertFileNotExists($path);
+        $this->assertEquals(realpath($targetPath), $movedFile->getRealPath());
+
+        @unlink($targetPath);
+    }
+
+    public function getFilenameFixtures()
+    {
+        return array(
+            array('original.gif', 'original.gif'),
+            array('..\\..\\original.gif', 'original.gif'),
+            array('../../original.gif', 'original.gif'),
+            array('файлfile.gif', 'файлfile.gif'),
+            array('..\\..\\файлfile.gif', 'файлfile.gif'),
+            array('../../файлfile.gif', 'файлfile.gif'),
+        );
+    }
+
+    /**
+     * @dataProvider getFilenameFixtures
+     */
+    public function testMoveWithNonLatinName($filename, $sanitizedFilename)
+    {
+        $path = __DIR__.'/Fixtures/'.$sanitizedFilename;
+        $targetDir = __DIR__.'/Fixtures/directory/';
+        $targetPath = $targetDir.$sanitizedFilename;
+        @unlink($path);
+        @unlink($targetPath);
+        copy(__DIR__.'/Fixtures/test.gif', $path);
+
+        $file = new File($path);
+        $movedFile = $file->move($targetDir, $filename);
+        $this->assertInstanceOf('Symfony\Component\HttpFoundation\File\File', $movedFile);
+
+        $this->assertFileExists($targetPath);
+        $this->assertFileNotExists($path);
+        $this->assertEquals(realpath($targetPath), $movedFile->getRealPath());
+
+        @unlink($targetPath);
+    }
+
+    public function testMoveToAnUnexistentDirectory()
+    {
+        $sourcePath = __DIR__.'/Fixtures/test.copy.gif';
+        $targetDir = __DIR__.'/Fixtures/directory/sub';
+        $targetPath = $targetDir.'/test.copy.gif';
+        @unlink($sourcePath);
+        @unlink($targetPath);
+        @rmdir($targetDir);
+        copy(__DIR__.'/Fixtures/test.gif', $sourcePath);
+
+        $file = new File($sourcePath);
+        $movedFile = $file->move($targetDir);
+
+        $this->assertFileExists($targetPath);
+        $this->assertFileNotExists($sourcePath);
+        $this->assertEquals(realpath($targetPath), $movedFile->getRealPath());
+
+        @unlink($sourcePath);
+        @unlink($targetPath);
+        @rmdir($targetDir);
+    }
+
+    protected function createMockGuesser($path, $mimeType)
+    {
+        $guesser = $this->getMockBuilder('Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface')->getMock();
+        $guesser
+            ->expects($this->once())
+            ->method('guess')
+            ->with($this->equalTo($path))
+            ->will($this->returnValue($mimeType))
+        ;
+
+        return $guesser;
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/File/Fixtures/.unknownextension b/vendor/symfony/http-foundation/Tests/File/Fixtures/.unknownextension
new file mode 100644
index 0000000000000000000000000000000000000000..4d1ae35ba2c8ec712fa2a379db44ad639ca277bd
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/File/Fixtures/.unknownextension
@@ -0,0 +1 @@
+f
\ No newline at end of file
diff --git a/vendor/symfony/http-foundation/Tests/File/Fixtures/directory/.empty b/vendor/symfony/http-foundation/Tests/File/Fixtures/directory/.empty
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/vendor/symfony/http-foundation/Tests/File/Fixtures/other-file.example b/vendor/symfony/http-foundation/Tests/File/Fixtures/other-file.example
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/vendor/symfony/http-foundation/Tests/File/Fixtures/test b/vendor/symfony/http-foundation/Tests/File/Fixtures/test
new file mode 100644
index 0000000000000000000000000000000000000000..b636f4b8df536b0a85e7cea1a6cf3f0bd3179b96
Binary files /dev/null and b/vendor/symfony/http-foundation/Tests/File/Fixtures/test differ
diff --git a/vendor/symfony/http-foundation/Tests/File/Fixtures/test.gif b/vendor/symfony/http-foundation/Tests/File/Fixtures/test.gif
new file mode 100644
index 0000000000000000000000000000000000000000..b636f4b8df536b0a85e7cea1a6cf3f0bd3179b96
Binary files /dev/null and b/vendor/symfony/http-foundation/Tests/File/Fixtures/test.gif differ
diff --git a/vendor/symfony/http-foundation/Tests/File/MimeType/MimeTypeTest.php b/vendor/symfony/http-foundation/Tests/File/MimeType/MimeTypeTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..b3f1f026a558fafdf304286a9093d0463437e183
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/File/MimeType/MimeTypeTest.php
@@ -0,0 +1,90 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\File\MimeType;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser;
+use Symfony\Component\HttpFoundation\File\MimeType\FileBinaryMimeTypeGuesser;
+
+/**
+ * @requires extension fileinfo
+ */
+class MimeTypeTest extends TestCase
+{
+    protected $path;
+
+    public function testGuessImageWithoutExtension()
+    {
+        $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test'));
+    }
+
+    public function testGuessImageWithDirectory()
+    {
+        $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException');
+
+        MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/directory');
+    }
+
+    public function testGuessImageWithFileBinaryMimeTypeGuesser()
+    {
+        $guesser = MimeTypeGuesser::getInstance();
+        $guesser->register(new FileBinaryMimeTypeGuesser());
+        $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test'));
+    }
+
+    public function testGuessImageWithKnownExtension()
+    {
+        $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test.gif'));
+    }
+
+    public function testGuessFileWithUnknownExtension()
+    {
+        $this->assertEquals('application/octet-stream', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/.unknownextension'));
+    }
+
+    public function testGuessWithIncorrectPath()
+    {
+        $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException');
+        MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/not_here');
+    }
+
+    public function testGuessWithNonReadablePath()
+    {
+        if ('\\' === DIRECTORY_SEPARATOR) {
+            $this->markTestSkipped('Can not verify chmod operations on Windows');
+        }
+
+        if (!getenv('USER') || 'root' === getenv('USER')) {
+            $this->markTestSkipped('This test will fail if run under superuser');
+        }
+
+        $path = __DIR__.'/../Fixtures/to_delete';
+        touch($path);
+        @chmod($path, 0333);
+
+        if ('0333' == substr(sprintf('%o', fileperms($path)), -4)) {
+            $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException');
+            MimeTypeGuesser::getInstance()->guess($path);
+        } else {
+            $this->markTestSkipped('Can not verify chmod operations, change of file permissions failed');
+        }
+    }
+
+    public static function tearDownAfterClass()
+    {
+        $path = __DIR__.'/../Fixtures/to_delete';
+        if (file_exists($path)) {
+            @chmod($path, 0666);
+            @unlink($path);
+        }
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/File/UploadedFileTest.php b/vendor/symfony/http-foundation/Tests/File/UploadedFileTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..36f122fe7922393b5212434d87e9ee3a9a27a82f
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/File/UploadedFileTest.php
@@ -0,0 +1,273 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\File;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\File\UploadedFile;
+
+class UploadedFileTest extends TestCase
+{
+    protected function setUp()
+    {
+        if (!ini_get('file_uploads')) {
+            $this->markTestSkipped('file_uploads is disabled in php.ini');
+        }
+    }
+
+    public function testConstructWhenFileNotExists()
+    {
+        $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException');
+
+        new UploadedFile(
+            __DIR__.'/Fixtures/not_here',
+            'original.gif',
+            null
+        );
+    }
+
+    public function testFileUploadsWithNoMimeType()
+    {
+        $file = new UploadedFile(
+            __DIR__.'/Fixtures/test.gif',
+            'original.gif',
+            null,
+            filesize(__DIR__.'/Fixtures/test.gif'),
+            UPLOAD_ERR_OK
+        );
+
+        $this->assertEquals('application/octet-stream', $file->getClientMimeType());
+
+        if (extension_loaded('fileinfo')) {
+            $this->assertEquals('image/gif', $file->getMimeType());
+        }
+    }
+
+    public function testFileUploadsWithUnknownMimeType()
+    {
+        $file = new UploadedFile(
+            __DIR__.'/Fixtures/.unknownextension',
+            'original.gif',
+            null,
+            filesize(__DIR__.'/Fixtures/.unknownextension'),
+            UPLOAD_ERR_OK
+        );
+
+        $this->assertEquals('application/octet-stream', $file->getClientMimeType());
+    }
+
+    public function testGuessClientExtension()
+    {
+        $file = new UploadedFile(
+            __DIR__.'/Fixtures/test.gif',
+            'original.gif',
+            'image/gif',
+            filesize(__DIR__.'/Fixtures/test.gif'),
+            null
+        );
+
+        $this->assertEquals('gif', $file->guessClientExtension());
+    }
+
+    public function testGuessClientExtensionWithIncorrectMimeType()
+    {
+        $file = new UploadedFile(
+            __DIR__.'/Fixtures/test.gif',
+            'original.gif',
+            'image/jpeg',
+            filesize(__DIR__.'/Fixtures/test.gif'),
+            null
+        );
+
+        $this->assertEquals('jpeg', $file->guessClientExtension());
+    }
+
+    public function testErrorIsOkByDefault()
+    {
+        $file = new UploadedFile(
+            __DIR__.'/Fixtures/test.gif',
+            'original.gif',
+            'image/gif',
+            filesize(__DIR__.'/Fixtures/test.gif'),
+            null
+        );
+
+        $this->assertEquals(UPLOAD_ERR_OK, $file->getError());
+    }
+
+    public function testGetClientOriginalName()
+    {
+        $file = new UploadedFile(
+            __DIR__.'/Fixtures/test.gif',
+            'original.gif',
+            'image/gif',
+            filesize(__DIR__.'/Fixtures/test.gif'),
+            null
+        );
+
+        $this->assertEquals('original.gif', $file->getClientOriginalName());
+    }
+
+    public function testGetClientOriginalExtension()
+    {
+        $file = new UploadedFile(
+            __DIR__.'/Fixtures/test.gif',
+            'original.gif',
+            'image/gif',
+            filesize(__DIR__.'/Fixtures/test.gif'),
+            null
+        );
+
+        $this->assertEquals('gif', $file->getClientOriginalExtension());
+    }
+
+    /**
+     * @expectedException \Symfony\Component\HttpFoundation\File\Exception\FileException
+     */
+    public function testMoveLocalFileIsNotAllowed()
+    {
+        $file = new UploadedFile(
+            __DIR__.'/Fixtures/test.gif',
+            'original.gif',
+            'image/gif',
+            filesize(__DIR__.'/Fixtures/test.gif'),
+            UPLOAD_ERR_OK
+        );
+
+        $movedFile = $file->move(__DIR__.'/Fixtures/directory');
+    }
+
+    public function testMoveLocalFileIsAllowedInTestMode()
+    {
+        $path = __DIR__.'/Fixtures/test.copy.gif';
+        $targetDir = __DIR__.'/Fixtures/directory';
+        $targetPath = $targetDir.'/test.copy.gif';
+        @unlink($path);
+        @unlink($targetPath);
+        copy(__DIR__.'/Fixtures/test.gif', $path);
+
+        $file = new UploadedFile(
+            $path,
+            'original.gif',
+            'image/gif',
+            filesize($path),
+            UPLOAD_ERR_OK,
+            true
+        );
+
+        $movedFile = $file->move(__DIR__.'/Fixtures/directory');
+
+        $this->assertFileExists($targetPath);
+        $this->assertFileNotExists($path);
+        $this->assertEquals(realpath($targetPath), $movedFile->getRealPath());
+
+        @unlink($targetPath);
+    }
+
+    public function testGetClientOriginalNameSanitizeFilename()
+    {
+        $file = new UploadedFile(
+            __DIR__.'/Fixtures/test.gif',
+            '../../original.gif',
+            'image/gif',
+            filesize(__DIR__.'/Fixtures/test.gif'),
+            null
+        );
+
+        $this->assertEquals('original.gif', $file->getClientOriginalName());
+    }
+
+    public function testGetSize()
+    {
+        $file = new UploadedFile(
+            __DIR__.'/Fixtures/test.gif',
+            'original.gif',
+            'image/gif',
+            filesize(__DIR__.'/Fixtures/test.gif'),
+            null
+        );
+
+        $this->assertEquals(filesize(__DIR__.'/Fixtures/test.gif'), $file->getSize());
+
+        $file = new UploadedFile(
+            __DIR__.'/Fixtures/test',
+            'original.gif',
+            'image/gif'
+        );
+
+        $this->assertEquals(filesize(__DIR__.'/Fixtures/test'), $file->getSize());
+    }
+
+    public function testGetExtension()
+    {
+        $file = new UploadedFile(
+            __DIR__.'/Fixtures/test.gif',
+            'original.gif',
+            null
+        );
+
+        $this->assertEquals('gif', $file->getExtension());
+    }
+
+    public function testIsValid()
+    {
+        $file = new UploadedFile(
+            __DIR__.'/Fixtures/test.gif',
+            'original.gif',
+            null,
+            filesize(__DIR__.'/Fixtures/test.gif'),
+            UPLOAD_ERR_OK,
+            true
+        );
+
+        $this->assertTrue($file->isValid());
+    }
+
+    /**
+     * @dataProvider uploadedFileErrorProvider
+     */
+    public function testIsInvalidOnUploadError($error)
+    {
+        $file = new UploadedFile(
+            __DIR__.'/Fixtures/test.gif',
+            'original.gif',
+            null,
+            filesize(__DIR__.'/Fixtures/test.gif'),
+            $error
+        );
+
+        $this->assertFalse($file->isValid());
+    }
+
+    public function uploadedFileErrorProvider()
+    {
+        return array(
+            array(UPLOAD_ERR_INI_SIZE),
+            array(UPLOAD_ERR_FORM_SIZE),
+            array(UPLOAD_ERR_PARTIAL),
+            array(UPLOAD_ERR_NO_TMP_DIR),
+            array(UPLOAD_ERR_EXTENSION),
+        );
+    }
+
+    public function testIsInvalidIfNotHttpUpload()
+    {
+        $file = new UploadedFile(
+            __DIR__.'/Fixtures/test.gif',
+            'original.gif',
+            null,
+            filesize(__DIR__.'/Fixtures/test.gif'),
+            UPLOAD_ERR_OK
+        );
+
+        $this->assertFalse($file->isValid());
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/FileBagTest.php b/vendor/symfony/http-foundation/Tests/FileBagTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..b1bbba0d3f57cc0db98d69bb2f319a414f3df923
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/FileBagTest.php
@@ -0,0 +1,175 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\File\UploadedFile;
+use Symfony\Component\HttpFoundation\FileBag;
+
+/**
+ * FileBagTest.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Bulat Shakirzyanov <mallluhuct@gmail.com>
+ */
+class FileBagTest extends TestCase
+{
+    /**
+     * @expectedException \InvalidArgumentException
+     */
+    public function testFileMustBeAnArrayOrUploadedFile()
+    {
+        new FileBag(array('file' => 'foo'));
+    }
+
+    public function testShouldConvertsUploadedFiles()
+    {
+        $tmpFile = $this->createTempFile();
+        $file = new UploadedFile($tmpFile, basename($tmpFile), 'text/plain', 100, 0);
+
+        $bag = new FileBag(array('file' => array(
+            'name' => basename($tmpFile),
+            'type' => 'text/plain',
+            'tmp_name' => $tmpFile,
+            'error' => 0,
+            'size' => 100,
+        )));
+
+        $this->assertEquals($file, $bag->get('file'));
+    }
+
+    public function testShouldSetEmptyUploadedFilesToNull()
+    {
+        $bag = new FileBag(array('file' => array(
+            'name' => '',
+            'type' => '',
+            'tmp_name' => '',
+            'error' => UPLOAD_ERR_NO_FILE,
+            'size' => 0,
+        )));
+
+        $this->assertNull($bag->get('file'));
+    }
+
+    public function testShouldRemoveEmptyUploadedFilesForMultiUpload()
+    {
+        $bag = new FileBag(array('files' => array(
+            'name' => array(''),
+            'type' => array(''),
+            'tmp_name' => array(''),
+            'error' => array(UPLOAD_ERR_NO_FILE),
+            'size' => array(0),
+        )));
+
+        $this->assertSame(array(), $bag->get('files'));
+    }
+
+    public function testShouldNotRemoveEmptyUploadedFilesForAssociativeArray()
+    {
+        $bag = new FileBag(array('files' => array(
+            'name' => array('file1' => ''),
+            'type' => array('file1' => ''),
+            'tmp_name' => array('file1' => ''),
+            'error' => array('file1' => UPLOAD_ERR_NO_FILE),
+            'size' => array('file1' => 0),
+        )));
+
+        $this->assertSame(array('file1' => null), $bag->get('files'));
+    }
+
+    public function testShouldConvertUploadedFilesWithPhpBug()
+    {
+        $tmpFile = $this->createTempFile();
+        $file = new UploadedFile($tmpFile, basename($tmpFile), 'text/plain', 100, 0);
+
+        $bag = new FileBag(array(
+            'child' => array(
+                'name' => array(
+                    'file' => basename($tmpFile),
+                ),
+                'type' => array(
+                    'file' => 'text/plain',
+                ),
+                'tmp_name' => array(
+                    'file' => $tmpFile,
+                ),
+                'error' => array(
+                    'file' => 0,
+                ),
+                'size' => array(
+                    'file' => 100,
+                ),
+            ),
+        ));
+
+        $files = $bag->all();
+        $this->assertEquals($file, $files['child']['file']);
+    }
+
+    public function testShouldConvertNestedUploadedFilesWithPhpBug()
+    {
+        $tmpFile = $this->createTempFile();
+        $file = new UploadedFile($tmpFile, basename($tmpFile), 'text/plain', 100, 0);
+
+        $bag = new FileBag(array(
+            'child' => array(
+                'name' => array(
+                    'sub' => array('file' => basename($tmpFile)),
+                ),
+                'type' => array(
+                    'sub' => array('file' => 'text/plain'),
+                ),
+                'tmp_name' => array(
+                    'sub' => array('file' => $tmpFile),
+                ),
+                'error' => array(
+                    'sub' => array('file' => 0),
+                ),
+                'size' => array(
+                    'sub' => array('file' => 100),
+                ),
+            ),
+        ));
+
+        $files = $bag->all();
+        $this->assertEquals($file, $files['child']['sub']['file']);
+    }
+
+    public function testShouldNotConvertNestedUploadedFiles()
+    {
+        $tmpFile = $this->createTempFile();
+        $file = new UploadedFile($tmpFile, basename($tmpFile), 'text/plain', 100, 0);
+        $bag = new FileBag(array('image' => array('file' => $file)));
+
+        $files = $bag->all();
+        $this->assertEquals($file, $files['image']['file']);
+    }
+
+    protected function createTempFile()
+    {
+        return tempnam(sys_get_temp_dir().'/form_test', 'FormTest');
+    }
+
+    protected function setUp()
+    {
+        mkdir(sys_get_temp_dir().'/form_test', 0777, true);
+    }
+
+    protected function tearDown()
+    {
+        foreach (glob(sys_get_temp_dir().'/form_test/*') as $file) {
+            unlink($file);
+        }
+
+        rmdir(sys_get_temp_dir().'/form_test');
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/HeaderBagTest.php b/vendor/symfony/http-foundation/Tests/HeaderBagTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..6d19ceb009f2316d13f730e145ccdf5d0d649241
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/HeaderBagTest.php
@@ -0,0 +1,205 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\HeaderBag;
+
+class HeaderBagTest extends TestCase
+{
+    public function testConstructor()
+    {
+        $bag = new HeaderBag(array('foo' => 'bar'));
+        $this->assertTrue($bag->has('foo'));
+    }
+
+    public function testToStringNull()
+    {
+        $bag = new HeaderBag();
+        $this->assertEquals('', $bag->__toString());
+    }
+
+    public function testToStringNotNull()
+    {
+        $bag = new HeaderBag(array('foo' => 'bar'));
+        $this->assertEquals("Foo: bar\r\n", $bag->__toString());
+    }
+
+    public function testKeys()
+    {
+        $bag = new HeaderBag(array('foo' => 'bar'));
+        $keys = $bag->keys();
+        $this->assertEquals('foo', $keys[0]);
+    }
+
+    public function testGetDate()
+    {
+        $bag = new HeaderBag(array('foo' => 'Tue, 4 Sep 2012 20:00:00 +0200'));
+        $headerDate = $bag->getDate('foo');
+        $this->assertInstanceOf('DateTime', $headerDate);
+    }
+
+    /**
+     * @expectedException \RuntimeException
+     */
+    public function testGetDateException()
+    {
+        $bag = new HeaderBag(array('foo' => 'Tue'));
+        $headerDate = $bag->getDate('foo');
+    }
+
+    public function testGetCacheControlHeader()
+    {
+        $bag = new HeaderBag();
+        $bag->addCacheControlDirective('public', '#a');
+        $this->assertTrue($bag->hasCacheControlDirective('public'));
+        $this->assertEquals('#a', $bag->getCacheControlDirective('public'));
+    }
+
+    public function testAll()
+    {
+        $bag = new HeaderBag(array('foo' => 'bar'));
+        $this->assertEquals(array('foo' => array('bar')), $bag->all(), '->all() gets all the input');
+
+        $bag = new HeaderBag(array('FOO' => 'BAR'));
+        $this->assertEquals(array('foo' => array('BAR')), $bag->all(), '->all() gets all the input key are lower case');
+    }
+
+    public function testReplace()
+    {
+        $bag = new HeaderBag(array('foo' => 'bar'));
+
+        $bag->replace(array('NOPE' => 'BAR'));
+        $this->assertEquals(array('nope' => array('BAR')), $bag->all(), '->replace() replaces the input with the argument');
+        $this->assertFalse($bag->has('foo'), '->replace() overrides previously set the input');
+    }
+
+    public function testGet()
+    {
+        $bag = new HeaderBag(array('foo' => 'bar', 'fuzz' => 'bizz'));
+        $this->assertEquals('bar', $bag->get('foo'), '->get return current value');
+        $this->assertEquals('bar', $bag->get('FoO'), '->get key in case insensitive');
+        $this->assertEquals(array('bar'), $bag->get('foo', 'nope', false), '->get return the value as array');
+
+        // defaults
+        $this->assertNull($bag->get('none'), '->get unknown values returns null');
+        $this->assertEquals('default', $bag->get('none', 'default'), '->get unknown values returns default');
+        $this->assertEquals(array('default'), $bag->get('none', 'default', false), '->get unknown values returns default as array');
+
+        $bag->set('foo', 'bor', false);
+        $this->assertEquals('bar', $bag->get('foo'), '->get return first value');
+        $this->assertEquals(array('bar', 'bor'), $bag->get('foo', 'nope', false), '->get return all values as array');
+    }
+
+    public function testSetAssociativeArray()
+    {
+        $bag = new HeaderBag();
+        $bag->set('foo', array('bad-assoc-index' => 'value'));
+        $this->assertSame('value', $bag->get('foo'));
+        $this->assertEquals(array('value'), $bag->get('foo', 'nope', false), 'assoc indices of multi-valued headers are ignored');
+    }
+
+    public function testContains()
+    {
+        $bag = new HeaderBag(array('foo' => 'bar', 'fuzz' => 'bizz'));
+        $this->assertTrue($bag->contains('foo', 'bar'), '->contains first value');
+        $this->assertTrue($bag->contains('fuzz', 'bizz'), '->contains second value');
+        $this->assertFalse($bag->contains('nope', 'nope'), '->contains unknown value');
+        $this->assertFalse($bag->contains('foo', 'nope'), '->contains unknown value');
+
+        // Multiple values
+        $bag->set('foo', 'bor', false);
+        $this->assertTrue($bag->contains('foo', 'bar'), '->contains first value');
+        $this->assertTrue($bag->contains('foo', 'bor'), '->contains second value');
+        $this->assertFalse($bag->contains('foo', 'nope'), '->contains unknown value');
+    }
+
+    public function testCacheControlDirectiveAccessors()
+    {
+        $bag = new HeaderBag();
+        $bag->addCacheControlDirective('public');
+
+        $this->assertTrue($bag->hasCacheControlDirective('public'));
+        $this->assertTrue($bag->getCacheControlDirective('public'));
+        $this->assertEquals('public', $bag->get('cache-control'));
+
+        $bag->addCacheControlDirective('max-age', 10);
+        $this->assertTrue($bag->hasCacheControlDirective('max-age'));
+        $this->assertEquals(10, $bag->getCacheControlDirective('max-age'));
+        $this->assertEquals('max-age=10, public', $bag->get('cache-control'));
+
+        $bag->removeCacheControlDirective('max-age');
+        $this->assertFalse($bag->hasCacheControlDirective('max-age'));
+    }
+
+    public function testCacheControlDirectiveParsing()
+    {
+        $bag = new HeaderBag(array('cache-control' => 'public, max-age=10'));
+        $this->assertTrue($bag->hasCacheControlDirective('public'));
+        $this->assertTrue($bag->getCacheControlDirective('public'));
+
+        $this->assertTrue($bag->hasCacheControlDirective('max-age'));
+        $this->assertEquals(10, $bag->getCacheControlDirective('max-age'));
+
+        $bag->addCacheControlDirective('s-maxage', 100);
+        $this->assertEquals('max-age=10, public, s-maxage=100', $bag->get('cache-control'));
+    }
+
+    public function testCacheControlDirectiveParsingQuotedZero()
+    {
+        $bag = new HeaderBag(array('cache-control' => 'max-age="0"'));
+        $this->assertTrue($bag->hasCacheControlDirective('max-age'));
+        $this->assertEquals(0, $bag->getCacheControlDirective('max-age'));
+    }
+
+    public function testCacheControlDirectiveOverrideWithReplace()
+    {
+        $bag = new HeaderBag(array('cache-control' => 'private, max-age=100'));
+        $bag->replace(array('cache-control' => 'public, max-age=10'));
+        $this->assertTrue($bag->hasCacheControlDirective('public'));
+        $this->assertTrue($bag->getCacheControlDirective('public'));
+
+        $this->assertTrue($bag->hasCacheControlDirective('max-age'));
+        $this->assertEquals(10, $bag->getCacheControlDirective('max-age'));
+    }
+
+    public function testCacheControlClone()
+    {
+        $headers = array('foo' => 'bar');
+        $bag1 = new HeaderBag($headers);
+        $bag2 = new HeaderBag($bag1->all());
+
+        $this->assertEquals($bag1->all(), $bag2->all());
+    }
+
+    public function testGetIterator()
+    {
+        $headers = array('foo' => 'bar', 'hello' => 'world', 'third' => 'charm');
+        $headerBag = new HeaderBag($headers);
+
+        $i = 0;
+        foreach ($headerBag as $key => $val) {
+            ++$i;
+            $this->assertEquals(array($headers[$key]), $val);
+        }
+
+        $this->assertEquals(count($headers), $i);
+    }
+
+    public function testCount()
+    {
+        $headers = array('foo' => 'bar', 'HELLO' => 'WORLD');
+        $headerBag = new HeaderBag($headers);
+
+        $this->assertCount(count($headers), $headerBag);
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/IpUtilsTest.php b/vendor/symfony/http-foundation/Tests/IpUtilsTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..7a93f9947262dbd03526002e1f702185c26c9426
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/IpUtilsTest.php
@@ -0,0 +1,104 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\IpUtils;
+
+class IpUtilsTest extends TestCase
+{
+    /**
+     * @dataProvider getIpv4Data
+     */
+    public function testIpv4($matches, $remoteAddr, $cidr)
+    {
+        $this->assertSame($matches, IpUtils::checkIp($remoteAddr, $cidr));
+    }
+
+    public function getIpv4Data()
+    {
+        return array(
+            array(true, '192.168.1.1', '192.168.1.1'),
+            array(true, '192.168.1.1', '192.168.1.1/1'),
+            array(true, '192.168.1.1', '192.168.1.0/24'),
+            array(false, '192.168.1.1', '1.2.3.4/1'),
+            array(false, '192.168.1.1', '192.168.1.1/33'), // invalid subnet
+            array(true, '192.168.1.1', array('1.2.3.4/1', '192.168.1.0/24')),
+            array(true, '192.168.1.1', array('192.168.1.0/24', '1.2.3.4/1')),
+            array(false, '192.168.1.1', array('1.2.3.4/1', '4.3.2.1/1')),
+            array(true, '1.2.3.4', '0.0.0.0/0'),
+            array(true, '1.2.3.4', '192.168.1.0/0'),
+            array(false, '1.2.3.4', '256.256.256/0'), // invalid CIDR notation
+            array(false, 'an_invalid_ip', '192.168.1.0/24'),
+        );
+    }
+
+    /**
+     * @dataProvider getIpv6Data
+     */
+    public function testIpv6($matches, $remoteAddr, $cidr)
+    {
+        if (!defined('AF_INET6')) {
+            $this->markTestSkipped('Only works when PHP is compiled without the option "disable-ipv6".');
+        }
+
+        $this->assertSame($matches, IpUtils::checkIp($remoteAddr, $cidr));
+    }
+
+    public function getIpv6Data()
+    {
+        return array(
+            array(true, '2a01:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'),
+            array(false, '2a00:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'),
+            array(false, '2a01:198:603:0:396e:4789:8e99:890f', '::1'),
+            array(true, '0:0:0:0:0:0:0:1', '::1'),
+            array(false, '0:0:603:0:396e:4789:8e99:0001', '::1'),
+            array(true, '0:0:603:0:396e:4789:8e99:0001', '::/0'),
+            array(true, '0:0:603:0:396e:4789:8e99:0001', '2a01:198:603:0::/0'),
+            array(true, '2a01:198:603:0:396e:4789:8e99:890f', array('::1', '2a01:198:603:0::/65')),
+            array(true, '2a01:198:603:0:396e:4789:8e99:890f', array('2a01:198:603:0::/65', '::1')),
+            array(false, '2a01:198:603:0:396e:4789:8e99:890f', array('::1', '1a01:198:603:0::/65')),
+            array(false, '}__test|O:21:&quot;JDatabaseDriverMysqli&quot;:3:{s:2', '::1'),
+            array(false, '2a01:198:603:0:396e:4789:8e99:890f', 'unknown'),
+        );
+    }
+
+    /**
+     * @expectedException \RuntimeException
+     * @requires extension sockets
+     */
+    public function testAnIpv6WithOptionDisabledIpv6()
+    {
+        if (defined('AF_INET6')) {
+            $this->markTestSkipped('Only works when PHP is compiled with the option "disable-ipv6".');
+        }
+
+        IpUtils::checkIp('2a01:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65');
+    }
+
+    /**
+     * @dataProvider invalidIpAddressData
+     */
+    public function testInvalidIpAddressesDoNotMatch($requestIp, $proxyIp)
+    {
+        $this->assertFalse(IpUtils::checkIp4($requestIp, $proxyIp));
+    }
+
+    public function invalidIpAddressData()
+    {
+        return array(
+            'invalid proxy wildcard' => array('192.168.20.13', '*'),
+            'invalid proxy missing netmask' => array('192.168.20.13', '0.0.0.0'),
+            'invalid request IP with invalid proxy wildcard' => array('0.0.0.0', '*'),
+        );
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/JsonResponseTest.php b/vendor/symfony/http-foundation/Tests/JsonResponseTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..201839f89c521f0f0dca61decd08f8cb206ece6f
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/JsonResponseTest.php
@@ -0,0 +1,257 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\JsonResponse;
+
+class JsonResponseTest extends TestCase
+{
+    public function testConstructorEmptyCreatesJsonObject()
+    {
+        $response = new JsonResponse();
+        $this->assertSame('{}', $response->getContent());
+    }
+
+    public function testConstructorWithArrayCreatesJsonArray()
+    {
+        $response = new JsonResponse(array(0, 1, 2, 3));
+        $this->assertSame('[0,1,2,3]', $response->getContent());
+    }
+
+    public function testConstructorWithAssocArrayCreatesJsonObject()
+    {
+        $response = new JsonResponse(array('foo' => 'bar'));
+        $this->assertSame('{"foo":"bar"}', $response->getContent());
+    }
+
+    public function testConstructorWithSimpleTypes()
+    {
+        $response = new JsonResponse('foo');
+        $this->assertSame('"foo"', $response->getContent());
+
+        $response = new JsonResponse(0);
+        $this->assertSame('0', $response->getContent());
+
+        $response = new JsonResponse(0.1);
+        $this->assertSame('0.1', $response->getContent());
+
+        $response = new JsonResponse(true);
+        $this->assertSame('true', $response->getContent());
+    }
+
+    public function testConstructorWithCustomStatus()
+    {
+        $response = new JsonResponse(array(), 202);
+        $this->assertSame(202, $response->getStatusCode());
+    }
+
+    public function testConstructorAddsContentTypeHeader()
+    {
+        $response = new JsonResponse();
+        $this->assertSame('application/json', $response->headers->get('Content-Type'));
+    }
+
+    public function testConstructorWithCustomHeaders()
+    {
+        $response = new JsonResponse(array(), 200, array('ETag' => 'foo'));
+        $this->assertSame('application/json', $response->headers->get('Content-Type'));
+        $this->assertSame('foo', $response->headers->get('ETag'));
+    }
+
+    public function testConstructorWithCustomContentType()
+    {
+        $headers = array('Content-Type' => 'application/vnd.acme.blog-v1+json');
+
+        $response = new JsonResponse(array(), 200, $headers);
+        $this->assertSame('application/vnd.acme.blog-v1+json', $response->headers->get('Content-Type'));
+    }
+
+    public function testSetJson()
+    {
+        $response = new JsonResponse('1', 200, array(), true);
+        $this->assertEquals('1', $response->getContent());
+
+        $response = new JsonResponse('[1]', 200, array(), true);
+        $this->assertEquals('[1]', $response->getContent());
+
+        $response = new JsonResponse(null, 200, array());
+        $response->setJson('true');
+        $this->assertEquals('true', $response->getContent());
+    }
+
+    public function testCreate()
+    {
+        $response = JsonResponse::create(array('foo' => 'bar'), 204);
+
+        $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response);
+        $this->assertEquals('{"foo":"bar"}', $response->getContent());
+        $this->assertEquals(204, $response->getStatusCode());
+    }
+
+    public function testStaticCreateEmptyJsonObject()
+    {
+        $response = JsonResponse::create();
+        $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response);
+        $this->assertSame('{}', $response->getContent());
+    }
+
+    public function testStaticCreateJsonArray()
+    {
+        $response = JsonResponse::create(array(0, 1, 2, 3));
+        $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response);
+        $this->assertSame('[0,1,2,3]', $response->getContent());
+    }
+
+    public function testStaticCreateJsonObject()
+    {
+        $response = JsonResponse::create(array('foo' => 'bar'));
+        $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response);
+        $this->assertSame('{"foo":"bar"}', $response->getContent());
+    }
+
+    public function testStaticCreateWithSimpleTypes()
+    {
+        $response = JsonResponse::create('foo');
+        $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response);
+        $this->assertSame('"foo"', $response->getContent());
+
+        $response = JsonResponse::create(0);
+        $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response);
+        $this->assertSame('0', $response->getContent());
+
+        $response = JsonResponse::create(0.1);
+        $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response);
+        $this->assertSame('0.1', $response->getContent());
+
+        $response = JsonResponse::create(true);
+        $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response);
+        $this->assertSame('true', $response->getContent());
+    }
+
+    public function testStaticCreateWithCustomStatus()
+    {
+        $response = JsonResponse::create(array(), 202);
+        $this->assertSame(202, $response->getStatusCode());
+    }
+
+    public function testStaticCreateAddsContentTypeHeader()
+    {
+        $response = JsonResponse::create();
+        $this->assertSame('application/json', $response->headers->get('Content-Type'));
+    }
+
+    public function testStaticCreateWithCustomHeaders()
+    {
+        $response = JsonResponse::create(array(), 200, array('ETag' => 'foo'));
+        $this->assertSame('application/json', $response->headers->get('Content-Type'));
+        $this->assertSame('foo', $response->headers->get('ETag'));
+    }
+
+    public function testStaticCreateWithCustomContentType()
+    {
+        $headers = array('Content-Type' => 'application/vnd.acme.blog-v1+json');
+
+        $response = JsonResponse::create(array(), 200, $headers);
+        $this->assertSame('application/vnd.acme.blog-v1+json', $response->headers->get('Content-Type'));
+    }
+
+    public function testSetCallback()
+    {
+        $response = JsonResponse::create(array('foo' => 'bar'))->setCallback('callback');
+
+        $this->assertEquals('/**/callback({"foo":"bar"});', $response->getContent());
+        $this->assertEquals('text/javascript', $response->headers->get('Content-Type'));
+    }
+
+    public function testJsonEncodeFlags()
+    {
+        $response = new JsonResponse('<>\'&"');
+
+        $this->assertEquals('"\u003C\u003E\u0027\u0026\u0022"', $response->getContent());
+    }
+
+    public function testGetEncodingOptions()
+    {
+        $response = new JsonResponse();
+
+        $this->assertEquals(JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT, $response->getEncodingOptions());
+    }
+
+    public function testSetEncodingOptions()
+    {
+        $response = new JsonResponse();
+        $response->setData(array(array(1, 2, 3)));
+
+        $this->assertEquals('[[1,2,3]]', $response->getContent());
+
+        $response->setEncodingOptions(JSON_FORCE_OBJECT);
+
+        $this->assertEquals('{"0":{"0":1,"1":2,"2":3}}', $response->getContent());
+    }
+
+    public function testItAcceptsJsonAsString()
+    {
+        $response = JsonResponse::fromJsonString('{"foo":"bar"}');
+        $this->assertSame('{"foo":"bar"}', $response->getContent());
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     */
+    public function testSetCallbackInvalidIdentifier()
+    {
+        $response = new JsonResponse('foo');
+        $response->setCallback('+invalid');
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     */
+    public function testSetContent()
+    {
+        JsonResponse::create("\xB1\x31");
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage This error is expected
+     */
+    public function testSetContentJsonSerializeError()
+    {
+        if (!interface_exists('JsonSerializable', false)) {
+            $this->markTestSkipped('JsonSerializable is required.');
+        }
+
+        $serializable = new JsonSerializableObject();
+
+        JsonResponse::create($serializable);
+    }
+
+    public function testSetComplexCallback()
+    {
+        $response = JsonResponse::create(array('foo' => 'bar'));
+        $response->setCallback('ಠ_ಠ["foo"].bar[0]');
+
+        $this->assertEquals('/**/ಠ_ಠ["foo"].bar[0]({"foo":"bar"});', $response->getContent());
+    }
+}
+
+if (interface_exists('JsonSerializable', false)) {
+    class JsonSerializableObject implements \JsonSerializable
+    {
+        public function jsonSerialize()
+        {
+            throw new \Exception('This error is expected');
+        }
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/ParameterBagTest.php b/vendor/symfony/http-foundation/Tests/ParameterBagTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..ab908d8d37de786e30d13f8cab1cefe8b671c345
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/ParameterBagTest.php
@@ -0,0 +1,194 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\ParameterBag;
+
+class ParameterBagTest extends TestCase
+{
+    public function testConstructor()
+    {
+        $this->testAll();
+    }
+
+    public function testAll()
+    {
+        $bag = new ParameterBag(array('foo' => 'bar'));
+        $this->assertEquals(array('foo' => 'bar'), $bag->all(), '->all() gets all the input');
+    }
+
+    public function testKeys()
+    {
+        $bag = new ParameterBag(array('foo' => 'bar'));
+        $this->assertEquals(array('foo'), $bag->keys());
+    }
+
+    public function testAdd()
+    {
+        $bag = new ParameterBag(array('foo' => 'bar'));
+        $bag->add(array('bar' => 'bas'));
+        $this->assertEquals(array('foo' => 'bar', 'bar' => 'bas'), $bag->all());
+    }
+
+    public function testRemove()
+    {
+        $bag = new ParameterBag(array('foo' => 'bar'));
+        $bag->add(array('bar' => 'bas'));
+        $this->assertEquals(array('foo' => 'bar', 'bar' => 'bas'), $bag->all());
+        $bag->remove('bar');
+        $this->assertEquals(array('foo' => 'bar'), $bag->all());
+    }
+
+    public function testReplace()
+    {
+        $bag = new ParameterBag(array('foo' => 'bar'));
+
+        $bag->replace(array('FOO' => 'BAR'));
+        $this->assertEquals(array('FOO' => 'BAR'), $bag->all(), '->replace() replaces the input with the argument');
+        $this->assertFalse($bag->has('foo'), '->replace() overrides previously set the input');
+    }
+
+    public function testGet()
+    {
+        $bag = new ParameterBag(array('foo' => 'bar', 'null' => null));
+
+        $this->assertEquals('bar', $bag->get('foo'), '->get() gets the value of a parameter');
+        $this->assertEquals('default', $bag->get('unknown', 'default'), '->get() returns second argument as default if a parameter is not defined');
+        $this->assertNull($bag->get('null', 'default'), '->get() returns null if null is set');
+    }
+
+    public function testGetDoesNotUseDeepByDefault()
+    {
+        $bag = new ParameterBag(array('foo' => array('bar' => 'moo')));
+
+        $this->assertNull($bag->get('foo[bar]'));
+    }
+
+    public function testSet()
+    {
+        $bag = new ParameterBag(array());
+
+        $bag->set('foo', 'bar');
+        $this->assertEquals('bar', $bag->get('foo'), '->set() sets the value of parameter');
+
+        $bag->set('foo', 'baz');
+        $this->assertEquals('baz', $bag->get('foo'), '->set() overrides previously set parameter');
+    }
+
+    public function testHas()
+    {
+        $bag = new ParameterBag(array('foo' => 'bar'));
+
+        $this->assertTrue($bag->has('foo'), '->has() returns true if a parameter is defined');
+        $this->assertFalse($bag->has('unknown'), '->has() return false if a parameter is not defined');
+    }
+
+    public function testGetAlpha()
+    {
+        $bag = new ParameterBag(array('word' => 'foo_BAR_012'));
+
+        $this->assertEquals('fooBAR', $bag->getAlpha('word'), '->getAlpha() gets only alphabetic characters');
+        $this->assertEquals('', $bag->getAlpha('unknown'), '->getAlpha() returns empty string if a parameter is not defined');
+    }
+
+    public function testGetAlnum()
+    {
+        $bag = new ParameterBag(array('word' => 'foo_BAR_012'));
+
+        $this->assertEquals('fooBAR012', $bag->getAlnum('word'), '->getAlnum() gets only alphanumeric characters');
+        $this->assertEquals('', $bag->getAlnum('unknown'), '->getAlnum() returns empty string if a parameter is not defined');
+    }
+
+    public function testGetDigits()
+    {
+        $bag = new ParameterBag(array('word' => 'foo_BAR_012'));
+
+        $this->assertEquals('012', $bag->getDigits('word'), '->getDigits() gets only digits as string');
+        $this->assertEquals('', $bag->getDigits('unknown'), '->getDigits() returns empty string if a parameter is not defined');
+    }
+
+    public function testGetInt()
+    {
+        $bag = new ParameterBag(array('digits' => '0123'));
+
+        $this->assertEquals(123, $bag->getInt('digits'), '->getInt() gets a value of parameter as integer');
+        $this->assertEquals(0, $bag->getInt('unknown'), '->getInt() returns zero if a parameter is not defined');
+    }
+
+    public function testFilter()
+    {
+        $bag = new ParameterBag(array(
+            'digits' => '0123ab',
+            'email' => 'example@example.com',
+            'url' => 'http://example.com/foo',
+            'dec' => '256',
+            'hex' => '0x100',
+            'array' => array('bang'),
+            ));
+
+        $this->assertEmpty($bag->filter('nokey'), '->filter() should return empty by default if no key is found');
+
+        $this->assertEquals('0123', $bag->filter('digits', '', FILTER_SANITIZE_NUMBER_INT), '->filter() gets a value of parameter as integer filtering out invalid characters');
+
+        $this->assertEquals('example@example.com', $bag->filter('email', '', FILTER_VALIDATE_EMAIL), '->filter() gets a value of parameter as email');
+
+        $this->assertEquals('http://example.com/foo', $bag->filter('url', '', FILTER_VALIDATE_URL, array('flags' => FILTER_FLAG_PATH_REQUIRED)), '->filter() gets a value of parameter as URL with a path');
+
+        // This test is repeated for code-coverage
+        $this->assertEquals('http://example.com/foo', $bag->filter('url', '', FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED), '->filter() gets a value of parameter as URL with a path');
+
+        $this->assertFalse($bag->filter('dec', '', FILTER_VALIDATE_INT, array(
+            'flags' => FILTER_FLAG_ALLOW_HEX,
+            'options' => array('min_range' => 1, 'max_range' => 0xff),
+        )), '->filter() gets a value of parameter as integer between boundaries');
+
+        $this->assertFalse($bag->filter('hex', '', FILTER_VALIDATE_INT, array(
+            'flags' => FILTER_FLAG_ALLOW_HEX,
+            'options' => array('min_range' => 1, 'max_range' => 0xff),
+        )), '->filter() gets a value of parameter as integer between boundaries');
+
+        $this->assertEquals(array('bang'), $bag->filter('array', ''), '->filter() gets a value of parameter as an array');
+    }
+
+    public function testGetIterator()
+    {
+        $parameters = array('foo' => 'bar', 'hello' => 'world');
+        $bag = new ParameterBag($parameters);
+
+        $i = 0;
+        foreach ($bag as $key => $val) {
+            ++$i;
+            $this->assertEquals($parameters[$key], $val);
+        }
+
+        $this->assertEquals(count($parameters), $i);
+    }
+
+    public function testCount()
+    {
+        $parameters = array('foo' => 'bar', 'hello' => 'world');
+        $bag = new ParameterBag($parameters);
+
+        $this->assertCount(count($parameters), $bag);
+    }
+
+    public function testGetBoolean()
+    {
+        $parameters = array('string_true' => 'true', 'string_false' => 'false');
+        $bag = new ParameterBag($parameters);
+
+        $this->assertTrue($bag->getBoolean('string_true'), '->getBoolean() gets the string true as boolean true');
+        $this->assertFalse($bag->getBoolean('string_false'), '->getBoolean() gets the string false as boolean false');
+        $this->assertFalse($bag->getBoolean('unknown'), '->getBoolean() returns false if a parameter is not defined');
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/RedirectResponseTest.php b/vendor/symfony/http-foundation/Tests/RedirectResponseTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..d389e83dbe14a76a1c4c9955a20c11796f26c6da
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/RedirectResponseTest.php
@@ -0,0 +1,97 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+
+class RedirectResponseTest extends TestCase
+{
+    public function testGenerateMetaRedirect()
+    {
+        $response = new RedirectResponse('foo.bar');
+
+        $this->assertEquals(1, preg_match(
+            '#<meta http-equiv="refresh" content="\d+;url=foo\.bar" />#',
+            preg_replace(array('/\s+/', '/\'/'), array(' ', '"'), $response->getContent())
+        ));
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     */
+    public function testRedirectResponseConstructorNullUrl()
+    {
+        $response = new RedirectResponse(null);
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     */
+    public function testRedirectResponseConstructorWrongStatusCode()
+    {
+        $response = new RedirectResponse('foo.bar', 404);
+    }
+
+    public function testGenerateLocationHeader()
+    {
+        $response = new RedirectResponse('foo.bar');
+
+        $this->assertTrue($response->headers->has('Location'));
+        $this->assertEquals('foo.bar', $response->headers->get('Location'));
+    }
+
+    public function testGetTargetUrl()
+    {
+        $response = new RedirectResponse('foo.bar');
+
+        $this->assertEquals('foo.bar', $response->getTargetUrl());
+    }
+
+    public function testSetTargetUrl()
+    {
+        $response = new RedirectResponse('foo.bar');
+        $response->setTargetUrl('baz.beep');
+
+        $this->assertEquals('baz.beep', $response->getTargetUrl());
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     */
+    public function testSetTargetUrlNull()
+    {
+        $response = new RedirectResponse('foo.bar');
+        $response->setTargetUrl(null);
+    }
+
+    public function testCreate()
+    {
+        $response = RedirectResponse::create('foo', 301);
+
+        $this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $response);
+        $this->assertEquals(301, $response->getStatusCode());
+    }
+
+    public function testCacheHeaders()
+    {
+        $response = new RedirectResponse('foo.bar', 301);
+        $this->assertFalse($response->headers->hasCacheControlDirective('no-cache'));
+
+        $response = new RedirectResponse('foo.bar', 301, array('cache-control' => 'max-age=86400'));
+        $this->assertFalse($response->headers->hasCacheControlDirective('no-cache'));
+        $this->assertTrue($response->headers->hasCacheControlDirective('max-age'));
+
+        $response = new RedirectResponse('foo.bar', 302);
+        $this->assertTrue($response->headers->hasCacheControlDirective('no-cache'));
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/RequestMatcherTest.php b/vendor/symfony/http-foundation/Tests/RequestMatcherTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..b5d80048ff11e2bf9abddfcd8cee115301e15e55
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/RequestMatcherTest.php
@@ -0,0 +1,151 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\RequestMatcher;
+use Symfony\Component\HttpFoundation\Request;
+
+class RequestMatcherTest extends TestCase
+{
+    /**
+     * @dataProvider getMethodData
+     */
+    public function testMethod($requestMethod, $matcherMethod, $isMatch)
+    {
+        $matcher = new RequestMatcher();
+        $matcher->matchMethod($matcherMethod);
+        $request = Request::create('', $requestMethod);
+        $this->assertSame($isMatch, $matcher->matches($request));
+
+        $matcher = new RequestMatcher(null, null, $matcherMethod);
+        $request = Request::create('', $requestMethod);
+        $this->assertSame($isMatch, $matcher->matches($request));
+    }
+
+    public function getMethodData()
+    {
+        return array(
+            array('get', 'get', true),
+            array('get', array('get', 'post'), true),
+            array('get', 'post', false),
+            array('get', 'GET', true),
+            array('get', array('GET', 'POST'), true),
+            array('get', 'POST', false),
+        );
+    }
+
+    public function testScheme()
+    {
+        $httpRequest = $request = $request = Request::create('');
+        $httpsRequest = $request = $request = Request::create('', 'get', array(), array(), array(), array('HTTPS' => 'on'));
+
+        $matcher = new RequestMatcher();
+        $matcher->matchScheme('https');
+        $this->assertFalse($matcher->matches($httpRequest));
+        $this->assertTrue($matcher->matches($httpsRequest));
+
+        $matcher->matchScheme('http');
+        $this->assertFalse($matcher->matches($httpsRequest));
+        $this->assertTrue($matcher->matches($httpRequest));
+
+        $matcher = new RequestMatcher();
+        $this->assertTrue($matcher->matches($httpsRequest));
+        $this->assertTrue($matcher->matches($httpRequest));
+    }
+
+    /**
+     * @dataProvider getHostData
+     */
+    public function testHost($pattern, $isMatch)
+    {
+        $matcher = new RequestMatcher();
+        $request = Request::create('', 'get', array(), array(), array(), array('HTTP_HOST' => 'foo.example.com'));
+
+        $matcher->matchHost($pattern);
+        $this->assertSame($isMatch, $matcher->matches($request));
+
+        $matcher = new RequestMatcher(null, $pattern);
+        $this->assertSame($isMatch, $matcher->matches($request));
+    }
+
+    public function getHostData()
+    {
+        return array(
+            array('.*\.example\.com', true),
+            array('\.example\.com$', true),
+            array('^.*\.example\.com$', true),
+            array('.*\.sensio\.com', false),
+            array('.*\.example\.COM', true),
+            array('\.example\.COM$', true),
+            array('^.*\.example\.COM$', true),
+            array('.*\.sensio\.COM', false),
+        );
+    }
+
+    public function testPath()
+    {
+        $matcher = new RequestMatcher();
+
+        $request = Request::create('/admin/foo');
+
+        $matcher->matchPath('/admin/.*');
+        $this->assertTrue($matcher->matches($request));
+
+        $matcher->matchPath('/admin');
+        $this->assertTrue($matcher->matches($request));
+
+        $matcher->matchPath('^/admin/.*$');
+        $this->assertTrue($matcher->matches($request));
+
+        $matcher->matchMethod('/blog/.*');
+        $this->assertFalse($matcher->matches($request));
+    }
+
+    public function testPathWithLocaleIsNotSupported()
+    {
+        $matcher = new RequestMatcher();
+        $request = Request::create('/en/login');
+        $request->setLocale('en');
+
+        $matcher->matchPath('^/{_locale}/login$');
+        $this->assertFalse($matcher->matches($request));
+    }
+
+    public function testPathWithEncodedCharacters()
+    {
+        $matcher = new RequestMatcher();
+        $request = Request::create('/admin/fo%20o');
+        $matcher->matchPath('^/admin/fo o*$');
+        $this->assertTrue($matcher->matches($request));
+    }
+
+    public function testAttributes()
+    {
+        $matcher = new RequestMatcher();
+
+        $request = Request::create('/admin/foo');
+        $request->attributes->set('foo', 'foo_bar');
+
+        $matcher->matchAttribute('foo', 'foo_.*');
+        $this->assertTrue($matcher->matches($request));
+
+        $matcher->matchAttribute('foo', 'foo');
+        $this->assertTrue($matcher->matches($request));
+
+        $matcher->matchAttribute('foo', '^foo_bar$');
+        $this->assertTrue($matcher->matches($request));
+
+        $matcher->matchAttribute('foo', 'babar');
+        $this->assertFalse($matcher->matches($request));
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/RequestStackTest.php b/vendor/symfony/http-foundation/Tests/RequestStackTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..a84fb26f0b59b72927e5e4c7c7a2460ad22e1a84
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/RequestStackTest.php
@@ -0,0 +1,70 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+class RequestStackTest extends TestCase
+{
+    public function testGetCurrentRequest()
+    {
+        $requestStack = new RequestStack();
+        $this->assertNull($requestStack->getCurrentRequest());
+
+        $request = Request::create('/foo');
+
+        $requestStack->push($request);
+        $this->assertSame($request, $requestStack->getCurrentRequest());
+
+        $this->assertSame($request, $requestStack->pop());
+        $this->assertNull($requestStack->getCurrentRequest());
+
+        $this->assertNull($requestStack->pop());
+    }
+
+    public function testGetMasterRequest()
+    {
+        $requestStack = new RequestStack();
+        $this->assertNull($requestStack->getMasterRequest());
+
+        $masterRequest = Request::create('/foo');
+        $subRequest = Request::create('/bar');
+
+        $requestStack->push($masterRequest);
+        $requestStack->push($subRequest);
+
+        $this->assertSame($masterRequest, $requestStack->getMasterRequest());
+    }
+
+    public function testGetParentRequest()
+    {
+        $requestStack = new RequestStack();
+        $this->assertNull($requestStack->getParentRequest());
+
+        $masterRequest = Request::create('/foo');
+
+        $requestStack->push($masterRequest);
+        $this->assertNull($requestStack->getParentRequest());
+
+        $firstSubRequest = Request::create('/bar');
+
+        $requestStack->push($firstSubRequest);
+        $this->assertSame($masterRequest, $requestStack->getParentRequest());
+
+        $secondSubRequest = Request::create('/baz');
+
+        $requestStack->push($secondSubRequest);
+        $this->assertSame($firstSubRequest, $requestStack->getParentRequest());
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/RequestTest.php b/vendor/symfony/http-foundation/Tests/RequestTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..230ad156738e8ffc4eacf4bbe8df70133cffb073
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/RequestTest.php
@@ -0,0 +1,2329 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
+use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
+use Symfony\Component\HttpFoundation\Session\Session;
+use Symfony\Component\HttpFoundation\Request;
+
+class RequestTest extends TestCase
+{
+    protected function tearDown()
+    {
+        // reset
+        Request::setTrustedProxies(array(), -1);
+    }
+
+    public function testInitialize()
+    {
+        $request = new Request();
+
+        $request->initialize(array('foo' => 'bar'));
+        $this->assertEquals('bar', $request->query->get('foo'), '->initialize() takes an array of query parameters as its first argument');
+
+        $request->initialize(array(), array('foo' => 'bar'));
+        $this->assertEquals('bar', $request->request->get('foo'), '->initialize() takes an array of request parameters as its second argument');
+
+        $request->initialize(array(), array(), array('foo' => 'bar'));
+        $this->assertEquals('bar', $request->attributes->get('foo'), '->initialize() takes an array of attributes as its third argument');
+
+        $request->initialize(array(), array(), array(), array(), array(), array('HTTP_FOO' => 'bar'));
+        $this->assertEquals('bar', $request->headers->get('FOO'), '->initialize() takes an array of HTTP headers as its sixth argument');
+    }
+
+    public function testGetLocale()
+    {
+        $request = new Request();
+        $request->setLocale('pl');
+        $locale = $request->getLocale();
+        $this->assertEquals('pl', $locale);
+    }
+
+    public function testGetUser()
+    {
+        $request = Request::create('http://user:password@test.com');
+        $user = $request->getUser();
+
+        $this->assertEquals('user', $user);
+    }
+
+    public function testGetPassword()
+    {
+        $request = Request::create('http://user:password@test.com');
+        $password = $request->getPassword();
+
+        $this->assertEquals('password', $password);
+    }
+
+    public function testIsNoCache()
+    {
+        $request = new Request();
+        $isNoCache = $request->isNoCache();
+
+        $this->assertFalse($isNoCache);
+    }
+
+    public function testGetContentType()
+    {
+        $request = new Request();
+        $contentType = $request->getContentType();
+
+        $this->assertNull($contentType);
+    }
+
+    public function testSetDefaultLocale()
+    {
+        $request = new Request();
+        $request->setDefaultLocale('pl');
+        $locale = $request->getLocale();
+
+        $this->assertEquals('pl', $locale);
+    }
+
+    public function testCreate()
+    {
+        $request = Request::create('http://test.com/foo?bar=baz');
+        $this->assertEquals('http://test.com/foo?bar=baz', $request->getUri());
+        $this->assertEquals('/foo', $request->getPathInfo());
+        $this->assertEquals('bar=baz', $request->getQueryString());
+        $this->assertEquals(80, $request->getPort());
+        $this->assertEquals('test.com', $request->getHttpHost());
+        $this->assertFalse($request->isSecure());
+
+        $request = Request::create('http://test.com/foo', 'GET', array('bar' => 'baz'));
+        $this->assertEquals('http://test.com/foo?bar=baz', $request->getUri());
+        $this->assertEquals('/foo', $request->getPathInfo());
+        $this->assertEquals('bar=baz', $request->getQueryString());
+        $this->assertEquals(80, $request->getPort());
+        $this->assertEquals('test.com', $request->getHttpHost());
+        $this->assertFalse($request->isSecure());
+
+        $request = Request::create('http://test.com/foo?bar=foo', 'GET', array('bar' => 'baz'));
+        $this->assertEquals('http://test.com/foo?bar=baz', $request->getUri());
+        $this->assertEquals('/foo', $request->getPathInfo());
+        $this->assertEquals('bar=baz', $request->getQueryString());
+        $this->assertEquals(80, $request->getPort());
+        $this->assertEquals('test.com', $request->getHttpHost());
+        $this->assertFalse($request->isSecure());
+
+        $request = Request::create('https://test.com/foo?bar=baz');
+        $this->assertEquals('https://test.com/foo?bar=baz', $request->getUri());
+        $this->assertEquals('/foo', $request->getPathInfo());
+        $this->assertEquals('bar=baz', $request->getQueryString());
+        $this->assertEquals(443, $request->getPort());
+        $this->assertEquals('test.com', $request->getHttpHost());
+        $this->assertTrue($request->isSecure());
+
+        $request = Request::create('test.com:90/foo');
+        $this->assertEquals('http://test.com:90/foo', $request->getUri());
+        $this->assertEquals('/foo', $request->getPathInfo());
+        $this->assertEquals('test.com', $request->getHost());
+        $this->assertEquals('test.com:90', $request->getHttpHost());
+        $this->assertEquals(90, $request->getPort());
+        $this->assertFalse($request->isSecure());
+
+        $request = Request::create('https://test.com:90/foo');
+        $this->assertEquals('https://test.com:90/foo', $request->getUri());
+        $this->assertEquals('/foo', $request->getPathInfo());
+        $this->assertEquals('test.com', $request->getHost());
+        $this->assertEquals('test.com:90', $request->getHttpHost());
+        $this->assertEquals(90, $request->getPort());
+        $this->assertTrue($request->isSecure());
+
+        $request = Request::create('https://127.0.0.1:90/foo');
+        $this->assertEquals('https://127.0.0.1:90/foo', $request->getUri());
+        $this->assertEquals('/foo', $request->getPathInfo());
+        $this->assertEquals('127.0.0.1', $request->getHost());
+        $this->assertEquals('127.0.0.1:90', $request->getHttpHost());
+        $this->assertEquals(90, $request->getPort());
+        $this->assertTrue($request->isSecure());
+
+        $request = Request::create('https://[::1]:90/foo');
+        $this->assertEquals('https://[::1]:90/foo', $request->getUri());
+        $this->assertEquals('/foo', $request->getPathInfo());
+        $this->assertEquals('[::1]', $request->getHost());
+        $this->assertEquals('[::1]:90', $request->getHttpHost());
+        $this->assertEquals(90, $request->getPort());
+        $this->assertTrue($request->isSecure());
+
+        $request = Request::create('https://[::1]/foo');
+        $this->assertEquals('https://[::1]/foo', $request->getUri());
+        $this->assertEquals('/foo', $request->getPathInfo());
+        $this->assertEquals('[::1]', $request->getHost());
+        $this->assertEquals('[::1]', $request->getHttpHost());
+        $this->assertEquals(443, $request->getPort());
+        $this->assertTrue($request->isSecure());
+
+        $json = '{"jsonrpc":"2.0","method":"echo","id":7,"params":["Hello World"]}';
+        $request = Request::create('http://example.com/jsonrpc', 'POST', array(), array(), array(), array(), $json);
+        $this->assertEquals($json, $request->getContent());
+        $this->assertFalse($request->isSecure());
+
+        $request = Request::create('http://test.com');
+        $this->assertEquals('http://test.com/', $request->getUri());
+        $this->assertEquals('/', $request->getPathInfo());
+        $this->assertEquals('', $request->getQueryString());
+        $this->assertEquals(80, $request->getPort());
+        $this->assertEquals('test.com', $request->getHttpHost());
+        $this->assertFalse($request->isSecure());
+
+        $request = Request::create('http://test.com?test=1');
+        $this->assertEquals('http://test.com/?test=1', $request->getUri());
+        $this->assertEquals('/', $request->getPathInfo());
+        $this->assertEquals('test=1', $request->getQueryString());
+        $this->assertEquals(80, $request->getPort());
+        $this->assertEquals('test.com', $request->getHttpHost());
+        $this->assertFalse($request->isSecure());
+
+        $request = Request::create('http://test.com:90/?test=1');
+        $this->assertEquals('http://test.com:90/?test=1', $request->getUri());
+        $this->assertEquals('/', $request->getPathInfo());
+        $this->assertEquals('test=1', $request->getQueryString());
+        $this->assertEquals(90, $request->getPort());
+        $this->assertEquals('test.com:90', $request->getHttpHost());
+        $this->assertFalse($request->isSecure());
+
+        $request = Request::create('http://username:password@test.com');
+        $this->assertEquals('http://test.com/', $request->getUri());
+        $this->assertEquals('/', $request->getPathInfo());
+        $this->assertEquals('', $request->getQueryString());
+        $this->assertEquals(80, $request->getPort());
+        $this->assertEquals('test.com', $request->getHttpHost());
+        $this->assertEquals('username', $request->getUser());
+        $this->assertEquals('password', $request->getPassword());
+        $this->assertFalse($request->isSecure());
+
+        $request = Request::create('http://username@test.com');
+        $this->assertEquals('http://test.com/', $request->getUri());
+        $this->assertEquals('/', $request->getPathInfo());
+        $this->assertEquals('', $request->getQueryString());
+        $this->assertEquals(80, $request->getPort());
+        $this->assertEquals('test.com', $request->getHttpHost());
+        $this->assertEquals('username', $request->getUser());
+        $this->assertSame('', $request->getPassword());
+        $this->assertFalse($request->isSecure());
+
+        $request = Request::create('http://test.com/?foo');
+        $this->assertEquals('/?foo', $request->getRequestUri());
+        $this->assertEquals(array('foo' => ''), $request->query->all());
+
+        // assume rewrite rule: (.*) --> app/app.php; app/ is a symlink to a symfony web/ directory
+        $request = Request::create('http://test.com/apparthotel-1234', 'GET', array(), array(), array(),
+            array(
+                'DOCUMENT_ROOT' => '/var/www/www.test.com',
+                'SCRIPT_FILENAME' => '/var/www/www.test.com/app/app.php',
+                'SCRIPT_NAME' => '/app/app.php',
+                'PHP_SELF' => '/app/app.php/apparthotel-1234',
+            ));
+        $this->assertEquals('http://test.com/apparthotel-1234', $request->getUri());
+        $this->assertEquals('/apparthotel-1234', $request->getPathInfo());
+        $this->assertEquals('', $request->getQueryString());
+        $this->assertEquals(80, $request->getPort());
+        $this->assertEquals('test.com', $request->getHttpHost());
+        $this->assertFalse($request->isSecure());
+    }
+
+    public function testCreateCheckPrecedence()
+    {
+        // server is used by default
+        $request = Request::create('/', 'DELETE', array(), array(), array(), array(
+            'HTTP_HOST' => 'example.com',
+            'HTTPS' => 'on',
+            'SERVER_PORT' => 443,
+            'PHP_AUTH_USER' => 'fabien',
+            'PHP_AUTH_PW' => 'pa$$',
+            'QUERY_STRING' => 'foo=bar',
+            'CONTENT_TYPE' => 'application/json',
+        ));
+        $this->assertEquals('example.com', $request->getHost());
+        $this->assertEquals(443, $request->getPort());
+        $this->assertTrue($request->isSecure());
+        $this->assertEquals('fabien', $request->getUser());
+        $this->assertEquals('pa$$', $request->getPassword());
+        $this->assertEquals('', $request->getQueryString());
+        $this->assertEquals('application/json', $request->headers->get('CONTENT_TYPE'));
+
+        // URI has precedence over server
+        $request = Request::create('http://thomas:pokemon@example.net:8080/?foo=bar', 'GET', array(), array(), array(), array(
+            'HTTP_HOST' => 'example.com',
+            'HTTPS' => 'on',
+            'SERVER_PORT' => 443,
+        ));
+        $this->assertEquals('example.net', $request->getHost());
+        $this->assertEquals(8080, $request->getPort());
+        $this->assertFalse($request->isSecure());
+        $this->assertEquals('thomas', $request->getUser());
+        $this->assertEquals('pokemon', $request->getPassword());
+        $this->assertEquals('foo=bar', $request->getQueryString());
+    }
+
+    public function testDuplicate()
+    {
+        $request = new Request(array('foo' => 'bar'), array('foo' => 'bar'), array('foo' => 'bar'), array(), array(), array('HTTP_FOO' => 'bar'));
+        $dup = $request->duplicate();
+
+        $this->assertEquals($request->query->all(), $dup->query->all(), '->duplicate() duplicates a request an copy the current query parameters');
+        $this->assertEquals($request->request->all(), $dup->request->all(), '->duplicate() duplicates a request an copy the current request parameters');
+        $this->assertEquals($request->attributes->all(), $dup->attributes->all(), '->duplicate() duplicates a request an copy the current attributes');
+        $this->assertEquals($request->headers->all(), $dup->headers->all(), '->duplicate() duplicates a request an copy the current HTTP headers');
+
+        $dup = $request->duplicate(array('foo' => 'foobar'), array('foo' => 'foobar'), array('foo' => 'foobar'), array(), array(), array('HTTP_FOO' => 'foobar'));
+
+        $this->assertEquals(array('foo' => 'foobar'), $dup->query->all(), '->duplicate() overrides the query parameters if provided');
+        $this->assertEquals(array('foo' => 'foobar'), $dup->request->all(), '->duplicate() overrides the request parameters if provided');
+        $this->assertEquals(array('foo' => 'foobar'), $dup->attributes->all(), '->duplicate() overrides the attributes if provided');
+        $this->assertEquals(array('foo' => array('foobar')), $dup->headers->all(), '->duplicate() overrides the HTTP header if provided');
+    }
+
+    public function testDuplicateWithFormat()
+    {
+        $request = new Request(array(), array(), array('_format' => 'json'));
+        $dup = $request->duplicate();
+
+        $this->assertEquals('json', $dup->getRequestFormat());
+        $this->assertEquals('json', $dup->attributes->get('_format'));
+
+        $request = new Request();
+        $request->setRequestFormat('xml');
+        $dup = $request->duplicate();
+
+        $this->assertEquals('xml', $dup->getRequestFormat());
+    }
+
+    /**
+     * @dataProvider getFormatToMimeTypeMapProviderWithAdditionalNullFormat
+     */
+    public function testGetFormatFromMimeType($format, $mimeTypes)
+    {
+        $request = new Request();
+        foreach ($mimeTypes as $mime) {
+            $this->assertEquals($format, $request->getFormat($mime));
+        }
+        $request->setFormat($format, $mimeTypes);
+        foreach ($mimeTypes as $mime) {
+            $this->assertEquals($format, $request->getFormat($mime));
+
+            if (null !== $format) {
+                $this->assertEquals($mimeTypes[0], $request->getMimeType($format));
+            }
+        }
+    }
+
+    public function getFormatToMimeTypeMapProviderWithAdditionalNullFormat()
+    {
+        return array_merge(
+            array(array(null, array(null, 'unexistent-mime-type'))),
+            $this->getFormatToMimeTypeMapProvider()
+        );
+    }
+
+    public function testGetFormatFromMimeTypeWithParameters()
+    {
+        $request = new Request();
+        $this->assertEquals('json', $request->getFormat('application/json; charset=utf-8'));
+    }
+
+    /**
+     * @dataProvider getFormatToMimeTypeMapProvider
+     */
+    public function testGetMimeTypeFromFormat($format, $mimeTypes)
+    {
+        $request = new Request();
+        $this->assertEquals($mimeTypes[0], $request->getMimeType($format));
+    }
+
+    /**
+     * @dataProvider getFormatToMimeTypeMapProvider
+     */
+    public function testGetMimeTypesFromFormat($format, $mimeTypes)
+    {
+        $this->assertEquals($mimeTypes, Request::getMimeTypes($format));
+    }
+
+    public function testGetMimeTypesFromInexistentFormat()
+    {
+        $request = new Request();
+        $this->assertNull($request->getMimeType('foo'));
+        $this->assertEquals(array(), Request::getMimeTypes('foo'));
+    }
+
+    public function testGetFormatWithCustomMimeType()
+    {
+        $request = new Request();
+        $request->setFormat('custom', 'application/vnd.foo.api;myversion=2.3');
+        $this->assertEquals('custom', $request->getFormat('application/vnd.foo.api;myversion=2.3'));
+    }
+
+    public function getFormatToMimeTypeMapProvider()
+    {
+        return array(
+            array('txt', array('text/plain')),
+            array('js', array('application/javascript', 'application/x-javascript', 'text/javascript')),
+            array('css', array('text/css')),
+            array('json', array('application/json', 'application/x-json')),
+            array('jsonld', array('application/ld+json')),
+            array('xml', array('text/xml', 'application/xml', 'application/x-xml')),
+            array('rdf', array('application/rdf+xml')),
+            array('atom', array('application/atom+xml')),
+        );
+    }
+
+    public function testGetUri()
+    {
+        $server = array();
+
+        // Standard Request on non default PORT
+        // http://host:8080/index.php/path/info?query=string
+
+        $server['HTTP_HOST'] = 'host:8080';
+        $server['SERVER_NAME'] = 'servername';
+        $server['SERVER_PORT'] = '8080';
+
+        $server['QUERY_STRING'] = 'query=string';
+        $server['REQUEST_URI'] = '/index.php/path/info?query=string';
+        $server['SCRIPT_NAME'] = '/index.php';
+        $server['PATH_INFO'] = '/path/info';
+        $server['PATH_TRANSLATED'] = 'redirect:/index.php/path/info';
+        $server['PHP_SELF'] = '/index_dev.php/path/info';
+        $server['SCRIPT_FILENAME'] = '/some/where/index.php';
+
+        $request = new Request();
+
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+
+        $this->assertEquals('http://host:8080/index.php/path/info?query=string', $request->getUri(), '->getUri() with non default port');
+
+        // Use std port number
+        $server['HTTP_HOST'] = 'host';
+        $server['SERVER_NAME'] = 'servername';
+        $server['SERVER_PORT'] = '80';
+
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+
+        $this->assertEquals('http://host/index.php/path/info?query=string', $request->getUri(), '->getUri() with default port');
+
+        // Without HOST HEADER
+        unset($server['HTTP_HOST']);
+        $server['SERVER_NAME'] = 'servername';
+        $server['SERVER_PORT'] = '80';
+
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+
+        $this->assertEquals('http://servername/index.php/path/info?query=string', $request->getUri(), '->getUri() with default port without HOST_HEADER');
+
+        // Request with URL REWRITING (hide index.php)
+        //   RewriteCond %{REQUEST_FILENAME} !-f
+        //   RewriteRule ^(.*)$ index.php [QSA,L]
+        // http://host:8080/path/info?query=string
+        $server = array();
+        $server['HTTP_HOST'] = 'host:8080';
+        $server['SERVER_NAME'] = 'servername';
+        $server['SERVER_PORT'] = '8080';
+
+        $server['REDIRECT_QUERY_STRING'] = 'query=string';
+        $server['REDIRECT_URL'] = '/path/info';
+        $server['SCRIPT_NAME'] = '/index.php';
+        $server['QUERY_STRING'] = 'query=string';
+        $server['REQUEST_URI'] = '/path/info?toto=test&1=1';
+        $server['SCRIPT_NAME'] = '/index.php';
+        $server['PHP_SELF'] = '/index.php';
+        $server['SCRIPT_FILENAME'] = '/some/where/index.php';
+
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+        $this->assertEquals('http://host:8080/path/info?query=string', $request->getUri(), '->getUri() with rewrite');
+
+        // Use std port number
+        //  http://host/path/info?query=string
+        $server['HTTP_HOST'] = 'host';
+        $server['SERVER_NAME'] = 'servername';
+        $server['SERVER_PORT'] = '80';
+
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+
+        $this->assertEquals('http://host/path/info?query=string', $request->getUri(), '->getUri() with rewrite and default port');
+
+        // Without HOST HEADER
+        unset($server['HTTP_HOST']);
+        $server['SERVER_NAME'] = 'servername';
+        $server['SERVER_PORT'] = '80';
+
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+
+        $this->assertEquals('http://servername/path/info?query=string', $request->getUri(), '->getUri() with rewrite, default port without HOST_HEADER');
+
+        // With encoded characters
+
+        $server = array(
+            'HTTP_HOST' => 'host:8080',
+            'SERVER_NAME' => 'servername',
+            'SERVER_PORT' => '8080',
+            'QUERY_STRING' => 'query=string',
+            'REQUEST_URI' => '/ba%20se/index_dev.php/foo%20bar/in+fo?query=string',
+            'SCRIPT_NAME' => '/ba se/index_dev.php',
+            'PATH_TRANSLATED' => 'redirect:/index.php/foo bar/in+fo',
+            'PHP_SELF' => '/ba se/index_dev.php/path/info',
+            'SCRIPT_FILENAME' => '/some/where/ba se/index_dev.php',
+        );
+
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+
+        $this->assertEquals(
+            'http://host:8080/ba%20se/index_dev.php/foo%20bar/in+fo?query=string',
+            $request->getUri()
+        );
+
+        // with user info
+
+        $server['PHP_AUTH_USER'] = 'fabien';
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+        $this->assertEquals('http://host:8080/ba%20se/index_dev.php/foo%20bar/in+fo?query=string', $request->getUri());
+
+        $server['PHP_AUTH_PW'] = 'symfony';
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+        $this->assertEquals('http://host:8080/ba%20se/index_dev.php/foo%20bar/in+fo?query=string', $request->getUri());
+    }
+
+    public function testGetUriForPath()
+    {
+        $request = Request::create('http://test.com/foo?bar=baz');
+        $this->assertEquals('http://test.com/some/path', $request->getUriForPath('/some/path'));
+
+        $request = Request::create('http://test.com:90/foo?bar=baz');
+        $this->assertEquals('http://test.com:90/some/path', $request->getUriForPath('/some/path'));
+
+        $request = Request::create('https://test.com/foo?bar=baz');
+        $this->assertEquals('https://test.com/some/path', $request->getUriForPath('/some/path'));
+
+        $request = Request::create('https://test.com:90/foo?bar=baz');
+        $this->assertEquals('https://test.com:90/some/path', $request->getUriForPath('/some/path'));
+
+        $server = array();
+
+        // Standard Request on non default PORT
+        // http://host:8080/index.php/path/info?query=string
+
+        $server['HTTP_HOST'] = 'host:8080';
+        $server['SERVER_NAME'] = 'servername';
+        $server['SERVER_PORT'] = '8080';
+
+        $server['QUERY_STRING'] = 'query=string';
+        $server['REQUEST_URI'] = '/index.php/path/info?query=string';
+        $server['SCRIPT_NAME'] = '/index.php';
+        $server['PATH_INFO'] = '/path/info';
+        $server['PATH_TRANSLATED'] = 'redirect:/index.php/path/info';
+        $server['PHP_SELF'] = '/index_dev.php/path/info';
+        $server['SCRIPT_FILENAME'] = '/some/where/index.php';
+
+        $request = new Request();
+
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+
+        $this->assertEquals('http://host:8080/index.php/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with non default port');
+
+        // Use std port number
+        $server['HTTP_HOST'] = 'host';
+        $server['SERVER_NAME'] = 'servername';
+        $server['SERVER_PORT'] = '80';
+
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+
+        $this->assertEquals('http://host/index.php/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with default port');
+
+        // Without HOST HEADER
+        unset($server['HTTP_HOST']);
+        $server['SERVER_NAME'] = 'servername';
+        $server['SERVER_PORT'] = '80';
+
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+
+        $this->assertEquals('http://servername/index.php/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with default port without HOST_HEADER');
+
+        // Request with URL REWRITING (hide index.php)
+        //   RewriteCond %{REQUEST_FILENAME} !-f
+        //   RewriteRule ^(.*)$ index.php [QSA,L]
+        // http://host:8080/path/info?query=string
+        $server = array();
+        $server['HTTP_HOST'] = 'host:8080';
+        $server['SERVER_NAME'] = 'servername';
+        $server['SERVER_PORT'] = '8080';
+
+        $server['REDIRECT_QUERY_STRING'] = 'query=string';
+        $server['REDIRECT_URL'] = '/path/info';
+        $server['SCRIPT_NAME'] = '/index.php';
+        $server['QUERY_STRING'] = 'query=string';
+        $server['REQUEST_URI'] = '/path/info?toto=test&1=1';
+        $server['SCRIPT_NAME'] = '/index.php';
+        $server['PHP_SELF'] = '/index.php';
+        $server['SCRIPT_FILENAME'] = '/some/where/index.php';
+
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+        $this->assertEquals('http://host:8080/some/path', $request->getUriForPath('/some/path'), '->getUri() with rewrite');
+
+        // Use std port number
+        //  http://host/path/info?query=string
+        $server['HTTP_HOST'] = 'host';
+        $server['SERVER_NAME'] = 'servername';
+        $server['SERVER_PORT'] = '80';
+
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+
+        $this->assertEquals('http://host/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with rewrite and default port');
+
+        // Without HOST HEADER
+        unset($server['HTTP_HOST']);
+        $server['SERVER_NAME'] = 'servername';
+        $server['SERVER_PORT'] = '80';
+
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+
+        $this->assertEquals('http://servername/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with rewrite, default port without HOST_HEADER');
+        $this->assertEquals('servername', $request->getHttpHost());
+
+        // with user info
+
+        $server['PHP_AUTH_USER'] = 'fabien';
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+        $this->assertEquals('http://servername/some/path', $request->getUriForPath('/some/path'));
+
+        $server['PHP_AUTH_PW'] = 'symfony';
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+        $this->assertEquals('http://servername/some/path', $request->getUriForPath('/some/path'));
+    }
+
+    /**
+     * @dataProvider getRelativeUriForPathData()
+     */
+    public function testGetRelativeUriForPath($expected, $pathinfo, $path)
+    {
+        $this->assertEquals($expected, Request::create($pathinfo)->getRelativeUriForPath($path));
+    }
+
+    public function getRelativeUriForPathData()
+    {
+        return array(
+            array('me.png', '/foo', '/me.png'),
+            array('../me.png', '/foo/bar', '/me.png'),
+            array('me.png', '/foo/bar', '/foo/me.png'),
+            array('../baz/me.png', '/foo/bar/b', '/foo/baz/me.png'),
+            array('../../fooz/baz/me.png', '/foo/bar/b', '/fooz/baz/me.png'),
+            array('baz/me.png', '/foo/bar/b', 'baz/me.png'),
+        );
+    }
+
+    public function testGetUserInfo()
+    {
+        $request = new Request();
+
+        $server = array('PHP_AUTH_USER' => 'fabien');
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+        $this->assertEquals('fabien', $request->getUserInfo());
+
+        $server['PHP_AUTH_USER'] = '0';
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+        $this->assertEquals('0', $request->getUserInfo());
+
+        $server['PHP_AUTH_PW'] = '0';
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+        $this->assertEquals('0:0', $request->getUserInfo());
+    }
+
+    public function testGetSchemeAndHttpHost()
+    {
+        $request = new Request();
+
+        $server = array();
+        $server['SERVER_NAME'] = 'servername';
+        $server['SERVER_PORT'] = '90';
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+        $this->assertEquals('http://servername:90', $request->getSchemeAndHttpHost());
+
+        $server['PHP_AUTH_USER'] = 'fabien';
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+        $this->assertEquals('http://servername:90', $request->getSchemeAndHttpHost());
+
+        $server['PHP_AUTH_USER'] = '0';
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+        $this->assertEquals('http://servername:90', $request->getSchemeAndHttpHost());
+
+        $server['PHP_AUTH_PW'] = '0';
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+        $this->assertEquals('http://servername:90', $request->getSchemeAndHttpHost());
+    }
+
+    /**
+     * @dataProvider getQueryStringNormalizationData
+     */
+    public function testGetQueryString($query, $expectedQuery, $msg)
+    {
+        $request = new Request();
+
+        $request->server->set('QUERY_STRING', $query);
+        $this->assertSame($expectedQuery, $request->getQueryString(), $msg);
+    }
+
+    public function getQueryStringNormalizationData()
+    {
+        return array(
+            array('foo', 'foo', 'works with valueless parameters'),
+            array('foo=', 'foo=', 'includes a dangling equal sign'),
+            array('bar=&foo=bar', 'bar=&foo=bar', '->works with empty parameters'),
+            array('foo=bar&bar=', 'bar=&foo=bar', 'sorts keys alphabetically'),
+
+            // GET parameters, that are submitted from a HTML form, encode spaces as "+" by default (as defined in enctype application/x-www-form-urlencoded).
+            // PHP also converts "+" to spaces when filling the global _GET or when using the function parse_str.
+            array('him=John%20Doe&her=Jane+Doe', 'her=Jane%20Doe&him=John%20Doe', 'normalizes spaces in both encodings "%20" and "+"'),
+
+            array('foo[]=1&foo[]=2', 'foo%5B%5D=1&foo%5B%5D=2', 'allows array notation'),
+            array('foo=1&foo=2', 'foo=1&foo=2', 'allows repeated parameters'),
+            array('pa%3Dram=foo%26bar%3Dbaz&test=test', 'pa%3Dram=foo%26bar%3Dbaz&test=test', 'works with encoded delimiters'),
+            array('0', '0', 'allows "0"'),
+            array('Jane Doe&John%20Doe', 'Jane%20Doe&John%20Doe', 'normalizes encoding in keys'),
+            array('her=Jane Doe&him=John%20Doe', 'her=Jane%20Doe&him=John%20Doe', 'normalizes encoding in values'),
+            array('foo=bar&&&test&&', 'foo=bar&test', 'removes unneeded delimiters'),
+            array('formula=e=m*c^2', 'formula=e%3Dm%2Ac%5E2', 'correctly treats only the first "=" as delimiter and the next as value'),
+
+            // Ignore pairs with empty key, even if there was a value, e.g. "=value", as such nameless values cannot be retrieved anyway.
+            // PHP also does not include them when building _GET.
+            array('foo=bar&=a=b&=x=y', 'foo=bar', 'removes params with empty key'),
+        );
+    }
+
+    public function testGetQueryStringReturnsNull()
+    {
+        $request = new Request();
+
+        $this->assertNull($request->getQueryString(), '->getQueryString() returns null for non-existent query string');
+
+        $request->server->set('QUERY_STRING', '');
+        $this->assertNull($request->getQueryString(), '->getQueryString() returns null for empty query string');
+    }
+
+    public function testGetHost()
+    {
+        $request = new Request();
+
+        $request->initialize(array('foo' => 'bar'));
+        $this->assertEquals('', $request->getHost(), '->getHost() return empty string if not initialized');
+
+        $request->initialize(array(), array(), array(), array(), array(), array('HTTP_HOST' => 'www.example.com'));
+        $this->assertEquals('www.example.com', $request->getHost(), '->getHost() from Host Header');
+
+        // Host header with port number
+        $request->initialize(array(), array(), array(), array(), array(), array('HTTP_HOST' => 'www.example.com:8080'));
+        $this->assertEquals('www.example.com', $request->getHost(), '->getHost() from Host Header with port number');
+
+        // Server values
+        $request->initialize(array(), array(), array(), array(), array(), array('SERVER_NAME' => 'www.example.com'));
+        $this->assertEquals('www.example.com', $request->getHost(), '->getHost() from server name');
+
+        $request->initialize(array(), array(), array(), array(), array(), array('SERVER_NAME' => 'www.example.com', 'HTTP_HOST' => 'www.host.com'));
+        $this->assertEquals('www.host.com', $request->getHost(), '->getHost() value from Host header has priority over SERVER_NAME ');
+    }
+
+    public function testGetPort()
+    {
+        $request = Request::create('http://example.com', 'GET', array(), array(), array(), array(
+            'HTTP_X_FORWARDED_PROTO' => 'https',
+            'HTTP_X_FORWARDED_PORT' => '443',
+        ));
+        $port = $request->getPort();
+
+        $this->assertEquals(80, $port, 'Without trusted proxies FORWARDED_PROTO and FORWARDED_PORT are ignored.');
+
+        Request::setTrustedProxies(array('1.1.1.1'), Request::HEADER_X_FORWARDED_ALL);
+        $request = Request::create('http://example.com', 'GET', array(), array(), array(), array(
+            'HTTP_X_FORWARDED_PROTO' => 'https',
+            'HTTP_X_FORWARDED_PORT' => '8443',
+        ));
+        $this->assertEquals(80, $request->getPort(), 'With PROTO and PORT on untrusted connection server value takes precedence.');
+        $request->server->set('REMOTE_ADDR', '1.1.1.1');
+        $this->assertEquals(8443, $request->getPort(), 'With PROTO and PORT set PORT takes precedence.');
+
+        $request = Request::create('http://example.com', 'GET', array(), array(), array(), array(
+            'HTTP_X_FORWARDED_PROTO' => 'https',
+        ));
+        $this->assertEquals(80, $request->getPort(), 'With only PROTO set getPort() ignores trusted headers on untrusted connection.');
+        $request->server->set('REMOTE_ADDR', '1.1.1.1');
+        $this->assertEquals(443, $request->getPort(), 'With only PROTO set getPort() defaults to 443.');
+
+        $request = Request::create('http://example.com', 'GET', array(), array(), array(), array(
+            'HTTP_X_FORWARDED_PROTO' => 'http',
+        ));
+        $this->assertEquals(80, $request->getPort(), 'If X_FORWARDED_PROTO is set to HTTP getPort() ignores trusted headers on untrusted connection.');
+        $request->server->set('REMOTE_ADDR', '1.1.1.1');
+        $this->assertEquals(80, $request->getPort(), 'If X_FORWARDED_PROTO is set to HTTP getPort() returns port of the original request.');
+
+        $request = Request::create('http://example.com', 'GET', array(), array(), array(), array(
+            'HTTP_X_FORWARDED_PROTO' => 'On',
+        ));
+        $this->assertEquals(80, $request->getPort(), 'With only PROTO set and value is On, getPort() ignores trusted headers on untrusted connection.');
+        $request->server->set('REMOTE_ADDR', '1.1.1.1');
+        $this->assertEquals(443, $request->getPort(), 'With only PROTO set and value is On, getPort() defaults to 443.');
+
+        $request = Request::create('http://example.com', 'GET', array(), array(), array(), array(
+            'HTTP_X_FORWARDED_PROTO' => '1',
+        ));
+        $this->assertEquals(80, $request->getPort(), 'With only PROTO set and value is 1, getPort() ignores trusted headers on untrusted connection.');
+        $request->server->set('REMOTE_ADDR', '1.1.1.1');
+        $this->assertEquals(443, $request->getPort(), 'With only PROTO set and value is 1, getPort() defaults to 443.');
+
+        $request = Request::create('http://example.com', 'GET', array(), array(), array(), array(
+            'HTTP_X_FORWARDED_PROTO' => 'something-else',
+        ));
+        $port = $request->getPort();
+        $this->assertEquals(80, $port, 'With only PROTO set and value is not recognized, getPort() defaults to 80.');
+    }
+
+    /**
+     * @expectedException \RuntimeException
+     */
+    public function testGetHostWithFakeHttpHostValue()
+    {
+        $request = new Request();
+        $request->initialize(array(), array(), array(), array(), array(), array('HTTP_HOST' => 'www.host.com?query=string'));
+        $request->getHost();
+    }
+
+    public function testGetSetMethod()
+    {
+        $request = new Request();
+
+        $this->assertEquals('GET', $request->getMethod(), '->getMethod() returns GET if no method is defined');
+
+        $request->setMethod('get');
+        $this->assertEquals('GET', $request->getMethod(), '->getMethod() returns an uppercased string');
+
+        $request->setMethod('PURGE');
+        $this->assertEquals('PURGE', $request->getMethod(), '->getMethod() returns the method even if it is not a standard one');
+
+        $request->setMethod('POST');
+        $this->assertEquals('POST', $request->getMethod(), '->getMethod() returns the method POST if no _method is defined');
+
+        $request->setMethod('POST');
+        $request->request->set('_method', 'purge');
+        $this->assertEquals('POST', $request->getMethod(), '->getMethod() does not return the method from _method if defined and POST but support not enabled');
+
+        $request = new Request();
+        $request->setMethod('POST');
+        $request->request->set('_method', 'purge');
+
+        $this->assertFalse(Request::getHttpMethodParameterOverride(), 'httpMethodParameterOverride should be disabled by default');
+
+        Request::enableHttpMethodParameterOverride();
+
+        $this->assertTrue(Request::getHttpMethodParameterOverride(), 'httpMethodParameterOverride should be enabled now but it is not');
+
+        $this->assertEquals('PURGE', $request->getMethod(), '->getMethod() returns the method from _method if defined and POST');
+        $this->disableHttpMethodParameterOverride();
+
+        $request = new Request();
+        $request->setMethod('POST');
+        $request->query->set('_method', 'purge');
+        $this->assertEquals('POST', $request->getMethod(), '->getMethod() does not return the method from _method if defined and POST but support not enabled');
+
+        $request = new Request();
+        $request->setMethod('POST');
+        $request->query->set('_method', 'purge');
+        Request::enableHttpMethodParameterOverride();
+        $this->assertEquals('PURGE', $request->getMethod(), '->getMethod() returns the method from _method if defined and POST');
+        $this->disableHttpMethodParameterOverride();
+
+        $request = new Request();
+        $request->setMethod('POST');
+        $request->headers->set('X-HTTP-METHOD-OVERRIDE', 'delete');
+        $this->assertEquals('DELETE', $request->getMethod(), '->getMethod() returns the method from X-HTTP-Method-Override even though _method is set if defined and POST');
+
+        $request = new Request();
+        $request->setMethod('POST');
+        $request->headers->set('X-HTTP-METHOD-OVERRIDE', 'delete');
+        $this->assertEquals('DELETE', $request->getMethod(), '->getMethod() returns the method from X-HTTP-Method-Override if defined and POST');
+    }
+
+    /**
+     * @dataProvider getClientIpsProvider
+     */
+    public function testGetClientIp($expected, $remoteAddr, $httpForwardedFor, $trustedProxies)
+    {
+        $request = $this->getRequestInstanceForClientIpTests($remoteAddr, $httpForwardedFor, $trustedProxies);
+
+        $this->assertEquals($expected[0], $request->getClientIp());
+    }
+
+    /**
+     * @dataProvider getClientIpsProvider
+     */
+    public function testGetClientIps($expected, $remoteAddr, $httpForwardedFor, $trustedProxies)
+    {
+        $request = $this->getRequestInstanceForClientIpTests($remoteAddr, $httpForwardedFor, $trustedProxies);
+
+        $this->assertEquals($expected, $request->getClientIps());
+    }
+
+    /**
+     * @dataProvider getClientIpsForwardedProvider
+     */
+    public function testGetClientIpsForwarded($expected, $remoteAddr, $httpForwarded, $trustedProxies)
+    {
+        $request = $this->getRequestInstanceForClientIpsForwardedTests($remoteAddr, $httpForwarded, $trustedProxies);
+
+        $this->assertEquals($expected, $request->getClientIps());
+    }
+
+    public function getClientIpsForwardedProvider()
+    {
+        //              $expected                                  $remoteAddr  $httpForwarded                                       $trustedProxies
+        return array(
+            array(array('127.0.0.1'),                              '127.0.0.1', 'for="_gazonk"',                                      null),
+            array(array('127.0.0.1'),                              '127.0.0.1', 'for="_gazonk"',                                      array('127.0.0.1')),
+            array(array('88.88.88.88'),                            '127.0.0.1', 'for="88.88.88.88:80"',                               array('127.0.0.1')),
+            array(array('192.0.2.60'),                             '::1',       'for=192.0.2.60;proto=http;by=203.0.113.43',          array('::1')),
+            array(array('2620:0:1cfe:face:b00c::3', '192.0.2.43'), '::1',       'for=192.0.2.43, for=2620:0:1cfe:face:b00c::3',       array('::1')),
+            array(array('2001:db8:cafe::17'),                      '::1',       'for="[2001:db8:cafe::17]:4711',                      array('::1')),
+        );
+    }
+
+    public function getClientIpsProvider()
+    {
+        //        $expected                   $remoteAddr                $httpForwardedFor            $trustedProxies
+        return array(
+            // simple IPv4
+            array(array('88.88.88.88'),              '88.88.88.88',              null,                        null),
+            // trust the IPv4 remote addr
+            array(array('88.88.88.88'),              '88.88.88.88',              null,                        array('88.88.88.88')),
+
+            // simple IPv6
+            array(array('::1'),                      '::1',                      null,                        null),
+            // trust the IPv6 remote addr
+            array(array('::1'),                      '::1',                      null,                        array('::1')),
+
+            // forwarded for with remote IPv4 addr not trusted
+            array(array('127.0.0.1'),                '127.0.0.1',                '88.88.88.88',               null),
+            // forwarded for with remote IPv4 addr trusted
+            array(array('88.88.88.88'),              '127.0.0.1',                '88.88.88.88',               array('127.0.0.1')),
+            // forwarded for with remote IPv4 and all FF addrs trusted
+            array(array('88.88.88.88'),              '127.0.0.1',                '88.88.88.88',               array('127.0.0.1', '88.88.88.88')),
+            // forwarded for with remote IPv4 range trusted
+            array(array('88.88.88.88'),              '123.45.67.89',             '88.88.88.88',               array('123.45.67.0/24')),
+
+            // forwarded for with remote IPv6 addr not trusted
+            array(array('1620:0:1cfe:face:b00c::3'), '1620:0:1cfe:face:b00c::3', '2620:0:1cfe:face:b00c::3',  null),
+            // forwarded for with remote IPv6 addr trusted
+            array(array('2620:0:1cfe:face:b00c::3'), '1620:0:1cfe:face:b00c::3', '2620:0:1cfe:face:b00c::3',  array('1620:0:1cfe:face:b00c::3')),
+            // forwarded for with remote IPv6 range trusted
+            array(array('88.88.88.88'),              '2a01:198:603:0:396e:4789:8e99:890f', '88.88.88.88',     array('2a01:198:603:0::/65')),
+
+            // multiple forwarded for with remote IPv4 addr trusted
+            array(array('88.88.88.88', '87.65.43.21', '127.0.0.1'), '123.45.67.89', '127.0.0.1, 87.65.43.21, 88.88.88.88', array('123.45.67.89')),
+            // multiple forwarded for with remote IPv4 addr and some reverse proxies trusted
+            array(array('87.65.43.21', '127.0.0.1'), '123.45.67.89',             '127.0.0.1, 87.65.43.21, 88.88.88.88', array('123.45.67.89', '88.88.88.88')),
+            // multiple forwarded for with remote IPv4 addr and some reverse proxies trusted but in the middle
+            array(array('88.88.88.88', '127.0.0.1'), '123.45.67.89',             '127.0.0.1, 87.65.43.21, 88.88.88.88', array('123.45.67.89', '87.65.43.21')),
+            // multiple forwarded for with remote IPv4 addr and all reverse proxies trusted
+            array(array('127.0.0.1'),                '123.45.67.89',             '127.0.0.1, 87.65.43.21, 88.88.88.88', array('123.45.67.89', '87.65.43.21', '88.88.88.88', '127.0.0.1')),
+
+            // multiple forwarded for with remote IPv6 addr trusted
+            array(array('2620:0:1cfe:face:b00c::3', '3620:0:1cfe:face:b00c::3'), '1620:0:1cfe:face:b00c::3', '3620:0:1cfe:face:b00c::3,2620:0:1cfe:face:b00c::3', array('1620:0:1cfe:face:b00c::3')),
+            // multiple forwarded for with remote IPv6 addr and some reverse proxies trusted
+            array(array('3620:0:1cfe:face:b00c::3'), '1620:0:1cfe:face:b00c::3', '3620:0:1cfe:face:b00c::3,2620:0:1cfe:face:b00c::3', array('1620:0:1cfe:face:b00c::3', '2620:0:1cfe:face:b00c::3')),
+            // multiple forwarded for with remote IPv4 addr and some reverse proxies trusted but in the middle
+            array(array('2620:0:1cfe:face:b00c::3', '4620:0:1cfe:face:b00c::3'), '1620:0:1cfe:face:b00c::3', '4620:0:1cfe:face:b00c::3,3620:0:1cfe:face:b00c::3,2620:0:1cfe:face:b00c::3', array('1620:0:1cfe:face:b00c::3', '3620:0:1cfe:face:b00c::3')),
+
+            // client IP with port
+            array(array('88.88.88.88'), '127.0.0.1', '88.88.88.88:12345, 127.0.0.1', array('127.0.0.1')),
+
+            // invalid forwarded IP is ignored
+            array(array('88.88.88.88'), '127.0.0.1', 'unknown,88.88.88.88', array('127.0.0.1')),
+            array(array('88.88.88.88'), '127.0.0.1', '}__test|O:21:&quot;JDatabaseDriverMysqli&quot;:3:{s:2,88.88.88.88', array('127.0.0.1')),
+        );
+    }
+
+    /**
+     * @expectedException \Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException
+     * @dataProvider getClientIpsWithConflictingHeadersProvider
+     */
+    public function testGetClientIpsWithConflictingHeaders($httpForwarded, $httpXForwardedFor)
+    {
+        $request = new Request();
+
+        $server = array(
+            'REMOTE_ADDR' => '88.88.88.88',
+            'HTTP_FORWARDED' => $httpForwarded,
+            'HTTP_X_FORWARDED_FOR' => $httpXForwardedFor,
+        );
+
+        Request::setTrustedProxies(array('88.88.88.88'), Request::HEADER_X_FORWARDED_ALL | Request::HEADER_FORWARDED);
+
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+
+        $request->getClientIps();
+    }
+
+    /**
+     * @dataProvider getClientIpsWithConflictingHeadersProvider
+     */
+    public function testGetClientIpsOnlyXHttpForwardedForTrusted($httpForwarded, $httpXForwardedFor)
+    {
+        $request = new Request();
+
+        $server = array(
+            'REMOTE_ADDR' => '88.88.88.88',
+            'HTTP_FORWARDED' => $httpForwarded,
+            'HTTP_X_FORWARDED_FOR' => $httpXForwardedFor,
+        );
+
+        Request::setTrustedProxies(array('88.88.88.88'), Request::HEADER_X_FORWARDED_FOR);
+
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+
+        $this->assertSame(array_reverse(explode(',', $httpXForwardedFor)), $request->getClientIps());
+    }
+
+    public function getClientIpsWithConflictingHeadersProvider()
+    {
+        //        $httpForwarded                   $httpXForwardedFor
+        return array(
+            array('for=87.65.43.21',                 '192.0.2.60'),
+            array('for=87.65.43.21, for=192.0.2.60', '192.0.2.60'),
+            array('for=192.0.2.60',                  '192.0.2.60,87.65.43.21'),
+            array('for="::face", for=192.0.2.60',    '192.0.2.60,192.0.2.43'),
+            array('for=87.65.43.21, for=192.0.2.60', '192.0.2.60,87.65.43.21'),
+        );
+    }
+
+    /**
+     * @dataProvider getClientIpsWithAgreeingHeadersProvider
+     */
+    public function testGetClientIpsWithAgreeingHeaders($httpForwarded, $httpXForwardedFor, $expectedIps)
+    {
+        $request = new Request();
+
+        $server = array(
+            'REMOTE_ADDR' => '88.88.88.88',
+            'HTTP_FORWARDED' => $httpForwarded,
+            'HTTP_X_FORWARDED_FOR' => $httpXForwardedFor,
+        );
+
+        Request::setTrustedProxies(array('88.88.88.88'), Request::HEADER_X_FORWARDED_ALL);
+
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+
+        $clientIps = $request->getClientIps();
+
+        $this->assertSame($expectedIps, $clientIps);
+    }
+
+    public function getClientIpsWithAgreeingHeadersProvider()
+    {
+        //        $httpForwarded                               $httpXForwardedFor
+        return array(
+            array('for="192.0.2.60"',                          '192.0.2.60',             array('192.0.2.60')),
+            array('for=192.0.2.60, for=87.65.43.21',           '192.0.2.60,87.65.43.21', array('87.65.43.21', '192.0.2.60')),
+            array('for="[::face]", for=192.0.2.60',            '::face,192.0.2.60',      array('192.0.2.60', '::face')),
+            array('for="192.0.2.60:80"',                       '192.0.2.60',             array('192.0.2.60')),
+            array('for=192.0.2.60;proto=http;by=203.0.113.43', '192.0.2.60',             array('192.0.2.60')),
+            array('for="[2001:db8:cafe::17]:4711"',            '2001:db8:cafe::17',      array('2001:db8:cafe::17')),
+        );
+    }
+
+    public function testGetContentWorksTwiceInDefaultMode()
+    {
+        $req = new Request();
+        $this->assertEquals('', $req->getContent());
+        $this->assertEquals('', $req->getContent());
+    }
+
+    public function testGetContentReturnsResource()
+    {
+        $req = new Request();
+        $retval = $req->getContent(true);
+        $this->assertInternalType('resource', $retval);
+        $this->assertEquals('', fread($retval, 1));
+        $this->assertTrue(feof($retval));
+    }
+
+    public function testGetContentReturnsResourceWhenContentSetInConstructor()
+    {
+        $req = new Request(array(), array(), array(), array(), array(), array(), 'MyContent');
+        $resource = $req->getContent(true);
+
+        $this->assertInternalType('resource', $resource);
+        $this->assertEquals('MyContent', stream_get_contents($resource));
+    }
+
+    public function testContentAsResource()
+    {
+        $resource = fopen('php://memory', 'r+');
+        fwrite($resource, 'My other content');
+        rewind($resource);
+
+        $req = new Request(array(), array(), array(), array(), array(), array(), $resource);
+        $this->assertEquals('My other content', stream_get_contents($req->getContent(true)));
+        $this->assertEquals('My other content', $req->getContent());
+    }
+
+    /**
+     * @expectedException \LogicException
+     * @dataProvider getContentCantBeCalledTwiceWithResourcesProvider
+     */
+    public function testGetContentCantBeCalledTwiceWithResources($first, $second)
+    {
+        if (\PHP_VERSION_ID >= 50600) {
+            $this->markTestSkipped('PHP >= 5.6 allows to open php://input several times.');
+        }
+
+        $req = new Request();
+        $req->getContent($first);
+        $req->getContent($second);
+    }
+
+    public function getContentCantBeCalledTwiceWithResourcesProvider()
+    {
+        return array(
+            'Resource then fetch' => array(true, false),
+            'Resource then resource' => array(true, true),
+        );
+    }
+
+    /**
+     * @dataProvider getContentCanBeCalledTwiceWithResourcesProvider
+     * @requires PHP 5.6
+     */
+    public function testGetContentCanBeCalledTwiceWithResources($first, $second)
+    {
+        $req = new Request();
+        $a = $req->getContent($first);
+        $b = $req->getContent($second);
+
+        if ($first) {
+            $a = stream_get_contents($a);
+        }
+
+        if ($second) {
+            $b = stream_get_contents($b);
+        }
+
+        $this->assertSame($a, $b);
+    }
+
+    public function getContentCanBeCalledTwiceWithResourcesProvider()
+    {
+        return array(
+            'Fetch then fetch' => array(false, false),
+            'Fetch then resource' => array(false, true),
+            'Resource then fetch' => array(true, false),
+            'Resource then resource' => array(true, true),
+        );
+    }
+
+    public function provideOverloadedMethods()
+    {
+        return array(
+            array('PUT'),
+            array('DELETE'),
+            array('PATCH'),
+            array('put'),
+            array('delete'),
+            array('patch'),
+        );
+    }
+
+    /**
+     * @dataProvider provideOverloadedMethods
+     */
+    public function testCreateFromGlobals($method)
+    {
+        $normalizedMethod = strtoupper($method);
+
+        $_GET['foo1'] = 'bar1';
+        $_POST['foo2'] = 'bar2';
+        $_COOKIE['foo3'] = 'bar3';
+        $_FILES['foo4'] = array('bar4');
+        $_SERVER['foo5'] = 'bar5';
+
+        $request = Request::createFromGlobals();
+        $this->assertEquals('bar1', $request->query->get('foo1'), '::fromGlobals() uses values from $_GET');
+        $this->assertEquals('bar2', $request->request->get('foo2'), '::fromGlobals() uses values from $_POST');
+        $this->assertEquals('bar3', $request->cookies->get('foo3'), '::fromGlobals() uses values from $_COOKIE');
+        $this->assertEquals(array('bar4'), $request->files->get('foo4'), '::fromGlobals() uses values from $_FILES');
+        $this->assertEquals('bar5', $request->server->get('foo5'), '::fromGlobals() uses values from $_SERVER');
+
+        unset($_GET['foo1'], $_POST['foo2'], $_COOKIE['foo3'], $_FILES['foo4'], $_SERVER['foo5']);
+
+        $_SERVER['REQUEST_METHOD'] = $method;
+        $_SERVER['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
+        $request = RequestContentProxy::createFromGlobals();
+        $this->assertEquals($normalizedMethod, $request->getMethod());
+        $this->assertEquals('mycontent', $request->request->get('content'));
+
+        unset($_SERVER['REQUEST_METHOD'], $_SERVER['CONTENT_TYPE']);
+
+        Request::createFromGlobals();
+        Request::enableHttpMethodParameterOverride();
+        $_POST['_method'] = $method;
+        $_POST['foo6'] = 'bar6';
+        $_SERVER['REQUEST_METHOD'] = 'PoSt';
+        $request = Request::createFromGlobals();
+        $this->assertEquals($normalizedMethod, $request->getMethod());
+        $this->assertEquals('POST', $request->getRealMethod());
+        $this->assertEquals('bar6', $request->request->get('foo6'));
+
+        unset($_POST['_method'], $_POST['foo6'], $_SERVER['REQUEST_METHOD']);
+        $this->disableHttpMethodParameterOverride();
+    }
+
+    public function testOverrideGlobals()
+    {
+        $request = new Request();
+        $request->initialize(array('foo' => 'bar'));
+
+        // as the Request::overrideGlobals really work, it erase $_SERVER, so we must backup it
+        $server = $_SERVER;
+
+        $request->overrideGlobals();
+
+        $this->assertEquals(array('foo' => 'bar'), $_GET);
+
+        $request->initialize(array(), array('foo' => 'bar'));
+        $request->overrideGlobals();
+
+        $this->assertEquals(array('foo' => 'bar'), $_POST);
+
+        $this->assertArrayNotHasKey('HTTP_X_FORWARDED_PROTO', $_SERVER);
+
+        $request->headers->set('X_FORWARDED_PROTO', 'https');
+
+        Request::setTrustedProxies(array('1.1.1.1'), Request::HEADER_X_FORWARDED_ALL);
+        $this->assertFalse($request->isSecure());
+        $request->server->set('REMOTE_ADDR', '1.1.1.1');
+        $this->assertTrue($request->isSecure());
+
+        $request->overrideGlobals();
+
+        $this->assertArrayHasKey('HTTP_X_FORWARDED_PROTO', $_SERVER);
+
+        $request->headers->set('CONTENT_TYPE', 'multipart/form-data');
+        $request->headers->set('CONTENT_LENGTH', 12345);
+
+        $request->overrideGlobals();
+
+        $this->assertArrayHasKey('CONTENT_TYPE', $_SERVER);
+        $this->assertArrayHasKey('CONTENT_LENGTH', $_SERVER);
+
+        $request->initialize(array('foo' => 'bar', 'baz' => 'foo'));
+        $request->query->remove('baz');
+
+        $request->overrideGlobals();
+
+        $this->assertEquals(array('foo' => 'bar'), $_GET);
+        $this->assertEquals('foo=bar', $_SERVER['QUERY_STRING']);
+        $this->assertEquals('foo=bar', $request->server->get('QUERY_STRING'));
+
+        // restore initial $_SERVER array
+        $_SERVER = $server;
+    }
+
+    public function testGetScriptName()
+    {
+        $request = new Request();
+        $this->assertEquals('', $request->getScriptName());
+
+        $server = array();
+        $server['SCRIPT_NAME'] = '/index.php';
+
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+
+        $this->assertEquals('/index.php', $request->getScriptName());
+
+        $server = array();
+        $server['ORIG_SCRIPT_NAME'] = '/frontend.php';
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+
+        $this->assertEquals('/frontend.php', $request->getScriptName());
+
+        $server = array();
+        $server['SCRIPT_NAME'] = '/index.php';
+        $server['ORIG_SCRIPT_NAME'] = '/frontend.php';
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+
+        $this->assertEquals('/index.php', $request->getScriptName());
+    }
+
+    public function testGetBasePath()
+    {
+        $request = new Request();
+        $this->assertEquals('', $request->getBasePath());
+
+        $server = array();
+        $server['SCRIPT_FILENAME'] = '/some/where/index.php';
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+        $this->assertEquals('', $request->getBasePath());
+
+        $server = array();
+        $server['SCRIPT_FILENAME'] = '/some/where/index.php';
+        $server['SCRIPT_NAME'] = '/index.php';
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+
+        $this->assertEquals('', $request->getBasePath());
+
+        $server = array();
+        $server['SCRIPT_FILENAME'] = '/some/where/index.php';
+        $server['PHP_SELF'] = '/index.php';
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+
+        $this->assertEquals('', $request->getBasePath());
+
+        $server = array();
+        $server['SCRIPT_FILENAME'] = '/some/where/index.php';
+        $server['ORIG_SCRIPT_NAME'] = '/index.php';
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+
+        $this->assertEquals('', $request->getBasePath());
+    }
+
+    public function testGetPathInfo()
+    {
+        $request = new Request();
+        $this->assertEquals('/', $request->getPathInfo());
+
+        $server = array();
+        $server['REQUEST_URI'] = '/path/info';
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+
+        $this->assertEquals('/path/info', $request->getPathInfo());
+
+        $server = array();
+        $server['REQUEST_URI'] = '/path%20test/info';
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+
+        $this->assertEquals('/path%20test/info', $request->getPathInfo());
+
+        $server = array();
+        $server['REQUEST_URI'] = '?a=b';
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+
+        $this->assertEquals('/', $request->getPathInfo());
+    }
+
+    public function testGetParameterPrecedence()
+    {
+        $request = new Request();
+        $request->attributes->set('foo', 'attr');
+        $request->query->set('foo', 'query');
+        $request->request->set('foo', 'body');
+
+        $this->assertSame('attr', $request->get('foo'));
+
+        $request->attributes->remove('foo');
+        $this->assertSame('query', $request->get('foo'));
+
+        $request->query->remove('foo');
+        $this->assertSame('body', $request->get('foo'));
+
+        $request->request->remove('foo');
+        $this->assertNull($request->get('foo'));
+    }
+
+    public function testGetPreferredLanguage()
+    {
+        $request = new Request();
+        $this->assertNull($request->getPreferredLanguage());
+        $this->assertNull($request->getPreferredLanguage(array()));
+        $this->assertEquals('fr', $request->getPreferredLanguage(array('fr')));
+        $this->assertEquals('fr', $request->getPreferredLanguage(array('fr', 'en')));
+        $this->assertEquals('en', $request->getPreferredLanguage(array('en', 'fr')));
+        $this->assertEquals('fr-ch', $request->getPreferredLanguage(array('fr-ch', 'fr-fr')));
+
+        $request = new Request();
+        $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6');
+        $this->assertEquals('en', $request->getPreferredLanguage(array('en', 'en-us')));
+
+        $request = new Request();
+        $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6');
+        $this->assertEquals('en', $request->getPreferredLanguage(array('fr', 'en')));
+
+        $request = new Request();
+        $request->headers->set('Accept-language', 'zh, en-us; q=0.8');
+        $this->assertEquals('en', $request->getPreferredLanguage(array('fr', 'en')));
+
+        $request = new Request();
+        $request->headers->set('Accept-language', 'zh, en-us; q=0.8, fr-fr; q=0.6, fr; q=0.5');
+        $this->assertEquals('en', $request->getPreferredLanguage(array('fr', 'en')));
+    }
+
+    public function testIsXmlHttpRequest()
+    {
+        $request = new Request();
+        $this->assertFalse($request->isXmlHttpRequest());
+
+        $request->headers->set('X-Requested-With', 'XMLHttpRequest');
+        $this->assertTrue($request->isXmlHttpRequest());
+
+        $request->headers->remove('X-Requested-With');
+        $this->assertFalse($request->isXmlHttpRequest());
+    }
+
+    /**
+     * @requires extension intl
+     */
+    public function testIntlLocale()
+    {
+        $request = new Request();
+
+        $request->setDefaultLocale('fr');
+        $this->assertEquals('fr', $request->getLocale());
+        $this->assertEquals('fr', \Locale::getDefault());
+
+        $request->setLocale('en');
+        $this->assertEquals('en', $request->getLocale());
+        $this->assertEquals('en', \Locale::getDefault());
+
+        $request->setDefaultLocale('de');
+        $this->assertEquals('en', $request->getLocale());
+        $this->assertEquals('en', \Locale::getDefault());
+    }
+
+    public function testGetCharsets()
+    {
+        $request = new Request();
+        $this->assertEquals(array(), $request->getCharsets());
+        $request->headers->set('Accept-Charset', 'ISO-8859-1, US-ASCII, UTF-8; q=0.8, ISO-10646-UCS-2; q=0.6');
+        $this->assertEquals(array(), $request->getCharsets()); // testing caching
+
+        $request = new Request();
+        $request->headers->set('Accept-Charset', 'ISO-8859-1, US-ASCII, UTF-8; q=0.8, ISO-10646-UCS-2; q=0.6');
+        $this->assertEquals(array('ISO-8859-1', 'US-ASCII', 'UTF-8', 'ISO-10646-UCS-2'), $request->getCharsets());
+
+        $request = new Request();
+        $request->headers->set('Accept-Charset', 'ISO-8859-1,utf-8;q=0.7,*;q=0.7');
+        $this->assertEquals(array('ISO-8859-1', 'utf-8', '*'), $request->getCharsets());
+    }
+
+    public function testGetEncodings()
+    {
+        $request = new Request();
+        $this->assertEquals(array(), $request->getEncodings());
+        $request->headers->set('Accept-Encoding', 'gzip,deflate,sdch');
+        $this->assertEquals(array(), $request->getEncodings()); // testing caching
+
+        $request = new Request();
+        $request->headers->set('Accept-Encoding', 'gzip,deflate,sdch');
+        $this->assertEquals(array('gzip', 'deflate', 'sdch'), $request->getEncodings());
+
+        $request = new Request();
+        $request->headers->set('Accept-Encoding', 'gzip;q=0.4,deflate;q=0.9,compress;q=0.7');
+        $this->assertEquals(array('deflate', 'compress', 'gzip'), $request->getEncodings());
+    }
+
+    public function testGetAcceptableContentTypes()
+    {
+        $request = new Request();
+        $this->assertEquals(array(), $request->getAcceptableContentTypes());
+        $request->headers->set('Accept', 'application/vnd.wap.wmlscriptc, text/vnd.wap.wml, application/vnd.wap.xhtml+xml, application/xhtml+xml, text/html, multipart/mixed, */*');
+        $this->assertEquals(array(), $request->getAcceptableContentTypes()); // testing caching
+
+        $request = new Request();
+        $request->headers->set('Accept', 'application/vnd.wap.wmlscriptc, text/vnd.wap.wml, application/vnd.wap.xhtml+xml, application/xhtml+xml, text/html, multipart/mixed, */*');
+        $this->assertEquals(array('application/vnd.wap.wmlscriptc', 'text/vnd.wap.wml', 'application/vnd.wap.xhtml+xml', 'application/xhtml+xml', 'text/html', 'multipart/mixed', '*/*'), $request->getAcceptableContentTypes());
+    }
+
+    public function testGetLanguages()
+    {
+        $request = new Request();
+        $this->assertEquals(array(), $request->getLanguages());
+
+        $request = new Request();
+        $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6');
+        $this->assertEquals(array('zh', 'en_US', 'en'), $request->getLanguages());
+        $this->assertEquals(array('zh', 'en_US', 'en'), $request->getLanguages());
+
+        $request = new Request();
+        $request->headers->set('Accept-language', 'zh, en-us; q=0.6, en; q=0.8');
+        $this->assertEquals(array('zh', 'en', 'en_US'), $request->getLanguages()); // Test out of order qvalues
+
+        $request = new Request();
+        $request->headers->set('Accept-language', 'zh, en, en-us');
+        $this->assertEquals(array('zh', 'en', 'en_US'), $request->getLanguages()); // Test equal weighting without qvalues
+
+        $request = new Request();
+        $request->headers->set('Accept-language', 'zh; q=0.6, en, en-us; q=0.6');
+        $this->assertEquals(array('en', 'zh', 'en_US'), $request->getLanguages()); // Test equal weighting with qvalues
+
+        $request = new Request();
+        $request->headers->set('Accept-language', 'zh, i-cherokee; q=0.6');
+        $this->assertEquals(array('zh', 'cherokee'), $request->getLanguages());
+    }
+
+    public function testGetRequestFormat()
+    {
+        $request = new Request();
+        $this->assertEquals('html', $request->getRequestFormat());
+
+        // Ensure that setting different default values over time is possible,
+        // aka. setRequestFormat determines the state.
+        $this->assertEquals('json', $request->getRequestFormat('json'));
+        $this->assertEquals('html', $request->getRequestFormat('html'));
+
+        $request = new Request();
+        $this->assertNull($request->getRequestFormat(null));
+
+        $request = new Request();
+        $this->assertNull($request->setRequestFormat('foo'));
+        $this->assertEquals('foo', $request->getRequestFormat(null));
+
+        $request = new Request(array('_format' => 'foo'));
+        $this->assertEquals('html', $request->getRequestFormat());
+    }
+
+    public function testHasSession()
+    {
+        $request = new Request();
+
+        $this->assertFalse($request->hasSession());
+        $request->setSession(new Session(new MockArraySessionStorage()));
+        $this->assertTrue($request->hasSession());
+    }
+
+    public function testGetSession()
+    {
+        $request = new Request();
+
+        $request->setSession(new Session(new MockArraySessionStorage()));
+        $this->assertTrue($request->hasSession());
+
+        $session = $request->getSession();
+        $this->assertObjectHasAttribute('storage', $session);
+        $this->assertObjectHasAttribute('flashName', $session);
+        $this->assertObjectHasAttribute('attributeName', $session);
+    }
+
+    public function testHasPreviousSession()
+    {
+        $request = new Request();
+
+        $this->assertFalse($request->hasPreviousSession());
+        $request->cookies->set('MOCKSESSID', 'foo');
+        $this->assertFalse($request->hasPreviousSession());
+        $request->setSession(new Session(new MockArraySessionStorage()));
+        $this->assertTrue($request->hasPreviousSession());
+    }
+
+    public function testToString()
+    {
+        $request = new Request();
+
+        $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6');
+        $request->cookies->set('Foo', 'Bar');
+
+        $asString = (string) $request;
+
+        $this->assertContains('Accept-Language: zh, en-us; q=0.8, en; q=0.6', $asString);
+        $this->assertContains('Cookie: Foo=Bar', $asString);
+
+        $request->cookies->set('Another', 'Cookie');
+
+        $asString = (string) $request;
+
+        $this->assertContains('Cookie: Foo=Bar; Another=Cookie', $asString);
+    }
+
+    public function testIsMethod()
+    {
+        $request = new Request();
+        $request->setMethod('POST');
+        $this->assertTrue($request->isMethod('POST'));
+        $this->assertTrue($request->isMethod('post'));
+        $this->assertFalse($request->isMethod('GET'));
+        $this->assertFalse($request->isMethod('get'));
+
+        $request->setMethod('GET');
+        $this->assertTrue($request->isMethod('GET'));
+        $this->assertTrue($request->isMethod('get'));
+        $this->assertFalse($request->isMethod('POST'));
+        $this->assertFalse($request->isMethod('post'));
+    }
+
+    /**
+     * @dataProvider getBaseUrlData
+     */
+    public function testGetBaseUrl($uri, $server, $expectedBaseUrl, $expectedPathInfo)
+    {
+        $request = Request::create($uri, 'GET', array(), array(), array(), $server);
+
+        $this->assertSame($expectedBaseUrl, $request->getBaseUrl(), 'baseUrl');
+        $this->assertSame($expectedPathInfo, $request->getPathInfo(), 'pathInfo');
+    }
+
+    public function getBaseUrlData()
+    {
+        return array(
+            array(
+                '/fruit/strawberry/1234index.php/blah',
+                array(
+                    'SCRIPT_FILENAME' => 'E:/Sites/cc-new/public_html/fruit/index.php',
+                    'SCRIPT_NAME' => '/fruit/index.php',
+                    'PHP_SELF' => '/fruit/index.php',
+                ),
+                '/fruit',
+                '/strawberry/1234index.php/blah',
+            ),
+            array(
+                '/fruit/strawberry/1234index.php/blah',
+                array(
+                    'SCRIPT_FILENAME' => 'E:/Sites/cc-new/public_html/index.php',
+                    'SCRIPT_NAME' => '/index.php',
+                    'PHP_SELF' => '/index.php',
+                ),
+                '',
+                '/fruit/strawberry/1234index.php/blah',
+            ),
+            array(
+                '/foo%20bar/',
+                array(
+                    'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo bar/app.php',
+                    'SCRIPT_NAME' => '/foo bar/app.php',
+                    'PHP_SELF' => '/foo bar/app.php',
+                ),
+                '/foo%20bar',
+                '/',
+            ),
+            array(
+                '/foo%20bar/home',
+                array(
+                    'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo bar/app.php',
+                    'SCRIPT_NAME' => '/foo bar/app.php',
+                    'PHP_SELF' => '/foo bar/app.php',
+                ),
+                '/foo%20bar',
+                '/home',
+            ),
+            array(
+                '/foo%20bar/app.php/home',
+                array(
+                    'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo bar/app.php',
+                    'SCRIPT_NAME' => '/foo bar/app.php',
+                    'PHP_SELF' => '/foo bar/app.php',
+                ),
+                '/foo%20bar/app.php',
+                '/home',
+            ),
+            array(
+                '/foo%20bar/app.php/home%3Dbaz',
+                array(
+                    'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo bar/app.php',
+                    'SCRIPT_NAME' => '/foo bar/app.php',
+                    'PHP_SELF' => '/foo bar/app.php',
+                ),
+                '/foo%20bar/app.php',
+                '/home%3Dbaz',
+            ),
+            array(
+                '/foo/bar+baz',
+                array(
+                    'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo/app.php',
+                    'SCRIPT_NAME' => '/foo/app.php',
+                    'PHP_SELF' => '/foo/app.php',
+                ),
+                '/foo',
+                '/bar+baz',
+            ),
+        );
+    }
+
+    /**
+     * @dataProvider urlencodedStringPrefixData
+     */
+    public function testUrlencodedStringPrefix($string, $prefix, $expect)
+    {
+        $request = new Request();
+
+        $me = new \ReflectionMethod($request, 'getUrlencodedPrefix');
+        $me->setAccessible(true);
+
+        $this->assertSame($expect, $me->invoke($request, $string, $prefix));
+    }
+
+    public function urlencodedStringPrefixData()
+    {
+        return array(
+            array('foo', 'foo', 'foo'),
+            array('fo%6f', 'foo', 'fo%6f'),
+            array('foo/bar', 'foo', 'foo'),
+            array('fo%6f/bar', 'foo', 'fo%6f'),
+            array('f%6f%6f/bar', 'foo', 'f%6f%6f'),
+            array('%66%6F%6F/bar', 'foo', '%66%6F%6F'),
+            array('fo+o/bar', 'fo+o', 'fo+o'),
+            array('fo%2Bo/bar', 'fo+o', 'fo%2Bo'),
+        );
+    }
+
+    private function disableHttpMethodParameterOverride()
+    {
+        $class = new \ReflectionClass('Symfony\\Component\\HttpFoundation\\Request');
+        $property = $class->getProperty('httpMethodParameterOverride');
+        $property->setAccessible(true);
+        $property->setValue(false);
+    }
+
+    private function getRequestInstanceForClientIpTests($remoteAddr, $httpForwardedFor, $trustedProxies)
+    {
+        $request = new Request();
+
+        $server = array('REMOTE_ADDR' => $remoteAddr);
+        if (null !== $httpForwardedFor) {
+            $server['HTTP_X_FORWARDED_FOR'] = $httpForwardedFor;
+        }
+
+        if ($trustedProxies) {
+            Request::setTrustedProxies($trustedProxies, Request::HEADER_X_FORWARDED_ALL);
+        }
+
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+
+        return $request;
+    }
+
+    private function getRequestInstanceForClientIpsForwardedTests($remoteAddr, $httpForwarded, $trustedProxies)
+    {
+        $request = new Request();
+
+        $server = array('REMOTE_ADDR' => $remoteAddr);
+
+        if (null !== $httpForwarded) {
+            $server['HTTP_FORWARDED'] = $httpForwarded;
+        }
+
+        if ($trustedProxies) {
+            Request::setTrustedProxies($trustedProxies, Request::HEADER_FORWARDED);
+        }
+
+        $request->initialize(array(), array(), array(), array(), array(), $server);
+
+        return $request;
+    }
+
+    public function testTrustedProxiesXForwardedFor()
+    {
+        $request = Request::create('http://example.com/');
+        $request->server->set('REMOTE_ADDR', '3.3.3.3');
+        $request->headers->set('X_FORWARDED_FOR', '1.1.1.1, 2.2.2.2');
+        $request->headers->set('X_FORWARDED_HOST', 'foo.example.com:1234, real.example.com:8080');
+        $request->headers->set('X_FORWARDED_PROTO', 'https');
+        $request->headers->set('X_FORWARDED_PORT', 443);
+
+        // no trusted proxies
+        $this->assertEquals('3.3.3.3', $request->getClientIp());
+        $this->assertEquals('example.com', $request->getHost());
+        $this->assertEquals(80, $request->getPort());
+        $this->assertFalse($request->isSecure());
+
+        // disabling proxy trusting
+        Request::setTrustedProxies(array(), Request::HEADER_X_FORWARDED_ALL);
+        $this->assertEquals('3.3.3.3', $request->getClientIp());
+        $this->assertEquals('example.com', $request->getHost());
+        $this->assertEquals(80, $request->getPort());
+        $this->assertFalse($request->isSecure());
+
+        // request is forwarded by a non-trusted proxy
+        Request::setTrustedProxies(array('2.2.2.2'), Request::HEADER_X_FORWARDED_ALL);
+        $this->assertEquals('3.3.3.3', $request->getClientIp());
+        $this->assertEquals('example.com', $request->getHost());
+        $this->assertEquals(80, $request->getPort());
+        $this->assertFalse($request->isSecure());
+
+        // trusted proxy via setTrustedProxies()
+        Request::setTrustedProxies(array('3.3.3.3', '2.2.2.2'), Request::HEADER_X_FORWARDED_ALL);
+        $this->assertEquals('1.1.1.1', $request->getClientIp());
+        $this->assertEquals('foo.example.com', $request->getHost());
+        $this->assertEquals(443, $request->getPort());
+        $this->assertTrue($request->isSecure());
+
+        // trusted proxy via setTrustedProxies()
+        Request::setTrustedProxies(array('3.3.3.4', '2.2.2.2'), Request::HEADER_X_FORWARDED_ALL);
+        $this->assertEquals('3.3.3.3', $request->getClientIp());
+        $this->assertEquals('example.com', $request->getHost());
+        $this->assertEquals(80, $request->getPort());
+        $this->assertFalse($request->isSecure());
+
+        // check various X_FORWARDED_PROTO header values
+        Request::setTrustedProxies(array('3.3.3.3', '2.2.2.2'), Request::HEADER_X_FORWARDED_ALL);
+        $request->headers->set('X_FORWARDED_PROTO', 'ssl');
+        $this->assertTrue($request->isSecure());
+
+        $request->headers->set('X_FORWARDED_PROTO', 'https, http');
+        $this->assertTrue($request->isSecure());
+    }
+
+    /**
+     * @group legacy
+     * @expectedDeprecation The "Symfony\Component\HttpFoundation\Request::setTrustedHeaderName()" method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the $trustedHeaderSet argument of the Request::setTrustedProxies() method instead.
+     */
+    public function testLegacyTrustedProxies()
+    {
+        $request = Request::create('http://example.com/');
+        $request->server->set('REMOTE_ADDR', '3.3.3.3');
+        $request->headers->set('X_FORWARDED_FOR', '1.1.1.1, 2.2.2.2');
+        $request->headers->set('X_FORWARDED_HOST', 'foo.example.com, real.example.com:8080');
+        $request->headers->set('X_FORWARDED_PROTO', 'https');
+        $request->headers->set('X_FORWARDED_PORT', 443);
+        $request->headers->set('X_MY_FOR', '3.3.3.3, 4.4.4.4');
+        $request->headers->set('X_MY_HOST', 'my.example.com');
+        $request->headers->set('X_MY_PROTO', 'http');
+        $request->headers->set('X_MY_PORT', 81);
+
+        Request::setTrustedProxies(array('3.3.3.3', '2.2.2.2'), Request::HEADER_X_FORWARDED_ALL);
+
+        // custom header names
+        Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, 'X_MY_FOR');
+        Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, 'X_MY_HOST');
+        Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, 'X_MY_PORT');
+        Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, 'X_MY_PROTO');
+        $this->assertEquals('4.4.4.4', $request->getClientIp());
+        $this->assertEquals('my.example.com', $request->getHost());
+        $this->assertEquals(81, $request->getPort());
+        $this->assertFalse($request->isSecure());
+
+        // disabling via empty header names
+        Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, null);
+        Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, null);
+        Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, null);
+        Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, null);
+        $this->assertEquals('3.3.3.3', $request->getClientIp());
+        $this->assertEquals('example.com', $request->getHost());
+        $this->assertEquals(80, $request->getPort());
+        $this->assertFalse($request->isSecure());
+
+        //reset
+        Request::setTrustedHeaderName(Request::HEADER_FORWARDED, 'FORWARDED');
+        Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, 'X_FORWARDED_FOR');
+        Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, 'X_FORWARDED_HOST');
+        Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, 'X_FORWARDED_PORT');
+        Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, 'X_FORWARDED_PROTO');
+    }
+
+    public function testTrustedProxiesForwarded()
+    {
+        $request = Request::create('http://example.com/');
+        $request->server->set('REMOTE_ADDR', '3.3.3.3');
+        $request->headers->set('FORWARDED', 'for=1.1.1.1, host=foo.example.com:8080, proto=https, for=2.2.2.2, host=real.example.com:8080');
+
+        // no trusted proxies
+        $this->assertEquals('3.3.3.3', $request->getClientIp());
+        $this->assertEquals('example.com', $request->getHost());
+        $this->assertEquals(80, $request->getPort());
+        $this->assertFalse($request->isSecure());
+
+        // disabling proxy trusting
+        Request::setTrustedProxies(array(), Request::HEADER_FORWARDED);
+        $this->assertEquals('3.3.3.3', $request->getClientIp());
+        $this->assertEquals('example.com', $request->getHost());
+        $this->assertEquals(80, $request->getPort());
+        $this->assertFalse($request->isSecure());
+
+        // request is forwarded by a non-trusted proxy
+        Request::setTrustedProxies(array('2.2.2.2'), Request::HEADER_FORWARDED);
+        $this->assertEquals('3.3.3.3', $request->getClientIp());
+        $this->assertEquals('example.com', $request->getHost());
+        $this->assertEquals(80, $request->getPort());
+        $this->assertFalse($request->isSecure());
+
+        // trusted proxy via setTrustedProxies()
+        Request::setTrustedProxies(array('3.3.3.3', '2.2.2.2'), Request::HEADER_FORWARDED);
+        $this->assertEquals('1.1.1.1', $request->getClientIp());
+        $this->assertEquals('foo.example.com', $request->getHost());
+        $this->assertEquals(8080, $request->getPort());
+        $this->assertTrue($request->isSecure());
+
+        // trusted proxy via setTrustedProxies()
+        Request::setTrustedProxies(array('3.3.3.4', '2.2.2.2'), Request::HEADER_FORWARDED);
+        $this->assertEquals('3.3.3.3', $request->getClientIp());
+        $this->assertEquals('example.com', $request->getHost());
+        $this->assertEquals(80, $request->getPort());
+        $this->assertFalse($request->isSecure());
+
+        // check various X_FORWARDED_PROTO header values
+        Request::setTrustedProxies(array('3.3.3.3', '2.2.2.2'), Request::HEADER_FORWARDED);
+        $request->headers->set('FORWARDED', 'proto=ssl');
+        $this->assertTrue($request->isSecure());
+
+        $request->headers->set('FORWARDED', 'proto=https, proto=http');
+        $this->assertTrue($request->isSecure());
+    }
+
+    /**
+     * @group legacy
+     * @expectedException \InvalidArgumentException
+     */
+    public function testSetTrustedProxiesInvalidHeaderName()
+    {
+        Request::create('http://example.com/');
+        Request::setTrustedHeaderName('bogus name', 'X_MY_FOR');
+    }
+
+    /**
+     * @group legacy
+     * @expectedException \InvalidArgumentException
+     */
+    public function testGetTrustedProxiesInvalidHeaderName()
+    {
+        Request::create('http://example.com/');
+        Request::getTrustedHeaderName('bogus name');
+    }
+
+    /**
+     * @dataProvider iisRequestUriProvider
+     */
+    public function testIISRequestUri($headers, $server, $expectedRequestUri)
+    {
+        $request = new Request();
+        $request->headers->replace($headers);
+        $request->server->replace($server);
+
+        $this->assertEquals($expectedRequestUri, $request->getRequestUri(), '->getRequestUri() is correct');
+
+        $subRequestUri = '/bar/foo';
+        $subRequest = Request::create($subRequestUri, 'get', array(), array(), array(), $request->server->all());
+        $this->assertEquals($subRequestUri, $subRequest->getRequestUri(), '->getRequestUri() is correct in sub request');
+    }
+
+    public function iisRequestUriProvider()
+    {
+        return array(
+            array(
+                array(
+                    'X_ORIGINAL_URL' => '/foo/bar',
+                ),
+                array(),
+                '/foo/bar',
+            ),
+            array(
+                array(
+                    'X_REWRITE_URL' => '/foo/bar',
+                ),
+                array(),
+                '/foo/bar',
+            ),
+            array(
+                array(),
+                array(
+                    'IIS_WasUrlRewritten' => '1',
+                    'UNENCODED_URL' => '/foo/bar',
+                ),
+                '/foo/bar',
+            ),
+            array(
+                array(
+                    'X_ORIGINAL_URL' => '/foo/bar',
+                ),
+                array(
+                    'HTTP_X_ORIGINAL_URL' => '/foo/bar',
+                ),
+                '/foo/bar',
+            ),
+            array(
+                array(
+                    'X_ORIGINAL_URL' => '/foo/bar',
+                ),
+                array(
+                    'IIS_WasUrlRewritten' => '1',
+                    'UNENCODED_URL' => '/foo/bar',
+                ),
+                '/foo/bar',
+            ),
+            array(
+                array(
+                    'X_ORIGINAL_URL' => '/foo/bar',
+                ),
+                array(
+                    'HTTP_X_ORIGINAL_URL' => '/foo/bar',
+                    'IIS_WasUrlRewritten' => '1',
+                    'UNENCODED_URL' => '/foo/bar',
+                ),
+                '/foo/bar',
+            ),
+            array(
+                array(),
+                array(
+                    'ORIG_PATH_INFO' => '/foo/bar',
+                ),
+                '/foo/bar',
+            ),
+            array(
+                array(),
+                array(
+                    'ORIG_PATH_INFO' => '/foo/bar',
+                    'QUERY_STRING' => 'foo=bar',
+                ),
+                '/foo/bar?foo=bar',
+            ),
+        );
+    }
+
+    public function testTrustedHosts()
+    {
+        // create a request
+        $request = Request::create('/');
+
+        // no trusted host set -> no host check
+        $request->headers->set('host', 'evil.com');
+        $this->assertEquals('evil.com', $request->getHost());
+
+        // add a trusted domain and all its subdomains
+        Request::setTrustedHosts(array('^([a-z]{9}\.)?trusted\.com$'));
+
+        // untrusted host
+        $request->headers->set('host', 'evil.com');
+        try {
+            $request->getHost();
+            $this->fail('Request::getHost() should throw an exception when host is not trusted.');
+        } catch (SuspiciousOperationException $e) {
+            $this->assertEquals('Untrusted Host "evil.com".', $e->getMessage());
+        }
+
+        // trusted hosts
+        $request->headers->set('host', 'trusted.com');
+        $this->assertEquals('trusted.com', $request->getHost());
+        $this->assertEquals(80, $request->getPort());
+
+        $request->server->set('HTTPS', true);
+        $request->headers->set('host', 'trusted.com');
+        $this->assertEquals('trusted.com', $request->getHost());
+        $this->assertEquals(443, $request->getPort());
+        $request->server->set('HTTPS', false);
+
+        $request->headers->set('host', 'trusted.com:8000');
+        $this->assertEquals('trusted.com', $request->getHost());
+        $this->assertEquals(8000, $request->getPort());
+
+        $request->headers->set('host', 'subdomain.trusted.com');
+        $this->assertEquals('subdomain.trusted.com', $request->getHost());
+
+        // reset request for following tests
+        Request::setTrustedHosts(array());
+    }
+
+    public function testFactory()
+    {
+        Request::setFactory(function (array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) {
+            return new NewRequest();
+        });
+
+        $this->assertEquals('foo', Request::create('/')->getFoo());
+
+        Request::setFactory(null);
+    }
+
+    /**
+     * @dataProvider getLongHostNames
+     */
+    public function testVeryLongHosts($host)
+    {
+        $start = microtime(true);
+
+        $request = Request::create('/');
+        $request->headers->set('host', $host);
+        $this->assertEquals($host, $request->getHost());
+        $this->assertLessThan(5, microtime(true) - $start);
+    }
+
+    /**
+     * @dataProvider getHostValidities
+     */
+    public function testHostValidity($host, $isValid, $expectedHost = null, $expectedPort = null)
+    {
+        $request = Request::create('/');
+        $request->headers->set('host', $host);
+
+        if ($isValid) {
+            $this->assertSame($expectedHost ?: $host, $request->getHost());
+            if ($expectedPort) {
+                $this->assertSame($expectedPort, $request->getPort());
+            }
+        } else {
+            if (method_exists($this, 'expectException')) {
+                $this->expectException(SuspiciousOperationException::class);
+                $this->expectExceptionMessage('Invalid Host');
+            } else {
+                $this->setExpectedException(SuspiciousOperationException::class, 'Invalid Host');
+            }
+
+            $request->getHost();
+        }
+    }
+
+    public function getHostValidities()
+    {
+        return array(
+            array('.a', false),
+            array('a..', false),
+            array('a.', true),
+            array("\xE9", false),
+            array('[::1]', true),
+            array('[::1]:80', true, '[::1]', 80),
+            array(str_repeat('.', 101), false),
+        );
+    }
+
+    public function getLongHostNames()
+    {
+        return array(
+            array('a'.str_repeat('.a', 40000)),
+            array(str_repeat(':', 101)),
+        );
+    }
+
+    /**
+     * @dataProvider methodIdempotentProvider
+     */
+    public function testMethodIdempotent($method, $idempotent)
+    {
+        $request = new Request();
+        $request->setMethod($method);
+        $this->assertEquals($idempotent, $request->isMethodIdempotent());
+    }
+
+    public function methodIdempotentProvider()
+    {
+        return array(
+            array('HEAD', true),
+            array('GET', true),
+            array('POST', false),
+            array('PUT', true),
+            array('PATCH', false),
+            array('DELETE', true),
+            array('PURGE', true),
+            array('OPTIONS', true),
+            array('TRACE', true),
+            array('CONNECT', false),
+        );
+    }
+
+    /**
+     * @dataProvider methodSafeProvider
+     */
+    public function testMethodSafe($method, $safe)
+    {
+        $request = new Request();
+        $request->setMethod($method);
+        $this->assertEquals($safe, $request->isMethodSafe(false));
+    }
+
+    public function methodSafeProvider()
+    {
+        return array(
+            array('HEAD', true),
+            array('GET', true),
+            array('POST', false),
+            array('PUT', false),
+            array('PATCH', false),
+            array('DELETE', false),
+            array('PURGE', false),
+            array('OPTIONS', true),
+            array('TRACE', true),
+            array('CONNECT', false),
+        );
+    }
+
+    /**
+     * @group legacy
+     * @expectedDeprecation Checking only for cacheable HTTP methods with Symfony\Component\HttpFoundation\Request::isMethodSafe() is deprecated since Symfony 3.2 and will throw an exception in 4.0. Disable checking only for cacheable methods by calling the method with `false` as first argument or use the Request::isMethodCacheable() instead.
+     */
+    public function testMethodSafeChecksCacheable()
+    {
+        $request = new Request();
+        $request->setMethod('OPTIONS');
+        $this->assertFalse($request->isMethodSafe());
+    }
+
+    /**
+     * @dataProvider methodCacheableProvider
+     */
+    public function testMethodCacheable($method, $cacheable)
+    {
+        $request = new Request();
+        $request->setMethod($method);
+        $this->assertEquals($cacheable, $request->isMethodCacheable());
+    }
+
+    public function methodCacheableProvider()
+    {
+        return array(
+            array('HEAD', true),
+            array('GET', true),
+            array('POST', false),
+            array('PUT', false),
+            array('PATCH', false),
+            array('DELETE', false),
+            array('PURGE', false),
+            array('OPTIONS', false),
+            array('TRACE', false),
+            array('CONNECT', false),
+        );
+    }
+
+    /**
+     * @group legacy
+     */
+    public function testGetTrustedHeaderName()
+    {
+        Request::setTrustedProxies(array('8.8.8.8'), Request::HEADER_X_FORWARDED_ALL);
+
+        $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_FORWARDED));
+        $this->assertSame('X_FORWARDED_FOR', Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP));
+        $this->assertSame('X_FORWARDED_HOST', Request::getTrustedHeaderName(Request::HEADER_CLIENT_HOST));
+        $this->assertSame('X_FORWARDED_PORT', Request::getTrustedHeaderName(Request::HEADER_CLIENT_PORT));
+        $this->assertSame('X_FORWARDED_PROTO', Request::getTrustedHeaderName(Request::HEADER_CLIENT_PROTO));
+
+        Request::setTrustedProxies(array('8.8.8.8'), Request::HEADER_FORWARDED);
+
+        $this->assertSame('FORWARDED', Request::getTrustedHeaderName(Request::HEADER_FORWARDED));
+        $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP));
+        $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_HOST));
+        $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_PORT));
+        $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_PROTO));
+
+        Request::setTrustedHeaderName(Request::HEADER_FORWARDED, 'A');
+        Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, 'B');
+        Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, 'C');
+        Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, 'D');
+        Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, 'E');
+
+        Request::setTrustedProxies(array('8.8.8.8'), Request::HEADER_FORWARDED);
+
+        $this->assertSame('A', Request::getTrustedHeaderName(Request::HEADER_FORWARDED));
+        $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP));
+        $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_HOST));
+        $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_PORT));
+        $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_PROTO));
+
+        Request::setTrustedProxies(array('8.8.8.8'), Request::HEADER_X_FORWARDED_ALL);
+
+        $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_FORWARDED));
+        $this->assertSame('B', Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP));
+        $this->assertSame('C', Request::getTrustedHeaderName(Request::HEADER_CLIENT_HOST));
+        $this->assertSame('D', Request::getTrustedHeaderName(Request::HEADER_CLIENT_PORT));
+        $this->assertSame('E', Request::getTrustedHeaderName(Request::HEADER_CLIENT_PROTO));
+
+        Request::setTrustedProxies(array('8.8.8.8'), Request::HEADER_FORWARDED);
+
+        $this->assertSame('A', Request::getTrustedHeaderName(Request::HEADER_FORWARDED));
+
+        //reset
+        Request::setTrustedHeaderName(Request::HEADER_FORWARDED, 'FORWARDED');
+        Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, 'X_FORWARDED_FOR');
+        Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, 'X_FORWARDED_HOST');
+        Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, 'X_FORWARDED_PORT');
+        Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, 'X_FORWARDED_PROTO');
+    }
+
+    /**
+     * @dataProvider protocolVersionProvider
+     */
+    public function testProtocolVersion($serverProtocol, $trustedProxy, $via, $expected)
+    {
+        if ($trustedProxy) {
+            Request::setTrustedProxies(array('1.1.1.1'), -1);
+        }
+
+        $request = new Request();
+        $request->server->set('SERVER_PROTOCOL', $serverProtocol);
+        $request->server->set('REMOTE_ADDR', '1.1.1.1');
+        $request->headers->set('Via', $via);
+
+        $this->assertSame($expected, $request->getProtocolVersion());
+    }
+
+    public function protocolVersionProvider()
+    {
+        return array(
+            'untrusted without via' => array('HTTP/2.0', false, '', 'HTTP/2.0'),
+            'untrusted with via' => array('HTTP/2.0', false, '1.0 fred, 1.1 nowhere.com (Apache/1.1)', 'HTTP/2.0'),
+            'trusted without via' => array('HTTP/2.0', true, '', 'HTTP/2.0'),
+            'trusted with via' => array('HTTP/2.0', true, '1.0 fred, 1.1 nowhere.com (Apache/1.1)', 'HTTP/1.0'),
+            'trusted with via and protocol name' => array('HTTP/2.0', true, 'HTTP/1.0 fred, HTTP/1.1 nowhere.com (Apache/1.1)', 'HTTP/1.0'),
+            'trusted with broken via' => array('HTTP/2.0', true, 'HTTP/1^0 foo', 'HTTP/2.0'),
+            'trusted with partially-broken via' => array('HTTP/2.0', true, '1.0 fred, foo', 'HTTP/1.0'),
+        );
+    }
+
+    public function nonstandardRequestsData()
+    {
+        return array(
+            array('',  '', '/', 'http://host:8080/', ''),
+            array('/', '', '/', 'http://host:8080/', ''),
+
+            array('hello/app.php/x',  '', '/x', 'http://host:8080/hello/app.php/x', '/hello', '/hello/app.php'),
+            array('/hello/app.php/x', '', '/x', 'http://host:8080/hello/app.php/x', '/hello', '/hello/app.php'),
+
+            array('',      'a=b', '/', 'http://host:8080/?a=b'),
+            array('?a=b',  'a=b', '/', 'http://host:8080/?a=b'),
+            array('/?a=b', 'a=b', '/', 'http://host:8080/?a=b'),
+
+            array('x',      'a=b', '/x', 'http://host:8080/x?a=b'),
+            array('x?a=b',  'a=b', '/x', 'http://host:8080/x?a=b'),
+            array('/x?a=b', 'a=b', '/x', 'http://host:8080/x?a=b'),
+
+            array('hello/x',  '', '/x', 'http://host:8080/hello/x', '/hello'),
+            array('/hello/x', '', '/x', 'http://host:8080/hello/x', '/hello'),
+
+            array('hello/app.php/x',      'a=b', '/x', 'http://host:8080/hello/app.php/x?a=b', '/hello', '/hello/app.php'),
+            array('hello/app.php/x?a=b',  'a=b', '/x', 'http://host:8080/hello/app.php/x?a=b', '/hello', '/hello/app.php'),
+            array('/hello/app.php/x?a=b', 'a=b', '/x', 'http://host:8080/hello/app.php/x?a=b', '/hello', '/hello/app.php'),
+        );
+    }
+
+    /**
+     * @dataProvider nonstandardRequestsData
+     */
+    public function testNonstandardRequests($requestUri, $queryString, $expectedPathInfo, $expectedUri, $expectedBasePath = '', $expectedBaseUrl = null)
+    {
+        if (null === $expectedBaseUrl) {
+            $expectedBaseUrl = $expectedBasePath;
+        }
+
+        $server = array(
+            'HTTP_HOST' => 'host:8080',
+            'SERVER_PORT' => '8080',
+            'QUERY_STRING' => $queryString,
+            'PHP_SELF' => '/hello/app.php',
+            'SCRIPT_FILENAME' => '/some/path/app.php',
+            'REQUEST_URI' => $requestUri,
+        );
+
+        $request = new Request(array(), array(), array(), array(), array(), $server);
+
+        $this->assertEquals($expectedPathInfo, $request->getPathInfo());
+        $this->assertEquals($expectedUri, $request->getUri());
+        $this->assertEquals($queryString, $request->getQueryString());
+        $this->assertEquals(8080, $request->getPort());
+        $this->assertEquals('host:8080', $request->getHttpHost());
+        $this->assertEquals($expectedBaseUrl, $request->getBaseUrl());
+        $this->assertEquals($expectedBasePath, $request->getBasePath());
+    }
+}
+
+class RequestContentProxy extends Request
+{
+    public function getContent($asResource = false)
+    {
+        return http_build_query(array('_method' => 'PUT', 'content' => 'mycontent'), '', '&');
+    }
+}
+
+class NewRequest extends Request
+{
+    public function getFoo()
+    {
+        return 'foo';
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/ResponseHeaderBagTest.php b/vendor/symfony/http-foundation/Tests/ResponseHeaderBagTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..ce8553590dcdbabfc301992649d66c7608cdef88
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/ResponseHeaderBagTest.php
@@ -0,0 +1,363 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\ResponseHeaderBag;
+use Symfony\Component\HttpFoundation\Cookie;
+
+/**
+ * @group time-sensitive
+ */
+class ResponseHeaderBagTest extends TestCase
+{
+    public function testAllPreserveCase()
+    {
+        $headers = array(
+            'fOo' => 'BAR',
+            'ETag' => 'xyzzy',
+            'Content-MD5' => 'Q2hlY2sgSW50ZWdyaXR5IQ==',
+            'P3P' => 'CP="CAO PSA OUR"',
+            'WWW-Authenticate' => 'Basic realm="WallyWorld"',
+            'X-UA-Compatible' => 'IE=edge,chrome=1',
+            'X-XSS-Protection' => '1; mode=block',
+        );
+
+        $bag = new ResponseHeaderBag($headers);
+        $allPreservedCase = $bag->allPreserveCase();
+
+        foreach (array_keys($headers) as $headerName) {
+            $this->assertArrayHasKey($headerName, $allPreservedCase, '->allPreserveCase() gets all input keys in original case');
+        }
+    }
+
+    public function testCacheControlHeader()
+    {
+        $bag = new ResponseHeaderBag(array());
+        $this->assertEquals('no-cache, private', $bag->get('Cache-Control'));
+        $this->assertTrue($bag->hasCacheControlDirective('no-cache'));
+
+        $bag = new ResponseHeaderBag(array('Cache-Control' => 'public'));
+        $this->assertEquals('public', $bag->get('Cache-Control'));
+        $this->assertTrue($bag->hasCacheControlDirective('public'));
+
+        $bag = new ResponseHeaderBag(array('ETag' => 'abcde'));
+        $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control'));
+        $this->assertTrue($bag->hasCacheControlDirective('private'));
+        $this->assertTrue($bag->hasCacheControlDirective('must-revalidate'));
+        $this->assertFalse($bag->hasCacheControlDirective('max-age'));
+
+        $bag = new ResponseHeaderBag(array('Expires' => 'Wed, 16 Feb 2011 14:17:43 GMT'));
+        $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control'));
+
+        $bag = new ResponseHeaderBag(array(
+            'Expires' => 'Wed, 16 Feb 2011 14:17:43 GMT',
+            'Cache-Control' => 'max-age=3600',
+        ));
+        $this->assertEquals('max-age=3600, private', $bag->get('Cache-Control'));
+
+        $bag = new ResponseHeaderBag(array('Last-Modified' => 'abcde'));
+        $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control'));
+
+        $bag = new ResponseHeaderBag(array('Etag' => 'abcde', 'Last-Modified' => 'abcde'));
+        $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control'));
+
+        $bag = new ResponseHeaderBag(array('cache-control' => 'max-age=100'));
+        $this->assertEquals('max-age=100, private', $bag->get('Cache-Control'));
+
+        $bag = new ResponseHeaderBag(array('cache-control' => 's-maxage=100'));
+        $this->assertEquals('s-maxage=100', $bag->get('Cache-Control'));
+
+        $bag = new ResponseHeaderBag(array('cache-control' => 'private, max-age=100'));
+        $this->assertEquals('max-age=100, private', $bag->get('Cache-Control'));
+
+        $bag = new ResponseHeaderBag(array('cache-control' => 'public, max-age=100'));
+        $this->assertEquals('max-age=100, public', $bag->get('Cache-Control'));
+
+        $bag = new ResponseHeaderBag();
+        $bag->set('Last-Modified', 'abcde');
+        $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control'));
+
+        $bag = new ResponseHeaderBag();
+        $bag->set('Cache-Control', array('public', 'must-revalidate'));
+        $this->assertCount(1, $bag->get('Cache-Control', null, false));
+        $this->assertEquals('must-revalidate, public', $bag->get('Cache-Control'));
+
+        $bag = new ResponseHeaderBag();
+        $bag->set('Cache-Control', 'public');
+        $bag->set('Cache-Control', 'must-revalidate', false);
+        $this->assertCount(1, $bag->get('Cache-Control', null, false));
+        $this->assertEquals('must-revalidate, public', $bag->get('Cache-Control'));
+    }
+
+    public function testCacheControlClone()
+    {
+        $headers = array('foo' => 'bar');
+        $bag1 = new ResponseHeaderBag($headers);
+        $bag2 = new ResponseHeaderBag($bag1->allPreserveCase());
+        $this->assertEquals($bag1->allPreserveCase(), $bag2->allPreserveCase());
+    }
+
+    public function testToStringIncludesCookieHeaders()
+    {
+        $bag = new ResponseHeaderBag(array());
+        $bag->setCookie(new Cookie('foo', 'bar'));
+
+        $this->assertSetCookieHeader('foo=bar; path=/; httponly', $bag);
+
+        $bag->clearCookie('foo');
+
+        $this->assertSetCookieHeader('foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; max-age=-31536001; path=/; httponly', $bag);
+    }
+
+    public function testClearCookieSecureNotHttpOnly()
+    {
+        $bag = new ResponseHeaderBag(array());
+
+        $bag->clearCookie('foo', '/', null, true, false);
+
+        $this->assertSetCookieHeader('foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; max-age=-31536001; path=/; secure', $bag);
+    }
+
+    public function testReplace()
+    {
+        $bag = new ResponseHeaderBag(array());
+        $this->assertEquals('no-cache, private', $bag->get('Cache-Control'));
+        $this->assertTrue($bag->hasCacheControlDirective('no-cache'));
+
+        $bag->replace(array('Cache-Control' => 'public'));
+        $this->assertEquals('public', $bag->get('Cache-Control'));
+        $this->assertTrue($bag->hasCacheControlDirective('public'));
+    }
+
+    public function testReplaceWithRemove()
+    {
+        $bag = new ResponseHeaderBag(array());
+        $this->assertEquals('no-cache, private', $bag->get('Cache-Control'));
+        $this->assertTrue($bag->hasCacheControlDirective('no-cache'));
+
+        $bag->remove('Cache-Control');
+        $bag->replace(array());
+        $this->assertEquals('no-cache, private', $bag->get('Cache-Control'));
+        $this->assertTrue($bag->hasCacheControlDirective('no-cache'));
+    }
+
+    public function testCookiesWithSameNames()
+    {
+        $bag = new ResponseHeaderBag();
+        $bag->setCookie(new Cookie('foo', 'bar', 0, '/path/foo', 'foo.bar'));
+        $bag->setCookie(new Cookie('foo', 'bar', 0, '/path/bar', 'foo.bar'));
+        $bag->setCookie(new Cookie('foo', 'bar', 0, '/path/bar', 'bar.foo'));
+        $bag->setCookie(new Cookie('foo', 'bar'));
+
+        $this->assertCount(4, $bag->getCookies());
+        $this->assertEquals('foo=bar; path=/path/foo; domain=foo.bar; httponly', $bag->get('set-cookie'));
+        $this->assertEquals(array(
+            'foo=bar; path=/path/foo; domain=foo.bar; httponly',
+            'foo=bar; path=/path/bar; domain=foo.bar; httponly',
+            'foo=bar; path=/path/bar; domain=bar.foo; httponly',
+            'foo=bar; path=/; httponly',
+        ), $bag->get('set-cookie', null, false));
+
+        $this->assertSetCookieHeader('foo=bar; path=/path/foo; domain=foo.bar; httponly', $bag);
+        $this->assertSetCookieHeader('foo=bar; path=/path/bar; domain=foo.bar; httponly', $bag);
+        $this->assertSetCookieHeader('foo=bar; path=/path/bar; domain=bar.foo; httponly', $bag);
+        $this->assertSetCookieHeader('foo=bar; path=/; httponly', $bag);
+
+        $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
+
+        $this->assertArrayHasKey('foo', $cookies['foo.bar']['/path/foo']);
+        $this->assertArrayHasKey('foo', $cookies['foo.bar']['/path/bar']);
+        $this->assertArrayHasKey('foo', $cookies['bar.foo']['/path/bar']);
+        $this->assertArrayHasKey('foo', $cookies['']['/']);
+    }
+
+    public function testRemoveCookie()
+    {
+        $bag = new ResponseHeaderBag();
+        $this->assertFalse($bag->has('set-cookie'));
+
+        $bag->setCookie(new Cookie('foo', 'bar', 0, '/path/foo', 'foo.bar'));
+        $bag->setCookie(new Cookie('bar', 'foo', 0, '/path/bar', 'foo.bar'));
+        $this->assertTrue($bag->has('set-cookie'));
+
+        $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
+        $this->assertArrayHasKey('/path/foo', $cookies['foo.bar']);
+
+        $bag->removeCookie('foo', '/path/foo', 'foo.bar');
+        $this->assertTrue($bag->has('set-cookie'));
+
+        $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
+        $this->assertArrayNotHasKey('/path/foo', $cookies['foo.bar']);
+
+        $bag->removeCookie('bar', '/path/bar', 'foo.bar');
+        $this->assertFalse($bag->has('set-cookie'));
+
+        $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
+        $this->assertArrayNotHasKey('foo.bar', $cookies);
+    }
+
+    public function testRemoveCookieWithNullRemove()
+    {
+        $bag = new ResponseHeaderBag();
+        $bag->setCookie(new Cookie('foo', 'bar', 0));
+        $bag->setCookie(new Cookie('bar', 'foo', 0));
+
+        $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
+        $this->assertArrayHasKey('/', $cookies['']);
+
+        $bag->removeCookie('foo', null);
+        $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
+        $this->assertArrayNotHasKey('foo', $cookies['']['/']);
+
+        $bag->removeCookie('bar', null);
+        $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
+        $this->assertFalse(isset($cookies['']['/']['bar']));
+    }
+
+    public function testSetCookieHeader()
+    {
+        $bag = new ResponseHeaderBag();
+        $bag->set('set-cookie', 'foo=bar');
+        $this->assertEquals(array(new Cookie('foo', 'bar', 0, '/', null, false, false, true)), $bag->getCookies());
+
+        $bag->set('set-cookie', 'foo2=bar2', false);
+        $this->assertEquals(array(
+            new Cookie('foo', 'bar', 0, '/', null, false, false, true),
+            new Cookie('foo2', 'bar2', 0, '/', null, false, false, true),
+        ), $bag->getCookies());
+
+        $bag->remove('set-cookie');
+        $this->assertEquals(array(), $bag->getCookies());
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     */
+    public function testGetCookiesWithInvalidArgument()
+    {
+        $bag = new ResponseHeaderBag();
+
+        $bag->getCookies('invalid_argument');
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     */
+    public function testMakeDispositionInvalidDisposition()
+    {
+        $headers = new ResponseHeaderBag();
+
+        $headers->makeDisposition('invalid', 'foo.html');
+    }
+
+    /**
+     * @dataProvider provideMakeDisposition
+     */
+    public function testMakeDisposition($disposition, $filename, $filenameFallback, $expected)
+    {
+        $headers = new ResponseHeaderBag();
+
+        $this->assertEquals($expected, $headers->makeDisposition($disposition, $filename, $filenameFallback));
+    }
+
+    public function testToStringDoesntMessUpHeaders()
+    {
+        $headers = new ResponseHeaderBag();
+
+        $headers->set('Location', 'http://www.symfony.com');
+        $headers->set('Content-type', 'text/html');
+
+        (string) $headers;
+
+        $allHeaders = $headers->allPreserveCase();
+        $this->assertEquals(array('http://www.symfony.com'), $allHeaders['Location']);
+        $this->assertEquals(array('text/html'), $allHeaders['Content-type']);
+    }
+
+    public function provideMakeDisposition()
+    {
+        return array(
+            array('attachment', 'foo.html', 'foo.html', 'attachment; filename="foo.html"'),
+            array('attachment', 'foo.html', '', 'attachment; filename="foo.html"'),
+            array('attachment', 'foo bar.html', '', 'attachment; filename="foo bar.html"'),
+            array('attachment', 'foo "bar".html', '', 'attachment; filename="foo \\"bar\\".html"'),
+            array('attachment', 'foo%20bar.html', 'foo bar.html', 'attachment; filename="foo bar.html"; filename*=utf-8\'\'foo%2520bar.html'),
+            array('attachment', 'föö.html', 'foo.html', 'attachment; filename="foo.html"; filename*=utf-8\'\'f%C3%B6%C3%B6.html'),
+        );
+    }
+
+    /**
+     * @dataProvider provideMakeDispositionFail
+     * @expectedException \InvalidArgumentException
+     */
+    public function testMakeDispositionFail($disposition, $filename)
+    {
+        $headers = new ResponseHeaderBag();
+
+        $headers->makeDisposition($disposition, $filename);
+    }
+
+    public function provideMakeDispositionFail()
+    {
+        return array(
+            array('attachment', 'foo%20bar.html'),
+            array('attachment', 'foo/bar.html'),
+            array('attachment', '/foo.html'),
+            array('attachment', 'foo\bar.html'),
+            array('attachment', '\foo.html'),
+            array('attachment', 'föö.html'),
+        );
+    }
+
+    public function testDateHeaderAddedOnCreation()
+    {
+        $now = time();
+
+        $bag = new ResponseHeaderBag();
+        $this->assertTrue($bag->has('Date'));
+
+        $this->assertEquals($now, $bag->getDate('Date')->getTimestamp());
+    }
+
+    public function testDateHeaderCanBeSetOnCreation()
+    {
+        $someDate = 'Thu, 23 Mar 2017 09:15:12 GMT';
+        $bag = new ResponseHeaderBag(array('Date' => $someDate));
+
+        $this->assertEquals($someDate, $bag->get('Date'));
+    }
+
+    public function testDateHeaderWillBeRecreatedWhenRemoved()
+    {
+        $someDate = 'Thu, 23 Mar 2017 09:15:12 GMT';
+        $bag = new ResponseHeaderBag(array('Date' => $someDate));
+        $bag->remove('Date');
+
+        // a (new) Date header is still present
+        $this->assertTrue($bag->has('Date'));
+        $this->assertNotEquals($someDate, $bag->get('Date'));
+    }
+
+    public function testDateHeaderWillBeRecreatedWhenHeadersAreReplaced()
+    {
+        $bag = new ResponseHeaderBag();
+        $bag->replace(array());
+
+        $this->assertTrue($bag->has('Date'));
+    }
+
+    private function assertSetCookieHeader($expected, ResponseHeaderBag $actual)
+    {
+        $this->assertRegExp('#^Set-Cookie:\s+'.preg_quote($expected, '#').'$#m', str_replace("\r\n", "\n", (string) $actual));
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/ResponseTest.php b/vendor/symfony/http-foundation/Tests/ResponseTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..350d972a945887a3f1f959a323ce6dae69b30785
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/ResponseTest.php
@@ -0,0 +1,1003 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * @group time-sensitive
+ */
+class ResponseTest extends ResponseTestCase
+{
+    public function testCreate()
+    {
+        $response = Response::create('foo', 301, array('Foo' => 'bar'));
+
+        $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response);
+        $this->assertEquals(301, $response->getStatusCode());
+        $this->assertEquals('bar', $response->headers->get('foo'));
+    }
+
+    public function testToString()
+    {
+        $response = new Response();
+        $response = explode("\r\n", $response);
+        $this->assertEquals('HTTP/1.0 200 OK', $response[0]);
+        $this->assertEquals('Cache-Control: no-cache, private', $response[1]);
+    }
+
+    public function testClone()
+    {
+        $response = new Response();
+        $responseClone = clone $response;
+        $this->assertEquals($response, $responseClone);
+    }
+
+    public function testSendHeaders()
+    {
+        $response = new Response();
+        $headers = $response->sendHeaders();
+        $this->assertObjectHasAttribute('headers', $headers);
+        $this->assertObjectHasAttribute('content', $headers);
+        $this->assertObjectHasAttribute('version', $headers);
+        $this->assertObjectHasAttribute('statusCode', $headers);
+        $this->assertObjectHasAttribute('statusText', $headers);
+        $this->assertObjectHasAttribute('charset', $headers);
+    }
+
+    public function testSend()
+    {
+        $response = new Response();
+        $responseSend = $response->send();
+        $this->assertObjectHasAttribute('headers', $responseSend);
+        $this->assertObjectHasAttribute('content', $responseSend);
+        $this->assertObjectHasAttribute('version', $responseSend);
+        $this->assertObjectHasAttribute('statusCode', $responseSend);
+        $this->assertObjectHasAttribute('statusText', $responseSend);
+        $this->assertObjectHasAttribute('charset', $responseSend);
+    }
+
+    public function testGetCharset()
+    {
+        $response = new Response();
+        $charsetOrigin = 'UTF-8';
+        $response->setCharset($charsetOrigin);
+        $charset = $response->getCharset();
+        $this->assertEquals($charsetOrigin, $charset);
+    }
+
+    public function testIsCacheable()
+    {
+        $response = new Response();
+        $this->assertFalse($response->isCacheable());
+    }
+
+    public function testIsCacheableWithErrorCode()
+    {
+        $response = new Response('', 500);
+        $this->assertFalse($response->isCacheable());
+    }
+
+    public function testIsCacheableWithNoStoreDirective()
+    {
+        $response = new Response();
+        $response->headers->set('cache-control', 'private');
+        $this->assertFalse($response->isCacheable());
+    }
+
+    public function testIsCacheableWithSetTtl()
+    {
+        $response = new Response();
+        $response->setTtl(10);
+        $this->assertTrue($response->isCacheable());
+    }
+
+    public function testMustRevalidate()
+    {
+        $response = new Response();
+        $this->assertFalse($response->mustRevalidate());
+    }
+
+    public function testMustRevalidateWithMustRevalidateCacheControlHeader()
+    {
+        $response = new Response();
+        $response->headers->set('cache-control', 'must-revalidate');
+
+        $this->assertTrue($response->mustRevalidate());
+    }
+
+    public function testMustRevalidateWithProxyRevalidateCacheControlHeader()
+    {
+        $response = new Response();
+        $response->headers->set('cache-control', 'proxy-revalidate');
+
+        $this->assertTrue($response->mustRevalidate());
+    }
+
+    public function testSetNotModified()
+    {
+        $response = new Response();
+        $modified = $response->setNotModified();
+        $this->assertObjectHasAttribute('headers', $modified);
+        $this->assertObjectHasAttribute('content', $modified);
+        $this->assertObjectHasAttribute('version', $modified);
+        $this->assertObjectHasAttribute('statusCode', $modified);
+        $this->assertObjectHasAttribute('statusText', $modified);
+        $this->assertObjectHasAttribute('charset', $modified);
+        $this->assertEquals(304, $modified->getStatusCode());
+    }
+
+    public function testIsSuccessful()
+    {
+        $response = new Response();
+        $this->assertTrue($response->isSuccessful());
+    }
+
+    public function testIsNotModified()
+    {
+        $response = new Response();
+        $modified = $response->isNotModified(new Request());
+        $this->assertFalse($modified);
+    }
+
+    public function testIsNotModifiedNotSafe()
+    {
+        $request = Request::create('/homepage', 'POST');
+
+        $response = new Response();
+        $this->assertFalse($response->isNotModified($request));
+    }
+
+    public function testIsNotModifiedLastModified()
+    {
+        $before = 'Sun, 25 Aug 2013 18:32:31 GMT';
+        $modified = 'Sun, 25 Aug 2013 18:33:31 GMT';
+        $after = 'Sun, 25 Aug 2013 19:33:31 GMT';
+
+        $request = new Request();
+        $request->headers->set('If-Modified-Since', $modified);
+
+        $response = new Response();
+
+        $response->headers->set('Last-Modified', $modified);
+        $this->assertTrue($response->isNotModified($request));
+
+        $response->headers->set('Last-Modified', $before);
+        $this->assertTrue($response->isNotModified($request));
+
+        $response->headers->set('Last-Modified', $after);
+        $this->assertFalse($response->isNotModified($request));
+
+        $response->headers->set('Last-Modified', '');
+        $this->assertFalse($response->isNotModified($request));
+    }
+
+    public function testIsNotModifiedEtag()
+    {
+        $etagOne = 'randomly_generated_etag';
+        $etagTwo = 'randomly_generated_etag_2';
+
+        $request = new Request();
+        $request->headers->set('if_none_match', sprintf('%s, %s, %s', $etagOne, $etagTwo, 'etagThree'));
+
+        $response = new Response();
+
+        $response->headers->set('ETag', $etagOne);
+        $this->assertTrue($response->isNotModified($request));
+
+        $response->headers->set('ETag', $etagTwo);
+        $this->assertTrue($response->isNotModified($request));
+
+        $response->headers->set('ETag', '');
+        $this->assertFalse($response->isNotModified($request));
+    }
+
+    public function testIsNotModifiedLastModifiedAndEtag()
+    {
+        $before = 'Sun, 25 Aug 2013 18:32:31 GMT';
+        $modified = 'Sun, 25 Aug 2013 18:33:31 GMT';
+        $after = 'Sun, 25 Aug 2013 19:33:31 GMT';
+        $etag = 'randomly_generated_etag';
+
+        $request = new Request();
+        $request->headers->set('if_none_match', sprintf('%s, %s', $etag, 'etagThree'));
+        $request->headers->set('If-Modified-Since', $modified);
+
+        $response = new Response();
+
+        $response->headers->set('ETag', $etag);
+        $response->headers->set('Last-Modified', $after);
+        $this->assertFalse($response->isNotModified($request));
+
+        $response->headers->set('ETag', 'non-existent-etag');
+        $response->headers->set('Last-Modified', $before);
+        $this->assertFalse($response->isNotModified($request));
+
+        $response->headers->set('ETag', $etag);
+        $response->headers->set('Last-Modified', $modified);
+        $this->assertTrue($response->isNotModified($request));
+    }
+
+    public function testIsNotModifiedIfModifiedSinceAndEtagWithoutLastModified()
+    {
+        $modified = 'Sun, 25 Aug 2013 18:33:31 GMT';
+        $etag = 'randomly_generated_etag';
+
+        $request = new Request();
+        $request->headers->set('if_none_match', sprintf('%s, %s', $etag, 'etagThree'));
+        $request->headers->set('If-Modified-Since', $modified);
+
+        $response = new Response();
+
+        $response->headers->set('ETag', $etag);
+        $this->assertTrue($response->isNotModified($request));
+
+        $response->headers->set('ETag', 'non-existent-etag');
+        $this->assertFalse($response->isNotModified($request));
+    }
+
+    public function testIsValidateable()
+    {
+        $response = new Response('', 200, array('Last-Modified' => $this->createDateTimeOneHourAgo()->format(DATE_RFC2822)));
+        $this->assertTrue($response->isValidateable(), '->isValidateable() returns true if Last-Modified is present');
+
+        $response = new Response('', 200, array('ETag' => '"12345"'));
+        $this->assertTrue($response->isValidateable(), '->isValidateable() returns true if ETag is present');
+
+        $response = new Response();
+        $this->assertFalse($response->isValidateable(), '->isValidateable() returns false when no validator is present');
+    }
+
+    public function testGetDate()
+    {
+        $oneHourAgo = $this->createDateTimeOneHourAgo();
+        $response = new Response('', 200, array('Date' => $oneHourAgo->format(DATE_RFC2822)));
+        $date = $response->getDate();
+        $this->assertEquals($oneHourAgo->getTimestamp(), $date->getTimestamp(), '->getDate() returns the Date header if present');
+
+        $response = new Response();
+        $date = $response->getDate();
+        $this->assertEquals(time(), $date->getTimestamp(), '->getDate() returns the current Date if no Date header present');
+
+        $response = new Response('', 200, array('Date' => $this->createDateTimeOneHourAgo()->format(DATE_RFC2822)));
+        $now = $this->createDateTimeNow();
+        $response->headers->set('Date', $now->format(DATE_RFC2822));
+        $date = $response->getDate();
+        $this->assertEquals($now->getTimestamp(), $date->getTimestamp(), '->getDate() returns the date when the header has been modified');
+
+        $response = new Response('', 200);
+        $now = $this->createDateTimeNow();
+        $response->headers->remove('Date');
+        $date = $response->getDate();
+        $this->assertEquals($now->getTimestamp(), $date->getTimestamp(), '->getDate() returns the current Date when the header has previously been removed');
+    }
+
+    public function testGetMaxAge()
+    {
+        $response = new Response();
+        $response->headers->set('Cache-Control', 's-maxage=600, max-age=0');
+        $this->assertEquals(600, $response->getMaxAge(), '->getMaxAge() uses s-maxage cache control directive when present');
+
+        $response = new Response();
+        $response->headers->set('Cache-Control', 'max-age=600');
+        $this->assertEquals(600, $response->getMaxAge(), '->getMaxAge() falls back to max-age when no s-maxage directive present');
+
+        $response = new Response();
+        $response->headers->set('Cache-Control', 'must-revalidate');
+        $response->headers->set('Expires', $this->createDateTimeOneHourLater()->format(DATE_RFC2822));
+        $this->assertEquals(3600, $response->getMaxAge(), '->getMaxAge() falls back to Expires when no max-age or s-maxage directive present');
+
+        $response = new Response();
+        $response->headers->set('Cache-Control', 'must-revalidate');
+        $response->headers->set('Expires', -1);
+        $this->assertEquals('Sat, 01 Jan 00 00:00:00 +0000', $response->getExpires()->format(DATE_RFC822));
+
+        $response = new Response();
+        $this->assertNull($response->getMaxAge(), '->getMaxAge() returns null if no freshness information available');
+    }
+
+    public function testSetSharedMaxAge()
+    {
+        $response = new Response();
+        $response->setSharedMaxAge(20);
+
+        $cacheControl = $response->headers->get('Cache-Control');
+        $this->assertEquals('public, s-maxage=20', $cacheControl);
+    }
+
+    public function testIsPrivate()
+    {
+        $response = new Response();
+        $response->headers->set('Cache-Control', 'max-age=100');
+        $response->setPrivate();
+        $this->assertEquals(100, $response->headers->getCacheControlDirective('max-age'), '->isPrivate() adds the private Cache-Control directive when set to true');
+        $this->assertTrue($response->headers->getCacheControlDirective('private'), '->isPrivate() adds the private Cache-Control directive when set to true');
+
+        $response = new Response();
+        $response->headers->set('Cache-Control', 'public, max-age=100');
+        $response->setPrivate();
+        $this->assertEquals(100, $response->headers->getCacheControlDirective('max-age'), '->isPrivate() adds the private Cache-Control directive when set to true');
+        $this->assertTrue($response->headers->getCacheControlDirective('private'), '->isPrivate() adds the private Cache-Control directive when set to true');
+        $this->assertFalse($response->headers->hasCacheControlDirective('public'), '->isPrivate() removes the public Cache-Control directive');
+    }
+
+    public function testExpire()
+    {
+        $response = new Response();
+        $response->headers->set('Cache-Control', 'max-age=100');
+        $response->expire();
+        $this->assertEquals(100, $response->headers->get('Age'), '->expire() sets the Age to max-age when present');
+
+        $response = new Response();
+        $response->headers->set('Cache-Control', 'max-age=100, s-maxage=500');
+        $response->expire();
+        $this->assertEquals(500, $response->headers->get('Age'), '->expire() sets the Age to s-maxage when both max-age and s-maxage are present');
+
+        $response = new Response();
+        $response->headers->set('Cache-Control', 'max-age=5, s-maxage=500');
+        $response->headers->set('Age', '1000');
+        $response->expire();
+        $this->assertEquals(1000, $response->headers->get('Age'), '->expire() does nothing when the response is already stale/expired');
+
+        $response = new Response();
+        $response->expire();
+        $this->assertFalse($response->headers->has('Age'), '->expire() does nothing when the response does not include freshness information');
+
+        $response = new Response();
+        $response->headers->set('Expires', -1);
+        $response->expire();
+        $this->assertNull($response->headers->get('Age'), '->expire() does not set the Age when the response is expired');
+    }
+
+    public function testGetTtl()
+    {
+        $response = new Response();
+        $this->assertNull($response->getTtl(), '->getTtl() returns null when no Expires or Cache-Control headers are present');
+
+        $response = new Response();
+        $response->headers->set('Expires', $this->createDateTimeOneHourLater()->format(DATE_RFC2822));
+        $this->assertEquals(3600, $response->getTtl(), '->getTtl() uses the Expires header when no max-age is present');
+
+        $response = new Response();
+        $response->headers->set('Expires', $this->createDateTimeOneHourAgo()->format(DATE_RFC2822));
+        $this->assertLessThan(0, $response->getTtl(), '->getTtl() returns negative values when Expires is in past');
+
+        $response = new Response();
+        $response->headers->set('Expires', $response->getDate()->format(DATE_RFC2822));
+        $response->headers->set('Age', 0);
+        $this->assertSame(0, $response->getTtl(), '->getTtl() correctly handles zero');
+
+        $response = new Response();
+        $response->headers->set('Cache-Control', 'max-age=60');
+        $this->assertEquals(60, $response->getTtl(), '->getTtl() uses Cache-Control max-age when present');
+    }
+
+    public function testSetClientTtl()
+    {
+        $response = new Response();
+        $response->setClientTtl(10);
+
+        $this->assertEquals($response->getMaxAge(), $response->getAge() + 10);
+    }
+
+    public function testGetSetProtocolVersion()
+    {
+        $response = new Response();
+
+        $this->assertEquals('1.0', $response->getProtocolVersion());
+
+        $response->setProtocolVersion('1.1');
+
+        $this->assertEquals('1.1', $response->getProtocolVersion());
+    }
+
+    public function testGetVary()
+    {
+        $response = new Response();
+        $this->assertEquals(array(), $response->getVary(), '->getVary() returns an empty array if no Vary header is present');
+
+        $response = new Response();
+        $response->headers->set('Vary', 'Accept-Language');
+        $this->assertEquals(array('Accept-Language'), $response->getVary(), '->getVary() parses a single header name value');
+
+        $response = new Response();
+        $response->headers->set('Vary', 'Accept-Language User-Agent    X-Foo');
+        $this->assertEquals(array('Accept-Language', 'User-Agent', 'X-Foo'), $response->getVary(), '->getVary() parses multiple header name values separated by spaces');
+
+        $response = new Response();
+        $response->headers->set('Vary', 'Accept-Language,User-Agent,    X-Foo');
+        $this->assertEquals(array('Accept-Language', 'User-Agent', 'X-Foo'), $response->getVary(), '->getVary() parses multiple header name values separated by commas');
+
+        $vary = array('Accept-Language', 'User-Agent', 'X-foo');
+
+        $response = new Response();
+        $response->headers->set('Vary', $vary);
+        $this->assertEquals($vary, $response->getVary(), '->getVary() parses multiple header name values in arrays');
+
+        $response = new Response();
+        $response->headers->set('Vary', 'Accept-Language, User-Agent, X-foo');
+        $this->assertEquals($vary, $response->getVary(), '->getVary() parses multiple header name values in arrays');
+    }
+
+    public function testSetVary()
+    {
+        $response = new Response();
+        $response->setVary('Accept-Language');
+        $this->assertEquals(array('Accept-Language'), $response->getVary());
+
+        $response->setVary('Accept-Language, User-Agent');
+        $this->assertEquals(array('Accept-Language', 'User-Agent'), $response->getVary(), '->setVary() replace the vary header by default');
+
+        $response->setVary('X-Foo', false);
+        $this->assertEquals(array('Accept-Language', 'User-Agent', 'X-Foo'), $response->getVary(), '->setVary() doesn\'t wipe out earlier Vary headers if replace is set to false');
+    }
+
+    public function testDefaultContentType()
+    {
+        $headerMock = $this->getMockBuilder('Symfony\Component\HttpFoundation\ResponseHeaderBag')->setMethods(array('set'))->getMock();
+        $headerMock->expects($this->at(0))
+            ->method('set')
+            ->with('Content-Type', 'text/html');
+        $headerMock->expects($this->at(1))
+            ->method('set')
+            ->with('Content-Type', 'text/html; charset=UTF-8');
+
+        $response = new Response('foo');
+        $response->headers = $headerMock;
+
+        $response->prepare(new Request());
+    }
+
+    public function testContentTypeCharset()
+    {
+        $response = new Response();
+        $response->headers->set('Content-Type', 'text/css');
+
+        // force fixContentType() to be called
+        $response->prepare(new Request());
+
+        $this->assertEquals('text/css; charset=UTF-8', $response->headers->get('Content-Type'));
+    }
+
+    public function testPrepareDoesNothingIfContentTypeIsSet()
+    {
+        $response = new Response('foo');
+        $response->headers->set('Content-Type', 'text/plain');
+
+        $response->prepare(new Request());
+
+        $this->assertEquals('text/plain; charset=UTF-8', $response->headers->get('content-type'));
+    }
+
+    public function testPrepareDoesNothingIfRequestFormatIsNotDefined()
+    {
+        $response = new Response('foo');
+
+        $response->prepare(new Request());
+
+        $this->assertEquals('text/html; charset=UTF-8', $response->headers->get('content-type'));
+    }
+
+    public function testPrepareSetContentType()
+    {
+        $response = new Response('foo');
+        $request = Request::create('/');
+        $request->setRequestFormat('json');
+
+        $response->prepare($request);
+
+        $this->assertEquals('application/json', $response->headers->get('content-type'));
+    }
+
+    public function testPrepareRemovesContentForHeadRequests()
+    {
+        $response = new Response('foo');
+        $request = Request::create('/', 'HEAD');
+
+        $length = 12345;
+        $response->headers->set('Content-Length', $length);
+        $response->prepare($request);
+
+        $this->assertEquals('', $response->getContent());
+        $this->assertEquals($length, $response->headers->get('Content-Length'), 'Content-Length should be as if it was GET; see RFC2616 14.13');
+    }
+
+    public function testPrepareRemovesContentForInformationalResponse()
+    {
+        $response = new Response('foo');
+        $request = Request::create('/');
+
+        $response->setContent('content');
+        $response->setStatusCode(101);
+        $response->prepare($request);
+        $this->assertEquals('', $response->getContent());
+        $this->assertFalse($response->headers->has('Content-Type'));
+        $this->assertFalse($response->headers->has('Content-Type'));
+
+        $response->setContent('content');
+        $response->setStatusCode(304);
+        $response->prepare($request);
+        $this->assertEquals('', $response->getContent());
+        $this->assertFalse($response->headers->has('Content-Type'));
+        $this->assertFalse($response->headers->has('Content-Length'));
+    }
+
+    public function testPrepareRemovesContentLength()
+    {
+        $response = new Response('foo');
+        $request = Request::create('/');
+
+        $response->headers->set('Content-Length', 12345);
+        $response->prepare($request);
+        $this->assertEquals(12345, $response->headers->get('Content-Length'));
+
+        $response->headers->set('Transfer-Encoding', 'chunked');
+        $response->prepare($request);
+        $this->assertFalse($response->headers->has('Content-Length'));
+    }
+
+    public function testPrepareSetsPragmaOnHttp10Only()
+    {
+        $request = Request::create('/', 'GET');
+        $request->server->set('SERVER_PROTOCOL', 'HTTP/1.0');
+
+        $response = new Response('foo');
+        $response->prepare($request);
+        $this->assertEquals('no-cache', $response->headers->get('pragma'));
+        $this->assertEquals('-1', $response->headers->get('expires'));
+
+        $request->server->set('SERVER_PROTOCOL', 'HTTP/1.1');
+        $response = new Response('foo');
+        $response->prepare($request);
+        $this->assertFalse($response->headers->has('pragma'));
+        $this->assertFalse($response->headers->has('expires'));
+    }
+
+    public function testSetCache()
+    {
+        $response = new Response();
+        //array('etag', 'last_modified', 'max_age', 's_maxage', 'private', 'public')
+        try {
+            $response->setCache(array('wrong option' => 'value'));
+            $this->fail('->setCache() throws an InvalidArgumentException if an option is not supported');
+        } catch (\Exception $e) {
+            $this->assertInstanceOf('InvalidArgumentException', $e, '->setCache() throws an InvalidArgumentException if an option is not supported');
+            $this->assertContains('"wrong option"', $e->getMessage());
+        }
+
+        $options = array('etag' => '"whatever"');
+        $response->setCache($options);
+        $this->assertEquals($response->getEtag(), '"whatever"');
+
+        $now = $this->createDateTimeNow();
+        $options = array('last_modified' => $now);
+        $response->setCache($options);
+        $this->assertEquals($response->getLastModified()->getTimestamp(), $now->getTimestamp());
+
+        $options = array('max_age' => 100);
+        $response->setCache($options);
+        $this->assertEquals($response->getMaxAge(), 100);
+
+        $options = array('s_maxage' => 200);
+        $response->setCache($options);
+        $this->assertEquals($response->getMaxAge(), 200);
+
+        $this->assertTrue($response->headers->hasCacheControlDirective('public'));
+        $this->assertFalse($response->headers->hasCacheControlDirective('private'));
+
+        $response->setCache(array('public' => true));
+        $this->assertTrue($response->headers->hasCacheControlDirective('public'));
+        $this->assertFalse($response->headers->hasCacheControlDirective('private'));
+
+        $response->setCache(array('public' => false));
+        $this->assertFalse($response->headers->hasCacheControlDirective('public'));
+        $this->assertTrue($response->headers->hasCacheControlDirective('private'));
+
+        $response->setCache(array('private' => true));
+        $this->assertFalse($response->headers->hasCacheControlDirective('public'));
+        $this->assertTrue($response->headers->hasCacheControlDirective('private'));
+
+        $response->setCache(array('private' => false));
+        $this->assertTrue($response->headers->hasCacheControlDirective('public'));
+        $this->assertFalse($response->headers->hasCacheControlDirective('private'));
+
+        $response->setCache(array('immutable' => true));
+        $this->assertTrue($response->headers->hasCacheControlDirective('immutable'));
+
+        $response->setCache(array('immutable' => false));
+        $this->assertFalse($response->headers->hasCacheControlDirective('immutable'));
+    }
+
+    public function testSendContent()
+    {
+        $response = new Response('test response rendering', 200);
+
+        ob_start();
+        $response->sendContent();
+        $string = ob_get_clean();
+        $this->assertContains('test response rendering', $string);
+    }
+
+    public function testSetPublic()
+    {
+        $response = new Response();
+        $response->setPublic();
+
+        $this->assertTrue($response->headers->hasCacheControlDirective('public'));
+        $this->assertFalse($response->headers->hasCacheControlDirective('private'));
+    }
+
+    public function testSetImmutable()
+    {
+        $response = new Response();
+        $response->setImmutable();
+
+        $this->assertTrue($response->headers->hasCacheControlDirective('immutable'));
+    }
+
+    public function testIsImmutable()
+    {
+        $response = new Response();
+        $response->setImmutable();
+
+        $this->assertTrue($response->isImmutable());
+    }
+
+    public function testSetExpires()
+    {
+        $response = new Response();
+        $response->setExpires(null);
+
+        $this->assertNull($response->getExpires(), '->setExpires() remove the header when passed null');
+
+        $now = $this->createDateTimeNow();
+        $response->setExpires($now);
+
+        $this->assertEquals($response->getExpires()->getTimestamp(), $now->getTimestamp());
+    }
+
+    public function testSetLastModified()
+    {
+        $response = new Response();
+        $response->setLastModified($this->createDateTimeNow());
+        $this->assertNotNull($response->getLastModified());
+
+        $response->setLastModified(null);
+        $this->assertNull($response->getLastModified());
+    }
+
+    public function testIsInvalid()
+    {
+        $response = new Response();
+
+        try {
+            $response->setStatusCode(99);
+            $this->fail();
+        } catch (\InvalidArgumentException $e) {
+            $this->assertTrue($response->isInvalid());
+        }
+
+        try {
+            $response->setStatusCode(650);
+            $this->fail();
+        } catch (\InvalidArgumentException $e) {
+            $this->assertTrue($response->isInvalid());
+        }
+
+        $response = new Response('', 200);
+        $this->assertFalse($response->isInvalid());
+    }
+
+    /**
+     * @dataProvider getStatusCodeFixtures
+     */
+    public function testSetStatusCode($code, $text, $expectedText)
+    {
+        $response = new Response();
+
+        $response->setStatusCode($code, $text);
+
+        $statusText = new \ReflectionProperty($response, 'statusText');
+        $statusText->setAccessible(true);
+
+        $this->assertEquals($expectedText, $statusText->getValue($response));
+    }
+
+    public function getStatusCodeFixtures()
+    {
+        return array(
+            array('200', null, 'OK'),
+            array('200', false, ''),
+            array('200', 'foo', 'foo'),
+            array('199', null, 'unknown status'),
+            array('199', false, ''),
+            array('199', 'foo', 'foo'),
+        );
+    }
+
+    public function testIsInformational()
+    {
+        $response = new Response('', 100);
+        $this->assertTrue($response->isInformational());
+
+        $response = new Response('', 200);
+        $this->assertFalse($response->isInformational());
+    }
+
+    public function testIsRedirectRedirection()
+    {
+        foreach (array(301, 302, 303, 307) as $code) {
+            $response = new Response('', $code);
+            $this->assertTrue($response->isRedirection());
+            $this->assertTrue($response->isRedirect());
+        }
+
+        $response = new Response('', 304);
+        $this->assertTrue($response->isRedirection());
+        $this->assertFalse($response->isRedirect());
+
+        $response = new Response('', 200);
+        $this->assertFalse($response->isRedirection());
+        $this->assertFalse($response->isRedirect());
+
+        $response = new Response('', 404);
+        $this->assertFalse($response->isRedirection());
+        $this->assertFalse($response->isRedirect());
+
+        $response = new Response('', 301, array('Location' => '/good-uri'));
+        $this->assertFalse($response->isRedirect('/bad-uri'));
+        $this->assertTrue($response->isRedirect('/good-uri'));
+    }
+
+    public function testIsNotFound()
+    {
+        $response = new Response('', 404);
+        $this->assertTrue($response->isNotFound());
+
+        $response = new Response('', 200);
+        $this->assertFalse($response->isNotFound());
+    }
+
+    public function testIsEmpty()
+    {
+        foreach (array(204, 304) as $code) {
+            $response = new Response('', $code);
+            $this->assertTrue($response->isEmpty());
+        }
+
+        $response = new Response('', 200);
+        $this->assertFalse($response->isEmpty());
+    }
+
+    public function testIsForbidden()
+    {
+        $response = new Response('', 403);
+        $this->assertTrue($response->isForbidden());
+
+        $response = new Response('', 200);
+        $this->assertFalse($response->isForbidden());
+    }
+
+    public function testIsOk()
+    {
+        $response = new Response('', 200);
+        $this->assertTrue($response->isOk());
+
+        $response = new Response('', 404);
+        $this->assertFalse($response->isOk());
+    }
+
+    public function testIsServerOrClientError()
+    {
+        $response = new Response('', 404);
+        $this->assertTrue($response->isClientError());
+        $this->assertFalse($response->isServerError());
+
+        $response = new Response('', 500);
+        $this->assertFalse($response->isClientError());
+        $this->assertTrue($response->isServerError());
+    }
+
+    public function testHasVary()
+    {
+        $response = new Response();
+        $this->assertFalse($response->hasVary());
+
+        $response->setVary('User-Agent');
+        $this->assertTrue($response->hasVary());
+    }
+
+    public function testSetEtag()
+    {
+        $response = new Response('', 200, array('ETag' => '"12345"'));
+        $response->setEtag();
+
+        $this->assertNull($response->headers->get('Etag'), '->setEtag() removes Etags when call with null');
+    }
+
+    /**
+     * @dataProvider validContentProvider
+     */
+    public function testSetContent($content)
+    {
+        $response = new Response();
+        $response->setContent($content);
+        $this->assertEquals((string) $content, $response->getContent());
+    }
+
+    /**
+     * @expectedException \UnexpectedValueException
+     * @dataProvider invalidContentProvider
+     */
+    public function testSetContentInvalid($content)
+    {
+        $response = new Response();
+        $response->setContent($content);
+    }
+
+    public function testSettersAreChainable()
+    {
+        $response = new Response();
+
+        $setters = array(
+            'setProtocolVersion' => '1.0',
+            'setCharset' => 'UTF-8',
+            'setPublic' => null,
+            'setPrivate' => null,
+            'setDate' => $this->createDateTimeNow(),
+            'expire' => null,
+            'setMaxAge' => 1,
+            'setSharedMaxAge' => 1,
+            'setTtl' => 1,
+            'setClientTtl' => 1,
+        );
+
+        foreach ($setters as $setter => $arg) {
+            $this->assertEquals($response, $response->{$setter}($arg));
+        }
+    }
+
+    public function testNoDeprecationsAreTriggered()
+    {
+        new DefaultResponse();
+        $this->getMockBuilder(Response::class)->getMock();
+
+        // we just need to ensure that subclasses of Response can be created without any deprecations
+        // being triggered if the subclass does not override any final methods
+        $this->addToAssertionCount(1);
+    }
+
+    public function validContentProvider()
+    {
+        return array(
+            'obj' => array(new StringableObject()),
+            'string' => array('Foo'),
+            'int' => array(2),
+        );
+    }
+
+    public function invalidContentProvider()
+    {
+        return array(
+            'obj' => array(new \stdClass()),
+            'array' => array(array()),
+            'bool' => array(true, '1'),
+        );
+    }
+
+    protected function createDateTimeOneHourAgo()
+    {
+        return $this->createDateTimeNow()->sub(new \DateInterval('PT1H'));
+    }
+
+    protected function createDateTimeOneHourLater()
+    {
+        return $this->createDateTimeNow()->add(new \DateInterval('PT1H'));
+    }
+
+    protected function createDateTimeNow()
+    {
+        $date = new \DateTime();
+
+        return $date->setTimestamp(time());
+    }
+
+    protected function provideResponse()
+    {
+        return new Response();
+    }
+
+    /**
+     * @see       http://github.com/zendframework/zend-diactoros for the canonical source repository
+     *
+     * @author    Fábio Pacheco
+     * @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
+     * @license   https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
+     */
+    public function ianaCodesReasonPhrasesProvider()
+    {
+        if (!in_array('https', stream_get_wrappers(), true)) {
+            $this->markTestSkipped('The "https" wrapper is not available');
+        }
+
+        $ianaHttpStatusCodes = new \DOMDocument();
+
+        libxml_set_streams_context(stream_context_create(array(
+            'http' => array(
+                'method' => 'GET',
+                'timeout' => 30,
+            ),
+        )));
+
+        $ianaHttpStatusCodes->load('https://www.iana.org/assignments/http-status-codes/http-status-codes.xml');
+        if (!$ianaHttpStatusCodes->relaxNGValidate(__DIR__.'/schema/http-status-codes.rng')) {
+            self::fail('Invalid IANA\'s HTTP status code list.');
+        }
+
+        $ianaCodesReasonPhrases = array();
+
+        $xpath = new \DOMXPath($ianaHttpStatusCodes);
+        $xpath->registerNamespace('ns', 'http://www.iana.org/assignments');
+
+        $records = $xpath->query('//ns:record');
+        foreach ($records as $record) {
+            $value = $xpath->query('.//ns:value', $record)->item(0)->nodeValue;
+            $description = $xpath->query('.//ns:description', $record)->item(0)->nodeValue;
+
+            if (in_array($description, array('Unassigned', '(Unused)'), true)) {
+                continue;
+            }
+
+            if (preg_match('/^([0-9]+)\s*\-\s*([0-9]+)$/', $value, $matches)) {
+                for ($value = $matches[1]; $value <= $matches[2]; ++$value) {
+                    $ianaCodesReasonPhrases[] = array($value, $description);
+                }
+            } else {
+                $ianaCodesReasonPhrases[] = array($value, $description);
+            }
+        }
+
+        return $ianaCodesReasonPhrases;
+    }
+
+    /**
+     * @dataProvider ianaCodesReasonPhrasesProvider
+     */
+    public function testReasonPhraseDefaultsAgainstIana($code, $reasonPhrase)
+    {
+        $this->assertEquals($reasonPhrase, Response::$statusTexts[$code]);
+    }
+}
+
+class StringableObject
+{
+    public function __toString()
+    {
+        return 'Foo';
+    }
+}
+
+class DefaultResponse extends Response
+{
+}
+
+class ExtendedResponse extends Response
+{
+    public function setLastModified(\DateTime $date = null)
+    {
+    }
+
+    public function getDate()
+    {
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/ResponseTestCase.php b/vendor/symfony/http-foundation/Tests/ResponseTestCase.php
new file mode 100644
index 0000000000000000000000000000000000000000..4ead34c1053d5bbfda8d9d58d6452dc462729b04
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/ResponseTestCase.php
@@ -0,0 +1,89 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Request;
+
+abstract class ResponseTestCase extends TestCase
+{
+    public function testNoCacheControlHeaderOnAttachmentUsingHTTPSAndMSIE()
+    {
+        // Check for HTTPS and IE 8
+        $request = new Request();
+        $request->server->set('HTTPS', true);
+        $request->server->set('HTTP_USER_AGENT', 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)');
+
+        $response = $this->provideResponse();
+        $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"');
+        $response->prepare($request);
+
+        $this->assertFalse($response->headers->has('Cache-Control'));
+
+        // Check for IE 10 and HTTPS
+        $request->server->set('HTTP_USER_AGENT', 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)');
+
+        $response = $this->provideResponse();
+        $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"');
+        $response->prepare($request);
+
+        $this->assertTrue($response->headers->has('Cache-Control'));
+
+        // Check for IE 9 and HTTPS
+        $request->server->set('HTTP_USER_AGENT', 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 7.1; Trident/5.0)');
+
+        $response = $this->provideResponse();
+        $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"');
+        $response->prepare($request);
+
+        $this->assertTrue($response->headers->has('Cache-Control'));
+
+        // Check for IE 9 and HTTP
+        $request->server->set('HTTPS', false);
+
+        $response = $this->provideResponse();
+        $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"');
+        $response->prepare($request);
+
+        $this->assertTrue($response->headers->has('Cache-Control'));
+
+        // Check for IE 8 and HTTP
+        $request->server->set('HTTP_USER_AGENT', 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)');
+
+        $response = $this->provideResponse();
+        $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"');
+        $response->prepare($request);
+
+        $this->assertTrue($response->headers->has('Cache-Control'));
+
+        // Check for non-IE and HTTPS
+        $request->server->set('HTTPS', true);
+        $request->server->set('HTTP_USER_AGENT', 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.60 Safari/537.17');
+
+        $response = $this->provideResponse();
+        $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"');
+        $response->prepare($request);
+
+        $this->assertTrue($response->headers->has('Cache-Control'));
+
+        // Check for non-IE and HTTP
+        $request->server->set('HTTPS', false);
+
+        $response = $this->provideResponse();
+        $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"');
+        $response->prepare($request);
+
+        $this->assertTrue($response->headers->has('Cache-Control'));
+    }
+
+    abstract protected function provideResponse();
+}
diff --git a/vendor/symfony/http-foundation/Tests/ServerBagTest.php b/vendor/symfony/http-foundation/Tests/ServerBagTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..f8becec5a982df0ffc049c1f409e334b7e260cc2
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/ServerBagTest.php
@@ -0,0 +1,170 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\ServerBag;
+
+/**
+ * ServerBagTest.
+ *
+ * @author Bulat Shakirzyanov <mallluhuct@gmail.com>
+ */
+class ServerBagTest extends TestCase
+{
+    public function testShouldExtractHeadersFromServerArray()
+    {
+        $server = array(
+            'SOME_SERVER_VARIABLE' => 'value',
+            'SOME_SERVER_VARIABLE2' => 'value',
+            'ROOT' => 'value',
+            'HTTP_CONTENT_TYPE' => 'text/html',
+            'HTTP_CONTENT_LENGTH' => '0',
+            'HTTP_ETAG' => 'asdf',
+            'PHP_AUTH_USER' => 'foo',
+            'PHP_AUTH_PW' => 'bar',
+        );
+
+        $bag = new ServerBag($server);
+
+        $this->assertEquals(array(
+            'CONTENT_TYPE' => 'text/html',
+            'CONTENT_LENGTH' => '0',
+            'ETAG' => 'asdf',
+            'AUTHORIZATION' => 'Basic '.base64_encode('foo:bar'),
+            'PHP_AUTH_USER' => 'foo',
+            'PHP_AUTH_PW' => 'bar',
+        ), $bag->getHeaders());
+    }
+
+    public function testHttpPasswordIsOptional()
+    {
+        $bag = new ServerBag(array('PHP_AUTH_USER' => 'foo'));
+
+        $this->assertEquals(array(
+            'AUTHORIZATION' => 'Basic '.base64_encode('foo:'),
+            'PHP_AUTH_USER' => 'foo',
+            'PHP_AUTH_PW' => '',
+        ), $bag->getHeaders());
+    }
+
+    public function testHttpBasicAuthWithPhpCgi()
+    {
+        $bag = new ServerBag(array('HTTP_AUTHORIZATION' => 'Basic '.base64_encode('foo:bar')));
+
+        $this->assertEquals(array(
+            'AUTHORIZATION' => 'Basic '.base64_encode('foo:bar'),
+            'PHP_AUTH_USER' => 'foo',
+            'PHP_AUTH_PW' => 'bar',
+        ), $bag->getHeaders());
+    }
+
+    public function testHttpBasicAuthWithPhpCgiBogus()
+    {
+        $bag = new ServerBag(array('HTTP_AUTHORIZATION' => 'Basic_'.base64_encode('foo:bar')));
+
+        // Username and passwords should not be set as the header is bogus
+        $headers = $bag->getHeaders();
+        $this->assertArrayNotHasKey('PHP_AUTH_USER', $headers);
+        $this->assertArrayNotHasKey('PHP_AUTH_PW', $headers);
+    }
+
+    public function testHttpBasicAuthWithPhpCgiRedirect()
+    {
+        $bag = new ServerBag(array('REDIRECT_HTTP_AUTHORIZATION' => 'Basic '.base64_encode('username:pass:word')));
+
+        $this->assertEquals(array(
+            'AUTHORIZATION' => 'Basic '.base64_encode('username:pass:word'),
+            'PHP_AUTH_USER' => 'username',
+            'PHP_AUTH_PW' => 'pass:word',
+        ), $bag->getHeaders());
+    }
+
+    public function testHttpBasicAuthWithPhpCgiEmptyPassword()
+    {
+        $bag = new ServerBag(array('HTTP_AUTHORIZATION' => 'Basic '.base64_encode('foo:')));
+
+        $this->assertEquals(array(
+            'AUTHORIZATION' => 'Basic '.base64_encode('foo:'),
+            'PHP_AUTH_USER' => 'foo',
+            'PHP_AUTH_PW' => '',
+        ), $bag->getHeaders());
+    }
+
+    public function testHttpDigestAuthWithPhpCgi()
+    {
+        $digest = 'Digest username="foo", realm="acme", nonce="'.md5('secret').'", uri="/protected, qop="auth"';
+        $bag = new ServerBag(array('HTTP_AUTHORIZATION' => $digest));
+
+        $this->assertEquals(array(
+            'AUTHORIZATION' => $digest,
+            'PHP_AUTH_DIGEST' => $digest,
+        ), $bag->getHeaders());
+    }
+
+    public function testHttpDigestAuthWithPhpCgiBogus()
+    {
+        $digest = 'Digest_username="foo", realm="acme", nonce="'.md5('secret').'", uri="/protected, qop="auth"';
+        $bag = new ServerBag(array('HTTP_AUTHORIZATION' => $digest));
+
+        // Username and passwords should not be set as the header is bogus
+        $headers = $bag->getHeaders();
+        $this->assertArrayNotHasKey('PHP_AUTH_USER', $headers);
+        $this->assertArrayNotHasKey('PHP_AUTH_PW', $headers);
+    }
+
+    public function testHttpDigestAuthWithPhpCgiRedirect()
+    {
+        $digest = 'Digest username="foo", realm="acme", nonce="'.md5('secret').'", uri="/protected, qop="auth"';
+        $bag = new ServerBag(array('REDIRECT_HTTP_AUTHORIZATION' => $digest));
+
+        $this->assertEquals(array(
+            'AUTHORIZATION' => $digest,
+            'PHP_AUTH_DIGEST' => $digest,
+        ), $bag->getHeaders());
+    }
+
+    public function testOAuthBearerAuth()
+    {
+        $headerContent = 'Bearer L-yLEOr9zhmUYRkzN1jwwxwQ-PBNiKDc8dgfB4hTfvo';
+        $bag = new ServerBag(array('HTTP_AUTHORIZATION' => $headerContent));
+
+        $this->assertEquals(array(
+            'AUTHORIZATION' => $headerContent,
+        ), $bag->getHeaders());
+    }
+
+    public function testOAuthBearerAuthWithRedirect()
+    {
+        $headerContent = 'Bearer L-yLEOr9zhmUYRkzN1jwwxwQ-PBNiKDc8dgfB4hTfvo';
+        $bag = new ServerBag(array('REDIRECT_HTTP_AUTHORIZATION' => $headerContent));
+
+        $this->assertEquals(array(
+            'AUTHORIZATION' => $headerContent,
+        ), $bag->getHeaders());
+    }
+
+    /**
+     * @see https://github.com/symfony/symfony/issues/17345
+     */
+    public function testItDoesNotOverwriteTheAuthorizationHeaderIfItIsAlreadySet()
+    {
+        $headerContent = 'Bearer L-yLEOr9zhmUYRkzN1jwwxwQ-PBNiKDc8dgfB4hTfvo';
+        $bag = new ServerBag(array('PHP_AUTH_USER' => 'foo', 'HTTP_AUTHORIZATION' => $headerContent));
+
+        $this->assertEquals(array(
+            'AUTHORIZATION' => $headerContent,
+            'PHP_AUTH_USER' => 'foo',
+            'PHP_AUTH_PW' => '',
+        ), $bag->getHeaders());
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Attribute/AttributeBagTest.php b/vendor/symfony/http-foundation/Tests/Session/Attribute/AttributeBagTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..724a0b9844700d79dae4c6842e58b304c07ac3be
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Attribute/AttributeBagTest.php
@@ -0,0 +1,186 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Attribute;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
+
+/**
+ * Tests AttributeBag.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class AttributeBagTest extends TestCase
+{
+    private $array = array();
+
+    /**
+     * @var AttributeBag
+     */
+    private $bag;
+
+    protected function setUp()
+    {
+        $this->array = array(
+            'hello' => 'world',
+            'always' => 'be happy',
+            'user.login' => 'drak',
+            'csrf.token' => array(
+                'a' => '1234',
+                'b' => '4321',
+            ),
+            'category' => array(
+                'fishing' => array(
+                    'first' => 'cod',
+                    'second' => 'sole',
+                ),
+            ),
+        );
+        $this->bag = new AttributeBag('_sf2');
+        $this->bag->initialize($this->array);
+    }
+
+    protected function tearDown()
+    {
+        $this->bag = null;
+        $this->array = array();
+    }
+
+    public function testInitialize()
+    {
+        $bag = new AttributeBag();
+        $bag->initialize($this->array);
+        $this->assertEquals($this->array, $bag->all());
+        $array = array('should' => 'change');
+        $bag->initialize($array);
+        $this->assertEquals($array, $bag->all());
+    }
+
+    public function testGetStorageKey()
+    {
+        $this->assertEquals('_sf2', $this->bag->getStorageKey());
+        $attributeBag = new AttributeBag('test');
+        $this->assertEquals('test', $attributeBag->getStorageKey());
+    }
+
+    public function testGetSetName()
+    {
+        $this->assertEquals('attributes', $this->bag->getName());
+        $this->bag->setName('foo');
+        $this->assertEquals('foo', $this->bag->getName());
+    }
+
+    /**
+     * @dataProvider attributesProvider
+     */
+    public function testHas($key, $value, $exists)
+    {
+        $this->assertEquals($exists, $this->bag->has($key));
+    }
+
+    /**
+     * @dataProvider attributesProvider
+     */
+    public function testGet($key, $value, $expected)
+    {
+        $this->assertEquals($value, $this->bag->get($key));
+    }
+
+    public function testGetDefaults()
+    {
+        $this->assertNull($this->bag->get('user2.login'));
+        $this->assertEquals('default', $this->bag->get('user2.login', 'default'));
+    }
+
+    /**
+     * @dataProvider attributesProvider
+     */
+    public function testSet($key, $value, $expected)
+    {
+        $this->bag->set($key, $value);
+        $this->assertEquals($value, $this->bag->get($key));
+    }
+
+    public function testAll()
+    {
+        $this->assertEquals($this->array, $this->bag->all());
+
+        $this->bag->set('hello', 'fabien');
+        $array = $this->array;
+        $array['hello'] = 'fabien';
+        $this->assertEquals($array, $this->bag->all());
+    }
+
+    public function testReplace()
+    {
+        $array = array();
+        $array['name'] = 'jack';
+        $array['foo.bar'] = 'beep';
+        $this->bag->replace($array);
+        $this->assertEquals($array, $this->bag->all());
+        $this->assertNull($this->bag->get('hello'));
+        $this->assertNull($this->bag->get('always'));
+        $this->assertNull($this->bag->get('user.login'));
+    }
+
+    public function testRemove()
+    {
+        $this->assertEquals('world', $this->bag->get('hello'));
+        $this->bag->remove('hello');
+        $this->assertNull($this->bag->get('hello'));
+
+        $this->assertEquals('be happy', $this->bag->get('always'));
+        $this->bag->remove('always');
+        $this->assertNull($this->bag->get('always'));
+
+        $this->assertEquals('drak', $this->bag->get('user.login'));
+        $this->bag->remove('user.login');
+        $this->assertNull($this->bag->get('user.login'));
+    }
+
+    public function testClear()
+    {
+        $this->bag->clear();
+        $this->assertEquals(array(), $this->bag->all());
+    }
+
+    public function attributesProvider()
+    {
+        return array(
+            array('hello', 'world', true),
+            array('always', 'be happy', true),
+            array('user.login', 'drak', true),
+            array('csrf.token', array('a' => '1234', 'b' => '4321'), true),
+            array('category', array('fishing' => array('first' => 'cod', 'second' => 'sole')), true),
+            array('user2.login', null, false),
+            array('never', null, false),
+            array('bye', null, false),
+            array('bye/for/now', null, false),
+        );
+    }
+
+    public function testGetIterator()
+    {
+        $i = 0;
+        foreach ($this->bag as $key => $val) {
+            $this->assertEquals($this->array[$key], $val);
+            ++$i;
+        }
+
+        $this->assertEquals(count($this->array), $i);
+    }
+
+    public function testCount()
+    {
+        $this->assertCount(count($this->array), $this->bag);
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Attribute/NamespacedAttributeBagTest.php b/vendor/symfony/http-foundation/Tests/Session/Attribute/NamespacedAttributeBagTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..f074ce1b26261e4adc4220d0980a7553396759ac
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Attribute/NamespacedAttributeBagTest.php
@@ -0,0 +1,182 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Attribute;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Attribute\NamespacedAttributeBag;
+
+/**
+ * Tests NamespacedAttributeBag.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class NamespacedAttributeBagTest extends TestCase
+{
+    private $array = array();
+
+    /**
+     * @var NamespacedAttributeBag
+     */
+    private $bag;
+
+    protected function setUp()
+    {
+        $this->array = array(
+            'hello' => 'world',
+            'always' => 'be happy',
+            'user.login' => 'drak',
+            'csrf.token' => array(
+                'a' => '1234',
+                'b' => '4321',
+            ),
+            'category' => array(
+                'fishing' => array(
+                    'first' => 'cod',
+                    'second' => 'sole',
+                ),
+            ),
+        );
+        $this->bag = new NamespacedAttributeBag('_sf2', '/');
+        $this->bag->initialize($this->array);
+    }
+
+    protected function tearDown()
+    {
+        $this->bag = null;
+        $this->array = array();
+    }
+
+    public function testInitialize()
+    {
+        $bag = new NamespacedAttributeBag();
+        $bag->initialize($this->array);
+        $this->assertEquals($this->array, $this->bag->all());
+        $array = array('should' => 'not stick');
+        $bag->initialize($array);
+
+        // should have remained the same
+        $this->assertEquals($this->array, $this->bag->all());
+    }
+
+    public function testGetStorageKey()
+    {
+        $this->assertEquals('_sf2', $this->bag->getStorageKey());
+        $attributeBag = new NamespacedAttributeBag('test');
+        $this->assertEquals('test', $attributeBag->getStorageKey());
+    }
+
+    /**
+     * @dataProvider attributesProvider
+     */
+    public function testHas($key, $value, $exists)
+    {
+        $this->assertEquals($exists, $this->bag->has($key));
+    }
+
+    /**
+     * @dataProvider attributesProvider
+     */
+    public function testGet($key, $value, $expected)
+    {
+        $this->assertEquals($value, $this->bag->get($key));
+    }
+
+    public function testGetDefaults()
+    {
+        $this->assertNull($this->bag->get('user2.login'));
+        $this->assertEquals('default', $this->bag->get('user2.login', 'default'));
+    }
+
+    /**
+     * @dataProvider attributesProvider
+     */
+    public function testSet($key, $value, $expected)
+    {
+        $this->bag->set($key, $value);
+        $this->assertEquals($value, $this->bag->get($key));
+    }
+
+    public function testAll()
+    {
+        $this->assertEquals($this->array, $this->bag->all());
+
+        $this->bag->set('hello', 'fabien');
+        $array = $this->array;
+        $array['hello'] = 'fabien';
+        $this->assertEquals($array, $this->bag->all());
+    }
+
+    public function testReplace()
+    {
+        $array = array();
+        $array['name'] = 'jack';
+        $array['foo.bar'] = 'beep';
+        $this->bag->replace($array);
+        $this->assertEquals($array, $this->bag->all());
+        $this->assertNull($this->bag->get('hello'));
+        $this->assertNull($this->bag->get('always'));
+        $this->assertNull($this->bag->get('user.login'));
+    }
+
+    public function testRemove()
+    {
+        $this->assertEquals('world', $this->bag->get('hello'));
+        $this->bag->remove('hello');
+        $this->assertNull($this->bag->get('hello'));
+
+        $this->assertEquals('be happy', $this->bag->get('always'));
+        $this->bag->remove('always');
+        $this->assertNull($this->bag->get('always'));
+
+        $this->assertEquals('drak', $this->bag->get('user.login'));
+        $this->bag->remove('user.login');
+        $this->assertNull($this->bag->get('user.login'));
+    }
+
+    public function testRemoveExistingNamespacedAttribute()
+    {
+        $this->assertSame('cod', $this->bag->remove('category/fishing/first'));
+    }
+
+    public function testRemoveNonexistingNamespacedAttribute()
+    {
+        $this->assertNull($this->bag->remove('foo/bar/baz'));
+    }
+
+    public function testClear()
+    {
+        $this->bag->clear();
+        $this->assertEquals(array(), $this->bag->all());
+    }
+
+    public function attributesProvider()
+    {
+        return array(
+            array('hello', 'world', true),
+            array('always', 'be happy', true),
+            array('user.login', 'drak', true),
+            array('csrf.token', array('a' => '1234', 'b' => '4321'), true),
+            array('csrf.token/a', '1234', true),
+            array('csrf.token/b', '4321', true),
+            array('category', array('fishing' => array('first' => 'cod', 'second' => 'sole')), true),
+            array('category/fishing', array('first' => 'cod', 'second' => 'sole'), true),
+            array('category/fishing/missing/first', null, false),
+            array('category/fishing/first', 'cod', true),
+            array('category/fishing/second', 'sole', true),
+            array('category/fishing/missing/second', null, false),
+            array('user2.login', null, false),
+            array('never', null, false),
+            array('bye', null, false),
+            array('bye/for/now', null, false),
+        );
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Flash/AutoExpireFlashBagTest.php b/vendor/symfony/http-foundation/Tests/Session/Flash/AutoExpireFlashBagTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..fa8626ab923b7b365a3ee4df035a29bfc962c829
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Flash/AutoExpireFlashBagTest.php
@@ -0,0 +1,161 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Flash;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag as FlashBag;
+
+/**
+ * AutoExpireFlashBagTest.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class AutoExpireFlashBagTest extends TestCase
+{
+    /**
+     * @var \Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag
+     */
+    private $bag;
+
+    protected $array = array();
+
+    protected function setUp()
+    {
+        parent::setUp();
+        $this->bag = new FlashBag();
+        $this->array = array('new' => array('notice' => array('A previous flash message')));
+        $this->bag->initialize($this->array);
+    }
+
+    protected function tearDown()
+    {
+        $this->bag = null;
+        parent::tearDown();
+    }
+
+    public function testInitialize()
+    {
+        $bag = new FlashBag();
+        $array = array('new' => array('notice' => array('A previous flash message')));
+        $bag->initialize($array);
+        $this->assertEquals(array('A previous flash message'), $bag->peek('notice'));
+        $array = array('new' => array(
+                'notice' => array('Something else'),
+                'error' => array('a'),
+            ));
+        $bag->initialize($array);
+        $this->assertEquals(array('Something else'), $bag->peek('notice'));
+        $this->assertEquals(array('a'), $bag->peek('error'));
+    }
+
+    public function testGetStorageKey()
+    {
+        $this->assertEquals('_symfony_flashes', $this->bag->getStorageKey());
+        $attributeBag = new FlashBag('test');
+        $this->assertEquals('test', $attributeBag->getStorageKey());
+    }
+
+    public function testGetSetName()
+    {
+        $this->assertEquals('flashes', $this->bag->getName());
+        $this->bag->setName('foo');
+        $this->assertEquals('foo', $this->bag->getName());
+    }
+
+    public function testPeek()
+    {
+        $this->assertEquals(array(), $this->bag->peek('non_existing'));
+        $this->assertEquals(array('default'), $this->bag->peek('non_existing', array('default')));
+        $this->assertEquals(array('A previous flash message'), $this->bag->peek('notice'));
+        $this->assertEquals(array('A previous flash message'), $this->bag->peek('notice'));
+    }
+
+    public function testSet()
+    {
+        $this->bag->set('notice', 'Foo');
+        $this->assertEquals(array('A previous flash message'), $this->bag->peek('notice'));
+    }
+
+    public function testHas()
+    {
+        $this->assertFalse($this->bag->has('nothing'));
+        $this->assertTrue($this->bag->has('notice'));
+    }
+
+    public function testKeys()
+    {
+        $this->assertEquals(array('notice'), $this->bag->keys());
+    }
+
+    public function testPeekAll()
+    {
+        $array = array(
+            'new' => array(
+                'notice' => 'Foo',
+                'error' => 'Bar',
+            ),
+        );
+
+        $this->bag->initialize($array);
+        $this->assertEquals(array(
+            'notice' => 'Foo',
+            'error' => 'Bar',
+            ), $this->bag->peekAll()
+        );
+
+        $this->assertEquals(array(
+            'notice' => 'Foo',
+            'error' => 'Bar',
+            ), $this->bag->peekAll()
+        );
+    }
+
+    public function testGet()
+    {
+        $this->assertEquals(array(), $this->bag->get('non_existing'));
+        $this->assertEquals(array('default'), $this->bag->get('non_existing', array('default')));
+        $this->assertEquals(array('A previous flash message'), $this->bag->get('notice'));
+        $this->assertEquals(array(), $this->bag->get('notice'));
+    }
+
+    public function testSetAll()
+    {
+        $this->bag->setAll(array('a' => 'first', 'b' => 'second'));
+        $this->assertFalse($this->bag->has('a'));
+        $this->assertFalse($this->bag->has('b'));
+    }
+
+    public function testAll()
+    {
+        $this->bag->set('notice', 'Foo');
+        $this->bag->set('error', 'Bar');
+        $this->assertEquals(array(
+            'notice' => array('A previous flash message'),
+            ), $this->bag->all()
+        );
+
+        $this->assertEquals(array(), $this->bag->all());
+    }
+
+    public function testClear()
+    {
+        $this->assertEquals(array('notice' => array('A previous flash message')), $this->bag->clear());
+    }
+
+    public function testDoNotRemoveTheNewFlashesWhenDisplayingTheExistingOnes()
+    {
+        $this->bag->add('success', 'Something');
+        $this->bag->all();
+
+        $this->assertEquals(array('new' => array('success' => array('Something')), 'display' => array()), $this->array);
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Flash/FlashBagTest.php b/vendor/symfony/http-foundation/Tests/Session/Flash/FlashBagTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..c4e75b1b18b4085e485b6345702b71fb37be21b4
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Flash/FlashBagTest.php
@@ -0,0 +1,132 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Flash;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
+
+/**
+ * FlashBagTest.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class FlashBagTest extends TestCase
+{
+    /**
+     * @var \Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface
+     */
+    private $bag;
+
+    protected $array = array();
+
+    protected function setUp()
+    {
+        parent::setUp();
+        $this->bag = new FlashBag();
+        $this->array = array('notice' => array('A previous flash message'));
+        $this->bag->initialize($this->array);
+    }
+
+    protected function tearDown()
+    {
+        $this->bag = null;
+        parent::tearDown();
+    }
+
+    public function testInitialize()
+    {
+        $bag = new FlashBag();
+        $bag->initialize($this->array);
+        $this->assertEquals($this->array, $bag->peekAll());
+        $array = array('should' => array('change'));
+        $bag->initialize($array);
+        $this->assertEquals($array, $bag->peekAll());
+    }
+
+    public function testGetStorageKey()
+    {
+        $this->assertEquals('_symfony_flashes', $this->bag->getStorageKey());
+        $attributeBag = new FlashBag('test');
+        $this->assertEquals('test', $attributeBag->getStorageKey());
+    }
+
+    public function testGetSetName()
+    {
+        $this->assertEquals('flashes', $this->bag->getName());
+        $this->bag->setName('foo');
+        $this->assertEquals('foo', $this->bag->getName());
+    }
+
+    public function testPeek()
+    {
+        $this->assertEquals(array(), $this->bag->peek('non_existing'));
+        $this->assertEquals(array('default'), $this->bag->peek('not_existing', array('default')));
+        $this->assertEquals(array('A previous flash message'), $this->bag->peek('notice'));
+        $this->assertEquals(array('A previous flash message'), $this->bag->peek('notice'));
+    }
+
+    public function testGet()
+    {
+        $this->assertEquals(array(), $this->bag->get('non_existing'));
+        $this->assertEquals(array('default'), $this->bag->get('not_existing', array('default')));
+        $this->assertEquals(array('A previous flash message'), $this->bag->get('notice'));
+        $this->assertEquals(array(), $this->bag->get('notice'));
+    }
+
+    public function testAll()
+    {
+        $this->bag->set('notice', 'Foo');
+        $this->bag->set('error', 'Bar');
+        $this->assertEquals(array(
+            'notice' => array('Foo'),
+            'error' => array('Bar'), ), $this->bag->all()
+        );
+
+        $this->assertEquals(array(), $this->bag->all());
+    }
+
+    public function testSet()
+    {
+        $this->bag->set('notice', 'Foo');
+        $this->bag->set('notice', 'Bar');
+        $this->assertEquals(array('Bar'), $this->bag->peek('notice'));
+    }
+
+    public function testHas()
+    {
+        $this->assertFalse($this->bag->has('nothing'));
+        $this->assertTrue($this->bag->has('notice'));
+    }
+
+    public function testKeys()
+    {
+        $this->assertEquals(array('notice'), $this->bag->keys());
+    }
+
+    public function testPeekAll()
+    {
+        $this->bag->set('notice', 'Foo');
+        $this->bag->set('error', 'Bar');
+        $this->assertEquals(array(
+            'notice' => array('Foo'),
+            'error' => array('Bar'),
+            ), $this->bag->peekAll()
+        );
+        $this->assertTrue($this->bag->has('notice'));
+        $this->assertTrue($this->bag->has('error'));
+        $this->assertEquals(array(
+            'notice' => array('Foo'),
+            'error' => array('Bar'),
+            ), $this->bag->peekAll()
+        );
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/SessionTest.php b/vendor/symfony/http-foundation/Tests/Session/SessionTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..41720e4b6fc4edf4bc01a3d31ae0f918714cb1e0
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/SessionTest.php
@@ -0,0 +1,242 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Session;
+use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
+use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
+use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
+
+/**
+ * SessionTest.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Robert Schönthal <seroscho@googlemail.com>
+ * @author Drak <drak@zikula.org>
+ */
+class SessionTest extends TestCase
+{
+    /**
+     * @var \Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface
+     */
+    protected $storage;
+
+    /**
+     * @var \Symfony\Component\HttpFoundation\Session\SessionInterface
+     */
+    protected $session;
+
+    protected function setUp()
+    {
+        $this->storage = new MockArraySessionStorage();
+        $this->session = new Session($this->storage, new AttributeBag(), new FlashBag());
+    }
+
+    protected function tearDown()
+    {
+        $this->storage = null;
+        $this->session = null;
+    }
+
+    public function testStart()
+    {
+        $this->assertEquals('', $this->session->getId());
+        $this->assertTrue($this->session->start());
+        $this->assertNotEquals('', $this->session->getId());
+    }
+
+    public function testIsStarted()
+    {
+        $this->assertFalse($this->session->isStarted());
+        $this->session->start();
+        $this->assertTrue($this->session->isStarted());
+    }
+
+    public function testSetId()
+    {
+        $this->assertEquals('', $this->session->getId());
+        $this->session->setId('0123456789abcdef');
+        $this->session->start();
+        $this->assertEquals('0123456789abcdef', $this->session->getId());
+    }
+
+    public function testSetName()
+    {
+        $this->assertEquals('MOCKSESSID', $this->session->getName());
+        $this->session->setName('session.test.com');
+        $this->session->start();
+        $this->assertEquals('session.test.com', $this->session->getName());
+    }
+
+    public function testGet()
+    {
+        // tests defaults
+        $this->assertNull($this->session->get('foo'));
+        $this->assertEquals(1, $this->session->get('foo', 1));
+    }
+
+    /**
+     * @dataProvider setProvider
+     */
+    public function testSet($key, $value)
+    {
+        $this->session->set($key, $value);
+        $this->assertEquals($value, $this->session->get($key));
+    }
+
+    /**
+     * @dataProvider setProvider
+     */
+    public function testHas($key, $value)
+    {
+        $this->session->set($key, $value);
+        $this->assertTrue($this->session->has($key));
+        $this->assertFalse($this->session->has($key.'non_value'));
+    }
+
+    public function testReplace()
+    {
+        $this->session->replace(array('happiness' => 'be good', 'symfony' => 'awesome'));
+        $this->assertEquals(array('happiness' => 'be good', 'symfony' => 'awesome'), $this->session->all());
+        $this->session->replace(array());
+        $this->assertEquals(array(), $this->session->all());
+    }
+
+    /**
+     * @dataProvider setProvider
+     */
+    public function testAll($key, $value, $result)
+    {
+        $this->session->set($key, $value);
+        $this->assertEquals($result, $this->session->all());
+    }
+
+    /**
+     * @dataProvider setProvider
+     */
+    public function testClear($key, $value)
+    {
+        $this->session->set('hi', 'fabien');
+        $this->session->set($key, $value);
+        $this->session->clear();
+        $this->assertEquals(array(), $this->session->all());
+    }
+
+    public function setProvider()
+    {
+        return array(
+            array('foo', 'bar', array('foo' => 'bar')),
+            array('foo.bar', 'too much beer', array('foo.bar' => 'too much beer')),
+            array('great', 'symfony is great', array('great' => 'symfony is great')),
+        );
+    }
+
+    /**
+     * @dataProvider setProvider
+     */
+    public function testRemove($key, $value)
+    {
+        $this->session->set('hi.world', 'have a nice day');
+        $this->session->set($key, $value);
+        $this->session->remove($key);
+        $this->assertEquals(array('hi.world' => 'have a nice day'), $this->session->all());
+    }
+
+    public function testInvalidate()
+    {
+        $this->session->set('invalidate', 123);
+        $this->session->invalidate();
+        $this->assertEquals(array(), $this->session->all());
+    }
+
+    public function testMigrate()
+    {
+        $this->session->set('migrate', 321);
+        $this->session->migrate();
+        $this->assertEquals(321, $this->session->get('migrate'));
+    }
+
+    public function testMigrateDestroy()
+    {
+        $this->session->set('migrate', 333);
+        $this->session->migrate(true);
+        $this->assertEquals(333, $this->session->get('migrate'));
+    }
+
+    public function testSave()
+    {
+        $this->session->start();
+        $this->session->save();
+
+        $this->assertFalse($this->session->isStarted());
+    }
+
+    public function testGetId()
+    {
+        $this->assertEquals('', $this->session->getId());
+        $this->session->start();
+        $this->assertNotEquals('', $this->session->getId());
+    }
+
+    public function testGetFlashBag()
+    {
+        $this->assertInstanceOf('Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface', $this->session->getFlashBag());
+    }
+
+    public function testGetIterator()
+    {
+        $attributes = array('hello' => 'world', 'symfony' => 'rocks');
+        foreach ($attributes as $key => $val) {
+            $this->session->set($key, $val);
+        }
+
+        $i = 0;
+        foreach ($this->session as $key => $val) {
+            $this->assertEquals($attributes[$key], $val);
+            ++$i;
+        }
+
+        $this->assertEquals(count($attributes), $i);
+    }
+
+    public function testGetCount()
+    {
+        $this->session->set('hello', 'world');
+        $this->session->set('symfony', 'rocks');
+
+        $this->assertCount(2, $this->session);
+    }
+
+    public function testGetMeta()
+    {
+        $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\MetadataBag', $this->session->getMetadataBag());
+    }
+
+    public function testIsEmpty()
+    {
+        $this->assertTrue($this->session->isEmpty());
+
+        $this->session->set('hello', 'world');
+        $this->assertFalse($this->session->isEmpty());
+
+        $this->session->remove('hello');
+        $this->assertTrue($this->session->isEmpty());
+
+        $flash = $this->session->getFlashBag();
+        $flash->set('hello', 'world');
+        $this->assertFalse($this->session->isEmpty());
+
+        $flash->get('hello');
+        $this->assertTrue($this->session->isEmpty());
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..3ac081e3884c1f0cfa37ea8fa4e15766e818316f
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php
@@ -0,0 +1,61 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+
+/**
+ * @requires PHP 7.0
+ */
+class AbstractSessionHandlerTest extends TestCase
+{
+    private static $server;
+
+    public static function setUpBeforeClass()
+    {
+        $spec = array(
+            1 => array('file', '/dev/null', 'w'),
+            2 => array('file', '/dev/null', 'w'),
+        );
+        if (!self::$server = @proc_open('exec php -S localhost:8053', $spec, $pipes, __DIR__.'/Fixtures')) {
+            self::markTestSkipped('PHP server unable to start.');
+        }
+        sleep(1);
+    }
+
+    public static function tearDownAfterClass()
+    {
+        if (self::$server) {
+            proc_terminate(self::$server);
+            proc_close(self::$server);
+        }
+    }
+
+    /**
+     * @dataProvider provideSession
+     */
+    public function testSession($fixture)
+    {
+        $context = array('http' => array('header' => "Cookie: sid=123abc\r\n"));
+        $context = stream_context_create($context);
+        $result = file_get_contents(sprintf('http://localhost:8053/%s.php', $fixture), false, $context);
+
+        $this->assertStringEqualsFile(__DIR__.sprintf('/Fixtures/%s.expected', $fixture), $result);
+    }
+
+    public function provideSession()
+    {
+        foreach (glob(__DIR__.'/Fixtures/*.php') as $file) {
+            yield array(pathinfo($file, PATHINFO_FILENAME));
+        }
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/common.inc b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/common.inc
new file mode 100644
index 0000000000000000000000000000000000000000..7a064c7f3f06121d0eb554508edf4ff5740af35f
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/common.inc
@@ -0,0 +1,151 @@
+<?php
+
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\AbstractSessionHandler;
+
+$parent = __DIR__;
+while (!@file_exists($parent.'/vendor/autoload.php')) {
+    if (!@file_exists($parent)) {
+        // open_basedir restriction in effect
+        break;
+    }
+    if ($parent === dirname($parent)) {
+        echo "vendor/autoload.php not found\n";
+        exit(1);
+    }
+
+    $parent = dirname($parent);
+}
+
+require $parent.'/vendor/autoload.php';
+
+error_reporting(-1);
+ini_set('html_errors', 0);
+ini_set('display_errors', 1);
+ini_set('session.gc_probability', 0);
+ini_set('session.serialize_handler', 'php');
+ini_set('session.cookie_lifetime', 0);
+ini_set('session.cookie_domain', '');
+ini_set('session.cookie_secure', '');
+ini_set('session.cookie_httponly', '');
+ini_set('session.use_cookies', 1);
+ini_set('session.use_only_cookies', 1);
+ini_set('session.cache_expire', 180);
+ini_set('session.cookie_path', '/');
+ini_set('session.cookie_domain', '');
+ini_set('session.cookie_secure', 1);
+ini_set('session.cookie_httponly', 1);
+ini_set('session.use_strict_mode', 1);
+ini_set('session.lazy_write', 1);
+ini_set('session.name', 'sid');
+ini_set('session.save_path', __DIR__);
+ini_set('session.cache_limiter', '');
+
+header_remove('X-Powered-By');
+header('Content-Type: text/plain; charset=utf-8');
+
+register_shutdown_function(function () {
+    echo "\n";
+    session_write_close();
+    print_r(headers_list());
+    echo "shutdown\n";
+});
+ob_start();
+
+class TestSessionHandler extends AbstractSessionHandler
+{
+    private $data;
+
+    public function __construct($data = '')
+    {
+        $this->data = $data;
+    }
+
+    public function open($path, $name)
+    {
+        echo __FUNCTION__, "\n";
+
+        return parent::open($path, $name);
+    }
+
+    public function validateId($sessionId)
+    {
+        echo __FUNCTION__, "\n";
+
+        return parent::validateId($sessionId);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function read($sessionId)
+    {
+        echo __FUNCTION__, "\n";
+
+        return parent::read($sessionId);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function updateTimestamp($sessionId, $data)
+    {
+        echo __FUNCTION__, "\n";
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function write($sessionId, $data)
+    {
+        echo __FUNCTION__, "\n";
+
+        return parent::write($sessionId, $data);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function destroy($sessionId)
+    {
+        echo __FUNCTION__, "\n";
+
+        return parent::destroy($sessionId);
+    }
+
+    public function close()
+    {
+        echo __FUNCTION__, "\n";
+
+        return true;
+    }
+
+    public function gc($maxLifetime)
+    {
+        echo __FUNCTION__, "\n";
+
+        return true;
+    }
+
+    protected function doRead($sessionId)
+    {
+        echo __FUNCTION__.': ', $this->data, "\n";
+
+        return $this->data;
+    }
+
+    protected function doWrite($sessionId, $data)
+    {
+        echo __FUNCTION__.': ', $data, "\n";
+
+        return true;
+    }
+
+    protected function doDestroy($sessionId)
+    {
+        echo __FUNCTION__, "\n";
+
+        return true;
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected
new file mode 100644
index 0000000000000000000000000000000000000000..820371474075273ae0d7cca07fe5f9eae360273a
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected
@@ -0,0 +1,17 @@
+open
+validateId
+read
+doRead: abc|i:123;
+read
+
+write
+destroy
+doDestroy
+close
+Array
+(
+    [0] => Content-Type: text/plain; charset=utf-8
+    [1] => Cache-Control: max-age=10800, private, must-revalidate
+    [2] => Set-Cookie: sid=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0; path=/; secure; HttpOnly
+)
+shutdown
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.php
new file mode 100644
index 0000000000000000000000000000000000000000..3cfc1250adad16832dab14707a20d9551cc5df49
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.php
@@ -0,0 +1,8 @@
+<?php
+
+require __DIR__.'/common.inc';
+
+session_set_save_handler(new TestSessionHandler('abc|i:123;'), false);
+session_start();
+
+unset($_SESSION['abc']);
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/read_only.expected b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/read_only.expected
new file mode 100644
index 0000000000000000000000000000000000000000..587adaf158b1d83994b7f54b4581fc5ab495db1d
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/read_only.expected
@@ -0,0 +1,14 @@
+open
+validateId
+read
+doRead: abc|i:123;
+read
+123
+updateTimestamp
+close
+Array
+(
+    [0] => Content-Type: text/plain; charset=utf-8
+    [1] => Cache-Control: max-age=10800, private, must-revalidate
+)
+shutdown
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/read_only.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/read_only.php
new file mode 100644
index 0000000000000000000000000000000000000000..3e62fb9ecbbddedd9e452c3401d9569be0a50e79
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/read_only.php
@@ -0,0 +1,8 @@
+<?php
+
+require __DIR__.'/common.inc';
+
+session_set_save_handler(new TestSessionHandler('abc|i:123;'), false);
+session_start();
+
+echo $_SESSION['abc'];
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/regenerate.expected b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/regenerate.expected
new file mode 100644
index 0000000000000000000000000000000000000000..baa5f2f6f5cb04ab11d243b52b933d765fc1c466
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/regenerate.expected
@@ -0,0 +1,24 @@
+open
+validateId
+read
+doRead: abc|i:123;
+read
+destroy
+doDestroy
+close
+open
+validateId
+read
+doRead: abc|i:123;
+read
+
+write
+doWrite: abc|i:123;
+close
+Array
+(
+    [0] => Content-Type: text/plain; charset=utf-8
+    [1] => Cache-Control: max-age=10800, private, must-revalidate
+    [2] => Set-Cookie: sid=random_session_id; path=/; secure; HttpOnly
+)
+shutdown
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php
new file mode 100644
index 0000000000000000000000000000000000000000..a0f635c8712ecc6fb5dde521fac82ab7fe9d4785
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php
@@ -0,0 +1,10 @@
+<?php
+
+require __DIR__.'/common.inc';
+
+session_set_save_handler(new TestSessionHandler('abc|i:123;'), false);
+session_start();
+
+session_regenerate_id(true);
+
+ob_start(function ($buffer) { return str_replace(session_id(), 'random_session_id', $buffer); });
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/storage.expected b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/storage.expected
new file mode 100644
index 0000000000000000000000000000000000000000..4533a10a1f7cf99dd7af1b7fc22a2fe0fa1c746a
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/storage.expected
@@ -0,0 +1,20 @@
+open
+validateId
+read
+doRead: 
+read
+Array
+(
+    [0] => bar
+)
+$_SESSION is not empty
+write
+destroy
+close
+$_SESSION is not empty
+Array
+(
+    [0] => Content-Type: text/plain; charset=utf-8
+    [1] => Cache-Control: max-age=0, private, must-revalidate
+)
+shutdown
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/storage.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/storage.php
new file mode 100644
index 0000000000000000000000000000000000000000..96dca3c2c0006247dc10140e29b0b7d3774ae607
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/storage.php
@@ -0,0 +1,24 @@
+<?php
+
+require __DIR__.'/common.inc';
+
+use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
+use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
+
+$storage = new NativeSessionStorage();
+$storage->setSaveHandler(new TestSessionHandler());
+$flash = new FlashBag();
+$storage->registerBag($flash);
+$storage->start();
+
+$flash->add('foo', 'bar');
+
+print_r($flash->get('foo'));
+echo empty($_SESSION) ? '$_SESSION is empty' : '$_SESSION is not empty';
+echo "\n";
+
+$storage->save();
+
+echo empty($_SESSION) ? '$_SESSION is empty' : '$_SESSION is not empty';
+
+ob_start(function ($buffer) { return str_replace(session_id(), 'random_session_id', $buffer); });
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.expected b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.expected
new file mode 100644
index 0000000000000000000000000000000000000000..33da0a5be6e052b0eb3d9eb1fb1a4f1f68c77dd6
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.expected
@@ -0,0 +1,15 @@
+open
+validateId
+read
+doRead: abc|i:123;
+read
+
+updateTimestamp
+close
+Array
+(
+    [0] => Content-Type: text/plain; charset=utf-8
+    [1] => Cache-Control: max-age=10800, private, must-revalidate
+    [2] => Set-Cookie: abc=def
+)
+shutdown
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.php
new file mode 100644
index 0000000000000000000000000000000000000000..ffb5b20a3774efd711dbb2023e9677e0b0aae0b5
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.php
@@ -0,0 +1,8 @@
+<?php
+
+require __DIR__.'/common.inc';
+
+session_set_save_handler(new TestSessionHandler('abc|i:123;'), false);
+session_start();
+
+setcookie('abc', 'def');
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.expected b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.expected
new file mode 100644
index 0000000000000000000000000000000000000000..5de2d9e3904ed4d37b75edf4811aa3863662cbfe
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.expected
@@ -0,0 +1,24 @@
+open
+validateId
+read
+doRead: abc|i:123;
+read
+updateTimestamp
+close
+open
+validateId
+read
+doRead: abc|i:123;
+read
+
+write
+destroy
+doDestroy
+close
+Array
+(
+    [0] => Content-Type: text/plain; charset=utf-8
+    [1] => Cache-Control: max-age=10800, private, must-revalidate
+    [2] => Set-Cookie: abc=def
+)
+shutdown
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.php
new file mode 100644
index 0000000000000000000000000000000000000000..ec5119323b757f6f9f8ae8474e97f6b452756223
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.php
@@ -0,0 +1,13 @@
+<?php
+
+require __DIR__.'/common.inc';
+
+setcookie('abc', 'def');
+
+session_set_save_handler(new TestSessionHandler('abc|i:123;'), false);
+session_start();
+session_write_close();
+session_start();
+
+$_SESSION['abc'] = 234;
+unset($_SESSION['abc']);
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcacheSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcacheSessionHandlerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..dda43c805ba7d1869f597568a79801d97d824b64
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcacheSessionHandlerTest.php
@@ -0,0 +1,135 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcacheSessionHandler;
+
+/**
+ * @requires extension memcache
+ * @group time-sensitive
+ * @group legacy
+ */
+class MemcacheSessionHandlerTest extends TestCase
+{
+    const PREFIX = 'prefix_';
+    const TTL = 1000;
+
+    /**
+     * @var MemcacheSessionHandler
+     */
+    protected $storage;
+
+    protected $memcache;
+
+    protected function setUp()
+    {
+        if (defined('HHVM_VERSION')) {
+            $this->markTestSkipped('PHPUnit_MockObject cannot mock the Memcache class on HHVM. See https://github.com/sebastianbergmann/phpunit-mock-objects/pull/289');
+        }
+
+        parent::setUp();
+        $this->memcache = $this->getMockBuilder('Memcache')->getMock();
+        $this->storage = new MemcacheSessionHandler(
+            $this->memcache,
+            array('prefix' => self::PREFIX, 'expiretime' => self::TTL)
+        );
+    }
+
+    protected function tearDown()
+    {
+        $this->memcache = null;
+        $this->storage = null;
+        parent::tearDown();
+    }
+
+    public function testOpenSession()
+    {
+        $this->assertTrue($this->storage->open('', ''));
+    }
+
+    public function testCloseSession()
+    {
+        $this->assertTrue($this->storage->close());
+    }
+
+    public function testReadSession()
+    {
+        $this->memcache
+            ->expects($this->once())
+            ->method('get')
+            ->with(self::PREFIX.'id')
+        ;
+
+        $this->assertEquals('', $this->storage->read('id'));
+    }
+
+    public function testWriteSession()
+    {
+        $this->memcache
+            ->expects($this->once())
+            ->method('set')
+            ->with(self::PREFIX.'id', 'data', 0, $this->equalTo(time() + self::TTL, 2))
+            ->will($this->returnValue(true))
+        ;
+
+        $this->assertTrue($this->storage->write('id', 'data'));
+    }
+
+    public function testDestroySession()
+    {
+        $this->memcache
+            ->expects($this->once())
+            ->method('delete')
+            ->with(self::PREFIX.'id')
+            ->will($this->returnValue(true))
+        ;
+
+        $this->assertTrue($this->storage->destroy('id'));
+    }
+
+    public function testGcSession()
+    {
+        $this->assertTrue($this->storage->gc(123));
+    }
+
+    /**
+     * @dataProvider getOptionFixtures
+     */
+    public function testSupportedOptions($options, $supported)
+    {
+        try {
+            new MemcacheSessionHandler($this->memcache, $options);
+            $this->assertTrue($supported);
+        } catch (\InvalidArgumentException $e) {
+            $this->assertFalse($supported);
+        }
+    }
+
+    public function getOptionFixtures()
+    {
+        return array(
+            array(array('prefix' => 'session'), true),
+            array(array('expiretime' => 100), true),
+            array(array('prefix' => 'session', 'expiretime' => 200), true),
+            array(array('expiretime' => 100, 'foo' => 'bar'), false),
+        );
+    }
+
+    public function testGetConnection()
+    {
+        $method = new \ReflectionMethod($this->storage, 'getMemcache');
+        $method->setAccessible(true);
+
+        $this->assertInstanceOf('\Memcache', $method->invoke($this->storage));
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..2e7be359efcfff04427367f21d8d216a660b7882
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php
@@ -0,0 +1,139 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcachedSessionHandler;
+
+/**
+ * @requires extension memcached
+ * @group time-sensitive
+ */
+class MemcachedSessionHandlerTest extends TestCase
+{
+    const PREFIX = 'prefix_';
+    const TTL = 1000;
+
+    /**
+     * @var MemcachedSessionHandler
+     */
+    protected $storage;
+
+    protected $memcached;
+
+    protected function setUp()
+    {
+        if (defined('HHVM_VERSION')) {
+            $this->markTestSkipped('PHPUnit_MockObject cannot mock the Memcached class on HHVM. See https://github.com/sebastianbergmann/phpunit-mock-objects/pull/289');
+        }
+
+        parent::setUp();
+
+        if (version_compare(phpversion('memcached'), '2.2.0', '>=') && version_compare(phpversion('memcached'), '3.0.0b1', '<')) {
+            $this->markTestSkipped('Tests can only be run with memcached extension 2.1.0 or lower, or 3.0.0b1 or higher');
+        }
+
+        $this->memcached = $this->getMockBuilder('Memcached')->getMock();
+        $this->storage = new MemcachedSessionHandler(
+            $this->memcached,
+            array('prefix' => self::PREFIX, 'expiretime' => self::TTL)
+        );
+    }
+
+    protected function tearDown()
+    {
+        $this->memcached = null;
+        $this->storage = null;
+        parent::tearDown();
+    }
+
+    public function testOpenSession()
+    {
+        $this->assertTrue($this->storage->open('', ''));
+    }
+
+    public function testCloseSession()
+    {
+        $this->assertTrue($this->storage->close());
+    }
+
+    public function testReadSession()
+    {
+        $this->memcached
+            ->expects($this->once())
+            ->method('get')
+            ->with(self::PREFIX.'id')
+        ;
+
+        $this->assertEquals('', $this->storage->read('id'));
+    }
+
+    public function testWriteSession()
+    {
+        $this->memcached
+            ->expects($this->once())
+            ->method('set')
+            ->with(self::PREFIX.'id', 'data', $this->equalTo(time() + self::TTL, 2))
+            ->will($this->returnValue(true))
+        ;
+
+        $this->assertTrue($this->storage->write('id', 'data'));
+    }
+
+    public function testDestroySession()
+    {
+        $this->memcached
+            ->expects($this->once())
+            ->method('delete')
+            ->with(self::PREFIX.'id')
+            ->will($this->returnValue(true))
+        ;
+
+        $this->assertTrue($this->storage->destroy('id'));
+    }
+
+    public function testGcSession()
+    {
+        $this->assertTrue($this->storage->gc(123));
+    }
+
+    /**
+     * @dataProvider getOptionFixtures
+     */
+    public function testSupportedOptions($options, $supported)
+    {
+        try {
+            new MemcachedSessionHandler($this->memcached, $options);
+            $this->assertTrue($supported);
+        } catch (\InvalidArgumentException $e) {
+            $this->assertFalse($supported);
+        }
+    }
+
+    public function getOptionFixtures()
+    {
+        return array(
+            array(array('prefix' => 'session'), true),
+            array(array('expiretime' => 100), true),
+            array(array('prefix' => 'session', 'expiretime' => 200), true),
+            array(array('expiretime' => 100, 'foo' => 'bar'), false),
+        );
+    }
+
+    public function testGetConnection()
+    {
+        $method = new \ReflectionMethod($this->storage, 'getMemcached');
+        $method->setAccessible(true);
+
+        $this->assertInstanceOf('\Memcached', $method->invoke($this->storage));
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..da051096c8730c1866b1dd198f251bcd39530de4
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php
@@ -0,0 +1,333 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler;
+
+/**
+ * @author Markus Bachmann <markus.bachmann@bachi.biz>
+ * @group time-sensitive
+ * @group legacy
+ */
+class MongoDbSessionHandlerTest extends TestCase
+{
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject
+     */
+    private $mongo;
+    private $storage;
+    public $options;
+
+    protected function setUp()
+    {
+        parent::setUp();
+
+        if (extension_loaded('mongodb')) {
+            if (!class_exists('MongoDB\Client')) {
+                $this->markTestSkipped('The mongodb/mongodb package is required.');
+            }
+        } elseif (!extension_loaded('mongo')) {
+            $this->markTestSkipped('The Mongo or MongoDB extension is required.');
+        }
+
+        if (phpversion('mongodb')) {
+            $mongoClass = 'MongoDB\Client';
+        } else {
+            $mongoClass = version_compare(phpversion('mongo'), '1.3.0', '<') ? 'Mongo' : 'MongoClient';
+        }
+
+        $this->mongo = $this->getMockBuilder($mongoClass)
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $this->options = array(
+            'id_field' => '_id',
+            'data_field' => 'data',
+            'time_field' => 'time',
+            'expiry_field' => 'expires_at',
+            'database' => 'sf2-test',
+            'collection' => 'session-test',
+        );
+
+        $this->storage = new MongoDbSessionHandler($this->mongo, $this->options);
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     */
+    public function testConstructorShouldThrowExceptionForInvalidMongo()
+    {
+        new MongoDbSessionHandler(new \stdClass(), $this->options);
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     */
+    public function testConstructorShouldThrowExceptionForMissingOptions()
+    {
+        new MongoDbSessionHandler($this->mongo, array());
+    }
+
+    public function testOpenMethodAlwaysReturnTrue()
+    {
+        $this->assertTrue($this->storage->open('test', 'test'), 'The "open" method should always return true');
+    }
+
+    public function testCloseMethodAlwaysReturnTrue()
+    {
+        $this->assertTrue($this->storage->close(), 'The "close" method should always return true');
+    }
+
+    public function testRead()
+    {
+        $collection = $this->createMongoCollectionMock();
+
+        $this->mongo->expects($this->once())
+            ->method('selectCollection')
+            ->with($this->options['database'], $this->options['collection'])
+            ->will($this->returnValue($collection));
+
+        // defining the timeout before the actual method call
+        // allows to test for "greater than" values in the $criteria
+        $testTimeout = time() + 1;
+
+        $collection->expects($this->once())
+            ->method('findOne')
+            ->will($this->returnCallback(function ($criteria) use ($testTimeout) {
+                $this->assertArrayHasKey($this->options['id_field'], $criteria);
+                $this->assertEquals($criteria[$this->options['id_field']], 'foo');
+
+                $this->assertArrayHasKey($this->options['expiry_field'], $criteria);
+                $this->assertArrayHasKey('$gte', $criteria[$this->options['expiry_field']]);
+
+                if (phpversion('mongodb')) {
+                    $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $criteria[$this->options['expiry_field']]['$gte']);
+                    $this->assertGreaterThanOrEqual(round((string) $criteria[$this->options['expiry_field']]['$gte'] / 1000), $testTimeout);
+                } else {
+                    $this->assertInstanceOf('MongoDate', $criteria[$this->options['expiry_field']]['$gte']);
+                    $this->assertGreaterThanOrEqual($criteria[$this->options['expiry_field']]['$gte']->sec, $testTimeout);
+                }
+
+                $fields = array(
+                    $this->options['id_field'] => 'foo',
+                );
+
+                if (phpversion('mongodb')) {
+                    $fields[$this->options['data_field']] = new \MongoDB\BSON\Binary('bar', \MongoDB\BSON\Binary::TYPE_OLD_BINARY);
+                    $fields[$this->options['id_field']] = new \MongoDB\BSON\UTCDateTime(time() * 1000);
+                } else {
+                    $fields[$this->options['data_field']] = new \MongoBinData('bar', \MongoBinData::BYTE_ARRAY);
+                    $fields[$this->options['id_field']] = new \MongoDate();
+                }
+
+                return $fields;
+            }));
+
+        $this->assertEquals('bar', $this->storage->read('foo'));
+    }
+
+    public function testWrite()
+    {
+        $collection = $this->createMongoCollectionMock();
+
+        $this->mongo->expects($this->once())
+            ->method('selectCollection')
+            ->with($this->options['database'], $this->options['collection'])
+            ->will($this->returnValue($collection));
+
+        $data = array();
+
+        $methodName = phpversion('mongodb') ? 'updateOne' : 'update';
+
+        $collection->expects($this->once())
+            ->method($methodName)
+            ->will($this->returnCallback(function ($criteria, $updateData, $options) use (&$data) {
+                $this->assertEquals(array($this->options['id_field'] => 'foo'), $criteria);
+
+                if (phpversion('mongodb')) {
+                    $this->assertEquals(array('upsert' => true), $options);
+                } else {
+                    $this->assertEquals(array('upsert' => true, 'multiple' => false), $options);
+                }
+
+                $data = $updateData['$set'];
+            }));
+
+        $expectedExpiry = time() + (int) ini_get('session.gc_maxlifetime');
+        $this->assertTrue($this->storage->write('foo', 'bar'));
+
+        if (phpversion('mongodb')) {
+            $this->assertEquals('bar', $data[$this->options['data_field']]->getData());
+            $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['time_field']]);
+            $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['expiry_field']]);
+            $this->assertGreaterThanOrEqual($expectedExpiry, round((string) $data[$this->options['expiry_field']] / 1000));
+        } else {
+            $this->assertEquals('bar', $data[$this->options['data_field']]->bin);
+            $this->assertInstanceOf('MongoDate', $data[$this->options['time_field']]);
+            $this->assertInstanceOf('MongoDate', $data[$this->options['expiry_field']]);
+            $this->assertGreaterThanOrEqual($expectedExpiry, $data[$this->options['expiry_field']]->sec);
+        }
+    }
+
+    public function testWriteWhenUsingExpiresField()
+    {
+        $this->options = array(
+            'id_field' => '_id',
+            'data_field' => 'data',
+            'time_field' => 'time',
+            'database' => 'sf2-test',
+            'collection' => 'session-test',
+            'expiry_field' => 'expiresAt',
+        );
+
+        $this->storage = new MongoDbSessionHandler($this->mongo, $this->options);
+
+        $collection = $this->createMongoCollectionMock();
+
+        $this->mongo->expects($this->once())
+            ->method('selectCollection')
+            ->with($this->options['database'], $this->options['collection'])
+            ->will($this->returnValue($collection));
+
+        $data = array();
+
+        $methodName = phpversion('mongodb') ? 'updateOne' : 'update';
+
+        $collection->expects($this->once())
+            ->method($methodName)
+            ->will($this->returnCallback(function ($criteria, $updateData, $options) use (&$data) {
+                $this->assertEquals(array($this->options['id_field'] => 'foo'), $criteria);
+
+                if (phpversion('mongodb')) {
+                    $this->assertEquals(array('upsert' => true), $options);
+                } else {
+                    $this->assertEquals(array('upsert' => true, 'multiple' => false), $options);
+                }
+
+                $data = $updateData['$set'];
+            }));
+
+        $this->assertTrue($this->storage->write('foo', 'bar'));
+
+        if (phpversion('mongodb')) {
+            $this->assertEquals('bar', $data[$this->options['data_field']]->getData());
+            $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['time_field']]);
+            $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['expiry_field']]);
+        } else {
+            $this->assertEquals('bar', $data[$this->options['data_field']]->bin);
+            $this->assertInstanceOf('MongoDate', $data[$this->options['time_field']]);
+            $this->assertInstanceOf('MongoDate', $data[$this->options['expiry_field']]);
+        }
+    }
+
+    public function testReplaceSessionData()
+    {
+        $collection = $this->createMongoCollectionMock();
+
+        $this->mongo->expects($this->once())
+            ->method('selectCollection')
+            ->with($this->options['database'], $this->options['collection'])
+            ->will($this->returnValue($collection));
+
+        $data = array();
+
+        $methodName = phpversion('mongodb') ? 'updateOne' : 'update';
+
+        $collection->expects($this->exactly(2))
+            ->method($methodName)
+            ->will($this->returnCallback(function ($criteria, $updateData, $options) use (&$data) {
+                $data = $updateData;
+            }));
+
+        $this->storage->write('foo', 'bar');
+        $this->storage->write('foo', 'foobar');
+
+        if (phpversion('mongodb')) {
+            $this->assertEquals('foobar', $data['$set'][$this->options['data_field']]->getData());
+        } else {
+            $this->assertEquals('foobar', $data['$set'][$this->options['data_field']]->bin);
+        }
+    }
+
+    public function testDestroy()
+    {
+        $collection = $this->createMongoCollectionMock();
+
+        $this->mongo->expects($this->once())
+            ->method('selectCollection')
+            ->with($this->options['database'], $this->options['collection'])
+            ->will($this->returnValue($collection));
+
+        $methodName = phpversion('mongodb') ? 'deleteOne' : 'remove';
+
+        $collection->expects($this->once())
+            ->method($methodName)
+            ->with(array($this->options['id_field'] => 'foo'));
+
+        $this->assertTrue($this->storage->destroy('foo'));
+    }
+
+    public function testGc()
+    {
+        $collection = $this->createMongoCollectionMock();
+
+        $this->mongo->expects($this->once())
+            ->method('selectCollection')
+            ->with($this->options['database'], $this->options['collection'])
+            ->will($this->returnValue($collection));
+
+        $methodName = phpversion('mongodb') ? 'deleteMany' : 'remove';
+
+        $collection->expects($this->once())
+            ->method($methodName)
+            ->will($this->returnCallback(function ($criteria) {
+                if (phpversion('mongodb')) {
+                    $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $criteria[$this->options['expiry_field']]['$lt']);
+                    $this->assertGreaterThanOrEqual(time() - 1, round((string) $criteria[$this->options['expiry_field']]['$lt'] / 1000));
+                } else {
+                    $this->assertInstanceOf('MongoDate', $criteria[$this->options['expiry_field']]['$lt']);
+                    $this->assertGreaterThanOrEqual(time() - 1, $criteria[$this->options['expiry_field']]['$lt']->sec);
+                }
+            }));
+
+        $this->assertTrue($this->storage->gc(1));
+    }
+
+    public function testGetConnection()
+    {
+        $method = new \ReflectionMethod($this->storage, 'getMongo');
+        $method->setAccessible(true);
+
+        if (phpversion('mongodb')) {
+            $mongoClass = 'MongoDB\Client';
+        } else {
+            $mongoClass = version_compare(phpversion('mongo'), '1.3.0', '<') ? 'Mongo' : 'MongoClient';
+        }
+
+        $this->assertInstanceOf($mongoClass, $method->invoke($this->storage));
+    }
+
+    private function createMongoCollectionMock()
+    {
+        $collectionClass = 'MongoCollection';
+        if (phpversion('mongodb')) {
+            $collectionClass = 'MongoDB\Collection';
+        }
+
+        $collection = $this->getMockBuilder($collectionClass)
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        return $collection;
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..a6264e51d2a72df84268d2a63ac5485f8f5f2bdb
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php
@@ -0,0 +1,77 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler;
+use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
+
+/**
+ * Test class for NativeFileSessionHandler.
+ *
+ * @author Drak <drak@zikula.org>
+ *
+ * @runTestsInSeparateProcesses
+ * @preserveGlobalState disabled
+ */
+class NativeFileSessionHandlerTest extends TestCase
+{
+    public function testConstruct()
+    {
+        $storage = new NativeSessionStorage(array('name' => 'TESTING'), new NativeFileSessionHandler(sys_get_temp_dir()));
+
+        $this->assertEquals('files', $storage->getSaveHandler()->getSaveHandlerName());
+        $this->assertEquals('user', ini_get('session.save_handler'));
+
+        $this->assertEquals(sys_get_temp_dir(), ini_get('session.save_path'));
+        $this->assertEquals('TESTING', ini_get('session.name'));
+    }
+
+    /**
+     * @dataProvider savePathDataProvider
+     */
+    public function testConstructSavePath($savePath, $expectedSavePath, $path)
+    {
+        $handler = new NativeFileSessionHandler($savePath);
+        $this->assertEquals($expectedSavePath, ini_get('session.save_path'));
+        $this->assertTrue(is_dir(realpath($path)));
+
+        rmdir($path);
+    }
+
+    public function savePathDataProvider()
+    {
+        $base = sys_get_temp_dir();
+
+        return array(
+            array("$base/foo", "$base/foo", "$base/foo"),
+            array("5;$base/foo", "5;$base/foo", "$base/foo"),
+            array("5;0600;$base/foo", "5;0600;$base/foo", "$base/foo"),
+        );
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     */
+    public function testConstructException()
+    {
+        $handler = new NativeFileSessionHandler('something;invalid;with;too-many-args');
+    }
+
+    public function testConstructDefault()
+    {
+        $path = ini_get('session.save_path');
+        $storage = new NativeSessionStorage(array('name' => 'TESTING'), new NativeFileSessionHandler());
+
+        $this->assertEquals($path, ini_get('session.save_path'));
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeSessionHandlerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..4a9fb600d2ddd273d95c6add23e1594ab11d99c6
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeSessionHandlerTest.php
@@ -0,0 +1,38 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler;
+
+/**
+ * Test class for NativeSessionHandler.
+ *
+ * @author Drak <drak@zikula.org>
+ *
+ * @runTestsInSeparateProcesses
+ * @preserveGlobalState disabled
+ * @group legacy
+ */
+class NativeSessionHandlerTest extends TestCase
+{
+    /**
+     * @expectedDeprecation The Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler class is deprecated since Symfony 3.4 and will be removed in 4.0. Use the \SessionHandler class instead.
+     */
+    public function testConstruct()
+    {
+        $handler = new NativeSessionHandler();
+
+        $this->assertInstanceOf('SessionHandler', $handler);
+        $this->assertTrue($handler instanceof NativeSessionHandler);
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..718fd0f830e492d68e758acdf4d33b0b09a12df5
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php
@@ -0,0 +1,59 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler;
+use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
+use Symfony\Component\HttpFoundation\Session\Session;
+
+/**
+ * Test class for NullSessionHandler.
+ *
+ * @author Drak <drak@zikula.org>
+ *
+ * @runTestsInSeparateProcesses
+ * @preserveGlobalState disabled
+ */
+class NullSessionHandlerTest extends TestCase
+{
+    public function testSaveHandlers()
+    {
+        $storage = $this->getStorage();
+        $this->assertEquals('user', ini_get('session.save_handler'));
+    }
+
+    public function testSession()
+    {
+        session_id('nullsessionstorage');
+        $storage = $this->getStorage();
+        $session = new Session($storage);
+        $this->assertNull($session->get('something'));
+        $session->set('something', 'unique');
+        $this->assertEquals('unique', $session->get('something'));
+    }
+
+    public function testNothingIsPersisted()
+    {
+        session_id('nullsessionstorage');
+        $storage = $this->getStorage();
+        $session = new Session($storage);
+        $session->start();
+        $this->assertEquals('nullsessionstorage', $session->getId());
+        $this->assertNull($session->get('something'));
+    }
+
+    public function getStorage()
+    {
+        return new NativeSessionStorage(array(), new NullSessionHandler());
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..0a0e449051a732a3af084075976f2546a27f986f
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php
@@ -0,0 +1,411 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
+
+/**
+ * @requires extension pdo_sqlite
+ * @group time-sensitive
+ */
+class PdoSessionHandlerTest extends TestCase
+{
+    private $dbFile;
+
+    protected function tearDown()
+    {
+        // make sure the temporary database file is deleted when it has been created (even when a test fails)
+        if ($this->dbFile) {
+            @unlink($this->dbFile);
+        }
+        parent::tearDown();
+    }
+
+    protected function getPersistentSqliteDsn()
+    {
+        $this->dbFile = tempnam(sys_get_temp_dir(), 'sf2_sqlite_sessions');
+
+        return 'sqlite:'.$this->dbFile;
+    }
+
+    protected function getMemorySqlitePdo()
+    {
+        $pdo = new \PDO('sqlite::memory:');
+        $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
+        $storage = new PdoSessionHandler($pdo);
+        $storage->createTable();
+
+        return $pdo;
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     */
+    public function testWrongPdoErrMode()
+    {
+        $pdo = $this->getMemorySqlitePdo();
+        $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_SILENT);
+
+        $storage = new PdoSessionHandler($pdo);
+    }
+
+    /**
+     * @expectedException \RuntimeException
+     */
+    public function testInexistentTable()
+    {
+        $storage = new PdoSessionHandler($this->getMemorySqlitePdo(), array('db_table' => 'inexistent_table'));
+        $storage->open('', 'sid');
+        $storage->read('id');
+        $storage->write('id', 'data');
+        $storage->close();
+    }
+
+    /**
+     * @expectedException \RuntimeException
+     */
+    public function testCreateTableTwice()
+    {
+        $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
+        $storage->createTable();
+    }
+
+    public function testWithLazyDsnConnection()
+    {
+        $dsn = $this->getPersistentSqliteDsn();
+
+        $storage = new PdoSessionHandler($dsn);
+        $storage->createTable();
+        $storage->open('', 'sid');
+        $data = $storage->read('id');
+        $storage->write('id', 'data');
+        $storage->close();
+        $this->assertSame('', $data, 'New session returns empty string data');
+
+        $storage->open('', 'sid');
+        $data = $storage->read('id');
+        $storage->close();
+        $this->assertSame('data', $data, 'Written value can be read back correctly');
+    }
+
+    public function testWithLazySavePathConnection()
+    {
+        $dsn = $this->getPersistentSqliteDsn();
+
+        // Open is called with what ini_set('session.save_path', $dsn) would mean
+        $storage = new PdoSessionHandler(null);
+        $storage->open($dsn, 'sid');
+        $storage->createTable();
+        $data = $storage->read('id');
+        $storage->write('id', 'data');
+        $storage->close();
+        $this->assertSame('', $data, 'New session returns empty string data');
+
+        $storage->open($dsn, 'sid');
+        $data = $storage->read('id');
+        $storage->close();
+        $this->assertSame('data', $data, 'Written value can be read back correctly');
+    }
+
+    public function testReadWriteReadWithNullByte()
+    {
+        $sessionData = 'da'."\0".'ta';
+
+        $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
+        $storage->open('', 'sid');
+        $readData = $storage->read('id');
+        $storage->write('id', $sessionData);
+        $storage->close();
+        $this->assertSame('', $readData, 'New session returns empty string data');
+
+        $storage->open('', 'sid');
+        $readData = $storage->read('id');
+        $storage->close();
+        $this->assertSame($sessionData, $readData, 'Written value can be read back correctly');
+    }
+
+    public function testReadConvertsStreamToString()
+    {
+        if (defined('HHVM_VERSION')) {
+            $this->markTestSkipped('PHPUnit_MockObject cannot mock the PDOStatement class on HHVM. See https://github.com/sebastianbergmann/phpunit-mock-objects/pull/289');
+        }
+
+        $pdo = new MockPdo('pgsql');
+        $pdo->prepareResult = $this->getMockBuilder('PDOStatement')->getMock();
+
+        $content = 'foobar';
+        $stream = $this->createStream($content);
+
+        $pdo->prepareResult->expects($this->once())->method('fetchAll')
+            ->will($this->returnValue(array(array($stream, 42, time()))));
+
+        $storage = new PdoSessionHandler($pdo);
+        $result = $storage->read('foo');
+
+        $this->assertSame($content, $result);
+    }
+
+    public function testReadLockedConvertsStreamToString()
+    {
+        if (defined('HHVM_VERSION')) {
+            $this->markTestSkipped('PHPUnit_MockObject cannot mock the PDOStatement class on HHVM. See https://github.com/sebastianbergmann/phpunit-mock-objects/pull/289');
+        }
+        if (ini_get('session.use_strict_mode')) {
+            $this->markTestSkipped('Strict mode needs no locking for new sessions.');
+        }
+
+        $pdo = new MockPdo('pgsql');
+        $selectStmt = $this->getMockBuilder('PDOStatement')->getMock();
+        $insertStmt = $this->getMockBuilder('PDOStatement')->getMock();
+
+        $pdo->prepareResult = function ($statement) use ($selectStmt, $insertStmt) {
+            return 0 === strpos($statement, 'INSERT') ? $insertStmt : $selectStmt;
+        };
+
+        $content = 'foobar';
+        $stream = $this->createStream($content);
+        $exception = null;
+
+        $selectStmt->expects($this->atLeast(2))->method('fetchAll')
+            ->will($this->returnCallback(function () use (&$exception, $stream) {
+                return $exception ? array(array($stream, 42, time())) : array();
+            }));
+
+        $insertStmt->expects($this->once())->method('execute')
+            ->will($this->returnCallback(function () use (&$exception) {
+                throw $exception = new \PDOException('', '23');
+            }));
+
+        $storage = new PdoSessionHandler($pdo);
+        $result = $storage->read('foo');
+
+        $this->assertSame($content, $result);
+    }
+
+    public function testReadingRequiresExactlySameId()
+    {
+        $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
+        $storage->open('', 'sid');
+        $storage->write('id', 'data');
+        $storage->write('test', 'data');
+        $storage->write('space ', 'data');
+        $storage->close();
+
+        $storage->open('', 'sid');
+        $readDataCaseSensitive = $storage->read('ID');
+        $readDataNoCharFolding = $storage->read('tést');
+        $readDataKeepSpace = $storage->read('space ');
+        $readDataExtraSpace = $storage->read('space  ');
+        $storage->close();
+
+        $this->assertSame('', $readDataCaseSensitive, 'Retrieval by ID should be case-sensitive (collation setting)');
+        $this->assertSame('', $readDataNoCharFolding, 'Retrieval by ID should not do character folding (collation setting)');
+        $this->assertSame('data', $readDataKeepSpace, 'Retrieval by ID requires spaces as-is');
+        $this->assertSame('', $readDataExtraSpace, 'Retrieval by ID requires spaces as-is');
+    }
+
+    /**
+     * Simulates session_regenerate_id(true) which will require an INSERT or UPDATE (replace).
+     */
+    public function testWriteDifferentSessionIdThanRead()
+    {
+        $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
+        $storage->open('', 'sid');
+        $storage->read('id');
+        $storage->destroy('id');
+        $storage->write('new_id', 'data_of_new_session_id');
+        $storage->close();
+
+        $storage->open('', 'sid');
+        $data = $storage->read('new_id');
+        $storage->close();
+
+        $this->assertSame('data_of_new_session_id', $data, 'Data of regenerated session id is available');
+    }
+
+    public function testWrongUsageStillWorks()
+    {
+        // wrong method sequence that should no happen, but still works
+        $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
+        $storage->write('id', 'data');
+        $storage->write('other_id', 'other_data');
+        $storage->destroy('inexistent');
+        $storage->open('', 'sid');
+        $data = $storage->read('id');
+        $otherData = $storage->read('other_id');
+        $storage->close();
+
+        $this->assertSame('data', $data);
+        $this->assertSame('other_data', $otherData);
+    }
+
+    public function testSessionDestroy()
+    {
+        $pdo = $this->getMemorySqlitePdo();
+        $storage = new PdoSessionHandler($pdo);
+
+        $storage->open('', 'sid');
+        $storage->read('id');
+        $storage->write('id', 'data');
+        $storage->close();
+        $this->assertEquals(1, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn());
+
+        $storage->open('', 'sid');
+        $storage->read('id');
+        $storage->destroy('id');
+        $storage->close();
+        $this->assertEquals(0, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn());
+
+        $storage->open('', 'sid');
+        $data = $storage->read('id');
+        $storage->close();
+        $this->assertSame('', $data, 'Destroyed session returns empty string');
+    }
+
+    /**
+     * @runInSeparateProcess
+     */
+    public function testSessionGC()
+    {
+        $previousLifeTime = ini_set('session.gc_maxlifetime', 1000);
+        $pdo = $this->getMemorySqlitePdo();
+        $storage = new PdoSessionHandler($pdo);
+
+        $storage->open('', 'sid');
+        $storage->read('id');
+        $storage->write('id', 'data');
+        $storage->close();
+
+        $storage->open('', 'sid');
+        $storage->read('gc_id');
+        ini_set('session.gc_maxlifetime', -1); // test that you can set lifetime of a session after it has been read
+        $storage->write('gc_id', 'data');
+        $storage->close();
+        $this->assertEquals(2, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn(), 'No session pruned because gc not called');
+
+        $storage->open('', 'sid');
+        $data = $storage->read('gc_id');
+        $storage->gc(-1);
+        $storage->close();
+
+        ini_set('session.gc_maxlifetime', $previousLifeTime);
+
+        $this->assertSame('', $data, 'Session already considered garbage, so not returning data even if it is not pruned yet');
+        $this->assertEquals(1, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn(), 'Expired session is pruned');
+    }
+
+    public function testGetConnection()
+    {
+        $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
+
+        $method = new \ReflectionMethod($storage, 'getConnection');
+        $method->setAccessible(true);
+
+        $this->assertInstanceOf('\PDO', $method->invoke($storage));
+    }
+
+    public function testGetConnectionConnectsIfNeeded()
+    {
+        $storage = new PdoSessionHandler('sqlite::memory:');
+
+        $method = new \ReflectionMethod($storage, 'getConnection');
+        $method->setAccessible(true);
+
+        $this->assertInstanceOf('\PDO', $method->invoke($storage));
+    }
+
+    /**
+     * @dataProvider provideUrlDsnPairs
+     */
+    public function testUrlDsn($url, $expectedDsn, $expectedUser = null, $expectedPassword = null)
+    {
+        $storage = new PdoSessionHandler($url);
+
+        $this->assertAttributeEquals($expectedDsn, 'dsn', $storage);
+
+        if (null !== $expectedUser) {
+            $this->assertAttributeEquals($expectedUser, 'username', $storage);
+        }
+
+        if (null !== $expectedPassword) {
+            $this->assertAttributeEquals($expectedPassword, 'password', $storage);
+        }
+    }
+
+    public function provideUrlDsnPairs()
+    {
+        yield array('mysql://localhost/test', 'mysql:host=localhost;dbname=test;');
+        yield array('mysql://localhost:56/test', 'mysql:host=localhost;port=56;dbname=test;');
+        yield array('mysql2://root:pwd@localhost/test', 'mysql:host=localhost;dbname=test;', 'root', 'pwd');
+        yield array('postgres://localhost/test', 'pgsql:host=localhost;dbname=test;');
+        yield array('postgresql://localhost:5634/test', 'pgsql:host=localhost;port=5634;dbname=test;');
+        yield array('postgres://root:pwd@localhost/test', 'pgsql:host=localhost;dbname=test;', 'root', 'pwd');
+        yield 'sqlite relative path' => array('sqlite://localhost/tmp/test', 'sqlite:tmp/test');
+        yield 'sqlite absolute path' => array('sqlite://localhost//tmp/test', 'sqlite:/tmp/test');
+        yield 'sqlite relative path without host' => array('sqlite:///tmp/test', 'sqlite:tmp/test');
+        yield 'sqlite absolute path without host' => array('sqlite3:////tmp/test', 'sqlite:/tmp/test');
+        yield array('sqlite://localhost/:memory:', 'sqlite::memory:');
+        yield array('mssql://localhost/test', 'sqlsrv:server=localhost;Database=test');
+        yield array('mssql://localhost:56/test', 'sqlsrv:server=localhost,56;Database=test');
+    }
+
+    private function createStream($content)
+    {
+        $stream = tmpfile();
+        fwrite($stream, $content);
+        fseek($stream, 0);
+
+        return $stream;
+    }
+}
+
+class MockPdo extends \PDO
+{
+    public $prepareResult;
+    private $driverName;
+    private $errorMode;
+
+    public function __construct($driverName = null, $errorMode = null)
+    {
+        $this->driverName = $driverName;
+        $this->errorMode = null !== $errorMode ?: \PDO::ERRMODE_EXCEPTION;
+    }
+
+    public function getAttribute($attribute)
+    {
+        if (\PDO::ATTR_ERRMODE === $attribute) {
+            return $this->errorMode;
+        }
+
+        if (\PDO::ATTR_DRIVER_NAME === $attribute) {
+            return $this->driverName;
+        }
+
+        return parent::getAttribute($attribute);
+    }
+
+    public function prepare($statement, $driverOptions = array())
+    {
+        return is_callable($this->prepareResult)
+            ? call_user_func($this->prepareResult, $statement, $driverOptions)
+            : $this->prepareResult;
+    }
+
+    public function beginTransaction()
+    {
+    }
+
+    public function rollBack()
+    {
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/StrictSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/StrictSessionHandlerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..b02c41ae898663f721cc439e2c1bdb2f4989539a
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/StrictSessionHandlerTest.php
@@ -0,0 +1,189 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\AbstractSessionHandler;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler;
+
+class StrictSessionHandlerTest extends TestCase
+{
+    public function testOpen()
+    {
+        $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+        $handler->expects($this->once())->method('open')
+            ->with('path', 'name')->willReturn(true);
+        $proxy = new StrictSessionHandler($handler);
+
+        $this->assertInstanceOf('SessionUpdateTimestampHandlerInterface', $proxy);
+        $this->assertInstanceOf(AbstractSessionHandler::class, $proxy);
+        $this->assertTrue($proxy->open('path', 'name'));
+    }
+
+    public function testCloseSession()
+    {
+        $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+        $handler->expects($this->once())->method('close')
+            ->willReturn(true);
+        $proxy = new StrictSessionHandler($handler);
+
+        $this->assertTrue($proxy->close());
+    }
+
+    public function testValidateIdOK()
+    {
+        $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+        $handler->expects($this->once())->method('read')
+            ->with('id')->willReturn('data');
+        $proxy = new StrictSessionHandler($handler);
+
+        $this->assertTrue($proxy->validateId('id'));
+    }
+
+    public function testValidateIdKO()
+    {
+        $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+        $handler->expects($this->once())->method('read')
+            ->with('id')->willReturn('');
+        $proxy = new StrictSessionHandler($handler);
+
+        $this->assertFalse($proxy->validateId('id'));
+    }
+
+    public function testRead()
+    {
+        $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+        $handler->expects($this->once())->method('read')
+            ->with('id')->willReturn('data');
+        $proxy = new StrictSessionHandler($handler);
+
+        $this->assertSame('data', $proxy->read('id'));
+    }
+
+    public function testReadWithValidateIdOK()
+    {
+        $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+        $handler->expects($this->once())->method('read')
+            ->with('id')->willReturn('data');
+        $proxy = new StrictSessionHandler($handler);
+
+        $this->assertTrue($proxy->validateId('id'));
+        $this->assertSame('data', $proxy->read('id'));
+    }
+
+    public function testReadWithValidateIdMismatch()
+    {
+        $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+        $handler->expects($this->exactly(2))->method('read')
+            ->withConsecutive(array('id1'), array('id2'))
+            ->will($this->onConsecutiveCalls('data1', 'data2'));
+        $proxy = new StrictSessionHandler($handler);
+
+        $this->assertTrue($proxy->validateId('id1'));
+        $this->assertSame('data2', $proxy->read('id2'));
+    }
+
+    public function testUpdateTimestamp()
+    {
+        $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+        $handler->expects($this->once())->method('write')
+            ->with('id', 'data')->willReturn(true);
+        $proxy = new StrictSessionHandler($handler);
+
+        $this->assertTrue($proxy->updateTimestamp('id', 'data'));
+    }
+
+    public function testWrite()
+    {
+        $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+        $handler->expects($this->once())->method('write')
+            ->with('id', 'data')->willReturn(true);
+        $proxy = new StrictSessionHandler($handler);
+
+        $this->assertTrue($proxy->write('id', 'data'));
+    }
+
+    public function testWriteEmptyNewSession()
+    {
+        $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+        $handler->expects($this->once())->method('read')
+            ->with('id')->willReturn('');
+        $handler->expects($this->never())->method('write');
+        $handler->expects($this->once())->method('destroy')->willReturn(true);
+        $proxy = new StrictSessionHandler($handler);
+
+        $this->assertFalse($proxy->validateId('id'));
+        $this->assertSame('', $proxy->read('id'));
+        $this->assertTrue($proxy->write('id', ''));
+    }
+
+    public function testWriteEmptyExistingSession()
+    {
+        $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+        $handler->expects($this->once())->method('read')
+            ->with('id')->willReturn('data');
+        $handler->expects($this->never())->method('write');
+        $handler->expects($this->once())->method('destroy')->willReturn(true);
+        $proxy = new StrictSessionHandler($handler);
+
+        $this->assertSame('data', $proxy->read('id'));
+        $this->assertTrue($proxy->write('id', ''));
+    }
+
+    public function testDestroy()
+    {
+        $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+        $handler->expects($this->once())->method('destroy')
+            ->with('id')->willReturn(true);
+        $proxy = new StrictSessionHandler($handler);
+
+        $this->assertTrue($proxy->destroy('id'));
+    }
+
+    public function testDestroyNewSession()
+    {
+        $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+        $handler->expects($this->once())->method('read')
+            ->with('id')->willReturn('');
+        $handler->expects($this->once())->method('destroy')->willReturn(true);
+        $proxy = new StrictSessionHandler($handler);
+
+        $this->assertSame('', $proxy->read('id'));
+        $this->assertTrue($proxy->destroy('id'));
+    }
+
+    public function testDestroyNonEmptyNewSession()
+    {
+        $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+        $handler->expects($this->once())->method('read')
+            ->with('id')->willReturn('');
+        $handler->expects($this->once())->method('write')
+            ->with('id', 'data')->willReturn(true);
+        $handler->expects($this->once())->method('destroy')
+            ->with('id')->willReturn(true);
+        $proxy = new StrictSessionHandler($handler);
+
+        $this->assertSame('', $proxy->read('id'));
+        $this->assertTrue($proxy->write('id', 'data'));
+        $this->assertTrue($proxy->destroy('id'));
+    }
+
+    public function testGc()
+    {
+        $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+        $handler->expects($this->once())->method('gc')
+            ->with(123)->willReturn(true);
+        $proxy = new StrictSessionHandler($handler);
+
+        $this->assertTrue($proxy->gc(123));
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..898a7d11a5ae266de7d114af5e49b2ce0b3b5f3f
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php
@@ -0,0 +1,97 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler;
+
+/**
+ * @author Adrien Brault <adrien.brault@gmail.com>
+ *
+ * @group legacy
+ */
+class WriteCheckSessionHandlerTest extends TestCase
+{
+    public function test()
+    {
+        $wrappedSessionHandlerMock = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+        $writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock);
+
+        $wrappedSessionHandlerMock
+            ->expects($this->once())
+            ->method('close')
+            ->with()
+            ->will($this->returnValue(true))
+        ;
+
+        $this->assertTrue($writeCheckSessionHandler->close());
+    }
+
+    public function testWrite()
+    {
+        $wrappedSessionHandlerMock = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+        $writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock);
+
+        $wrappedSessionHandlerMock
+            ->expects($this->once())
+            ->method('write')
+            ->with('foo', 'bar')
+            ->will($this->returnValue(true))
+        ;
+
+        $this->assertTrue($writeCheckSessionHandler->write('foo', 'bar'));
+    }
+
+    public function testSkippedWrite()
+    {
+        $wrappedSessionHandlerMock = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+        $writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock);
+
+        $wrappedSessionHandlerMock
+            ->expects($this->once())
+            ->method('read')
+            ->with('foo')
+            ->will($this->returnValue('bar'))
+        ;
+
+        $wrappedSessionHandlerMock
+            ->expects($this->never())
+            ->method('write')
+        ;
+
+        $this->assertEquals('bar', $writeCheckSessionHandler->read('foo'));
+        $this->assertTrue($writeCheckSessionHandler->write('foo', 'bar'));
+    }
+
+    public function testNonSkippedWrite()
+    {
+        $wrappedSessionHandlerMock = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+        $writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock);
+
+        $wrappedSessionHandlerMock
+            ->expects($this->once())
+            ->method('read')
+            ->with('foo')
+            ->will($this->returnValue('bar'))
+        ;
+
+        $wrappedSessionHandlerMock
+            ->expects($this->once())
+            ->method('write')
+            ->with('foo', 'baZZZ')
+            ->will($this->returnValue(true))
+        ;
+
+        $this->assertEquals('bar', $writeCheckSessionHandler->read('foo'));
+        $this->assertTrue($writeCheckSessionHandler->write('foo', 'baZZZ'));
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/MetadataBagTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/MetadataBagTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..69cf6163c16f3030f8ea0f5d83745d6fa926c55d
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/MetadataBagTest.php
@@ -0,0 +1,139 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag;
+
+/**
+ * Test class for MetadataBag.
+ *
+ * @group time-sensitive
+ */
+class MetadataBagTest extends TestCase
+{
+    /**
+     * @var MetadataBag
+     */
+    protected $bag;
+
+    protected $array = array();
+
+    protected function setUp()
+    {
+        parent::setUp();
+        $this->bag = new MetadataBag();
+        $this->array = array(MetadataBag::CREATED => 1234567, MetadataBag::UPDATED => 12345678, MetadataBag::LIFETIME => 0);
+        $this->bag->initialize($this->array);
+    }
+
+    protected function tearDown()
+    {
+        $this->array = array();
+        $this->bag = null;
+        parent::tearDown();
+    }
+
+    public function testInitialize()
+    {
+        $sessionMetadata = array();
+
+        $bag1 = new MetadataBag();
+        $bag1->initialize($sessionMetadata);
+        $this->assertGreaterThanOrEqual(time(), $bag1->getCreated());
+        $this->assertEquals($bag1->getCreated(), $bag1->getLastUsed());
+
+        sleep(1);
+        $bag2 = new MetadataBag();
+        $bag2->initialize($sessionMetadata);
+        $this->assertEquals($bag1->getCreated(), $bag2->getCreated());
+        $this->assertEquals($bag1->getLastUsed(), $bag2->getLastUsed());
+        $this->assertEquals($bag2->getCreated(), $bag2->getLastUsed());
+
+        sleep(1);
+        $bag3 = new MetadataBag();
+        $bag3->initialize($sessionMetadata);
+        $this->assertEquals($bag1->getCreated(), $bag3->getCreated());
+        $this->assertGreaterThan($bag2->getLastUsed(), $bag3->getLastUsed());
+        $this->assertNotEquals($bag3->getCreated(), $bag3->getLastUsed());
+    }
+
+    public function testGetSetName()
+    {
+        $this->assertEquals('__metadata', $this->bag->getName());
+        $this->bag->setName('foo');
+        $this->assertEquals('foo', $this->bag->getName());
+    }
+
+    public function testGetStorageKey()
+    {
+        $this->assertEquals('_sf2_meta', $this->bag->getStorageKey());
+    }
+
+    public function testGetLifetime()
+    {
+        $bag = new MetadataBag();
+        $array = array(MetadataBag::CREATED => 1234567, MetadataBag::UPDATED => 12345678, MetadataBag::LIFETIME => 1000);
+        $bag->initialize($array);
+        $this->assertEquals(1000, $bag->getLifetime());
+    }
+
+    public function testGetCreated()
+    {
+        $this->assertEquals(1234567, $this->bag->getCreated());
+    }
+
+    public function testGetLastUsed()
+    {
+        $this->assertLessThanOrEqual(time(), $this->bag->getLastUsed());
+    }
+
+    public function testClear()
+    {
+        $this->bag->clear();
+
+        // the clear method has no side effects, we just want to ensure it doesn't trigger any exceptions
+        $this->addToAssertionCount(1);
+    }
+
+    public function testSkipLastUsedUpdate()
+    {
+        $bag = new MetadataBag('', 30);
+        $timeStamp = time();
+
+        $created = $timeStamp - 15;
+        $sessionMetadata = array(
+            MetadataBag::CREATED => $created,
+            MetadataBag::UPDATED => $created,
+            MetadataBag::LIFETIME => 1000,
+        );
+        $bag->initialize($sessionMetadata);
+
+        $this->assertEquals($created, $sessionMetadata[MetadataBag::UPDATED]);
+    }
+
+    public function testDoesNotSkipLastUsedUpdate()
+    {
+        $bag = new MetadataBag('', 30);
+        $timeStamp = time();
+
+        $created = $timeStamp - 45;
+        $sessionMetadata = array(
+            MetadataBag::CREATED => $created,
+            MetadataBag::UPDATED => $created,
+            MetadataBag::LIFETIME => 1000,
+        );
+        $bag->initialize($sessionMetadata);
+
+        $this->assertEquals($timeStamp, $sessionMetadata[MetadataBag::UPDATED]);
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/MockArraySessionStorageTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/MockArraySessionStorageTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..82df5543eb540265f7e1e6eba09de94d2155fba5
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/MockArraySessionStorageTest.php
@@ -0,0 +1,131 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
+use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
+use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
+
+/**
+ * Test class for MockArraySessionStorage.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class MockArraySessionStorageTest extends TestCase
+{
+    /**
+     * @var MockArraySessionStorage
+     */
+    private $storage;
+
+    /**
+     * @var AttributeBag
+     */
+    private $attributes;
+
+    /**
+     * @var FlashBag
+     */
+    private $flashes;
+
+    private $data;
+
+    protected function setUp()
+    {
+        $this->attributes = new AttributeBag();
+        $this->flashes = new FlashBag();
+
+        $this->data = array(
+            $this->attributes->getStorageKey() => array('foo' => 'bar'),
+            $this->flashes->getStorageKey() => array('notice' => 'hello'),
+            );
+
+        $this->storage = new MockArraySessionStorage();
+        $this->storage->registerBag($this->flashes);
+        $this->storage->registerBag($this->attributes);
+        $this->storage->setSessionData($this->data);
+    }
+
+    protected function tearDown()
+    {
+        $this->data = null;
+        $this->flashes = null;
+        $this->attributes = null;
+        $this->storage = null;
+    }
+
+    public function testStart()
+    {
+        $this->assertEquals('', $this->storage->getId());
+        $this->storage->start();
+        $id = $this->storage->getId();
+        $this->assertNotEquals('', $id);
+        $this->storage->start();
+        $this->assertEquals($id, $this->storage->getId());
+    }
+
+    public function testRegenerate()
+    {
+        $this->storage->start();
+        $id = $this->storage->getId();
+        $this->storage->regenerate();
+        $this->assertNotEquals($id, $this->storage->getId());
+        $this->assertEquals(array('foo' => 'bar'), $this->storage->getBag('attributes')->all());
+        $this->assertEquals(array('notice' => 'hello'), $this->storage->getBag('flashes')->peekAll());
+
+        $id = $this->storage->getId();
+        $this->storage->regenerate(true);
+        $this->assertNotEquals($id, $this->storage->getId());
+        $this->assertEquals(array('foo' => 'bar'), $this->storage->getBag('attributes')->all());
+        $this->assertEquals(array('notice' => 'hello'), $this->storage->getBag('flashes')->peekAll());
+    }
+
+    public function testGetId()
+    {
+        $this->assertEquals('', $this->storage->getId());
+        $this->storage->start();
+        $this->assertNotEquals('', $this->storage->getId());
+    }
+
+    public function testClearClearsBags()
+    {
+        $this->storage->clear();
+
+        $this->assertSame(array(), $this->storage->getBag('attributes')->all());
+        $this->assertSame(array(), $this->storage->getBag('flashes')->peekAll());
+    }
+
+    public function testClearStartsSession()
+    {
+        $this->storage->clear();
+
+        $this->assertTrue($this->storage->isStarted());
+    }
+
+    public function testClearWithNoBagsStartsSession()
+    {
+        $storage = new MockArraySessionStorage();
+
+        $storage->clear();
+
+        $this->assertTrue($storage->isStarted());
+    }
+
+    /**
+     * @expectedException \RuntimeException
+     */
+    public function testUnstartedSave()
+    {
+        $this->storage->save();
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/MockFileSessionStorageTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/MockFileSessionStorageTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..53accd38447d3809286da95db538bf33e10cc499
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/MockFileSessionStorageTest.php
@@ -0,0 +1,127 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorage;
+use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
+use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
+
+/**
+ * Test class for MockFileSessionStorage.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class MockFileSessionStorageTest extends TestCase
+{
+    /**
+     * @var string
+     */
+    private $sessionDir;
+
+    /**
+     * @var MockFileSessionStorage
+     */
+    protected $storage;
+
+    protected function setUp()
+    {
+        $this->sessionDir = sys_get_temp_dir().'/sf2test';
+        $this->storage = $this->getStorage();
+    }
+
+    protected function tearDown()
+    {
+        $this->sessionDir = null;
+        $this->storage = null;
+        array_map('unlink', glob($this->sessionDir.'/*.session'));
+        if (is_dir($this->sessionDir)) {
+            rmdir($this->sessionDir);
+        }
+    }
+
+    public function testStart()
+    {
+        $this->assertEquals('', $this->storage->getId());
+        $this->assertTrue($this->storage->start());
+        $id = $this->storage->getId();
+        $this->assertNotEquals('', $this->storage->getId());
+        $this->assertTrue($this->storage->start());
+        $this->assertEquals($id, $this->storage->getId());
+    }
+
+    public function testRegenerate()
+    {
+        $this->storage->start();
+        $this->storage->getBag('attributes')->set('regenerate', 1234);
+        $this->storage->regenerate();
+        $this->assertEquals(1234, $this->storage->getBag('attributes')->get('regenerate'));
+        $this->storage->regenerate(true);
+        $this->assertEquals(1234, $this->storage->getBag('attributes')->get('regenerate'));
+    }
+
+    public function testGetId()
+    {
+        $this->assertEquals('', $this->storage->getId());
+        $this->storage->start();
+        $this->assertNotEquals('', $this->storage->getId());
+    }
+
+    public function testSave()
+    {
+        $this->storage->start();
+        $id = $this->storage->getId();
+        $this->assertNotEquals('108', $this->storage->getBag('attributes')->get('new'));
+        $this->assertFalse($this->storage->getBag('flashes')->has('newkey'));
+        $this->storage->getBag('attributes')->set('new', '108');
+        $this->storage->getBag('flashes')->set('newkey', 'test');
+        $this->storage->save();
+
+        $storage = $this->getStorage();
+        $storage->setId($id);
+        $storage->start();
+        $this->assertEquals('108', $storage->getBag('attributes')->get('new'));
+        $this->assertTrue($storage->getBag('flashes')->has('newkey'));
+        $this->assertEquals(array('test'), $storage->getBag('flashes')->peek('newkey'));
+    }
+
+    public function testMultipleInstances()
+    {
+        $storage1 = $this->getStorage();
+        $storage1->start();
+        $storage1->getBag('attributes')->set('foo', 'bar');
+        $storage1->save();
+
+        $storage2 = $this->getStorage();
+        $storage2->setId($storage1->getId());
+        $storage2->start();
+        $this->assertEquals('bar', $storage2->getBag('attributes')->get('foo'), 'values persist between instances');
+    }
+
+    /**
+     * @expectedException \RuntimeException
+     */
+    public function testSaveWithoutStart()
+    {
+        $storage1 = $this->getStorage();
+        $storage1->save();
+    }
+
+    private function getStorage()
+    {
+        $storage = new MockFileSessionStorage($this->sessionDir);
+        $storage->registerBag(new FlashBag());
+        $storage->registerBag(new AttributeBag());
+
+        return $storage;
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/NativeSessionStorageTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/NativeSessionStorageTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..8fb8b4222835b1c67bb2bedfdd3871000a24e129
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/NativeSessionStorageTest.php
@@ -0,0 +1,277 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
+use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler;
+use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
+use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
+
+/**
+ * Test class for NativeSessionStorage.
+ *
+ * @author Drak <drak@zikula.org>
+ *
+ * These tests require separate processes.
+ *
+ * @runTestsInSeparateProcesses
+ * @preserveGlobalState disabled
+ */
+class NativeSessionStorageTest extends TestCase
+{
+    private $savePath;
+
+    protected function setUp()
+    {
+        $this->iniSet('session.save_handler', 'files');
+        $this->iniSet('session.save_path', $this->savePath = sys_get_temp_dir().'/sf2test');
+        if (!is_dir($this->savePath)) {
+            mkdir($this->savePath);
+        }
+    }
+
+    protected function tearDown()
+    {
+        session_write_close();
+        array_map('unlink', glob($this->savePath.'/*'));
+        if (is_dir($this->savePath)) {
+            rmdir($this->savePath);
+        }
+
+        $this->savePath = null;
+    }
+
+    /**
+     * @return NativeSessionStorage
+     */
+    protected function getStorage(array $options = array())
+    {
+        $storage = new NativeSessionStorage($options);
+        $storage->registerBag(new AttributeBag());
+
+        return $storage;
+    }
+
+    public function testBag()
+    {
+        $storage = $this->getStorage();
+        $bag = new FlashBag();
+        $storage->registerBag($bag);
+        $this->assertSame($bag, $storage->getBag($bag->getName()));
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     */
+    public function testRegisterBagException()
+    {
+        $storage = $this->getStorage();
+        $storage->getBag('non_existing');
+    }
+
+    /**
+     * @expectedException \LogicException
+     */
+    public function testRegisterBagForAStartedSessionThrowsException()
+    {
+        $storage = $this->getStorage();
+        $storage->start();
+        $storage->registerBag(new AttributeBag());
+    }
+
+    public function testGetId()
+    {
+        $storage = $this->getStorage();
+        $this->assertSame('', $storage->getId(), 'Empty ID before starting session');
+
+        $storage->start();
+        $id = $storage->getId();
+        $this->assertInternalType('string', $id);
+        $this->assertNotSame('', $id);
+
+        $storage->save();
+        $this->assertSame($id, $storage->getId(), 'ID stays after saving session');
+    }
+
+    public function testRegenerate()
+    {
+        $storage = $this->getStorage();
+        $storage->start();
+        $id = $storage->getId();
+        $storage->getBag('attributes')->set('lucky', 7);
+        $storage->regenerate();
+        $this->assertNotEquals($id, $storage->getId());
+        $this->assertEquals(7, $storage->getBag('attributes')->get('lucky'));
+    }
+
+    public function testRegenerateDestroy()
+    {
+        $storage = $this->getStorage();
+        $storage->start();
+        $id = $storage->getId();
+        $storage->getBag('attributes')->set('legs', 11);
+        $storage->regenerate(true);
+        $this->assertNotEquals($id, $storage->getId());
+        $this->assertEquals(11, $storage->getBag('attributes')->get('legs'));
+    }
+
+    public function testSessionGlobalIsUpToDateAfterIdRegeneration()
+    {
+        $storage = $this->getStorage();
+        $storage->start();
+        $storage->getBag('attributes')->set('lucky', 7);
+        $storage->regenerate();
+        $storage->getBag('attributes')->set('lucky', 42);
+
+        $this->assertEquals(42, $_SESSION['_sf2_attributes']['lucky']);
+    }
+
+    public function testRegenerationFailureDoesNotFlagStorageAsStarted()
+    {
+        $storage = $this->getStorage();
+        $this->assertFalse($storage->regenerate());
+        $this->assertFalse($storage->isStarted());
+    }
+
+    public function testDefaultSessionCacheLimiter()
+    {
+        $this->iniSet('session.cache_limiter', 'nocache');
+
+        $storage = new NativeSessionStorage();
+        $this->assertEquals('', ini_get('session.cache_limiter'));
+    }
+
+    public function testExplicitSessionCacheLimiter()
+    {
+        $this->iniSet('session.cache_limiter', 'nocache');
+
+        $storage = new NativeSessionStorage(array('cache_limiter' => 'public'));
+        $this->assertEquals('public', ini_get('session.cache_limiter'));
+    }
+
+    public function testCookieOptions()
+    {
+        $options = array(
+            'cookie_lifetime' => 123456,
+            'cookie_path' => '/my/cookie/path',
+            'cookie_domain' => 'symfony.example.com',
+            'cookie_secure' => true,
+            'cookie_httponly' => false,
+        );
+
+        $this->getStorage($options);
+        $temp = session_get_cookie_params();
+        $gco = array();
+
+        foreach ($temp as $key => $value) {
+            $gco['cookie_'.$key] = $value;
+        }
+
+        $this->assertEquals($options, $gco);
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     */
+    public function testSetSaveHandlerException()
+    {
+        $storage = $this->getStorage();
+        $storage->setSaveHandler(new \stdClass());
+    }
+
+    public function testSetSaveHandler()
+    {
+        $this->iniSet('session.save_handler', 'files');
+        $storage = $this->getStorage();
+        $storage->setSaveHandler();
+        $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
+        $storage->setSaveHandler(null);
+        $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
+        $storage->setSaveHandler(new SessionHandlerProxy(new NativeFileSessionHandler()));
+        $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
+        $storage->setSaveHandler(new NativeFileSessionHandler());
+        $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
+        $storage->setSaveHandler(new SessionHandlerProxy(new NullSessionHandler()));
+        $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
+        $storage->setSaveHandler(new NullSessionHandler());
+        $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
+    }
+
+    /**
+     * @expectedException \RuntimeException
+     */
+    public function testStarted()
+    {
+        $storage = $this->getStorage();
+
+        $this->assertFalse($storage->getSaveHandler()->isActive());
+        $this->assertFalse($storage->isStarted());
+
+        session_start();
+        $this->assertTrue(isset($_SESSION));
+        $this->assertTrue($storage->getSaveHandler()->isActive());
+
+        // PHP session might have started, but the storage driver has not, so false is correct here
+        $this->assertFalse($storage->isStarted());
+
+        $key = $storage->getMetadataBag()->getStorageKey();
+        $this->assertArrayNotHasKey($key, $_SESSION);
+        $storage->start();
+    }
+
+    public function testRestart()
+    {
+        $storage = $this->getStorage();
+        $storage->start();
+        $id = $storage->getId();
+        $storage->getBag('attributes')->set('lucky', 7);
+        $storage->save();
+        $storage->start();
+        $this->assertSame($id, $storage->getId(), 'Same session ID after restarting');
+        $this->assertSame(7, $storage->getBag('attributes')->get('lucky'), 'Data still available');
+    }
+
+    public function testCanCreateNativeSessionStorageWhenSessionAlreadyStarted()
+    {
+        session_start();
+        $this->getStorage();
+
+        // Assert no exception has been thrown by `getStorage()`
+        $this->addToAssertionCount(1);
+    }
+
+    public function testSetSessionOptionsOnceSessionStartedIsIgnored()
+    {
+        session_start();
+        $this->getStorage(array(
+            'name' => 'something-else',
+        ));
+
+        // Assert no exception has been thrown by `getStorage()`
+        $this->addToAssertionCount(1);
+    }
+
+    public function testGetBagsOnceSessionStartedIsIgnored()
+    {
+        session_start();
+        $bag = new AttributeBag();
+        $bag->setName('flashes');
+
+        $storage = $this->getStorage();
+        $storage->registerBag($bag);
+
+        $this->assertEquals($storage->getBag('flashes'), $bag);
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..958dc0bc089841cbb80dff16b0affcf6c8d79851
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php
@@ -0,0 +1,96 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\PhpBridgeSessionStorage;
+use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
+
+/**
+ * Test class for PhpSessionStorage.
+ *
+ * @author Drak <drak@zikula.org>
+ *
+ * These tests require separate processes.
+ *
+ * @runTestsInSeparateProcesses
+ * @preserveGlobalState disabled
+ */
+class PhpBridgeSessionStorageTest extends TestCase
+{
+    private $savePath;
+
+    protected function setUp()
+    {
+        $this->iniSet('session.save_handler', 'files');
+        $this->iniSet('session.save_path', $this->savePath = sys_get_temp_dir().'/sf2test');
+        if (!is_dir($this->savePath)) {
+            mkdir($this->savePath);
+        }
+    }
+
+    protected function tearDown()
+    {
+        session_write_close();
+        array_map('unlink', glob($this->savePath.'/*'));
+        if (is_dir($this->savePath)) {
+            rmdir($this->savePath);
+        }
+
+        $this->savePath = null;
+    }
+
+    /**
+     * @return PhpBridgeSessionStorage
+     */
+    protected function getStorage()
+    {
+        $storage = new PhpBridgeSessionStorage();
+        $storage->registerBag(new AttributeBag());
+
+        return $storage;
+    }
+
+    public function testPhpSession()
+    {
+        $storage = $this->getStorage();
+
+        $this->assertFalse($storage->getSaveHandler()->isActive());
+        $this->assertFalse($storage->isStarted());
+
+        session_start();
+        $this->assertTrue(isset($_SESSION));
+        // in PHP 5.4 we can reliably detect a session started
+        $this->assertTrue($storage->getSaveHandler()->isActive());
+        // PHP session might have started, but the storage driver has not, so false is correct here
+        $this->assertFalse($storage->isStarted());
+
+        $key = $storage->getMetadataBag()->getStorageKey();
+        $this->assertArrayNotHasKey($key, $_SESSION);
+        $storage->start();
+        $this->assertArrayHasKey($key, $_SESSION);
+    }
+
+    public function testClear()
+    {
+        $storage = $this->getStorage();
+        session_start();
+        $_SESSION['drak'] = 'loves symfony';
+        $storage->getBag('attributes')->set('symfony', 'greatness');
+        $key = $storage->getBag('attributes')->getStorageKey();
+        $this->assertEquals($_SESSION[$key], array('symfony' => 'greatness'));
+        $this->assertEquals($_SESSION['drak'], 'loves symfony');
+        $storage->clear();
+        $this->assertEquals($_SESSION[$key], array());
+        $this->assertEquals($_SESSION['drak'], 'loves symfony');
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..cbb291f19fc3f3c6daf9ddbadc46230c02cbc892
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php
@@ -0,0 +1,113 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Proxy;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
+use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
+
+/**
+ * Test class for AbstractProxy.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class AbstractProxyTest extends TestCase
+{
+    /**
+     * @var AbstractProxy
+     */
+    protected $proxy;
+
+    protected function setUp()
+    {
+        $this->proxy = $this->getMockForAbstractClass(AbstractProxy::class);
+    }
+
+    protected function tearDown()
+    {
+        $this->proxy = null;
+    }
+
+    public function testGetSaveHandlerName()
+    {
+        $this->assertNull($this->proxy->getSaveHandlerName());
+    }
+
+    public function testIsSessionHandlerInterface()
+    {
+        $this->assertFalse($this->proxy->isSessionHandlerInterface());
+        $sh = new SessionHandlerProxy(new \SessionHandler());
+        $this->assertTrue($sh->isSessionHandlerInterface());
+    }
+
+    public function testIsWrapper()
+    {
+        $this->assertFalse($this->proxy->isWrapper());
+    }
+
+    /**
+     * @runInSeparateProcess
+     * @preserveGlobalState disabled
+     */
+    public function testIsActive()
+    {
+        $this->assertFalse($this->proxy->isActive());
+        session_start();
+        $this->assertTrue($this->proxy->isActive());
+    }
+
+    /**
+     * @runInSeparateProcess
+     * @preserveGlobalState disabled
+     */
+    public function testName()
+    {
+        $this->assertEquals(session_name(), $this->proxy->getName());
+        $this->proxy->setName('foo');
+        $this->assertEquals('foo', $this->proxy->getName());
+        $this->assertEquals(session_name(), $this->proxy->getName());
+    }
+
+    /**
+     * @runInSeparateProcess
+     * @preserveGlobalState disabled
+     * @expectedException \LogicException
+     */
+    public function testNameException()
+    {
+        session_start();
+        $this->proxy->setName('foo');
+    }
+
+    /**
+     * @runInSeparateProcess
+     * @preserveGlobalState disabled
+     */
+    public function testId()
+    {
+        $this->assertEquals(session_id(), $this->proxy->getId());
+        $this->proxy->setId('foo');
+        $this->assertEquals('foo', $this->proxy->getId());
+        $this->assertEquals(session_id(), $this->proxy->getId());
+    }
+
+    /**
+     * @runInSeparateProcess
+     * @preserveGlobalState disabled
+     * @expectedException \LogicException
+     */
+    public function testIdException()
+    {
+        session_start();
+        $this->proxy->setId('foo');
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/NativeProxyTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/NativeProxyTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..ed4fee6bfec0f0ddcbac07ade06d798c40ee22dc
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/NativeProxyTest.php
@@ -0,0 +1,38 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Proxy;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy;
+
+/**
+ * Test class for NativeProxy.
+ *
+ * @group legacy
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class NativeProxyTest extends TestCase
+{
+    public function testIsWrapper()
+    {
+        $proxy = new NativeProxy();
+        $this->assertFalse($proxy->isWrapper());
+    }
+
+    public function testGetSaveHandlerName()
+    {
+        $name = ini_get('session.save_handler');
+        $proxy = new NativeProxy();
+        $this->assertEquals($name, $proxy->getSaveHandlerName());
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..682825356a7246a329f25fda230fa960f061d184
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php
@@ -0,0 +1,124 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Proxy;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
+
+/**
+ * Tests for SessionHandlerProxy class.
+ *
+ * @author Drak <drak@zikula.org>
+ *
+ * @runTestsInSeparateProcesses
+ * @preserveGlobalState disabled
+ */
+class SessionHandlerProxyTest extends TestCase
+{
+    /**
+     * @var \PHPUnit_Framework_MockObject_Matcher
+     */
+    private $mock;
+
+    /**
+     * @var SessionHandlerProxy
+     */
+    private $proxy;
+
+    protected function setUp()
+    {
+        $this->mock = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+        $this->proxy = new SessionHandlerProxy($this->mock);
+    }
+
+    protected function tearDown()
+    {
+        $this->mock = null;
+        $this->proxy = null;
+    }
+
+    public function testOpenTrue()
+    {
+        $this->mock->expects($this->once())
+            ->method('open')
+            ->will($this->returnValue(true));
+
+        $this->assertFalse($this->proxy->isActive());
+        $this->proxy->open('name', 'id');
+        $this->assertFalse($this->proxy->isActive());
+    }
+
+    public function testOpenFalse()
+    {
+        $this->mock->expects($this->once())
+            ->method('open')
+            ->will($this->returnValue(false));
+
+        $this->assertFalse($this->proxy->isActive());
+        $this->proxy->open('name', 'id');
+        $this->assertFalse($this->proxy->isActive());
+    }
+
+    public function testClose()
+    {
+        $this->mock->expects($this->once())
+            ->method('close')
+            ->will($this->returnValue(true));
+
+        $this->assertFalse($this->proxy->isActive());
+        $this->proxy->close();
+        $this->assertFalse($this->proxy->isActive());
+    }
+
+    public function testCloseFalse()
+    {
+        $this->mock->expects($this->once())
+            ->method('close')
+            ->will($this->returnValue(false));
+
+        $this->assertFalse($this->proxy->isActive());
+        $this->proxy->close();
+        $this->assertFalse($this->proxy->isActive());
+    }
+
+    public function testRead()
+    {
+        $this->mock->expects($this->once())
+            ->method('read');
+
+        $this->proxy->read('id');
+    }
+
+    public function testWrite()
+    {
+        $this->mock->expects($this->once())
+            ->method('write');
+
+        $this->proxy->write('id', 'data');
+    }
+
+    public function testDestroy()
+    {
+        $this->mock->expects($this->once())
+            ->method('destroy');
+
+        $this->proxy->destroy('id');
+    }
+
+    public function testGc()
+    {
+        $this->mock->expects($this->once())
+            ->method('gc');
+
+        $this->proxy->gc(86400);
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/StreamedResponseTest.php b/vendor/symfony/http-foundation/Tests/StreamedResponseTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..c2ded996fab4fa72f0bddea1d8757d55d4f22e56
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/StreamedResponseTest.php
@@ -0,0 +1,126 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\StreamedResponse;
+
+class StreamedResponseTest extends TestCase
+{
+    public function testConstructor()
+    {
+        $response = new StreamedResponse(function () { echo 'foo'; }, 404, array('Content-Type' => 'text/plain'));
+
+        $this->assertEquals(404, $response->getStatusCode());
+        $this->assertEquals('text/plain', $response->headers->get('Content-Type'));
+    }
+
+    public function testPrepareWith11Protocol()
+    {
+        $response = new StreamedResponse(function () { echo 'foo'; });
+        $request = Request::create('/');
+        $request->server->set('SERVER_PROTOCOL', 'HTTP/1.1');
+
+        $response->prepare($request);
+
+        $this->assertEquals('1.1', $response->getProtocolVersion());
+        $this->assertNotEquals('chunked', $response->headers->get('Transfer-Encoding'), 'Apache assumes responses with a Transfer-Encoding header set to chunked to already be encoded.');
+    }
+
+    public function testPrepareWith10Protocol()
+    {
+        $response = new StreamedResponse(function () { echo 'foo'; });
+        $request = Request::create('/');
+        $request->server->set('SERVER_PROTOCOL', 'HTTP/1.0');
+
+        $response->prepare($request);
+
+        $this->assertEquals('1.0', $response->getProtocolVersion());
+        $this->assertNull($response->headers->get('Transfer-Encoding'));
+    }
+
+    public function testPrepareWithHeadRequest()
+    {
+        $response = new StreamedResponse(function () { echo 'foo'; }, 200, array('Content-Length' => '123'));
+        $request = Request::create('/', 'HEAD');
+
+        $response->prepare($request);
+
+        $this->assertSame('123', $response->headers->get('Content-Length'));
+    }
+
+    public function testPrepareWithCacheHeaders()
+    {
+        $response = new StreamedResponse(function () { echo 'foo'; }, 200, array('Cache-Control' => 'max-age=600, public'));
+        $request = Request::create('/', 'GET');
+
+        $response->prepare($request);
+        $this->assertEquals('max-age=600, public', $response->headers->get('Cache-Control'));
+    }
+
+    public function testSendContent()
+    {
+        $called = 0;
+
+        $response = new StreamedResponse(function () use (&$called) { ++$called; });
+
+        $response->sendContent();
+        $this->assertEquals(1, $called);
+
+        $response->sendContent();
+        $this->assertEquals(1, $called);
+    }
+
+    /**
+     * @expectedException \LogicException
+     */
+    public function testSendContentWithNonCallable()
+    {
+        $response = new StreamedResponse(null);
+        $response->sendContent();
+    }
+
+    /**
+     * @expectedException \LogicException
+     */
+    public function testSetContent()
+    {
+        $response = new StreamedResponse(function () { echo 'foo'; });
+        $response->setContent('foo');
+    }
+
+    public function testGetContent()
+    {
+        $response = new StreamedResponse(function () { echo 'foo'; });
+        $this->assertFalse($response->getContent());
+    }
+
+    public function testCreate()
+    {
+        $response = StreamedResponse::create(function () {}, 204);
+
+        $this->assertInstanceOf('Symfony\Component\HttpFoundation\StreamedResponse', $response);
+        $this->assertEquals(204, $response->getStatusCode());
+    }
+
+    public function testReturnThis()
+    {
+        $response = new StreamedResponse(function () {});
+        $this->assertInstanceOf('Symfony\Component\HttpFoundation\StreamedResponse', $response->sendContent());
+        $this->assertInstanceOf('Symfony\Component\HttpFoundation\StreamedResponse', $response->sendContent());
+
+        $response = new StreamedResponse(function () {});
+        $this->assertInstanceOf('Symfony\Component\HttpFoundation\StreamedResponse', $response->sendHeaders());
+        $this->assertInstanceOf('Symfony\Component\HttpFoundation\StreamedResponse', $response->sendHeaders());
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/schema/http-status-codes.rng b/vendor/symfony/http-foundation/Tests/schema/http-status-codes.rng
new file mode 100644
index 0000000000000000000000000000000000000000..73708ca680416476abe79fa0c3d7928addead3ef
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/schema/http-status-codes.rng
@@ -0,0 +1,31 @@
+<?xml version='1.0'?>
+<grammar xmlns="http://relaxng.org/ns/structure/1.0"
+  datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"
+  ns="http://www.iana.org/assignments">
+
+  <include href="iana-registry.rng"/>
+
+  <start>
+    <element name="registry">
+      <ref name="registryMeta"/>
+      <element name="registry">
+        <ref name="registryMeta"/>
+        <zeroOrMore>
+          <element name="record">
+            <optional>
+              <attribute name="date"><ref name="genericDate"/></attribute>
+            </optional>
+            <optional>
+              <attribute name="updated"><ref name="genericDate"/></attribute>
+            </optional>
+            <element name="value"><ref name="genericRange"/></element>
+            <element name="description"><text/></element>
+            <ref name="references"/>
+          </element>
+        </zeroOrMore>
+      </element>
+      <ref name="people"/>
+    </element>
+  </start>
+
+</grammar>
diff --git a/vendor/symfony/http-foundation/Tests/schema/iana-registry.rng b/vendor/symfony/http-foundation/Tests/schema/iana-registry.rng
new file mode 100644
index 0000000000000000000000000000000000000000..b9c3ca9d940030e80e8494002bcd72c49837c594
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/schema/iana-registry.rng
@@ -0,0 +1,198 @@
+<?xml version='1.0'?>
+<grammar xmlns="http://relaxng.org/ns/structure/1.0"
+  datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"
+  ns="http://www.iana.org/assignments">
+
+  <define name="registryMeta">
+    <interleave>
+      <attribute name="id"><data type="ID"/></attribute>
+      <optional><element name="title"><ref name="text_with_references"/></element></optional>
+      <optional><element name="created"><ref name="genericDate"/></element></optional>
+      <optional><element name="updated"><data type="date"/></element></optional>
+      <optional><element name="registration_rule"><ref
+            name="text_with_references"/></element></optional>
+      <optional><element name="expert"><text/></element></optional>
+      <optional><element name="description"><ref name="text_with_references"/></element></optional>
+      <zeroOrMore><element name="note"><ref name="text_with_references"/></element></zeroOrMore>
+      <ref name="references"/>
+      <optional><element name="hide"><empty/></element></optional>
+      <zeroOrMore><element name="category"><text/></element></zeroOrMore>
+      <zeroOrMore><ref name="range"/></zeroOrMore>
+      <optional><ref name="file"/></optional>
+    </interleave>
+  </define>
+
+  <define name="range">
+    <element name="range">
+       <interleave>
+	  <element name="value"><text/></element>
+	  <optional><element name="hex"><text/></element></optional>
+	  <element name="registration_rule"><ref name="text_with_references"/></element>
+	  <optional><element name="note"><ref name="text_with_references"/></element></optional>
+	  <optional><ref name="xref"/></optional>
+       </interleave>
+    </element>
+  </define>
+
+  <define name="people">
+    <element name="people">
+      <zeroOrMore>
+        <element name="person">
+          <attribute name="id"><data type="ID"/></attribute>
+          <optional><element name="name"><text/></element></optional>
+          <optional><element name="org"><text/></element></optional>
+          <zeroOrMore><element name="uri"><data type="anyURI"/></element></zeroOrMore>
+          <optional><element name="updated"><ref name="genericDate"/></element></optional>
+        </element>
+      </zeroOrMore>
+    </element>
+  </define>
+
+  <define name="xref">
+    <element name="xref">
+      <optional>
+        <attribute name="lastupdated"><ref name="genericDate"/></attribute>
+      </optional>
+      <choice>
+        <group>
+          <attribute name="type"><value>uri</value></attribute>
+          <attribute name="data"><data type="anyURI"/></attribute>
+        </group>
+        <group>
+          <attribute name="type"><value>rfc</value></attribute>
+          <attribute name="data">
+            <data type="string">
+              <param name="pattern">(rfc|bcp|std)\d+</param>
+            </data>
+          </attribute>
+        </group>
+        <group>
+          <attribute name="type"><value>rfc-errata</value></attribute>
+          <attribute name="data"><data type="positiveInteger"/></attribute>
+        </group>
+        <group>
+          <attribute name="type"><value>draft</value></attribute>
+          <attribute name="data">
+            <data type="string">
+              <param name="pattern">(draft|RFC)(-[a-zA-Z0-9]+)+</param>
+            </data>
+          </attribute>
+        </group>
+        <group>
+          <attribute name="type"><value>registry</value></attribute>
+          <attribute name="data"><data type="NCName"/></attribute>
+        </group>
+        <group>
+          <attribute name="type"><value>person</value></attribute>
+          <attribute name="data"><data type="NCName"/></attribute>
+        </group>
+        <group>
+          <attribute name="type"><value>text</value></attribute>
+        </group>
+        <group>
+          <attribute name="type"><value>note</value></attribute>
+          <attribute name="data"><data type="positiveInteger"/></attribute>
+        </group>
+        <group>
+          <attribute name="type"><value>unicode</value></attribute>
+          <attribute name="data">
+            <data type="string">
+              <param name="pattern">ucd\d+\.\d+\.\d+</param>
+            </data>
+          </attribute>
+        </group>
+      </choice>
+      <text/>
+    </element>
+  </define>
+
+  <define name="references">
+    <zeroOrMore>
+      <ref name="xref"/>
+    </zeroOrMore>
+  </define>
+
+  <define name="text_with_references">
+    <interleave>
+      <zeroOrMore>
+        <text/>
+        <optional><ref name="xref"/></optional>
+      </zeroOrMore>
+    </interleave>
+  </define>
+
+  <define name="richText">
+    <zeroOrMore>
+      <choice>
+        <interleave>
+          <ref name="text_with_references"/>
+          <optional><element name="br"><empty/></element></optional>
+        </interleave>
+        <element name="paragraph">
+          <interleave>
+            <ref name="text_with_references"/>
+            <optional><element name="br"><empty/></element></optional>
+          </interleave>
+        </element>
+        <element name="artwork"><text/></element>
+      </choice>
+    </zeroOrMore>
+  </define>
+
+  <define name="genericRange">
+    <data type="string">
+      <param name="pattern">(\d+|0x[\da-fA-F]+)(\s*-\s*(\d+|0x[\da-fA-F]+))?</param>
+    </data>
+  </define>
+
+  <define name="genericDate">
+    <choice>
+      <data type="date"/>
+      <data type="gYearMonth"/>
+    </choice>
+  </define>
+
+  <define name="hex32">
+    <data type="string">
+      <param name="pattern">0x[0-9]{8}</param>
+    </data>
+  </define>
+
+  <define name="binary">
+    <data type="string">
+      <param name="pattern">[0-1]+</param>
+    </data>
+  </define>
+
+  <define name="footnotes">
+    <zeroOrMore>
+      <element name="footnote">
+        <attribute name="anchor"><data type="positiveInteger"/></attribute>
+        <interleave>
+          <zeroOrMore>
+            <text/>
+            <optional><ref name="xref"/></optional>
+          </zeroOrMore>
+        </interleave>
+      </element>
+    </zeroOrMore>
+  </define>
+
+  <define name="file">
+    <element name="file">
+      <attribute name="type">
+        <choice>
+          <value>legacy</value>
+          <value>mib</value>
+          <value>template</value>
+          <value>json</value>
+        </choice>
+      </attribute>
+      <optional>
+        <attribute name="name"/>
+      </optional>
+      <data type="anyURI"/>
+    </element>
+  </define>
+
+</grammar>
diff --git a/vendor/symfony/http-foundation/composer.json b/vendor/symfony/http-foundation/composer.json
new file mode 100644
index 0000000000000000000000000000000000000000..f6c6f2e623fe695e6384e606306514541cad742f
--- /dev/null
+++ b/vendor/symfony/http-foundation/composer.json
@@ -0,0 +1,38 @@
+{
+    "name": "symfony/http-foundation",
+    "type": "library",
+    "description": "Symfony HttpFoundation Component",
+    "keywords": [],
+    "homepage": "https://symfony.com",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Fabien Potencier",
+            "email": "fabien@symfony.com"
+        },
+        {
+            "name": "Symfony Community",
+            "homepage": "https://symfony.com/contributors"
+        }
+    ],
+    "require": {
+        "php": "^5.5.9|>=7.0.8",
+        "symfony/polyfill-mbstring": "~1.1",
+        "symfony/polyfill-php70": "~1.6"
+    },
+    "require-dev": {
+        "symfony/expression-language": "~2.8|~3.0|~4.0"
+    },
+    "autoload": {
+        "psr-4": { "Symfony\\Component\\HttpFoundation\\": "" },
+        "exclude-from-classmap": [
+            "/Tests/"
+        ]
+    },
+    "minimum-stability": "dev",
+    "extra": {
+        "branch-alias": {
+            "dev-master": "3.4-dev"
+        }
+    }
+}
diff --git a/vendor/symfony/http-foundation/phpunit.xml.dist b/vendor/symfony/http-foundation/phpunit.xml.dist
new file mode 100644
index 0000000000000000000000000000000000000000..c1d61f8bf1da0b6d47f92152164b63f3bb7e6f37
--- /dev/null
+++ b/vendor/symfony/http-foundation/phpunit.xml.dist
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
+         backupGlobals="false"
+         colors="true"
+         bootstrap="vendor/autoload.php"
+         failOnRisky="true"
+         failOnWarning="true"
+>
+    <php>
+        <ini name="error_reporting" value="-1" />
+    </php>
+
+    <testsuites>
+        <testsuite name="Symfony HttpFoundation Component Test Suite">
+            <directory>./Tests/</directory>
+        </testsuite>
+    </testsuites>
+
+    <filter>
+        <whitelist>
+            <directory>./</directory>
+            <exclude>
+                <directory>./Resources</directory>
+                <directory>./Tests</directory>
+                <directory>./vendor</directory>
+            </exclude>
+        </whitelist>
+    </filter>
+</phpunit>
diff --git a/vendor/symfony/polyfill-mbstring/LICENSE b/vendor/symfony/polyfill-mbstring/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..24fa32c2e9b27aef3eac523f0042b8ab9ba81944
--- /dev/null
+++ b/vendor/symfony/polyfill-mbstring/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015-2018 Fabien Potencier
+
+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/vendor/symfony/polyfill-mbstring/Mbstring.php b/vendor/symfony/polyfill-mbstring/Mbstring.php
new file mode 100644
index 0000000000000000000000000000000000000000..67cf9ab8bd0c48b94e2f2b966597e1f511435280
--- /dev/null
+++ b/vendor/symfony/polyfill-mbstring/Mbstring.php
@@ -0,0 +1,791 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Polyfill\Mbstring;
+
+/**
+ * Partial mbstring implementation in PHP, iconv based, UTF-8 centric.
+ *
+ * Implemented:
+ * - mb_chr                  - Returns a specific character from its Unicode code point
+ * - mb_convert_encoding     - Convert character encoding
+ * - mb_convert_variables    - Convert character code in variable(s)
+ * - mb_decode_mimeheader    - Decode string in MIME header field
+ * - mb_encode_mimeheader    - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED
+ * - mb_decode_numericentity - Decode HTML numeric string reference to character
+ * - mb_encode_numericentity - Encode character to HTML numeric string reference
+ * - mb_convert_case         - Perform case folding on a string
+ * - mb_detect_encoding      - Detect character encoding
+ * - mb_get_info             - Get internal settings of mbstring
+ * - mb_http_input           - Detect HTTP input character encoding
+ * - mb_http_output          - Set/Get HTTP output character encoding
+ * - mb_internal_encoding    - Set/Get internal character encoding
+ * - mb_list_encodings       - Returns an array of all supported encodings
+ * - mb_ord                  - Returns the Unicode code point of a character
+ * - mb_output_handler       - Callback function converts character encoding in output buffer
+ * - mb_scrub                - Replaces ill-formed byte sequences with substitute characters
+ * - mb_strlen               - Get string length
+ * - mb_strpos               - Find position of first occurrence of string in a string
+ * - mb_strrpos              - Find position of last occurrence of a string in a string
+ * - mb_strtolower           - Make a string lowercase
+ * - mb_strtoupper           - Make a string uppercase
+ * - mb_substitute_character - Set/Get substitution character
+ * - mb_substr               - Get part of string
+ * - mb_stripos              - Finds position of first occurrence of a string within another, case insensitive
+ * - mb_stristr              - Finds first occurrence of a string within another, case insensitive
+ * - mb_strrchr              - Finds the last occurrence of a character in a string within another
+ * - mb_strrichr             - Finds the last occurrence of a character in a string within another, case insensitive
+ * - mb_strripos             - Finds position of last occurrence of a string within another, case insensitive
+ * - mb_strstr               - Finds first occurrence of a string within anothers
+ * - mb_strwidth             - Return width of string
+ * - mb_substr_count         - Count the number of substring occurrences
+ *
+ * Not implemented:
+ * - mb_convert_kana         - Convert "kana" one from another ("zen-kaku", "han-kaku" and more)
+ * - mb_ereg_*               - Regular expression with multibyte support
+ * - mb_parse_str            - Parse GET/POST/COOKIE data and set global variable
+ * - mb_preferred_mime_name  - Get MIME charset string
+ * - mb_regex_encoding       - Returns current encoding for multibyte regex as string
+ * - mb_regex_set_options    - Set/Get the default options for mbregex functions
+ * - mb_send_mail            - Send encoded mail
+ * - mb_split                - Split multibyte string using regular expression
+ * - mb_strcut               - Get part of string
+ * - mb_strimwidth           - Get truncated string with specified width
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @internal
+ */
+final class Mbstring
+{
+    const MB_CASE_FOLD = PHP_INT_MAX;
+
+    private static $encodingList = array('ASCII', 'UTF-8');
+    private static $language = 'neutral';
+    private static $internalEncoding = 'UTF-8';
+    private static $caseFold = array(
+        array('µ','ſ',"\xCD\x85",'ς',"\xCF\x90","\xCF\x91","\xCF\x95","\xCF\x96","\xCF\xB0","\xCF\xB1","\xCF\xB5","\xE1\xBA\x9B","\xE1\xBE\xBE"),
+        array('μ','s','ι',       'σ','β',       'θ',       'φ',       'π',       'κ',       'ρ',       'ε',       "\xE1\xB9\xA1",'ι'),
+    );
+
+    public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null)
+    {
+        if (is_array($fromEncoding) || false !== strpos($fromEncoding, ',')) {
+            $fromEncoding = self::mb_detect_encoding($s, $fromEncoding);
+        } else {
+            $fromEncoding = self::getEncoding($fromEncoding);
+        }
+
+        $toEncoding = self::getEncoding($toEncoding);
+
+        if ('BASE64' === $fromEncoding) {
+            $s = base64_decode($s);
+            $fromEncoding = $toEncoding;
+        }
+
+        if ('BASE64' === $toEncoding) {
+            return base64_encode($s);
+        }
+
+        if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) {
+            if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) {
+                $fromEncoding = 'Windows-1252';
+            }
+            if ('UTF-8' !== $fromEncoding) {
+                $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s);
+            }
+
+            return preg_replace_callback('/[\x80-\xFF]+/', array(__CLASS__, 'html_encoding_callback'), $s);
+        }
+
+        if ('HTML-ENTITIES' === $fromEncoding) {
+            $s = html_entity_decode($s, ENT_COMPAT, 'UTF-8');
+            $fromEncoding = 'UTF-8';
+        }
+
+        return iconv($fromEncoding, $toEncoding.'//IGNORE', $s);
+    }
+
+    public static function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null)
+    {
+        $vars = array(&$a, &$b, &$c, &$d, &$e, &$f);
+
+        $ok = true;
+        array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) {
+            if (false === $v = Mbstring::mb_convert_encoding($v, $toEncoding, $fromEncoding)) {
+                $ok = false;
+            }
+        });
+
+        return $ok ? $fromEncoding : false;
+    }
+
+    public static function mb_decode_mimeheader($s)
+    {
+        return iconv_mime_decode($s, 2, self::$internalEncoding);
+    }
+
+    public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null)
+    {
+        trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', E_USER_WARNING);
+    }
+
+    public static function mb_decode_numericentity($s, $convmap, $encoding = null)
+    {
+        if (null !== $s && !is_scalar($s) && !(is_object($s) && method_exists($s, '__toString'))) {
+            trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.gettype($s).' given', E_USER_WARNING);
+            return null;
+        }
+
+        if (!is_array($convmap) || !$convmap) {
+            return false;
+        }
+
+        if (null !== $encoding && !is_scalar($encoding)) {
+            trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.gettype($s).' given', E_USER_WARNING);
+            return '';  // Instead of null (cf. mb_encode_numericentity).
+        }
+
+        $s = (string) $s;
+        if ('' === $s) {
+            return '';
+        }
+
+        $encoding = self::getEncoding($encoding);
+
+        if ('UTF-8' === $encoding) {
+            $encoding = null;
+            if (!preg_match('//u', $s)) {
+                $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
+            }
+        } else {
+            $s = iconv($encoding, 'UTF-8//IGNORE', $s);
+        }
+
+        $cnt = floor(count($convmap) / 4) * 4;
+
+        for ($i = 0; $i < $cnt; $i += 4) {
+            // collector_decode_htmlnumericentity ignores $convmap[$i + 3]
+            $convmap[$i] += $convmap[$i + 2];
+            $convmap[$i + 1] += $convmap[$i + 2];
+        }
+
+        $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) {
+            $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1];
+            for ($i = 0; $i < $cnt; $i += 4) {
+                if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) {
+                    return Mbstring::mb_chr($c - $convmap[$i + 2]);
+                }
+            }
+            return $m[0];
+        }, $s);
+
+        if (null === $encoding) {
+            return $s;
+        }
+
+        return iconv('UTF-8', $encoding.'//IGNORE', $s);
+    }
+
+    public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false)
+    {
+        if (null !== $s && !is_scalar($s) && !(is_object($s) && method_exists($s, '__toString'))) {
+            trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.gettype($s).' given', E_USER_WARNING);
+            return null;
+        }
+
+        if (!is_array($convmap) || !$convmap) {
+            return false;
+        }
+
+        if (null !== $encoding && !is_scalar($encoding)) {
+            trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.gettype($s).' given', E_USER_WARNING);
+            return null;  // Instead of '' (cf. mb_decode_numericentity).
+        }
+
+        if (null !== $is_hex && !is_scalar($is_hex)) {
+            trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.gettype($s).' given', E_USER_WARNING);
+            return null;
+        }
+
+        $s = (string) $s;
+        if ('' === $s) {
+            return '';
+        }
+
+        $encoding = self::getEncoding($encoding);
+
+        if ('UTF-8' === $encoding) {
+            $encoding = null;
+            if (!preg_match('//u', $s)) {
+                $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
+            }
+        } else {
+            $s = iconv($encoding, 'UTF-8//IGNORE', $s);
+        }
+
+        static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4);
+
+        $cnt = floor(count($convmap) / 4) * 4;
+        $i = 0;
+        $len = strlen($s);
+        $result = '';
+
+        while ($i < $len) {
+            $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
+            $uchr = substr($s, $i, $ulen);
+            $i += $ulen;
+            $c = self::mb_ord($uchr);
+
+            for ($j = 0; $j < $cnt; $j += 4) {
+                if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) {
+                    $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3];
+                    $result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';';
+                    continue 2;
+                }
+            }
+            $result .= $uchr;
+        }
+
+        if (null === $encoding) {
+            return $result;
+        }
+
+        return iconv('UTF-8', $encoding.'//IGNORE', $result);
+    }
+
+    public static function mb_convert_case($s, $mode, $encoding = null)
+    {
+        $s = (string) $s;
+        if ('' === $s) {
+            return '';
+        }
+
+        $encoding = self::getEncoding($encoding);
+
+        if ('UTF-8' === $encoding) {
+            $encoding = null;
+            if (!preg_match('//u', $s)) {
+                $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
+            }
+        } else {
+            $s = iconv($encoding, 'UTF-8//IGNORE', $s);
+        }
+
+        if (MB_CASE_TITLE == $mode) {
+            $s = preg_replace_callback('/\b\p{Ll}/u', array(__CLASS__, 'title_case_upper'), $s);
+            $s = preg_replace_callback('/\B[\p{Lu}\p{Lt}]+/u', array(__CLASS__, 'title_case_lower'), $s);
+        } else {
+            if (MB_CASE_UPPER == $mode) {
+                static $upper = null;
+                if (null === $upper) {
+                    $upper = self::getData('upperCase');
+                }
+                $map = $upper;
+            } else {
+                if (self::MB_CASE_FOLD === $mode) {
+                    $s = str_replace(self::$caseFold[0], self::$caseFold[1], $s);
+                }
+
+                static $lower = null;
+                if (null === $lower) {
+                    $lower = self::getData('lowerCase');
+                }
+                $map = $lower;
+            }
+
+            static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4);
+
+            $i = 0;
+            $len = strlen($s);
+
+            while ($i < $len) {
+                $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
+                $uchr = substr($s, $i, $ulen);
+                $i += $ulen;
+
+                if (isset($map[$uchr])) {
+                    $uchr = $map[$uchr];
+                    $nlen = strlen($uchr);
+
+                    if ($nlen == $ulen) {
+                        $nlen = $i;
+                        do {
+                            $s[--$nlen] = $uchr[--$ulen];
+                        } while ($ulen);
+                    } else {
+                        $s = substr_replace($s, $uchr, $i - $ulen, $ulen);
+                        $len += $nlen - $ulen;
+                        $i   += $nlen - $ulen;
+                    }
+                }
+            }
+        }
+
+        if (null === $encoding) {
+            return $s;
+        }
+
+        return iconv('UTF-8', $encoding.'//IGNORE', $s);
+    }
+
+    public static function mb_internal_encoding($encoding = null)
+    {
+        if (null === $encoding) {
+            return self::$internalEncoding;
+        }
+
+        $encoding = self::getEncoding($encoding);
+
+        if ('UTF-8' === $encoding || false !== @iconv($encoding, $encoding, ' ')) {
+            self::$internalEncoding = $encoding;
+
+            return true;
+        }
+
+        return false;
+    }
+
+    public static function mb_language($lang = null)
+    {
+        if (null === $lang) {
+            return self::$language;
+        }
+
+        switch ($lang = strtolower($lang)) {
+            case 'uni':
+            case 'neutral':
+                self::$language = $lang;
+
+                return true;
+        }
+
+        return false;
+    }
+
+    public static function mb_list_encodings()
+    {
+        return array('UTF-8');
+    }
+
+    public static function mb_encoding_aliases($encoding)
+    {
+        switch (strtoupper($encoding)) {
+            case 'UTF8':
+            case 'UTF-8':
+                return array('utf8');
+        }
+
+        return false;
+    }
+
+    public static function mb_check_encoding($var = null, $encoding = null)
+    {
+        if (null === $encoding) {
+            if (null === $var) {
+                return false;
+            }
+            $encoding = self::$internalEncoding;
+        }
+
+        return self::mb_detect_encoding($var, array($encoding)) || false !== @iconv($encoding, $encoding, $var);
+    }
+
+    public static function mb_detect_encoding($str, $encodingList = null, $strict = false)
+    {
+        if (null === $encodingList) {
+            $encodingList = self::$encodingList;
+        } else {
+            if (!is_array($encodingList)) {
+                $encodingList = array_map('trim', explode(',', $encodingList));
+            }
+            $encodingList = array_map('strtoupper', $encodingList);
+        }
+
+        foreach ($encodingList as $enc) {
+            switch ($enc) {
+                case 'ASCII':
+                    if (!preg_match('/[\x80-\xFF]/', $str)) {
+                        return $enc;
+                    }
+                    break;
+
+                case 'UTF8':
+                case 'UTF-8':
+                    if (preg_match('//u', $str)) {
+                        return 'UTF-8';
+                    }
+                    break;
+
+                default:
+                    if (0 === strncmp($enc, 'ISO-8859-', 9)) {
+                        return $enc;
+                    }
+            }
+        }
+
+        return false;
+    }
+
+    public static function mb_detect_order($encodingList = null)
+    {
+        if (null === $encodingList) {
+            return self::$encodingList;
+        }
+
+        if (!is_array($encodingList)) {
+            $encodingList = array_map('trim', explode(',', $encodingList));
+        }
+        $encodingList = array_map('strtoupper', $encodingList);
+
+        foreach ($encodingList as $enc) {
+            switch ($enc) {
+                default:
+                    if (strncmp($enc, 'ISO-8859-', 9)) {
+                        return false;
+                    }
+                case 'ASCII':
+                case 'UTF8':
+                case 'UTF-8':
+            }
+        }
+
+        self::$encodingList = $encodingList;
+
+        return true;
+    }
+
+    public static function mb_strlen($s, $encoding = null)
+    {
+        $encoding = self::getEncoding($encoding);
+        if ('CP850' === $encoding || 'ASCII' === $encoding) {
+            return strlen($s);
+        }
+
+        return @iconv_strlen($s, $encoding);
+    }
+
+    public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null)
+    {
+        $encoding = self::getEncoding($encoding);
+        if ('CP850' === $encoding || 'ASCII' === $encoding) {
+            return strpos($haystack, $needle, $offset);
+        }
+
+        $needle = (string) $needle;
+        if ('' === $needle) {
+            trigger_error(__METHOD__.': Empty delimiter', E_USER_WARNING);
+
+            return false;
+        }
+
+        return iconv_strpos($haystack, $needle, $offset, $encoding);
+    }
+
+    public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null)
+    {
+        $encoding = self::getEncoding($encoding);
+        if ('CP850' === $encoding || 'ASCII' === $encoding) {
+            return strrpos($haystack, $needle, $offset);
+        }
+
+        if ($offset != (int) $offset) {
+            $offset = 0;
+        } elseif ($offset = (int) $offset) {
+            if ($offset < 0) {
+                $haystack = self::mb_substr($haystack, 0, $offset, $encoding);
+                $offset = 0;
+            } else {
+                $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding);
+            }
+        }
+
+        $pos = iconv_strrpos($haystack, $needle, $encoding);
+
+        return false !== $pos ? $offset + $pos : false;
+    }
+
+    public static function mb_strtolower($s, $encoding = null)
+    {
+        return self::mb_convert_case($s, MB_CASE_LOWER, $encoding);
+    }
+
+    public static function mb_strtoupper($s, $encoding = null)
+    {
+        return self::mb_convert_case($s, MB_CASE_UPPER, $encoding);
+    }
+
+    public static function mb_substitute_character($c = null)
+    {
+        if (0 === strcasecmp($c, 'none')) {
+            return true;
+        }
+
+        return null !== $c ? false : 'none';
+    }
+
+    public static function mb_substr($s, $start, $length = null, $encoding = null)
+    {
+        $encoding = self::getEncoding($encoding);
+        if ('CP850' === $encoding || 'ASCII' === $encoding) {
+            return substr($s, $start, null === $length ? 2147483647 : $length);
+        }
+
+        if ($start < 0) {
+            $start = iconv_strlen($s, $encoding) + $start;
+            if ($start < 0) {
+                $start = 0;
+            }
+        }
+
+        if (null === $length) {
+            $length = 2147483647;
+        } elseif ($length < 0) {
+            $length = iconv_strlen($s, $encoding) + $length - $start;
+            if ($length < 0) {
+                return '';
+            }
+        }
+
+        return (string) iconv_substr($s, $start, $length, $encoding);
+    }
+
+    public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null)
+    {
+        $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding);
+        $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding);
+
+        return self::mb_strpos($haystack, $needle, $offset, $encoding);
+    }
+
+    public static function mb_stristr($haystack, $needle, $part = false, $encoding = null)
+    {
+        $pos = self::mb_stripos($haystack, $needle, 0, $encoding);
+
+        return self::getSubpart($pos, $part, $haystack, $encoding);
+    }
+
+    public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null)
+    {
+        $encoding = self::getEncoding($encoding);
+        if ('CP850' === $encoding || 'ASCII' === $encoding) {
+            return strrchr($haystack, $needle, $part);
+        }
+        $needle = self::mb_substr($needle, 0, 1, $encoding);
+        $pos = iconv_strrpos($haystack, $needle, $encoding);
+
+        return self::getSubpart($pos, $part, $haystack, $encoding);
+    }
+
+    public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null)
+    {
+        $needle = self::mb_substr($needle, 0, 1, $encoding);
+        $pos = self::mb_strripos($haystack, $needle, $encoding);
+
+        return self::getSubpart($pos, $part, $haystack, $encoding);
+    }
+
+    public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null)
+    {
+        $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding);
+        $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding);
+
+        return self::mb_strrpos($haystack, $needle, $offset, $encoding);
+    }
+
+    public static function mb_strstr($haystack, $needle, $part = false, $encoding = null)
+    {
+        $pos = strpos($haystack, $needle);
+        if (false === $pos) {
+            return false;
+        }
+        if ($part) {
+            return substr($haystack, 0, $pos);
+        }
+
+        return substr($haystack, $pos);
+    }
+
+    public static function mb_get_info($type = 'all')
+    {
+        $info = array(
+            'internal_encoding' => self::$internalEncoding,
+            'http_output' => 'pass',
+            'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)',
+            'func_overload' => 0,
+            'func_overload_list' => 'no overload',
+            'mail_charset' => 'UTF-8',
+            'mail_header_encoding' => 'BASE64',
+            'mail_body_encoding' => 'BASE64',
+            'illegal_chars' => 0,
+            'encoding_translation' => 'Off',
+            'language' => self::$language,
+            'detect_order' => self::$encodingList,
+            'substitute_character' => 'none',
+            'strict_detection' => 'Off',
+        );
+
+        if ('all' === $type) {
+            return $info;
+        }
+        if (isset($info[$type])) {
+            return $info[$type];
+        }
+
+        return false;
+    }
+
+    public static function mb_http_input($type = '')
+    {
+        return false;
+    }
+
+    public static function mb_http_output($encoding = null)
+    {
+        return null !== $encoding ? 'pass' === $encoding : 'pass';
+    }
+
+    public static function mb_strwidth($s, $encoding = null)
+    {
+        $encoding = self::getEncoding($encoding);
+
+        if ('UTF-8' !== $encoding) {
+            $s = iconv($encoding, 'UTF-8//IGNORE', $s);
+        }
+
+        $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide);
+
+        return ($wide << 1) + iconv_strlen($s, 'UTF-8');
+    }
+
+    public static function mb_substr_count($haystack, $needle, $encoding = null)
+    {
+        return substr_count($haystack, $needle);
+    }
+
+    public static function mb_output_handler($contents, $status)
+    {
+        return $contents;
+    }
+
+    public static function mb_chr($code, $encoding = null)
+    {
+        if (0x80 > $code %= 0x200000) {
+            $s = chr($code);
+        } elseif (0x800 > $code) {
+            $s = chr(0xC0 | $code >> 6).chr(0x80 | $code & 0x3F);
+        } elseif (0x10000 > $code) {
+            $s = chr(0xE0 | $code >> 12).chr(0x80 | $code >> 6 & 0x3F).chr(0x80 | $code & 0x3F);
+        } else {
+            $s = chr(0xF0 | $code >> 18).chr(0x80 | $code >> 12 & 0x3F).chr(0x80 | $code >> 6 & 0x3F).chr(0x80 | $code & 0x3F);
+        }
+
+        if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
+            $s = mb_convert_encoding($s, $encoding, 'UTF-8');
+        }
+
+        return $s;
+    }
+
+    public static function mb_ord($s, $encoding = null)
+    {
+        if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
+            $s = mb_convert_encoding($s, 'UTF-8', $encoding);
+        }
+
+        $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0;
+        if (0xF0 <= $code) {
+            return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80;
+        }
+        if (0xE0 <= $code) {
+            return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80;
+        }
+        if (0xC0 <= $code) {
+            return (($code - 0xC0) << 6) + $s[2] - 0x80;
+        }
+
+        return $code;
+    }
+
+    private static function getSubpart($pos, $part, $haystack, $encoding)
+    {
+        if (false === $pos) {
+            return false;
+        }
+        if ($part) {
+            return self::mb_substr($haystack, 0, $pos, $encoding);
+        }
+
+        return self::mb_substr($haystack, $pos, null, $encoding);
+    }
+
+    private static function html_encoding_callback(array $m)
+    {
+        $i = 1;
+        $entities = '';
+        $m = unpack('C*', htmlentities($m[0], ENT_COMPAT, 'UTF-8'));
+
+        while (isset($m[$i])) {
+            if (0x80 > $m[$i]) {
+                $entities .= chr($m[$i++]);
+                continue;
+            }
+            if (0xF0 <= $m[$i]) {
+                $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
+            } elseif (0xE0 <= $m[$i]) {
+                $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
+            } else {
+                $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80;
+            }
+
+            $entities .= '&#'.$c.';';
+        }
+
+        return $entities;
+    }
+
+    private static function title_case_lower(array $s)
+    {
+        return self::mb_convert_case($s[0], MB_CASE_LOWER, 'UTF-8');
+    }
+
+    private static function title_case_upper(array $s)
+    {
+        return self::mb_convert_case($s[0], MB_CASE_UPPER, 'UTF-8');
+    }
+
+    private static function getData($file)
+    {
+        if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) {
+            return require $file;
+        }
+
+        return false;
+    }
+
+    private static function getEncoding($encoding)
+    {
+        if (null === $encoding) {
+            return self::$internalEncoding;
+        }
+
+        $encoding = strtoupper($encoding);
+
+        if ('8BIT' === $encoding || 'BINARY' === $encoding) {
+            return 'CP850';
+        }
+        if ('UTF8' === $encoding) {
+            return 'UTF-8';
+        }
+
+        return $encoding;
+    }
+}
diff --git a/vendor/symfony/polyfill-mbstring/README.md b/vendor/symfony/polyfill-mbstring/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..342e8286dbf694422c31e61b1a5bdf16f71785b4
--- /dev/null
+++ b/vendor/symfony/polyfill-mbstring/README.md
@@ -0,0 +1,13 @@
+Symfony Polyfill / Mbstring
+===========================
+
+This component provides a partial, native PHP implementation for the
+[Mbstring](http://php.net/mbstring) extension.
+
+More information can be found in the
+[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md).
+
+License
+=======
+
+This library is released under the [MIT license](LICENSE).
diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php
new file mode 100644
index 0000000000000000000000000000000000000000..3ca16416a82580b15bd446ba5cf75dc3d87fa922
--- /dev/null
+++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php
@@ -0,0 +1,1101 @@
+<?php
+
+static $data = array (
+  'A' => 'a',
+  'B' => 'b',
+  'C' => 'c',
+  'D' => 'd',
+  'E' => 'e',
+  'F' => 'f',
+  'G' => 'g',
+  'H' => 'h',
+  'I' => 'i',
+  'J' => 'j',
+  'K' => 'k',
+  'L' => 'l',
+  'M' => 'm',
+  'N' => 'n',
+  'O' => 'o',
+  'P' => 'p',
+  'Q' => 'q',
+  'R' => 'r',
+  'S' => 's',
+  'T' => 't',
+  'U' => 'u',
+  'V' => 'v',
+  'W' => 'w',
+  'X' => 'x',
+  'Y' => 'y',
+  'Z' => 'z',
+  'À' => 'à',
+  'Á' => 'á',
+  'Â' => 'â',
+  'Ã' => 'ã',
+  'Ä' => 'ä',
+  'Ã…' => 'Ã¥',
+  'Æ' => 'æ',
+  'Ç' => 'ç',
+  'È' => 'è',
+  'É' => 'é',
+  'Ê' => 'ê',
+  'Ë' => 'ë',
+  'Ì' => 'ì',
+  'Í' => 'í',
+  'Î' => 'î',
+  'Ï' => 'ï',
+  'Ð' => 'ð',
+  'Ñ' => 'ñ',
+  'Ò' => 'ò',
+  'Ó' => 'ó',
+  'Ô' => 'ô',
+  'Õ' => 'õ',
+  'Ö' => 'ö',
+  'Ø' => 'ø',
+  'Ù' => 'ù',
+  'Ú' => 'ú',
+  'Û' => 'û',
+  'Ü' => 'ü',
+  'Ý' => 'ý',
+  'Þ' => 'þ',
+  'Ā' => 'ā',
+  'Ă' => 'ă',
+  'Ä„' => 'Ä…',
+  'Ć' => 'ć',
+  'Ĉ' => 'ĉ',
+  'ÄŠ' => 'Ä‹',
+  'Č' => 'č',
+  'Ď' => 'ď',
+  'Đ' => 'đ',
+  'Ä’' => 'Ä“',
+  'Ä”' => 'Ä•',
+  'Ä–' => 'Ä—',
+  'Ę' => 'ę',
+  'Äš' => 'Ä›',
+  'Ĝ' => 'ĝ',
+  'Äž' => 'ÄŸ',
+  'Ä ' => 'Ä¡',
+  'Ä¢' => 'Ä£',
+  'Ĥ' => 'ĥ',
+  'Ħ' => 'ħ',
+  'Ĩ' => 'ĩ',
+  'Ī' => 'ī',
+  'Ĭ' => 'ĭ',
+  'Į' => 'į',
+  'İ' => 'i',
+  'IJ' => 'ij',
+  'Ĵ' => 'ĵ',
+  'Ķ' => 'ķ',
+  'Ĺ' => 'ĺ',
+  'Ļ' => 'ļ',
+  'Ľ' => 'ľ',
+  'Ä¿' => 'Å€',
+  'Ł' => 'ł',
+  'Ń' => 'ń',
+  'Ņ' => 'ņ',
+  'Ň' => 'ň',
+  'ÅŠ' => 'Å‹',
+  'Ō' => 'ō',
+  'Ŏ' => 'ŏ',
+  'Ő' => 'ő',
+  'Å’' => 'Å“',
+  'Å”' => 'Å•',
+  'Å–' => 'Å—',
+  'Ř' => 'ř',
+  'Åš' => 'Å›',
+  'Ŝ' => 'ŝ',
+  'Åž' => 'ÅŸ',
+  'Å ' => 'Å¡',
+  'Å¢' => 'Å£',
+  'Ť' => 'ť',
+  'Ŧ' => 'ŧ',
+  'Ũ' => 'ũ',
+  'Ū' => 'ū',
+  'Ŭ' => 'ŭ',
+  'Ů' => 'ů',
+  'Ű' => 'ű',
+  'Ų' => 'ų',
+  'Ŵ' => 'ŵ',
+  'Ŷ' => 'ŷ',
+  'Ÿ' => 'ÿ',
+  'Ź' => 'ź',
+  'Ż' => 'ż',
+  'Ž' => 'ž',
+  'Ɓ' => 'ɓ',
+  'Ƃ' => 'ƃ',
+  'Æ„' => 'Æ…',
+  'Ɔ' => 'ɔ',
+  'Ƈ' => 'ƈ',
+  'Ɖ' => 'ɖ',
+  'ÆŠ' => 'É—',
+  'Ƌ' => 'ƌ',
+  'Ǝ' => 'ǝ',
+  'Ə' => 'ə',
+  'Ɛ' => 'ɛ',
+  'Æ‘' => 'Æ’',
+  'Æ“' => 'É ',
+  'Æ”' => 'É£',
+  'Æ–' => 'É©',
+  'Ɨ' => 'ɨ',
+  'Ƙ' => 'ƙ',
+  'Ɯ' => 'ɯ',
+  'Ɲ' => 'ɲ',
+  'Ɵ' => 'ɵ',
+  'Æ ' => 'Æ¡',
+  'Æ¢' => 'Æ£',
+  'Ƥ' => 'ƥ',
+  'Ʀ' => 'ʀ',
+  'Ƨ' => 'ƨ',
+  'Ʃ' => 'ʃ',
+  'Ƭ' => 'ƭ',
+  'Ʈ' => 'ʈ',
+  'Ư' => 'ư',
+  'Ʊ' => 'ʊ',
+  'Ʋ' => 'ʋ',
+  'Ƴ' => 'ƴ',
+  'Ƶ' => 'ƶ',
+  'Æ·' => 'Ê’',
+  'Ƹ' => 'ƹ',
+  'Ƽ' => 'ƽ',
+  'DŽ' => 'dž',
+  'Dž' => 'dž',
+  'LJ' => 'lj',
+  'Lj' => 'lj',
+  'NJ' => 'nj',
+  'Nj' => 'nj',
+  'Ǎ' => 'ǎ',
+  'Ǐ' => 'ǐ',
+  'Ç‘' => 'Ç’',
+  'Ç“' => 'Ç”',
+  'Ç•' => 'Ç–',
+  'Ǘ' => 'ǘ',
+  'Ç™' => 'Çš',
+  'Ǜ' => 'ǜ',
+  'Çž' => 'ÇŸ',
+  'Ç ' => 'Ç¡',
+  'Ç¢' => 'Ç£',
+  'Ǥ' => 'ǥ',
+  'Ǧ' => 'ǧ',
+  'Ǩ' => 'ǩ',
+  'Ǫ' => 'ǫ',
+  'Ǭ' => 'ǭ',
+  'Ǯ' => 'ǯ',
+  'DZ' => 'dz',
+  'Dz' => 'dz',
+  'Ǵ' => 'ǵ',
+  'Ƕ' => 'ƕ',
+  'Ç·' => 'Æ¿',
+  'Ǹ' => 'ǹ',
+  'Ǻ' => 'ǻ',
+  'Ǽ' => 'ǽ',
+  'Ǿ' => 'ǿ',
+  'Ȁ' => 'ȁ',
+  'Ȃ' => 'ȃ',
+  'È„' => 'È…',
+  'Ȇ' => 'ȇ',
+  'Ȉ' => 'ȉ',
+  'ÈŠ' => 'È‹',
+  'Ȍ' => 'ȍ',
+  'Ȏ' => 'ȏ',
+  'Ȑ' => 'ȑ',
+  'È’' => 'È“',
+  'È”' => 'È•',
+  'È–' => 'È—',
+  'Ș' => 'ș',
+  'Èš' => 'È›',
+  'Ȝ' => 'ȝ',
+  'Èž' => 'ÈŸ',
+  'È ' => 'Æž',
+  'È¢' => 'È£',
+  'Ȥ' => 'ȥ',
+  'Ȧ' => 'ȧ',
+  'Ȩ' => 'ȩ',
+  'Ȫ' => 'ȫ',
+  'Ȭ' => 'ȭ',
+  'Ȯ' => 'ȯ',
+  'Ȱ' => 'ȱ',
+  'Ȳ' => 'ȳ',
+  'Ⱥ' => 'ⱥ',
+  'Ȼ' => 'ȼ',
+  'Ƚ' => 'ƚ',
+  'Ⱦ' => 'ⱦ',
+  'Ɂ' => 'ɂ',
+  'Ƀ' => 'ƀ',
+  'Ʉ' => 'ʉ',
+  'Ʌ' => 'ʌ',
+  'Ɇ' => 'ɇ',
+  'Ɉ' => 'ɉ',
+  'ÉŠ' => 'É‹',
+  'Ɍ' => 'ɍ',
+  'Ɏ' => 'ɏ',
+  'Ͱ' => 'ͱ',
+  'Ͳ' => 'ͳ',
+  'Ͷ' => 'ͷ',
+  'Ϳ' => 'ϳ',
+  'Ά' => 'ά',
+  'Έ' => 'έ',
+  'Ή' => 'ή',
+  'Ί' => 'ί',
+  'Ό' => 'ό',
+  'Ύ' => 'ύ',
+  'Ώ' => 'ώ',
+  'Α' => 'α',
+  'Β' => 'β',
+  'Γ' => 'γ',
+  'Δ' => 'δ',
+  'Ε' => 'ε',
+  'Ζ' => 'ζ',
+  'Η' => 'η',
+  'Θ' => 'θ',
+  'Ι' => 'ι',
+  'Κ' => 'κ',
+  'Λ' => 'λ',
+  'Μ' => 'μ',
+  'Ν' => 'ν',
+  'Ξ' => 'ξ',
+  'Ο' => 'ο',
+  'Π' => 'π',
+  'Ρ' => 'ρ',
+  'Σ' => 'σ',
+  'Τ' => 'τ',
+  'Î¥' => 'Ï…',
+  'Φ' => 'φ',
+  'Χ' => 'χ',
+  'Ψ' => 'ψ',
+  'Ω' => 'ω',
+  'Ϊ' => 'ϊ',
+  'Ϋ' => 'ϋ',
+  'Ϗ' => 'ϗ',
+  'Ϙ' => 'ϙ',
+  'Ïš' => 'Ï›',
+  'Ϝ' => 'ϝ',
+  'Ïž' => 'ÏŸ',
+  'Ï ' => 'Ï¡',
+  'Ï¢' => 'Ï£',
+  'Ϥ' => 'ϥ',
+  'Ϧ' => 'ϧ',
+  'Ϩ' => 'ϩ',
+  'Ϫ' => 'ϫ',
+  'Ϭ' => 'ϭ',
+  'Ϯ' => 'ϯ',
+  'ϴ' => 'θ',
+  'Ϸ' => 'ϸ',
+  'Ϲ' => 'ϲ',
+  'Ϻ' => 'ϻ',
+  'Ͻ' => 'ͻ',
+  'Ͼ' => 'ͼ',
+  'Ͽ' => 'ͽ',
+  'Ѐ' => 'ѐ',
+  'Ё' => 'ё',
+  'Ђ' => 'ђ',
+  'Ѓ' => 'ѓ',
+  'Є' => 'є',
+  'Ð…' => 'Ñ•',
+  'І' => 'і',
+  'Ї' => 'ї',
+  'Ј' => 'ј',
+  'Љ' => 'љ',
+  'Њ' => 'њ',
+  'Ћ' => 'ћ',
+  'Ќ' => 'ќ',
+  'Ѝ' => 'ѝ',
+  'ÐŽ' => 'Ñž',
+  'Џ' => 'џ',
+  'А' => 'а',
+  'Б' => 'б',
+  'В' => 'в',
+  'Г' => 'г',
+  'Д' => 'д',
+  'Е' => 'е',
+  'Ж' => 'ж',
+  'З' => 'з',
+  'И' => 'и',
+  'Й' => 'й',
+  'К' => 'к',
+  'Л' => 'л',
+  'М' => 'м',
+  'Н' => 'н',
+  'О' => 'о',
+  'П' => 'п',
+  'Р' => 'р',
+  'С' => 'с',
+  'Т' => 'т',
+  'У' => 'у',
+  'Ф' => 'ф',
+  'Ð¥' => 'Ñ…',
+  'Ц' => 'ц',
+  'Ч' => 'ч',
+  'Ш' => 'ш',
+  'Щ' => 'щ',
+  'Ъ' => 'ъ',
+  'Ы' => 'ы',
+  'Ь' => 'ь',
+  'Э' => 'э',
+  'Ю' => 'ю',
+  'Я' => 'я',
+  'Ñ ' => 'Ñ¡',
+  'Ñ¢' => 'Ñ£',
+  'Ѥ' => 'ѥ',
+  'Ѧ' => 'ѧ',
+  'Ѩ' => 'ѩ',
+  'Ѫ' => 'ѫ',
+  'Ѭ' => 'ѭ',
+  'Ѯ' => 'ѯ',
+  'Ѱ' => 'ѱ',
+  'Ѳ' => 'ѳ',
+  'Ѵ' => 'ѵ',
+  'Ѷ' => 'ѷ',
+  'Ѹ' => 'ѹ',
+  'Ѻ' => 'ѻ',
+  'Ѽ' => 'ѽ',
+  'Ѿ' => 'ѿ',
+  'Ҁ' => 'ҁ',
+  'ÒŠ' => 'Ò‹',
+  'Ҍ' => 'ҍ',
+  'Ҏ' => 'ҏ',
+  'Ґ' => 'ґ',
+  'Ò’' => 'Ò“',
+  'Ò”' => 'Ò•',
+  'Ò–' => 'Ò—',
+  'Ò˜' => 'Ò™',
+  'Òš' => 'Ò›',
+  'Ҝ' => 'ҝ',
+  'Òž' => 'ÒŸ',
+  'Ò ' => 'Ò¡',
+  'Ò¢' => 'Ò£',
+  'Ò¤' => 'Ò¥',
+  'Ò¦' => 'Ò§',
+  'Ò¨' => 'Ò©',
+  'Òª' => 'Ò«',
+  'Ò¬' => 'Ò­',
+  'Ò®' => 'Ò¯',
+  'Ò°' => 'Ò±',
+  'Ò²' => 'Ò³',
+  'Ò´' => 'Òµ',
+  'Ò¶' => 'Ò·',
+  'Ò¸' => 'Ò¹',
+  'Òº' => 'Ò»',
+  'Ò¼' => 'Ò½',
+  'Ò¾' => 'Ò¿',
+  'Ӏ' => 'ӏ',
+  'Ӂ' => 'ӂ',
+  'Óƒ' => 'Ó„',
+  'Ó…' => 'Ó†',
+  'Ó‡' => 'Óˆ',
+  'Ó‰' => 'ÓŠ',
+  'Ӌ' => 'ӌ',
+  'Ӎ' => 'ӎ',
+  'Ӑ' => 'ӑ',
+  'Ó’' => 'Ó“',
+  'Ó”' => 'Ó•',
+  'Ó–' => 'Ó—',
+  'Ó˜' => 'Ó™',
+  'Óš' => 'Ó›',
+  'Ӝ' => 'ӝ',
+  'Óž' => 'ÓŸ',
+  'Ó ' => 'Ó¡',
+  'Ó¢' => 'Ó£',
+  'Ó¤' => 'Ó¥',
+  'Ó¦' => 'Ó§',
+  'Ó¨' => 'Ó©',
+  'Óª' => 'Ó«',
+  'Ó¬' => 'Ó­',
+  'Ó®' => 'Ó¯',
+  'Ó°' => 'Ó±',
+  'Ó²' => 'Ó³',
+  'Ó´' => 'Óµ',
+  'Ó¶' => 'Ó·',
+  'Ó¸' => 'Ó¹',
+  'Óº' => 'Ó»',
+  'Ó¼' => 'Ó½',
+  'Ó¾' => 'Ó¿',
+  'Ԁ' => 'ԁ',
+  'Ô‚' => 'Ôƒ',
+  'Ô„' => 'Ô…',
+  'Ô†' => 'Ô‡',
+  'Ôˆ' => 'Ô‰',
+  'ÔŠ' => 'Ô‹',
+  'Ԍ' => 'ԍ',
+  'Ԏ' => 'ԏ',
+  'Ԑ' => 'ԑ',
+  'Ô’' => 'Ô“',
+  'Ô”' => 'Ô•',
+  'Ô–' => 'Ô—',
+  'Ô˜' => 'Ô™',
+  'Ôš' => 'Ô›',
+  'Ԝ' => 'ԝ',
+  'Ôž' => 'ÔŸ',
+  'Ô ' => 'Ô¡',
+  'Ô¢' => 'Ô£',
+  'Ô¤' => 'Ô¥',
+  'Ô¦' => 'Ô§',
+  'Ô¨' => 'Ô©',
+  'Ôª' => 'Ô«',
+  'Ô¬' => 'Ô­',
+  'Ô®' => 'Ô¯',
+  'Ô±' => 'Õ¡',
+  'Ô²' => 'Õ¢',
+  'Ô³' => 'Õ£',
+  'Ô´' => 'Õ¤',
+  'Ôµ' => 'Õ¥',
+  'Ô¶' => 'Õ¦',
+  'Ô·' => 'Õ§',
+  'Ô¸' => 'Õ¨',
+  'Ô¹' => 'Õ©',
+  'Ôº' => 'Õª',
+  'Ô»' => 'Õ«',
+  'Ô¼' => 'Õ¬',
+  'Ô½' => 'Õ­',
+  'Ô¾' => 'Õ®',
+  'Ô¿' => 'Õ¯',
+  'Õ€' => 'Õ°',
+  'Ձ' => 'ձ',
+  'Õ‚' => 'Õ²',
+  'Õƒ' => 'Õ³',
+  'Õ„' => 'Õ´',
+  'Õ…' => 'Õµ',
+  'Õ†' => 'Õ¶',
+  'Õ‡' => 'Õ·',
+  'Õˆ' => 'Õ¸',
+  'Õ‰' => 'Õ¹',
+  'ÕŠ' => 'Õº',
+  'Õ‹' => 'Õ»',
+  'Ռ' => 'ռ',
+  'Ս' => 'ս',
+  'ÕŽ' => 'Õ¾',
+  'Տ' => 'տ',
+  'Ր' => 'ր',
+  'Ց' => 'ց',
+  'Õ’' => 'Ö‚',
+  'Õ“' => 'Öƒ',
+  'Õ”' => 'Ö„',
+  'Õ•' => 'Ö…',
+  'Õ–' => 'Ö†',
+  'á‚ ' => 'â´€',
+  'Ⴁ' => 'ⴁ',
+  'á‚¢' => 'â´‚',
+  'á‚£' => 'â´ƒ',
+  'Ⴄ' => 'ⴄ',
+  'á‚¥' => 'â´…',
+  'Ⴆ' => 'ⴆ',
+  'á‚§' => 'â´‡',
+  'Ⴈ' => 'ⴈ',
+  'á‚©' => 'â´‰',
+  'Ⴊ' => 'ⴊ',
+  'á‚«' => 'â´‹',
+  'Ⴌ' => 'ⴌ',
+  'Ⴍ' => 'ⴍ',
+  'á‚®' => 'â´Ž',
+  'Ⴏ' => 'ⴏ',
+  'Ⴐ' => 'ⴐ',
+  'Ⴑ' => 'ⴑ',
+  'Ⴒ' => 'ⴒ',
+  'Ⴓ' => 'ⴓ',
+  'á‚´' => 'â´”',
+  'Ⴕ' => 'ⴕ',
+  'á‚¶' => 'â´–',
+  'á‚·' => 'â´—',
+  'Ⴘ' => 'ⴘ',
+  'Ⴙ' => 'ⴙ',
+  'Ⴚ' => 'ⴚ',
+  'á‚»' => 'â´›',
+  'Ⴜ' => 'ⴜ',
+  'Ⴝ' => 'ⴝ',
+  'Ⴞ' => 'ⴞ',
+  'á‚¿' => 'â´Ÿ',
+  'Ⴠ' => 'ⴠ',
+  'Ⴡ' => 'ⴡ',
+  'Ⴢ' => 'ⴢ',
+  'Ⴣ' => 'ⴣ',
+  'Ⴤ' => 'ⴤ',
+  'Ⴥ' => 'ⴥ',
+  'Ⴧ' => 'ⴧ',
+  'Ⴭ' => 'ⴭ',
+  'Ḁ' => 'ḁ',
+  'Ḃ' => 'ḃ',
+  'Ḅ' => 'ḅ',
+  'Ḇ' => 'ḇ',
+  'Ḉ' => 'ḉ',
+  'Ḋ' => 'ḋ',
+  'Ḍ' => 'ḍ',
+  'Ḏ' => 'ḏ',
+  'Ḑ' => 'ḑ',
+  'Ḓ' => 'ḓ',
+  'Ḕ' => 'ḕ',
+  'Ḗ' => 'ḗ',
+  'Ḙ' => 'ḙ',
+  'Ḛ' => 'ḛ',
+  'Ḝ' => 'ḝ',
+  'Ḟ' => 'ḟ',
+  'Ḡ' => 'ḡ',
+  'Ḣ' => 'ḣ',
+  'Ḥ' => 'ḥ',
+  'Ḧ' => 'ḧ',
+  'Ḩ' => 'ḩ',
+  'Ḫ' => 'ḫ',
+  'Ḭ' => 'ḭ',
+  'Ḯ' => 'ḯ',
+  'Ḱ' => 'ḱ',
+  'Ḳ' => 'ḳ',
+  'Ḵ' => 'ḵ',
+  'Ḷ' => 'ḷ',
+  'Ḹ' => 'ḹ',
+  'Ḻ' => 'ḻ',
+  'Ḽ' => 'ḽ',
+  'Ḿ' => 'ḿ',
+  'Ṁ' => 'ṁ',
+  'Ṃ' => 'ṃ',
+  'Ṅ' => 'ṅ',
+  'Ṇ' => 'ṇ',
+  'Ṉ' => 'ṉ',
+  'Ṋ' => 'ṋ',
+  'Ṍ' => 'ṍ',
+  'Ṏ' => 'ṏ',
+  'Ṑ' => 'ṑ',
+  'Ṓ' => 'ṓ',
+  'Ṕ' => 'ṕ',
+  'á¹–' => 'á¹—',
+  'Ṙ' => 'ṙ',
+  'Ṛ' => 'ṛ',
+  'Ṝ' => 'ṝ',
+  'Ṟ' => 'ṟ',
+  'Ṡ' => 'ṡ',
+  'á¹¢' => 'á¹£',
+  'Ṥ' => 'ṥ',
+  'Ṧ' => 'ṧ',
+  'Ṩ' => 'ṩ',
+  'Ṫ' => 'ṫ',
+  'Ṭ' => 'ṭ',
+  'Ṯ' => 'ṯ',
+  'á¹°' => 'á¹±',
+  'á¹²' => 'á¹³',
+  'á¹´' => 'á¹µ',
+  'á¹¶' => 'á¹·',
+  'Ṹ' => 'ṹ',
+  'Ṻ' => 'ṻ',
+  'á¹¼' => 'á¹½',
+  'Ṿ' => 'ṿ',
+  'Ẁ' => 'ẁ',
+  'Ẃ' => 'ẃ',
+  'Ẅ' => 'ẅ',
+  'Ẇ' => 'ẇ',
+  'Ẉ' => 'ẉ',
+  'Ẋ' => 'ẋ',
+  'Ẍ' => 'ẍ',
+  'Ẏ' => 'ẏ',
+  'Ẑ' => 'ẑ',
+  'Ẓ' => 'ẓ',
+  'Ẕ' => 'ẕ',
+  'ẞ' => 'ß',
+  'Ạ' => 'ạ',
+  'Ả' => 'ả',
+  'Ấ' => 'ấ',
+  'Ầ' => 'ầ',
+  'Ẩ' => 'ẩ',
+  'Ẫ' => 'ẫ',
+  'Ậ' => 'ậ',
+  'Ắ' => 'ắ',
+  'Ằ' => 'ằ',
+  'Ẳ' => 'ẳ',
+  'Ẵ' => 'ẵ',
+  'Ặ' => 'ặ',
+  'Ẹ' => 'ẹ',
+  'Ẻ' => 'ẻ',
+  'Ẽ' => 'ẽ',
+  'Ế' => 'ế',
+  'Ề' => 'ề',
+  'Ể' => 'ể',
+  'Ễ' => 'ễ',
+  'Ệ' => 'ệ',
+  'Ỉ' => 'ỉ',
+  'Ị' => 'ị',
+  'Ọ' => 'ọ',
+  'Ỏ' => 'ỏ',
+  'Ố' => 'ố',
+  'Ồ' => 'ồ',
+  'Ổ' => 'ổ',
+  'á»–' => 'á»—',
+  'Ộ' => 'ộ',
+  'Ớ' => 'ớ',
+  'Ờ' => 'ờ',
+  'Ở' => 'ở',
+  'Ỡ' => 'ỡ',
+  'Ợ' => 'ợ',
+  'Ụ' => 'ụ',
+  'Ủ' => 'ủ',
+  'Ứ' => 'ứ',
+  'Ừ' => 'ừ',
+  'Ử' => 'ử',
+  'Ữ' => 'ữ',
+  'á»°' => 'á»±',
+  'Ỳ' => 'ỳ',
+  'Ỵ' => 'ỵ',
+  'á»¶' => 'á»·',
+  'Ỹ' => 'ỹ',
+  'Ỻ' => 'ỻ',
+  'Ỽ' => 'ỽ',
+  'Ỿ' => 'ỿ',
+  'Ἀ' => 'ἀ',
+  'Ἁ' => 'ἁ',
+  'Ἂ' => 'ἂ',
+  'Ἃ' => 'ἃ',
+  'Ἄ' => 'ἄ',
+  'Ἅ' => 'ἅ',
+  'Ἆ' => 'ἆ',
+  'Ἇ' => 'ἇ',
+  'Ἐ' => 'ἐ',
+  'Ἑ' => 'ἑ',
+  'Ἒ' => 'ἒ',
+  'Ἓ' => 'ἓ',
+  'Ἔ' => 'ἔ',
+  'Ἕ' => 'ἕ',
+  'Ἠ' => 'ἠ',
+  'Ἡ' => 'ἡ',
+  'Ἢ' => 'ἢ',
+  'Ἣ' => 'ἣ',
+  'Ἤ' => 'ἤ',
+  'á¼­' => 'á¼¥',
+  'Ἦ' => 'ἦ',
+  'Ἧ' => 'ἧ',
+  'Ἰ' => 'ἰ',
+  'á¼¹' => 'á¼±',
+  'Ἲ' => 'ἲ',
+  'á¼»' => 'á¼³',
+  'á¼¼' => 'á¼´',
+  'á¼½' => 'á¼µ',
+  'á¼¾' => 'á¼¶',
+  'Ἷ' => 'ἷ',
+  'Ὀ' => 'ὀ',
+  'Ὁ' => 'ὁ',
+  'Ὂ' => 'ὂ',
+  'Ὃ' => 'ὃ',
+  'Ὄ' => 'ὄ',
+  'Ὅ' => 'ὅ',
+  'Ὑ' => 'ὑ',
+  'Ὓ' => 'ὓ',
+  'Ὕ' => 'ὕ',
+  'Ὗ' => 'ὗ',
+  'Ὠ' => 'ὠ',
+  'Ὡ' => 'ὡ',
+  'Ὢ' => 'ὢ',
+  'Ὣ' => 'ὣ',
+  'Ὤ' => 'ὤ',
+  'á½­' => 'á½¥',
+  'Ὦ' => 'ὦ',
+  'Ὧ' => 'ὧ',
+  'ᾈ' => 'ᾀ',
+  'ᾉ' => 'ᾁ',
+  'ᾊ' => 'ᾂ',
+  'ᾋ' => 'ᾃ',
+  'ᾌ' => 'ᾄ',
+  'ᾍ' => 'ᾅ',
+  'ᾎ' => 'ᾆ',
+  'ᾏ' => 'ᾇ',
+  'ᾘ' => 'ᾐ',
+  'ᾙ' => 'ᾑ',
+  'ᾚ' => 'ᾒ',
+  'ᾛ' => 'ᾓ',
+  'ᾜ' => 'ᾔ',
+  'ᾝ' => 'ᾕ',
+  'ᾞ' => 'ᾖ',
+  'ᾟ' => 'ᾗ',
+  'ᾨ' => 'ᾠ',
+  'ᾩ' => 'ᾡ',
+  'ᾪ' => 'ᾢ',
+  'ᾫ' => 'ᾣ',
+  'ᾬ' => 'ᾤ',
+  'á¾­' => 'á¾¥',
+  'ᾮ' => 'ᾦ',
+  'ᾯ' => 'ᾧ',
+  'Ᾰ' => 'ᾰ',
+  'á¾¹' => 'á¾±',
+  'Ὰ' => 'ὰ',
+  'á¾»' => 'á½±',
+  'á¾¼' => 'á¾³',
+  'Ὲ' => 'ὲ',
+  'Έ' => 'έ',
+  'Ὴ' => 'ὴ',
+  'á¿‹' => 'á½µ',
+  'ῌ' => 'ῃ',
+  'Ῐ' => 'ῐ',
+  'á¿™' => 'á¿‘',
+  'Ὶ' => 'ὶ',
+  'á¿›' => 'á½·',
+  'Ῠ' => 'ῠ',
+  'á¿©' => 'á¿¡',
+  'Ὺ' => 'ὺ',
+  'á¿«' => 'á½»',
+  'Ῥ' => 'ῥ',
+  'Ὸ' => 'ὸ',
+  'Ό' => 'ό',
+  'Ὼ' => 'ὼ',
+  'á¿»' => 'á½½',
+  'ῼ' => 'ῳ',
+  'Ω' => 'ω',
+  'K' => 'k',
+  'â„«' => 'Ã¥',
+  'Ⅎ' => 'ⅎ',
+  'â… ' => 'â…°',
+  'â…¡' => 'â…±',
+  'â…¢' => 'â…²',
+  'â…£' => 'â…³',
+  'â…¤' => 'â…´',
+  'â…¥' => 'â…µ',
+  'â…¦' => 'â…¶',
+  'â…§' => 'â…·',
+  'â…¨' => 'â…¸',
+  'â…©' => 'â…¹',
+  'â…ª' => 'â…º',
+  'â…«' => 'â…»',
+  'â…¬' => 'â…¼',
+  'â…­' => 'â…½',
+  'â…®' => 'â…¾',
+  'â…¯' => 'â…¿',
+  'Ↄ' => 'ↄ',
+  'Ⓐ' => 'ⓐ',
+  'â’·' => 'â“‘',
+  'â’¸' => 'â“’',
+  'â’¹' => 'â““',
+  'â’º' => 'â“”',
+  'â’»' => 'â“•',
+  'â’¼' => 'â“–',
+  'â’½' => 'â“—',
+  'Ⓘ' => 'ⓘ',
+  'â’¿' => 'â“™',
+  'Ⓚ' => 'ⓚ',
+  'Ⓛ' => 'ⓛ',
+  'Ⓜ' => 'ⓜ',
+  'Ⓝ' => 'ⓝ',
+  'Ⓞ' => 'ⓞ',
+  'Ⓟ' => 'ⓟ',
+  'Ⓠ' => 'ⓠ',
+  'Ⓡ' => 'ⓡ',
+  'Ⓢ' => 'ⓢ',
+  'Ⓣ' => 'ⓣ',
+  'Ⓤ' => 'ⓤ',
+  'â“‹' => 'â“¥',
+  'Ⓦ' => 'ⓦ',
+  'Ⓧ' => 'ⓧ',
+  'Ⓨ' => 'ⓨ',
+  'Ⓩ' => 'ⓩ',
+  'â°€' => 'â°°',
+  'Ⰱ' => 'ⰱ',
+  'â°‚' => 'â°²',
+  'â°ƒ' => 'â°³',
+  'â°„' => 'â°´',
+  'â°…' => 'â°µ',
+  'â°†' => 'â°¶',
+  'â°‡' => 'â°·',
+  'â°ˆ' => 'â°¸',
+  'â°‰' => 'â°¹',
+  'â°Š' => 'â°º',
+  'â°‹' => 'â°»',
+  'Ⰼ' => 'ⰼ',
+  'Ⰽ' => 'ⰽ',
+  'â°Ž' => 'â°¾',
+  'Ⰿ' => 'ⰿ',
+  'Ⱀ' => 'ⱀ',
+  'Ⱁ' => 'ⱁ',
+  'Ⱂ' => 'ⱂ',
+  'Ⱃ' => 'ⱃ',
+  'Ⱄ' => 'ⱄ',
+  'â°•' => 'â±…',
+  'Ⱆ' => 'ⱆ',
+  'Ⱇ' => 'ⱇ',
+  'Ⱈ' => 'ⱈ',
+  'Ⱉ' => 'ⱉ',
+  'Ⱊ' => 'ⱊ',
+  'Ⱋ' => 'ⱋ',
+  'Ⱌ' => 'ⱌ',
+  'Ⱍ' => 'ⱍ',
+  'Ⱎ' => 'ⱎ',
+  'Ⱏ' => 'ⱏ',
+  'Ⱐ' => 'ⱐ',
+  'Ⱑ' => 'ⱑ',
+  'â°¢' => 'â±’',
+  'Ⱓ' => 'ⱓ',
+  'â°¤' => 'â±”',
+  'Ⱕ' => 'ⱕ',
+  'â°¦' => 'â±–',
+  'â°§' => 'â±—',
+  'Ⱘ' => 'ⱘ',
+  'â°©' => 'â±™',
+  'Ⱚ' => 'ⱚ',
+  'â°«' => 'â±›',
+  'Ⱜ' => 'ⱜ',
+  'Ⱝ' => 'ⱝ',
+  'Ⱞ' => 'ⱞ',
+  'Ⱡ' => 'ⱡ',
+  'â±¢' => 'É«',
+  'â±£' => 'áµ½',
+  'Ɽ' => 'ɽ',
+  'Ⱨ' => 'ⱨ',
+  'Ⱪ' => 'ⱪ',
+  'Ⱬ' => 'ⱬ',
+  'â±­' => 'É‘',
+  'Ɱ' => 'ɱ',
+  'Ɐ' => 'ɐ',
+  'â±°' => 'É’',
+  'â±²' => 'â±³',
+  'â±µ' => 'â±¶',
+  'â±¾' => 'È¿',
+  'Ɀ' => 'ɀ',
+  'Ⲁ' => 'ⲁ',
+  'Ⲃ' => 'ⲃ',
+  'Ⲅ' => 'ⲅ',
+  'Ⲇ' => 'ⲇ',
+  'Ⲉ' => 'ⲉ',
+  'Ⲋ' => 'ⲋ',
+  'Ⲍ' => 'ⲍ',
+  'Ⲏ' => 'ⲏ',
+  'Ⲑ' => 'ⲑ',
+  'Ⲓ' => 'ⲓ',
+  'Ⲕ' => 'ⲕ',
+  'â²–' => 'â²—',
+  'Ⲙ' => 'ⲙ',
+  'Ⲛ' => 'ⲛ',
+  'Ⲝ' => 'ⲝ',
+  'Ⲟ' => 'ⲟ',
+  'Ⲡ' => 'ⲡ',
+  'â²¢' => 'â²£',
+  'Ⲥ' => 'ⲥ',
+  'Ⲧ' => 'ⲧ',
+  'Ⲩ' => 'ⲩ',
+  'Ⲫ' => 'ⲫ',
+  'Ⲭ' => 'ⲭ',
+  'Ⲯ' => 'ⲯ',
+  'â²°' => 'â²±',
+  'â²²' => 'â²³',
+  'â²´' => 'â²µ',
+  'â²¶' => 'â²·',
+  'Ⲹ' => 'ⲹ',
+  'Ⲻ' => 'ⲻ',
+  'â²¼' => 'â²½',
+  'Ⲿ' => 'ⲿ',
+  'Ⳁ' => 'ⳁ',
+  'Ⳃ' => 'ⳃ',
+  'Ⳅ' => 'ⳅ',
+  'Ⳇ' => 'ⳇ',
+  'Ⳉ' => 'ⳉ',
+  'Ⳋ' => 'ⳋ',
+  'Ⳍ' => 'ⳍ',
+  'Ⳏ' => 'ⳏ',
+  'Ⳑ' => 'ⳑ',
+  'Ⳓ' => 'ⳓ',
+  'Ⳕ' => 'ⳕ',
+  'â³–' => 'â³—',
+  'Ⳙ' => 'ⳙ',
+  'Ⳛ' => 'ⳛ',
+  'Ⳝ' => 'ⳝ',
+  'Ⳟ' => 'ⳟ',
+  'Ⳡ' => 'ⳡ',
+  'â³¢' => 'â³£',
+  'Ⳬ' => 'ⳬ',
+  'â³­' => 'â³®',
+  'â³²' => 'â³³',
+  'Ꙁ' => 'ꙁ',
+  'Ꙃ' => 'ꙃ',
+  'Ꙅ' => 'ꙅ',
+  'Ꙇ' => 'ꙇ',
+  'Ꙉ' => 'ꙉ',
+  'Ꙋ' => 'ꙋ',
+  'Ꙍ' => 'ꙍ',
+  'Ꙏ' => 'ꙏ',
+  'Ꙑ' => 'ꙑ',
+  'Ꙓ' => 'ꙓ',
+  'Ꙕ' => 'ꙕ',
+  'ê™–' => 'ê™—',
+  'Ꙙ' => 'ꙙ',
+  'Ꙛ' => 'ꙛ',
+  'Ꙝ' => 'ꙝ',
+  'Ꙟ' => 'ꙟ',
+  'Ꙡ' => 'ꙡ',
+  'Ꙣ' => 'ꙣ',
+  'Ꙥ' => 'ꙥ',
+  'Ꙧ' => 'ꙧ',
+  'Ꙩ' => 'ꙩ',
+  'Ꙫ' => 'ꙫ',
+  'Ꙭ' => 'ꙭ',
+  'Ꚁ' => 'ꚁ',
+  'Ꚃ' => 'ꚃ',
+  'êš„' => 'êš…',
+  'Ꚇ' => 'ꚇ',
+  'Ꚉ' => 'ꚉ',
+  'Ꚋ' => 'ꚋ',
+  'Ꚍ' => 'ꚍ',
+  'Ꚏ' => 'ꚏ',
+  'Ꚑ' => 'ꚑ',
+  'êš’' => 'êš“',
+  'êš”' => 'êš•',
+  'êš–' => 'êš—',
+  'Ꚙ' => 'ꚙ',
+  'êšš' => 'êš›',
+  'Ꜣ' => 'ꜣ',
+  'Ꜥ' => 'ꜥ',
+  'Ꜧ' => 'ꜧ',
+  'Ꜩ' => 'ꜩ',
+  'Ꜫ' => 'ꜫ',
+  'Ꜭ' => 'ꜭ',
+  'Ꜯ' => 'ꜯ',
+  'Ꜳ' => 'ꜳ',
+  'Ꜵ' => 'ꜵ',
+  'Ꜷ' => 'ꜷ',
+  'Ꜹ' => 'ꜹ',
+  'Ꜻ' => 'ꜻ',
+  'Ꜽ' => 'ꜽ',
+  'Ꜿ' => 'ꜿ',
+  'Ꝁ' => 'ꝁ',
+  'Ꝃ' => 'ꝃ',
+  'Ꝅ' => 'ꝅ',
+  'Ꝇ' => 'ꝇ',
+  'Ꝉ' => 'ꝉ',
+  'Ꝋ' => 'ꝋ',
+  'Ꝍ' => 'ꝍ',
+  'Ꝏ' => 'ꝏ',
+  'Ꝑ' => 'ꝑ',
+  'Ꝓ' => 'ꝓ',
+  'Ꝕ' => 'ꝕ',
+  'Ꝗ' => 'ꝗ',
+  'Ꝙ' => 'ꝙ',
+  'Ꝛ' => 'ꝛ',
+  'Ꝝ' => 'ꝝ',
+  'Ꝟ' => 'ꝟ',
+  'Ꝡ' => 'ꝡ',
+  'Ꝣ' => 'ꝣ',
+  'Ꝥ' => 'ꝥ',
+  'Ꝧ' => 'ꝧ',
+  'Ꝩ' => 'ꝩ',
+  'Ꝫ' => 'ꝫ',
+  'Ꝭ' => 'ꝭ',
+  'Ꝯ' => 'ꝯ',
+  'Ꝺ' => 'ꝺ',
+  'Ꝼ' => 'ꝼ',
+  'Ᵹ' => 'ᵹ',
+  'Ꝿ' => 'ꝿ',
+  'Ꞁ' => 'ꞁ',
+  'Ꞃ' => 'ꞃ',
+  'êž„' => 'êž…',
+  'Ꞇ' => 'ꞇ',
+  'Ꞌ' => 'ꞌ',
+  'Ɥ' => 'ɥ',
+  'Ꞑ' => 'ꞑ',
+  'êž’' => 'êž“',
+  'êž–' => 'êž—',
+  'Ꞙ' => 'ꞙ',
+  'êžš' => 'êž›',
+  'Ꞝ' => 'ꞝ',
+  'Ꞟ' => 'ꞟ',
+  'êž ' => 'êž¡',
+  'Ꞣ' => 'ꞣ',
+  'Ꞥ' => 'ꞥ',
+  'Ꞧ' => 'ꞧ',
+  'Ꞩ' => 'ꞩ',
+  'Ɦ' => 'ɦ',
+  'Ɜ' => 'ɜ',
+  'Ɡ' => 'ɡ',
+  'Ɬ' => 'ɬ',
+  'êž°' => 'Êž',
+  'Ʇ' => 'ʇ',
+  'A' => 'a',
+  'B' => 'b',
+  'C' => 'c',
+  'D' => 'd',
+  'ï¼¥' => 'ï½…',
+  'F' => 'f',
+  'G' => 'g',
+  'H' => 'h',
+  'I' => 'i',
+  'J' => 'j',
+  'K' => 'k',
+  'L' => 'l',
+  'M' => 'm',
+  'N' => 'n',
+  'O' => 'o',
+  'P' => 'p',
+  'Q' => 'q',
+  'ï¼²' => 'ï½’',
+  'S' => 's',
+  'ï¼´' => 'ï½”',
+  'U' => 'u',
+  'ï¼¶' => 'ï½–',
+  'ï¼·' => 'ï½—',
+  'X' => 'x',
+  'ï¼¹' => 'ï½™',
+  'Z' => 'z',
+  '𐐀' => '𐐨',
+  '𐐁' => '𐐩',
+  '𐐂' => '𐐪',
+  '𐐃' => '𐐫',
+  '𐐄' => '𐐬',
+  '𐐅' => '𐐭',
+  '𐐆' => '𐐮',
+  '𐐇' => '𐐯',
+  '𐐈' => '𐐰',
+  '𐐉' => '𐐱',
+  '𐐊' => '𐐲',
+  '𐐋' => '𐐳',
+  '𐐌' => '𐐴',
+  '𐐍' => '𐐵',
+  '𐐎' => '𐐶',
+  '𐐏' => '𐐷',
+  '𐐐' => '𐐸',
+  '𐐑' => '𐐹',
+  '𐐒' => '𐐺',
+  '𐐓' => '𐐻',
+  '𐐔' => '𐐼',
+  '𐐕' => '𐐽',
+  '𐐖' => '𐐾',
+  '𐐗' => '𐐿',
+  '𐐘' => '𐑀',
+  '𐐙' => '𐑁',
+  '𐐚' => '𐑂',
+  '𐐛' => '𐑃',
+  '𐐜' => '𐑄',
+  '𐐝' => '𐑅',
+  '𐐞' => '𐑆',
+  '𐐟' => '𐑇',
+  '𐐠' => '𐑈',
+  '𐐡' => '𐑉',
+  '𐐢' => '𐑊',
+  '𐐣' => '𐑋',
+  '𐐤' => '𐑌',
+  '𐐥' => '𐑍',
+  '𐐦' => '𐑎',
+  '𐐧' => '𐑏',
+  'ð‘¢ ' => 'ð‘£€',
+  '𑢡' => '𑣁',
+  '𑢢' => '𑣂',
+  '𑢣' => '𑣃',
+  '𑢤' => '𑣄',
+  'ð‘¢¥' => 'ð‘£…',
+  '𑢦' => '𑣆',
+  '𑢧' => '𑣇',
+  '𑢨' => '𑣈',
+  '𑢩' => '𑣉',
+  '𑢪' => '𑣊',
+  '𑢫' => '𑣋',
+  '𑢬' => '𑣌',
+  '𑢭' => '𑣍',
+  '𑢮' => '𑣎',
+  '𑢯' => '𑣏',
+  '𑢰' => '𑣐',
+  '𑢱' => '𑣑',
+  'ð‘¢²' => 'ð‘£’',
+  '𑢳' => '𑣓',
+  'ð‘¢´' => 'ð‘£”',
+  '𑢵' => '𑣕',
+  'ð‘¢¶' => 'ð‘£–',
+  'ð‘¢·' => 'ð‘£—',
+  '𑢸' => '𑣘',
+  'ð‘¢¹' => 'ð‘£™',
+  '𑢺' => '𑣚',
+  'ð‘¢»' => 'ð‘£›',
+  '𑢼' => '𑣜',
+  '𑢽' => '𑣝',
+  '𑢾' => '𑣞',
+  '𑢿' => '𑣟',
+);
+
+$result =& $data;
+unset($data);
+
+return $result;
diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php
new file mode 100644
index 0000000000000000000000000000000000000000..ec9422121cae73c8baa8d0cfc695897a5c091961
--- /dev/null
+++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php
@@ -0,0 +1,1109 @@
+<?php
+
+static $data = array (
+  'a' => 'A',
+  'b' => 'B',
+  'c' => 'C',
+  'd' => 'D',
+  'e' => 'E',
+  'f' => 'F',
+  'g' => 'G',
+  'h' => 'H',
+  'i' => 'I',
+  'j' => 'J',
+  'k' => 'K',
+  'l' => 'L',
+  'm' => 'M',
+  'n' => 'N',
+  'o' => 'O',
+  'p' => 'P',
+  'q' => 'Q',
+  'r' => 'R',
+  's' => 'S',
+  't' => 'T',
+  'u' => 'U',
+  'v' => 'V',
+  'w' => 'W',
+  'x' => 'X',
+  'y' => 'Y',
+  'z' => 'Z',
+  'µ' => 'Μ',
+  'à' => 'À',
+  'á' => 'Á',
+  'â' => 'Â',
+  'ã' => 'Ã',
+  'ä' => 'Ä',
+  'Ã¥' => 'Ã…',
+  'æ' => 'Æ',
+  'ç' => 'Ç',
+  'è' => 'È',
+  'é' => 'É',
+  'ê' => 'Ê',
+  'ë' => 'Ë',
+  'ì' => 'Ì',
+  'í' => 'Í',
+  'î' => 'Î',
+  'ï' => 'Ï',
+  'ð' => 'Ð',
+  'ñ' => 'Ñ',
+  'ò' => 'Ò',
+  'ó' => 'Ó',
+  'ô' => 'Ô',
+  'õ' => 'Õ',
+  'ö' => 'Ö',
+  'ø' => 'Ø',
+  'ù' => 'Ù',
+  'ú' => 'Ú',
+  'û' => 'Û',
+  'ü' => 'Ü',
+  'ý' => 'Ý',
+  'þ' => 'Þ',
+  'ÿ' => 'Ÿ',
+  'ā' => 'Ā',
+  'ă' => 'Ă',
+  'Ä…' => 'Ä„',
+  'ć' => 'Ć',
+  'ĉ' => 'Ĉ',
+  'Ä‹' => 'ÄŠ',
+  'č' => 'Č',
+  'ď' => 'Ď',
+  'đ' => 'Đ',
+  'Ä“' => 'Ä’',
+  'Ä•' => 'Ä”',
+  'Ä—' => 'Ä–',
+  'ę' => 'Ę',
+  'Ä›' => 'Äš',
+  'ĝ' => 'Ĝ',
+  'ÄŸ' => 'Äž',
+  'Ä¡' => 'Ä ',
+  'Ä£' => 'Ä¢',
+  'ĥ' => 'Ĥ',
+  'ħ' => 'Ħ',
+  'ĩ' => 'Ĩ',
+  'ī' => 'Ī',
+  'ĭ' => 'Ĭ',
+  'į' => 'Į',
+  'ı' => 'I',
+  'ij' => 'IJ',
+  'ĵ' => 'Ĵ',
+  'ķ' => 'Ķ',
+  'ĺ' => 'Ĺ',
+  'ļ' => 'Ļ',
+  'ľ' => 'Ľ',
+  'Å€' => 'Ä¿',
+  'ł' => 'Ł',
+  'ń' => 'Ń',
+  'ņ' => 'Ņ',
+  'ň' => 'Ň',
+  'Å‹' => 'ÅŠ',
+  'ō' => 'Ō',
+  'ŏ' => 'Ŏ',
+  'ő' => 'Ő',
+  'Å“' => 'Å’',
+  'Å•' => 'Å”',
+  'Å—' => 'Å–',
+  'ř' => 'Ř',
+  'Å›' => 'Åš',
+  'ŝ' => 'Ŝ',
+  'ÅŸ' => 'Åž',
+  'Å¡' => 'Å ',
+  'Å£' => 'Å¢',
+  'ť' => 'Ť',
+  'ŧ' => 'Ŧ',
+  'ũ' => 'Ũ',
+  'ū' => 'Ū',
+  'ŭ' => 'Ŭ',
+  'ů' => 'Ů',
+  'ű' => 'Ű',
+  'ų' => 'Ų',
+  'ŵ' => 'Ŵ',
+  'ŷ' => 'Ŷ',
+  'ź' => 'Ź',
+  'ż' => 'Ż',
+  'ž' => 'Ž',
+  'Å¿' => 'S',
+  'ƀ' => 'Ƀ',
+  'ƃ' => 'Ƃ',
+  'Æ…' => 'Æ„',
+  'ƈ' => 'Ƈ',
+  'ƌ' => 'Ƌ',
+  'Æ’' => 'Æ‘',
+  'ƕ' => 'Ƕ',
+  'ƙ' => 'Ƙ',
+  'ƚ' => 'Ƚ',
+  'Æž' => 'È ',
+  'Æ¡' => 'Æ ',
+  'Æ£' => 'Æ¢',
+  'ƥ' => 'Ƥ',
+  'ƨ' => 'Ƨ',
+  'ƭ' => 'Ƭ',
+  'ư' => 'Ư',
+  'ƴ' => 'Ƴ',
+  'ƶ' => 'Ƶ',
+  'ƹ' => 'Ƹ',
+  'ƽ' => 'Ƽ',
+  'Æ¿' => 'Ç·',
+  'Ç…' => 'Ç„',
+  'dž' => 'DŽ',
+  'Lj' => 'LJ',
+  'lj' => 'LJ',
+  'Ç‹' => 'ÇŠ',
+  'nj' => 'NJ',
+  'ǎ' => 'Ǎ',
+  'ǐ' => 'Ǐ',
+  'Ç’' => 'Ç‘',
+  'Ç”' => 'Ç“',
+  'Ç–' => 'Ç•',
+  'ǘ' => 'Ǘ',
+  'Çš' => 'Ç™',
+  'ǜ' => 'Ǜ',
+  'ǝ' => 'Ǝ',
+  'ÇŸ' => 'Çž',
+  'Ç¡' => 'Ç ',
+  'Ç£' => 'Ç¢',
+  'ǥ' => 'Ǥ',
+  'ǧ' => 'Ǧ',
+  'ǩ' => 'Ǩ',
+  'ǫ' => 'Ǫ',
+  'ǭ' => 'Ǭ',
+  'ǯ' => 'Ǯ',
+  'Dz' => 'DZ',
+  'dz' => 'DZ',
+  'ǵ' => 'Ǵ',
+  'ǹ' => 'Ǹ',
+  'ǻ' => 'Ǻ',
+  'ǽ' => 'Ǽ',
+  'ǿ' => 'Ǿ',
+  'ȁ' => 'Ȁ',
+  'ȃ' => 'Ȃ',
+  'È…' => 'È„',
+  'ȇ' => 'Ȇ',
+  'ȉ' => 'Ȉ',
+  'È‹' => 'ÈŠ',
+  'ȍ' => 'Ȍ',
+  'ȏ' => 'Ȏ',
+  'ȑ' => 'Ȑ',
+  'È“' => 'È’',
+  'È•' => 'È”',
+  'È—' => 'È–',
+  'ș' => 'Ș',
+  'È›' => 'Èš',
+  'ȝ' => 'Ȝ',
+  'ÈŸ' => 'Èž',
+  'È£' => 'È¢',
+  'ȥ' => 'Ȥ',
+  'ȧ' => 'Ȧ',
+  'ȩ' => 'Ȩ',
+  'ȫ' => 'Ȫ',
+  'ȭ' => 'Ȭ',
+  'ȯ' => 'Ȯ',
+  'ȱ' => 'Ȱ',
+  'ȳ' => 'Ȳ',
+  'ȼ' => 'Ȼ',
+  'È¿' => 'â±¾',
+  'ɀ' => 'Ɀ',
+  'ɂ' => 'Ɂ',
+  'ɇ' => 'Ɇ',
+  'ɉ' => 'Ɉ',
+  'É‹' => 'ÉŠ',
+  'ɍ' => 'Ɍ',
+  'ɏ' => 'Ɏ',
+  'ɐ' => 'Ɐ',
+  'É‘' => 'â±­',
+  'É’' => 'â±°',
+  'ɓ' => 'Ɓ',
+  'ɔ' => 'Ɔ',
+  'ɖ' => 'Ɖ',
+  'É—' => 'ÆŠ',
+  'ə' => 'Ə',
+  'ɛ' => 'Ɛ',
+  'ɜ' => 'Ɜ',
+  'É ' => 'Æ“',
+  'ɡ' => 'Ɡ',
+  'É£' => 'Æ”',
+  'ɥ' => 'Ɥ',
+  'ɦ' => 'Ɦ',
+  'ɨ' => 'Ɨ',
+  'É©' => 'Æ–',
+  'É«' => 'â±¢',
+  'ɬ' => 'Ɬ',
+  'ɯ' => 'Ɯ',
+  'ɱ' => 'Ɱ',
+  'ɲ' => 'Ɲ',
+  'ɵ' => 'Ɵ',
+  'ɽ' => 'Ɽ',
+  'ʀ' => 'Ʀ',
+  'ʃ' => 'Ʃ',
+  'ʇ' => 'Ʇ',
+  'ʈ' => 'Ʈ',
+  'ʉ' => 'Ʉ',
+  'ʊ' => 'Ʊ',
+  'ʋ' => 'Ʋ',
+  'ʌ' => 'Ʌ',
+  'Ê’' => 'Æ·',
+  'Êž' => 'êž°',
+  'ͅ' => 'Ι',
+  'ͱ' => 'Ͱ',
+  'ͳ' => 'Ͳ',
+  'ͷ' => 'Ͷ',
+  'ͻ' => 'Ͻ',
+  'ͼ' => 'Ͼ',
+  'ͽ' => 'Ͽ',
+  'ά' => 'Ά',
+  'έ' => 'Έ',
+  'ή' => 'Ή',
+  'ί' => 'Ί',
+  'α' => 'Α',
+  'β' => 'Β',
+  'γ' => 'Γ',
+  'δ' => 'Δ',
+  'ε' => 'Ε',
+  'ζ' => 'Ζ',
+  'η' => 'Η',
+  'θ' => 'Θ',
+  'ι' => 'Ι',
+  'κ' => 'Κ',
+  'λ' => 'Λ',
+  'μ' => 'Μ',
+  'ν' => 'Ν',
+  'ξ' => 'Ξ',
+  'ο' => 'Ο',
+  'π' => 'Π',
+  'ρ' => 'Ρ',
+  'ς' => 'Σ',
+  'σ' => 'Σ',
+  'τ' => 'Τ',
+  'Ï…' => 'Î¥',
+  'φ' => 'Φ',
+  'χ' => 'Χ',
+  'ψ' => 'Ψ',
+  'ω' => 'Ω',
+  'ϊ' => 'Ϊ',
+  'ϋ' => 'Ϋ',
+  'ό' => 'Ό',
+  'ύ' => 'Ύ',
+  'ώ' => 'Ώ',
+  'ϐ' => 'Β',
+  'ϑ' => 'Θ',
+  'ϕ' => 'Φ',
+  'ϖ' => 'Π',
+  'ϗ' => 'Ϗ',
+  'ϙ' => 'Ϙ',
+  'Ï›' => 'Ïš',
+  'ϝ' => 'Ϝ',
+  'ÏŸ' => 'Ïž',
+  'Ï¡' => 'Ï ',
+  'Ï£' => 'Ï¢',
+  'ϥ' => 'Ϥ',
+  'ϧ' => 'Ϧ',
+  'ϩ' => 'Ϩ',
+  'ϫ' => 'Ϫ',
+  'ϭ' => 'Ϭ',
+  'ϯ' => 'Ϯ',
+  'ϰ' => 'Κ',
+  'ϱ' => 'Ρ',
+  'ϲ' => 'Ϲ',
+  'ϳ' => 'Ϳ',
+  'ϵ' => 'Ε',
+  'ϸ' => 'Ϸ',
+  'ϻ' => 'Ϻ',
+  'а' => 'А',
+  'б' => 'Б',
+  'в' => 'В',
+  'г' => 'Г',
+  'д' => 'Д',
+  'е' => 'Е',
+  'ж' => 'Ж',
+  'з' => 'З',
+  'и' => 'И',
+  'й' => 'Й',
+  'к' => 'К',
+  'л' => 'Л',
+  'м' => 'М',
+  'н' => 'Н',
+  'о' => 'О',
+  'п' => 'П',
+  'р' => 'Р',
+  'с' => 'С',
+  'т' => 'Т',
+  'у' => 'У',
+  'ф' => 'Ф',
+  'Ñ…' => 'Ð¥',
+  'ц' => 'Ц',
+  'ч' => 'Ч',
+  'ш' => 'Ш',
+  'щ' => 'Щ',
+  'ъ' => 'Ъ',
+  'ы' => 'Ы',
+  'ь' => 'Ь',
+  'э' => 'Э',
+  'ю' => 'Ю',
+  'я' => 'Я',
+  'ѐ' => 'Ѐ',
+  'ё' => 'Ё',
+  'ђ' => 'Ђ',
+  'ѓ' => 'Ѓ',
+  'є' => 'Є',
+  'Ñ•' => 'Ð…',
+  'і' => 'І',
+  'ї' => 'Ї',
+  'ј' => 'Ј',
+  'љ' => 'Љ',
+  'њ' => 'Њ',
+  'ћ' => 'Ћ',
+  'ќ' => 'Ќ',
+  'ѝ' => 'Ѝ',
+  'Ñž' => 'ÐŽ',
+  'џ' => 'Џ',
+  'Ñ¡' => 'Ñ ',
+  'Ñ£' => 'Ñ¢',
+  'ѥ' => 'Ѥ',
+  'ѧ' => 'Ѧ',
+  'ѩ' => 'Ѩ',
+  'ѫ' => 'Ѫ',
+  'ѭ' => 'Ѭ',
+  'ѯ' => 'Ѯ',
+  'ѱ' => 'Ѱ',
+  'ѳ' => 'Ѳ',
+  'ѵ' => 'Ѵ',
+  'ѷ' => 'Ѷ',
+  'ѹ' => 'Ѹ',
+  'ѻ' => 'Ѻ',
+  'ѽ' => 'Ѽ',
+  'ѿ' => 'Ѿ',
+  'ҁ' => 'Ҁ',
+  'Ò‹' => 'ÒŠ',
+  'ҍ' => 'Ҍ',
+  'ҏ' => 'Ҏ',
+  'ґ' => 'Ґ',
+  'Ò“' => 'Ò’',
+  'Ò•' => 'Ò”',
+  'Ò—' => 'Ò–',
+  'Ò™' => 'Ò˜',
+  'Ò›' => 'Òš',
+  'ҝ' => 'Ҝ',
+  'ÒŸ' => 'Òž',
+  'Ò¡' => 'Ò ',
+  'Ò£' => 'Ò¢',
+  'Ò¥' => 'Ò¤',
+  'Ò§' => 'Ò¦',
+  'Ò©' => 'Ò¨',
+  'Ò«' => 'Òª',
+  'Ò­' => 'Ò¬',
+  'Ò¯' => 'Ò®',
+  'Ò±' => 'Ò°',
+  'Ò³' => 'Ò²',
+  'Òµ' => 'Ò´',
+  'Ò·' => 'Ò¶',
+  'Ò¹' => 'Ò¸',
+  'Ò»' => 'Òº',
+  'Ò½' => 'Ò¼',
+  'Ò¿' => 'Ò¾',
+  'ӂ' => 'Ӂ',
+  'Ó„' => 'Óƒ',
+  'Ó†' => 'Ó…',
+  'Óˆ' => 'Ó‡',
+  'ÓŠ' => 'Ó‰',
+  'ӌ' => 'Ӌ',
+  'ӎ' => 'Ӎ',
+  'ӏ' => 'Ӏ',
+  'ӑ' => 'Ӑ',
+  'Ó“' => 'Ó’',
+  'Ó•' => 'Ó”',
+  'Ó—' => 'Ó–',
+  'Ó™' => 'Ó˜',
+  'Ó›' => 'Óš',
+  'ӝ' => 'Ӝ',
+  'ÓŸ' => 'Óž',
+  'Ó¡' => 'Ó ',
+  'Ó£' => 'Ó¢',
+  'Ó¥' => 'Ó¤',
+  'Ó§' => 'Ó¦',
+  'Ó©' => 'Ó¨',
+  'Ó«' => 'Óª',
+  'Ó­' => 'Ó¬',
+  'Ó¯' => 'Ó®',
+  'Ó±' => 'Ó°',
+  'Ó³' => 'Ó²',
+  'Óµ' => 'Ó´',
+  'Ó·' => 'Ó¶',
+  'Ó¹' => 'Ó¸',
+  'Ó»' => 'Óº',
+  'Ó½' => 'Ó¼',
+  'Ó¿' => 'Ó¾',
+  'ԁ' => 'Ԁ',
+  'Ôƒ' => 'Ô‚',
+  'Ô…' => 'Ô„',
+  'Ô‡' => 'Ô†',
+  'Ô‰' => 'Ôˆ',
+  'Ô‹' => 'ÔŠ',
+  'ԍ' => 'Ԍ',
+  'ԏ' => 'Ԏ',
+  'ԑ' => 'Ԑ',
+  'Ô“' => 'Ô’',
+  'Ô•' => 'Ô”',
+  'Ô—' => 'Ô–',
+  'Ô™' => 'Ô˜',
+  'Ô›' => 'Ôš',
+  'ԝ' => 'Ԝ',
+  'ÔŸ' => 'Ôž',
+  'Ô¡' => 'Ô ',
+  'Ô£' => 'Ô¢',
+  'Ô¥' => 'Ô¤',
+  'Ô§' => 'Ô¦',
+  'Ô©' => 'Ô¨',
+  'Ô«' => 'Ôª',
+  'Ô­' => 'Ô¬',
+  'Ô¯' => 'Ô®',
+  'Õ¡' => 'Ô±',
+  'Õ¢' => 'Ô²',
+  'Õ£' => 'Ô³',
+  'Õ¤' => 'Ô´',
+  'Õ¥' => 'Ôµ',
+  'Õ¦' => 'Ô¶',
+  'Õ§' => 'Ô·',
+  'Õ¨' => 'Ô¸',
+  'Õ©' => 'Ô¹',
+  'Õª' => 'Ôº',
+  'Õ«' => 'Ô»',
+  'Õ¬' => 'Ô¼',
+  'Õ­' => 'Ô½',
+  'Õ®' => 'Ô¾',
+  'Õ¯' => 'Ô¿',
+  'Õ°' => 'Õ€',
+  'ձ' => 'Ձ',
+  'Õ²' => 'Õ‚',
+  'Õ³' => 'Õƒ',
+  'Õ´' => 'Õ„',
+  'Õµ' => 'Õ…',
+  'Õ¶' => 'Õ†',
+  'Õ·' => 'Õ‡',
+  'Õ¸' => 'Õˆ',
+  'Õ¹' => 'Õ‰',
+  'Õº' => 'ÕŠ',
+  'Õ»' => 'Õ‹',
+  'ռ' => 'Ռ',
+  'ս' => 'Ս',
+  'Õ¾' => 'ÕŽ',
+  'տ' => 'Տ',
+  'ր' => 'Ր',
+  'ց' => 'Ց',
+  'Ö‚' => 'Õ’',
+  'Öƒ' => 'Õ“',
+  'Ö„' => 'Õ”',
+  'Ö…' => 'Õ•',
+  'Ö†' => 'Õ–',
+  'ᵹ' => 'Ᵹ',
+  'áµ½' => 'â±£',
+  'ḁ' => 'Ḁ',
+  'ḃ' => 'Ḃ',
+  'ḅ' => 'Ḅ',
+  'ḇ' => 'Ḇ',
+  'ḉ' => 'Ḉ',
+  'ḋ' => 'Ḋ',
+  'ḍ' => 'Ḍ',
+  'ḏ' => 'Ḏ',
+  'ḑ' => 'Ḑ',
+  'ḓ' => 'Ḓ',
+  'ḕ' => 'Ḕ',
+  'ḗ' => 'Ḗ',
+  'ḙ' => 'Ḙ',
+  'ḛ' => 'Ḛ',
+  'ḝ' => 'Ḝ',
+  'ḟ' => 'Ḟ',
+  'ḡ' => 'Ḡ',
+  'ḣ' => 'Ḣ',
+  'ḥ' => 'Ḥ',
+  'ḧ' => 'Ḧ',
+  'ḩ' => 'Ḩ',
+  'ḫ' => 'Ḫ',
+  'ḭ' => 'Ḭ',
+  'ḯ' => 'Ḯ',
+  'ḱ' => 'Ḱ',
+  'ḳ' => 'Ḳ',
+  'ḵ' => 'Ḵ',
+  'ḷ' => 'Ḷ',
+  'ḹ' => 'Ḹ',
+  'ḻ' => 'Ḻ',
+  'ḽ' => 'Ḽ',
+  'ḿ' => 'Ḿ',
+  'ṁ' => 'Ṁ',
+  'ṃ' => 'Ṃ',
+  'ṅ' => 'Ṅ',
+  'ṇ' => 'Ṇ',
+  'ṉ' => 'Ṉ',
+  'ṋ' => 'Ṋ',
+  'ṍ' => 'Ṍ',
+  'ṏ' => 'Ṏ',
+  'ṑ' => 'Ṑ',
+  'ṓ' => 'Ṓ',
+  'ṕ' => 'Ṕ',
+  'á¹—' => 'á¹–',
+  'ṙ' => 'Ṙ',
+  'ṛ' => 'Ṛ',
+  'ṝ' => 'Ṝ',
+  'ṟ' => 'Ṟ',
+  'ṡ' => 'Ṡ',
+  'á¹£' => 'á¹¢',
+  'ṥ' => 'Ṥ',
+  'ṧ' => 'Ṧ',
+  'ṩ' => 'Ṩ',
+  'ṫ' => 'Ṫ',
+  'ṭ' => 'Ṭ',
+  'ṯ' => 'Ṯ',
+  'á¹±' => 'á¹°',
+  'á¹³' => 'á¹²',
+  'á¹µ' => 'á¹´',
+  'á¹·' => 'á¹¶',
+  'ṹ' => 'Ṹ',
+  'ṻ' => 'Ṻ',
+  'á¹½' => 'á¹¼',
+  'ṿ' => 'Ṿ',
+  'ẁ' => 'Ẁ',
+  'ẃ' => 'Ẃ',
+  'ẅ' => 'Ẅ',
+  'ẇ' => 'Ẇ',
+  'ẉ' => 'Ẉ',
+  'ẋ' => 'Ẋ',
+  'ẍ' => 'Ẍ',
+  'ẏ' => 'Ẏ',
+  'ẑ' => 'Ẑ',
+  'ẓ' => 'Ẓ',
+  'ẕ' => 'Ẕ',
+  'ẛ' => 'Ṡ',
+  'ạ' => 'Ạ',
+  'ả' => 'Ả',
+  'ấ' => 'Ấ',
+  'ầ' => 'Ầ',
+  'ẩ' => 'Ẩ',
+  'ẫ' => 'Ẫ',
+  'ậ' => 'Ậ',
+  'ắ' => 'Ắ',
+  'ằ' => 'Ằ',
+  'ẳ' => 'Ẳ',
+  'ẵ' => 'Ẵ',
+  'ặ' => 'Ặ',
+  'ẹ' => 'Ẹ',
+  'ẻ' => 'Ẻ',
+  'ẽ' => 'Ẽ',
+  'ế' => 'Ế',
+  'ề' => 'Ề',
+  'ể' => 'Ể',
+  'ễ' => 'Ễ',
+  'ệ' => 'Ệ',
+  'ỉ' => 'Ỉ',
+  'ị' => 'Ị',
+  'ọ' => 'Ọ',
+  'ỏ' => 'Ỏ',
+  'ố' => 'Ố',
+  'ồ' => 'Ồ',
+  'ổ' => 'Ổ',
+  'á»—' => 'á»–',
+  'ộ' => 'Ộ',
+  'ớ' => 'Ớ',
+  'ờ' => 'Ờ',
+  'ở' => 'Ở',
+  'ỡ' => 'Ỡ',
+  'ợ' => 'Ợ',
+  'ụ' => 'Ụ',
+  'ủ' => 'Ủ',
+  'ứ' => 'Ứ',
+  'ừ' => 'Ừ',
+  'ử' => 'Ử',
+  'ữ' => 'Ữ',
+  'á»±' => 'á»°',
+  'ỳ' => 'Ỳ',
+  'ỵ' => 'Ỵ',
+  'á»·' => 'á»¶',
+  'ỹ' => 'Ỹ',
+  'ỻ' => 'Ỻ',
+  'ỽ' => 'Ỽ',
+  'ỿ' => 'Ỿ',
+  'ἀ' => 'Ἀ',
+  'ἁ' => 'Ἁ',
+  'ἂ' => 'Ἂ',
+  'ἃ' => 'Ἃ',
+  'ἄ' => 'Ἄ',
+  'ἅ' => 'Ἅ',
+  'ἆ' => 'Ἆ',
+  'ἇ' => 'Ἇ',
+  'ἐ' => 'Ἐ',
+  'ἑ' => 'Ἑ',
+  'ἒ' => 'Ἒ',
+  'ἓ' => 'Ἓ',
+  'ἔ' => 'Ἔ',
+  'ἕ' => 'Ἕ',
+  'ἠ' => 'Ἠ',
+  'ἡ' => 'Ἡ',
+  'ἢ' => 'Ἢ',
+  'ἣ' => 'Ἣ',
+  'ἤ' => 'Ἤ',
+  'á¼¥' => 'á¼­',
+  'ἦ' => 'Ἦ',
+  'ἧ' => 'Ἧ',
+  'ἰ' => 'Ἰ',
+  'á¼±' => 'á¼¹',
+  'ἲ' => 'Ἲ',
+  'á¼³' => 'á¼»',
+  'á¼´' => 'á¼¼',
+  'á¼µ' => 'á¼½',
+  'á¼¶' => 'á¼¾',
+  'ἷ' => 'Ἷ',
+  'ὀ' => 'Ὀ',
+  'ὁ' => 'Ὁ',
+  'ὂ' => 'Ὂ',
+  'ὃ' => 'Ὃ',
+  'ὄ' => 'Ὄ',
+  'ὅ' => 'Ὅ',
+  'ὑ' => 'Ὑ',
+  'ὓ' => 'Ὓ',
+  'ὕ' => 'Ὕ',
+  'ὗ' => 'Ὗ',
+  'ὠ' => 'Ὠ',
+  'ὡ' => 'Ὡ',
+  'ὢ' => 'Ὢ',
+  'ὣ' => 'Ὣ',
+  'ὤ' => 'Ὤ',
+  'á½¥' => 'á½­',
+  'ὦ' => 'Ὦ',
+  'ὧ' => 'Ὧ',
+  'ὰ' => 'Ὰ',
+  'á½±' => 'á¾»',
+  'ὲ' => 'Ὲ',
+  'έ' => 'Έ',
+  'ὴ' => 'Ὴ',
+  'á½µ' => 'á¿‹',
+  'ὶ' => 'Ὶ',
+  'á½·' => 'á¿›',
+  'ὸ' => 'Ὸ',
+  'ό' => 'Ό',
+  'ὺ' => 'Ὺ',
+  'á½»' => 'á¿«',
+  'ὼ' => 'Ὼ',
+  'á½½' => 'á¿»',
+  'ᾀ' => 'ᾈ',
+  'ᾁ' => 'ᾉ',
+  'ᾂ' => 'ᾊ',
+  'ᾃ' => 'ᾋ',
+  'ᾄ' => 'ᾌ',
+  'ᾅ' => 'ᾍ',
+  'ᾆ' => 'ᾎ',
+  'ᾇ' => 'ᾏ',
+  'ᾐ' => 'ᾘ',
+  'ᾑ' => 'ᾙ',
+  'ᾒ' => 'ᾚ',
+  'ᾓ' => 'ᾛ',
+  'ᾔ' => 'ᾜ',
+  'ᾕ' => 'ᾝ',
+  'ᾖ' => 'ᾞ',
+  'ᾗ' => 'ᾟ',
+  'ᾠ' => 'ᾨ',
+  'ᾡ' => 'ᾩ',
+  'ᾢ' => 'ᾪ',
+  'ᾣ' => 'ᾫ',
+  'ᾤ' => 'ᾬ',
+  'á¾¥' => 'á¾­',
+  'ᾦ' => 'ᾮ',
+  'ᾧ' => 'ᾯ',
+  'ᾰ' => 'Ᾰ',
+  'á¾±' => 'á¾¹',
+  'á¾³' => 'á¾¼',
+  'ι' => 'Ι',
+  'ῃ' => 'ῌ',
+  'ῐ' => 'Ῐ',
+  'á¿‘' => 'á¿™',
+  'ῠ' => 'Ῠ',
+  'á¿¡' => 'á¿©',
+  'ῥ' => 'Ῥ',
+  'ῳ' => 'ῼ',
+  'ⅎ' => 'Ⅎ',
+  'â…°' => 'â… ',
+  'â…±' => 'â…¡',
+  'â…²' => 'â…¢',
+  'â…³' => 'â…£',
+  'â…´' => 'â…¤',
+  'â…µ' => 'â…¥',
+  'â…¶' => 'â…¦',
+  'â…·' => 'â…§',
+  'â…¸' => 'â…¨',
+  'â…¹' => 'â…©',
+  'â…º' => 'â…ª',
+  'â…»' => 'â…«',
+  'â…¼' => 'â…¬',
+  'â…½' => 'â…­',
+  'â…¾' => 'â…®',
+  'â…¿' => 'â…¯',
+  'ↄ' => 'Ↄ',
+  'ⓐ' => 'Ⓐ',
+  'â“‘' => 'â’·',
+  'â“’' => 'â’¸',
+  'â““' => 'â’¹',
+  'â“”' => 'â’º',
+  'â“•' => 'â’»',
+  'â“–' => 'â’¼',
+  'â“—' => 'â’½',
+  'ⓘ' => 'Ⓘ',
+  'â“™' => 'â’¿',
+  'ⓚ' => 'Ⓚ',
+  'ⓛ' => 'Ⓛ',
+  'ⓜ' => 'Ⓜ',
+  'ⓝ' => 'Ⓝ',
+  'ⓞ' => 'Ⓞ',
+  'ⓟ' => 'Ⓟ',
+  'ⓠ' => 'Ⓠ',
+  'ⓡ' => 'Ⓡ',
+  'ⓢ' => 'Ⓢ',
+  'ⓣ' => 'Ⓣ',
+  'ⓤ' => 'Ⓤ',
+  'â“¥' => 'â“‹',
+  'ⓦ' => 'Ⓦ',
+  'ⓧ' => 'Ⓧ',
+  'ⓨ' => 'Ⓨ',
+  'ⓩ' => 'Ⓩ',
+  'â°°' => 'â°€',
+  'ⰱ' => 'Ⰱ',
+  'â°²' => 'â°‚',
+  'â°³' => 'â°ƒ',
+  'â°´' => 'â°„',
+  'â°µ' => 'â°…',
+  'â°¶' => 'â°†',
+  'â°·' => 'â°‡',
+  'â°¸' => 'â°ˆ',
+  'â°¹' => 'â°‰',
+  'â°º' => 'â°Š',
+  'â°»' => 'â°‹',
+  'ⰼ' => 'Ⰼ',
+  'ⰽ' => 'Ⰽ',
+  'â°¾' => 'â°Ž',
+  'ⰿ' => 'Ⰿ',
+  'ⱀ' => 'Ⱀ',
+  'ⱁ' => 'Ⱁ',
+  'ⱂ' => 'Ⱂ',
+  'ⱃ' => 'Ⱃ',
+  'ⱄ' => 'Ⱄ',
+  'â±…' => 'â°•',
+  'ⱆ' => 'Ⱆ',
+  'ⱇ' => 'Ⱇ',
+  'ⱈ' => 'Ⱈ',
+  'ⱉ' => 'Ⱉ',
+  'ⱊ' => 'Ⱊ',
+  'ⱋ' => 'Ⱋ',
+  'ⱌ' => 'Ⱌ',
+  'ⱍ' => 'Ⱍ',
+  'ⱎ' => 'Ⱎ',
+  'ⱏ' => 'Ⱏ',
+  'ⱐ' => 'Ⱐ',
+  'ⱑ' => 'Ⱑ',
+  'â±’' => 'â°¢',
+  'ⱓ' => 'Ⱓ',
+  'â±”' => 'â°¤',
+  'ⱕ' => 'Ⱕ',
+  'â±–' => 'â°¦',
+  'â±—' => 'â°§',
+  'ⱘ' => 'Ⱘ',
+  'â±™' => 'â°©',
+  'ⱚ' => 'Ⱚ',
+  'â±›' => 'â°«',
+  'ⱜ' => 'Ⱜ',
+  'ⱝ' => 'Ⱝ',
+  'ⱞ' => 'Ⱞ',
+  'ⱡ' => 'Ⱡ',
+  'ⱥ' => 'Ⱥ',
+  'ⱦ' => 'Ⱦ',
+  'ⱨ' => 'Ⱨ',
+  'ⱪ' => 'Ⱪ',
+  'ⱬ' => 'Ⱬ',
+  'â±³' => 'â±²',
+  'â±¶' => 'â±µ',
+  'ⲁ' => 'Ⲁ',
+  'ⲃ' => 'Ⲃ',
+  'ⲅ' => 'Ⲅ',
+  'ⲇ' => 'Ⲇ',
+  'ⲉ' => 'Ⲉ',
+  'ⲋ' => 'Ⲋ',
+  'ⲍ' => 'Ⲍ',
+  'ⲏ' => 'Ⲏ',
+  'ⲑ' => 'Ⲑ',
+  'ⲓ' => 'Ⲓ',
+  'ⲕ' => 'Ⲕ',
+  'â²—' => 'â²–',
+  'ⲙ' => 'Ⲙ',
+  'ⲛ' => 'Ⲛ',
+  'ⲝ' => 'Ⲝ',
+  'ⲟ' => 'Ⲟ',
+  'ⲡ' => 'Ⲡ',
+  'â²£' => 'â²¢',
+  'ⲥ' => 'Ⲥ',
+  'ⲧ' => 'Ⲧ',
+  'ⲩ' => 'Ⲩ',
+  'ⲫ' => 'Ⲫ',
+  'ⲭ' => 'Ⲭ',
+  'ⲯ' => 'Ⲯ',
+  'â²±' => 'â²°',
+  'â²³' => 'â²²',
+  'â²µ' => 'â²´',
+  'â²·' => 'â²¶',
+  'ⲹ' => 'Ⲹ',
+  'ⲻ' => 'Ⲻ',
+  'â²½' => 'â²¼',
+  'ⲿ' => 'Ⲿ',
+  'ⳁ' => 'Ⳁ',
+  'ⳃ' => 'Ⳃ',
+  'ⳅ' => 'Ⳅ',
+  'ⳇ' => 'Ⳇ',
+  'ⳉ' => 'Ⳉ',
+  'ⳋ' => 'Ⳋ',
+  'ⳍ' => 'Ⳍ',
+  'ⳏ' => 'Ⳏ',
+  'ⳑ' => 'Ⳑ',
+  'ⳓ' => 'Ⳓ',
+  'ⳕ' => 'Ⳕ',
+  'â³—' => 'â³–',
+  'ⳙ' => 'Ⳙ',
+  'ⳛ' => 'Ⳛ',
+  'ⳝ' => 'Ⳝ',
+  'ⳟ' => 'Ⳟ',
+  'ⳡ' => 'Ⳡ',
+  'â³£' => 'â³¢',
+  'ⳬ' => 'Ⳬ',
+  'â³®' => 'â³­',
+  'â³³' => 'â³²',
+  'â´€' => 'á‚ ',
+  'ⴁ' => 'Ⴁ',
+  'â´‚' => 'á‚¢',
+  'â´ƒ' => 'á‚£',
+  'ⴄ' => 'Ⴄ',
+  'â´…' => 'á‚¥',
+  'ⴆ' => 'Ⴆ',
+  'â´‡' => 'á‚§',
+  'ⴈ' => 'Ⴈ',
+  'â´‰' => 'á‚©',
+  'ⴊ' => 'Ⴊ',
+  'â´‹' => 'á‚«',
+  'ⴌ' => 'Ⴌ',
+  'ⴍ' => 'Ⴍ',
+  'â´Ž' => 'á‚®',
+  'ⴏ' => 'Ⴏ',
+  'ⴐ' => 'Ⴐ',
+  'ⴑ' => 'Ⴑ',
+  'ⴒ' => 'Ⴒ',
+  'ⴓ' => 'Ⴓ',
+  'â´”' => 'á‚´',
+  'ⴕ' => 'Ⴕ',
+  'â´–' => 'á‚¶',
+  'â´—' => 'á‚·',
+  'ⴘ' => 'Ⴘ',
+  'ⴙ' => 'Ⴙ',
+  'ⴚ' => 'Ⴚ',
+  'â´›' => 'á‚»',
+  'ⴜ' => 'Ⴜ',
+  'ⴝ' => 'Ⴝ',
+  'ⴞ' => 'Ⴞ',
+  'â´Ÿ' => 'á‚¿',
+  'ⴠ' => 'Ⴠ',
+  'ⴡ' => 'Ⴡ',
+  'ⴢ' => 'Ⴢ',
+  'ⴣ' => 'Ⴣ',
+  'ⴤ' => 'Ⴤ',
+  'ⴥ' => 'Ⴥ',
+  'ⴧ' => 'Ⴧ',
+  'ⴭ' => 'Ⴭ',
+  'ꙁ' => 'Ꙁ',
+  'ꙃ' => 'Ꙃ',
+  'ꙅ' => 'Ꙅ',
+  'ꙇ' => 'Ꙇ',
+  'ꙉ' => 'Ꙉ',
+  'ꙋ' => 'Ꙋ',
+  'ꙍ' => 'Ꙍ',
+  'ꙏ' => 'Ꙏ',
+  'ꙑ' => 'Ꙑ',
+  'ꙓ' => 'Ꙓ',
+  'ꙕ' => 'Ꙕ',
+  'ê™—' => 'ê™–',
+  'ꙙ' => 'Ꙙ',
+  'ꙛ' => 'Ꙛ',
+  'ꙝ' => 'Ꙝ',
+  'ꙟ' => 'Ꙟ',
+  'ꙡ' => 'Ꙡ',
+  'ꙣ' => 'Ꙣ',
+  'ꙥ' => 'Ꙥ',
+  'ꙧ' => 'Ꙧ',
+  'ꙩ' => 'Ꙩ',
+  'ꙫ' => 'Ꙫ',
+  'ꙭ' => 'Ꙭ',
+  'ꚁ' => 'Ꚁ',
+  'ꚃ' => 'Ꚃ',
+  'êš…' => 'êš„',
+  'ꚇ' => 'Ꚇ',
+  'ꚉ' => 'Ꚉ',
+  'ꚋ' => 'Ꚋ',
+  'ꚍ' => 'Ꚍ',
+  'ꚏ' => 'Ꚏ',
+  'ꚑ' => 'Ꚑ',
+  'êš“' => 'êš’',
+  'êš•' => 'êš”',
+  'êš—' => 'êš–',
+  'ꚙ' => 'Ꚙ',
+  'êš›' => 'êšš',
+  'ꜣ' => 'Ꜣ',
+  'ꜥ' => 'Ꜥ',
+  'ꜧ' => 'Ꜧ',
+  'ꜩ' => 'Ꜩ',
+  'ꜫ' => 'Ꜫ',
+  'ꜭ' => 'Ꜭ',
+  'ꜯ' => 'Ꜯ',
+  'ꜳ' => 'Ꜳ',
+  'ꜵ' => 'Ꜵ',
+  'ꜷ' => 'Ꜷ',
+  'ꜹ' => 'Ꜹ',
+  'ꜻ' => 'Ꜻ',
+  'ꜽ' => 'Ꜽ',
+  'ꜿ' => 'Ꜿ',
+  'ꝁ' => 'Ꝁ',
+  'ꝃ' => 'Ꝃ',
+  'ꝅ' => 'Ꝅ',
+  'ꝇ' => 'Ꝇ',
+  'ꝉ' => 'Ꝉ',
+  'ꝋ' => 'Ꝋ',
+  'ꝍ' => 'Ꝍ',
+  'ꝏ' => 'Ꝏ',
+  'ꝑ' => 'Ꝑ',
+  'ꝓ' => 'Ꝓ',
+  'ꝕ' => 'Ꝕ',
+  'ꝗ' => 'Ꝗ',
+  'ꝙ' => 'Ꝙ',
+  'ꝛ' => 'Ꝛ',
+  'ꝝ' => 'Ꝝ',
+  'ꝟ' => 'Ꝟ',
+  'ꝡ' => 'Ꝡ',
+  'ꝣ' => 'Ꝣ',
+  'ꝥ' => 'Ꝥ',
+  'ꝧ' => 'Ꝧ',
+  'ꝩ' => 'Ꝩ',
+  'ꝫ' => 'Ꝫ',
+  'ꝭ' => 'Ꝭ',
+  'ꝯ' => 'Ꝯ',
+  'ꝺ' => 'Ꝺ',
+  'ꝼ' => 'Ꝼ',
+  'ꝿ' => 'Ꝿ',
+  'ꞁ' => 'Ꞁ',
+  'ꞃ' => 'Ꞃ',
+  'êž…' => 'êž„',
+  'ꞇ' => 'Ꞇ',
+  'ꞌ' => 'Ꞌ',
+  'ꞑ' => 'Ꞑ',
+  'êž“' => 'êž’',
+  'êž—' => 'êž–',
+  'ꞙ' => 'Ꞙ',
+  'êž›' => 'êžš',
+  'ꞝ' => 'Ꞝ',
+  'ꞟ' => 'Ꞟ',
+  'êž¡' => 'êž ',
+  'ꞣ' => 'Ꞣ',
+  'ꞥ' => 'Ꞥ',
+  'ꞧ' => 'Ꞧ',
+  'ꞩ' => 'Ꞩ',
+  'a' => 'A',
+  'b' => 'B',
+  'c' => 'C',
+  'd' => 'D',
+  'ï½…' => 'ï¼¥',
+  'f' => 'F',
+  'g' => 'G',
+  'h' => 'H',
+  'i' => 'I',
+  'j' => 'J',
+  'k' => 'K',
+  'l' => 'L',
+  'm' => 'M',
+  'n' => 'N',
+  'o' => 'O',
+  'p' => 'P',
+  'q' => 'Q',
+  'ï½’' => 'ï¼²',
+  's' => 'S',
+  'ï½”' => 'ï¼´',
+  'u' => 'U',
+  'ï½–' => 'ï¼¶',
+  'ï½—' => 'ï¼·',
+  'x' => 'X',
+  'ï½™' => 'ï¼¹',
+  'z' => 'Z',
+  '𐐨' => '𐐀',
+  '𐐩' => '𐐁',
+  '𐐪' => '𐐂',
+  '𐐫' => '𐐃',
+  '𐐬' => '𐐄',
+  '𐐭' => '𐐅',
+  '𐐮' => '𐐆',
+  '𐐯' => '𐐇',
+  '𐐰' => '𐐈',
+  '𐐱' => '𐐉',
+  '𐐲' => '𐐊',
+  '𐐳' => '𐐋',
+  '𐐴' => '𐐌',
+  '𐐵' => '𐐍',
+  '𐐶' => '𐐎',
+  '𐐷' => '𐐏',
+  '𐐸' => '𐐐',
+  '𐐹' => '𐐑',
+  '𐐺' => '𐐒',
+  '𐐻' => '𐐓',
+  '𐐼' => '𐐔',
+  '𐐽' => '𐐕',
+  '𐐾' => '𐐖',
+  '𐐿' => '𐐗',
+  '𐑀' => '𐐘',
+  '𐑁' => '𐐙',
+  '𐑂' => '𐐚',
+  '𐑃' => '𐐛',
+  '𐑄' => '𐐜',
+  '𐑅' => '𐐝',
+  '𐑆' => '𐐞',
+  '𐑇' => '𐐟',
+  '𐑈' => '𐐠',
+  '𐑉' => '𐐡',
+  '𐑊' => '𐐢',
+  '𐑋' => '𐐣',
+  '𐑌' => '𐐤',
+  '𐑍' => '𐐥',
+  '𐑎' => '𐐦',
+  '𐑏' => '𐐧',
+  'ð‘£€' => 'ð‘¢ ',
+  '𑣁' => '𑢡',
+  '𑣂' => '𑢢',
+  '𑣃' => '𑢣',
+  '𑣄' => '𑢤',
+  'ð‘£…' => 'ð‘¢¥',
+  '𑣆' => '𑢦',
+  '𑣇' => '𑢧',
+  '𑣈' => '𑢨',
+  '𑣉' => '𑢩',
+  '𑣊' => '𑢪',
+  '𑣋' => '𑢫',
+  '𑣌' => '𑢬',
+  '𑣍' => '𑢭',
+  '𑣎' => '𑢮',
+  '𑣏' => '𑢯',
+  '𑣐' => '𑢰',
+  '𑣑' => '𑢱',
+  'ð‘£’' => 'ð‘¢²',
+  '𑣓' => '𑢳',
+  'ð‘£”' => 'ð‘¢´',
+  '𑣕' => '𑢵',
+  'ð‘£–' => 'ð‘¢¶',
+  'ð‘£—' => 'ð‘¢·',
+  '𑣘' => '𑢸',
+  'ð‘£™' => 'ð‘¢¹',
+  '𑣚' => '𑢺',
+  'ð‘£›' => 'ð‘¢»',
+  '𑣜' => '𑢼',
+  '𑣝' => '𑢽',
+  '𑣞' => '𑢾',
+  '𑣟' => '𑢿',
+);
+
+$result =& $data;
+unset($data);
+
+return $result;
diff --git a/vendor/symfony/polyfill-mbstring/bootstrap.php b/vendor/symfony/polyfill-mbstring/bootstrap.php
new file mode 100644
index 0000000000000000000000000000000000000000..2fdcc5a6f69ceb58f3e948cac4d99bb20edb4fbf
--- /dev/null
+++ b/vendor/symfony/polyfill-mbstring/bootstrap.php
@@ -0,0 +1,58 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Polyfill\Mbstring as p;
+
+if (!function_exists('mb_strlen')) {
+    define('MB_CASE_UPPER', 0);
+    define('MB_CASE_LOWER', 1);
+    define('MB_CASE_TITLE', 2);
+
+    function mb_convert_encoding($s, $to, $from = null) { return p\Mbstring::mb_convert_encoding($s, $to, $from); }
+    function mb_decode_mimeheader($s) { return p\Mbstring::mb_decode_mimeheader($s); }
+    function mb_encode_mimeheader($s, $charset = null, $transferEnc = null, $lf = null, $indent = null) { return p\Mbstring::mb_encode_mimeheader($s, $charset, $transferEnc, $lf, $indent); }
+    function mb_decode_numericentity($s, $convmap, $enc = null) { return p\Mbstring::mb_decode_numericentity($s, $convmap, $enc); }
+    function mb_encode_numericentity($s, $convmap, $enc = null, $is_hex = false) { return p\Mbstring::mb_encode_numericentity($s, $convmap, $enc, $is_hex); }
+    function mb_convert_case($s, $mode, $enc = null) { return p\Mbstring::mb_convert_case($s, $mode, $enc); }
+    function mb_internal_encoding($enc = null) { return p\Mbstring::mb_internal_encoding($enc); }
+    function mb_language($lang = null) { return p\Mbstring::mb_language($lang); }
+    function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); }
+    function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); }
+    function mb_check_encoding($var = null, $encoding = null) { return p\Mbstring::mb_check_encoding($var, $encoding); }
+    function mb_detect_encoding($str, $encodingList = null, $strict = false) { return p\Mbstring::mb_detect_encoding($str, $encodingList, $strict); }
+    function mb_detect_order($encodingList = null) { return p\Mbstring::mb_detect_order($encodingList); }
+    function mb_parse_str($s, &$result = array()) { parse_str($s, $result); }
+    function mb_strlen($s, $enc = null) { return p\Mbstring::mb_strlen($s, $enc); }
+    function mb_strpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strpos($s, $needle, $offset, $enc); }
+    function mb_strtolower($s, $enc = null) { return p\Mbstring::mb_strtolower($s, $enc); }
+    function mb_strtoupper($s, $enc = null) { return p\Mbstring::mb_strtoupper($s, $enc); }
+    function mb_substitute_character($char = null) { return p\Mbstring::mb_substitute_character($char); }
+    function mb_substr($s, $start, $length = 2147483647, $enc = null) { return p\Mbstring::mb_substr($s, $start, $length, $enc); }
+    function mb_stripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_stripos($s, $needle, $offset, $enc); }
+    function mb_stristr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_stristr($s, $needle, $part, $enc); }
+    function mb_strrchr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrchr($s, $needle, $part, $enc); }
+    function mb_strrichr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrichr($s, $needle, $part, $enc); }
+    function mb_strripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strripos($s, $needle, $offset, $enc); }
+    function mb_strrpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strrpos($s, $needle, $offset, $enc); }
+    function mb_strstr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strstr($s, $needle, $part, $enc); }
+    function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); }
+    function mb_http_output($enc = null) { return p\Mbstring::mb_http_output($enc); }
+    function mb_strwidth($s, $enc = null) { return p\Mbstring::mb_strwidth($s, $enc); }
+    function mb_substr_count($haystack, $needle, $enc = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $enc); }
+    function mb_output_handler($contents, $status) { return p\Mbstring::mb_output_handler($contents, $status); }
+    function mb_http_input($type = '') { return p\Mbstring::mb_http_input($type); }
+    function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null) { return p\Mbstring::mb_convert_variables($toEncoding, $fromEncoding, $a, $b, $c, $d, $e, $f); }
+}
+if (!function_exists('mb_chr')) {
+    function mb_ord($s, $enc = null) { return p\Mbstring::mb_ord($s, $enc); }
+    function mb_chr($code, $enc = null) { return p\Mbstring::mb_chr($code, $enc); }
+    function mb_scrub($s, $enc = null) { $enc = null === $enc ? mb_internal_encoding() : $enc; return mb_convert_encoding($s, $enc, $enc); }
+}
diff --git a/vendor/symfony/polyfill-mbstring/composer.json b/vendor/symfony/polyfill-mbstring/composer.json
new file mode 100644
index 0000000000000000000000000000000000000000..4febcdd5c0051eb3b0dc1cd71114f7b27eee0e16
--- /dev/null
+++ b/vendor/symfony/polyfill-mbstring/composer.json
@@ -0,0 +1,34 @@
+{
+    "name": "symfony/polyfill-mbstring",
+    "type": "library",
+    "description": "Symfony polyfill for the Mbstring extension",
+    "keywords": ["polyfill", "shim", "compatibility", "portable", "mbstring"],
+    "homepage": "https://symfony.com",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Nicolas Grekas",
+            "email": "p@tchwork.com"
+        },
+        {
+            "name": "Symfony Community",
+            "homepage": "https://symfony.com/contributors"
+        }
+    ],
+    "require": {
+        "php": ">=5.3.3"
+    },
+    "autoload": {
+        "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" },
+        "files": [ "bootstrap.php" ]
+    },
+    "suggest": {
+        "ext-mbstring": "For best performance"
+    },
+    "minimum-stability": "dev",
+    "extra": {
+        "branch-alias": {
+            "dev-master": "1.7-dev"
+        }
+    }
+}
diff --git a/vendor/symfony/polyfill-php70/LICENSE b/vendor/symfony/polyfill-php70/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..24fa32c2e9b27aef3eac523f0042b8ab9ba81944
--- /dev/null
+++ b/vendor/symfony/polyfill-php70/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015-2018 Fabien Potencier
+
+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/vendor/symfony/polyfill-php70/Php70.php b/vendor/symfony/polyfill-php70/Php70.php
new file mode 100644
index 0000000000000000000000000000000000000000..8e7845066399b456741bba723f56ecfeb99ccae8
--- /dev/null
+++ b/vendor/symfony/polyfill-php70/Php70.php
@@ -0,0 +1,74 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Polyfill\Php70;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @internal
+ */
+final class Php70
+{
+    public static function intdiv($dividend, $divisor)
+    {
+        $dividend = self::intArg($dividend, __FUNCTION__, 1);
+        $divisor = self::intArg($divisor, __FUNCTION__, 2);
+
+        if (0 === $divisor) {
+            throw new \DivisionByZeroError('Division by zero');
+        }
+        if (-1 === $divisor && ~PHP_INT_MAX === $dividend) {
+            throw new \ArithmeticError('Division of PHP_INT_MIN by -1 is not an integer');
+        }
+
+        return ($dividend - ($dividend % $divisor)) / $divisor;
+    }
+
+    public static function preg_replace_callback_array(array $patterns, $subject, $limit = -1, &$count = 0)
+    {
+        $count = 0;
+        $result = (string) $subject;
+        if (0 === $limit = self::intArg($limit, __FUNCTION__, 3)) {
+            return $result;
+        }
+
+        foreach ($patterns as $pattern => $callback) {
+            $result = preg_replace_callback($pattern, $callback, $result, $limit, $c);
+            $count += $c;
+        }
+
+        return $result;
+    }
+
+    public static function error_clear_last()
+    {
+        static $handler;
+        if (!$handler) {
+             $handler = function() { return false; };
+        }
+        set_error_handler($handler);
+        @trigger_error('');
+        restore_error_handler();
+    }
+
+    public static function intArg($value, $caller, $pos)
+    {
+        if (is_int($value)) {
+            return $value;
+        }
+        if (!is_numeric($value) || PHP_INT_MAX <= ($value += 0) || ~PHP_INT_MAX >= $value) {
+            throw new \TypeError(sprintf('%s() expects parameter %d to be integer, %s given', $caller, $pos, gettype($value)));
+        }
+
+        return (int) $value;
+    }
+}
diff --git a/vendor/symfony/polyfill-php70/README.md b/vendor/symfony/polyfill-php70/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..04988c6f93571afa9b2d2a32e8701043441ce26c
--- /dev/null
+++ b/vendor/symfony/polyfill-php70/README.md
@@ -0,0 +1,28 @@
+Symfony Polyfill / Php70
+========================
+
+This component provides features unavailable in releases prior to PHP 7.0:
+
+- [`intdiv`](http://php.net/intdiv)
+- [`preg_replace_callback_array`](http://php.net/preg_replace_callback_array)
+- [`error_clear_last`](http://php.net/error_clear_last)
+- `random_bytes` and `random_int` (from [paragonie/random_compat](https://github.com/paragonie/random_compat))
+- [`*Error` throwable classes](http://php.net/Error)
+- [`PHP_INT_MIN`](http://php.net/manual/en/reserved.constants.php#constant.php-int-min)
+- `SessionUpdateTimestampHandlerInterface`
+
+More information can be found in the
+[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md).
+
+Compatibility notes
+===================
+
+To write portable code between PHP5 and PHP7, some care must be taken:
+- `\*Error` exceptions must be caught before `\Exception`;
+- after calling `error_clear_last()`, the result of `$e = error_get_last()` must be
+  verified using `isset($e['message'][0])` instead of `null !== $e`.
+
+License
+=======
+
+This library is released under the [MIT license](LICENSE).
diff --git a/vendor/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php b/vendor/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php
new file mode 100644
index 0000000000000000000000000000000000000000..68191244625989ccc21e579a4b321b5e79d6660c
--- /dev/null
+++ b/vendor/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php
@@ -0,0 +1,5 @@
+<?php
+
+class ArithmeticError extends Error
+{
+}
diff --git a/vendor/symfony/polyfill-php70/Resources/stubs/AssertionError.php b/vendor/symfony/polyfill-php70/Resources/stubs/AssertionError.php
new file mode 100644
index 0000000000000000000000000000000000000000..acb125080f798881790a72e505a9bf6d5aee5800
--- /dev/null
+++ b/vendor/symfony/polyfill-php70/Resources/stubs/AssertionError.php
@@ -0,0 +1,5 @@
+<?php
+
+class AssertionError extends Error
+{
+}
diff --git a/vendor/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php b/vendor/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php
new file mode 100644
index 0000000000000000000000000000000000000000..c99278b31d71290acf06f01c9dbe814756c5d68f
--- /dev/null
+++ b/vendor/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php
@@ -0,0 +1,5 @@
+<?php
+
+class DivisionByZeroError extends Error
+{
+}
diff --git a/vendor/symfony/polyfill-php70/Resources/stubs/Error.php b/vendor/symfony/polyfill-php70/Resources/stubs/Error.php
new file mode 100644
index 0000000000000000000000000000000000000000..405847fb891b72772f66dcca131cfece6709b1f2
--- /dev/null
+++ b/vendor/symfony/polyfill-php70/Resources/stubs/Error.php
@@ -0,0 +1,5 @@
+<?php
+
+class Error extends Exception
+{
+}
diff --git a/vendor/symfony/polyfill-php70/Resources/stubs/ParseError.php b/vendor/symfony/polyfill-php70/Resources/stubs/ParseError.php
new file mode 100644
index 0000000000000000000000000000000000000000..2dd447dd4314a6da483be9763cf9ab99aa2787a5
--- /dev/null
+++ b/vendor/symfony/polyfill-php70/Resources/stubs/ParseError.php
@@ -0,0 +1,5 @@
+<?php
+
+class ParseError extends Error
+{
+}
diff --git a/vendor/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php b/vendor/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..0cc02c8f92c35687fac53151d6eb6729bada1779
--- /dev/null
+++ b/vendor/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php
@@ -0,0 +1,23 @@
+<?php
+
+interface SessionUpdateTimestampHandlerInterface
+{
+    /**
+     * Checks if a session identifier already exists or not.
+     *
+     * @param string $key
+     *
+     * @return bool
+     */
+    public function validateId($key);
+
+    /**
+     * Updates the timestamp of a session when its data didn't change.
+     *
+     * @param string $key
+     * @param string $val
+     *
+     * @return bool
+     */
+    public function updateTimestamp($key, $val);
+}
diff --git a/vendor/symfony/polyfill-php70/Resources/stubs/TypeError.php b/vendor/symfony/polyfill-php70/Resources/stubs/TypeError.php
new file mode 100644
index 0000000000000000000000000000000000000000..2bed1b48317528da308a71512e4191c8d3c33f0b
--- /dev/null
+++ b/vendor/symfony/polyfill-php70/Resources/stubs/TypeError.php
@@ -0,0 +1,5 @@
+<?php
+
+class TypeError extends Error
+{
+}
diff --git a/vendor/symfony/polyfill-php70/bootstrap.php b/vendor/symfony/polyfill-php70/bootstrap.php
new file mode 100644
index 0000000000000000000000000000000000000000..445c39839c4f7dd4bcc4e942dd17f18d6fe36199
--- /dev/null
+++ b/vendor/symfony/polyfill-php70/bootstrap.php
@@ -0,0 +1,27 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Polyfill\Php70 as p;
+
+if (PHP_VERSION_ID < 70000) {
+    if (!defined('PHP_INT_MIN')) {
+        define('PHP_INT_MIN', ~PHP_INT_MAX);
+    }
+    if (!function_exists('intdiv')) {
+        function intdiv($dividend, $divisor) { return p\Php70::intdiv($dividend, $divisor); }
+    }
+    if (!function_exists('preg_replace_callback_array')) {
+        function preg_replace_callback_array(array $patterns, $subject, $limit = -1, &$count = 0) { return p\Php70::preg_replace_callback_array($patterns, $subject, $limit, $count); }
+    }
+    if (!function_exists('error_clear_last')) {
+        function error_clear_last() { return p\Php70::error_clear_last(); }
+    }
+}
diff --git a/vendor/symfony/polyfill-php70/composer.json b/vendor/symfony/polyfill-php70/composer.json
new file mode 100644
index 0000000000000000000000000000000000000000..88ff357a56b0591146dad2233ef4717de236c275
--- /dev/null
+++ b/vendor/symfony/polyfill-php70/composer.json
@@ -0,0 +1,33 @@
+{
+    "name": "symfony/polyfill-php70",
+    "type": "library",
+    "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions",
+    "keywords": ["polyfill", "shim", "compatibility", "portable"],
+    "homepage": "https://symfony.com",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Nicolas Grekas",
+            "email": "p@tchwork.com"
+        },
+        {
+            "name": "Symfony Community",
+            "homepage": "https://symfony.com/contributors"
+        }
+    ],
+    "require": {
+        "php": ">=5.3.3",
+        "paragonie/random_compat": "~1.0|~2.0"
+    },
+    "autoload": {
+        "psr-4": { "Symfony\\Polyfill\\Php70\\": "" },
+        "files": [ "bootstrap.php" ],
+        "classmap": [ "Resources/stubs" ]
+    },
+    "minimum-stability": "dev",
+    "extra": {
+        "branch-alias": {
+            "dev-master": "1.7-dev"
+        }
+    }
+}